Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,9 @@ func _show_page(i: int) -> void:
left_body.text = pages[i]
right_body.text = pages_right[i] if i < pages_right.size() else ""
if i < pages.size() - 1:
hint.text = "▶ clic / Enter · Esc para saltar"
hint.text = "▶ click / Enter · Esc to skip"
else:
hint.text = "▶ clic / Enter para terminar"
hint.text = "▶ click / Enter to finish"


func _unhandled_input(event: InputEvent) -> void:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
[ext_resource type="Resource" uid="uid://bu8sab18188qj" path="res://scenes/quests/lore_quests/quest_004/01_dream_threshold/noria.dialogue" id="15_noria"]
[ext_resource type="Script" uid="uid://ba7f2rw4pca5k" path="res://scenes/quests/lore_quests/quest_004/menhir/ability_exit.gd" id="16_exit"]
[ext_resource type="Script" uid="uid://2u0dkcuoogj4" path="res://scenes/quests/lore_quests/quest_004/menhir/caracol_powerup.gd" id="18_carjuice"]
[ext_resource type="SpriteFrames" uid="uid://cfgahyntmtfgu" path="res://scenes/game_elements/props/powerup/components/powerup_thread.tres" id="19_xfrwg"]
[ext_resource type="Script" uid="uid://c8405c212rbn6" path="res://scenes/game_elements/props/background_music/components/background_music.gd" id="20_tkt3q"]
[ext_resource type="AudioStream" uid="uid://ofn2fhr2a2n8" path="res://assets/first_party/music/Threadbare Loop_Main_Intro_01.ogg" id="21_q8elv"]
[ext_resource type="PackedScene" uid="uid://tdvdp0jvbt2s" path="res://scenes/quests/lore_quests/quest_004/menhir/hum_hint.tscn" id="30_humhint"]
Expand Down Expand Up @@ -192,8 +193,6 @@ turbulence_noise_scale = 1.2
[sub_resource type="RectangleShape2D" id="RectangleShape2D_exit"]
size = Vector2(80, 220)

[sub_resource type="SpriteFrames" id="SpriteFrames_carac"]

[sub_resource type="RectangleShape2D" id="RectangleShape2D_wallH"]
size = Vector2(1140, 40)

Expand Down Expand Up @@ -364,7 +363,7 @@ editor_draw_limits = true
[node name="AbilityExit" type="Area2D" parent="." unique_id=777222333]
position = Vector2(1205, 385)
script = ExtResource("16_exit")
next_scene = "res://scenes/quests/lore_quests/quest_004/01-El_umbral_del_sueño/la_pradera_dorada.tscn"
next_scene = "res://scenes/quests/lore_quests/quest_004/01_dream_threshold/the_golden_meadow.tscn"

[node name="CollisionShape2D" type="CollisionShape2D" parent="AbilityExit" unique_id=777222334]
position = Vector2(-30, -29)
Expand All @@ -378,14 +377,15 @@ radius = 650.0
[node name="LearnHumPowerup" parent="." unique_id=297202740 instance=ExtResource("13_d8brw")]
position = Vector2(987, 222)
ability = 256
sprite_frames = SubResource("SpriteFrames_carac")
sprite_frames = ExtResource("19_xfrwg")
highlight_color = Color(0.55, 0.85, 0.95, 1)

[node name="ExtraAbility" type="Node" parent="LearnHumPowerup" unique_id=133561057]
script = ExtResource("13_n2343")

[node name="CaracolPowerup" type="Node" parent="LearnHumPowerup" unique_id=777444001]
script = ExtResource("18_carjuice")
icon_scale = 1.0

[node name="Walls" type="StaticBody2D" parent="." unique_id=777444002]
collision_layer = 16
Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,23 @@
# SPDX-FileCopyrightText: The Threadbare Authors
# SPDX-License-Identifier: MPL-2.0
extends CharacterBody2D
## Roca EMPUJABLE estilo Sokoban: se mueve CUBO POR CUBO (una casilla por empujón)
## cuando el jugador la empuja con el cuerpo. Mientras se desliza "tiembla" (shader
## menhir_tremble) dando sensación de movimiento.
##
## Está en el grupo "pushable_box" para que la [PressurePlate] la detecte encima.
## No toca al jugador ni a ningún script base: solo lee la intención de movimiento
## (las teclas) y comprueba con [method PhysicsBody2D.test_move] si la casilla
## destino está libre.

## Tamaño de la casilla en px (cuánto avanza la roca por paso).

@export var cell_size: float = 64.0
## Duración del deslizamiento de un paso, en segundos.

@export var step_time: float = 0.12
## Cuánto tiembla mientras se mueve.

@export var tremble_amplitude: float = 1.0

var _moving: bool = false

@onready var sprite: Sprite2D = $Sprite2D
@onready var push_sensor: Area2D = $PushSensor
@onready var hookable_area: HookableArea = $HookableArea


func _ready() -> void:
add_to_group(&"pushable_box")
# Material propio (si no, varias rocas temblarían a la vez).

if sprite.material:
sprite.material = sprite.material.duplicate()
_set_tremble(0.0)
Expand All @@ -39,31 +31,51 @@ func _physics_process(_delta: float) -> void:
if player == null or not push_sensor.overlaps_body(player):
return

