Squashed commit

This commit is contained in:
Jennie Robinson Faber 2026-05-17 19:20:58 +01:00
parent c713781de4
commit c3d8ff6989
14 changed files with 308 additions and 120 deletions

BIN
images/goatech_screen.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 873 B

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dddmw71jfy4yq"
path="res://.godot/imported/goatech_screen.png-55b460e1ad15ace769d864d7c2c15920.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://images/goatech_screen.png"
dest_files=["res://.godot/imported/goatech_screen.png-55b460e1ad15ace769d864d7c2c15920.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

View file

@ -47,7 +47,11 @@ func _on_pulse_started(duration: float):
var lag = max(PULSE_CLOSE_LAG_MIN, PULSE_CLOSE_LAG_TOP - floors_descended * PULSE_CLOSE_LAG_PER_FLOOR) var lag = max(PULSE_CLOSE_LAG_MIN, PULSE_CLOSE_LAG_TOP - floors_descended * PULSE_CLOSE_LAG_PER_FLOOR)
get_tree().create_timer(lag, false).timeout.connect( get_tree().create_timer(lag, false).timeout.connect(
func(): func():
if _pulse_close_pending: if not _pulse_close_pending:
return
if $ElevatorDoorRight.position.x < DOOR_OPEN_X - 0.001:
_pulse_close_pending = false
return
_pulse_close_pending = false _pulse_close_pending = false
_tween_doors(DOOR_CLOSED_X, duration), _tween_doors(DOOR_CLOSED_X, duration),
CONNECT_ONE_SHOT CONNECT_ONE_SHOT

View file

@ -4,6 +4,7 @@
[node name="ElevatorButton" type="Sprite3D" unique_id=1758559173] [node name="ElevatorButton" type="Sprite3D" unique_id=1758559173]
billboard = 1 billboard = 1
alpha_cut = 1
texture_filter = 0 texture_filter = 0
texture = ExtResource("1_sy1b3") texture = ExtResource("1_sy1b3")
hframes = 2 hframes = 2

View file

@ -1,7 +1,6 @@
extends PanelContainer extends PanelContainer
@onready var _screen = $PanelMargin/PanelColumn/Screen @onready var _screen = $PanelMargin/PanelColumn/Screen
@onready var _close_button: Button = $PanelMargin/PanelColumn/CloseButton
var current_floor: int = EventBus.STARTING_FLOOR var current_floor: int = EventBus.STARTING_FLOOR
var saved_count := 0 var saved_count := 0
@ -32,10 +31,17 @@ var people_in_elevator := 0
const PERFECT_STUN_DURATION := 1.5 const PERFECT_STUN_DURATION := 1.5
const DING_TO_DOORS_DELAY := 2.0 const DING_TO_DOORS_DELAY := 2.0
func _ready(): const HACK_DELAY_MIN := 1.25
_close_button.text = "CLOSE" const HACK_DELAY_MAX := 1.75
_close_button.pressed.connect(_on_block_pressed) 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_started.connect(_on_pulse_started)
_screen.pulse_blocked.connect(_on_pulse_blocked) _screen.pulse_blocked.connect(_on_pulse_blocked)
_screen.pulse_blocked_perfect.connect(_on_pulse_blocked_perfect) _screen.pulse_blocked_perfect.connect(_on_pulse_blocked_perfect)
@ -45,11 +51,15 @@ func _ready():
EventBus.game_lost.connect(func(_reason): $SfxRobotIUnderstand.play()) EventBus.game_lost.connect(func(_reason): $SfxRobotIUnderstand.play())
EventBus.survivor_squeaked_in.connect(_on_survivor_squeaked_in) EventBus.survivor_squeaked_in.connect(_on_survivor_squeaked_in)
EventBus.robot_close_warning.connect(_on_robot_close_warning) EventBus.robot_close_warning.connect(_on_robot_close_warning)
EventBus.survivor_entered_elevator.connect(_on_survivor_entered_elevator)
func _unhandled_input(event): func _unhandled_input(event):
if event is InputEventKey and event.pressed and not event.echo: if event is InputEventKey and event.pressed and not event.echo:
if event.keycode == KEY_SPACE: if event.keycode == KEY_SPACE:
_on_block_pressed() match state:
ScreenState.HACKED: _on_open_pressed()
ScreenState.IDLE: _on_close_pressed()
ScreenState.STATIC: pass
elif event.keycode == KEY_R: elif event.keycode == KEY_R:
get_tree().reload_current_scene() get_tree().reload_current_scene()
elif event.keycode >= KEY_1 and event.keycode <= KEY_9: elif event.keycode >= KEY_1 and event.keycode <= KEY_9:
@ -66,16 +76,23 @@ func _on_game_started():
func _reset_floor_state(): func _reset_floor_state():
doors_closing_flag = false doors_closing_flag = false
people_in_elevator = 0 people_in_elevator = 0
_close_button.text = "CLOSE"
var floors_remaining = current_floor - 1 var floors_remaining = current_floor - 1
survivors_remaining = BASE_SURVIVORS + (floors_remaining * SURVIVORS_PER_FLOOR_INCREASE) 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(): func _start_floor():
if EventBus.debug_starting_floor > 0: if EventBus.debug_starting_floor > 0:
current_floor = EventBus.debug_starting_floor current_floor = EventBus.debug_starting_floor
_onboarded = true _onboarded = true
EventBus.debug_starting_floor = 0 EventBus.debug_starting_floor = 0
floor_token += 1
var token = floor_token
_dbg("start_floor IDLE")
$SfxBell.play() $SfxBell.play()
if _onboarded and randf() < 0.4: if _onboarded and randf() < 0.4:
$SfxRobotDeepBreath.play() $SfxRobotDeepBreath.play()
@ -107,11 +124,51 @@ func _start_floor():
if not _onboarded: if not _onboarded:
_onboarded = true _onboarded = true
EventBus.doors_nearly_opened.connect(
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(): func():
if not is_instance_valid(self) or doors_closing_flag: if token != floor_token or doors_closing_flag:
_dbg("first-floor delay bail")
return return
_screen.launch_pulse(), _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 CONNECT_ONE_SHOT
) )
@ -123,46 +180,71 @@ func _on_robot_close_warning():
if not $SfxRobotDeepBreath.playing: if not $SfxRobotDeepBreath.playing:
$SfxRobotDeepBreath.play() $SfxRobotDeepBreath.play()
func _on_block_pressed(): func _on_open_pressed():
if state != ScreenState.HACKED:
_dbg("open rejected (state=%d)" % state)
return
if doors_closing_flag: if doors_closing_flag:
return return
EventBus.block_pressed.emit() if not _screen.pulse_active:
if _screen.pulse_active: return
EventBus.button_pressed.emit()
_screen.attempt_block() _screen.attempt_block()
else:
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() $SfxRobotThankYou.play()
_on_doors_closing(false) _on_doors_closing(false)
func _on_survivor_entered_elevator():
if doors_closing_flag:
return
$SfxDing.play()
func _on_pulse_started(duration: float): func _on_pulse_started(duration: float):
_close_button.text = "BLOCK"
EventBus.pulse_started.emit(duration) EventBus.pulse_started.emit(duration)
func _on_pulse_blocked_perfect(): func _on_pulse_blocked_perfect():
EventBus.robot_stun_requested.emit(PERFECT_STUN_DURATION) EventBus.robot_stun_requested.emit(PERFECT_STUN_DURATION)
func _on_pulse_blocked(): func _on_pulse_blocked():
_close_button.text = "CLOSE"
EventBus.pulse_blocked.emit() EventBus.pulse_blocked.emit()
survivors_remaining -= 1 survivors_remaining -= 1
people_in_elevator += 1 people_in_elevator += 1
$SfxDing.play()
EventBus.people_changed.emit(people_in_elevator, THRESHOLD) EventBus.people_changed.emit(people_in_elevator, THRESHOLD)
if $SfxBlock.stream:
$SfxBlock.play()
if survivors_remaining <= 0: if survivors_remaining <= 0:
get_tree().create_timer(0.25, false).timeout.connect(_on_doors_closing, CONNECT_ONE_SHOT) get_tree().create_timer(0.25, false).timeout.connect(_on_doors_closing, CONNECT_ONE_SHOT)
return return
var gap = _screen.get_pulse_gap() var token = floor_token
get_tree().create_timer(gap, false).timeout.connect( state = ScreenState.STATIC
_screen.enter_static()
_dbg("post-block STATIC")
get_tree().create_timer(STATIC_DURATION, false).timeout.connect(
func(): func():
if not doors_closing_flag: if token != floor_token or doors_closing_flag:
_screen.launch_pulse(), _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 CONNECT_ONE_SHOT
) )
func _on_doors_closing(fast: bool = false): func _on_doors_closing(fast: bool = false):
if doors_closing_flag: if doors_closing_flag:
return return
_dbg("doors_closing fast=%s" % fast)
doors_closing_flag = true doors_closing_flag = true
EventBus.doors_closed.emit(fast) EventBus.doors_closed.emit(fast)

View file

@ -12,26 +12,26 @@ corner_radius_top_right = 4
corner_radius_bottom_right = 4 corner_radius_bottom_right = 4
corner_radius_bottom_left = 4 corner_radius_bottom_left = 4
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_panel"]
[node name="ElevatorPanel" type="PanelContainer" unique_id=574176994] [node name="ElevatorPanel" type="PanelContainer" unique_id=574176994]
anchors_preset = 6 anchors_preset = 1
anchor_left = 1.0 anchor_left = 1.0
anchor_top = 0.5
anchor_right = 1.0 anchor_right = 1.0
anchor_bottom = 0.5
offset_left = -230.0 offset_left = -230.0
offset_top = -160.0 offset_top = 20.0
offset_right = -10.0 offset_right = -10.0
offset_bottom = 160.0 offset_bottom = 200.0
grow_horizontal = 0 grow_horizontal = 0
grow_vertical = 2 theme_override_styles/panel = SubResource("StyleBoxEmpty_panel")
script = ExtResource("1_1gr6t") script = ExtResource("1_1gr6t")
[node name="PanelMargin" type="MarginContainer" parent="." unique_id=1575963889] [node name="PanelMargin" type="MarginContainer" parent="." unique_id=1575963889]
layout_mode = 2 layout_mode = 2
theme_override_constants/margin_left = 15 theme_override_constants/margin_left = 0
theme_override_constants/margin_top = 15 theme_override_constants/margin_top = 0
theme_override_constants/margin_right = 15 theme_override_constants/margin_right = 0
theme_override_constants/margin_bottom = 15 theme_override_constants/margin_bottom = 0
[node name="VirtuaHandTemp" type="Sprite2D" parent="PanelMargin" unique_id=14079675] [node name="VirtuaHandTemp" type="Sprite2D" parent="PanelMargin" unique_id=14079675]
visible = false visible = false
@ -45,7 +45,7 @@ texture = ExtResource("2_03crn")
layout_mode = 2 layout_mode = 2
[node name="Screen" type="Panel" parent="PanelMargin/PanelColumn" unique_id=1395085208] [node name="Screen" type="Panel" parent="PanelMargin/PanelColumn" unique_id=1395085208]
custom_minimum_size = Vector2(160, 160) custom_minimum_size = Vector2(190, 160)
layout_mode = 2 layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_n2snw") theme_override_styles/panel = SubResource("StyleBoxFlat_n2snw")
script = ExtResource("1_3gei6") script = ExtResource("1_3gei6")
@ -76,9 +76,3 @@ text = ">:)"
[node name="Pulse" type="Node" parent="PanelMargin/PanelColumn/Screen" unique_id=988615527] [node name="Pulse" type="Node" parent="PanelMargin/PanelColumn/Screen" unique_id=988615527]
script = ExtResource("2_pulse") script = ExtResource("2_pulse")
[node name="CloseButton" type="Button" parent="PanelMargin/PanelColumn" unique_id=554485629]
custom_minimum_size = Vector2(0, 60)
layout_mode = 2
theme_override_font_sizes/font_size = 28
text = "CLOSE"

View file

@ -37,6 +37,7 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.004168272, 1.1920929e-07,
[node name="SfxDing" type="AudioStreamPlayer3D" parent="CanvasPanel/ElevatorPanel" unique_id=529226129] [node name="SfxDing" type="AudioStreamPlayer3D" parent="CanvasPanel/ElevatorPanel" unique_id=529226129]
stream = ExtResource("4_p57ef") stream = ExtResource("4_p57ef")
volume_db = 3.0 volume_db = 3.0
max_polyphony = 4
[node name="SfxOpen" type="AudioStreamPlayer3D" parent="CanvasPanel/ElevatorPanel" unique_id=3954090] [node name="SfxOpen" type="AudioStreamPlayer3D" parent="CanvasPanel/ElevatorPanel" unique_id=3954090]
stream = ExtResource("5_u5sy4") stream = ExtResource("5_u5sy4")
@ -62,6 +63,8 @@ stream = ExtResource("10_under")
[node name="SfxRobotThankYou" type="AudioStreamPlayer3D" parent="CanvasPanel/ElevatorPanel" unique_id=1100000004] [node name="SfxRobotThankYou" type="AudioStreamPlayer3D" parent="CanvasPanel/ElevatorPanel" unique_id=1100000004]
stream = ExtResource("11_thx") stream = ExtResource("11_thx")
[node name="SfxBlock" type="AudioStreamPlayer3D" parent="CanvasPanel/ElevatorPanel" unique_id=1100000007]
[node name="Ambience" type="AudioStreamPlayer" parent="." unique_id=1100000005] [node name="Ambience" type="AudioStreamPlayer" parent="." unique_id=1100000005]
process_mode = 3 process_mode = 3
stream = ExtResource("12_amb") stream = ExtResource("12_amb")

View file

@ -1,38 +1,4 @@
extends MarginContainer extends MarginContainer
var _displayed_floor := -1
func _ready(): func _ready():
$StatsColumn/FloorLabel.text = "FLOOR: 10" pass
$StatsColumn/SavedLabel.text = "SAVED: 0"
$StatsColumn/PeopleLabel.text = "PEOPLE: 0/2"
$StatsColumn/ScoreLabel.text = "SCORE: 0"
EventBus.floor_changed.connect(update_floor)
EventBus.saved_changed.connect(update_saved)
EventBus.people_changed.connect(update_people)
EventBus.score_changed.connect(update_score)
func update_floor(floor_num: int):
if _displayed_floor == floor_num:
return
var label = $StatsColumn/FloorLabel
var new_text = "FLOOR: " + str(floor_num)
if _displayed_floor == -1:
label.text = new_text
_displayed_floor = floor_num
return
_displayed_floor = floor_num
UIUtils.flip_label_text(label, new_text)
func update_saved(count: int):
$StatsColumn/SavedLabel.text = "SAVED: " + str(count)
func update_people(count: int, threshold: int):
var label = $StatsColumn/PeopleLabel
label.text = "PEOPLE: " + str(count) + "/" + str(threshold)
var color = Color(0.4, 1.0, 0.4) if count >= threshold else Color(1.0, 0.4, 0.4)
label.add_theme_color_override("font_color", color)
func update_score(new_score: int):
$StatsColumn/ScoreLabel.text = "SCORE: " + str(new_score)

View file

@ -8,25 +8,3 @@ offset_bottom = 40.0
theme_override_constants/margin_left = 72 theme_override_constants/margin_left = 72
theme_override_constants/margin_top = 20 theme_override_constants/margin_top = 20
script = ExtResource("1_64ctp") script = ExtResource("1_64ctp")
[node name="StatsColumn" type="VBoxContainer" parent="." unique_id=1476756270]
layout_mode = 2
[node name="PeopleLabel" type="Label" parent="StatsColumn" unique_id=1174457387]
layout_mode = 2
theme_override_font_sizes/font_size = 24
text = "PEOPLE: 0"
[node name="SavedLabel" type="Label" parent="StatsColumn" unique_id=969002022]
layout_mode = 2
theme_override_font_sizes/font_size = 24
text = "SAVED: 0"
[node name="FloorLabel" type="Label" parent="StatsColumn" unique_id=67147411]
layout_mode = 2
theme_override_font_sizes/font_size = 24
text = "FLOOR: 0"
[node name="ScoreLabel" type="Label" parent="StatsColumn" unique_id=1595653166]
layout_mode = 2
theme_override_font_sizes/font_size = 24

View file

@ -36,6 +36,11 @@ var _quota_threshold := 0
var _quota_filled_style: StyleBoxFlat var _quota_filled_style: StyleBoxFlat
var _quota_empty_style: StyleBoxFlat var _quota_empty_style: StyleBoxFlat
var _idle_image: TextureRect
var _static_overlay: ColorRect
var _score_label: Label
var _score_value := 0
@onready var _pulse = $Pulse @onready var _pulse = $Pulse
signal pulse_started(duration: float) signal pulse_started(duration: float)
@ -90,6 +95,42 @@ func _ready():
EventBus.floor_changed.connect(_update_floor_label) EventBus.floor_changed.connect(_update_floor_label)
EventBus.people_changed.connect(_update_quota_dots) EventBus.people_changed.connect(_update_quota_dots)
_idle_image = TextureRect.new()
_idle_image.texture = load("res://images/goatech_screen.png")
_idle_image.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
_idle_image.anchor_right = 1.0
_idle_image.anchor_bottom = 1.0
_idle_image.mouse_filter = Control.MOUSE_FILTER_IGNORE
_idle_image.visible = false
add_child(_idle_image)
_static_overlay = ColorRect.new()
_static_overlay.color = Color(1.0, 1.0, 1.0, 0.6)
_static_overlay.anchor_right = 1.0
_static_overlay.anchor_bottom = 1.0
_static_overlay.mouse_filter = Control.MOUSE_FILTER_IGNORE
_static_overlay.visible = false
add_child(_static_overlay)
_score_label = Label.new()
_score_label.add_theme_font_size_override("font_size", 12)
_score_label.add_theme_color_override("font_color", Color(0.2, 1, 0.2, 1))
_score_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
_score_label.anchor_left = 0.0
_score_label.anchor_right = 1.0
_score_label.anchor_top = 0.0
_score_label.anchor_bottom = 0.0
_score_label.offset_top = QUOTA_DOT_Y + QUOTA_DOT_SIZE + 2
_score_label.offset_bottom = QUOTA_DOT_Y + QUOTA_DOT_SIZE + 18
_score_label.offset_right = -QUOTA_DOT_RIGHT_MARGIN
_score_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
_score_label.visible = false
_score_label.text = "SCORE: 0"
add_child(_score_label)
EventBus.score_changed.connect(_on_score_changed)
enter_idle()
func start(floor_num: int = EventBus.STARTING_FLOOR): func start(floor_num: int = EventBus.STARTING_FLOOR):
active = true active = true
$AIFace.text = ">:)" $AIFace.text = ">:)"
@ -186,6 +227,10 @@ func _update_quota_dots(count: int, threshold: int):
var style = _quota_filled_style if i < count else _quota_empty_style var style = _quota_filled_style if i < count else _quota_empty_style
_quota_dots[i].add_theme_stylebox_override("panel", style) _quota_dots[i].add_theme_stylebox_override("panel", style)
func _on_score_changed(new_score: int):
_score_value = new_score
_score_label.text = "SCORE: " + str(new_score)
func _update_perfect_zone(): func _update_perfect_zone():
var bz = $TargetZone var bz = $TargetZone
var width = bz.size.x * PERFECT_ZONE_RATIO var width = bz.size.x * PERFECT_ZONE_RATIO
@ -235,6 +280,7 @@ func flash(color: Color):
_flash_tween.tween_property(style, "bg_color", base_color, 0.3) _flash_tween.tween_property(style, "bg_color", base_color, 0.3)
func show_countdown(): func show_countdown():
$AIFace.visible = true
stop() stop()
$AIFace.text = "3" $AIFace.text = "3"
var tween = create_tween() var tween = create_tween()
@ -243,15 +289,17 @@ func show_countdown():
tween.tween_interval(0.6) tween.tween_interval(0.6)
tween.tween_callback(func(): $AIFace.text = "1") tween.tween_callback(func(): $AIFace.text = "1")
tween.tween_interval(0.6) tween.tween_interval(0.6)
tween.tween_callback(func(): $AIFace.text = "CLOSED") tween.tween_callback(func(): $AIFace.text = "")
func show_win(): func show_win():
$AIFace.visible = true
stop() stop()
$TargetZone.visible = false $TargetZone.visible = false
$AIFace.text = "ESCAPED" $AIFace.text = "ESCAPED"
flash(Color.GREEN) flash(Color.GREEN)
func show_loss(message: String): func show_loss(message: String):
$AIFace.visible = true
stop() stop()
$TargetZone.visible = false $TargetZone.visible = false
$AIFace.text = message $AIFace.text = message
@ -268,6 +316,30 @@ func end_onboarding():
$AIFace.size = Vector2(size.x, 30) $AIFace.size = Vector2(size.x, 30)
$TargetZone.visible = true $TargetZone.visible = true
func enter_idle() -> void:
_idle_image.visible = true
$AIFace.visible = false
$TargetZone.visible = false
$SweepLine.visible = false
_static_overlay.visible = false
_score_label.visible = true
move_child(_score_label, get_child_count() - 1)
func enter_static() -> void:
move_child(_static_overlay, get_child_count() - 1)
_static_overlay.visible = true
block_zone_moving = false
_score_label.visible = false
func enter_hacked() -> void:
_idle_image.visible = false
$AIFace.visible = true
$TargetZone.visible = true
_static_overlay.visible = false
_score_label.visible = false
block_zone_moving = block_zone_speed > 0.0
launch_pulse()
func _start_ready_pulse(): func _start_ready_pulse():
if _ready_pulse_tween: if _ready_pulse_tween:
_ready_pulse_tween.kill() _ready_pulse_tween.kill()

View file

@ -60,6 +60,7 @@ func _on_area_3d_area_entered(area: Area3D) -> void:
if final_progress > 0.49: if final_progress > 0.49:
return return
_saved = true _saved = true
EventBus.survivor_entered_elevator.emit()
queue_free(), queue_free(),
CONNECT_ONE_SHOT CONNECT_ONE_SHOT
) )

