From 51add6219aeb647a01533f717bd7e7d86cd01fdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Qui=C3=B1ones?= Date: Wed, 15 Apr 2026 14:34:29 -0300 Subject: [PATCH 1/4] Add project setting to skip the sokobans The Sokobans may be repurposed in the future. For now, add a setting so developers can skip them with a debug setting, in project.godot or in an override.cfg local file. In the settings addon, add a class name so scripts can reference the settings by the constant. For that, move the "threadbare/" prefix to the constants and add an assertion for the prefix. Also move the comment to hint strings. --- .../threadbare_project_settings.gd | 18 +++++++++++++----- .../eternal_loom/components/eternal_loom.gd | 12 +++++++----- .../aspect_ratio_debugger.gd | 2 +- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/addons/threadbare_project_settings/threadbare_project_settings.gd b/addons/threadbare_project_settings/threadbare_project_settings.gd index 34f9b83ac5..b3bc3ecefe 100644 --- a/addons/threadbare_project_settings/threadbare_project_settings.gd +++ b/addons/threadbare_project_settings/threadbare_project_settings.gd @@ -1,16 +1,24 @@ # SPDX-FileCopyrightText: The Threadbare Authors # SPDX-License-Identifier: MPL-2.0 @tool +class_name ThreadbareProjectSettings extends EditorPlugin -## Debug aspect ratio while playing the game. -const DEBUG_ASPECT_RATIO = "debugging/debug_aspect_ratio" +const DEBUG_ASPECT_RATIO = "threadbare/debugging/debug_aspect_ratio" +const SKIP_SOKOBANS = "threadbare/debugging/skip_sokobans" static var setttings_configuration = { DEBUG_ASPECT_RATIO: { value = false, type = TYPE_BOOL, + hint_string = "Display a letterbox overlay in the game, to debug aspect ratio issues.", + }, + SKIP_SOKOBANS: + { + value = false, + type = TYPE_BOOL, + hint_string = "Skip the sokobans from the core game loop, and complete the quest directly.", }, } @@ -20,9 +28,9 @@ func _enter_tree() -> void: static func setup_threadbare_settings() -> void: - for key: String in setttings_configuration: - var setting_config: Dictionary = setttings_configuration[key] - var setting_name: String = "threadbare/%s" % key + for setting_name: String in setttings_configuration: + var setting_config: Dictionary = setttings_configuration[setting_name] + assert(setting_name.begins_with("threadbare")) if not ProjectSettings.has_setting(setting_name): ProjectSettings.set_setting(setting_name, setting_config.value) diff --git a/scenes/game_elements/props/eternal_loom/components/eternal_loom.gd b/scenes/game_elements/props/eternal_loom/components/eternal_loom.gd index f67306ca60..1e1df9ed50 100644 --- a/scenes/game_elements/props/eternal_loom/components/eternal_loom.gd +++ b/scenes/game_elements/props/eternal_loom/components/eternal_loom.gd @@ -56,11 +56,13 @@ func _find_elder(quest: Quest) -> Elder: func _on_interaction_ended() -> void: if _have_threads: - # Hide interact label during scene transition - interact_area.disabled = true - - GameState.set_incorporating_threads(true) - SceneSwitcher.change_to_file_with_transition(SOKOBANS.pick_random()) + if not ProjectSettings.get_setting(ThreadbareProjectSettings.SKIP_SOKOBANS): + # Hide interact label during scene transition + interact_area.disabled = true + GameState.set_incorporating_threads(true) + SceneSwitcher.change_to_file_with_transition(SOKOBANS.pick_random()) + else: + GameState.mark_quest_completed() func on_offering_succeeded() -> void: diff --git a/scenes/globals/aspect_ratio_debugger/aspect_ratio_debugger.gd b/scenes/globals/aspect_ratio_debugger/aspect_ratio_debugger.gd index 23da106ec3..2257d22aa1 100644 --- a/scenes/globals/aspect_ratio_debugger/aspect_ratio_debugger.gd +++ b/scenes/globals/aspect_ratio_debugger/aspect_ratio_debugger.gd @@ -4,5 +4,5 @@ extends CanvasLayer func _ready() -> void: - if not ProjectSettings.get_setting("threadbare/debugging/debug_aspect_ratio"): + if not ProjectSettings.get_setting(ThreadbareProjectSettings.DEBUG_ASPECT_RATIO): queue_free() From f6bbcfcd1d386f3434e76e168e5d5bb6f496f7e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Qui=C3=B1ones?= Date: Fri, 17 Apr 2026 18:51:38 -0300 Subject: [PATCH 2/4] Add project settings to skip the spash + title screens And directly resume the persisted state. When developing, it becomes a bit tedious to reach the persisted state by pressing keys and waiting for the transitions to happen. And starting the game from the main scene (splash) is the only way to debug saved state. --- .../threadbare_project_settings.gd | 8 ++++++++ scenes/menus/splash/components/splash.gd | 3 +++ scenes/menus/title/components/title_screen.gd | 12 ++++++++++++ 3 files changed, 23 insertions(+) diff --git a/addons/threadbare_project_settings/threadbare_project_settings.gd b/addons/threadbare_project_settings/threadbare_project_settings.gd index b3bc3ecefe..5eb1f140bd 100644 --- a/addons/threadbare_project_settings/threadbare_project_settings.gd +++ b/addons/threadbare_project_settings/threadbare_project_settings.gd @@ -6,6 +6,7 @@ extends EditorPlugin const DEBUG_ASPECT_RATIO = "threadbare/debugging/debug_aspect_ratio" const SKIP_SOKOBANS = "threadbare/debugging/skip_sokobans" +const SKIP_SPLASH = "threadbare/debugging/skip_splash" static var setttings_configuration = { DEBUG_ASPECT_RATIO: @@ -20,6 +21,13 @@ static var setttings_configuration = { type = TYPE_BOOL, hint_string = "Skip the sokobans from the core game loop, and complete the quest directly.", }, + SKIP_SPLASH: + { + value = false, + type = TYPE_BOOL, + hint_string = + "Skip the splash screen and title menu, and resume the game state. Like when clicking Continue.", + }, } diff --git a/scenes/menus/splash/components/splash.gd b/scenes/menus/splash/components/splash.gd index 977f3eb034..35c2a8a1bc 100644 --- a/scenes/menus/splash/components/splash.gd +++ b/scenes/menus/splash/components/splash.gd @@ -9,6 +9,9 @@ extends Control func _ready() -> void: + if ProjectSettings.get_setting(ThreadbareProjectSettings.SKIP_SPLASH): + SceneSwitcher.change_to_file(next_scene) + return logo_stitcher.finished.connect(scene_switch_timer.start) scene_switch_timer.timeout.connect(switch_to_intro) diff --git a/scenes/menus/title/components/title_screen.gd b/scenes/menus/title/components/title_screen.gd index dfb2533736..43df13a288 100644 --- a/scenes/menus/title/components/title_screen.gd +++ b/scenes/menus/title/components/title_screen.gd @@ -9,6 +9,18 @@ extends Control @onready var credits: Control = %Credits +func _ready() -> void: + if ProjectSettings.get_setting(ThreadbareProjectSettings.SKIP_SPLASH): + var saved_scene: Dictionary = GameState.restore() + ( + SceneSwitcher + . change_to_file( + saved_scene["scene_path"], + saved_scene["spawn_point"], + ) + ) + + func _input(event: InputEvent) -> void: if event.is_action_pressed(&"pause"): get_viewport().set_input_as_handled() From 17fa331eb82ed8924b36b8d15510380bf3c02646 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Qui=C3=B1ones?= Date: Fri, 17 Apr 2026 19:16:37 -0300 Subject: [PATCH 3/4] CharacterRandomizer: Use own random number generator Avoid changing the seed of the base random number generator, which makes the rest of the game predictable. Instead, using its own RNG in this class. Pass the RNG as argument to the CelShadingRecolor and RandomTextureSpriteBehavior, which now accept one. This way, the character is consistent with its character_seed. Luckly, characters randomized this way stay the same, because textures and skin colors are immediately called with the same seed as before. Fix https://github.com/endlessm/threadbare/issues/2130 --- .../characters/components/character_randomizer.gd | 11 +++++++---- .../game_elements/components/cel_shading_recolor.gd | 6 ++++-- .../random_texture_sprite_behavior.gd | 7 +++++-- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/scenes/game_elements/characters/components/character_randomizer.gd b/scenes/game_elements/characters/components/character_randomizer.gd index 8a8eca8d1d..aeaec9996e 100644 --- a/scenes/game_elements/characters/components/character_randomizer.gd +++ b/scenes/game_elements/characters/components/character_randomizer.gd @@ -1,7 +1,8 @@ # SPDX-FileCopyrightText: The Threadbare Authors # SPDX-License-Identifier: MPL-2.0 @tool -extends Node2D +class_name CharacterRandomizer +extends CharacterBody2D ## @experimental ## ## Provide a single button to randomize various aspects of a character. @@ -41,6 +42,8 @@ var random_texture_nodes: Array[RandomTextureSpriteBehavior] = [] var _undoredo: Object # EditorUndoRedoManager +var _random_number_generator := RandomNumberGenerator.new() + var _previous_look_at_side: Enums.LookAtSide = Enums.LookAtSide.UNSPECIFIED @@ -49,18 +52,18 @@ var _previous_look_at_side: Enums.LookAtSide = Enums.LookAtSide.UNSPECIFIED ## Do it in a consistent way by first seeding the default random number generator ## with the [member character_seed]. func apply_character_randomizations() -> void: - seed(character_seed) + _random_number_generator.seed = character_seed if skin_recolor_nodes: var new_skin_medium_color: Color - skin_recolor_nodes[-1].set_random_skin_color() + skin_recolor_nodes[-1].set_random_skin_color(_random_number_generator) new_skin_medium_color = skin_recolor_nodes[-1].medium_color for n in skin_recolor_nodes: n.automatic_shades = true n.medium_color = new_skin_medium_color for n in random_texture_nodes: - n.randomize_texture() + n.randomize_texture(_random_number_generator) ## Set a random seed and randomize the character. diff --git a/scenes/game_elements/components/cel_shading_recolor.gd b/scenes/game_elements/components/cel_shading_recolor.gd index 0de77f578b..91bff0ad51 100644 --- a/scenes/game_elements/components/cel_shading_recolor.gd +++ b/scenes/game_elements/components/cel_shading_recolor.gd @@ -159,9 +159,11 @@ func colorize() -> void: ## Pick a random color from [constant SKIN_COLORS] and automatically set all shades from it. -func set_random_skin_color() -> void: +func set_random_skin_color(rng: RandomNumberGenerator = null) -> void: automatic_shades = true - medium_color = SKIN_COLORS.values().pick_random() + var random_int: int = rng.randi() if rng else randi() + var index := random_int % SKIN_COLORS.size() + medium_color = SKIN_COLORS.values()[index] func _get_configuration_warnings() -> PackedStringArray: diff --git a/scenes/game_logic/sprite_behaviors/random_texture_sprite_behavior.gd b/scenes/game_logic/sprite_behaviors/random_texture_sprite_behavior.gd index 2fb6d23749..b234551967 100644 --- a/scenes/game_logic/sprite_behaviors/random_texture_sprite_behavior.gd +++ b/scenes/game_logic/sprite_behaviors/random_texture_sprite_behavior.gd @@ -45,8 +45,11 @@ func _offset_child_sprites(offset: Vector2) -> void: ## Pick a random texture from [member textures] and set it to the sprite. -func randomize_texture() -> void: - var new_texture: Texture2D = textures.pick_random() +func randomize_texture(rng: RandomNumberGenerator = null) -> void: + var random_int: int = rng.randi() if rng else randi() + var index := random_int % textures.size() + var new_texture := textures[index] + var offset := _get_offset_from_texture_filename(new_texture) _offset_child_sprites(offset) SpriteFramesHelper.replace_texture(null, new_texture, sprite.sprite_frames) From 0948ae650600ec56dedce00fcad9196d19bd3414 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Qui=C3=B1ones?= Date: Fri, 17 Apr 2026 21:16:10 -0300 Subject: [PATCH 4/4] Add retelling to Fray's End --- project.godot | 5 ++ .../npcs/components/default_helper.dialogue | 7 ++ .../components/default_helper.dialogue.import | 16 ++++ .../npcs/components/helper_character.gd | 22 +++++ .../npcs/components/helper_character.gd.uid | 1 + .../eternal_loom/components/eternal_loom.gd | 80 +++++++++++++++++- .../eternal_loom_interaction.dialogue | 4 + scenes/globals/game_state/game_state.gd | 35 ++++++++ .../game_state/inventory/inventory_item.gd | 1 + scenes/menus/storybook/components/quest.gd | 4 + .../quests/lore_quests/quest_001/quest.tres | 2 + .../quest_001/quest_001_retelling.dialogue | 28 +++++++ .../quest_001_retelling.dialogue.import | 16 ++++ .../quests/lore_quests/quest_002/quest.tres | 2 + .../quest_002/quest_002_retelling.dialogue | 28 +++++++ .../quest_002_retelling.dialogue.import | 16 ++++ .../components/story_quest_progress.gd | 32 +++++++- .../story_quest_progress.tscn | 20 ++++- scenes/world_map/components/frays_end.gd | 2 +- .../world_map/components/retelling_townies.gd | 42 ++++++++++ .../components/retelling_townies.gd.uid | 1 + scenes/world_map/frays_end.tscn | 26 ++++++ scenes/world_map/frays_end_west.tscn | 82 +++++++++++++++++++ 23 files changed, 468 insertions(+), 4 deletions(-) create mode 100644 scenes/game_elements/characters/npcs/components/default_helper.dialogue create mode 100644 scenes/game_elements/characters/npcs/components/default_helper.dialogue.import create mode 100644 scenes/game_elements/characters/npcs/components/helper_character.gd create mode 100644 scenes/game_elements/characters/npcs/components/helper_character.gd.uid create mode 100644 scenes/quests/lore_quests/quest_001/quest_001_retelling.dialogue create mode 100644 scenes/quests/lore_quests/quest_001/quest_001_retelling.dialogue.import create mode 100644 scenes/quests/lore_quests/quest_002/quest_002_retelling.dialogue create mode 100644 scenes/quests/lore_quests/quest_002/quest_002_retelling.dialogue.import create mode 100644 scenes/world_map/components/retelling_townies.gd create mode 100644 scenes/world_map/components/retelling_townies.gd.uid diff --git a/project.godot b/project.godot index 968d1fbb63..0e0922db7c 100644 --- a/project.godot +++ b/project.godot @@ -308,3 +308,8 @@ locale/translations_pot_files=PackedStringArray("res://scenes/menus/title/compon textures/canvas_textures/default_texture_filter=0 renderer/rendering_method="gl_compatibility" renderer/rendering_method.mobile="gl_compatibility" + +[threadbare] + +debugging/skip_sokobans=true +debugging/skip_splash=true diff --git a/scenes/game_elements/characters/npcs/components/default_helper.dialogue b/scenes/game_elements/characters/npcs/components/default_helper.dialogue new file mode 100644 index 0000000000..69d34cf01c --- /dev/null +++ b/scenes/game_elements/characters/npcs/components/default_helper.dialogue @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: The Threadbare Authors +# SPDX-License-Identifier: MPL-2.0 +~ start +Here's some help! +Bye! +do GameState.clear_upgrade() +=> END diff --git a/scenes/game_elements/characters/npcs/components/default_helper.dialogue.import b/scenes/game_elements/characters/npcs/components/default_helper.dialogue.import new file mode 100644 index 0000000000..57a43be5c4 --- /dev/null +++ b/scenes/game_elements/characters/npcs/components/default_helper.dialogue.import @@ -0,0 +1,16 @@ +[remap] + +importer="dialogue_manager" +importer_version=15 +type="Resource" +uid="uid://c667fv6ngpc7s" +path="res://.godot/imported/default_helper.dialogue-2d2de185b0afee791b6bd97e2398a0de.tres" + +[deps] + +source_file="res://scenes/game_elements/characters/npcs/components/default_helper.dialogue" +dest_files=["res://.godot/imported/default_helper.dialogue-2d2de185b0afee791b6bd97e2398a0de.tres"] + +[params] + +defaults=true diff --git a/scenes/game_elements/characters/npcs/components/helper_character.gd b/scenes/game_elements/characters/npcs/components/helper_character.gd new file mode 100644 index 0000000000..b8b58c08f6 --- /dev/null +++ b/scenes/game_elements/characters/npcs/components/helper_character.gd @@ -0,0 +1,22 @@ +# SPDX-FileCopyrightText: The Threadbare Authors +# SPDX-License-Identifier: MPL-2.0 +extends Node + +@export var target_upgrade: InventoryItem.ItemType = InventoryItem.ItemType.MEMORY +@onready var character: CharacterRandomizer = get_parent() + + +func _ready() -> void: + GameState.upgrade_changed.connect(_on_upgrade_changed) + _on_upgrade_changed() + + +func _on_upgrade_changed() -> void: + var is_enabled := ( + target_upgrade == GameState.upgrade_type and bool(GameState.upgrade_character_seed) + ) + character.visible = is_enabled + character.process_mode = Node.PROCESS_MODE_INHERIT if is_enabled else Node.PROCESS_MODE_DISABLED + if is_enabled: + character.character_seed = GameState.upgrade_character_seed + character.apply_character_randomizations() diff --git a/scenes/game_elements/characters/npcs/components/helper_character.gd.uid b/scenes/game_elements/characters/npcs/components/helper_character.gd.uid new file mode 100644 index 0000000000..9e04d6d5f7 --- /dev/null +++ b/scenes/game_elements/characters/npcs/components/helper_character.gd.uid @@ -0,0 +1 @@ +uid://diskln3jup064 diff --git a/scenes/game_elements/props/eternal_loom/components/eternal_loom.gd b/scenes/game_elements/props/eternal_loom/components/eternal_loom.gd index 1e1df9ed50..6ce50344d1 100644 --- a/scenes/game_elements/props/eternal_loom/components/eternal_loom.gd +++ b/scenes/game_elements/props/eternal_loom/components/eternal_loom.gd @@ -3,6 +3,10 @@ class_name EternalLoom extends Node2D +signal retelling_started +signal retelling_finished +signal give_retelling_upgrade(type: InventoryItem.ItemType) + const ETERNAL_LOOM_INTERACTION: DialogueResource = preload("uid://yafw7bf362gh") ## Scenes that are the first of three Sokoban puzzles. A random one will be used @@ -24,7 +28,7 @@ var _have_threads := is_item_offering_possible() func _ready() -> void: talk_behavior.dialogue = ETERNAL_LOOM_INTERACTION - talk_behavior.title = "have_threads" if _have_threads else "no_threads" + talk_behavior.before_dialogue = _before_dialogue interact_area.interaction_ended.connect(self._on_interaction_ended) if GameState.incorporating_threads: @@ -46,6 +50,16 @@ func _ready() -> void: GameState.mark_quest_completed() +func _before_dialogue() -> void: + if _have_threads: + if GameState.current_quest and GameState.current_quest.retelling: + talk_behavior.title = "start_retelling" + else: + talk_behavior.title = "have_threads" + else: + talk_behavior.title = "no_threads" + + func _find_elder(quest: Quest) -> Elder: for elder in elders: if quest.resource_path.begins_with(elder.quest_directory): @@ -56,6 +70,16 @@ func _find_elder(quest: Quest) -> Elder: func _on_interaction_ended() -> void: if _have_threads: + if GameState.current_quest and GameState.current_quest.retelling: + # Hide interact label during retelling + interact_area.disabled = true + DialogueManager.show_dialogue_balloon(GameState.current_quest.retelling, "", [self]) + retelling_started.emit() + await DialogueManager.dialogue_ended + await on_offering_succeeded() + interact_area.disabled = false + retelling_finished.emit() + if not ProjectSettings.get_setting(ThreadbareProjectSettings.SKIP_SOKOBANS): # Hide interact label during scene transition interact_area.disabled = true @@ -65,6 +89,60 @@ func _on_interaction_ended() -> void: GameState.mark_quest_completed() +func _has_magical_thread_of_type(type: InventoryItem.ItemType) -> bool: + for item in GameState.items_collected(): + if item.type == type: + return true + return false + + +func has_memory() -> bool: + return _has_magical_thread_of_type(InventoryItem.ItemType.MEMORY) + + +func has_imagination() -> bool: + return _has_magical_thread_of_type(InventoryItem.ItemType.IMAGINATION) + + +func has_spirit() -> bool: + return _has_magical_thread_of_type(InventoryItem.ItemType.SPIRIT) + + +func memory_text() -> String: + var has_it := _has_magical_thread_of_type(InventoryItem.ItemType.MEMORY) + return "(Memory available!)" if has_it else "" + + +func imagination_text() -> String: + var has_it := _has_magical_thread_of_type(InventoryItem.ItemType.IMAGINATION) + return "(Imagination available!)" if has_it else "" + + +func spirit_text() -> String: + var has_it := _has_magical_thread_of_type(InventoryItem.ItemType.SPIRIT) + return "(Spirit available!)" if has_it else "" + + +func _give_upgrade(type: InventoryItem.ItemType) -> void: + var has_it := _has_magical_thread_of_type(type) + if not has_it: + push_warning("Trying to give an upgrade for missing item type", type) + return + give_retelling_upgrade.emit(type) + + +func give_memory_upgrade() -> void: + _give_upgrade(InventoryItem.ItemType.MEMORY) + + +func give_imagination_upgrade() -> void: + _give_upgrade(InventoryItem.ItemType.IMAGINATION) + + +func give_spirit_upgrade() -> void: + _give_upgrade(InventoryItem.ItemType.SPIRIT) + + func on_offering_succeeded() -> void: loom_offering_animation_player.play(&"loom_offering") await loom_offering_animation_player.animation_finished diff --git a/scenes/game_elements/props/eternal_loom/components/eternal_loom_interaction.dialogue b/scenes/game_elements/props/eternal_loom/components/eternal_loom_interaction.dialogue index e5a13dd299..dbff28eb90 100644 --- a/scenes/game_elements/props/eternal_loom/components/eternal_loom_interaction.dialogue +++ b/scenes/game_elements/props/eternal_loom/components/eternal_loom_interaction.dialogue @@ -14,3 +14,7 @@ It seems you are lacking the threads of Memory, Imagination, and Spirit... try c The Memory, Imagination, and Spirit threads are ready to be incorporated into the loom! do on_offering_succeeded() => END + +~ start_retelling +Tell us a story about your last adventure! +=> END diff --git a/scenes/globals/game_state/game_state.gd b/scenes/globals/game_state/game_state.gd index 6f8472ddfe..db66f2ce10 100644 --- a/scenes/globals/game_state/game_state.gd +++ b/scenes/globals/game_state/game_state.gd @@ -29,6 +29,8 @@ signal completed_quests_changed ## Emitted when lore or StoryQuest player abilities change. signal abilities_changed +signal upgrade_changed + const GAME_STATE_PATH := "user://game_state.cfg" const INVENTORY_SECTION := "inventory" const INVENTORY_ITEMS_KEY := "items_collected" @@ -38,6 +40,8 @@ const QUEST_CHALLENGE_START_KEY := "challenge_start_scene" const QUEST_PLAYER_ABILITIES_KEY := "quest_player_abilities" const GLOBAL_SECTION := "global" const GLOBAL_INCORPORATING_THREADS_KEY := "incorporating_threads" +const GLOBAL_UPGRADE_TYPE_KEY := "upgrade_type" +const GLOBAL_UPGRADE_CHARACTER_SEED_KEY := "upgrade_character_seed" const COMPLETED_QUESTS_KEY := "completed_quests" const CURRENTSCENE_KEY := "current_scene" const SPAWNPOINT_KEY := "current_spawn_point" @@ -87,6 +91,9 @@ var current_lives: int = MAX_LIVES ## Current state of artificial lights. var lights_on: bool +var upgrade_type: InventoryItem.ItemType = InventoryItem.ItemType.NONE +var upgrade_character_seed: int = 0 + ## Set when the loom transports the player to a trio of Sokoban puzzles, so that ## when the player returns to Fray's End the loom can trigger a brief cutscene. var incorporating_threads: bool = false @@ -148,6 +155,28 @@ func _ready() -> void: prints("[LIVES DEBUG] GameState initialized with", current_lives, "lives") +func set_upgrade(new_upgrade_type: InventoryItem.ItemType, new_upgrade_character_seed: int) -> void: + upgrade_type = new_upgrade_type + upgrade_character_seed = new_upgrade_character_seed + upgrade_changed.emit() + + _state.set_value(GLOBAL_SECTION, GLOBAL_UPGRADE_TYPE_KEY, upgrade_type) + _state.set_value(GLOBAL_SECTION, GLOBAL_UPGRADE_CHARACTER_SEED_KEY, upgrade_character_seed) + _save() + + +func clear_upgrade() -> void: + upgrade_type = InventoryItem.ItemType.NONE + upgrade_character_seed = 0 + upgrade_changed.emit() + + if _state.has_section_key(GLOBAL_SECTION, GLOBAL_UPGRADE_TYPE_KEY): + _state.erase_section_key(GLOBAL_SECTION, GLOBAL_UPGRADE_TYPE_KEY) + if _state.has_section_key(GLOBAL_SECTION, GLOBAL_UPGRADE_CHARACTER_SEED_KEY): + _state.erase_section_key(GLOBAL_SECTION, GLOBAL_UPGRADE_CHARACTER_SEED_KEY) + _save() + + ## Set the [member incorporating_threads] flag. func set_incorporating_threads(new_incorporating_threads: bool) -> void: incorporating_threads = new_incorporating_threads @@ -484,6 +513,12 @@ func restore() -> Dictionary: var scene_path: String = _state.get_value(GLOBAL_SECTION, CURRENTSCENE_KEY, "") current_spawn_point = _state.get_value(GLOBAL_SECTION, SPAWNPOINT_KEY, ^"") + if _state.has_section_key(GLOBAL_SECTION, GLOBAL_UPGRADE_TYPE_KEY): + upgrade_type = _state.get_value(GLOBAL_SECTION, GLOBAL_UPGRADE_TYPE_KEY) + if _state.has_section_key(GLOBAL_SECTION, GLOBAL_UPGRADE_CHARACTER_SEED_KEY): + upgrade_character_seed = _state.get_value( + GLOBAL_SECTION, GLOBAL_UPGRADE_CHARACTER_SEED_KEY, 0 + ) incorporating_threads = _state.get_value( GLOBAL_SECTION, GLOBAL_INCORPORATING_THREADS_KEY, false ) diff --git a/scenes/globals/game_state/inventory/inventory_item.gd b/scenes/globals/game_state/inventory/inventory_item.gd index 2e04645c24..5c715f0afb 100644 --- a/scenes/globals/game_state/inventory/inventory_item.gd +++ b/scenes/globals/game_state/inventory/inventory_item.gd @@ -8,6 +8,7 @@ enum ItemType { MEMORY, IMAGINATION, SPIRIT, + NONE, } const HUD_TEXTURES: Dictionary[ItemType, Texture2D] = { diff --git a/scenes/menus/storybook/components/quest.gd b/scenes/menus/storybook/components/quest.gd index 1741bf78de..880311181a 100644 --- a/scenes/menus/storybook/components/quest.gd +++ b/scenes/menus/storybook/components/quest.gd @@ -45,6 +45,10 @@ const FILENAME := "quest.tres" ## Whether this is a lore quest (part of the main storyline). @export var is_lore_quest: bool = false +## Optional dialogue to retell the adventures that occurred in the quest, +## when returning the magical threads to the loom. +@export var retelling: DialogueResource + @export_group("Animation") ## An optional sprite frame library to show in the storybook page for this quest. diff --git a/scenes/quests/lore_quests/quest_001/quest.tres b/scenes/quests/lore_quests/quest_001/quest.tres index 4d00de2e2c..534631efeb 100644 --- a/scenes/quests/lore_quests/quest_001/quest.tres +++ b/scenes/quests/lore_quests/quest_001/quest.tres @@ -1,6 +1,7 @@ [gd_resource type="Resource" script_class="Quest" format=3 uid="uid://doovydomib7rj"] [ext_resource type="Script" uid="uid://dts1hwdy3phin" path="res://scenes/menus/storybook/components/quest.gd" id="1_3ntet"] +[ext_resource type="Resource" uid="uid://cs8ay5uof8uap" path="res://scenes/quests/lore_quests/quest_001/quest_001_retelling.dialogue" id="1_8ruhb"] [ext_resource type="Texture2D" uid="uid://gcollkx3tkmm" path="res://scenes/quests/lore_quests/quest_001/Musician.png" id="2_xjg3h"] [sub_resource type="SpriteFrames" id="SpriteFrames_8ruhb"] @@ -21,6 +22,7 @@ title = "The Musician's Quest" description = "StoryWeaver meets a musician and a series of terrifying creatures." first_scene = "uid://7hoy2p14t6kc" is_lore_quest = true +retelling = ExtResource("1_8ruhb") sprite_frames = SubResource("SpriteFrames_8ruhb") animation_name = &"default" metadata/_custom_type_script = "uid://dts1hwdy3phin" diff --git a/scenes/quests/lore_quests/quest_001/quest_001_retelling.dialogue b/scenes/quests/lore_quests/quest_001/quest_001_retelling.dialogue new file mode 100644 index 0000000000..b855fcdce0 --- /dev/null +++ b/scenes/quests/lore_quests/quest_001/quest_001_retelling.dialogue @@ -0,0 +1,28 @@ +# SPDX-FileCopyrightText: The Threadbare Authors +# SPDX-License-Identifier: MPL-2.0 +~ start +StoryWeaver: I went on a quest with the musician and I... +- Learned a forgotten song {{ memory_text() }} => retell_memory +- Slipped past the guards {{ imagination_text() }} => retell_imagination +- Fought off some InkDrinkers {{ spirit_text() }} => retell_spirit + +~ retell_memory +StoryWeaver: Let me whistle it to you... +if has_memory() + do give_memory_upgrade() + Townie: Oh I remember it! That's the lullaby my mother used to sing! +=> END + +~ retell_imagination +StoryWeaver: Like I was a lizard, fast as a hare. +if has_imagination() + do give_imagination_upgrade() + Townie: I start imagining it now! +=> END + +~ retell_spirit +StoryWeaver: Because they had stolen the ink! +if has_imagination() + do give_spirit_upgrade() + Townie: Ha! I feel brave now! +=> END diff --git a/scenes/quests/lore_quests/quest_001/quest_001_retelling.dialogue.import b/scenes/quests/lore_quests/quest_001/quest_001_retelling.dialogue.import new file mode 100644 index 0000000000..27e67af562 --- /dev/null +++ b/scenes/quests/lore_quests/quest_001/quest_001_retelling.dialogue.import @@ -0,0 +1,16 @@ +[remap] + +importer="dialogue_manager" +importer_version=15 +type="Resource" +uid="uid://cs8ay5uof8uap" +path="res://.godot/imported/quest_001_retelling.dialogue-af3cf8eb6b127b847e73c404a987c3fb.tres" + +[deps] + +source_file="res://scenes/quests/lore_quests/quest_001/quest_001_retelling.dialogue" +dest_files=["res://.godot/imported/quest_001_retelling.dialogue-af3cf8eb6b127b847e73c404a987c3fb.tres"] + +[params] + +defaults=true diff --git a/scenes/quests/lore_quests/quest_002/quest.tres b/scenes/quests/lore_quests/quest_002/quest.tres index b9b2f80fa1..37764ab62f 100644 --- a/scenes/quests/lore_quests/quest_002/quest.tres +++ b/scenes/quests/lore_quests/quest_002/quest.tres @@ -1,6 +1,7 @@ [gd_resource type="Resource" script_class="Quest" format=3 uid="uid://t50glay2iqhg"] [ext_resource type="Script" uid="uid://dts1hwdy3phin" path="res://scenes/menus/storybook/components/quest.gd" id="1_dlhxi"] +[ext_resource type="Resource" uid="uid://uxwsefmegw3o" path="res://scenes/quests/lore_quests/quest_002/quest_002_retelling.dialogue" id="1_l1xe8"] [ext_resource type="Texture2D" uid="uid://csbhg24hsxecd" path="res://scenes/quests/lore_quests/quest_002/Void.png" id="2_a6kb4"] [sub_resource type="SpriteFrames" id="SpriteFrames_l1xe8"] @@ -20,6 +21,7 @@ title = "The Void" description = "StoryWeaver runs away from a growing emptiness that spreads across the land, smothering and swallowing everything it covers." first_scene = "uid://bm4ewr8p48x0i" is_lore_quest = true +retelling = ExtResource("1_l1xe8") sprite_frames = SubResource("SpriteFrames_l1xe8") animation_name = &"default" metadata/_custom_type_script = "uid://dts1hwdy3phin" diff --git a/scenes/quests/lore_quests/quest_002/quest_002_retelling.dialogue b/scenes/quests/lore_quests/quest_002/quest_002_retelling.dialogue new file mode 100644 index 0000000000..a42946f0aa --- /dev/null +++ b/scenes/quests/lore_quests/quest_002/quest_002_retelling.dialogue @@ -0,0 +1,28 @@ +# SPDX-FileCopyrightText: The Threadbare Authors +# SPDX-License-Identifier: MPL-2.0 +~ start +StoryWeaver: I went on a quest with the monk and I... +- Had to run! {{ spirit_text() }} => retell_spirit +- Found inventive ways of moving {{ imagination_text() }} => retell_imagination +- Delivered a story to the town {{ memory_text() }} => retell_memory + +~ retell_memory +StoryWeaver: We saved the story from The Void... +if has_memory() + do give_memory_upgrade() + Townie: Oh I remember now, the monk told it to me! +=> END + +~ retell_imagination +StoryWeaver: Using a grappling hook, pins and needles. +if has_imagination() + do give_imagination_upgrade() + Townie: That really feeds my imagination! +=> END + +~ retell_spirit +StoryWeaver: The Void was eating the floor behind me, but I managed to escape! +if has_imagination() + do give_spirit_upgrade() + Townie: Whoa. I feel brave now! +=> END diff --git a/scenes/quests/lore_quests/quest_002/quest_002_retelling.dialogue.import b/scenes/quests/lore_quests/quest_002/quest_002_retelling.dialogue.import new file mode 100644 index 0000000000..a4918ebb47 --- /dev/null +++ b/scenes/quests/lore_quests/quest_002/quest_002_retelling.dialogue.import @@ -0,0 +1,16 @@ +[remap] + +importer="dialogue_manager" +importer_version=15 +type="Resource" +uid="uid://uxwsefmegw3o" +path="res://.godot/imported/quest_002_retelling.dialogue-8eacbd3feb2cf351587c0351c43d711b.tres" + +[deps] + +source_file="res://scenes/quests/lore_quests/quest_002/quest_002_retelling.dialogue" +dest_files=["res://.godot/imported/quest_002_retelling.dialogue-8eacbd3feb2cf351587c0351c43d711b.tres"] + +[params] + +defaults=true diff --git a/scenes/ui_elements/story_quest_progress/components/story_quest_progress.gd b/scenes/ui_elements/story_quest_progress/components/story_quest_progress.gd index 8c09d5ec68..cc102d7cbe 100644 --- a/scenes/ui_elements/story_quest_progress/components/story_quest_progress.gd +++ b/scenes/ui_elements/story_quest_progress/components/story_quest_progress.gd @@ -3,16 +3,23 @@ extends PanelContainer const ITEM_SLOT: PackedScene = preload("uid://1mjm4atk2j6e") +const TOWNIE = preload("uid://dgrrudegturnw") @onready var items_container: HBoxContainer = %ItemsContainer +@onready var helper_container: CenterContainer = %HelperContainer +@onready var helper_marker: Marker2D = %HelperMarker +@onready var helper_color: ColorRect = %HelperColor func _ready() -> void: + GameState.upgrade_changed.connect(_on_upgrade_changed) + _on_upgrade_changed() + if not GameState.current_quest: return if GameState.current_quest.threads_to_collect == 0: - visible = false + # visible = false return # Add one slot for each item in the current quest @@ -30,6 +37,29 @@ func _ready() -> void: GameState.item_consumed.connect(self._on_item_consumed) +func _on_upgrade_changed() -> void: + var has_helper := bool(GameState.upgrade_character_seed) + helper_container.visible = has_helper + if has_helper: + var helper_character: CharacterRandomizer = TOWNIE.instantiate() + add_child(helper_character) + helper_character.character_seed = GameState.upgrade_character_seed + helper_character.apply_character_randomizations() + var head := helper_character.get_child(1).get_child(1).get_child(2) + head.global_position = helper_marker.global_position + head.reparent(helper_container) + head.process_mode = Node.PROCESS_MODE_DISABLED + remove_child(helper_character) + + match GameState.upgrade_type: + InventoryItem.ItemType.MEMORY: + helper_color.color = Color(0.459, 0.867, 0.0, 1.0) + InventoryItem.ItemType.IMAGINATION: + helper_color.color = Color(0.969, 0.792, 0.0, 1.0) + InventoryItem.ItemType.SPIRIT: + helper_color.color = Color(0.929, 0.0, 0.0, 1.0) + + func _on_item_collected(item: InventoryItem) -> void: for child in items_container.get_children(): var item_slot := child as ItemSlot diff --git a/scenes/ui_elements/story_quest_progress/story_quest_progress.tscn b/scenes/ui_elements/story_quest_progress/story_quest_progress.tscn index 062c8faa9e..0a173361c9 100644 --- a/scenes/ui_elements/story_quest_progress/story_quest_progress.tscn +++ b/scenes/ui_elements/story_quest_progress/story_quest_progress.tscn @@ -22,7 +22,25 @@ offset_bottom = 96.0 theme_override_styles/panel = SubResource("StyleBoxTexture_hqnrr") script = ExtResource("2_hvmtc") -[node name="ItemsContainer" type="HBoxContainer" parent="." unique_id=202523362] +[node name="HBoxContainer" type="HBoxContainer" parent="." unique_id=2124538819] +layout_mode = 2 + +[node name="HelperContainer" type="CenterContainer" parent="HBoxContainer" unique_id=1334612586] +unique_name_in_owner = true +custom_minimum_size = Vector2(80, 0) +layout_mode = 2 + +[node name="HelperMarker" type="Marker2D" parent="HBoxContainer/HelperContainer" unique_id=1030669604] +unique_name_in_owner = true +position = Vector2(40, 18) + +[node name="HelperColor" type="ColorRect" parent="HBoxContainer/HelperContainer" unique_id=1044648131] +unique_name_in_owner = true +custom_minimum_size = Vector2(60, 30) +layout_mode = 2 +color = Color(1, 0, 1, 1) + +[node name="ItemsContainer" type="HBoxContainer" parent="HBoxContainer" unique_id=202523362] unique_name_in_owner = true custom_minimum_size = Vector2(32, 32) layout_mode = 2 diff --git a/scenes/world_map/components/frays_end.gd b/scenes/world_map/components/frays_end.gd index 16d8e45b6f..fb7eb8b2ce 100644 --- a/scenes/world_map/components/frays_end.gd +++ b/scenes/world_map/components/frays_end.gd @@ -7,7 +7,7 @@ extends Node2D func _ready() -> void: - _update_story_quest_progress_visibility() + # _update_story_quest_progress_visibility() GameState.collected_items_changed.connect(_update_story_quest_progress_visibility) # Restore lives to maximum when entering Fray's End diff --git a/scenes/world_map/components/retelling_townies.gd b/scenes/world_map/components/retelling_townies.gd new file mode 100644 index 0000000000..d4f90a46da --- /dev/null +++ b/scenes/world_map/components/retelling_townies.gd @@ -0,0 +1,42 @@ +# SPDX-FileCopyrightText: The Threadbare Authors +# SPDX-License-Identifier: MPL-2.0 +extends Node2D + +const TOWNIE = preload("uid://dgrrudegturnw") +@export var marks: Array[Marker2D] = [] + +var _townies: Array[CharacterBody2D] = [] + +@onready var eternal_loom: EternalLoom = %EternalLoom + + +func _ready() -> void: + eternal_loom.retelling_started.connect(_on_eternal_loom_retelling_started) + eternal_loom.retelling_finished.connect(_on_eternal_loom_retelling_finished) + eternal_loom.give_retelling_upgrade.connect(_on_eternal_loom_give_retelling_upgrade) + + +func _on_eternal_loom_retelling_started() -> void: + var player: Node2D = get_tree().get_first_node_in_group("player") + for m in marks: + var t: CharacterRandomizer = TOWNIE.instantiate() + add_child(t) + t.global_position = m.global_position + t.look_at_side = ( + Enums.LookAtSide.LEFT + if (t.global_position.x < player.global_position.x) + else Enums.LookAtSide.RIGHT + ) + t.randomize_character() + _townies.append(t) + + +func _on_eternal_loom_retelling_finished() -> void: + for t: CharacterBody2D in _townies: + t.queue_free() + + +func _on_eternal_loom_give_retelling_upgrade(type: InventoryItem.ItemType) -> void: + var t: CharacterRandomizer = _townies.pick_random() + GameState.set_upgrade(type, t.character_seed) + t.scale *= 2 diff --git a/scenes/world_map/components/retelling_townies.gd.uid b/scenes/world_map/components/retelling_townies.gd.uid new file mode 100644 index 0000000000..52bad1fcbb --- /dev/null +++ b/scenes/world_map/components/retelling_townies.gd.uid @@ -0,0 +1 @@ +uid://dntu74hg8e5mt diff --git a/scenes/world_map/frays_end.tscn b/scenes/world_map/frays_end.tscn index 36af70d45f..f71bf67b2d 100644 --- a/scenes/world_map/frays_end.tscn +++ b/scenes/world_map/frays_end.tscn @@ -43,6 +43,7 @@ [ext_resource type="Script" uid="uid://bdhjixygupit1" path="res://scenes/game_elements/props/area_filler/area_filler.gd" id="38_cjmkx"] [ext_resource type="PackedScene" uid="uid://dv4f232y8w8dv" path="res://scenes/game_elements/props/decoration/water_rock/water_rock.tscn" id="38_jqkod"] [ext_resource type="PackedScene" uid="uid://bqkui35x5ly0" path="res://scenes/game_elements/props/decoration/crops/tomato.tscn" id="39_ljovl"] +[ext_resource type="Script" uid="uid://dntu74hg8e5mt" path="res://scenes/world_map/components/retelling_townies.gd" id="41_ojao8"] [ext_resource type="PackedScene" uid="uid://ccpgw7k4rlmea" path="res://scenes/game_elements/props/decoration/crops/barley.tscn" id="42_eodqy"] [ext_resource type="PackedScene" uid="uid://c78fj5em5tpm5" path="res://scenes/game_elements/props/decoration/crops/carrot.tscn" id="42_ljovl"] [ext_resource type="PackedScene" uid="uid://vb5o7hh5an8j" path="res://scenes/game_elements/characters/npcs/elder/template_elder.tscn" id="43_ppslc"] @@ -427,6 +428,31 @@ unique_name_in_owner = true position = Vector2(993, 485) scale = Vector2(0.9, 0.9) +[node name="RetellingTownies" type="Node2D" parent="OnTheGround" unique_id=366416019 node_paths=PackedStringArray("marks")] +y_sort_enabled = true +position = Vector2(993, 485) +scale = Vector2(0.9, 0.9) +script = ExtResource("41_ojao8") +marks = [NodePath("Marker2D"), NodePath("Marker2D2"), NodePath("Marker2D3"), NodePath("Marker2D4"), NodePath("Marker2D5"), NodePath("Marker2D6")] + +[node name="Marker2D" type="Marker2D" parent="OnTheGround/RetellingTownies" unique_id=75230768] +position = Vector2(-178.8889, 264.44446) + +[node name="Marker2D2" type="Marker2D" parent="OnTheGround/RetellingTownies" unique_id=1164173184] +position = Vector2(-154.44444, 342.22226) + +[node name="Marker2D3" type="Marker2D" parent="OnTheGround/RetellingTownies" unique_id=1652485887] +position = Vector2(-86.666664, 420.00006) + +[node name="Marker2D4" type="Marker2D" parent="OnTheGround/RetellingTownies" unique_id=392080699] +position = Vector2(86.66667, 426.6667) + +[node name="Marker2D5" type="Marker2D" parent="OnTheGround/RetellingTownies" unique_id=1854491904] +position = Vector2(152.22223, 345.5556) + +[node name="Marker2D6" type="Marker2D" parent="OnTheGround/RetellingTownies" unique_id=1058460818] +position = Vector2(172.22223, 258.88892) + [node name="ForestLimit" type="StaticBody2D" parent="OnTheGround" unique_id=1908977889] position = Vector2(972, 2047) scale = Vector2(1.10441, 1.00725) diff --git a/scenes/world_map/frays_end_west.tscn b/scenes/world_map/frays_end_west.tscn index cfe2b145d5..a8eb1ba979 100644 --- a/scenes/world_map/frays_end_west.tscn +++ b/scenes/world_map/frays_end_west.tscn @@ -23,6 +23,8 @@ [ext_resource type="PackedScene" uid="uid://daqd67aro1o1m" path="res://scenes/game_elements/fx/time_and_weather/time_and_weather.tscn" id="13_ltemt"] [ext_resource type="Script" uid="uid://du8wfijr35r35" path="res://scenes/game_elements/props/interact_area/interact_area.gd" id="13_otyi0"] [ext_resource type="Texture2D" uid="uid://d2kjk3oiu5o6o" path="res://scenes/game_elements/props/buildings/house/components/House_Wool_Blue_02.png" id="13_qm4wv"] +[ext_resource type="Script" uid="uid://diskln3jup064" path="res://scenes/game_elements/characters/npcs/components/helper_character.gd" id="14_8o6qx"] +[ext_resource type="Resource" uid="uid://c667fv6ngpc7s" path="res://scenes/game_elements/characters/npcs/components/default_helper.dialogue" id="14_kxo1k"] [ext_resource type="Resource" uid="uid://d4kkr5cifitfm" path="res://scenes/world_map/components/sigurd.dialogue" id="14_otyi0"] [ext_resource type="SpriteFrames" uid="uid://b0ba41utstkyk" path="res://scenes/game_elements/characters/components/sprite_frames/sheep.tres" id="20_feqgw"] [ext_resource type="Script" uid="uid://cms2wqtbxjl1h" path="res://scenes/game_logic/sprite_behaviors/random_frame_sprite_behavior.gd" id="21_qm4wv"] @@ -127,6 +129,86 @@ position = Vector2(0, -23.5) shape = SubResource("RectangleShape2D_kxo1k") debug_color = Color(0.600391, 0.54335, 0, 0.42) +[node name="Helper" parent="OnTheGround" unique_id=695704794 instance=ExtResource("5_wf3xu")] +position = Vector2(854, 489) + +[node name="TalkBehavior" type="Node" parent="OnTheGround/Helper" unique_id=1731180800 node_paths=PackedStringArray("interact_area")] +script = ExtResource("11_ovokv") +dialogue = ExtResource("14_kxo1k") +interact_area = NodePath("../InteractArea") +metadata/_custom_type_script = "uid://edcifob4jc4s" + +[node name="InteractArea" type="Area2D" parent="OnTheGround/Helper" unique_id=1552188155] +visible = false +collision_layer = 32 +collision_mask = 0 +script = ExtResource("13_otyi0") +interact_label_position = Vector2(0, -100) +action = "Want some help?" +metadata/_custom_type_script = "uid://du8wfijr35r35" + +[node name="CollisionShape2D" type="CollisionShape2D" parent="OnTheGround/Helper/InteractArea" unique_id=1387075493] +position = Vector2(0, -23.5) +shape = SubResource("RectangleShape2D_kxo1k") +debug_color = Color(0.600391, 0.54335, 0, 0.42) + +[node name="HelperCharacter" type="Node" parent="OnTheGround/Helper" unique_id=1687792664] +script = ExtResource("14_8o6qx") +target_upgrade = 1 + +[node name="Helper2" parent="OnTheGround" unique_id=900576840 instance=ExtResource("5_wf3xu")] +position = Vector2(725, 381) + +[node name="TalkBehavior" type="Node" parent="OnTheGround/Helper2" unique_id=229271193 node_paths=PackedStringArray("interact_area")] +script = ExtResource("11_ovokv") +dialogue = ExtResource("14_kxo1k") +interact_area = NodePath("../InteractArea") +metadata/_custom_type_script = "uid://edcifob4jc4s" + +[node name="InteractArea" type="Area2D" parent="OnTheGround/Helper2" unique_id=1474548326] +visible = false +collision_layer = 32 +collision_mask = 0 +script = ExtResource("13_otyi0") +interact_label_position = Vector2(0, -100) +action = "Want some help?" +metadata/_custom_type_script = "uid://du8wfijr35r35" + +[node name="CollisionShape2D" type="CollisionShape2D" parent="OnTheGround/Helper2/InteractArea" unique_id=1673215791] +position = Vector2(0, -23.5) +shape = SubResource("RectangleShape2D_kxo1k") +debug_color = Color(0.600391, 0.54335, 0, 0.42) + +[node name="HelperCharacter" type="Node" parent="OnTheGround/Helper2" unique_id=1349047491] +script = ExtResource("14_8o6qx") + +[node name="Helper3" parent="OnTheGround" unique_id=1312833537 instance=ExtResource("5_wf3xu")] +position = Vector2(973, 609) + +[node name="TalkBehavior" type="Node" parent="OnTheGround/Helper3" unique_id=1769877341 node_paths=PackedStringArray("interact_area")] +script = ExtResource("11_ovokv") +dialogue = ExtResource("14_kxo1k") +interact_area = NodePath("../InteractArea") +metadata/_custom_type_script = "uid://edcifob4jc4s" + +[node name="InteractArea" type="Area2D" parent="OnTheGround/Helper3" unique_id=572249902] +visible = false +collision_layer = 32 +collision_mask = 0 +script = ExtResource("13_otyi0") +interact_label_position = Vector2(0, -100) +action = "Want some help?" +metadata/_custom_type_script = "uid://du8wfijr35r35" + +[node name="CollisionShape2D" type="CollisionShape2D" parent="OnTheGround/Helper3/InteractArea" unique_id=187152608] +position = Vector2(0, -23.5) +shape = SubResource("RectangleShape2D_kxo1k") +debug_color = Color(0.600391, 0.54335, 0, 0.42) + +[node name="HelperCharacter" type="Node" parent="OnTheGround/Helper3" unique_id=58760936] +script = ExtResource("14_8o6qx") +target_upgrade = 2 + [node name="Sigurd" parent="OnTheGround" unique_id=1735084916 instance=ExtResource("5_wf3xu")] position = Vector2(429, 271) scale = Vector2(-1, 1)