From 322cca50a6d0ef97222e1b3c2437481ef3ba190e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Qui=C3=B1ones?= Date: Thu, 26 Mar 2026 11:30:03 -0300 Subject: [PATCH 1/6] Physics layers: Rename projectile to repellable Similar to the "hookable" layer. To make the repel mechanic a bit more generic. Update the enum and the references in eldrune StoryQuest. --- project.godot | 2 +- scenes/globals/enums.gd | 2 +- .../eldrune/2_combat/combat_components/eldrune_projectile.gd | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/project.godot b/project.godot index 1e49f54ab..bb91ec11f 100644 --- a/project.godot +++ b/project.godot @@ -294,7 +294,7 @@ locale/translations_pot_files=PackedStringArray("res://scenes/menus/title/compon 2d_physics/layer_6="interactable" 2d_physics/layer_7="players hitbox" 2d_physics/layer_8="enemies hitbox" -2d_physics/layer_9="projectiles" +2d_physics/layer_9="repellable" 2d_physics/layer_10="non_walkable_floor" 2d_physics/layer_13="hookable" diff --git a/scenes/globals/enums.gd b/scenes/globals/enums.gd index b049f70b7..166c3b620 100644 --- a/scenes/globals/enums.gd +++ b/scenes/globals/enums.gd @@ -21,7 +21,7 @@ enum CollisionLayers { INTERACTABLE = 6, PLAYERS_HITBOX = 7, ENEMIES_HITBOX = 8, - PROJECTILES = 9, + REPELLABLE = 9, NON_WALKABLE_FLOOR = 10, HOOKABLE = 13, } diff --git a/scenes/quests/story_quests/eldrune/2_combat/combat_components/eldrune_projectile.gd b/scenes/quests/story_quests/eldrune/2_combat/combat_components/eldrune_projectile.gd index b86415ac0..a489581c6 100644 --- a/scenes/quests/story_quests/eldrune/2_combat/combat_components/eldrune_projectile.gd +++ b/scenes/quests/story_quests/eldrune/2_combat/combat_components/eldrune_projectile.gd @@ -123,13 +123,13 @@ func _configure_disabled_collision_layers() -> void: set_collision_mask_value(Enums.CollisionLayers.PLAYERS_HITBOX, false) # Change from PROJECTILES to NON_WALKABLE_FLOOR layer - set_collision_layer_value(Enums.CollisionLayers.PROJECTILES, false) + set_collision_layer_value(Enums.CollisionLayers.REPELLABLE, false) set_collision_layer_value(Enums.CollisionLayers.NON_WALKABLE_FLOOR, true) # Enable collisions with environment and entities set_collision_mask_value(Enums.CollisionLayers.WALLS, true) set_collision_mask_value(Enums.CollisionLayers.PLAYERS, true) - set_collision_mask_value(Enums.CollisionLayers.PROJECTILES, true) + set_collision_mask_value(Enums.CollisionLayers.REPELLABLE, true) set_collision_mask_value(Enums.CollisionLayers.NON_WALKABLE_FLOOR, true) set_collision_mask_value(Enums.CollisionLayers.ENEMIES_HITBOX, false) From ff69804045aeeed4e19a5d1608080674bb54ad0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Qui=C3=B1ones?= Date: Thu, 26 Mar 2026 15:28:52 -0300 Subject: [PATCH 2/6] Project: Set default gravity to zero Otherwise adding a RigidBody2D to the scene, the bodies go down until they hit a wall. Setting the default gravity to zero, the object looks like it's on the ground. --- project.godot | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/project.godot b/project.godot index bb91ec11f..044a15d0e 100644 --- a/project.godot +++ b/project.godot @@ -298,6 +298,10 @@ locale/translations_pot_files=PackedStringArray("res://scenes/menus/title/compon 2d_physics/layer_10="non_walkable_floor" 2d_physics/layer_13="hookable" +[physics] + +2d/default_gravity=0.0 + [rendering] textures/canvas_textures/default_texture_filter=0 From a748ffe278460009ba0cdb70409a7c1cd82f8679 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Qui=C3=B1ones?= Date: Thu, 26 Mar 2026 16:36:54 -0300 Subject: [PATCH 3/6] Repel mechanic: Make it work with any Node2D That has a got_repelled() method. So far we've been assuming a Projectile node for the on_body_entered handler of the Repel area. But the Area2D.body_entered signal is sent with the body: Node2D parameter (which in reality can be a PhysicsBody2D or a TileMap). So use duck typing and also rename the expected method to "got_repelled". Also, call it with the direction of the repel. Previously the Player was passed and the repel direction was calculated on the called node. This makes the repel ability more generic. Also adapt the eldrune_projectile.gd script. --- .../characters/player/components/player_repel.gd | 6 ++++-- .../game_elements/props/projectile/components/projectile.gd | 6 ++++-- .../2_combat/combat_components/eldrune_projectile.gd | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/scenes/game_elements/characters/player/components/player_repel.gd b/scenes/game_elements/characters/player/components/player_repel.gd index 23d8183ac..f1fac57f1 100644 --- a/scenes/game_elements/characters/player/components/player_repel.gd +++ b/scenes/game_elements/characters/player/components/player_repel.gd @@ -29,5 +29,7 @@ func _unhandled_input(_event: InputEvent) -> void: repelling = false -func _on_air_stream_body_entered(body: Projectile) -> void: - body.got_hit(owner) +func _on_air_stream_body_entered(body: Node2D) -> void: + if body.has_method("got_repelled"): + var direction := global_position.direction_to(body.global_position) + body.got_repelled(direction) diff --git a/scenes/game_elements/props/projectile/components/projectile.gd b/scenes/game_elements/props/projectile/components/projectile.gd index 9e8d5a07e..99f35d918 100644 --- a/scenes/game_elements/props/projectile/components/projectile.gd +++ b/scenes/game_elements/props/projectile/components/projectile.gd @@ -153,10 +153,12 @@ func _on_body_entered(body: Node2D) -> void: queue_free() -func got_hit(player: Player) -> void: +## Called from the Repel component when this body +## enters the repel area. +func got_repelled(repel_direction: Vector2) -> void: add_small_fx() duration_timer.start() - var hit_vector: Vector2 = player.global_position.direction_to(global_position) * hit_speed + var hit_vector: Vector2 = repel_direction * hit_speed hit_sound.play() animated_sprite_2d.speed_scale = 2 if _trail_particles: diff --git a/scenes/quests/story_quests/eldrune/2_combat/combat_components/eldrune_projectile.gd b/scenes/quests/story_quests/eldrune/2_combat/combat_components/eldrune_projectile.gd index a489581c6..c385ff01a 100644 --- a/scenes/quests/story_quests/eldrune/2_combat/combat_components/eldrune_projectile.gd +++ b/scenes/quests/story_quests/eldrune/2_combat/combat_components/eldrune_projectile.gd @@ -87,7 +87,7 @@ func _physics_process(_delta: float) -> void: _is_sliding = false -func got_hit(player: Player) -> void: +func got_repelled(_direction: Vector2) -> void: if _is_disabled: return From 13737353df717313c192725e56c0f49eae27632c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Qui=C3=B1ones?= Date: Fri, 27 Mar 2026 15:26:15 -0300 Subject: [PATCH 4/6] Shaker: Fix testing the shake in the editor canvas Pressing the Test button was failing with: ``` Invalid access to property or key 'device_index' on a base object of type 'Node (input_helper.gd)'. ``` And in any case, the vibration of the joypad while working in the editor is not desired. --- scenes/game_elements/fx/shaker/components/shaker.gd | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scenes/game_elements/fx/shaker/components/shaker.gd b/scenes/game_elements/fx/shaker/components/shaker.gd index 3b1985b76..c597527a2 100644 --- a/scenes/game_elements/fx/shaker/components/shaker.gd +++ b/scenes/game_elements/fx/shaker/components/shaker.gd @@ -63,8 +63,10 @@ func _ready() -> void: func shake(intensity: float = shake_intensity, time: float = duration) -> void: noise.seed = randi() started.emit() - if InputHelper.device_index >= 0: - Input.start_joy_vibration(InputHelper.device_index, 0.5, 0.5, time) + if not Engine.is_editor_hint(): + # Don't vibrate the joypad when using the Test button in the editor: + if InputHelper.device_index >= 0: + Input.start_joy_vibration(InputHelper.device_index, 0.5, 0.5, time) var shaking_already_in_progress: bool = shake_tween and shake_tween.is_valid() if shaking_already_in_progress: From 7ffabdc406d26943d61e2c6f941bb247954336a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Qui=C3=B1ones?= Date: Fri, 27 Mar 2026 15:33:06 -0300 Subject: [PATCH 5/6] Shaker: Automatically set target to parent And show warning in the editor when the target is not set. --- .../fx/shaker/components/shaker.gd | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/scenes/game_elements/fx/shaker/components/shaker.gd b/scenes/game_elements/fx/shaker/components/shaker.gd index c597527a2..bfcc8e466 100644 --- a/scenes/game_elements/fx/shaker/components/shaker.gd +++ b/scenes/game_elements/fx/shaker/components/shaker.gd @@ -13,8 +13,11 @@ signal started ## Emitted when the target stopped shaking signal finished -## Node that will be shaked -@export var target: CanvasItem +## Node that will be shaked. If the parent node is a CanvasItem and target isn't set, +## the parent node will be automatically assigned to this variable. +@export var target: CanvasItem: + set = _set_target + ## Maximum possible value in which the position of the node might be offset. @export_range(1.0, 100.0, 1.0, "or_greater", "or_less") var shake_intensity: float = 30.0 ## How much time (in seconds) the node will be shaken. @@ -43,11 +46,28 @@ var current_intensity: float = 0.0 var shake_tween: Tween +func _enter_tree() -> void: + if not target and get_parent() is CanvasItem: + target = get_parent() + + func _ready() -> void: noise.noise_type = FastNoiseLite.TYPE_PERLIN noise.frequency = 1.0 +func _set_target(new_target: CanvasItem) -> void: + target = new_target + update_configuration_warnings() + + +func _get_configuration_warnings() -> PackedStringArray: + var warnings: PackedStringArray + if not target: + warnings.append("Target must be set.") + return warnings + + ## Shake the node's position by a maximum of [param intensity] and rotation by ## a maximum of [param intensity] * 0.01 during [param time]. ## When the effect finishes, [member target]'s position and rotation end up From a7e99a67a4d11b2cff09ca7b8f8e783b1a07243e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Qui=C3=B1ones?= Date: Thu, 26 Mar 2026 16:55:20 -0300 Subject: [PATCH 6/6] different repel objects --- .../components/custom_repellable_objects.tscn | 3 + .../components/repellable_box.gd | 63 +++++ .../components/repellable_box.gd.uid | 1 + .../props/repellable_box/repellable_box.tscn | 44 ++++ scenes/world_map/frays_end.tscn | 240 +++++++++++++++++- 5 files changed, 350 insertions(+), 1 deletion(-) create mode 100644 scenes/game_elements/components/custom_repellable_objects.tscn create mode 100644 scenes/game_elements/props/repellable_box/components/repellable_box.gd create mode 100644 scenes/game_elements/props/repellable_box/components/repellable_box.gd.uid create mode 100644 scenes/game_elements/props/repellable_box/repellable_box.tscn diff --git a/scenes/game_elements/components/custom_repellable_objects.tscn b/scenes/game_elements/components/custom_repellable_objects.tscn new file mode 100644 index 000000000..c853eb40f --- /dev/null +++ b/scenes/game_elements/components/custom_repellable_objects.tscn @@ -0,0 +1,3 @@ +[gd_scene format=3 uid="uid://blnnwmrypq0a6"] + +[node name="CustomRepellableObjects" type="Node2D" unique_id=324172432] diff --git a/scenes/game_elements/props/repellable_box/components/repellable_box.gd b/scenes/game_elements/props/repellable_box/components/repellable_box.gd new file mode 100644 index 000000000..85319ec83 --- /dev/null +++ b/scenes/game_elements/props/repellable_box/components/repellable_box.gd @@ -0,0 +1,63 @@ +# 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 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.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_repelled(direction: Vector2) -> void: + var axis := get_closest_axis(direction) + 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() + 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) diff --git a/scenes/game_elements/props/repellable_box/components/repellable_box.gd.uid b/scenes/game_elements/props/repellable_box/components/repellable_box.gd.uid new file mode 100644 index 000000000..d2e3a9a7a --- /dev/null +++ b/scenes/game_elements/props/repellable_box/components/repellable_box.gd.uid @@ -0,0 +1 @@ +uid://c334l8qftb4fy diff --git a/scenes/game_elements/props/repellable_box/repellable_box.tscn b/scenes/game_elements/props/repellable_box/repellable_box.tscn new file mode 100644 index 000000000..c1be4ae81 --- /dev/null +++ b/scenes/game_elements/props/repellable_box/repellable_box.tscn @@ -0,0 +1,44 @@ +[gd_scene format=3 uid="uid://dqo4d6mp4hwhi"] + +[ext_resource type="Script" uid="uid://c334l8qftb4fy" path="res://scenes/game_elements/props/repellable_box/components/repellable_box.gd" id="1_c0tix"] +[ext_resource type="Texture2D" uid="uid://c7oht7wudd8wa" path="res://assets/first_party/tiles/Cliff_Tiles.png" id="2_c0tix"] +[ext_resource type="Texture2D" uid="uid://dslom0xbe1if7" path="res://assets/third_party/tiny-swords/Terrain/Ground/Shadows.png" id="2_sbite"] +[ext_resource type="Script" uid="uid://dunsvrhq42214" path="res://scenes/game_elements/fx/shaker/components/shaker.gd" id="3_2i1pw"] + +[sub_resource type="AtlasTexture" id="AtlasTexture_bu3x1"] +atlas = ExtResource("2_c0tix") +region = Rect2(192, 256, 64, 128) + +[sub_resource type="RectangleShape2D" id="RectangleShape2D_8dti7"] +size = Vector2(64, 64) + +[node name="RepellableBox" type="AnimatableBody2D" unique_id=1805651676] +editor_description = "A repellable box that moves in a fixed grid. + +This is an AnimatableBody2D so it can move in the 64x64 tiles grid (using a Tweener for animating the position). + +It needs a TileMapLayer and it is constrained to the painted tiles. + + But also it doesn't collide with other bodies so it doesn't stop!" +collision_layer = 768 +collision_mask = 531 +script = ExtResource("1_c0tix") + +[node name="Sprite2D2" type="Sprite2D" parent="." unique_id=1393129317] +texture = ExtResource("2_sbite") + +[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")] +script = ExtResource("3_2i1pw") +target = NodePath("..") +shake_intensity = 60.0 +duration = 0.5 +frequency = 30.0 +metadata/_custom_type_script = "uid://dunsvrhq42214" diff --git a/scenes/world_map/frays_end.tscn b/scenes/world_map/frays_end.tscn index a2628de7d..cd83d49af 100644 --- a/scenes/world_map/frays_end.tscn +++ b/scenes/world_map/frays_end.tscn @@ -59,12 +59,19 @@ [ext_resource type="PackedScene" uid="uid://dgrrudegturnw" path="res://scenes/game_elements/characters/npcs/townie.tscn" id="54_duxxr"] [ext_resource type="PackedScene" uid="uid://daqd67aro1o1m" path="res://scenes/game_elements/fx/time_and_weather/time_and_weather.tscn" id="55_ojao8"] [ext_resource type="Script" uid="uid://cbj0406q05dly" path="res://scenes/game_elements/props/hint/input_key/interact_input.gd" id="55_wymun"] +[ext_resource type="Texture2D" uid="uid://ctwx8gghts62p" path="res://assets/third_party/tiny-swords/Deco/06.png" id="56_7kfal"] [ext_resource type="Script" uid="uid://edcifob4jc4s" path="res://scenes/game_logic/talk_behavior.gd" id="56_ojao8"] +[ext_resource type="Texture2D" uid="uid://ciwy8mfi0mv65" path="res://scenes/quests/story_quests/after_the_tremor/0_intro/Imagenes/ball_free.png" id="57_8dti7"] +[ext_resource type="Texture2D" uid="uid://pjdmmuwvkw14" path="res://assets/third_party/tiny-swords-non-cc0/Terrain/Resources/Gold/Gold Stones/Gold Stone 6.png" id="57_bu3x1"] +[ext_resource type="Texture2D" uid="uid://dlpjjca2udf42" path="res://scenes/game_elements/props/button_item/components/button-shadow.png" id="58_7hq0m"] [ext_resource type="Script" uid="uid://uaaaiviytliw" path="res://scenes/world_map/components/quest_progress_unlocker.gd" id="58_ojao8"] [ext_resource type="Script" uid="uid://dts1hwdy3phin" path="res://scenes/menus/storybook/components/quest.gd" id="59_qgpx3"] [ext_resource type="Resource" uid="uid://t50glay2iqhg" path="res://scenes/quests/lore_quests/quest_002/quest.tres" id="60_6b07c"] +[ext_resource type="PackedScene" uid="uid://dqo4d6mp4hwhi" path="res://scenes/game_elements/props/repellable_box/repellable_box.tscn" id="60_p2k53"] [ext_resource type="Script" uid="uid://0enyu5v4ra34" path="res://scenes/game_elements/props/spawn_point/components/spawn_point.gd" id="61_6b07c"] +[ext_resource type="Texture2D" uid="uid://uy2acspf6apo" path="res://scenes/game_elements/props/lever/components/Lever.png" id="62_p2k53"] [ext_resource type="Script" uid="uid://hqdquinbimce" path="res://scenes/game_elements/props/teleporter/teleporter.gd" id="62_t7c6s"] +[ext_resource type="Script" uid="uid://dunsvrhq42214" path="res://scenes/game_elements/fx/shaker/components/shaker.gd" id="63_a070f"] [ext_resource type="PackedScene" uid="uid://covsdqqsd6rsy" path="res://scenes/game_elements/props/sign/sign.tscn" id="64_uxfrp"] [sub_resource type="RectangleShape2D" id="RectangleShape2D_duxxr"] @@ -82,6 +89,117 @@ size = Vector2(52, 61) [sub_resource type="RectangleShape2D" id="RectangleShape2D_ulm71"] size = Vector2(128, 128) +[sub_resource type="PhysicsMaterial" id="PhysicsMaterial_16ifw"] + +[sub_resource type="GDScript" id="GDScript_bu3x1"] +script/source = "# SPDX-FileCopyrightText: The Threadbare Authors +# SPDX-License-Identifier: MPL-2.0 +extends RigidBody2D + +var tween: Tween + + +func got_repelled(direction: Vector2) -> void: + var hit_vector: Vector2 = direction * 300.0 + linear_velocity = Vector2.ZERO + apply_impulse(hit_vector) +" + +[sub_resource type="CapsuleShape2D" id="CapsuleShape2D_7n6eg"] +radius = 11.0 +height = 36.0 + +[sub_resource type="PhysicsMaterial" id="PhysicsMaterial_bu3x1"] +friction = 0.5 +bounce = 1.0 + +[sub_resource type="GDScript" id="GDScript_8dti7"] +script/source = "# SPDX-FileCopyrightText: The Threadbare Authors +# SPDX-License-Identifier: MPL-2.0 +extends RigidBody2D + +var tween: Tween + + +func got_repelled(direction: Vector2) -> void: + var hit_vector: Vector2 = direction * 300.0 + linear_damp = 2 + linear_velocity = Vector2.ZERO + apply_impulse(hit_vector) + + +func _on_sleeping_state_changed() -> void: + linear_damp = 10 +" + +[sub_resource type="CircleShape2D" id="CircleShape2D_bu3x1"] +radius = 19.104973 + +[sub_resource type="GDScript" id="GDScript_7kfal"] +script/source = "# SPDX-FileCopyrightText: The Threadbare Authors +# SPDX-License-Identifier: MPL-2.0 +extends RigidBody2D + + +func get_closest_axis(vector: Vector2) -> Vector2: + if abs(vector.x) > abs(vector.y): + # Closer to Horizontal (X-axis) + return Vector2(sign(vector.x), 0) + else: + # Closer to Vertical (Y-axis) + return Vector2(0, sign(vector.y)) + + +func got_repelled(direction: Vector2) -> void: + var hit_vector: Vector2 = get_closest_axis(direction) * 100.0 + linear_velocity = Vector2.ZERO + apply_impulse(hit_vector) +" + +[sub_resource type="CapsuleShape2D" id="CapsuleShape2D_bu3x1"] +radius = 19.0 +height = 64.0 + +[sub_resource type="GDScript" id="GDScript_p2k53"] +script/source = "# SPDX-FileCopyrightText: The Threadbare Authors +# SPDX-License-Identifier: MPL-2.0 +extends StaticBody2D + +const FRAME_FOR_SIDE: Dictionary[Enums.LookAtSide, int] = { + Enums.LookAtSide.LEFT: 0, + Enums.LookAtSide.RIGHT: 1, +} + +@onready var lever_sprite: Sprite2D = $LeverSprite +@onready var shaker: Shaker = $Shaker + +var lever_side: Enums.LookAtSide = Enums.LookAtSide.LEFT: + set = set_lever_side + +func set_lever_side(new_side: Enums.LookAtSide) -> void: + lever_side = new_side + lever_sprite.frame = FRAME_FOR_SIDE[lever_side] + +func _ready() -> void: + set_lever_side(lever_side) + + +func got_repelled(direction: Vector2) -> void: + var sign_x := signf(direction.x) + var new_side: Enums.LookAtSide + if sign_x == 1: + new_side = Enums.LookAtSide.RIGHT + elif sign_x == -1: + new_side = Enums.LookAtSide.LEFT + if new_side == lever_side: + shaker.shake() + else: + lever_side = new_side +" + +[sub_resource type="CapsuleShape2D" id="CapsuleShape2D_p2k53"] +height = 58.0 + [node name="FraysEnd" type="Node2D" unique_id=1849947277] script = ExtResource("1_4gse6") metadata/_edit_lock_ = true @@ -125,7 +243,7 @@ tile_map_data = PackedByteArray("AAAaAAYABgACAAAAAAAaAAcABgACAAIAAAAZAAcABgABAAI tile_set = ExtResource("7_r1ek1") [node name="Paths_Level1" type="TileMapLayer" parent="TileMapLayers" unique_id=971351640] -tile_map_data = PackedByteArray("AAARAA0AAQAHAAIAAAARAAwAAQAHAAEAAAAQAA0AAQAGAAIAAAAQAAwAAQAGAAEAAAAPAA4AAQAIAAEAAAAPAA0AAQAGAAEAAAAPAAwAAQAGAAEAAAAOAA0AAQAGAAIAAAAOAAwAAQAGAAEAAAANAA0AAQAFAAIAAAANAAwAAQAFAAEAAAARAAsAAQAHAAEAAAAQAAsAAQAGAAEAAAAPABEAAQAIAAEAAAAPABAAAQAIAAEAAAAPAA8AAQAIAAEAAAAPAAsAAQAGAAEAAAAOAAsAAQAGAAEAAAANAAsAAQAFAAEAAAAPABkAAQAHAAIAAAAPABgAAQAIAAEAAAAOABkAAQAGAAMAAAAPABcAAQAIAAAAAAAPABMAAQAIAAIAAAABABkAAQAGAAAAAAACABkAAQAGAAAAAAADABkAAQAGAAAAAAAEABkAAQAGAAMAAAAFABkAAQAGAAMAAAAGABkAAQAGAAMAAAAHABkAAQAGAAMAAAAIABkAAQAGAAMAAAAJABkAAQAGAAMAAAAKABkAAQAGAAMAAAALABkAAQAGAAMAAAAMABkAAQAGAAMAAAANABkAAQAGAAMAAAAAABkAAQAFAAAAAAAAABoAAQAFAAEAAAABABoAAQAGAAEAAAACABoAAQAGAAEAAAADABoAAQAHAAEAAAAAABsAAQAFAAIAAAABABsAAQAGAAIAAAACABsAAQAGAAIAAAADABsAAQAHAAIAAAAPABIAAQAIAAEAAAARAAoAAQAHAAEAAAANAAoAAQAFAAEAAAANAAkAAQAFAAAAAAAOAAkAAQAGAAAAAAAPAAkAAQAGAAAAAAAQAAkAAQAGAAAAAAARAAkAAQAHAAAAAAAOAAoAAQAGAAEAAAAPAAoAAQAGAAEAAAAQAAoAAQAGAAEAAAA=") +tile_map_data = PackedByteArray("AAARAA0AAQAHAAIAAAARAAwAAQAHAAEAAAAQAA0AAQAGAAIAAAAQAAwAAQAGAAEAAAAPAA4AAQAIAAEAAAAPAA0AAQAGAAEAAAAPAAwAAQAGAAEAAAAOAA0AAQAGAAIAAAAOAAwAAQAGAAEAAAANAA0AAQAFAAIAAAANAAwAAQAFAAEAAAARAAsAAQAHAAEAAAAQAAsAAQAGAAEAAAAPABEAAQAIAAEAAAAPABAAAQAIAAEAAAAPAA8AAQAIAAEAAAAPAAsAAQAGAAEAAAAOAAsAAQAGAAEAAAANAAsAAQAFAAEAAAAPABkAAQAHAAIAAAAPABgAAQAIAAEAAAAOABkAAQAGAAMAAAAPABcAAQAIAAAAAAAPABMAAQAGAAIAAAABABkAAQAGAAAAAAACABkAAQAGAAAAAAADABkAAQAGAAAAAAAEABkAAQAGAAMAAAAFABkAAQAGAAMAAAAGABkAAQAGAAMAAAAHABkAAQAGAAMAAAAIABkAAQAGAAMAAAAJABkAAQAGAAMAAAAKABkAAQAGAAMAAAALABkAAQAGAAMAAAAMABkAAQAGAAMAAAANABkAAQAGAAMAAAAAABkAAQAFAAAAAAAAABoAAQAFAAEAAAABABoAAQAGAAEAAAACABoAAQAGAAEAAAADABoAAQAHAAEAAAAAABsAAQAFAAIAAAABABsAAQAGAAIAAAACABsAAQAGAAIAAAADABsAAQAHAAIAAAAPABIAAQAGAAEAAAARAAoAAQAHAAEAAAANAAoAAQAFAAEAAAANAAkAAQAFAAAAAAAOAAkAAQAGAAAAAAAPAAkAAQAGAAAAAAAQAAkAAQAGAAAAAAARAAkAAQAHAAAAAAAOAAoAAQAGAAEAAAAPAAoAAQAGAAEAAAAQAAoAAQAGAAEAAAAOABMAAQAFAAIAAAAQABMAAQAHAAIAAAAOABIAAQAFAAAAAAAQABIAAQAHAAAAAAA=") tile_set = ExtResource("7_r1ek1") [node name="Paths_Level2" type="TileMapLayer" parent="TileMapLayers" unique_id=1784740911] @@ -1144,6 +1262,124 @@ shape = SubResource("RectangleShape2D_ulm71") position = Vector2(95, 664) text = "West End" +[node name="RepellableRock" type="RigidBody2D" parent="OnTheGround" unique_id=1901173256] +editor_description = "A repellable rock. + +It's in collision layer repellable for it to work, and has a got_repelled() method defined. + +It has some dumping to simulate friction that's why it stops (unlike the ice cube)." +position = Vector2(913, 868) +collision_layer = 768 +collision_mask = 531 +physics_material_override = SubResource("PhysicsMaterial_16ifw") +lock_rotation = true +linear_damp = 2.0 +script = SubResource("GDScript_bu3x1") + +[node name="Sprite2D" type="Sprite2D" parent="OnTheGround/RepellableRock" unique_id=513881379] +position = Vector2(-2, -3) +texture = ExtResource("56_7kfal") + +[node name="CollisionShape2D" type="CollisionShape2D" parent="OnTheGround/RepellableRock" unique_id=589342826] +rotation = -1.5707964 +shape = SubResource("CapsuleShape2D_7n6eg") + +[node name="BallAnchor" type="Node2D" parent="OnTheGround" unique_id=1725206225] +position = Vector2(1136, 1007) + +[node name="Sprite2D" type="Sprite2D" parent="OnTheGround/BallAnchor" unique_id=2045011868] +position = Vector2(0, -30) +scale = Vector2(2.489, 2) +texture = ExtResource("58_7hq0m") +hframes = 4 + +[node name="BallSprite" type="Sprite2D" parent="OnTheGround/BallAnchor" unique_id=1585779611] +position = Vector2(0, -17) +scale = Vector2(0.18770815, 0.18770815) +texture = ExtResource("57_8dti7") + +[node name="RepellableBall" type="RigidBody2D" parent="OnTheGround" unique_id=1394157854] +editor_description = "A repellable ball. +" +position = Vector2(1136, 1007) +scale = Vector2(0.99999994, 0.99999994) +collision_layer = 256 +collision_mask = 531 +mass = 0.1 +physics_material_override = SubResource("PhysicsMaterial_bu3x1") +linear_damp = 20.0 +angular_damp = 2.0 +script = SubResource("GDScript_8dti7") + +[node name="CollisionShape2D" type="CollisionShape2D" parent="OnTheGround/RepellableBall" unique_id=271750635] +rotation = -1.5707964 +shape = SubResource("CircleShape2D_bu3x1") +debug_color = Color(0.9570816, 1.0588765e-06, 0.5370912, 0.41960785) + +[node name="RemoteTransform2D" type="RemoteTransform2D" parent="OnTheGround/RepellableBall" unique_id=1152753866] +position = Vector2(-1.1920929e-07, -1.9073486e-06) +scale = Vector2(0.18770815, 0.18770815) +remote_path = NodePath("../../BallAnchor") +update_rotation = false +update_scale = false + +[node name="RemoteTransform2D2" type="RemoteTransform2D" parent="OnTheGround/RepellableBall" unique_id=650646433] +position = Vector2(-1.1920929e-07, -1.9073486e-06) +scale = Vector2(0.18770815, 0.18770815) +remote_path = NodePath("../../BallAnchor/BallSprite") +update_position = false +update_scale = false + +[node name="RepellableIceCube" type="RigidBody2D" parent="OnTheGround" unique_id=1744685581] +editor_description = "A repellable ice cube. + +The got_repelled() method constrains the direction to the 4 axis (top, down, left, right). + +Has no damping so it moves until it hits a wall or something." +position = Vector2(1204, 899) +collision_layer = 768 +collision_mask = 531 +lock_rotation = true +script = SubResource("GDScript_7kfal") + +[node name="Sprite2D" type="Sprite2D" parent="OnTheGround/RepellableIceCube" unique_id=276581240] +position = Vector2(2, -5) +texture = ExtResource("57_bu3x1") + +[node name="CollisionShape2D" type="CollisionShape2D" parent="OnTheGround/RepellableIceCube" unique_id=1875420561] +rotation = -1.5707964 +shape = SubResource("CapsuleShape2D_bu3x1") + +[node name="RepellableBox" parent="OnTheGround" unique_id=1805651676 node_paths=PackedStringArray("constrain_layer") instance=ExtResource("60_p2k53")] +position = Vector2(990, 1256) +constrain_layer = NodePath("../../TileMapLayers/Paths_Level1") + +[node name="RepellableBox2" parent="OnTheGround" unique_id=2072092672 node_paths=PackedStringArray("constrain_layer") instance=ExtResource("60_p2k53")] +position = Vector2(932, 1191) +constrain_layer = NodePath("../../TileMapLayers/Paths_Level1") + +[node name="RepellableLever" type="StaticBody2D" parent="OnTheGround" unique_id=1178804777] +position = Vector2(747, 914) +collision_layer = 768 +collision_mask = 0 +script = SubResource("GDScript_p2k53") + +[node name="LeverSprite" type="Sprite2D" parent="OnTheGround/RepellableLever" unique_id=118513672] +position = Vector2(0, -10) +texture = ExtResource("62_p2k53") +hframes = 2 + +[node name="CollisionShape2D" type="CollisionShape2D" parent="OnTheGround/RepellableLever" unique_id=287400683] +rotation = -1.5707964 +shape = SubResource("CapsuleShape2D_p2k53") + +[node name="Shaker" type="Node2D" parent="OnTheGround/RepellableLever" unique_id=2090116419 node_paths=PackedStringArray("target")] +script = ExtResource("63_a070f") +target = NodePath("..") +duration = 0.5 +frequency = 30.0 +metadata/_custom_type_script = "uid://dunsvrhq42214" + [node name="ScreenOverlay" type="CanvasLayer" parent="." unique_id=1144668032] [node name="HBoxContainer" type="HBoxContainer" parent="ScreenOverlay" unique_id=1583452192] @@ -1209,3 +1445,5 @@ position = Vector2(62, 1747) [connection signal="interaction_started" from="OnTheGround/NPCs/GardenerA/InteractArea" to="OnTheGround/NPCs/GardenerA" method="_on_interact_area_interaction_started"] [connection signal="interaction_ended" from="OnTheGround/Tutorial/TutorialGuy/InteractArea" to="OnTheGround/Tutorial/TutorialGuy" method="_on_interact_area_interaction_ended"] [connection signal="interaction_started" from="OnTheGround/Tutorial/TutorialGuy/InteractArea" to="OnTheGround/Tutorial/TutorialGuy" method="_on_interact_area_interaction_started"] +[connection signal="sleeping_state_changed" from="OnTheGround/RepellableRock" to="." method="_on_repellable_rock_sleeping_state_changed"] +[connection signal="sleeping_state_changed" from="OnTheGround/RepellableBall" to="OnTheGround/RepellableBall" method="_on_sleeping_state_changed"]