View file

@ -1,25 +1,70 @@
extends Sprite3D extends Sprite3D
const SLIDE_DURATION := 0.25
const UP_DURATION := 0.1 const UP_DURATION := 0.1
const HOLD_DURATION := 0.05 const HOLD_DURATION := 0.05
const DOWN_DURATION := 0.18 const DOWN_DURATION := 0.18
var _rest_position: Vector3 var _open_button: Node3D
var _tween: Tween var _close_button: Node3D
var _target_button: Node3D
@onready var _button: Node3D = get_parent().get_node("ElevatorButton") var _close_rest_x: float
var _open_rest_x: float
var _rest_y: float
var _slide_tween: Tween
var _press_tween: Tween
func _ready() -> void: func _ready() -> void:
_rest_position = position if not get_parent().is_node_ready():
EventBus.block_pressed.connect(_animate_press) await get_parent().ready
await get_tree().process_frame
_open_button = get_parent().get_node("DoorOpen")
_close_button = get_parent().get_node("DoorClose")
_close_rest_x = _project_to_hand_plane(_close_button)
_open_rest_x = _project_to_hand_plane(_open_button)
_rest_y = position.y
_target_button = _close_button
position = Vector3(_close_rest_x, _rest_y, position.z)
print("[VirtuaHand] close_x=", _close_rest_x, " open_x=", _open_rest_x, " hand_pos=", position)
EventBus.button_pressed.connect(_animate_press)
EventBus.active_button_changed.connect(_on_active_button_changed)
func _project_to_hand_plane(button: Node3D) -> float:
var cam := get_viewport().get_camera_3d()
if not cam:
return button.position.x
var screen_pos := cam.unproject_position(button.global_position)
var ray_origin := cam.project_ray_origin(screen_pos)
var ray_dir := cam.project_ray_normal(screen_pos)
if absf(ray_dir.z) < 0.0001:
return button.position.x
var t := (global_position.z - ray_origin.z) / ray_dir.z
var hit := ray_origin + ray_dir * t
return get_parent().to_local(hit).x
func _on_active_button_changed(target: String) -> void:
var new_button: Node3D = _open_button if target == "open" else _close_button
if new_button == _target_button:
return
_target_button = new_button
var target_x: float = _open_rest_x if target == "open" else _close_rest_x
print("[VirtuaHand] slide target=", target, " target_x=", target_x, " current_pos=", position)
if _press_tween and _press_tween.is_valid():
_press_tween.kill()
if _slide_tween and _slide_tween.is_valid():
_slide_tween.kill()
_slide_tween = create_tween()
_slide_tween.tween_property(self, "position:x", target_x, SLIDE_DURATION).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
func _animate_press() -> void: func _animate_press() -> void:
if not is_instance_valid(_button): if not is_instance_valid(_target_button):
return return
if _tween and _tween.is_valid(): if _press_tween and _press_tween.is_valid():
_tween.kill() _press_tween.kill()
var press_pos := Vector3(_rest_position.x, _button.position.y, _rest_position.z) var fingertip_offset := texture.get_height() * 0.5 * pixel_size * scale.y
_tween = create_tween() var press_y := _target_button.position.y - fingertip_offset
_tween.tween_property(self, "position", press_pos, UP_DURATION).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_OUT) _press_tween = create_tween()
_tween.tween_interval(HOLD_DURATION) _press_tween.tween_property(self, "position:y", press_y, UP_DURATION).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_OUT)
_tween.tween_property(self, "position", _rest_position, DOWN_DURATION).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN) _press_tween.tween_interval(HOLD_DURATION)
_press_tween.tween_property(self, "position:y", _rest_y, DOWN_DURATION).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN)

