diff --git a/.gitignore b/.gitignore index c3092eb..2e25732 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ .godot/ /android/ /.DS_Store +/*.md +/jennie-notes/ 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/hud.tscn b/hud.tscn new file mode 100644 index 0000000..414f43e --- /dev/null +++ b/hud.tscn @@ -0,0 +1,97 @@ +[gd_scene format=3 uid="uid://dt0vbt6xgms6m"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_n2snw"] +bg_color = Color(0.039215688, 0.16470589, 0.039215688, 1) +corner_radius_top_left = 4 +corner_radius_top_right = 4 +corner_radius_bottom_right = 4 +corner_radius_bottom_left = 4 + +[node name="HUD" type="Control" unique_id=1519812318] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="StatsMargin" type="MarginContainer" parent="." unique_id=428486326] +layout_mode = 0 +offset_right = 40.0 +offset_bottom = 40.0 +theme_override_constants/margin_left = 20 +theme_override_constants/margin_top = 20 + +[node name="StatsColumn" type="VBoxContainer" parent="StatsMargin" unique_id=365054136] +layout_mode = 2 + +[node name="PeopleLabel" type="Label" parent="StatsMargin/StatsColumn" unique_id=203661849] +layout_mode = 2 +theme_override_font_sizes/font_size = 24 +text = "PEOPLE: 0" + +[node name="SavedLabel" type="Label" parent="StatsMargin/StatsColumn" unique_id=830917509] +layout_mode = 2 +theme_override_font_sizes/font_size = 24 +text = "SAVED: 0" + +[node name="FloorLabel" type="Label" parent="StatsMargin/StatsColumn" unique_id=320822205] +layout_mode = 2 +theme_override_font_sizes/font_size = 24 +text = "FLOOR: 0" + +[node name="ElevatorPanel" type="PanelContainer" parent="." unique_id=528786554] +custom_minimum_size = Vector2(250, 0) +layout_mode = 1 +anchors_preset = 11 +anchor_left = 1.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = -8.0 +grow_horizontal = 0 +grow_vertical = 2 + +[node name="PanelMargin" type="MarginContainer" parent="ElevatorPanel" unique_id=277992523] +layout_mode = 2 +theme_override_constants/margin_left = 15 +theme_override_constants/margin_top = 15 +theme_override_constants/margin_right = 15 +theme_override_constants/margin_bottom = 15 + +[node name="PanelColumn" type="VBoxContainer" parent="ElevatorPanel/PanelMargin" unique_id=617812367] +layout_mode = 2 + +[node name="Screen" type="Panel" parent="ElevatorPanel/PanelMargin/PanelColumn" unique_id=1525784323] +custom_minimum_size = Vector2(200, 200) +layout_mode = 2 +theme_override_styles/panel = SubResource("StyleBoxFlat_n2snw") + +[node name="TargetZone" type="ColorRect" parent="ElevatorPanel/PanelMargin/PanelColumn/Screen" unique_id=1945539908] +custom_minimum_size = Vector2(40, 160) +layout_mode = 0 +offset_left = 88.0 +offset_top = 18.0 +offset_right = 128.0 +offset_bottom = 178.0 +color = Color(0.101960786, 0.3529412, 0.101960786, 1) + +[node name="SweepLine" type="ColorRect" parent="ElevatorPanel/PanelMargin/PanelColumn/Screen" unique_id=1798423613] +custom_minimum_size = Vector2(4, 160) +layout_mode = 0 +offset_right = 40.0 +offset_bottom = 40.0 +color = Color(1, 0, 0, 1) + +[node name="AIFace" type="Label" parent="ElevatorPanel/PanelMargin/PanelColumn/Screen" unique_id=1410806030] +layout_mode = 0 +offset_right = 40.0 +offset_bottom = 23.0 +theme_override_colors/font_color = Color(0.2, 1, 0.2, 1) +theme_override_font_sizes/font_size = 32 +text = ">:)" + +[node name="CloseButton" type="Button" parent="ElevatorPanel/PanelMargin/PanelColumn" unique_id=427545014] +custom_minimum_size = Vector2(0, 60) +layout_mode = 2 +theme_override_font_sizes/font_size = 28 +text = "CLOSE" diff --git a/scenes/elevator_panel.gd b/scenes/elevator_panel.gd index 80944a9..f22187a 100644 --- a/scenes/elevator_panel.gd +++ b/scenes/elevator_panel.gd @@ -1,91 +1,122 @@ -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 - -# placeholder until hnry's collision signal is ready -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 +var people_in_elevator := 0 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() -func _on_timer_expired(): - if doors_closing: - return - doors_closing = true - var screen = $PanelMargin/PanelColumn/Screen - screen.show_loss("TOO LATE :[") - EventBus.game_lost.emit() +# --- Floor lifecycle --- -func _on_close_pressed(): - if doors_closing: - return +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) + + EventBus.people_changed.emit(people_in_elevator, threshold) + EventBus.floor_changed.emit(current_floor) var screen = $PanelMargin/PanelColumn/Screen - var success = screen.attempt_hack() - if success: - doors_closing = true - floor_timer.stop() + screen.start() + screen.launch_pulse() # first survivor arrives - if people_in_elevator < get_threshold(): - screen.show_loss("TOO FEW :[[") - EventBus.game_lost.emit() - return +# --- Input --- - # 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) +func _on_block_pressed(): + if doors_closing_flag: + return + $PanelMargin/PanelColumn/Screen.attempt_block() - saved_count += people_in_elevator - current_floor -= 1 - EventBus.saved_changed.emit(saved_count) +# --- Pulse callbacks --- + +func _on_pulse_blocked(): + # This survivor made it in + survivors_remaining -= 1 + people_in_elevator += 1 + EventBus.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 + + var screen = $PanelMargin/PanelColumn/Screen + + # Lose: not enough people + if people_in_elevator < threshold: + screen.show_loss("TOO FEW") + EventBus.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 + + EventBus.score_changed.emit(score) + EventBus.saved_changed.emit(saved_count) + + current_floor -= 1 + + # Win: reached ground floor + if current_floor <= 1: + screen.show_win() EventBus.floor_changed.emit(current_floor) + EventBus.game_won.emit() + return - 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!") + # 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) diff --git a/scenes/game.tscn b/scenes/game.tscn index d07ae0a..8187b59 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") @@ -20,5 +23,18 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.004168272, 1.1920929e-07, [node name="ElevatorPanel" parent="CanvasPanel" unique_id=574176994 instance=ExtResource("3_lnu2h")] +<<<<<<< HEAD [node name="ComponentSpawn" parent="." unique_id=649225939 instance=ExtResource("5_iywne")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.004472971, 0) +======= +[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")] +>>>>>>> f2b176c978c80dee71e2e640451aa7b607238400 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