diff --git a/audio/ElevatorClose1.wav b/audio/ElevatorClose1.wav new file mode 100644 index 0000000..55b0574 Binary files /dev/null and b/audio/ElevatorClose1.wav differ diff --git a/audio/ElevatorClose1.wav.import b/audio/ElevatorClose1.wav.import new file mode 100644 index 0000000..745a8ed --- /dev/null +++ b/audio/ElevatorClose1.wav.import @@ -0,0 +1,24 @@ +[remap] + +importer="wav" +type="AudioStreamWAV" +uid="uid://dgqb1rovgwuan" +path="res://.godot/imported/ElevatorClose1.wav-62a28fe304e25bbeaadaf96c7277c8f4.sample" + +[deps] + +source_file="res://audio/ElevatorClose1.wav" +dest_files=["res://.godot/imported/ElevatorClose1.wav-62a28fe304e25bbeaadaf96c7277c8f4.sample"] + +[params] + +force/8_bit=false +force/mono=false +force/max_rate=false +force/max_rate_hz=44100 +edit/trim=false +edit/normalize=false +edit/loop_mode=0 +edit/loop_begin=0 +edit/loop_end=-1 +compress/mode=2 diff --git a/audio/ElevatorDing1.wav b/audio/ElevatorDing1.wav new file mode 100644 index 0000000..1315ab5 Binary files /dev/null and b/audio/ElevatorDing1.wav differ diff --git a/audio/ElevatorDing1.wav.import b/audio/ElevatorDing1.wav.import new file mode 100644 index 0000000..aa469fb --- /dev/null +++ b/audio/ElevatorDing1.wav.import @@ -0,0 +1,24 @@ +[remap] + +importer="wav" +type="AudioStreamWAV" +uid="uid://mqoxmuiw0obf" +path="res://.godot/imported/ElevatorDing1.wav-ffc48b0f83fdbf8986f81b07b7f55b94.sample" + +[deps] + +source_file="res://audio/ElevatorDing1.wav" +dest_files=["res://.godot/imported/ElevatorDing1.wav-ffc48b0f83fdbf8986f81b07b7f55b94.sample"] + +[params] + +force/8_bit=false +force/mono=false +force/max_rate=false +force/max_rate_hz=44100 +edit/trim=false +edit/normalize=false +edit/loop_mode=0 +edit/loop_begin=0 +edit/loop_end=-1 +compress/mode=2 diff --git a/audio/ElevatorOpen1.wav b/audio/ElevatorOpen1.wav new file mode 100644 index 0000000..fbb8a81 Binary files /dev/null and b/audio/ElevatorOpen1.wav differ diff --git a/audio/ElevatorOpen1.wav.import b/audio/ElevatorOpen1.wav.import new file mode 100644 index 0000000..6a4643c --- /dev/null +++ b/audio/ElevatorOpen1.wav.import @@ -0,0 +1,24 @@ +[remap] + +importer="wav" +type="AudioStreamWAV" +uid="uid://bavo8f76jr7i6" +path="res://.godot/imported/ElevatorOpen1.wav-aad7a3c9d4a0cc4e4383f9c0ab196d12.sample" + +[deps] + +source_file="res://audio/ElevatorOpen1.wav" +dest_files=["res://.godot/imported/ElevatorOpen1.wav-aad7a3c9d4a0cc4e4383f9c0ab196d12.sample"] + +[params] + +force/8_bit=false +force/mono=false +force/max_rate=false +force/max_rate_hz=44100 +edit/trim=false +edit/normalize=false +edit/loop_mode=0 +edit/loop_begin=0 +edit/loop_end=-1 +compress/mode=2 diff --git a/scenes/component_spawn.gd b/scenes/component_spawn.gd index b887d7d..cf8723c 100644 --- a/scenes/component_spawn.gd +++ b/scenes/component_spawn.gd @@ -12,11 +12,16 @@ var spawn_readiness: bool = false @onready var start_pos = survivor_spawn.global_position func _ready() -> void: - start() + spawn_readiness = false + +func _physics_process(delta): + if spawn_readiness == true: + spawn_survivor() + spawn_readiness = false + else: func start() -> void: spawn_readiness = true - spawn_survivor() #func _physics_process(delta): #if spawn_readiness == true: @@ -32,7 +37,7 @@ func spawn_survivor(): else: var survivor_delay: float = randf_range(1, 2.5) - var start_variance = Vector3((randf_range(-1, 2)),0,0) + for x in range(survivors): var s = survivor.instantiate() diff --git a/scenes/elevator_panel.gd b/scenes/elevator_panel.gd index 80944a9..aaf694c 100644 --- a/scenes/elevator_panel.gd +++ b/scenes/elevator_panel.gd @@ -1,91 +1,171 @@ -extends Control -# now we're cookin with bacon! +extends PanelContainer +# Floor state var current_floor := 10 var saved_count := 0 -var doors_closing := false +var doors_closing_flag := false -# threshold increases as you descend! -var base_threshold := 2 -var threshold_increase_interval := 2 # +1 required every x floors +# Survivors per floor: more at top, fewer as you descend +var base_survivors := 2 +var survivors_per_floor_increase := 1 # per floor above ground +var survivors_remaining := 0 -# scoring +# Flat threshold: must save at least this many per floor +var threshold := 2 + +# Scoring var score := 0 -var points_per_person := 100 +var points_per_person := 100 # base + bonus per person above threshold -# placeholder until henry's system provides real count -var people_in_elevator := 3 +var people_in_elevator := 0 -# placeholder until hnry's collision signal is ready +# PLACEHOLDER: replaced by Henry's robot collision signal. +# Handler is _on_timer_expired(). var floor_time_limit := 10.0 -var floor_timer: Timer -func get_threshold() -> int: - var floors_cleared := 10 - current_floor - return base_threshold + floors_cleared / threshold_increase_interval +# PLACEHOLDER: remove when Henry's spawning system is in. +@onready var spawner = get_node("../../Node3D") + +signal floor_changed(floor_num: int) +signal saved_changed(count: int) +signal score_changed(new_score: int) +signal people_changed(count: int, threshold_val: int) +signal game_won +signal game_lost func _ready(): - $PanelMargin/PanelColumn/CloseButton.pressed.connect(_on_close_pressed) + var button = $PanelMargin/PanelColumn/CloseButton + button.text = "BLOCK" + button.pressed.connect(_on_block_pressed) - floor_timer = Timer.new() - floor_timer.one_shot = true - floor_timer.wait_time = floor_time_limit - floor_timer.timeout.connect(_on_timer_expired) - add_child(floor_timer) - floor_timer.start() + var screen = $PanelMargin/PanelColumn/Screen + screen.pulse_blocked.connect(_on_pulse_blocked) + screen.doors_closing.connect(_on_doors_closing) - EventBus.floor_changed.emit(current_floor) - EventBus.people_changed.emit(people_in_elevator, get_threshold()) + _start_floor() func _unhandled_input(event): - if event is InputEventKey and event.pressed and event.keycode == KEY_SPACE: - _on_close_pressed() + if event is InputEventKey and event.pressed and not event.echo: + if event.keycode == KEY_SPACE: + _on_block_pressed() + +# --- Floor lifecycle --- + +func _start_floor(): + doors_closing_flag = false + people_in_elevator = 0 + + # More survivors on higher floors, fewer near the ground + var floors_remaining = current_floor - 1 + survivors_remaining = base_survivors + (floors_remaining * survivors_per_floor_increase) + + people_changed.emit(people_in_elevator, threshold) + floor_changed.emit(current_floor) + + # Spawn 3D survivors (placeholder) + print("spawner: ", spawner, " survivors: ", survivors_remaining) + if spawner: + spawner.start_spawning(survivors_remaining) + + var screen = $PanelMargin/PanelColumn/Screen + screen.start() + screen.launch_pulse() # first survivor arrives + + # Floor timer (placeholder for robot collision) + if has_node("FloorTimer"): + $FloorTimer.queue_free() + var timer = Timer.new() + timer.name = "FloorTimer" + timer.wait_time = floor_time_limit + timer.one_shot = true + timer.timeout.connect(_on_timer_expired) + add_child(timer) + timer.start() + +# --- Input --- + +func _on_block_pressed(): + if doors_closing_flag: + return + $PanelMargin/PanelColumn/Screen.attempt_block() + +# --- Pulse callbacks --- + +func _on_pulse_blocked(): + # This survivor made it in + survivors_remaining -= 1 + people_in_elevator += 1 + people_changed.emit(people_in_elevator, threshold) + + if survivors_remaining <= 0: + # Everyone's in, close doors (auto-success) + _on_doors_closing() + return + + # More survivors approaching: launch next pulse after gap + var screen = $PanelMargin/PanelColumn/Screen + var gap = screen.get_pulse_gap() + get_tree().create_timer(gap).timeout.connect( + func(): + if not doors_closing_flag: + screen.launch_pulse(), + CONNECT_ONE_SHOT + ) + +func _on_doors_closing(): + if doors_closing_flag: + return + doors_closing_flag = true + + # Stop spawning (placeholder) + if spawner: + spawner.stop_spawning() + + if has_node("FloorTimer"): + $FloorTimer.stop() + + var screen = $PanelMargin/PanelColumn/Screen + + # Lose: not enough people + if people_in_elevator < threshold: + screen.show_loss("TOO FEW") + game_lost.emit() + return + + # Score this floor + var base_points = people_in_elevator * points_per_person + var bonus = max(0, people_in_elevator - threshold) * points_per_person + score += base_points + bonus + saved_count += people_in_elevator + + score_changed.emit(score) + saved_changed.emit(saved_count) + + current_floor -= 1 + + # Win: reached ground floor + if current_floor <= 1: + screen.show_win() + floor_changed.emit(current_floor) + game_won.emit() + return + + # Next floor after countdown + screen.show_countdown() + screen.shrink_block_zone() + + var tween = create_tween() + tween.tween_interval(2.0) + tween.tween_callback(_start_floor) + +# --- Timer (placeholder for robot collision) --- func _on_timer_expired(): - if doors_closing: + if doors_closing_flag: return - doors_closing = true - var screen = $PanelMargin/PanelColumn/Screen - screen.show_loss("TOO LATE :[") - EventBus.game_lost.emit() - -func _on_close_pressed(): - if doors_closing: - return - - var screen = $PanelMargin/PanelColumn/Screen - var success = screen.attempt_hack() - if success: - doors_closing = true - floor_timer.stop() - - if people_in_elevator < get_threshold(): - screen.show_loss("TOO FEW :[[") - EventBus.game_lost.emit() - return - - # base points per person, bonus for each person above threshold?? - var bonus: int = max(0, people_in_elevator - get_threshold()) * points_per_person - score += people_in_elevator * points_per_person + bonus - EventBus.score_changed.emit(score) - - saved_count += people_in_elevator - current_floor -= 1 - EventBus.saved_changed.emit(saved_count) - EventBus.floor_changed.emit(current_floor) - - if current_floor <= 1: - screen.show_win() - EventBus.game_won.emit() - else: - screen.show_countdown() - var tween = create_tween() - tween.tween_callback(func(): - screen.reset_hack() - doors_closing = false - people_in_elevator = 3 # placeholder - EventBus.people_changed.emit(people_in_elevator, get_threshold()) - floor_timer.start() - ).set_delay(2.0) - else: - print("MISSED! AI is faster now!") + doors_closing_flag = true + # Stop spawning (placeholder) + if spawner: + spawner.stop_spawning() + $PanelMargin/PanelColumn/Screen.show_loss("TOO LATE") + game_lost.emit() diff --git a/scenes/game.tscn b/scenes/game.tscn index acb45e6..55ad02d 100644 --- a/scenes/game.tscn +++ b/scenes/game.tscn @@ -1,10 +1,13 @@ [gd_scene format=3 uid="uid://b4llhxe3hjbgv"] -[ext_resource type="Script" uid="uid://bo7jao24qe2o7" path="res://scenes/game.gd" id="1_lbhrr"] +[ext_resource type="Script" path="res://scenes/game.gd" id="1_lbhrr"] [ext_resource type="PackedScene" uid="uid://bnv1xxgceqbrc" path="res://scenes/world.tscn" id="1_uwrxv"] [ext_resource type="PackedScene" uid="uid://cbvi51vvpt7mu" path="res://scenes/hud.tscn" id="2_yqjtg"] [ext_resource type="PackedScene" uid="uid://nca1hcujxru0" path="res://scenes/elevator_panel.tscn" id="3_lnu2h"] -[ext_resource type="PackedScene" uid="uid://bb0o3ov6u308v" path="res://scenes/component_spawn.tscn" id="5_iywne"] +[ext_resource type="AudioStream" uid="uid://mqoxmuiw0obf" path="res://audio/ElevatorDing1.wav" id="4_p57ef"] +[ext_resource type="AudioStream" uid="uid://bavo8f76jr7i6" path="res://audio/ElevatorOpen1.wav" id="5_u5sy4"] +[ext_resource type="AudioStream" uid="uid://dgqb1rovgwuan" path="res://audio/ElevatorClose1.wav" id="6_gee14"] +[ext_resource type="PackedScene" path="res://scenes/component_spawn.tscn" id="9_0tnpc"] [node name="Game" type="Node3D" unique_id=1456297160] script = ExtResource("1_lbhrr") @@ -19,4 +22,13 @@ script = ExtResource("1_lbhrr") [node name="ElevatorPanel" parent="CanvasPanel" unique_id=574176994 instance=ExtResource("3_lnu2h")] -[node name="ComponentSpawn" parent="." unique_id=649225939 instance=ExtResource("5_iywne")] +[node name="SfxDing" type="AudioStreamPlayer3D" parent="CanvasPanel/ElevatorPanel" unique_id=529226129] +stream = ExtResource("4_p57ef") + +[node name="SfxOpen" type="AudioStreamPlayer3D" parent="CanvasPanel/ElevatorPanel" unique_id=3954090] +stream = ExtResource("5_u5sy4") + +[node name="SfxClose" type="AudioStreamPlayer3D" parent="CanvasPanel/ElevatorPanel" unique_id=380307389] +stream = ExtResource("6_gee14") + +[node name="ComponentSpawn" parent="." unique_id=649225939 instance=ExtResource("9_0tnpc")] diff --git a/scenes/node_3d.gd b/scenes/node_3d.gd new file mode 100644 index 0000000..839b941 --- /dev/null +++ b/scenes/node_3d.gd @@ -0,0 +1,43 @@ +extends Node3D + +@export var spawn_position := Vector3(0, 0, -20) +@export var spawn_spread := 2.0 +@export var spawn_interval := 1.5 + +var survivor_scene := preload("res://scenes/survivor.tscn") +var survivors_to_spawn := 0 +var spawn_timer: Timer + +func start_spawning(count: int): + survivors_to_spawn = count + if not spawn_timer: + spawn_timer = Timer.new() + spawn_timer.one_shot = false + spawn_timer.wait_time = spawn_interval + spawn_timer.timeout.connect(_spawn_one) + add_child(spawn_timer) + spawn_timer.start() + _spawn_one() # first one immediately + +func stop_spawning(): + if spawn_timer: + spawn_timer.stop() + survivors_to_spawn = 0 + +func _spawn_one(): + if survivors_to_spawn <= 0: + if spawn_timer: + spawn_timer.stop() + return + + var survivor = survivor_scene.instantiate() + var offset_x = randf_range(-spawn_spread, spawn_spread) + var xform = Transform3D() + xform.origin = spawn_position + Vector3(offset_x, 0, 0) + survivors_to_spawn -= 1 + _deferred_add.call_deferred(survivor, xform) + +func _deferred_add(survivor, xform): + get_tree().current_scene.add_child(survivor) + survivor.owner = get_tree().current_scene + survivor.start(xform) diff --git a/scenes/node_3d.gd.uid b/scenes/node_3d.gd.uid new file mode 100644 index 0000000..61526f8 --- /dev/null +++ b/scenes/node_3d.gd.uid @@ -0,0 +1 @@ +uid://co8y4knanokeg diff --git a/scenes/screen.gd b/scenes/screen.gd index 573d22b..44baa32 100644 --- a/scenes/screen.gd +++ b/scenes/screen.gd @@ -1,106 +1,165 @@ extends Panel -# my beautiful screen -var sweep_speed := 150.0 -var sweep_direction := 1 -var sweep_x := 0.0 -var is_hacked := true -var style_box: StyleBoxFlat +# Pulse movement +var pulse_speed := 120.0 +var pulse_speed_increase := 15.0 +var pulse_x := 0.0 +var pulse_active := false +var pulse_gap := 1.0 +var pulse_gap_decrease := 0.05 +var pulse_gap_min := 0.3 + +# Block zone (shrinks per floor, not per block) +var block_zone_shrink := 3.0 +var block_zone_min := 12.0 + +# Visual var base_color: Color -var target_shrink := 4.0 -var target_min_width := 8.0 +# State +var active := false +var pulses_blocked := 0 -signal hack_defeated -signal hack_failed +signal pulse_blocked +signal doors_closing func _ready(): - style_box = get_theme_stylebox("panel").duplicate() - add_theme_stylebox_override("panel", style_box) - base_color = style_box.bg_color - - var tz = $TargetZone - tz.position = Vector2((size.x - tz.size.x) / 2, 20) - tz.size = Vector2(40, size.y - 40) + # Block zone on right side of screen + var bz = $TargetZone + bz.size = Vector2(50, size.y - 40) + bz.position = Vector2(size.x - bz.size.x - 10, 20) + # Pulse line (hidden until first pulse) var sl = $SweepLine - sl.position = Vector2(0, 20) sl.size = Vector2(4, size.y - 40) - sweep_x = 0.0 + sl.position = Vector2(0, 20) + sl.visible = false + # AI face var face = $AIFace face.position = Vector2(0, 0) face.size = Vector2(size.x, 30) face.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + face.text = ">:)" -func _process(delta): - if not is_hacked: + # Duplicate the StyleBoxFlat so flash tweens don't bleed into other panels + var style = get_theme_stylebox("panel") as StyleBoxFlat + if style: + style = style.duplicate() + add_theme_stylebox_override("panel", style) + base_color = style.bg_color + +# --- Per-floor lifecycle --- + +func start(): + active = true + pulses_blocked = 0 + pulse_speed = 120.0 + pulse_gap = 1.0 + $AIFace.text = ">:)" + $TargetZone.visible = true + +func stop(): + active = false + pulse_active = false + $SweepLine.visible = false + +func shrink_block_zone(): + var bz = $TargetZone + var new_width = max(bz.size.x - block_zone_shrink, block_zone_min) + bz.size.x = new_width + bz.position.x = size.x - new_width - 10 + +# --- Pulse logic --- + +# Called by elevator_panel when a survivor reaches the door. +func launch_pulse(): + if not active: return - - sweep_x += sweep_speed * sweep_direction * delta - - if sweep_x >= size.x: - sweep_direction = -1 - elif sweep_x <= 0: - sweep_direction = 1 - - $SweepLine.position.x = sweep_x - -func flash(color: Color): - style_box.bg_color = color - var tween = create_tween() - tween.tween_property(style_box, "bg_color", base_color, 0.3) - -func reset_hack(): - is_hacked = true - sweep_x = 0.0 - sweep_direction = 1 + pulse_x = 0.0 + pulse_active = true $SweepLine.visible = true $SweepLine.position.x = 0 $AIFace.text = ">:)" - var tz = $TargetZone - tz.size.x = max(tz.size.x - target_shrink, target_min_width) - tz.position.x = (size.x - tz.size.x) / 2 +func get_pulse_gap() -> float: + return pulse_gap -# messtastic -func show_countdown(): +func _process(delta): + if not active or not pulse_active: + return + + pulse_x += pulse_speed * delta + $SweepLine.position.x = pulse_x + + # Pulse reached the far side unblocked: AI wins, doors close + if pulse_x >= size.x: + pulse_active = false + $SweepLine.visible = false + $AIFace.text = ":D" + flash(Color.RED) + active = false + doors_closing.emit() + +# Called by elevator_panel when player presses BLOCK. +func attempt_block() -> bool: + if not pulse_active: + return false # no pulse on screen, button does nothing + + var bz = $TargetZone + var bz_left = bz.position.x + var bz_right = bz.position.x + bz.size.x + + if pulse_x >= bz_left and pulse_x <= bz_right: + # Blocked: doors stay open, this survivor gets in + pulse_active = false + pulses_blocked += 1 + pulse_speed += pulse_speed_increase + pulse_gap = max(pulse_gap - pulse_gap_decrease, pulse_gap_min) + $SweepLine.visible = false + $AIFace.text = ">:(" + flash(Color.GREEN) + pulse_blocked.emit() + return true + else: + # Mistimed: same result as letting it through + pulse_active = false + $SweepLine.visible = false + $AIFace.text = ":D" + flash(Color.RED) + active = false + doors_closing.emit() + return false + +# --- Display helpers --- + +func flash(color: Color): + var style = get_theme_stylebox("panel") as StyleBoxFlat + if not style: + return + style.bg_color = color var tween = create_tween() + tween.tween_property(style, "bg_color", base_color, 0.3) + +func show_countdown(): + stop() $AIFace.text = "3" - tween.tween_callback(func(): $AIFace.text = "2").set_delay(0.6) - tween.tween_callback(func(): $AIFace.text = "1").set_delay(0.6) - tween.tween_callback(func(): $AIFace.text = "CLOSED").set_delay(0.6) + var tween = create_tween() + tween.tween_interval(0.6) + tween.tween_callback(func(): $AIFace.text = "2") + tween.tween_interval(0.6) + tween.tween_callback(func(): $AIFace.text = "1") + tween.tween_interval(0.6) + tween.tween_callback(func(): $AIFace.text = "CLOSED") func show_win(): - is_hacked = false - $SweepLine.visible = false + stop() $TargetZone.visible = false $AIFace.text = "ESCAPED" flash(Color.GREEN) -func show_loss(message := "TOO FEW"): - is_hacked = false - $SweepLine.visible = false +func show_loss(message: String): + stop() $TargetZone.visible = false $AIFace.text = message flash(Color.RED) - -# target zones -func attempt_hack() -> bool: - var tz = $TargetZone - var tz_left = tz.position.x - var tz_right = tz.position.x + tz.size.x - - if sweep_x >= tz_left and sweep_x <= tz_right: - is_hacked = false - $AIFace.text = ">:(" - $SweepLine.visible = false - flash(Color.GREEN) - hack_defeated.emit() - return true - else: - sweep_speed += 20.0 - $AIFace.text = ":D" - flash(Color.RED) - hack_failed.emit() - return false