View file

@ -2,7 +2,7 @@
[ext_resource type="PackedScene" uid="uid://dowvqakiqkvk5" path="res://scenes/camera_3d.tscn" id="1_f3sb7"] [ext_resource type="PackedScene" uid="uid://dowvqakiqkvk5" path="res://scenes/camera_3d.tscn" id="1_f3sb7"]
[ext_resource type="PackedScene" uid="uid://cxnocjdotkl5e" path="res://scenes/hall_block.tscn" id="1_tlwt5"] [ext_resource type="PackedScene" uid="uid://cxnocjdotkl5e" path="res://scenes/hall_block.tscn" id="1_tlwt5"]
[ext_resource type="PackedScene" uid="uid://brd3iponame0e" path="res://scenes/elevator.tscn" id="4_k0juu"] [ext_resource type="PackedScene" path="res://scenes/elevator.tscn" id="4_k0juu"]
[ext_resource type="PackedScene" uid="uid://bkqwi2yqa0nvg" path="res://scenes/virtua_hand.tscn" id="5_71j4m"] [ext_resource type="PackedScene" uid="uid://bkqwi2yqa0nvg" path="res://scenes/virtua_hand.tscn" id="5_71j4m"]
[ext_resource type="PackedScene" uid="uid://cwwexawpj46hk" path="res://scenes/elevator_button.tscn" id="5_qfnet"] [ext_resource type="PackedScene" uid="uid://cwwexawpj46hk" path="res://scenes/elevator_button.tscn" id="5_qfnet"]
[ext_resource type="Script" uid="uid://c6v2lhrkeup5i" path="res://scenes/world.gd" id="5_world"] [ext_resource type="Script" uid="uid://c6v2lhrkeup5i" path="res://scenes/world.gd" id="5_world"]
@ -39,13 +39,13 @@ unique_name_in_owner = true
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -8.351522) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -8.351522)
[node name="VirtuaHand" parent="ElevatorSafeZone" unique_id=1811028460 instance=ExtResource("5_71j4m")] [node name="VirtuaHand" parent="ElevatorSafeZone" unique_id=1811028460 instance=ExtResource("5_71j4m")]
transform = Transform3D(3, 0, 0, 0, 3, 0, 0, 0, 3, -2.61384, 0.554309, -0.67426205) transform = Transform3D(3, 0, 0, 0, 3, 0, 0, 0, 3, -2.9201458, 1.49612185, -0.67426205)
[node name="DoorOpen" parent="ElevatorSafeZone" unique_id=1758559173 instance=ExtResource("5_qfnet")] [node name="DoorOpen" parent="ElevatorSafeZone" unique_id=1758559173 instance=ExtResource("5_qfnet")]
transform = Transform3D(0.35, 0, 0, 0, 0.35, 0, 0, 0, 0.35, -3.082784, 2.258, -0.39000034) transform = Transform3D(0.35, 0, 0, 0, 0.35, 0, 0, 0, 0.35, -2.882784, 3.348, -0.39000034)
[node name="DoorClose" parent="ElevatorSafeZone" unique_id=513173058 instance=ExtResource("5_qfnet")] [node name="DoorClose" parent="ElevatorSafeZone" unique_id=513173058 instance=ExtResource("5_qfnet")]
transform = Transform3D(0.35, 0, 0, 0, 0.35, 0, 0, 0, 0.35, -3.6393127, 2.258, -0.39000034) transform = Transform3D(0.35, 0, 0, 0, 0.35, 0, 0, 0, 0.35, -3.4393127, 3.348, -0.39000034)
frame = 1 frame = 1
[node name="Elevator" parent="ElevatorSafeZone" unique_id=242258467 instance=ExtResource("4_k0juu")] [node name="Elevator" parent="ElevatorSafeZone" unique_id=242258467 instance=ExtResource("4_k0juu")]
@ -70,7 +70,7 @@ shape = SubResource("BoxShape3D_k0juu")
[node name="ButtonPanel" parent="ElevatorSafeZone" unique_id=1270714626 instance=ExtResource("7_i7141")] [node name="ButtonPanel" parent="ElevatorSafeZone" unique_id=1270714626 instance=ExtResource("7_i7141")]
transform = Transform3D(0.3, 0, 0, 0, 0.3, 0, 0, 0, 0.3, 3.4193654, 2.2598982, -0.39259052) transform = Transform3D(0.3, 0, 0, 0, 0.3, 0, 0, 0, 0.3, 3.4193654, 2.2598982, -0.39259052)
[node name="LeftHand" parent="ElevatorSafeZone" instance=ExtResource("8_lefth")] [node name="LeftHand" parent="ElevatorSafeZone" unique_id=547108366 instance=ExtResource("8_lefth")]
transform = Transform3D(3, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0) transform = Transform3D(3, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0)
visible = false visible = false

View file

@ -25,7 +25,9 @@ signal robot_stun_requested(duration: float)
signal robot_floor_started(delay: float, robot_speed: float) signal robot_floor_started(delay: float, robot_speed: float)
signal robot_close_warning signal robot_close_warning
signal survivor_squeaked_in signal survivor_squeaked_in
signal block_pressed signal button_pressed
signal survivor_entered_elevator
signal intro_finished signal intro_finished
signal active_button_changed(target: String)
@warning_ignore_restore("unused_signal") @warning_ignore_restore("unused_signal")