# Intención de movimiento (las teclas), no la velocidad ya frenada por chocar.
var input := Input.get_vector(&"move_left", &"move_right", &"move_up", &"move_down")
if input.length() < 0.5:
return

var dir := _cardinal(input)
# Solo empuja si el jugador está del lado opuesto (presiona HACIA la roca).

if (global_position - player.global_position).dot(dir) <= 0.0:
return

var step := dir * cell_size
# No avanzar si la casilla destino está bloqueada (muro / portón cerrado).

if test_move(global_transform, step):
return

_step_to(global_position + step)


## Reduce el vector de entrada a una sola dirección cardinal (la dominante).
func _cardinal(v: Vector2) -> Vector2:
if absf(v.x) >= absf(v.y):
return Vector2(signf(v.x), 0.0)
return Vector2(0.0, signf(v.y))


func _try_step(direction: Vector2) -> bool:
if _moving:
return false
var step := _cardinal(direction) * cell_size
if test_move(global_transform, step):
return false
_step_to(global_position + step)
return true


func got_repelled(direction: Vector2) -> void:
_try_step(direction)


func got_pulled(direction: Vector2) -> void:
if _try_step(direction):
await get_tree().create_timer(step_time).timeout
hookable_area.release_from_pull()
else:
hookable_area.release_from_pull(true)


func _step_to(target: Vector2) -> void:
_moving = true
_set_tremble(tremble_amplitude)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
[ext_resource type="Script" uid="uid://bhvlqxguk8mt8" path="res://scenes/quests/lore_quests/quest_004/02_caretaker_of_growth/box_pushable.gd" id="1_b"]
[ext_resource type="Texture2D" uid="uid://bhqanyxhpxpm" path="res://assets/first_party/rocks/Rock_Idle.png" id="2_t"]
[ext_resource type="Shader" uid="uid://cptuiiq2m0fxr" path="res://scenes/quests/lore_quests/quest_004/menhir/menhir_tremble.gdshader" id="3_sh"]
[ext_resource type="Script" uid="uid://dabvr3pqmyya4" path="res://scenes/game_elements/props/hookable_area/components/hookable_area.gd" id="4_hook"]

[sub_resource type="ShaderMaterial" id="ShaderMaterial_b"]
shader = ExtResource("3_sh")
Expand All @@ -15,9 +16,12 @@ size = Vector2(44, 30)
[sub_resource type="RectangleShape2D" id="RectangleShape2D_sensor"]
size = Vector2(64, 50)

[sub_resource type="RectangleShape2D" id="RectangleShape2D_hook"]
size = Vector2(56, 44)

[node name="Box" type="CharacterBody2D" unique_id=1697541390]
collision_layer = 512
collision_mask = 16
collision_mask = 528
script = ExtResource("1_b")

[node name="Sprite2D" type="Sprite2D" parent="." unique_id=437919106]
Expand All @@ -31,8 +35,20 @@ shape = SubResource("RectangleShape2D_body")

[node name="PushSensor" type="Area2D" parent="." unique_id=1083185627]
collision_layer = 0
collision_mask = 1

[node name="CollisionShape2D" type="CollisionShape2D" parent="PushSensor" unique_id=1670780157]
position = Vector2(0, -6)
shape = SubResource("RectangleShape2D_sensor")

[node name="HookableArea" type="Area2D" parent="." unique_id=1670780158 node_paths=PackedStringArray("controlled_entity", "anchor_point")]
script = ExtResource("4_hook")
controlled_entity = NodePath("..")
anchor_point = NodePath("Marker2D")
weight = 0.0

[node name="CollisionShape2D" type="CollisionShape2D" parent="HookableArea" unique_id=1670780159]
position = Vector2(0, -6)
shape = SubResource("RectangleShape2D_hook")

[node name="Marker2D" type="Marker2D" parent="HookableArea" unique_id=1670780160]
position = Vector2(0, -6)
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
# SPDX-FileCopyrightText: The Threadbare Authors
# SPDX-License-Identifier: MPL-2.0
extends Node2D
## Mariposa ENEMIGA que vuela LENTO hacia el jugador y lo derrota al tocarlo.
## Vuela libre (ignora paredes y el vacío), así que persigue por toda la sala.
## No toca al jugador base: solo lee su posición y, al contacto, llama defeat().
extends CharacterBody2D

## Velocidad de vuelo (px/s). Baja = el jugador puede correr más rápido.
@export var speed: float = 48.0
## Si está activo, al tocar al jugador lo derrota.
@export var defeat_on_touch: bool = true
@export var patrol_path: PathFollow2D
@export var chase_radius: float = 400.0
@export var shake_radius: float = 140.0
@export var shake_intensity: float = 6.0
@export var shake_interval: float = 0.8

var _shake_timer: Timer

@onready var sprite: AnimatedSprite2D = $AnimatedSprite2D
@onready var touch_area: Area2D = $TouchArea
Expand All @@ -19,15 +21,63 @@ func _ready() -> void:
sprite.play()
touch_area.body_entered.connect(_on_body_entered)

