From b79c48077c64258ca4e88db8dac16ce84440b6e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Qui=C3=B1ones?= Date: Thu, 2 Apr 2026 16:50:48 -0300 Subject: [PATCH 1/3] PlayerHook: Allow the hooked object to act when hooked When the grappling hook starts pulling the string, check if the hooked entity has a got_hooked handler method and if so: 1. call it. And 2. connect to its area hook_released signal (also added in this commit). Pass the hooked direction (the direction between the first 2 points of the thread line) to the handler. With this, custom objects can react differently to the grappling hook pull and also release themselves at any time. --- .../characters/player/components/player_hook.gd | 17 ++++++++++++++++- .../player/components/player_hook.tscn | 3 +-- .../hookable_area/components/hookable_area.gd | 8 ++++++++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/scenes/game_elements/characters/player/components/player_hook.gd b/scenes/game_elements/characters/player/components/player_hook.gd index abab6883b..cbd99043c 100644 --- a/scenes/game_elements/characters/player/components/player_hook.gd +++ b/scenes/game_elements/characters/player/components/player_hook.gd @@ -249,11 +249,26 @@ func shatter_string() -> void: ## While pulling, the player is allowed to go through non-walkable floor. func pull_string() -> void: pulling = true + # While pulling, this class takes control over the player movement. if character.has_method("take_control"): character.take_control(self) character.set_collision_mask_value(Enums.CollisionLayers.NON_WALKABLE_FLOOR, false) + # If the entity has a got_pulled handler, call it and connect to the pull_released signal + # of the HookableArea. The entity is responsible to call it. + var ending_area := get_ending_area() + if ending_area.controlled_entity.has_method("got_pulled"): + ending_area.pull_released.connect(_on_pull_released, CONNECT_ONE_SHOT) + var direction := hook_string.points[0].direction_to(hook_string.points[1]) + ending_area.controlled_entity.got_pulled(direction) + + +func _on_pull_released(cancelled: bool) -> void: + if cancelled and hook_string: + shatter_string() + stop_pulling() + ## Stop pulling and remove the [member hook_string]. ## [br][br] @@ -332,7 +347,7 @@ func _process_pulling(_delta: float) -> void: return var target := ending_area.controlled_entity - var weight := ending_area.weight if target is CharacterBody2D else 1.0 + var weight := ending_area.weight # Vector from player to first point: var player_distance: Vector2 = hook_string.points[-2] - hook_string.points[-1] diff --git a/scenes/game_elements/characters/player/components/player_hook.tscn b/scenes/game_elements/characters/player/components/player_hook.tscn index 7e6fb12fa..725878946 100644 --- a/scenes/game_elements/characters/player/components/player_hook.tscn +++ b/scenes/game_elements/characters/player/components/player_hook.tscn @@ -3,9 +3,8 @@ [ext_resource type="Script" uid="uid://b7704hdflt5t8" path="res://scenes/game_elements/characters/player/components/player_hook.gd" id="1_40sye"] [ext_resource type="PackedScene" uid="uid://b0dehcnfo68j1" path="res://scenes/game_elements/props/hook_control/hook_control.tscn" id="2_5svv0"] -[node name="PlayerHook" type="Node2D" unique_id=1972626703 node_paths=PackedStringArray("character") groups=["hook_listener"]] +[node name="PlayerHook" type="Node2D" unique_id=1972626703 groups=["hook_listener"]] script = ExtResource("1_40sye") -character = NodePath("") [node name="HookControl" parent="." unique_id=1022482491 instance=ExtResource("2_5svv0")] state = 1 diff --git a/scenes/game_elements/props/hookable_area/components/hookable_area.gd b/scenes/game_elements/props/hookable_area/components/hookable_area.gd index ea7e37db5..3c58dd7b7 100644 --- a/scenes/game_elements/props/hookable_area/components/hookable_area.gd +++ b/scenes/game_elements/props/hookable_area/components/hookable_area.gd @@ -29,6 +29,9 @@ extends Area2D ## This script automatically configures the correct [member collision_layer] and ## [member collision_mask] values to enable interaction with the grappling hook. +## Use this signal to release itself from a grappling hook pull. +signal pull_released(cancelled: bool) + ## The game entity that becomes hookable. ## [br][br] ## [b]Note:[/b] If the parent node is a Node2D and this isn't set, @@ -70,6 +73,11 @@ func get_anchor_position() -> Vector2: return anchor_point.global_position if anchor_point else global_position +## Emit the [signal pull_released] signal. +func release_from_pull(cancelled: bool = false) -> void: + pull_released.emit(cancelled) + + func _get_configuration_warnings() -> PackedStringArray: var warnings: PackedStringArray if not controlled_entity: From 82079e9a5f63069bfb0575d4f5dad66a7cdabf6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Qui=C3=B1ones?= Date: Thu, 2 Apr 2026 17:39:53 -0300 Subject: [PATCH 2/3] Add examples of custom pullable objects --- .../custom_hookable_objects_test.tscn | 284 ++++++++++++++++++ .../hookable_box/components/hookable_box.gd | 74 +++++ .../components/hookable_box.gd.uid | 1 + .../props/hookable_box/hookable_box.tscn | 69 +++++ .../components/hookable_lever.gd | 65 ++++ .../components/hookable_lever.gd.uid | 1 + .../props/hookable_lever/hookable_lever.tscn | 51 ++++ 7 files changed, 545 insertions(+) create mode 100644 scenes/game_elements/components/custom_hookable_objects_test.tscn create mode 100644 scenes/game_elements/props/hookable_box/components/hookable_box.gd create mode 100644 scenes/game_elements/props/hookable_box/components/hookable_box.gd.uid create mode 100644 scenes/game_elements/props/hookable_box/hookable_box.tscn create mode 100644 scenes/game_elements/props/hookable_lever/components/hookable_lever.gd create mode 100644 scenes/game_elements/props/hookable_lever/components/hookable_lever.gd.uid create mode 100644 scenes/game_elements/props/hookable_lever/hookable_lever.tscn diff --git a/scenes/game_elements/components/custom_hookable_objects_test.tscn b/scenes/game_elements/components/custom_hookable_objects_test.tscn new file mode 100644 index 000000000..e04839a77 --- /dev/null +++ b/scenes/game_elements/components/custom_hookable_objects_test.tscn @@ -0,0 +1,284 @@ +[gd_scene format=4 uid="uid://s7y5x5caaah3"] + +[ext_resource type="TileSet" uid="uid://oynx002hv8tl" path="res://tiles/water.tres" id="2_1is7x"] +[ext_resource type="TileSet" uid="uid://bdc7ucso7bx0s" path="res://tiles/shadows.tres" id="3_3tad6"] +[ext_resource type="TileSet" uid="uid://b778cuoftt88r" path="res://tiles/elevation_2.tres" id="4_3uytt"] +[ext_resource type="TileSet" uid="uid://b8qnr0owsbhhn" path="res://tiles/exterior_floors.tres" id="5_717he"] +[ext_resource type="TileSet" uid="uid://do0ffypatd77h" path="res://tiles/bridges.tres" id="6_obhrw"] +[ext_resource type="TileSet" uid="uid://41pk6xbhypue" path="res://tiles/fence.tres" id="7_5lu30"] +[ext_resource type="PackedScene" uid="uid://iu2q66clupc6" path="res://scenes/game_elements/characters/player/player.tscn" id="8_2l846"] +[ext_resource type="SpriteFrames" uid="uid://cxjwwgm86d6it" path="res://scenes/game_elements/characters/components/sprite_frames/storyweaver_red.tres" id="9_powm1"] +[ext_resource type="PackedScene" uid="uid://dkpelgxwtvcxd" path="res://scenes/game_elements/props/hookable_box/hookable_box.tscn" id="11_b1u67"] +[ext_resource type="PackedScene" uid="uid://d37mebu7atru7" path="res://scenes/game_elements/characters/enemies/guard/guard.tscn" id="13_rna1f"] +[ext_resource type="SpriteFrames" uid="uid://qwlhm21ndgn2" path="res://scenes/game_elements/characters/enemies/guard/components/storyvore_frames_orange.tres" id="14_1is7x"] +[ext_resource type="PackedScene" uid="uid://c47lkkvljrsg" path="res://scenes/game_elements/props/hookable_lever/hookable_lever.tscn" id="14_esoiq"] +[ext_resource type="AudioStream" uid="uid://dgpeb1dtmfqud" path="res://assets/third_party/sounds/characters/enemies/guard/AlertSound.ogg" id="15_3tad6"] +[ext_resource type="PackedScene" uid="uid://dohb701lxbe5s" path="res://scenes/game_elements/props/hookable_needle/hookable_needle.tscn" id="15_bhn5g"] +[ext_resource type="AudioStream" uid="uid://4ec6e2alns71" path="res://assets/third_party/sounds/characters/enemies/guard/GrassFoot.ogg" id="16_3uytt"] +[ext_resource type="AudioStream" uid="uid://c7om8kdork2rx" path="res://assets/third_party/sounds/characters/enemies/guard/Torch.ogg" id="17_717he"] +[ext_resource type="PackedScene" uid="uid://covsdqqsd6rsy" path="res://scenes/game_elements/props/sign/sign.tscn" id="18_d3uwq"] +[ext_resource type="AudioStream" uid="uid://bwif2oo6ymiu2" path="res://assets/third_party/sounds/characters/enemies/guard/TorchHit.ogg" id="18_obhrw"] +[ext_resource type="Script" uid="uid://dnp0tjloec2d7" path="res://scenes/game_logic/stealth_game_logic.gd" id="20_rna1f"] +[ext_resource type="Script" uid="uid://c5jp8y8mpge4w" path="res://scenes/world_map/components/ToggleableTileMapLayer.gd" id="20_tbxxm"] + +[sub_resource type="Gradient" id="Gradient_5j6h7"] +interpolation_mode = 2 +interpolation_color_space = 2 +colors = PackedColorArray(1, 1, 1, 1, 0, 0, 0, 1) + +[sub_resource type="GradientTexture2D" id="GradientTexture2D_3tad6"] +gradient = SubResource("Gradient_5j6h7") +width = 256 +height = 256 +fill = 1 +fill_from = Vector2(0.5, 0.5) +fill_to = Vector2(0, 0.5) +metadata/_snap_enabled = true + +[sub_resource type="Curve2D" id="Curve2D_rna1f"] +_data = { +"points": PackedVector2Array(0, 0, 0, 0, 17, 2.9580078, 0, 0, 0, 0, 721, 2.9580078, 0, 0, 0, 0, 721, 194.95801, 0, 0, 0, 0, 1425, 194.95801) +} +point_count = 4 + +[node name="CustomHookableObjectsTest" type="Node2D" unique_id=324172432] + +[node name="TileMapLayers" type="Node2D" parent="." unique_id=1129826282] + +[node name="Water" type="TileMapLayer" parent="TileMapLayers" unique_id=1017266357] +tile_map_data = PackedByteArray("AAABAP//AAAAAAAAAAACAP//AAAAAAAAAAADAP//AAAAAAAAAAAEAP//AAAAAAAAAAAFAP//AAAAAAAAAAAGAP//AAAAAAAAAAAHAP//AAAAAAAAAAAIAP//AAAAAAAAAAAJAP//AAAAAAAAAAAKAP//AAAAAAAAAAALAP//AAAAAAAAAAAMAP//AAAAAAAAAAAMAP7/AAAAAAAAAAALAP7/AAAAAAAAAAAKAP7/AAAAAAAAAAAJAP7/AAAAAAAAAAAIAP7/AAAAAAAAAAAHAP7/AAAAAAAAAAAGAP7/AAAAAAAAAAAFAP7/AAAAAAAAAAAEAP7/AAAAAAAAAAADAP7/AAAAAAAAAAACAP7/AAAAAAAAAAABAP7/AAAAAAAAAAAAAP7/AAAAAAAAAAD///7/AAAAAAAAAAABAAAAAAAAAAAAAAABAAEAAAAAAAAAAAABAAIAAAAAAAAAAAABAAMAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//AAAAAAAAAAD/////AAAAAAAAAAD//wAAAAAAAAAAAAD//wEAAAAAAAAAAAD//wIAAAAAAAAAAAD//wMAAAAAAAAAAAD//wQAAAAAAAAAAAD//wUAAAAAAAAAAAAAAAUAAAAAAAAAAAABAAUAAAAAAAAAAAABAAQAAAAAAAAAAAABAAYAAAAAAAAAAAABAAcAAAAAAAAAAAABAAgAAAAAAAAAAAAAAAgAAAAAAAAAAAD//wgAAAAAAAAAAAD//wcAAAAAAAAAAAD//wYAAAAAAAAAAAAAAAYAAAAAAAAAAAAAAAcAAAAAAAAAAAAQAAcAAAAAAAAAAAARAAcAAAAAAAAAAAASAAcAAAAAAAAAAAATAAcAAAAAAAAAAAAUAAcAAAAAAAAAAAAQAAkAAAAAAAAAAAARAAkAAAAAAAAAAAASAAkAAAAAAAAAAAATAAkAAAAAAAAAAAAUAAkAAAAAAAAAAAAUAAoAAAAAAAAAAAATAAoAAAAAAAAAAAASAAoAAAAAAAAAAAARAAoAAAAAAAAAAAAQAAoAAAAAAAAAAAAQAAYAAAAAAAAAAAARAAYAAAAAAAAAAAASAAYAAAAAAAAAAAATAAYAAAAAAAAAAAAUAAYAAAAAAAAAAAAQAAsAAAAAAAAAAAARAAsAAAAAAAAAAAASAAsAAAAAAAAAAAATAAsAAAAAAAAAAAAUAAsAAAAAAAAAAAANAP7/AAAAAAAAAAAOAP7/AAAAAAAAAAAPAP7/AAAAAAAAAAAQAP7/AAAAAAAAAAARAP7/AAAAAAAAAAASAP7/AAAAAAAAAAATAP7/AAAAAAAAAAAUAP7/AAAAAAAAAAAVAP7/AAAAAAAAAAAVAP//AAAAAAAAAAAUAP//AAAAAAAAAAATAP//AAAAAAAAAAASAP//AAAAAAAAAAARAP//AAAAAAAAAAAQAP//AAAAAAAAAAAPAP//AAAAAAAAAAAOAP//AAAAAAAAAAANAP//AAAAAAAAAAAUAAQAAAAAAAAAAAAUAAUAAAAAAAAAAAATAAQAAAAAAAAAAAATAAUAAAAAAAAAAAARAAQAAAAAAAAAAAARAAUAAAAAAAAAAAAQAAUAAAAAAAAAAAAQAAQAAAAAAAAAAAASAAQAAAAAAAAAAQASAAUAAAAAAAAAAQAQAAgAAAAAAAAAAQARAAgAAAAAAAAAAQASAAgAAAAAAAAAAQATAAgAAAAAAAAAAQAUAAgAAAAAAAAAAQAVAAQAAAAAAAAAAAAWAAQAAAAAAAAAAAAXAAQAAAAAAAAAAAAYAAQAAAAAAAAAAAAZAAQAAAAAAAAAAAAZAAUAAAAAAAAAAAAYAAUAAAAAAAAAAAAXAAUAAAAAAAAAAAAWAAUAAAAAAAAAAAAVAAUAAAAAAAAAAAAaAAQAAAAAAAAAAAAbAAQAAAAAAAAAAAAbAAUAAAAAAAAAAAAbAAYAAAAAAAAAAAAaAAUAAAAAAAAAAAAaAAYAAAAAAAAAAAAaAAcAAAAAAAAAAAAaAAgAAAAAAAAAAAAbAAgAAAAAAAAAAAAbAAcAAAAAAAAAAAAaAAkAAAAAAAAAAAAbAAkAAAAAAAAAAAAbAAoAAAAAAAAAAAAaAAoAAAAAAAAAAAAaAAsAAAAAAAAAAAAbAAsAAAAAAAAAAAAbAAwAAAAAAAAAAAAaAAwAAAAAAAAAAAAZAAwAAAAAAAAAAAAYAAwAAAAAAAAAAAAXAAwAAAAAAAAAAAAWAAwAAAAAAAAAAAAWAAsAAAAAAAAAAAAVAAsAAAAAAAAAAAASAAwAAAAAAAAAAAARAAwAAAAAAAAAAAAQAAwAAAAAAAAAAAATAAwAAAAAAAAAAAAUAAwAAAAAAAAAAAAVAAwAAAAAAAAAAAAXAAsAAAAAAAAAAAAYAAsAAAAAAAAAAAAZAAsAAAAAAAAAAAAPAAsAAAAAAAAAAAAOAAsAAAAAAAAAAAANAAsAAAAAAAAAAAAMAAsAAAAAAAAAAAALAAsAAAAAAAAAAAAKAAsAAAAAAAAAAAAJAAsAAAAAAAAAAAAIAAsAAAAAAAAAAAAHAAsAAAAAAAAAAAAGAAsAAAAAAAAAAAAFAAsAAAAAAAAAAAAEAAsAAAAAAAAAAAADAAsAAAAAAAAAAAACAAsAAAAAAAAAAAABAAsAAAAAAAAAAAAAAAsAAAAAAAAAAAD//wsAAAAAAAAAAAD//wwAAAAAAAAAAAAAAAwAAAAAAAAAAAABAAwAAAAAAAAAAAACAAwAAAAAAAAAAAADAAwAAAAAAAAAAAAEAAwAAAAAAAAAAAAFAAwAAAAAAAAAAAAGAAwAAAAAAAAAAAAHAAwAAAAAAAAAAAAIAAwAAAAAAAAAAAAJAAwAAAAAAAAAAAAKAAwAAAAAAAAAAAALAAwAAAAAAAAAAAAMAAwAAAAAAAAAAAANAAwAAAAAAAAAAAAOAAwAAAAAAAAAAAAPAAwAAAAAAAAAAAAVAAMAAAAAAAAAAAAVAAIAAAAAAAAAAAAVAAEAAAAAAAAAAAAVAAAAAAAAAAAAAAAWAP7/AAAAAAAAAAAXAP7/AAAAAAAAAAAYAP7/AAAAAAAAAAAZAP7/AAAAAAAAAAAaAP7/AAAAAAAAAAAbAP7/AAAAAAAAAAAbAP//AAAAAAAAAAAbAAAAAAAAAAAAAAAbAAEAAAAAAAAAAAAbAAIAAAAAAAAAAAAbAAMAAAAAAAAAAAAaAAMAAAAAAAAAAAAZAAMAAAAAAAAAAAAYAAMAAAAAAAAAAAAXAAMAAAAAAAAAAAAWAAMAAAAAAAAAAAAWAAIAAAAAAAAAAAAWAAEAAAAAAAAAAAAWAAAAAAAAAAAAAAAWAP//AAAAAAAAAAAXAP//AAAAAAAAAAAYAP//AAAAAAAAAAAZAP//AAAAAAAAAAAZAAAAAAAAAAAAAAAaAAAAAAAAAAAAAAAaAAEAAAAAAAAAAAAZAAEAAAAAAAAAAAAZAAIAAAAAAAAAAAAYAAIAAAAAAAAAAAAYAAEAAAAAAAAAAAAXAAEAAAAAAAAAAAAXAAAAAAAAAAAAAAAYAAAAAAAAAAAAAAAXAAIAAAAAAAAAAAAaAAIAAAAAAAAAAAAaAP//AAAAAAAAAAAQAAIAAAAAAAAAAAAQAAMAAAAAAAAAAAAQAAAAAAAAAAAAAAAQAAEAAAAAAAAAAQAcAP7/AAAAAAAAAAAcAP//AAAAAAAAAAAcAAAAAAAAAAAAAAAcAAEAAAAAAAAAAAAcAAIAAAAAAAAAAAAcAAMAAAAAAAAAAAAcAAQAAAAAAAAAAAAcAAUAAAAAAAAAAAAcAAYAAAAAAAAAAAAcAAcAAAAAAAAAAAAcAAgAAAAAAAAAAAAcAAkAAAAAAAAAAAAcAAoAAAAAAAAAAAAcAAsAAAAAAAAAAAAcAAwAAAAAAAAAAAD//w0AAAAAAAAAAAAAAA0AAAAAAAAAAAABAA0AAAAAAAAAAAACAA0AAAAAAAAAAAADAA0AAAAAAAAAAAAEAA0AAAAAAAAAAAAFAA0AAAAAAAAAAAAGAA0AAAAAAAAAAAAHAA0AAAAAAAAAAAAIAA0AAAAAAAAAAAAJAA0AAAAAAAAAAAAKAA0AAAAAAAAAAAALAA0AAAAAAAAAAAAMAA0AAAAAAAAAAAANAA0AAAAAAAAAAAAOAA0AAAAAAAAAAAAPAA0AAAAAAAAAAAAQAA0AAAAAAAAAAAARAA0AAAAAAAAAAAAcAA0AAAAAAAAAAAAbAA0AAAAAAAAAAAAaAA0AAAAAAAAAAAAZAA0AAAAAAAAAAAAYAA0AAAAAAAAAAAAXAA0AAAAAAAAAAAAWAA0AAAAAAAAAAAAVAA0AAAAAAAAAAAAUAA0AAAAAAAAAAAATAA0AAAAAAAAAAAASAA0AAAAAAAAAAAD+//7/AAAAAAAAAAD+////AAAAAAAAAAD+/wAAAAAAAAAAAAD+/wEAAAAAAAAAAAD+/wIAAAAAAAAAAAD+/wMAAAAAAAAAAAD+/wQAAAAAAAAAAAD+/wUAAAAAAAAAAAD+/wYAAAAAAAAAAAD+/wcAAAAAAAAAAAD+/wgAAAAAAAAAAAD+/wkAAAAAAAAAAAD+/woAAAAAAAAAAAD+/wsAAAAAAAAAAAD+/wwAAAAAAAAAAAD+/w0AAAAAAAAAAAA=") +tile_set = ExtResource("2_1is7x") + +[node name="Shadows" type="TileMapLayer" parent="TileMapLayers" unique_id=453712517] +tile_map_data = PackedByteArray("AAASAAUAAAAAAAAAAAASAAYAAAAAAAAAAAAVAAsAAAAAAAAAAAAWAAsAAAAAAAAAAAAXAAsAAAAAAAAAAAAYAAsAAAAAAAAAAAAZAAsAAAAAAAAAAAAVAAcAAAAAAAAAAAAVAAgAAAAAAAAAAAAVAAkAAAAAAAAAAAAVAAoAAAAAAAAAAAAZAAcAAAAAAAAAAAAZAAgAAAAAAAAAAAAZAAkAAAAAAAAAAAAZAAoAAAAAAAAAAAARAAQAAAAAAAAAAAASAAQAAAAAAAAAAAATAAQAAAAAAAAAAAAUAAQAAAAAAAAAAAARAAMAAAAAAAAAAAAUAAEAAAAAAAAAAAAUAAIAAAAAAAAAAAAUAAMAAAAAAAAAAAAPAAIAAAAAAAAAAAAPAAMAAAAAAAAAAAAPAAQAAAAAAAAAAAAPAAUAAAAAAAAAAAAPAAYAAAAAAAAAAAAPAAcAAAAAAAAAAAAPAAgAAAAAAAAAAAAPAAkAAAAAAAAAAAAPAAoAAAAAAAAAAAAPAAsAAAAAAAAAAAD//wsAAAAAAAAAAAAAAAsAAAAAAAAAAAABAAsAAAAAAAAAAAACAAsAAAAAAAAAAAADAAsAAAAAAAAAAAAEAAsAAAAAAAAAAAAFAAsAAAAAAAAAAAAGAAsAAAAAAAAAAAAHAAsAAAAAAAAAAAAIAAsAAAAAAAAAAAAJAAsAAAAAAAAAAAAKAAsAAAAAAAAAAAALAAsAAAAAAAAAAAAMAAsAAAAAAAAAAAANAAsAAAAAAAAAAAAOAAsAAAAAAAAAAAD//woAAAAAAAAAAAACAAEAAAAAAAAAAAACAAIAAAAAAAAAAAACAAMAAAAAAAAAAAACAAQAAAAAAAAAAAACAAUAAAAAAAAAAAACAAYAAAAAAAAAAAACAAcAAAAAAAAAAAACAAgAAAAAAAAAAAARAAEAAAAAAAAAAAARAAIAAAAAAAAAAAAPAAEAAAAAAAAAAAA=") +tile_set = ExtResource("3_3tad6") + +[node name="BridgeShadows" type="TileMapLayer" parent="TileMapLayers" unique_id=1489783672] +tile_map_data = PackedByteArray("AAAQAAkAAwACAAMAAAARAAkAAwACAAMAAAASAAkAAwACAAMAAAATAAkAAwACAAMAAAAUAAkAAwACAAMAAAAQAAIAAwACAAMAAAA=") +tile_set = ExtResource("6_obhrw") + +[node name="ElevationLow" type="TileMapLayer" parent="TileMapLayers" unique_id=356899944] +tile_map_data = PackedByteArray("AAD//woABAAAAAQAAAAAAAoABAABAAQAAAABAAoABAABAAQAAAACAAoABAABAAQAAAADAAoABAABAAQAAAAEAAoABAABAAQAAAAFAAoABAABAAQAAAAGAAoABAABAAQAAAAHAAoABAABAAQAAAAIAAoABAABAAQAAAAJAAoABAABAAQAAAAKAAoABAABAAQAAAALAAoABAABAAQAAAAMAAoABAABAAQAAAANAAoABAABAAQAAAAOAAoABAABAAQAAAAPAAoABAACAAQAAAARAAMABAAAAAQAAAASAAMABAABAAQAAAATAAMABAABAAQAAAAUAAMABAACAAQAAAAVAAoABAAAAAQAAAAWAAoABAABAAQAAAAXAAoABAABAAQAAAAYAAoABAABAAQAAAAZAAoABAACAAQAAAA=") +tile_set = ExtResource("4_3uytt") +occlusion_enabled = false +collision_enabled = false + +[node name="Grass" type="TileMapLayer" parent="TileMapLayers" unique_id=102871699] +tile_map_data = PackedByteArray("AAALAAgAAQABAAEAAAALAAcAAQABAAEAAAALAAYAAQABAAEAAAALAAUAAQABAAEAAAALAAQAAQABAAEAAAALAAMAAQABAAEAAAALAAIAAQABAAEAAAAKAAgAAQABAAEAAAAKAAcAAQABAAEAAAAKAAYAAQABAAEAAAAKAAUAAQABAAEAAAAKAAQAAQABAAEAAAAKAAMAAQABAAEAAAAKAAIAAQABAAEAAAAJAAgAAQABAAEAAAAJAAcAAQABAAEAAAAJAAYAAQABAAEAAAAJAAUAAQABAAEAAAAJAAQAAQABAAEAAAAJAAMAAQABAAEAAAAJAAIAAQABAAEAAAAIAAgAAQABAAEAAAAIAAcAAQABAAEAAAAIAAYAAQABAAEAAAAIAAUAAQABAAEAAAAIAAQAAQABAAEAAAAIAAMAAQABAAEAAAAIAAIAAQABAAEAAAAHAAgAAQABAAEAAAAHAAcAAQABAAEAAAAHAAYAAQABAAEAAAAHAAUAAQABAAEAAAAHAAQAAQABAAEAAAAHAAMAAQABAAEAAAAHAAIAAQABAAEAAAAGAAgAAQABAAEAAAAGAAcAAQABAAEAAAAGAAYAAQABAAEAAAAGAAUAAQABAAEAAAAGAAQAAQABAAEAAAAGAAMAAQABAAEAAAAGAAIAAQABAAEAAAAFAAgAAQABAAEAAAAFAAcAAQABAAEAAAAFAAYAAQABAAEAAAAFAAUAAQABAAEAAAAFAAQAAQABAAEAAAAFAAMAAQABAAEAAAAFAAIAAQABAAEAAAAEAAcAAQABAAEAAAAEAAYAAQABAAEAAAAEAAUAAQABAAEAAAAEAAQAAQABAAEAAAAEAAMAAQABAAEAAAAEAAIAAQABAAEAAAADAAcAAQABAAEAAAADAAYAAQABAAEAAAADAAUAAQABAAEAAAADAAQAAQABAAEAAAADAAMAAQABAAEAAAADAAIAAQABAAEAAAACAAcAAQAAAAEAAAACAAYAAQAAAAEAAAACAAUAAQAAAAEAAAACAAQAAQAAAAEAAAACAAMAAQAAAAEAAAACAAIAAQAAAAEAAAAEAAgAAQABAAEAAAADAAgAAQABAAEAAAACAAgAAQAAAAEAAAD//wkAAQAAAAAAAAAAAAkAAQABAAAAAAABAAkAAQABAAAAAAACAAkAAQABAAEAAAADAAkAAQABAAEAAAAEAAkAAQABAAEAAAAFAAkAAQABAAEAAAAGAAkAAQABAAEAAAD//woAAQAAAAIAAAAAAAoAAQABAAIAAAABAAoAAQABAAIAAAACAAoAAQABAAIAAAADAAoAAQABAAIAAAAEAAoAAQABAAIAAAAFAAoAAQABAAIAAAAGAAoAAQABAAIAAAAMAAkAAQABAAEAAAALAAkAAQABAAEAAAAKAAkAAQABAAEAAAAJAAkAAQABAAEAAAAIAAkAAQABAAEAAAAHAAkAAQABAAEAAAAHAAoAAQABAAIAAAAIAAoAAQABAAIAAAAJAAoAAQABAAIAAAAKAAoAAQABAAIAAAALAAoAAQABAAIAAAAMAAoAAQABAAIAAAAMAAgAAQABAAEAAAAMAAcAAQABAAEAAAAMAAYAAQABAAEAAAAMAAQAAQABAAEAAAAMAAMAAQABAAEAAAAMAAIAAQABAAEAAAACAAEAAQAAAAEAAAADAAEAAQABAAEAAAAEAAEAAQABAAEAAAAFAAEAAQABAAEAAAAGAAEAAQABAAEAAAAHAAEAAQABAAEAAAAIAAEAAQABAAEAAAAJAAEAAQABAAEAAAAKAAEAAQABAAEAAAALAAEAAQABAAEAAAAMAAEAAQABAAEAAAACAAAAAQAAAAAAAAADAAAAAQABAAAAAAAEAAAAAQABAAAAAAAFAAAAAQABAAAAAAAGAAAAAQABAAAAAAAHAAAAAQABAAAAAAAIAAAAAQABAAAAAAAJAAAAAQABAAAAAAAKAAAAAQABAAAAAAALAAAAAQABAAAAAAAMAAAAAQABAAAAAAAMAAUAAQABAAEAAAAPAAoAAQACAAIAAAAPAAkAAQACAAEAAAAPAAgAAQACAAEAAAAPAAcAAQACAAEAAAAPAAYAAQACAAEAAAAPAAUAAQACAAEAAAAOAAoAAQABAAIAAAAOAAkAAQABAAEAAAAOAAgAAQABAAEAAAAOAAcAAQABAAEAAAAOAAYAAQABAAEAAAAOAAUAAQABAAEAAAANAAoAAQABAAIAAAANAAkAAQABAAEAAAANAAgAAQABAAEAAAANAAcAAQABAAEAAAANAAYAAQABAAEAAAANAAUAAQABAAEAAAAPAAQAAQACAAEAAAAPAAMAAQACAAEAAAAPAAIAAQACAAEAAAAPAAEAAQACAAEAAAAPAAAAAQACAAAAAAAOAAQAAQABAAEAAAAOAAMAAQABAAEAAAAOAAIAAQABAAEAAAAOAAEAAQABAAEAAAAOAAAAAQABAAAAAAANAAQAAQABAAEAAAANAAMAAQABAAEAAAANAAIAAQABAAEAAAANAAEAAQABAAEAAAANAAAAAQABAAAAAAAVAAoAAQAAAAIAAAAVAAkAAQAAAAEAAAAVAAgAAQAAAAEAAAAVAAcAAQAAAAEAAAAVAAYAAQAAAAAAAAAZAAoAAQACAAIAAAAZAAkAAQACAAEAAAAZAAgAAQACAAEAAAAZAAcAAQACAAEAAAAZAAYAAQACAAAAAAAYAAYAAQABAAAAAAAXAAYAAQABAAAAAAAWAAYAAQABAAAAAAAYAAoAAQABAAIAAAAYAAkAAQABAAEAAAAYAAgAAQABAAEAAAAYAAcAAQABAAEAAAAXAAoAAQABAAIAAAAWAAoAAQABAAIAAAAXAAkAAQABAAEAAAAWAAkAAQABAAEAAAAXAAgAAQABAAEAAAAWAAgAAQABAAEAAAAXAAcAAQABAAEAAAAWAAcAAQABAAEAAAAUAAMABQACAAIAAAAUAAIABQACAAEAAAAUAAEABQACAAEAAAAUAAAABQACAAAAAAATAAMABQABAAIAAAATAAIABQABAAEAAAATAAEABQABAAEAAAATAAAABQABAAAAAAASAAMABQABAAIAAAASAAIABQABAAEAAAASAAEABQABAAEAAAASAAAABQABAAAAAAARAAMABQAAAAIAAAARAAIABQAAAAEAAAARAAEABQAAAAEAAAARAAAABQAAAAAAAAA=") +tile_set = ExtResource("5_717he") + +[node name="Paths" type="TileMapLayer" parent="TileMapLayers" unique_id=137950784] +tile_map_data = PackedByteArray("AAALAAUAAQAGAAMAAAAKAAUAAQAGAAMAAAAJAAUAAQAGAAMAAAAIAAUAAQAGAAMAAAAHAAUAAQAGAAMAAAAGAAUAAQAGAAEAAAAFAAUAAQAGAAEAAAAEAAUAAQAGAAEAAAADAAUAAQAFAAEAAAAGAAQAAQAHAAAAAAAFAAQAAQAGAAAAAAAEAAQAAQAGAAAAAAADAAQAAQAFAAAAAAAGAAYAAQAHAAIAAAAFAAYAAQAGAAIAAAAEAAYAAQAGAAIAAAADAAYAAQAFAAIAAAAMAAUAAQAGAAMAAAANAAUAAQAGAAMAAAAOAAUAAQAHAAAAAAAOAAYAAQAIAAEAAAAOAAcAAQAIAAEAAAAOAAgAAQAFAAIAAAAPAAgAAQAHAAMAAAAVAAgAAQAFAAEAAAAVAAcAAQAFAAAAAAAWAAcAAQAGAAAAAAAXAAcAAQAHAAAAAAAXAAgAAQAHAAEAAAAXAAkAAQAHAAIAAAAWAAkAAQAGAAIAAAAVAAkAAQAFAAIAAAAWAAgAAQAGAAEAAAA=") +tile_set = ExtResource("5_717he") + +[node name="Elevation" type="TileMapLayer" parent="TileMapLayers" unique_id=886359704] +tile_map_data = PackedByteArray("AAAIAAEABAACAAQAAAAHAAEABAABAAQAAAAGAAEABAABAAQAAAAFAAEABAABAAQAAAADAAEABAABAAQAAAAEAAEABAABAAQAAAACAAEABAAAAAQAAAA=") +tile_set = ExtResource("4_3uytt") + +[node name="Bridge" type="TileMapLayer" parent="TileMapLayers" unique_id=1571624698] +tile_map_data = PackedByteArray("AAAQAAgAAwABAAAAAAARAAgAAwABAAAAAAASAAgAAwABAAAAAAATAAgAAwABAAAAAAAUAAgAAwABAAAAAAAPAAgAAwAAAAAAAAAVAAgAAwACAAAAAAASAAUAAwAAAAIAAAASAAQAAwAAAAIAAAASAAMAAwAAAAEAAAASAAYAAwAAAAMAAAARAAEAAwACAAAAAAAQAAEAAwABAAAAAAAPAAEAAwAAAAAAAAA=") +tile_set = ExtResource("6_obhrw") + +[node name="Fence" type="TileMapLayer" parent="TileMapLayers" unique_id=1482788515] +tile_map_data = PackedByteArray("AAACAAMAAAAAAAAAAAADAAMAAAABAAIAAAAEAAMAAAABAAIAAAAFAAMAAAABAAAAAAAGAAMAAAABAAAAAAAHAAMAAAABAAAAAAAIAAMAAAACAAAAAAAIAAcAAAACAAIAAAAHAAcAAAABAAIAAAAGAAcAAAABAAAAAAAFAAcAAAABAAAAAAAEAAcAAAABAAAAAAADAAcAAAABAAIAAAACAAcAAAAAAAIAAAACAAYAAAACAAEAAAACAAUAAAACAAEAAAACAAQAAAACAAEAAAAIAAQAAAACAAIAAAAHAAQAAAADAAAAAAAIAAYAAAACAAAAAAAHAAYAAAADAAAAAAA=") +tile_set = ExtResource("7_5lu30") + +[node name="FenceDoor" type="TileMapLayer" parent="TileMapLayers" unique_id=1123335364] +tile_map_data = PackedByteArray("AAAHAAQAAAADAAEAAAAHAAUAAAACAAEAAAAHAAYAAAADAAIAAAA=") +tile_set = ExtResource("7_5lu30") + +[node name="OnTheGround" type="Node2D" parent="." unique_id=888515986] +y_sort_enabled = true + +[node name="Player" parent="OnTheGround" unique_id=296354958 instance=ExtResource("8_2l846")] +position = Vector2(496, 48) +sprite_frames = ExtResource("9_powm1") + +[node name="Camera2D" type="Camera2D" parent="OnTheGround/Player" unique_id=1223276421] +limit_left = -64 +limit_top = -64 +limit_right = 1792 +limit_bottom = 832 +position_smoothing_enabled = true + +[node name="HookableBox" parent="OnTheGround" unique_id=1813344410 node_paths=PackedStringArray("constrain_layer") instance=ExtResource("11_b1u67")] +position = Vector2(1504, 480) +constrain_layer = NodePath("../../TileMapLayers/Paths") + +[node name="HookableLever" parent="OnTheGround" unique_id=653701664 node_paths=PackedStringArray("targets") instance=ExtResource("14_esoiq")] +position = Vector2(703, 448) +targets = [NodePath("../../ToggleableTileMapLayer")] + +[node name="HookableNeedle" parent="OnTheGround" unique_id=1182662384 instance=ExtResource("15_bhn5g")] +position = Vector2(64, 96) + +[node name="PointLight2D" type="PointLight2D" parent="OnTheGround/HookableNeedle" unique_id=734470694 groups=["night-lights"]] +visibility_layer = 2 +position = Vector2(0, -48) +scale = Vector2(0.5, 0.5) +energy = 0.6 +texture = SubResource("GradientTexture2D_3tad6") + +[node name="HookableNeedle2" parent="OnTheGround" unique_id=386123252 instance=ExtResource("15_bhn5g")] +position = Vector2(64, 352) + +[node name="PointLight2D" type="PointLight2D" parent="OnTheGround/HookableNeedle2" unique_id=1969055104 groups=["night-lights"]] +visibility_layer = 2 +position = Vector2(0, -48) +scale = Vector2(0.5, 0.5) +energy = 0.6 +texture = SubResource("GradientTexture2D_3tad6") + +[node name="HookableNeedle3" parent="OnTheGround" unique_id=1541754292 instance=ExtResource("15_bhn5g")] +position = Vector2(64, 576) + +[node name="PointLight2D" type="PointLight2D" parent="OnTheGround/HookableNeedle3" unique_id=865877770 groups=["night-lights"]] +visibility_layer = 2 +position = Vector2(0, -48) +scale = Vector2(0.5, 0.5) +energy = 0.6 +texture = SubResource("GradientTexture2D_3tad6") + +[node name="HookableNeedle4" parent="OnTheGround" unique_id=1696148782 instance=ExtResource("15_bhn5g")] +position = Vector2(352, 576) + +[node name="PointLight2D" type="PointLight2D" parent="OnTheGround/HookableNeedle4" unique_id=1217951757 groups=["night-lights"]] +visibility_layer = 2 +position = Vector2(0, -48) +scale = Vector2(0.5, 0.5) +energy = 0.6 +texture = SubResource("GradientTexture2D_3tad6") + +[node name="HookableNeedle5" parent="OnTheGround" unique_id=283131822 instance=ExtResource("15_bhn5g")] +position = Vector2(592, 576) + +[node name="PointLight2D" type="PointLight2D" parent="OnTheGround/HookableNeedle5" unique_id=1253293046 groups=["night-lights"]] +visibility_layer = 2 +position = Vector2(0, -48) +scale = Vector2(0.5, 0.5) +energy = 0.6 +texture = SubResource("GradientTexture2D_3tad6") + +[node name="HookableNeedle6" parent="OnTheGround" unique_id=656099003 instance=ExtResource("15_bhn5g")] +position = Vector2(816, 576) + +[node name="PointLight2D" type="PointLight2D" parent="OnTheGround/HookableNeedle6" unique_id=886695842 groups=["night-lights"]] +visibility_layer = 2 +position = Vector2(0, -48) +scale = Vector2(0.5, 0.5) +energy = 0.6 +texture = SubResource("GradientTexture2D_3tad6") + +[node name="HookableNeedle7" parent="OnTheGround" unique_id=1298558885 instance=ExtResource("15_bhn5g")] +position = Vector2(1184, 560) + +[node name="PointLight2D" type="PointLight2D" parent="OnTheGround/HookableNeedle7" unique_id=90025616 groups=["night-lights"]] +visibility_layer = 2 +position = Vector2(0, -48) +scale = Vector2(0.5, 0.5) +energy = 0.6 +texture = SubResource("GradientTexture2D_3tad6") + +[node name="HookableNeedle8" parent="OnTheGround" unique_id=143284863 instance=ExtResource("15_bhn5g")] +position = Vector2(1440, 688) + +[node name="PointLight2D" type="PointLight2D" parent="OnTheGround/HookableNeedle8" unique_id=2023086458 groups=["night-lights"]] +visibility_layer = 2 +position = Vector2(0, -48) +scale = Vector2(0.5, 0.5) +energy = 0.6 +texture = SubResource("GradientTexture2D_3tad6") + +[node name="HookableNeedle9" parent="OnTheGround" unique_id=116616300 instance=ExtResource("15_bhn5g")] +position = Vector2(1568, 560) + +[node name="PointLight2D" type="PointLight2D" parent="OnTheGround/HookableNeedle9" unique_id=1265095091 groups=["night-lights"]] +visibility_layer = 2 +position = Vector2(0, -48) +scale = Vector2(0.5, 0.5) +energy = 0.6 +texture = SubResource("GradientTexture2D_3tad6") + +[node name="HookableNeedle10" parent="OnTheGround" unique_id=1455715096 instance=ExtResource("15_bhn5g")] +position = Vector2(1440, 400) + +[node name="PointLight2D" type="PointLight2D" parent="OnTheGround/HookableNeedle10" unique_id=203226480 groups=["night-lights"]] +visibility_layer = 2 +position = Vector2(0, -48) +scale = Vector2(0.5, 0.5) +energy = 0.6 +texture = SubResource("GradientTexture2D_3tad6") + +[node name="HookableNeedle11" parent="OnTheGround" unique_id=877767314 instance=ExtResource("15_bhn5g")] +position = Vector2(1376, 400) + +[node name="PointLight2D" type="PointLight2D" parent="OnTheGround/HookableNeedle11" unique_id=1701548273 groups=["night-lights"]] +visibility_layer = 2 +position = Vector2(0, -48) +scale = Vector2(0.5, 0.5) +energy = 0.6 +texture = SubResource("GradientTexture2D_3tad6") + +[node name="HookableNeedle12" parent="OnTheGround" unique_id=1544142832 instance=ExtResource("15_bhn5g")] +position = Vector2(1376, 688) + +[node name="PointLight2D" type="PointLight2D" parent="OnTheGround/HookableNeedle12" unique_id=1998005919 groups=["night-lights"]] +visibility_layer = 2 +position = Vector2(0, -48) +scale = Vector2(0.5, 0.5) +energy = 0.6 +texture = SubResource("GradientTexture2D_3tad6") + +[node name="Decoration" type="Node2D" parent="OnTheGround" unique_id=1499875127] +y_sort_enabled = true + +[node name="SignPosts" type="Node2D" parent="OnTheGround" unique_id=1821765009] +y_sort_enabled = true + +[node name="Sign" parent="OnTheGround/SignPosts" unique_id=1579530966 instance=ExtResource("18_d3uwq")] +position = Vector2(170, -522) +direction = 1 +text = "FIXME" + +[node name="Guard" parent="OnTheGround" unique_id=689832587 node_paths=PackedStringArray("patrol_path") instance=ExtResource("13_rna1f")] +position = Vector2(224, 352) +sprite_frames = ExtResource("14_1is7x") +alerted_sound_stream = ExtResource("15_3tad6") +footsteps_sound_stream = ExtResource("16_3uytt") +idle_sound_stream = ExtResource("17_717he") +alert_others_sound_stream = ExtResource("18_obhrw") +patrol_path = NodePath("../Guard-PatrolPath") +time_to_detect_player = 5.0 + +[node name="Guard2" parent="OnTheGround" unique_id=1756140672 instance=ExtResource("13_rna1f")] +position = Vector2(593, 463) +sprite_frames = ExtResource("14_1is7x") +alerted_sound_stream = ExtResource("15_3tad6") +footsteps_sound_stream = ExtResource("16_3uytt") +idle_sound_stream = ExtResource("17_717he") +alert_others_sound_stream = ExtResource("18_obhrw") + +[node name="Guard3" parent="OnTheGround" unique_id=605843330 instance=ExtResource("13_rna1f")] +position = Vector2(592, 288) +sprite_frames = ExtResource("14_1is7x") +alerted_sound_stream = ExtResource("15_3tad6") +footsteps_sound_stream = ExtResource("16_3uytt") +idle_sound_stream = ExtResource("17_717he") +alert_others_sound_stream = ExtResource("18_obhrw") + +[node name="Guard-PatrolPath" type="Path2D" parent="OnTheGround" unique_id=165237872] +position = Vector2(207, 349.042) +curve = SubResource("Curve2D_rna1f") + +[node name="ToggleableTileMapLayer" type="Node2D" parent="." unique_id=374995496 node_paths=PackedStringArray("target")] +script = ExtResource("20_tbxxm") +target = NodePath("../TileMapLayers/FenceDoor") +metadata/_custom_type_script = "uid://c5jp8y8mpge4w" + +[node name="ScreenOverlay" type="CanvasLayer" parent="." unique_id=789862376] + +[node name="CanvasModulate" type="CanvasModulate" parent="." unique_id=1807394969] +color = Color(0.481789, 0.48179, 0.481789, 1) + +[node name="StealthGameLogic" type="Node" parent="." unique_id=39585550] +script = ExtResource("20_rna1f") diff --git a/scenes/game_elements/props/hookable_box/components/hookable_box.gd b/scenes/game_elements/props/hookable_box/components/hookable_box.gd new file mode 100644 index 000000000..f853b17ab --- /dev/null +++ b/scenes/game_elements/props/hookable_box/components/hookable_box.gd @@ -0,0 +1,74 @@ +# SPDX-FileCopyrightText: The Threadbare Authors +# SPDX-License-Identifier: MPL-2.0 +extends AnimatableBody2D + +const NEIGHBORS_FOR_AXIS: Dictionary[Vector2i, TileSet.CellNeighbor] = { + Vector2i.DOWN: TileSet.CELL_NEIGHBOR_BOTTOM_SIDE, + Vector2i.LEFT: TileSet.CELL_NEIGHBOR_LEFT_SIDE, + Vector2i.UP: TileSet.CELL_NEIGHBOR_TOP_SIDE, + Vector2i.RIGHT: TileSet.CELL_NEIGHBOR_RIGHT_SIDE, +} + +@export var constrain_layer: TileMapLayer + +var tween: Tween + +@onready var hookable_area: HookableArea = %HookableArea +@onready var shaker: Shaker = %Shaker + + +func global_position_to_tile_coordinate(global_pos: Vector2) -> Vector2i: + return constrain_layer.local_to_map(constrain_layer.to_local(global_pos)) + + +func tile_coordinate_to_global_position(coord: Vector2i) -> Vector2: + return constrain_layer.to_global(constrain_layer.map_to_local(coord)) + + +func _ready() -> void: + # Put this object on the grid: + var coord := global_position_to_tile_coordinate(global_position) + global_position = tile_coordinate_to_global_position(coord) + + +func get_closest_axis(vector: Vector2) -> Vector2i: + if abs(vector.x) > abs(vector.y): + # Closer to Horizontal (X-axis) + return Vector2i(sign(vector.x), 0) + + # Closer to Vertical (Y-axis) + return Vector2i(0, sign(vector.y)) + + +func got_pulled(direction: Vector2) -> void: + var axis := get_closest_axis(direction) + if axis == Vector2i.ZERO: + shaker.shake() + await Engine.get_main_loop().create_timer(0.1).timeout + hookable_area.release_from_pull(true) + return + + var neighbor := NEIGHBORS_FOR_AXIS[axis] + var coord := global_position_to_tile_coordinate(global_position) + assert(constrain_layer.get_cell_tile_data(coord) != null) + var new_coord := constrain_layer.get_neighbor_cell(coord, neighbor) + var data := constrain_layer.get_cell_tile_data(new_coord) + + if not data: + shaker.shake() + await Engine.get_main_loop().create_timer(0.1).timeout + hookable_area.release_from_pull(true) + return + + if tween: + if tween.is_running(): + return + tween.kill() + + tween = create_tween() + tween.set_ease(Tween.EASE_OUT) + # Assuming that the tile size is square: + var new_position := position + Vector2(axis) * constrain_layer.tile_set.tile_size.x + tween.tween_property(self, "position", new_position, .2) + await tween.finished + hookable_area.release_from_pull() diff --git a/scenes/game_elements/props/hookable_box/components/hookable_box.gd.uid b/scenes/game_elements/props/hookable_box/components/hookable_box.gd.uid new file mode 100644 index 000000000..421b8aab4 --- /dev/null +++ b/scenes/game_elements/props/hookable_box/components/hookable_box.gd.uid @@ -0,0 +1 @@ +uid://gvjhy1uvsqpc diff --git a/scenes/game_elements/props/hookable_box/hookable_box.tscn b/scenes/game_elements/props/hookable_box/hookable_box.tscn new file mode 100644 index 000000000..8cf6a7de8 --- /dev/null +++ b/scenes/game_elements/props/hookable_box/hookable_box.tscn @@ -0,0 +1,69 @@ +[gd_scene format=3 uid="uid://dkpelgxwtvcxd"] + +[ext_resource type="Script" uid="uid://gvjhy1uvsqpc" path="res://scenes/game_elements/props/hookable_box/components/hookable_box.gd" id="1_34j61"] +[ext_resource type="Texture2D" uid="uid://dslom0xbe1if7" path="res://assets/third_party/tiny-swords/Terrain/Ground/Shadows.png" id="2_f8qwn"] +[ext_resource type="Texture2D" uid="uid://c7oht7wudd8wa" path="res://assets/first_party/tiles/Cliff_Tiles.png" id="3_u0gye"] +[ext_resource type="Script" uid="uid://dabvr3pqmyya4" path="res://scenes/game_elements/props/hookable_area/components/hookable_area.gd" id="4_ml4a5"] +[ext_resource type="Script" uid="uid://dunsvrhq42214" path="res://scenes/game_elements/fx/shaker/components/shaker.gd" id="4_to7a4"] + +[sub_resource type="AtlasTexture" id="AtlasTexture_bu3x1"] +atlas = ExtResource("3_u0gye") +region = Rect2(192, 256, 64, 128) + +[sub_resource type="RectangleShape2D" id="RectangleShape2D_8dti7"] +size = Vector2(64, 64) + +[sub_resource type="RectangleShape2D" id="RectangleShape2D_ml4a5"] +size = Vector2(96, 160) + +[sub_resource type="OccluderPolygon2D" id="OccluderPolygon2D_34j61"] +polygon = PackedVector2Array(-32, 32, 32, 32, 32, -64, -32, -64) + +[node name="HookableBox" type="AnimatableBody2D" unique_id=1805651676] +editor_description = "A hookable box that moves in a fixed grid. + +Almost the same as the repellable box example, but instead has a HookableArea and defines a got_hooked() handler method." +collision_layer = 768 +collision_mask = 531 +script = ExtResource("1_34j61") + +[node name="Sprite2D2" type="Sprite2D" parent="." unique_id=1393129317] +texture = ExtResource("2_f8qwn") + +[node name="Sprite2D" type="Sprite2D" parent="." unique_id=898668959] +position = Vector2(0, -32) +texture = SubResource("AtlasTexture_bu3x1") + +[node name="CollisionShape2D" type="CollisionShape2D" parent="." unique_id=363689712] +rotation = -1.5707964 +shape = SubResource("RectangleShape2D_8dti7") + +[node name="Shaker" type="Node2D" parent="." unique_id=103380831 node_paths=PackedStringArray("target")] +unique_name_in_owner = true +script = ExtResource("4_to7a4") +target = NodePath("..") +shake_intensity = 60.0 +duration = 0.5 +frequency = 30.0 +metadata/_custom_type_script = "uid://dunsvrhq42214" + +[node name="HookableArea" type="Area2D" parent="." unique_id=1417858664 node_paths=PackedStringArray("controlled_entity", "anchor_point")] +unique_name_in_owner = true +collision_layer = 4096 +collision_mask = 0 +script = ExtResource("4_ml4a5") +controlled_entity = NodePath("..") +anchor_point = NodePath("Marker2D") +weight = 0.0 +metadata/_custom_type_script = "uid://dabvr3pqmyya4" + +[node name="CollisionShape2D" type="CollisionShape2D" parent="HookableArea" unique_id=68720889] +position = Vector2(0, -32) +shape = SubResource("RectangleShape2D_ml4a5") +debug_color = Color(0.689707, 0.288376, 1, 0.42) + +[node name="Marker2D" type="Marker2D" parent="HookableArea" unique_id=1758696959] +position = Vector2(0, -16) + +[node name="LightOccluder2D" type="LightOccluder2D" parent="." unique_id=1375993843] +occluder = SubResource("OccluderPolygon2D_34j61") diff --git a/scenes/game_elements/props/hookable_lever/components/hookable_lever.gd b/scenes/game_elements/props/hookable_lever/components/hookable_lever.gd new file mode 100644 index 000000000..0b36357cc --- /dev/null +++ b/scenes/game_elements/props/hookable_lever/components/hookable_lever.gd @@ -0,0 +1,65 @@ +# SPDX-FileCopyrightText: The Threadbare Authors +# SPDX-License-Identifier: MPL-2.0 +extends StaticBody2D + +## Emitted when the scene starts, indicating the initial state of this lever. +signal initialized(satisfied: bool) + +## Emitted when the lever state changes. +signal toggled(is_on: bool) + +# Note: Changing the value of "is_on" won't emit a signal. To do that, use "toggle" +@export var is_on: bool = false: + set(new_val): + is_on = new_val + update_appearance() + +# Toggles can be connected via targets (simple) or via signal (using the toggled signal) +@export var targets: Array[Toggleable] + +@onready var hookable_area: HookableArea = %HookableArea +@onready var lever_sprite: Sprite2D = %LeverSprite +@onready var shaker: Shaker = %Shaker + + +func update_appearance() -> void: + if not is_node_ready(): + return + lever_sprite.frame = 1 if is_on else 0 + + +func _ready() -> void: + _connect_targets() + + # To ensure the targets are ready, we do a "call_deferred" + _initialize_toggle_state.call_deferred() + + +func _initialize_toggle_state() -> void: + initialized.emit(is_on) + + +func _connect_targets() -> void: + for target: Toggleable in targets: + initialized.connect(target.initialize_with_value) + toggled.connect(target.set_toggled) + + +func got_pulled(direction: Vector2) -> void: + var sign_x := signf(direction.x) + if sign_x == 1 and not is_on: + toggle() + await get_tree().create_timer(0.2).timeout + hookable_area.release_from_pull() + elif sign_x == -1 and is_on: + toggle() + await get_tree().create_timer(0.2).timeout + hookable_area.release_from_pull() + else: + shaker.shake() + hookable_area.release_from_pull(true) + + +func toggle(new_val: bool = not is_on) -> void: + is_on = new_val + toggled.emit(is_on) diff --git a/scenes/game_elements/props/hookable_lever/components/hookable_lever.gd.uid b/scenes/game_elements/props/hookable_lever/components/hookable_lever.gd.uid new file mode 100644 index 000000000..6592199fc --- /dev/null +++ b/scenes/game_elements/props/hookable_lever/components/hookable_lever.gd.uid @@ -0,0 +1 @@ +uid://d3iwkqlwrjrg0 diff --git a/scenes/game_elements/props/hookable_lever/hookable_lever.tscn b/scenes/game_elements/props/hookable_lever/hookable_lever.tscn new file mode 100644 index 000000000..80436521f --- /dev/null +++ b/scenes/game_elements/props/hookable_lever/hookable_lever.tscn @@ -0,0 +1,51 @@ +[gd_scene format=3 uid="uid://c47lkkvljrsg"] + +[ext_resource type="Script" uid="uid://d3iwkqlwrjrg0" path="res://scenes/game_elements/props/hookable_lever/components/hookable_lever.gd" id="1_mrig6"] +[ext_resource type="Texture2D" uid="uid://uy2acspf6apo" path="res://scenes/game_elements/props/lever/components/Lever.png" id="2_ved61"] +[ext_resource type="Script" uid="uid://dunsvrhq42214" path="res://scenes/game_elements/fx/shaker/components/shaker.gd" id="3_082ac"] +[ext_resource type="Script" uid="uid://dabvr3pqmyya4" path="res://scenes/game_elements/props/hookable_area/components/hookable_area.gd" id="4_l44sk"] + +[sub_resource type="CapsuleShape2D" id="CapsuleShape2D_p2k53"] +height = 58.0 + +[sub_resource type="RectangleShape2D" id="RectangleShape2D_h3468"] +size = Vector2(96, 96) + +[node name="HookableLever" type="StaticBody2D" unique_id=37452001] +editor_description = "A repellable lever. + +It can be switched in the horizontal direction that's being repelled. It will shake when the direction is the same as the lever, indicating that it couldn't be switched from there." +collision_layer = 768 +collision_mask = 0 +script = ExtResource("1_mrig6") + +[node name="LeverSprite" type="Sprite2D" parent="." unique_id=2043405312] +unique_name_in_owner = true +position = Vector2(0, -10) +texture = ExtResource("2_ved61") +hframes = 2 + +[node name="CollisionShape2D" type="CollisionShape2D" parent="." unique_id=1984459816] +rotation = -1.5707964 +shape = SubResource("CapsuleShape2D_p2k53") + +[node name="Shaker" type="Node2D" parent="." unique_id=1105059859 node_paths=PackedStringArray("target")] +unique_name_in_owner = true +script = ExtResource("3_082ac") +target = NodePath("..") +duration = 0.5 +frequency = 30.0 +metadata/_custom_type_script = "uid://dunsvrhq42214" + +[node name="HookableArea" type="Area2D" parent="." unique_id=72195883 node_paths=PackedStringArray("controlled_entity")] +unique_name_in_owner = true +collision_layer = 4096 +collision_mask = 0 +script = ExtResource("4_l44sk") +controlled_entity = NodePath("..") +weight = 0.0 +metadata/_custom_type_script = "uid://dabvr3pqmyya4" + +[node name="CollisionShape2D" type="CollisionShape2D" parent="HookableArea" unique_id=503665020] +shape = SubResource("RectangleShape2D_h3468") +debug_color = Color(0.689707, 0.288376, 1, 0.42) From 7cc764a8a08ecc5a73bae45918320b0dbfea04d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Qui=C3=B1ones?= Date: Thu, 9 Apr 2026 13:14:31 -0300 Subject: [PATCH 3/3] Repellable box: Add light occluder --- .../game_elements/props/repellable_box/repellable_box.tscn | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scenes/game_elements/props/repellable_box/repellable_box.tscn b/scenes/game_elements/props/repellable_box/repellable_box.tscn index 8508bbcce..37e6592dd 100644 --- a/scenes/game_elements/props/repellable_box/repellable_box.tscn +++ b/scenes/game_elements/props/repellable_box/repellable_box.tscn @@ -12,6 +12,9 @@ region = Rect2(192, 256, 64, 128) [sub_resource type="RectangleShape2D" id="RectangleShape2D_8dti7"] size = Vector2(64, 64) +[sub_resource type="OccluderPolygon2D" id="OccluderPolygon2D_sbite"] +polygon = PackedVector2Array(-32, 32, 32, 32, 32, -64, -32, -64) + [node name="RepellableBox" type="AnimatableBody2D" unique_id=1805651676] editor_description = "A repellable box that moves in a fixed grid. @@ -43,3 +46,6 @@ shake_intensity = 60.0 duration = 0.5 frequency = 30.0 metadata/_custom_type_script = "uid://dunsvrhq42214" + +[node name="LightOccluder2D" type="LightOccluder2D" parent="." unique_id=2018120731] +occluder = SubResource("OccluderPolygon2D_sbite")