From 5d6d2e6e25e2100e675cb792d59a8046818a6ed1 Mon Sep 17 00:00:00 2001 From: Will Thompson Date: Sun, 20 Sep 2026 17:09:25 +0100 Subject: [PATCH] Remember threads between quest playthroughs Quests in Threadbare can be replayed. Previously you would be able to collect each thread again, and take them to the loom a second time. Then they would be forgotten forever. We would also like to make it possible to have optional threads in a quest, so that there is value in replaying a quest. Perhaps there's a thread you can only get through careful exploration, or via an optional puzzle, or with an ability unlocked later in the game. Finally, we would like to make threads a sort of currency, with which you can buy powerups of some kind. It follows that you shouldn't be able to collect the same thread in the same scene multiple times because then you could just grind an easy quest to buy all the powerups. Change the game state storage to record the scene and node from which a thread was collected. At the end of a quest, copy the new threads to a new global inventory. When starting a quest, pre-populate the quest inventory with the threads already collected in a previous run. In CollectibleItem, and in the HUD, detect when a thread is from a previous run through the quest and dither it. In the two places where threads are awarded in dialogue, add a CollectibleItem to the scene and use that as the source of the item. Perhaps in future it would be better to have a method on CollectibleItem to do this outside of an interaction. Or maybe these should be playable scenes and this should not be supported? --- scenes/dev/dev_archipelago.tscn | 1 + .../optional_threads.dialogue | 13 ++ .../optional_threads.dialogue.import | 16 ++ .../optional_threads/optional_threads.gd | 32 +++ .../optional_threads/optional_threads.gd.uid | 1 + .../optional_threads/optional_threads.tscn | 196 ++++++++++++++++++ .../level_design/optional_threads/quest.tres | 11 + .../characters/npcs/elder/components/elder.gd | 1 + .../game_elements/fx/dither/dither.gdshader | 17 ++ .../fx/dither/dither.gdshader.uid | 1 + scenes/game_elements/fx/dither/dither.tres | 8 + .../collectible_item/collectible_item.tscn | 2 + .../components/collectible_item.gd | 29 ++- .../eternal_loom/components/eternal_loom.gd | 13 +- .../components/loom_offering_animation.gd | 5 +- scenes/globals/game_state/game_state.gd | 8 + scenes/globals/game_state/global_state.gd | 2 + .../game_state/inventory/tagged_item.gd | 35 ++++ .../game_state/inventory/tagged_item.gd.uid | 1 + scenes/globals/game_state/inventory_state.gd | 50 ++++- .../closing_transition.tscn | 8 + .../components/closing_cinematic.dialogue | 2 +- .../components/closing_transition.gd | 1 + .../4_outro/el_ojo_revelador_outro.tscn | 9 + .../el_ojo_revelador_outro.dialogue | 2 +- .../el_ojo_revelador_outro.gd | 5 + .../el_ojo_revelador_outro.gd.uid | 1 + .../item_slot/components/item_slot.gd | 12 +- .../components/item_slot/item_slot.tscn | 2 + .../components/story_quest_progress.gd | 15 +- scenes/world_map/components/frays_end.gd | 2 + 31 files changed, 472 insertions(+), 29 deletions(-) create mode 100644 scenes/dev/level_design/optional_threads/optional_threads.dialogue create mode 100644 scenes/dev/level_design/optional_threads/optional_threads.dialogue.import create mode 100644 scenes/dev/level_design/optional_threads/optional_threads.gd create mode 100644 scenes/dev/level_design/optional_threads/optional_threads.gd.uid create mode 100644 scenes/dev/level_design/optional_threads/optional_threads.tscn create mode 100644 scenes/dev/level_design/optional_threads/quest.tres create mode 100644 scenes/game_elements/fx/dither/dither.gdshader create mode 100644 scenes/game_elements/fx/dither/dither.gdshader.uid create mode 100644 scenes/game_elements/fx/dither/dither.tres create mode 100644 scenes/globals/game_state/inventory/tagged_item.gd create mode 100644 scenes/globals/game_state/inventory/tagged_item.gd.uid create mode 100644 scenes/quests/story_quests/el_ojo_revelador/4_outro/outro_components/el_ojo_revelador_outro.gd create mode 100644 scenes/quests/story_quests/el_ojo_revelador/4_outro/outro_components/el_ojo_revelador_outro.gd.uid diff --git a/scenes/dev/dev_archipelago.tscn b/scenes/dev/dev_archipelago.tscn index 98f0807753..c4a7643ca5 100644 --- a/scenes/dev/dev_archipelago.tscn +++ b/scenes/dev/dev_archipelago.tscn @@ -216,6 +216,7 @@ position = Vector2(0, 64) [node name="TemplateElder" parent="OnTheGround/Elders" unique_id=1625796178 node_paths=PackedStringArray("abandon_point") instance=ExtResource("11_ui3i3")] position = Vector2(481, 1107) +quest_directory = "res://scenes/dev/level_design/" abandon_point = NodePath("SpawnPoint") [node name="SpawnPoint" parent="OnTheGround/Elders/TemplateElder" unique_id=1656237801 instance=ExtResource("18_elr7o")] diff --git a/scenes/dev/level_design/optional_threads/optional_threads.dialogue b/scenes/dev/level_design/optional_threads/optional_threads.dialogue new file mode 100644 index 0000000000..46755d7402 --- /dev/null +++ b/scenes/dev/level_design/optional_threads/optional_threads.dialogue @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: The Threadbare Authors +# SPDX-License-Identifier: MPL-2.0 +~ start +if optional_collectible_item: + Ulysses Thriftweed: If you go East, you can get out of here. + Ulysses Thriftweed: If you go through the cave up North, there's another thread you can collect first, if you're feeling brave. +else: + Ulysses Thriftweed: I hope now you've learned to stop picking your nose. +=> END + +~ wow +Ulysses Thriftweed: You run THAT fast? +=> END diff --git a/scenes/dev/level_design/optional_threads/optional_threads.dialogue.import b/scenes/dev/level_design/optional_threads/optional_threads.dialogue.import new file mode 100644 index 0000000000..9c0d5023da --- /dev/null +++ b/scenes/dev/level_design/optional_threads/optional_threads.dialogue.import @@ -0,0 +1,16 @@ +[remap] + +importer="dialogue_manager" +importer_version=18 +type="Resource" +uid="uid://b1ywwu7aese02" +path="res://.godot/imported/optional_threads.dialogue-f583759440f80634b369fea5863492be.tres" + +[deps] + +source_file="res://scenes/dev/level_design/optional_threads/optional_threads.dialogue" +dest_files=["res://.godot/imported/optional_threads.dialogue-f583759440f80634b369fea5863492be.tres"] + +[params] + +defaults=true diff --git a/scenes/dev/level_design/optional_threads/optional_threads.gd b/scenes/dev/level_design/optional_threads/optional_threads.gd new file mode 100644 index 0000000000..74440297c2 --- /dev/null +++ b/scenes/dev/level_design/optional_threads/optional_threads.gd @@ -0,0 +1,32 @@ +# SPDX-FileCopyrightText: The Threadbare Authors +# SPDX-License-Identifier: MPL-2.0 +extends Node2D + +@export var dark_sky_color: Color +var _tween: Tween + +@onready var cave_lighting: Area2D = %CaveLighting +@onready var canvas_modulate: CanvasModulate = %CanvasModulate +@onready var optional_collectible_item: CollectibleItem = %OptionalCollectibleItem + + +func _ready() -> void: + optional_collectible_item.tree_exiting.connect(func() -> void: optional_collectible_item = null) + cave_lighting.body_entered.connect(_on_cave_lighting_body_entered) + cave_lighting.body_exited.connect(_on_cave_lighting_body_exited) + + +func _adjust_modulation(target: Color) -> void: + if _tween: + _tween.kill() + + _tween = create_tween() + _tween.tween_property(canvas_modulate, "color", target, 0.5) + + +func _on_cave_lighting_body_entered(_body: Node2D) -> void: + _adjust_modulation(dark_sky_color) + + +func _on_cave_lighting_body_exited(_body: Node2D) -> void: + _adjust_modulation(Color.WHITE) diff --git a/scenes/dev/level_design/optional_threads/optional_threads.gd.uid b/scenes/dev/level_design/optional_threads/optional_threads.gd.uid new file mode 100644 index 0000000000..c73c000501 --- /dev/null +++ b/scenes/dev/level_design/optional_threads/optional_threads.gd.uid @@ -0,0 +1 @@ +uid://b4muk3fixh36l diff --git a/scenes/dev/level_design/optional_threads/optional_threads.tscn b/scenes/dev/level_design/optional_threads/optional_threads.tscn new file mode 100644 index 0000000000..12ef7334a0 --- /dev/null +++ b/scenes/dev/level_design/optional_threads/optional_threads.tscn @@ -0,0 +1,196 @@ +[gd_scene format=4 uid="uid://cplmo457h47p8"] + +[ext_resource type="Script" uid="uid://b4muk3fixh36l" path="res://scenes/dev/level_design/optional_threads/optional_threads.gd" id="1_gyr7v"] +[ext_resource type="TileSet" uid="uid://oynx002hv8tl" path="res://tiles/water.tres" id="2_dhrfw"] +[ext_resource type="TileSet" uid="uid://bjx3gvah0ycl1" path="res://tiles/foam_2.tres" id="3_8uyjj"] +[ext_resource type="TileSet" uid="uid://b8qnr0owsbhhn" path="res://tiles/exterior_floors.tres" id="4_4c3u0"] +[ext_resource type="TileSet" uid="uid://bdc7ucso7bx0s" path="res://tiles/shadows.tres" id="4_pppnl"] +[ext_resource type="TileSet" uid="uid://b778cuoftt88r" path="res://tiles/elevation_2.tres" id="5_vs7wd"] +[ext_resource type="Script" uid="uid://du8wfijr35r35" path="res://scenes/game_elements/props/interact_area/interact_area.gd" id="6_6kkli"] +[ext_resource type="PackedScene" uid="uid://iu2q66clupc6" path="res://scenes/game_elements/characters/player/player.tscn" id="7_a3wsq"] +[ext_resource type="PackedScene" uid="uid://fuhl3l6gxq5k" path="res://scenes/game_elements/props/collectible_item/collectible_item.tscn" id="7_r84jh"] +[ext_resource type="SpriteFrames" uid="uid://dtoylirwywk0j" path="res://scenes/game_elements/characters/components/sprite_frames/storyweaver_blue.tres" id="8_f3fhx"] +[ext_resource type="Resource" uid="uid://ck0wbckgm5un8" path="res://scenes/globals/game_state/inventory/memory.tres" id="8_me0l1"] +[ext_resource type="Resource" uid="uid://c44l6x4e7sfx3" path="res://scenes/globals/game_state/inventory/spirit.tres" id="9_vii7y"] +[ext_resource type="PackedScene" uid="uid://dgrrudegturnw" path="res://scenes/game_elements/characters/npcs/townie.tscn" id="10_fvfn2"] +[ext_resource type="PackedScene" uid="uid://d37mebu7atru7" path="res://scenes/game_elements/characters/enemies/guard/guard.tscn" id="11_8y2ta"] +[ext_resource type="Script" uid="uid://edcifob4jc4s" path="res://scenes/game_logic/talk_behavior.gd" id="11_fvfn2"] +[ext_resource type="Resource" uid="uid://b1ywwu7aese02" path="res://scenes/dev/level_design/optional_threads/optional_threads.dialogue" id="12_8y2ta"] +[ext_resource type="SpriteFrames" uid="uid://d1k25io0tiiij" path="res://scenes/game_elements/characters/enemies/guard/components/storyvoretorch_frames_blue.tres" id="12_pppnl"] +[ext_resource type="AudioStream" uid="uid://dgpeb1dtmfqud" path="res://assets/third_party/sounds/characters/enemies/guard/AlertSound.ogg" id="13_vs7wd"] +[ext_resource type="AudioStream" uid="uid://4ec6e2alns71" path="res://assets/third_party/sounds/characters/enemies/guard/GrassFoot.ogg" id="14_72lml"] +[ext_resource type="AudioStream" uid="uid://c7om8kdork2rx" path="res://assets/third_party/sounds/characters/enemies/guard/Torch.ogg" id="15_l2eis"] +[ext_resource type="AudioStream" uid="uid://bwif2oo6ymiu2" path="res://assets/third_party/sounds/characters/enemies/guard/TorchHit.ogg" id="16_mxlxh"] +[ext_resource type="Script" uid="uid://dnp0tjloec2d7" path="res://scenes/game_logic/stealth_game_logic.gd" id="19_pppnl"] +[ext_resource type="SpriteFrames" uid="uid://tchths61dylv" path="res://scenes/game_elements/characters/enemies/guard/components/storyvoretorch_frames_green.tres" id="21_vs7wd"] +[ext_resource type="PackedScene" uid="uid://dua6mynlw2ptw" path="res://scenes/game_elements/props/checkpoint/checkpoint.tscn" id="22_l2eis"] +[ext_resource type="PackedScene" uid="uid://cfcgrfvtn04yp" path="res://scenes/ui_elements/hud/hud.tscn" id="23_72lml"] +[ext_resource type="SpriteFrames" uid="uid://3elulifvmamm" path="res://scenes/game_elements/props/checkpoint/components/knitwitch_frames_blue.tres" id="23_mxlxh"] + +[sub_resource type="RectangleShape2D" id="RectangleShape2D_vs7wd"] +size = Vector2(52, 61) + +[sub_resource type="Curve2D" id="Curve2D_gyr7v"] +_data = { +"points": PackedVector2Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 256, 0, 0, 0, 0, 0, 256, 384, 0, 0, 0, 0, 0, 384, 0, 0, 0, 0, 0, 0) +} +point_count = 5 + +[sub_resource type="Curve2D" id="Curve2D_72lml"] +_data = { +"points": PackedVector2Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 256, 0, 0, 0, 0, 0, 256, 384, 0, 0, 0, 0, 0, 384, 0, 0, 0, 0, 0, 0) +} +point_count = 5 + +[sub_resource type="RectangleShape2D" id="RectangleShape2D_s5hwq"] +size = Vector2(192, 192) + +[sub_resource type="RectangleShape2D" id="RectangleShape2D_me0l1"] +size = Vector2(1358, 654) + +[node name="OptionalThreadsTest" type="Node2D" unique_id=60120374] +script = ExtResource("1_gyr7v") +dark_sky_color = Color(0.48, 0.48, 0.48, 1) + +[node name="TileMapLayers" type="Node2D" parent="." unique_id=1892145315] + +[node name="Water" type="TileMapLayer" parent="TileMapLayers" unique_id=568547056] +tile_map_data = PackedByteArray("AAAEAPf/AAAAAAAAAAAEAPj/AAAAAAAAAAAEAPn/AAAAAAAAAAAEAPr/AAAAAAAAAAAEAPv/AAAAAAAAAAAEAPz/AAAAAAAAAAAEAP3/AAAAAAAAAAAEAP7/AAAAAAAAAAAEAP//AAAAAAAAAAAEAAAAAAAAAAAAAAAEAAEAAAAAAAAAAAAEAAIAAAAAAAAAAAAEAAMAAAAAAAAAAAAEAAQAAAAAAAAAAAAEAAUAAAAAAAAAAAAEAAYAAAAAAAAAAAAEAAcAAAAAAAAAAAAEAAgAAAAAAAAAAAAEAAkAAAAAAAAAAAAEAAoAAAAAAAAAAAAEAAsAAAAAAAAAAAAEAAwAAAAAAAAAAAAEAA0AAAAAAAAAAAAEAA4AAAAAAAAAAAAEAA8AAAAAAAAAAAAFAPf/AAAAAAAAAAAFAPj/AAAAAAAAAAAFAPn/AAAAAAAAAAAFAPr/AAAAAAAAAAAFAPv/AAAAAAAAAAAFAPz/AAAAAAAAAAAFAP3/AAAAAAAAAAAFAP7/AAAAAAAAAAAFAP//AAAAAAAAAAAFAAAAAAAAAAAAAAAFAAEAAAAAAAAAAAAFAAIAAAAAAAAAAAAFAAMAAAAAAAAAAAAFAAQAAAAAAAAAAAAFAAUAAAAAAAAAAAAFAAYAAAAAAAAAAAAFAAcAAAAAAAAAAAAFAAgAAAAAAAAAAAAFAAkAAAAAAAAAAAAFAAoAAAAAAAAAAAAFAAsAAAAAAAAAAAAFAAwAAAAAAAAAAAAFAA0AAAAAAAAAAAAFAA4AAAAAAAAAAAAFAA8AAAAAAAAAAAAGAPf/AAAAAAAAAAAGAPj/AAAAAAAAAAAGAPn/AAAAAAAAAAAGAPr/AAAAAAAAAAAGAPv/AAAAAAAAAAAGAPz/AAAAAAAAAAAGAP3/AAAAAAAAAAAGAP7/AAAAAAAAAAAGAP//AAAAAAAAAAAGAAAAAAAAAAAAAAAGAAEAAAAAAAAAAAAGAAIAAAAAAAAAAAAGAAMAAAAAAAAAAAAGAAQAAAAAAAAAAAAGAAUAAAAAAAAAAAAGAAYAAAAAAAAAAAAGAAcAAAAAAAAAAAAGAAgAAAAAAAAAAAAGAAkAAAAAAAAAAAAGAAoAAAAAAAAAAAAGAAsAAAAAAAAAAAAGAAwAAAAAAAAAAAAGAA0AAAAAAAAAAAAGAA4AAAAAAAAAAAAGAA8AAAAAAAAAAAAHAPf/AAAAAAAAAAAHAPj/AAAAAAAAAAAHAPn/AAAAAAAAAAAHAPr/AAAAAAAAAAAHAPv/AAAAAAAAAAAHAA4AAAAAAAAAAAAHAA8AAAAAAAAAAAAIAPf/AAAAAAAAAAAIAPj/AAAAAAAAAAAIAPn/AAAAAAAAAAAIAPr/AAAAAAAAAAAIAPv/AAAAAAAAAAAIAA4AAAAAAAAAAAAIAA8AAAAAAAAAAAAJAPf/AAAAAAAAAAAJAPj/AAAAAAAAAAAJAPn/AAAAAAAAAAAJAPr/AAAAAAAAAAAJAPv/AAAAAAAAAAAJAA4AAAAAAAAAAAAJAA8AAAAAAAAAAAAKAPf/AAAAAAAAAAAKAPj/AAAAAAAAAAAKAPn/AAAAAAAAAAAKAPr/AAAAAAAAAAAKAPv/AAAAAAAAAAAKAAkAAAAAAAAAAAAKAAoAAAAAAAAAAAAKAAsAAAAAAAAAAAAKAAwAAAAAAAAAAAAKAA0AAAAAAAAAAAAKAA4AAAAAAAAAAAAKAA8AAAAAAAAAAAALAPf/AAAAAAAAAAALAPj/AAAAAAAAAAALAPn/AAAAAAAAAAALAPr/AAAAAAAAAAALAPv/AAAAAAAAAAALAAkAAAAAAAAAAAALAAoAAAAAAAAAAAALAAsAAAAAAAAAAAALAAwAAAAAAAAAAAALAA0AAAAAAAAAAAALAA4AAAAAAAAAAAALAA8AAAAAAAAAAAAMAPf/AAAAAAAAAAAMAPj/AAAAAAAAAAAMAPn/AAAAAAAAAAAMAPr/AAAAAAAAAAAMAPv/AAAAAAAAAAAMAAkAAAAAAAAAAAAMAAoAAAAAAAAAAAAMAAsAAAAAAAAAAAAMAAwAAAAAAAAAAAAMAA0AAAAAAAAAAAAMAA4AAAAAAAAAAAAMAA8AAAAAAAAAAAANAPf/AAAAAAAAAAANAPj/AAAAAAAAAAANAPn/AAAAAAAAAAANAPr/AAAAAAAAAAANAPv/AAAAAAAAAAANAAkAAAAAAAAAAAANAAoAAAAAAAAAAAANAAsAAAAAAAAAAAANAAwAAAAAAAAAAAANAA0AAAAAAAAAAAANAA4AAAAAAAAAAAANAA8AAAAAAAAAAAAOAPf/AAAAAAAAAAAOAPj/AAAAAAAAAAAOAPn/AAAAAAAAAAAOAPr/AAAAAAAAAAAOAPv/AAAAAAAAAAAOAAkAAAAAAAAAAAAOAAoAAAAAAAAAAAAOAAsAAAAAAAAAAAAOAAwAAAAAAAAAAAAOAA0AAAAAAAAAAAAOAA4AAAAAAAAAAAAOAA8AAAAAAAAAAAAPAPf/AAAAAAAAAAAPAPj/AAAAAAAAAAAPAPn/AAAAAAAAAAAPAPr/AAAAAAAAAAAPAPv/AAAAAAAAAAAPAAkAAAAAAAAAAAAPAAoAAAAAAAAAAAAPAAsAAAAAAAAAAAAPAAwAAAAAAAAAAAAPAA0AAAAAAAAAAAAPAA4AAAAAAAAAAAAPAA8AAAAAAAAAAAAQAPf/AAAAAAAAAAAQAPj/AAAAAAAAAAAQAPn/AAAAAAAAAAAQAPr/AAAAAAAAAAAQAPv/AAAAAAAAAAAQAAkAAAAAAAAAAAAQAAoAAAAAAAAAAAAQAAsAAAAAAAAAAAAQAAwAAAAAAAAAAAAQAA0AAAAAAAAAAAAQAA4AAAAAAAAAAAAQAA8AAAAAAAAAAAARAPf/AAAAAAAAAAARAPj/AAAAAAAAAAARAPn/AAAAAAAAAAARAPr/AAAAAAAAAAARAPv/AAAAAAAAAAARAAkAAAAAAAAAAAARAAoAAAAAAAAAAAARAAsAAAAAAAAAAAARAAwAAAAAAAAAAAARAA0AAAAAAAAAAAARAA4AAAAAAAAAAAARAA8AAAAAAAAAAAASAPf/AAAAAAAAAAASAPj/AAAAAAAAAAASAPn/AAAAAAAAAAASAPr/AAAAAAAAAAASAPv/AAAAAAAAAAASAAkAAAAAAAAAAAASAAoAAAAAAAAAAAASAAsAAAAAAAAAAAASAAwAAAAAAAAAAAASAA0AAAAAAAAAAAASAA4AAAAAAAAAAAASAA8AAAAAAAAAAAATAPf/AAAAAAAAAAATAPj/AAAAAAAAAAATAPn/AAAAAAAAAAATAPr/AAAAAAAAAAATAPv/AAAAAAAAAAATAAkAAAAAAAAAAAATAAoAAAAAAAAAAAATAAsAAAAAAAAAAAATAAwAAAAAAAAAAAATAA0AAAAAAAAAAAATAA4AAAAAAAAAAAATAA8AAAAAAAAAAAAUAPf/AAAAAAAAAAAUAPj/AAAAAAAAAAAUAPn/AAAAAAAAAAAUAPr/AAAAAAAAAAAUAPv/AAAAAAAAAAAUAAkAAAAAAAAAAAAUAAoAAAAAAAAAAAAUAAsAAAAAAAAAAAAUAAwAAAAAAAAAAAAUAA0AAAAAAAAAAAAUAA4AAAAAAAAAAAAUAA8AAAAAAAAAAAAVAPf/AAAAAAAAAAAVAPj/AAAAAAAAAAAVAPn/AAAAAAAAAAAVAPr/AAAAAAAAAAAVAPv/AAAAAAAAAAAVAAkAAAAAAAAAAAAVAAoAAAAAAAAAAAAVAAsAAAAAAAAAAAAVAAwAAAAAAAAAAAAVAA0AAAAAAAAAAAAVAA4AAAAAAAAAAAAVAA8AAAAAAAAAAAAWAPf/AAAAAAAAAAAWAPj/AAAAAAAAAAAWAPn/AAAAAAAAAAAWAPr/AAAAAAAAAAAWAPv/AAAAAAAAAAAWAAkAAAAAAAAAAAAWAAoAAAAAAAAAAAAWAAsAAAAAAAAAAAAWAAwAAAAAAAAAAAAWAA0AAAAAAAAAAAAWAA4AAAAAAAAAAAAWAA8AAAAAAAAAAAAXAPf/AAAAAAAAAAAXAPj/AAAAAAAAAAAXAPn/AAAAAAAAAAAXAAIAAAAAAAAAAAAXAAMAAAAAAAAAAAAXAAQAAAAAAAAAAAAXAAkAAAAAAAAAAAAXAAoAAAAAAAAAAAAXAAsAAAAAAAAAAAAXAAwAAAAAAAAAAAAXAA0AAAAAAAAAAAAXAA4AAAAAAAAAAAAXAA8AAAAAAAAAAAAYAPf/AAAAAAAAAAAYAPj/AAAAAAAAAAAYAPn/AAAAAAAAAAAYAAIAAAAAAAAAAAAYAAMAAAAAAAAAAAAYAAQAAAAAAAAAAAAYAAkAAAAAAAAAAAAYAAoAAAAAAAAAAAAYAAsAAAAAAAAAAAAYAAwAAAAAAAAAAAAYAA0AAAAAAAAAAAAYAA4AAAAAAAAAAAAYAA8AAAAAAAAAAAAZAPf/AAAAAAAAAAAZAPj/AAAAAAAAAAAZAPn/AAAAAAAAAAAZAAIAAAAAAAAAAAAZAAMAAAAAAAAAAAAZAAQAAAAAAAAAAAAZAAkAAAAAAAAAAAAZAAoAAAAAAAAAAAAZAAsAAAAAAAAAAAAZAAwAAAAAAAAAAAAZAA0AAAAAAAAAAAAZAA4AAAAAAAAAAAAZAA8AAAAAAAAAAAAaAPf/AAAAAAAAAAAaAPj/AAAAAAAAAAAaAPn/AAAAAAAAAAAaAP//AAAAAAAAAAAaAAAAAAAAAAAAAAAaAAEAAAAAAAAAAAAaAAIAAAAAAAAAAAAaAAMAAAAAAAAAAAAaAAQAAAAAAAAAAAAaAAUAAAAAAAAAAAAaAAYAAAAAAAAAAAAaAAcAAAAAAAAAAAAaAAgAAAAAAAAAAAAaAAkAAAAAAAAAAAAaAAoAAAAAAAAAAAAaAAsAAAAAAAAAAAAaAAwAAAAAAAAAAAAaAA0AAAAAAAAAAAAaAA4AAAAAAAAAAAAaAA8AAAAAAAAAAAAbAPf/AAAAAAAAAAAbAPj/AAAAAAAAAAAbAPn/AAAAAAAAAAAbAPr/AAAAAAAAAAAbAPv/AAAAAAAAAAAbAPz/AAAAAAAAAAAbAP3/AAAAAAAAAAAbAP7/AAAAAAAAAAAbAP//AAAAAAAAAAAbAAAAAAAAAAAAAAAbAAEAAAAAAAAAAAAbAAIAAAAAAAAAAAAbAAMAAAAAAAAAAAAbAAQAAAAAAAAAAAAbAAUAAAAAAAAAAAAbAAYAAAAAAAAAAAAbAAcAAAAAAAAAAAAbAAgAAAAAAAAAAAAbAAkAAAAAAAAAAAAbAAoAAAAAAAAAAAAbAAsAAAAAAAAAAAAbAAwAAAAAAAAAAAAbAA0AAAAAAAAAAAAbAA4AAAAAAAAAAAAbAA8AAAAAAAAAAAAcAPf/AAAAAAAAAAAcAPj/AAAAAAAAAAAcAPn/AAAAAAAAAAAcAPr/AAAAAAAAAAAcAPv/AAAAAAAAAAAcAPz/AAAAAAAAAAAcAP3/AAAAAAAAAAAcAP7/AAAAAAAAAAAcAP//AAAAAAAAAAAcAAAAAAAAAAAAAAAcAAEAAAAAAAAAAAAcAAIAAAAAAAAAAAAcAAMAAAAAAAAAAAAcAAQAAAAAAAAAAAAcAAUAAAAAAAAAAAAcAAYAAAAAAAAAAAAcAAcAAAAAAAAAAAAcAAgAAAAAAAAAAAAcAAkAAAAAAAAAAAAcAAoAAAAAAAAAAAAcAAsAAAAAAAAAAAAcAAwAAAAAAAAAAAAcAA0AAAAAAAAAAAAcAA4AAAAAAAAAAAAcAA8AAAAAAAAAAAAdAPf/AAAAAAAAAAAdAPj/AAAAAAAAAAAdAPn/AAAAAAAAAAAdAPr/AAAAAAAAAAAdAPv/AAAAAAAAAAAdAPz/AAAAAAAAAAAdAP3/AAAAAAAAAAAdAP7/AAAAAAAAAAAdAP//AAAAAAAAAAAdAAAAAAAAAAAAAAAdAAEAAAAAAAAAAAAdAAIAAAAAAAAAAAAdAAMAAAAAAAAAAAAdAAQAAAAAAAAAAAAdAAUAAAAAAAAAAAAdAAYAAAAAAAAAAAAdAAcAAAAAAAAAAAAdAAgAAAAAAAAAAAAdAAkAAAAAAAAAAAAdAAoAAAAAAAAAAAAdAAsAAAAAAAAAAAAdAAwAAAAAAAAAAAAdAA0AAAAAAAAAAAAdAA4AAAAAAAAAAAAdAA8AAAAAAAAAAAAeAPf/AAAAAAAAAAAeAPj/AAAAAAAAAAAeAPn/AAAAAAAAAAAeAPr/AAAAAAAAAAAeAPv/AAAAAAAAAAAeAPz/AAAAAAAAAAAeAP3/AAAAAAAAAAAeAP7/AAAAAAAAAAAeAP//AAAAAAAAAAAeAAAAAAAAAAAAAAAeAAEAAAAAAAAAAAAeAAIAAAAAAAAAAAAeAAMAAAAAAAAAAAAeAAQAAAAAAAAAAAAeAAUAAAAAAAAAAAAeAAYAAAAAAAAAAAAeAAcAAAAAAAAAAAAeAAgAAAAAAAAAAAAeAAkAAAAAAAAAAAAeAAoAAAAAAAAAAAAeAAsAAAAAAAAAAAAeAAwAAAAAAAAAAAAeAA0AAAAAAAAAAAAeAA4AAAAAAAAAAAAeAA8AAAAAAAAAAAA=") +tile_set = ExtResource("2_dhrfw") + +[node name="Foam" type="TileMapLayer" parent="TileMapLayers" unique_id=1920376928] +tile_map_data = PackedByteArray("AAAHAPz/AgAAAAAAAAAIAPz/AgAAAAAAAAAJAPz/AgAAAAAAAAAKAPz/AgAAAAAAAAALAPz/AgAAAAAAAAAMAPz/AgAAAAAAAAANAPz/AgAAAAAAAAAOAPz/AgAAAAAAAAAPAPz/AgAAAAAAAAAQAPz/AgAAAAAAAAARAPz/AgAAAAAAAAASAPz/AgAAAAAAAAATAPz/AgAAAAAAAAAUAPz/AgAAAAAAAAAVAPz/AgAAAAAAAAAWAPz/AgAAAAAAAAAXAPz/AgAAAAAAAAAXAPr/AgAAAAAAAAAXAPv/AgAAAAAAAAAYAPr/AgAAAAAAAAAZAPr/AgAAAAAAAAAaAPr/AgAAAAAAAAAaAPv/AgAAAAAAAAAaAPz/AgAAAAAAAAAaAP3/AgAAAAAAAAAaAP7/AgAAAAAAAAAZAP//AgAAAAAAAAAZAAAAAgAAAAAAAAAZAAEAAgAAAAAAAAAYAAEAAgAAAAAAAAAXAAEAAgAAAAAAAAAWAAEAAgAAAAAAAAAWAAIAAgAAAAAAAAAWAAMAAgAAAAAAAAAWAAQAAgAAAAAAAAAWAAUAAgAAAAAAAAAXAAUAAgAAAAAAAAAYAAUAAgAAAAAAAAAZAAUAAgAAAAAAAAAZAAYAAgAAAAAAAAAZAAcAAgAAAAAAAAAZAAgAAgAAAAAAAAAYAAgAAgAAAAAAAAAXAAgAAgAAAAAAAAAWAAgAAgAAAAAAAAAVAAgAAgAAAAAAAAAUAAgAAgAAAAAAAAATAAgAAgAAAAAAAAASAAgAAgAAAAAAAAARAAgAAgAAAAAAAAAQAAgAAgAAAAAAAAAPAAgAAgAAAAAAAAAOAAgAAgAAAAAAAAANAAgAAgAAAAAAAAAMAAgAAgAAAAAAAAALAAgAAgAAAAAAAAAKAAgAAgAAAAAAAAAJAAgAAgAAAAAAAAAJAA0AAgAAAAAAAAAJAAwAAgAAAAAAAAAJAAsAAgAAAAAAAAAJAAoAAgAAAAAAAAAJAAkAAgAAAAAAAAAIAA0AAgAAAAAAAAAHAA0AAgAAAAAAAAAHAAwAAgAAAAAAAAAHAAsAAgAAAAAAAAAHAAoAAgAAAAAAAAAHAAkAAgAAAAAAAAAHAAgAAgAAAAAAAAAHAAcAAgAAAAAAAAAHAAYAAgAAAAAAAAAHAAUAAgAAAAAAAAAHAAQAAgAAAAAAAAAHAAMAAgAAAAAAAAAHAAIAAgAAAAAAAAAHAAEAAgAAAAAAAAAHAAAAAgAAAAAAAAAHAP//AgAAAAAAAAAHAP7/AgAAAAAAAAAHAP3/AgAAAAAAAAA=") +tile_set = ExtResource("3_8uyjj") + +[node name="Ground" type="TileMapLayer" parent="TileMapLayers" unique_id=284844611] +tile_map_data = PackedByteArray("AAAZAAgAAQACAAIAAAAYAAgAAQABAAIAAAAXAAgAAQABAAIAAAAWAAgAAQABAAIAAAAVAAgAAQABAAIAAAAUAAgAAQABAAIAAAATAAgAAQABAAIAAAASAAgAAQABAAIAAAARAAgAAQABAAIAAAAQAAgAAQABAAIAAAAPAAgAAQABAAIAAAAOAAgAAQABAAIAAAANAAgAAQABAAIAAAAMAAgAAQABAAIAAAALAAgAAQABAAIAAAAKAAgAAQABAAIAAAAJAAgAAQABAAEAAAAIAAgAAQABAAEAAAAHAAgAAQAAAAEAAAAJAAcAAQABAAEAAAAJAAYAAQABAAEAAAAJAAUAAQABAAEAAAAJAAQAAQABAAEAAAAJAAMAAQABAAEAAAAJAAIAAQABAAEAAAAJAAEAAQABAAEAAAAJAAAAAQABAAEAAAAJAP//AQABAAEAAAAIAAcAAQABAAEAAAAIAAYAAQABAAEAAAAIAAUAAQABAAEAAAAIAAQAAQABAAEAAAAIAAMAAQABAAEAAAAIAAIAAQABAAEAAAAIAAEAAQABAAEAAAAIAAAAAQABAAEAAAAIAP//AQABAAEAAAAHAAcAAQAAAAEAAAAHAAYAAQAAAAEAAAAHAAUAAQAAAAEAAAAHAAQAAQAAAAEAAAAHAAMAAQAAAAEAAAAHAAIAAQAAAAEAAAAHAAEAAQAAAAEAAAAHAAAAAQAAAAEAAAAHAP//AQAAAAEAAAAZAAEAAQACAAIAAAAZAAAAAQACAAEAAAAZAP//AQACAAEAAAAYAAEAAQABAAIAAAAYAAAAAQABAAEAAAAYAP//AQABAAEAAAAXAAEAAQABAAIAAAAXAAAAAQABAAEAAAAXAP//AQABAAEAAAAWAAIAAQACAAEAAAAWAAEAAQABAAEAAAAWAAAAAQABAAEAAAAWAP//AQABAAEAAAAVAAIAAQABAAEAAAAVAAEAAQABAAEAAAAVAAAAAQABAAEAAAAVAP//AQABAAEAAAAUAAIAAQABAAEAAAAUAAEAAQABAAEAAAAUAAAAAQABAAEAAAAUAP//AQABAAEAAAATAAIAAQABAAEAAAATAAEAAQABAAEAAAATAAAAAQABAAEAAAATAP//AQABAAEAAAASAAIAAQABAAEAAAASAAEAAQABAAEAAAASAAAAAQABAAEAAAASAP//AQABAAEAAAARAAIAAQABAAEAAAARAAEAAQABAAEAAAARAAAAAQABAAEAAAARAP//AQABAAEAAAAQAAIAAQABAAEAAAAQAAEAAQABAAEAAAAQAAAAAQABAAEAAAAQAP//AQABAAEAAAAPAAIAAQABAAEAAAAPAAEAAQABAAEAAAAPAAAAAQABAAEAAAAPAP//AQABAAEAAAAOAAIAAQABAAEAAAAOAAEAAQABAAEAAAAOAAAAAQABAAEAAAAOAP//AQABAAEAAAANAAIAAQABAAEAAAANAAEAAQABAAEAAAANAAAAAQABAAEAAAANAP//AQABAAEAAAAMAAIAAQABAAEAAAAMAAEAAQABAAEAAAAMAAAAAQABAAEAAAAMAP//AQABAAEAAAALAAIAAQABAAEAAAALAAEAAQABAAEAAAALAAAAAQABAAEAAAALAP//AQABAAEAAAAKAAIAAQABAAEAAAAKAAEAAQABAAEAAAAKAAAAAQABAAEAAAAKAP//AQABAAEAAAAZAP7/AQABAAEAAAAYAP7/AQABAAEAAAAXAP7/AQABAAEAAAAWAP7/AQABAAEAAAAVAP7/AQABAAEAAAAUAP7/AQABAAEAAAATAP7/AQABAAEAAAASAP7/AQABAAEAAAARAP7/AQABAAEAAAAQAP7/AQABAAEAAAAPAP7/AQABAAEAAAAOAP7/AQABAAEAAAANAP7/AQABAAEAAAAMAP7/AQABAAEAAAALAP7/AQABAAEAAAAKAP7/AQABAAEAAAAJAP7/AQABAAEAAAAIAP7/AQABAAEAAAAHAP7/AQAAAAEAAAAZAAcAAQACAAEAAAAYAAcAAQABAAEAAAAXAAcAAQABAAEAAAAWAAcAAQABAAEAAAAWAAMAAQACAAEAAAAVAAMAAQABAAEAAAAUAAMAAQABAAEAAAATAAMAAQABAAEAAAASAAMAAQABAAEAAAARAAMAAQABAAEAAAAQAAMAAQABAAEAAAAPAAMAAQABAAEAAAAOAAMAAQABAAEAAAANAAMAAQABAAEAAAAMAAMAAQABAAEAAAALAAMAAQABAAEAAAAKAAMAAQABAAEAAAAVAP3/AQABAAEAAAAUAP3/AQABAAEAAAATAP3/AQABAAEAAAASAP3/AQABAAEAAAARAP3/AQABAAEAAAAQAP3/AQABAAEAAAAPAP3/AQABAAEAAAAOAP3/AQABAAEAAAANAP3/AQABAAEAAAAMAP3/AQABAAEAAAALAP3/AQABAAEAAAAKAP3/AQABAAEAAAAZAAYAAQACAAEAAAAZAAUAAQACAAAAAAAYAAYAAQABAAEAAAAYAAUAAQABAAAAAAAXAAYAAQABAAEAAAAXAAUAAQABAAAAAAAWAAYAAQABAAEAAAAWAAUAAQABAAEAAAAWAAQAAQACAAEAAAAVAAcAAQABAAEAAAAVAAYAAQABAAEAAAAVAAUAAQABAAEAAAAVAAQAAQABAAEAAAAUAAcAAQABAAEAAAAUAAYAAQABAAEAAAAUAAUAAQABAAEAAAAUAAQAAQABAAEAAAATAAcAAQABAAEAAAATAAYAAQABAAEAAAATAAUAAQABAAEAAAATAAQAAQABAAEAAAASAAcAAQABAAEAAAASAAYAAQABAAEAAAASAAUAAQABAAEAAAASAAQAAQABAAEAAAARAAcAAQABAAEAAAARAAYAAQABAAEAAAARAAUAAQABAAEAAAARAAQAAQABAAEAAAAQAAcAAQABAAEAAAAQAAYAAQABAAEAAAAQAAUAAQABAAEAAAAQAAQAAQABAAEAAAAPAAcAAQABAAEAAAAPAAYAAQABAAEAAAAPAAUAAQABAAEAAAAPAAQAAQABAAEAAAAOAAcAAQABAAEAAAAOAAYAAQABAAEAAAAOAAUAAQABAAEAAAAOAAQAAQABAAEAAAANAAcAAQABAAEAAAANAAYAAQABAAEAAAANAAUAAQABAAEAAAANAAQAAQABAAEAAAAMAAcAAQABAAEAAAAMAAYAAQABAAEAAAAMAAUAAQABAAEAAAAMAAQAAQABAAEAAAALAAcAAQABAAEAAAALAAYAAQABAAEAAAALAAUAAQABAAEAAAALAAQAAQABAAEAAAAKAAcAAQABAAEAAAAKAAYAAQABAAEAAAAKAAUAAQABAAEAAAAKAAQAAQABAAEAAAAJAA0AAQACAAIAAAAJAAwAAQACAAEAAAAJAAsAAQACAAEAAAAJAAoAAQACAAEAAAAJAAkAAQACAAEAAAAIAA0AAQABAAIAAAAIAAwAAQABAAEAAAAIAAsAAQABAAEAAAAIAAoAAQABAAEAAAAIAAkAAQABAAEAAAAHAA0AAQAAAAIAAAAHAAwAAQAAAAEAAAAHAAsAAQAAAAEAAAAHAAoAAQAAAAEAAAAHAAkAAQAAAAEAAAAKAPz/AQABAAAAAAAJAP3/AQABAAEAAAAJAPz/AQABAAAAAAAIAP3/AQABAAEAAAAIAPz/AQABAAAAAAAHAP3/AQAAAAEAAAAHAPz/AQAAAAAAAAAaAP3/AQACAAEAAAAaAPz/AQACAAEAAAAZAP3/AQABAAEAAAAZAPz/AQABAAEAAAAYAP3/AQABAAEAAAAYAPz/AQABAAEAAAAXAP3/AQABAAEAAAAXAPz/AQABAAEAAAAWAP3/AQABAAEAAAAWAPz/AQABAAAAAAAVAPz/AQABAAAAAAAUAPz/AQABAAAAAAATAPz/AQABAAAAAAASAPz/AQABAAAAAAARAPz/AQABAAAAAAAQAPz/AQABAAAAAAAPAPz/AQABAAAAAAAOAPz/AQABAAAAAAANAPz/AQABAAAAAAAMAPz/AQABAAAAAAALAPz/AQABAAAAAAAaAP7/AQACAAIAAAAaAPv/AQACAAEAAAAaAPr/AQACAAAAAAAZAPv/AQABAAEAAAAZAPr/AQABAAAAAAAYAPv/AQABAAEAAAAYAPr/AQABAAAAAAAXAPv/AQAAAAEAAAAXAPr/AQAAAAAAAAA=") +tile_set = ExtResource("4_4c3u0") + +[node name="Paths" type="TileMapLayer" parent="TileMapLayers" unique_id=1247853237] +tile_map_data = PackedByteArray("AAAIAAYAAQAIAAEAAAAIAAUAAQAIAAEAAAAIAAQAAQAIAAEAAAAIAAMAAQAIAAEAAAAIAAIAAQAIAAEAAAAIAAEAAQAIAAEAAAAXAAAAAQAGAAMAAAAWAAAAAQAGAAMAAAAVAAAAAQAGAAMAAAAUAAAAAQAFAAEAAAAMAAAAAQAHAAEAAAALAAAAAQAGAAMAAAAKAAAAAQAGAAMAAAAJAAAAAQAGAAMAAAAIAAAAAQAFAAAAAAAYAAAAAQAHAAIAAAAYAP//AQAIAAEAAAAUAP7/AQAIAAEAAAAUAP//AQAIAAEAAAAMAP3/AQAFAAAAAAANAP3/AQAGAAMAAAAOAP3/AQAGAAMAAAAPAP3/AQAGAAMAAAAQAP3/AQAGAAAAAAARAP3/AQAGAAMAAAASAP3/AQAGAAMAAAATAP3/AQAGAAMAAAAUAP3/AQAHAAAAAAAMAP//AQAIAAEAAAAMAP7/AQAIAAEAAAAMAAEAAQAIAAEAAAAMAAIAAQAIAAEAAAAMAAMAAQAFAAIAAAANAAMAAQAGAAMAAAAOAAMAAQAGAAMAAAAPAAMAAQAGAAMAAAAQAAMAAQAGAAIAAAARAAMAAQAGAAMAAAASAAMAAQAGAAMAAAATAAMAAQAGAAMAAAAUAAMAAQAHAAIAAAAUAAEAAQAIAAEAAAAUAAIAAQAIAAEAAAAYAPz/AQAFAAEAAAAYAP3/AQAIAAEAAAAYAP7/AQAIAAEAAAAYAPv/AQAFAAAAAAAZAPz/AQAHAAIAAAAZAPv/AQAHAAAAAAAIAAcAAQAFAAEAAAAXAAcAAQAGAAIAAAAYAAcAAQAHAAIAAAAJAAcAAQAGAAMAAAAKAAcAAQAGAAMAAAALAAcAAQAGAAMAAAAMAAcAAQAGAAMAAAANAAcAAQAGAAMAAAAOAAcAAQAGAAMAAAAPAAcAAQAGAAMAAAAQAAcAAQAGAAMAAAARAAcAAQAGAAMAAAASAAcAAQAGAAMAAAATAAcAAQAGAAMAAAAUAAcAAQAGAAMAAAAVAAcAAQAGAAMAAAAWAAcAAQAGAAMAAAAXAAYAAQAFAAAAAAAYAAYAAQAHAAAAAAAIAAwAAQAIAAIAAAAIAAsAAQAIAAEAAAAIAAoAAQAIAAEAAAAIAAkAAQAIAAEAAAAIAAgAAQAIAAEAAAAQAAIAAQAIAAEAAAAQAAEAAQAIAAEAAAAQAAAAAQAIAAEAAAAQAP//AQAIAAEAAAAQAP7/AQAIAAEAAAA=") +tile_set = ExtResource("4_4c3u0") + +[node name="Shadows" type="TileMapLayer" parent="TileMapLayers" unique_id=1138255870] +tile_map_data = PackedByteArray("AAAKAAUAAAAAAAAAAAALAAUAAAAAAAAAAAAMAAUAAAAAAAAAAAANAAUAAAAAAAAAAAAOAAUAAAAAAAAAAAAPAAUAAAAAAAAAAAAQAAUAAAAAAAAAAAARAAUAAAAAAAAAAAASAAUAAAAAAAAAAAATAAUAAAAAAAAAAAAUAAUAAAAAAAAAAAAVAAUAAAAAAAAAAAAWAAUAAAAAAAAAAAAWAAQAAAAAAAAAAAAWAAMAAAAAAAAAAAAWAAIAAAAAAAAAAAAKAAQAAAAAAAAAAAAKAAMAAAAAAAAAAAAKAAIAAAAAAAAAAAAKAPz/AAAAAAAAAAAKAP3/AAAAAAAAAAAKAP7/AAAAAAAAAAAKAP//AAAAAAAAAAALAPz/AAAAAAAAAAAMAPz/AAAAAAAAAAANAPz/AAAAAAAAAAAOAPz/AAAAAAAAAAAPAPz/AAAAAAAAAAAQAPz/AAAAAAAAAAARAPz/AAAAAAAAAAASAPz/AAAAAAAAAAATAPz/AAAAAAAAAAAUAPz/AAAAAAAAAAAVAPz/AAAAAAAAAAAWAPz/AAAAAAAAAAAWAP3/AAAAAAAAAAAWAP7/AAAAAAAAAAAWAP//AAAAAAAAAAASAAEAAAAAAAAAAAASAAAAAAAAAAAAAAAOAAEAAAAAAAAAAAAOAAAAAAAAAAAAAAA=") +tile_set = ExtResource("4_pppnl") + +[node name="Stones" type="TileMapLayer" parent="TileMapLayers" unique_id=15259980] +tile_map_data = PackedByteArray("AAAKAAEABAADAAAAAAAKAP3/BAADAAEAAAAKAAIABAADAAEAAAAKAPz/BAADAAEAAAASAAAABAADAAIAAAASAP//BAADAAAAAAAOAAAABAADAAIAAAAOAP//BAADAAAAAAAWAPz/BAADAAEAAAAWAP3/BAADAAEAAAAWAP7/BAADAAIAAAAWAAEABAADAAAAAAAWAAIABAADAAEAAAAVAPv/BAABAAQAAAAUAPv/BAABAAQAAAATAPv/BAABAAQAAAASAPv/BAABAAQAAAARAPv/BAABAAQAAAAQAPv/BAABAAQAAAAPAPv/BAABAAQAAAAOAPv/BAABAAQAAAANAPv/BAABAAQAAAAMAPv/BAABAAQAAAALAPv/BAABAAQAAAAKAPv/BAAAAAAAAAAWAPv/BAACAAAAAAAKAAQABAAAAAIAAAAKAAMABAADAAEAAAAWAAQABAACAAIAAAAVAAQABAABAAQAAAAUAAQABAABAAQAAAATAAQABAABAAQAAAASAAQABAABAAQAAAARAAQABAABAAQAAAAQAAQABAABAAQAAAAPAAQABAABAAQAAAAOAAQABAABAAQAAAANAAQABAABAAQAAAAMAAQABAABAAQAAAALAAQABAABAAQAAAAWAAMABAADAAEAAAAKAP7/BAADAAIAAAA=") +tile_set = ExtResource("5_vs7wd") + +[node name="OnTheGround" type="Node2D" parent="." unique_id=1769700087] +y_sort_enabled = true + +[node name="Player" parent="OnTheGround" unique_id=296354958 instance=ExtResource("7_a3wsq")] +position = Vector2(538, 810) +sprite_frames = ExtResource("8_f3fhx") + +[node name="Camera2D" type="Camera2D" parent="OnTheGround/Player" unique_id=2136918817] +limit_left = 416 +limit_top = -448 +limit_right = 1760 +limit_bottom = 960 +editor_draw_limits = true + +[node name="Signs" type="Node2D" parent="OnTheGround" unique_id=540449734] +y_sort_enabled = true + +[node name="MandatoryCollectibleItem" parent="OnTheGround" unique_id=327991066 instance=ExtResource("7_r84jh")] +position = Vector2(1536, 440) +item = ExtResource("8_me0l1") +target = 1 + +[node name="OptionalCollectibleItem" parent="OnTheGround" unique_id=510029093 instance=ExtResource("7_r84jh")] +unique_name_in_owner = true +position = Vector2(1599, -259) +item = ExtResource("9_vii7y") +collected_dialogue = ExtResource("12_8y2ta") +dialogue_title = &"wow" + +[node name="Townie" parent="OnTheGround" unique_id=1098678013 instance=ExtResource("10_fvfn2")] +position = Vector2(615, 426) +scale = Vector2(-1, 1) +character_seed = 738434190 +look_at_side = 1 + +[node name="InteractArea" type="Area2D" parent="OnTheGround/Townie" unique_id=1795419351 node_paths=PackedStringArray("marker")] +collision_layer = 32 +collision_mask = 0 +script = ExtResource("6_6kkli") +marker = NodePath("Marker2D") +metadata/_custom_type_script = "uid://du8wfijr35r35" + +[node name="CollisionShape2D" type="CollisionShape2D" parent="OnTheGround/Townie/InteractArea" unique_id=1395317728] +position = Vector2(0, -23.5) +shape = SubResource("RectangleShape2D_vs7wd") +debug_color = Color(0.6, 0.545, 0, 0.42) + +[node name="Marker2D" type="Marker2D" parent="OnTheGround/Townie/InteractArea" unique_id=1604575681] +position = Vector2(0, -88) + +[node name="TalkBehavior" type="Node" parent="OnTheGround/Townie" unique_id=1777083832 node_paths=PackedStringArray("interact_area")] +script = ExtResource("11_fvfn2") +dialogue = ExtResource("12_8y2ta") +interact_area = NodePath("../InteractArea") +metadata/_custom_type_script = "uid://edcifob4jc4s" + +[node name="Guard" parent="OnTheGround" unique_id=689832587 node_paths=PackedStringArray("patrol_path") instance=ExtResource("11_8y2ta")] +position = Vector2(1056, -160) +sprite_frames = ExtResource("12_pppnl") +alerted_sound_stream = ExtResource("13_vs7wd") +footsteps_sound_stream = ExtResource("14_72lml") +idle_sound_stream = ExtResource("15_l2eis") +alert_others_sound_stream = ExtResource("16_mxlxh") +patrol_path = NodePath("../PatrolPath") +move_speed = 200.0 + +[node name="PatrolPath" type="Path2D" parent="OnTheGround" unique_id=224740749] +position = Vector2(1056, -160) +curve = SubResource("Curve2D_gyr7v") + +[node name="Guard2" parent="OnTheGround" unique_id=123245618 node_paths=PackedStringArray("patrol_path") instance=ExtResource("11_8y2ta")] +position = Vector2(800, -160) +sprite_frames = ExtResource("21_vs7wd") +alerted_sound_stream = ExtResource("13_vs7wd") +footsteps_sound_stream = ExtResource("14_72lml") +idle_sound_stream = ExtResource("15_l2eis") +alert_others_sound_stream = ExtResource("16_mxlxh") +patrol_path = NodePath("../PatrolPath2") +move_speed = 200.0 + +[node name="PatrolPath2" type="Path2D" parent="OnTheGround" unique_id=2135394700] +position = Vector2(800, -160) +curve = SubResource("Curve2D_72lml") + +[node name="Checkpoint" parent="OnTheGround" unique_id=1789892556 instance=ExtResource("22_l2eis")] +position = Vector2(544, 35) +sprite_frames = ExtResource("23_mxlxh") + +[node name="CollisionShape2D" type="CollisionShape2D" parent="OnTheGround/Checkpoint" unique_id=1934354000] +shape = SubResource("RectangleShape2D_s5hwq") + +[node name="Checkpoint2" parent="OnTheGround" unique_id=1673516805 instance=ExtResource("22_l2eis")] +position = Vector2(1569, 34) + +[node name="CollisionShape2D" type="CollisionShape2D" parent="OnTheGround/Checkpoint2" unique_id=1467543648] +shape = SubResource("RectangleShape2D_s5hwq") + +[node name="CaveLighting" type="Area2D" parent="." unique_id=1100100279] +unique_name_in_owner = true +position = Vector2(-232, -213) +collision_layer = 0 + +[node name="CollisionShape2D" type="CollisionShape2D" parent="CaveLighting" unique_id=2136984051] +position = Vector2(1287, 171) +shape = SubResource("RectangleShape2D_me0l1") +debug_color = Color(0.95686275, 0, 0.53333336, 0.13333334) +metadata/_edit_lock_ = true + +[node name="CanvasModulate" type="CanvasModulate" parent="." unique_id=839196292] +unique_name_in_owner = true + +[node name="StealthGameLogic" type="Node" parent="." unique_id=2135139021] +script = ExtResource("19_pppnl") +metadata/_custom_type_script = "uid://dnp0tjloec2d7" + +[node name="HUD" parent="." unique_id=1426591993 instance=ExtResource("23_72lml")] diff --git a/scenes/dev/level_design/optional_threads/quest.tres b/scenes/dev/level_design/optional_threads/quest.tres new file mode 100644 index 0000000000..551e7f4eda --- /dev/null +++ b/scenes/dev/level_design/optional_threads/quest.tres @@ -0,0 +1,11 @@ +[gd_resource type="Resource" script_class="Quest" format=3 uid="uid://pdpqinul8nu5"] + +[ext_resource type="Script" uid="uid://dts1hwdy3phin" path="res://scenes/menus/storybook/components/quest.gd" id="1_cjuqo"] + +[resource] +script = ExtResource("1_cjuqo") +title = "Optional Threads" +description = "Shows how to add an optional thread to a scene." +first_scene = "uid://cplmo457h47p8" +threads_to_collect = 2 +metadata/_custom_type_script = "uid://dts1hwdy3phin" diff --git a/scenes/game_elements/characters/npcs/elder/components/elder.gd b/scenes/game_elements/characters/npcs/elder/components/elder.gd index 1f12f4550e..cbbe813dbc 100644 --- a/scenes/game_elements/characters/npcs/elder/components/elder.gd +++ b/scenes/game_elements/characters/npcs/elder/components/elder.gd @@ -60,6 +60,7 @@ func _ready() -> void: interact_area.interaction_ended.connect(_on_interaction_ended) animated_sprite_2d.connect("frame_changed", _on_frame_changed) + GameState.quest_changed.connect(_update_dialogue_cue) if GameState.quest: GameState.quest.inventory.item_collected.connect(_update_dialogue_cue) GameState.quest.inventory.item_consumed.connect(_update_dialogue_cue) diff --git a/scenes/game_elements/fx/dither/dither.gdshader b/scenes/game_elements/fx/dither/dither.gdshader new file mode 100644 index 0000000000..c557289174 --- /dev/null +++ b/scenes/game_elements/fx/dither/dither.gdshader @@ -0,0 +1,17 @@ +// Copyright 2024 KingToot +// SPDX-License-Identifier: CC0-1.0 +// https://godotshaders.com/shader/dithering-screen-door-transparency/ +shader_type canvas_item; + +uniform float intensity: hint_range(0.0, 1.0, 0.05) = 0.0; + +const mat4 THRESHOLD_MATRIX = mat4( + vec4(1.0 / 17.0, 9.0 / 17.0, 3.0 / 17.0, 11.0 / 17.0), + vec4(13.0 / 17.0, 5.0 / 17.0, 15.0 / 17.0, 7.0 / 17.0), + vec4(4.0 / 17.0, 12.0 / 17.0, 2.0 / 17.0, 10.0 / 17.0), + vec4(16.0 / 17.0, 8.0 / 17.0, 14.0 / 17.0, 6.0 / 17.0)); + +void fragment() { + vec2 uv = UV / TEXTURE_PIXEL_SIZE; + COLOR.a *= step(0.0, THRESHOLD_MATRIX[int(uv.x) % 4][int(uv.y) % 4] - intensity); +} diff --git a/scenes/game_elements/fx/dither/dither.gdshader.uid b/scenes/game_elements/fx/dither/dither.gdshader.uid new file mode 100644 index 0000000000..7a12241974 --- /dev/null +++ b/scenes/game_elements/fx/dither/dither.gdshader.uid @@ -0,0 +1 @@ +uid://cj250ayvmpwf3 diff --git a/scenes/game_elements/fx/dither/dither.tres b/scenes/game_elements/fx/dither/dither.tres new file mode 100644 index 0000000000..e57e3b86ca --- /dev/null +++ b/scenes/game_elements/fx/dither/dither.tres @@ -0,0 +1,8 @@ +[gd_resource type="ShaderMaterial" format=3 uid="uid://dlnyw2c84jxlj"] + +[ext_resource type="Shader" uid="uid://cj250ayvmpwf3" path="res://scenes/game_elements/fx/dither/dither.gdshader" id="1_xv4rv"] + +[resource] +resource_local_to_scene = true +shader = ExtResource("1_xv4rv") +shader_parameter/intensity = 0.0 diff --git a/scenes/game_elements/props/collectible_item/collectible_item.tscn b/scenes/game_elements/props/collectible_item/collectible_item.tscn index 4581dcd741..a76ae4d310 100644 --- a/scenes/game_elements/props/collectible_item/collectible_item.tscn +++ b/scenes/game_elements/props/collectible_item/collectible_item.tscn @@ -2,6 +2,7 @@ [ext_resource type="Script" uid="uid://be7lr8ty24cix" path="res://scenes/game_elements/props/collectible_item/components/collectible_item.gd" id="1_7ff3m"] [ext_resource type="Script" uid="uid://du8wfijr35r35" path="res://scenes/game_elements/props/interact_area/interact_area.gd" id="2_1kegn"] +[ext_resource type="Material" uid="uid://dlnyw2c84jxlj" path="res://scenes/game_elements/fx/dither/dither.tres" id="4_hhgm5"] [ext_resource type="AudioStream" uid="uid://bhgpds38q0j4r" path="res://assets/third_party/sounds/collectibles/MemoryCollectable.ogg" id="5_hhgm5"] [ext_resource type="AudioStream" uid="uid://38xwdjm46478" path="res://assets/third_party/sounds/collectibles/CollectibleAppear.ogg" id="5_oh62b"] @@ -322,6 +323,7 @@ libraries/ = SubResource("AnimationLibrary_8dm1m") [node name="Sprite2D" type="Sprite2D" parent="." unique_id=1620327010] visible = false modulate = Color(1, 1, 1, 0) +material = ExtResource("4_hhgm5") [node name="CollectedSound" type="AudioStreamPlayer" parent="." unique_id=420528907] unique_name_in_owner = true diff --git a/scenes/game_elements/props/collectible_item/components/collectible_item.gd b/scenes/game_elements/props/collectible_item/components/collectible_item.gd index 76583394e7..04415c663c 100644 --- a/scenes/game_elements/props/collectible_item/components/collectible_item.gd +++ b/scenes/game_elements/props/collectible_item/components/collectible_item.gd @@ -31,6 +31,16 @@ extends SceneLink ## The dialogue title from where [member collected_dialogue] will start. @export var dialogue_title: StringName = "" +var _ghost := false: + set(new_value): + _ghost = new_value + # TODO: effect is ugly + var sm := sprite_2d.material as ShaderMaterial + sm.set_shader_parameter("intensity", 0.25 if _ghost else 0.0) +# Key in GameState.quest.facts which, if present, indicates that this item was +# previously collected on the current run. +var _fact_key: String + @onready var interact_area: InteractArea = $InteractArea @onready var animation_player: AnimationPlayer = $AnimationPlayer @onready var sprite_2d: Sprite2D = $Sprite2D @@ -82,6 +92,21 @@ func _ready() -> void: if Engine.is_editor_hint(): return + # Collected in current run through this quest? + # TODO: if TaggedItem was constructed here we could put a workaround for + # use_unique_path not working there. + _fact_key = "%s:%s:collected" % [owner.scene_file_path, owner.get_path_to(self, true)] + if GameState.quest.facts.get(_fact_key, false): + # We assume any side-effects from collecting it have also been saved. + queue_free() + return + + # Collected in previous run? TODO: supposedly quest.inventory is populated + # from global.inventory when starting quest so only the latter check should + # be needed + if GameState.global.inventory.has(item, self) or GameState.quest.inventory.has(item, self): + _ghost = true + interact_area.interaction_started.connect(self._on_interacted) @@ -111,13 +136,15 @@ func _on_interacted(player: Player, _from_right: bool) -> void: animation_player.play("collected") await animation_player.animation_finished - GameState.quest.inventory.add_collected_item(item) + if not _ghost: + GameState.quest.inventory.add_collected_item(item, self) if collected_dialogue: DialogueManager.show_dialogue_balloon(collected_dialogue, dialogue_title, [self, player]) await DialogueManager.dialogue_ended interact_area.end_interaction() + GameState.quest.facts[_fact_key] = true queue_free() if next_scene: 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 e65ebb9d60..f43cf1ae1c 100644 --- a/scenes/game_elements/props/eternal_loom/components/eternal_loom.gd +++ b/scenes/game_elements/props/eternal_loom/components/eternal_loom.gd @@ -43,8 +43,8 @@ func show_retelling_dialogue() -> void: func _has_magical_thread_of_type(type: InventoryItem.ItemType) -> bool: - for item: InventoryItem in GameState.quest.inventory.items: - if item.type == type: + for ci: TaggedItem in GameState.quest.inventory.items: + if ci.item.type == type: return true return false @@ -92,7 +92,7 @@ func on_offering_succeeded() -> void: loom_offering_animation.play_loom_animation() await loom_offering_animation.animation_finished - GameState.quest.inventory.clear_inventory() + GameState.global.inventory.merge(GameState.quest.inventory) if load_error != OK: on_rewoven_finished() @@ -121,7 +121,6 @@ func is_item_offering_possible() -> bool: if not GameState.quest: return false - if GameState.quest.quest.threads_to_collect <= 0: - return false - - return GameState.quest.inventory.items.size() >= GameState.quest.quest.threads_to_collect + # TODO: only if there are *new* threads collected. Problem is - what + # triggers the end-of-quest cutscene if there are no new threads? + return true diff --git a/scenes/game_elements/props/eternal_loom/components/loom_offering_animation.gd b/scenes/game_elements/props/eternal_loom/components/loom_offering_animation.gd index cf1f48b70d..2482284b14 100644 --- a/scenes/game_elements/props/eternal_loom/components/loom_offering_animation.gd +++ b/scenes/game_elements/props/eternal_loom/components/loom_offering_animation.gd @@ -18,7 +18,10 @@ signal animation_finished func play_loom_animation() -> void: - _loom_animation_play(GameState.quest.inventory.items) + var items: Array[InventoryItem] + for ti: TaggedItem in GameState.quest.inventory.items: + items.append(ti.item) + _loom_animation_play(items) ## Only used for the in-editor preview diff --git a/scenes/globals/game_state/game_state.gd b/scenes/globals/game_state/game_state.gd index ea2a9618ea..3b362eec83 100644 --- a/scenes/globals/game_state/game_state.gd +++ b/scenes/globals/game_state/game_state.gd @@ -7,6 +7,10 @@ extends Node ## signals like [signal PlayerState.abilities_changed]. signal player_changed(old: PlayerState, new: PlayerState) +## Emitted when [member quest] changes, due to a quest being started, completed, +## or abandoned. +signal quest_changed + const SAVE_PATH := "user://saved_game.tres" ## The player abilities to have from the beginning @@ -41,6 +45,7 @@ var quest: QuestState: var old_player_state := player _saved_game.quest = new_value player_changed.emit(old_player_state, player) + quest_changed.emit() ## State concerning the current scene, or [code]null[/code] if there is no current scene var scene: PerSceneState: @@ -137,6 +142,9 @@ func set_quest(new_quest: Quest) -> void: quest_player_state = PlayerState.new() quest = QuestState.new(new_quest, quest_player_state) + quest.inventory.items = global.inventory.items.filter( + func(i: TaggedItem) -> bool: return i.quest_path == new_quest.resource_path + ) ## Guess which quest the given scene is part of, and set [member quest] diff --git a/scenes/globals/game_state/global_state.gd b/scenes/globals/game_state/global_state.gd index bc08101cdf..0a585a78e4 100644 --- a/scenes/globals/game_state/global_state.gd +++ b/scenes/globals/game_state/global_state.gd @@ -45,6 +45,8 @@ signal helper_changed @export var helper: HelperCharacterState +@export var inventory: InventoryState = InventoryState.new() + ## Quests that were started & abandoned without being completed, keyed by ## [member Resource.resource_path] of each [Quest]. ## Captures the [QuestState] and [PerSceneState] at point, so that the player diff --git a/scenes/globals/game_state/inventory/tagged_item.gd b/scenes/globals/game_state/inventory/tagged_item.gd new file mode 100644 index 0000000000..b588ef76aa --- /dev/null +++ b/scenes/globals/game_state/inventory/tagged_item.gd @@ -0,0 +1,35 @@ +# SPDX-FileCopyrightText: The Threadbare Authors +# SPDX-License-Identifier: MPL-2.0 +class_name TaggedItem +extends Resource +## An [InventoryItem] that knows where it came from + +@export var item: InventoryItem +@export var quest_path: String +@export var source_scene: String +## TODO: construct unique node path here... +@export var source_node: NodePath + + +static func make(an_item: InventoryItem, quest: Quest, a_source_node: Node = null) -> TaggedItem: + var ci := new() + ci.item = an_item + ci.quest_path = quest.resource_path if quest else "" + if a_source_node: + ci.source_scene = a_source_node.owner.scene_file_path + # TODO: use_unique_path doesn't work when called on the target's owner. + # Reported as https://github.com/godotengine/godot/issues/122411. If + # eventually fixed, remove the condition and just keep the else branch. + if a_source_node.unique_name_in_owner: + ci.source_node = "%" + a_source_node.name + else: + ci.source_node = a_source_node.owner.get_path_to(a_source_node, true) + return ci + + +func matches(that: TaggedItem) -> bool: + return ( + self.item == that.item + and self.source_scene == that.source_scene + and self.source_node == that.source_node + ) diff --git a/scenes/globals/game_state/inventory/tagged_item.gd.uid b/scenes/globals/game_state/inventory/tagged_item.gd.uid new file mode 100644 index 0000000000..8003d1ee50 --- /dev/null +++ b/scenes/globals/game_state/inventory/tagged_item.gd.uid @@ -0,0 +1 @@ +uid://cvmcbvc6skodh diff --git a/scenes/globals/game_state/inventory_state.gd b/scenes/globals/game_state/inventory_state.gd index ec7899afe8..f547bf1ba1 100644 --- a/scenes/globals/game_state/inventory_state.gd +++ b/scenes/globals/game_state/inventory_state.gd @@ -5,27 +5,55 @@ class_name InventoryState extends Resource ## Emitted when a new item is collected and added to [member items]. -signal item_collected(item: InventoryItem) +signal item_collected(item: TaggedItem) ## Emitted when a item is consumed, causing it to be removed from ## [member items]. -signal item_consumed(item: InventoryItem) +signal item_consumed(item: TaggedItem) ## Collected threads. Modify with [member add_collected_item] and ## [member clear_inventory]. -@export var items: Array[InventoryItem] +@export var items: Array[TaggedItem] -## Add the [InventoryItem] to [member items]. -func add_collected_item(item: InventoryItem) -> void: - items.append(item) - item_collected.emit(item) +## Move all items from [param other] to this inventory. +func merge(other: InventoryState) -> void: + for ci: TaggedItem in other.items: + if not _has(ci): + _add(ci) + other.clear_inventory() + + +## Returns true if the inventory contains [param item], +## collected in the current quest (TODO), specifically from [param source_node] +## (if provided). +func has(item: InventoryItem, source_node: Node = null) -> bool: + # TODO: should the caller make the TaggedItem instead? + return _has(TaggedItem.make(item, GameState.quest.quest, source_node)) + + +## Add [param item] to [member items], tagging it as having been collected in +## the current quest from [param source_node]. +func add_collected_item(item: InventoryItem, source_node: Node = null) -> void: + _add(TaggedItem.make(item, GameState.quest.quest, source_node)) + + +func _has(ci: TaggedItem) -> bool: + return items.find_custom(ci.matches) >= 0 + + +func _add(ci: TaggedItem) -> void: + if _has(ci): + return + + items.append(ci) + item_collected.emit(ci) emit_changed() -## Remove all [InventoryItem]s from [member items]. +## Remove all items from [member items]. func clear_inventory() -> void: - for item: InventoryItem in items.duplicate(): - items.erase(item) - item_consumed.emit(item) + for ci: TaggedItem in items.duplicate(): + items.erase(ci) + item_consumed.emit(ci) emit_changed() diff --git a/scenes/quests/lore_quests/quest_002/4_closing_transition/closing_transition.tscn b/scenes/quests/lore_quests/quest_002/4_closing_transition/closing_transition.tscn index 08ede3dc8c..29114628cf 100644 --- a/scenes/quests/lore_quests/quest_002/4_closing_transition/closing_transition.tscn +++ b/scenes/quests/lore_quests/quest_002/4_closing_transition/closing_transition.tscn @@ -25,7 +25,9 @@ [ext_resource type="Texture2D" uid="uid://ol20om7xc1o7" path="res://scenes/game_elements/props/buildings/house/components/House_Wool_Red_Stage1.png" id="14_tl8te"] [ext_resource type="Texture2D" uid="uid://0xceungm1p8a" path="res://scenes/game_elements/props/buildings/house/components/House_Wool_Red_Stage2.png" id="15_hbvq8"] [ext_resource type="Texture2D" uid="uid://be3845r07rhkm" path="res://scenes/game_elements/props/buildings/house/components/House_Wool_Red_Stage3.png" id="16_7wffi"] +[ext_resource type="PackedScene" uid="uid://fuhl3l6gxq5k" path="res://scenes/game_elements/props/collectible_item/collectible_item.tscn" id="17_e2p5u"] [ext_resource type="Texture2D" uid="uid://lylpujgq8e4v" path="res://scenes/game_elements/props/buildings/house/components/House_Patches_Red_Stage1.png" id="17_g6oum"] +[ext_resource type="Resource" uid="uid://c44l6x4e7sfx3" path="res://scenes/globals/game_state/inventory/spirit.tres" id="18_0oqxp"] [ext_resource type="PackedScene" uid="uid://7873qa54birk" path="res://scenes/game_elements/props/tree/tree.tscn" id="19_hbvq8"] [ext_resource type="SpriteFrames" uid="uid://2ek86nvw6y28" path="res://scenes/game_elements/props/tree/components/tree_spriteframes_yellow.tres" id="20_7wffi"] [ext_resource type="PackedScene" uid="uid://crqjcicx0vdu" path="res://scenes/game_elements/props/decoration/bush/bush.tscn" id="21_y3es2"] @@ -500,6 +502,12 @@ script = ExtResource("14_pditp") interact_area = NodePath("../InteractArea") metadata/_custom_type_script = "uid://edcifob4jc4s" +[node name="CollectibleItem" parent="OnTheGround" unique_id=327991066 instance=ExtResource("17_e2p5u")] +unique_name_in_owner = true +position = Vector2(864, 418) +revealed = false +item = ExtResource("18_0oqxp") + [node name="InteractArea" type="Area2D" parent="OnTheGround" unique_id=248145530 node_paths=PackedStringArray("marker")] position = Vector2(928, 424) rotation = 3.1415927 diff --git a/scenes/quests/lore_quests/quest_002/4_closing_transition/components/closing_cinematic.dialogue b/scenes/quests/lore_quests/quest_002/4_closing_transition/components/closing_cinematic.dialogue index f7940841c2..c9abaf2006 100644 --- a/scenes/quests/lore_quests/quest_002/4_closing_transition/components/closing_cinematic.dialogue +++ b/scenes/quests/lore_quests/quest_002/4_closing_transition/components/closing_cinematic.dialogue @@ -11,7 +11,7 @@ do animation_player.play("retreat") do animation_player.animation_finished Flaxfred: Stranger, you have helped us more than you know! Flaxfred: It feels like LinenVille’s spirit has come back to us. Here: take this thread as a parting gift. -do GameState.quest.inventory.add_collected_item(InventoryItem.with_type(InventoryItem.ItemType.SPIRIT)) +do GameState.quest.inventory.add_collected_item(collectible_item.item, collectible_item) do animation_player.play("leave") do wait(1.0) => END diff --git a/scenes/quests/lore_quests/quest_002/4_closing_transition/components/closing_transition.gd b/scenes/quests/lore_quests/quest_002/4_closing_transition/components/closing_transition.gd index ebcd6c822e..979a61c8a4 100644 --- a/scenes/quests/lore_quests/quest_002/4_closing_transition/components/closing_transition.gd +++ b/scenes/quests/lore_quests/quest_002/4_closing_transition/components/closing_transition.gd @@ -3,6 +3,7 @@ extends Node2D @onready var animation_player: AnimationPlayer = %AnimationPlayer +@onready var collectible_item: CollectibleItem = %CollectibleItem func reveal() -> void: diff --git a/scenes/quests/story_quests/el_ojo_revelador/4_outro/el_ojo_revelador_outro.tscn b/scenes/quests/story_quests/el_ojo_revelador/4_outro/el_ojo_revelador_outro.tscn index fd45539f88..1e22150999 100644 --- a/scenes/quests/story_quests/el_ojo_revelador/4_outro/el_ojo_revelador_outro.tscn +++ b/scenes/quests/story_quests/el_ojo_revelador/4_outro/el_ojo_revelador_outro.tscn @@ -1,5 +1,6 @@ [gd_scene format=4 uid="uid://dd12p8od2ec3w"] +[ext_resource type="Script" uid="uid://cnrj4ro1remne" path="res://scenes/quests/story_quests/el_ojo_revelador/4_outro/outro_components/el_ojo_revelador_outro.gd" id="1_ffkfc"] [ext_resource type="PackedScene" uid="uid://2rbpl811wlv1" path="res://scenes/game_elements/props/background_music/background_music.tscn" id="1_lmq3l"] [ext_resource type="PackedScene" uid="uid://cfcgrfvtn04yp" path="res://scenes/ui_elements/hud/hud.tscn" id="1_peaqk"] [ext_resource type="AudioStream" uid="uid://b4spu8ssi17qf" path="res://scenes/quests/story_quests/el_ojo_revelador/el_ojo_revelador_audios/outro.mp3" id="2_ffkfc"] @@ -8,8 +9,11 @@ [ext_resource type="Resource" uid="uid://c8ud2l86e5u5y" path="res://scenes/quests/story_quests/el_ojo_revelador/4_outro/outro_components/el_ojo_revelador_outro.dialogue" id="5_f32la"] [ext_resource type="SpriteFrames" uid="uid://bhegk5e0gh8n4" path="res://scenes/quests/story_quests/el_ojo_revelador/player_components/el_ojo_revelador_player.tres" id="5_vf5av"] [ext_resource type="PackedScene" uid="uid://d0c6ti0aqpt5o" path="res://scenes/quests/story_quests/el_juguete_perdido/objects_components/tree_idle.tscn" id="8_qs8h0"] +[ext_resource type="PackedScene" uid="uid://fuhl3l6gxq5k" path="res://scenes/game_elements/props/collectible_item/collectible_item.tscn" id="9_4gx01"] +[ext_resource type="Resource" uid="uid://c44l6x4e7sfx3" path="res://scenes/globals/game_state/inventory/spirit.tres" id="10_lmq3l"] [node name="Outro" type="Node2D" unique_id=1601499411] +script = ExtResource("1_ffkfc") [node name="BackgroundMusic" parent="." unique_id=1932281440 instance=ExtResource("1_lmq3l")] stream = ExtResource("2_ffkfc") @@ -64,6 +68,11 @@ dialogue = ExtResource("5_f32la") target = 1 metadata/_custom_type_script = "uid://x1mxt6bmei2o" +[node name="CollectibleItem" parent="." unique_id=327991066 instance=ExtResource("9_4gx01")] +unique_name_in_owner = true +revealed = false +item = ExtResource("10_lmq3l") + [node name="Arboles" type="Node" parent="." unique_id=1133400546] [node name="Tree" parent="Arboles" unique_id=781477103 instance=ExtResource("8_qs8h0")] diff --git a/scenes/quests/story_quests/el_ojo_revelador/4_outro/outro_components/el_ojo_revelador_outro.dialogue b/scenes/quests/story_quests/el_ojo_revelador/4_outro/outro_components/el_ojo_revelador_outro.dialogue index c7f99db250..e376c8cc08 100644 --- a/scenes/quests/story_quests/el_ojo_revelador/4_outro/outro_components/el_ojo_revelador_outro.dialogue +++ b/scenes/quests/story_quests/el_ojo_revelador/4_outro/outro_components/el_ojo_revelador_outro.dialogue @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: The Threadbare Authors # SPDX-License-Identifier: MPL-2.0 ~ start -do GameState.quest.inventory.add_collected_item(InventoryItem.with_type(InventoryItem.ItemType.SPIRIT)) +do GameState.quest.inventory.add_collected_item(collectible_item.item, collectible_item) Jeremy, envuelto en polvo y con la ropa rota, mira salir el deslumbrante sol. Por primera vez en su vida , no se siente apurado ni ansioso por lo que venga después. Está disfrutando del silencio, el aire y la luz. El Ojo Revelador mostró su alma. le permitió diff --git a/scenes/quests/story_quests/el_ojo_revelador/4_outro/outro_components/el_ojo_revelador_outro.gd b/scenes/quests/story_quests/el_ojo_revelador/4_outro/outro_components/el_ojo_revelador_outro.gd new file mode 100644 index 0000000000..97250a8844 --- /dev/null +++ b/scenes/quests/story_quests/el_ojo_revelador/4_outro/outro_components/el_ojo_revelador_outro.gd @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: The Threadbare Authors +# SPDX-License-Identifier: MPL-2.0 +extends Node2D + +@onready var collectible_item: CollectibleItem = %CollectibleItem diff --git a/scenes/quests/story_quests/el_ojo_revelador/4_outro/outro_components/el_ojo_revelador_outro.gd.uid b/scenes/quests/story_quests/el_ojo_revelador/4_outro/outro_components/el_ojo_revelador_outro.gd.uid new file mode 100644 index 0000000000..9ef7bbc8fb --- /dev/null +++ b/scenes/quests/story_quests/el_ojo_revelador/4_outro/outro_components/el_ojo_revelador_outro.gd.uid @@ -0,0 +1 @@ +uid://cnrj4ro1remne diff --git a/scenes/ui_elements/story_quest_progress/components/item_slot/components/item_slot.gd b/scenes/ui_elements/story_quest_progress/components/item_slot/components/item_slot.gd index e1f964f409..ef1b99768a 100644 --- a/scenes/ui_elements/story_quest_progress/components/item_slot/components/item_slot.gd +++ b/scenes/ui_elements/story_quest_progress/components/item_slot/components/item_slot.gd @@ -10,14 +10,20 @@ var filled_with_item: InventoryItem = null: if filled_with_item: texture = filled_with_item.hud_texture +var _ghost := false: + set(new_value): + _ghost = new_value + (material as ShaderMaterial).set_shader_parameter("intensity", 0.25 if _ghost else 0.0) + @onready var animation_player: AnimationPlayer = $AnimationPlayer ## Shows the collected [InventoryItem] in this item slot without animation. -func start_as_filled(inventory_item: InventoryItem) -> void: +func start_as_filled(inventory_item: InventoryItem, ghost: bool) -> void: if is_filled(): return + _ghost = ghost filled_with_item = inventory_item modulate = Color.WHITE @@ -27,10 +33,11 @@ func is_filled() -> bool: ## Shows the collected [InventoryItem] in this item slot with a quick animation. -func fill(inventory_item: InventoryItem) -> void: +func fill(inventory_item: InventoryItem, ghost: bool) -> void: if is_filled(): return + _ghost = ghost filled_with_item = inventory_item texture = inventory_item.hud_texture pivot_offset = size / 2.0 @@ -45,3 +52,4 @@ func is_filled_with_same_item_type_as(inventory_item: InventoryItem) -> bool: func free_slot() -> void: filled_with_item = null modulate = Color(Color.BLACK, 0.7) + _ghost = false diff --git a/scenes/ui_elements/story_quest_progress/components/item_slot/item_slot.tscn b/scenes/ui_elements/story_quest_progress/components/item_slot/item_slot.tscn index 2885599b99..45e8a0da18 100644 --- a/scenes/ui_elements/story_quest_progress/components/item_slot/item_slot.tscn +++ b/scenes/ui_elements/story_quest_progress/components/item_slot/item_slot.tscn @@ -1,6 +1,7 @@ [gd_scene format=3 uid="uid://1mjm4atk2j6e"] [ext_resource type="Texture2D" uid="uid://wyiamtqmp4gk" path="res://assets/first_party/collectibles/imagination.png" id="1_epgds"] +[ext_resource type="Material" uid="uid://dlnyw2c84jxlj" path="res://scenes/game_elements/fx/dither/dither.tres" id="1_wyrnr"] [ext_resource type="Script" uid="uid://c0tsmn060ymdh" path="res://scenes/ui_elements/story_quest_progress/components/item_slot/components/item_slot.gd" id="2_ijx2u"] [sub_resource type="Animation" id="Animation_e5gxt"] @@ -91,6 +92,7 @@ _data = { [node name="ItemSlot" type="TextureRect" unique_id=1956926033] modulate = Color(0, 0, 0, 0.690196) texture_filter = 6 +material = ExtResource("1_wyrnr") custom_minimum_size = Vector2(32, 32) anchors_preset = 15 anchor_right = 1.0 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 6c3b50146f..a0d8b30923 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 @@ -31,7 +31,10 @@ func _ready() -> void: # far in the quest. var items_collected := GameState.quest.inventory.items for i: int in min(items_collected.size(), n): - items_container.get_child(i).start_as_filled(items_collected[i]) + var ti := items_collected[i] + var ghost := GameState.global.inventory._has(ti) + var slot := items_container.get_child(i) as ItemSlot + slot.start_as_filled(ti.item, ghost) # Then, when each new item is collected, it is added to the progress UI GameState.quest.inventory.item_collected.connect(self._on_item_collected) @@ -56,17 +59,19 @@ func _on_helper_state_changed() -> void: helper_color.color = item.color -func _on_item_collected(item: InventoryItem) -> void: +func _on_item_collected(item: TaggedItem) -> void: + var ghost := GameState.global.inventory._has(item) for child in items_container.get_children(): var item_slot := child as ItemSlot if not item_slot.is_filled(): - item_slot.fill(item) + item_slot.fill(item.item, ghost) return -func _on_item_consumed(item: InventoryItem) -> void: +func _on_item_consumed(item: TaggedItem) -> void: + # TODO: backwards for child in items_container.get_children(): var item_slot := child as ItemSlot - if item_slot.is_filled_with_same_item_type_as(item): + if item_slot.is_filled_with_same_item_type_as(item.item): item_slot.free_slot() return diff --git a/scenes/world_map/components/frays_end.gd b/scenes/world_map/components/frays_end.gd index 312d1cdd27..58a307e3ae 100644 --- a/scenes/world_map/components/frays_end.gd +++ b/scenes/world_map/components/frays_end.gd @@ -11,6 +11,8 @@ extends Node2D func _ready() -> void: _update_story_quest_progress_visibility() + + GameState.quest_changed.connect(_update_story_quest_progress_visibility) if GameState.quest: GameState.quest.inventory.item_collected.connect(_update_story_quest_progress_visibility) GameState.quest.inventory.item_consumed.connect(_update_story_quest_progress_visibility)