_shake_timer = Timer.new()
_shake_timer.wait_time = shake_interval
add_child(_shake_timer)
_shake_timer.timeout.connect(_do_shake)


func _physics_process(delta: float) -> void:
var player := get_tree().get_first_node_in_group(&"player") as Node2D
var chasing := (
player != null
and global_position.distance_to(player.global_position) <= chase_radius
and _has_line_of_sight(player.global_position)
)

var target := Vector2.ZERO
if chasing:
target = player.global_position
elif patrol_path:
patrol_path.progress += speed * delta
target = patrol_path.global_position

if target != Vector2.ZERO:
var to_target := target - global_position
velocity = to_target.normalized() * speed if to_target.length() > 2.0 else Vector2.ZERO
if absf(velocity.x) > 0.1:
sprite.flip_h = velocity.x < 0.0
move_and_slide()

_update_shake(player)


func _has_line_of_sight(to: Vector2) -> bool:
var space_state := get_world_2d().direct_space_state
var query := PhysicsRayQueryParameters2D.create(global_position, to, collision_mask)
query.exclude = [self]
return space_state.intersect_ray(query).is_empty()


func _update_shake(player: Node2D) -> void:
if player == null:
return
var to_player := player.global_position - global_position
if to_player.length() > 2.0:
global_position += to_player.normalized() * speed * delta
sprite.flip_h = to_player.x < 0.0
var near := global_position.distance_to(player.global_position) <= shake_radius
if near and _shake_timer.is_stopped():
_do_shake()
_shake_timer.start()
elif not near and not _shake_timer.is_stopped():
_shake_timer.stop()


func _do_shake() -> void:
if CameraShake.shaker == null:
return
var cam := get_viewport().get_camera_2d()
if cam == null:
return
CameraShake.shaker.target = cam
CameraShake.shaker.shake(shake_intensity, shake_interval * 1.5)


func _on_body_entered(body: Node2D) -> void:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,19 @@
[sub_resource type="CircleShape2D" id="CircleShape2D_b"]
radius = 14.0

[node name="ButterflyChaser" type="Node2D" unique_id=578265269]
[sub_resource type="CircleShape2D" id="CircleShape2D_body"]
radius = 14.0

[node name="ButterflyChaser" type="CharacterBody2D" unique_id=578265269]
collision_mask = 16
script = ExtResource("1_b")

[node name="AnimatedSprite2D" type="AnimatedSprite2D" parent="." unique_id=1502087872]
sprite_frames = ExtResource("2_sf")

[node name="CollisionShape2D" type="CollisionShape2D" parent="." unique_id=435099013]
shape = SubResource("CircleShape2D_body")

[node name="TouchArea" type="Area2D" parent="." unique_id=1895063277]
collision_layer = 0
collision_mask = 1
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
# SPDX-FileCopyrightText: The Threadbare Authors
# SPDX-License-Identifier: MPL-2.0
extends Node
## Abre un objetivo (Door / cualquier Toggleable) SOLO cuando TODAS las palancas
## de la lista están encendidas. Para el puzzle "activa las 3 palancas para abrir
## el paso". Cuélgalo en la escena, asígnale las palancas y la puerta.
## Opens a target (Door / Toggleable) ONLY when ALL assigned levers are active.
## Used for "turn on all levers to open the gate" puzzles.

## Las palancas (nodos lever.tscn) que hay que encender todas.
## Array of levers (lever.tscn) that must be turned on.
@export var levers: Array[Node]
## El objetivo a abrir (un Door u otro Toggleable con set_toggled).
## Target node to trigger (a Door or Toggleable with set_toggled).
@export var target: Node


Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
# SPDX-FileCopyrightText: The Threadbare Authors
# SPDX-License-Identifier: MPL-2.0
extends Node
## Abre uno o varios objetivos SOLO cuando TODAS las placas tienen una roca encima.
## Para el puzzle "pon las 3 rocas en las 3 placas para abrir la puerta".
## Cuélgalo en la escena y asígnale las placas y los objetivos.
## Triggers one or more targets ONLY when ALL assigned pressure plates are pressed.
## Used for "place all rocks on all plates to open the path" puzzles.

## Las placas ([PressurePlate]) que hay que pisar todas con una roca.
## Array of pressure plates ([PressurePlate]) required to trigger the targets.
@export var plates: Array[Node]
## Objetivos a abrir cuando estén las 3: si tienen open()/close() se usan (Door, con
## su sonido); si no, set_toggled(bool); si son StaticBody2D se ocultan (portón/escalera).
## Target nodes to activate. Uses open()/close() if available (Doors with sound),
## falls back to set_toggled(bool), or toggles collision/visibility for StaticBody2D.
@export var targets: Array[Node]

var _is_open: bool = false
Expand All @@ -18,7 +17,7 @@ func _ready() -> void:
for plate in plates:
if plate and plate.has_signal(&"pressed"):
plate.pressed.connect(_on_plate_pressed)
# Estado inicial sin sonido.
# Initial state without playing sound.
_is_open = _all_on()
for target in targets:
_apply_initial(target, _is_open)
Expand Down
Loading