extends Panel # 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 # State var active := false var pulses_blocked := 0 signal pulse_blocked signal doors_closing func _ready(): # 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.size = Vector2(4, size.y - 40) 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 = ">:)" # 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 pulse_x = 0.0 pulse_active = true $SweepLine.visible = true $SweepLine.position.x = 0 $AIFace.text = ">:)" func get_pulse_gap() -> float: return pulse_gap 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" 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(): stop() $TargetZone.visible = false $AIFace.text = "ESCAPED" flash(Color.GREEN) func show_loss(message: String): stop() $TargetZone.visible = false $AIFace.text = message flash(Color.RED)