extends PanelContainer @onready var _screen = $PanelMargin/PanelColumn/Screen var current_floor: int = EventBus.STARTING_FLOOR var saved_count := 0 var doors_closing_flag := false var _onboarded := false const BASE_SURVIVORS := 2 const SURVIVORS_PER_FLOOR_INCREASE := 1 var survivors_remaining := 0 const THRESHOLD := 2 var score := 0 const POINTS_PER_PERSON := 100 const MOVING_ZONE_SPEED_MIN := 20.0 const MOVING_ZONE_SPEED_MAX := 130.0 const DIFFICULTY_EXPONENT := 1.4 const ROBOT_SPEED_TOP := 2.0 const ROBOT_SPEED_PER_FLOOR := 0.15 const ROBOT_DELAY_TOP := 3.0 const ROBOT_DELAY_PER_FLOOR := 0.6 const ROBOT_DELAY_MIN := 1.0 var people_in_elevator := 0 const PERFECT_STUN_DURATION := 1.5 const DING_TO_DOORS_DELAY := 2.0 const HACK_DELAY_MIN := 1.25 const HACK_DELAY_MAX := 1.75 const FIRST_FLOOR_INTRO_DELAY := 2.0 const STATIC_DURATION := 0.2 const DEBUG_SCREEN_STATE := false enum ScreenState { IDLE, STATIC, HACKED } var state: ScreenState = ScreenState.IDLE var floor_token: int = 0 func _ready(): _screen.pulse_started.connect(_on_pulse_started) _screen.pulse_blocked.connect(_on_pulse_blocked) _screen.pulse_blocked_perfect.connect(_on_pulse_blocked_perfect) _screen.doors_closing.connect(func(): _on_doors_closing(true)) EventBus.game_started.connect(_on_game_started) EventBus.game_lost.connect(func(_reason): $SfxRobotIUnderstand.play()) EventBus.survivor_squeaked_in.connect(_on_survivor_squeaked_in) EventBus.robot_close_warning.connect(_on_robot_close_warning) EventBus.survivor_entered_elevator.connect(_on_survivor_entered_elevator) func _unhandled_input(event): if event is InputEventKey and event.pressed and not event.echo: if event.keycode == KEY_SPACE: match state: ScreenState.HACKED: _on_open_pressed() ScreenState.IDLE: _on_close_pressed() ScreenState.STATIC: pass elif event.keycode == KEY_R: get_tree().reload_current_scene() elif event.keycode >= KEY_1 and event.keycode <= KEY_9: EventBus.debug_starting_floor = event.keycode - KEY_0 get_tree().reload_current_scene() elif event.keycode == KEY_0: EventBus.debug_starting_floor = EventBus.STARTING_FLOOR get_tree().reload_current_scene() func _on_game_started(): await EventBus.intro_finished _start_floor() func _reset_floor_state(): doors_closing_flag = false people_in_elevator = 0 var floors_remaining = current_floor - 1 survivors_remaining = BASE_SURVIVORS + (floors_remaining * SURVIVORS_PER_FLOOR_INCREASE) func _dbg(msg: String) -> void: if DEBUG_SCREEN_STATE: print("[screen] floor=%d token=%d %s" % [current_floor, floor_token, msg]) func _start_floor(): if EventBus.debug_starting_floor > 0: current_floor = EventBus.debug_starting_floor _onboarded = true EventBus.debug_starting_floor = 0 floor_token += 1 var token = floor_token _dbg("start_floor IDLE") $SfxBell.play() if _onboarded and randf() < 0.4: $SfxRobotDeepBreath.play() _reset_floor_state() get_tree().create_timer(DING_TO_DOORS_DELAY, false).timeout.connect( func(): if not is_instance_valid(self) or doors_closing_flag: return $SfxOpen.play() EventBus.doors_opened.emit(), CONNECT_ONE_SHOT ) EventBus.people_changed.emit(people_in_elevator, THRESHOLD) EventBus.floor_changed.emit(current_floor) EventBus.floor_started.emit(survivors_remaining) var floors_descended = EventBus.STARTING_FLOOR - current_floor var robot_speed = ROBOT_SPEED_TOP + floors_descended * ROBOT_SPEED_PER_FLOOR var robot_delay = max(ROBOT_DELAY_MIN, ROBOT_DELAY_TOP - floors_descended * ROBOT_DELAY_PER_FLOOR) EventBus.robot_floor_started.emit(robot_delay, robot_speed) _screen.start(current_floor) var t_raw = float(floors_descended) / float(EventBus.STARTING_FLOOR - 1) var t_difficulty = pow(t_raw, DIFFICULTY_EXPONENT) var zone_speed = lerp(MOVING_ZONE_SPEED_MIN, MOVING_ZONE_SPEED_MAX, t_difficulty) _screen.set_block_zone_movement(zone_speed) if not _onboarded: _onboarded = true state = ScreenState.IDLE _screen.enter_idle() EventBus.active_button_changed.emit("close") if current_floor == EventBus.STARTING_FLOOR: get_tree().create_timer(FIRST_FLOOR_INTRO_DELAY, false).timeout.connect( func(): if token != floor_token or doors_closing_flag: _dbg("first-floor delay bail") return _enter_static_then_hacked(token), CONNECT_ONE_SHOT ) else: schedule_next_hack(token) func schedule_next_hack(token: int) -> void: var delay := randf_range(HACK_DELAY_MIN, HACK_DELAY_MAX) _dbg("schedule_next_hack delay=%.2f" % delay) get_tree().create_timer(delay, false).timeout.connect( func(): if token != floor_token: _dbg("schedule_next_hack token bail") return if doors_closing_flag: _dbg("schedule_next_hack doors_closing bail") return _enter_static_then_hacked(token), CONNECT_ONE_SHOT ) func _enter_static_then_hacked(token: int) -> void: state = ScreenState.STATIC _screen.enter_static() _dbg("STATIC") get_tree().create_timer(STATIC_DURATION, false).timeout.connect( func(): if token != floor_token or doors_closing_flag: _dbg("post-STATIC bail") return state = ScreenState.HACKED _dbg("HACKED") _screen.enter_hacked() EventBus.active_button_changed.emit("open"), CONNECT_ONE_SHOT ) func _on_survivor_squeaked_in(): if not $SfxRobotSoundsDifficult.playing: $SfxRobotSoundsDifficult.play() func _on_robot_close_warning(): if not $SfxRobotDeepBreath.playing: $SfxRobotDeepBreath.play() func _on_open_pressed(): if state != ScreenState.HACKED: _dbg("open rejected (state=%d)" % state) return if doors_closing_flag: return if not _screen.pulse_active: return EventBus.button_pressed.emit() _screen.attempt_block() func _on_close_pressed(): if state != ScreenState.IDLE: _dbg("close rejected (state=%d)" % state) return if doors_closing_flag: return EventBus.button_pressed.emit() $SfxRobotThankYou.play() _on_doors_closing(false) func _on_survivor_entered_elevator(): if doors_closing_flag: return $SfxDing.play() func _on_pulse_started(duration: float): EventBus.pulse_started.emit(duration) func _on_pulse_blocked_perfect(): EventBus.robot_stun_requested.emit(PERFECT_STUN_DURATION) func _on_pulse_blocked(): EventBus.pulse_blocked.emit() survivors_remaining -= 1 people_in_elevator += 1 EventBus.people_changed.emit(people_in_elevator, THRESHOLD) if $SfxBlock.stream: $SfxBlock.play() if survivors_remaining <= 0: get_tree().create_timer(0.25, false).timeout.connect(_on_doors_closing, CONNECT_ONE_SHOT) return var token = floor_token state = ScreenState.STATIC _screen.enter_static() _dbg("post-block STATIC") get_tree().create_timer(STATIC_DURATION, false).timeout.connect( func(): if token != floor_token or doors_closing_flag: _dbg("post-block-STATIC bail") return state = ScreenState.IDLE _screen.enter_idle() _dbg("post-block IDLE") EventBus.active_button_changed.emit("close") schedule_next_hack(token), CONNECT_ONE_SHOT ) func _on_doors_closing(fast: bool = false): if doors_closing_flag: return _dbg("doors_closing fast=%s" % fast) doors_closing_flag = true EventBus.doors_closed.emit(fast) if people_in_elevator < THRESHOLD: _screen.show_loss("TOO FEW") EventBus.game_lost.emit("TOO FEW") return 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 if current_floor <= 1: _screen.show_win() EventBus.floor_changed.emit(current_floor) EventBus.game_won.emit() return $SfxClose.play() _screen.show_countdown() _screen.shrink_block_zone() var tween = create_tween() tween.tween_interval(3.0) tween.tween_callback(_start_floor)