From eb4da62f995d6d455cafa768096c6f71006546b5 Mon Sep 17 00:00:00 2001 From: daddy Date: Sun, 6 Sep 2026 14:24:45 +0000 Subject: [PATCH] Fix ESC-crashes-the-game, avatar facing backward, invisible build stamp ESC: player.gd changed scene directly while game.gd._exit_tree closed the socket, which re-fired server_disconnected -> _on_server_lost ran mid- teardown (add_child on an exiting node, await on a null get_tree(), a second change_scene_to_file). Moved ESC to game.gd as one ordered path: _leave_world() sets a _leaving guard, disconnects the _on_server_lost handlers before closing the connection, then changes scene. _on_server_lost and _exit_tree now respect the guard. Verified headless: ESC -> world_list, no errors; 2p multiplayer still connects. Avatar faced +Z but a Godot node's forward is -Z, so walking forward showed the character its own face. Rebuilt the placeholder facing -Z (face plates, eyes, jaw, toes, and front-mounted attachments all moved to negative Z; backpack/ponytail to +Z). The procedural walk was already written for -Z forward, so it's unchanged. Build stamp was there but tiny/low-contrast bottom-left -- bumped to 20px opaque yellow with a heavy outline, moved to the top-left strip. limb_animator: guard _process with is_instance_valid(_skel) so a frame landing during avatar teardown can't touch a freed skeleton. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_015jtxAiu5dAW4WxCNdGkrkJ --- assets/avatars/accessory_necklace_01.glb | Bin 129388 -> 129392 bytes assets/avatars/back_backpack_01.glb | Bin 3008 -> 3004 bytes assets/avatars/base_human_male_01.glb | Bin 24864 -> 24872 bytes assets/avatars/face_glasses_01.glb | Bin 3008 -> 3008 bytes assets/avatars/hair_ponytail_01.glb | Bin 3008 -> 3004 bytes assets/avatars/left_foot_boot_01.glb | Bin 3008 -> 3012 bytes assets/avatars/right_foot_boot_01.glb | Bin 3012 -> 3012 bytes scripts/avatar/limb_animator.gd | 2 +- scripts/game.gd | 69 +++++++++++++++++------ scripts/player.gd | 7 +-- scripts/tools/gen_placeholder_avatars.gd | 32 ++++++----- 11 files changed, 71 insertions(+), 39 deletions(-) diff --git a/assets/avatars/accessory_necklace_01.glb b/assets/avatars/accessory_necklace_01.glb index 400a3284c272369f617fa626d910625d87debd95..016ca2bcd68d18e80ce083a535545a404af434f3 100644 GIT binary patch delta 43 zcmaF!iT%STcHZ=y5H}_U28M#4j0`FpdEc@z>Q4T}X2qzTJeK(c%*TP&*C7 delta 37 tcmezHiT%wdcHZ=y5H}_U28NuUj0_4JdEc^4{>^69Y{9gzDJxdJtxGCiGhJ(4>tqDAGVErC)p<7V6)sT!oG$D0G!+jrvLx| delta 38 scmdlZen6ZrJtxGCiGhLP05_0e-^h28jZt^|N^6`(56N-@AD_=(*>f@0{t^HVhqrWeTZ$&kDy8bh*fBw07p}DMgCA?tbO-0T*y1g7N{w_5g=W|O< zy+>-&UQo-Wl}w!J*i3L-8l*CfSXt-E08Dv9ZIqXSeEGF6yNxfQ@B7WamFpI;dvt4X zukgJwkB0`ee|je&^uVMcY-<=Vn^;gWH?=W0<&AP4n3}eO(FE1b8kMu|_yz*_^xPCp zb5p{c#{dX}Kj&Q;9Z`}fVVV#PY5h6JFx5@OG@J)UE%{3nM8_49C<-Hdm7FofS(GkjC>HxJ$nTi-@pVo6$Ee|MoAh>j9~qE!-L@u z^P1=(b3AmfT3xCCjkd^aL_EW$m;#^&CQXE68qPBua!<(@^F%g}V45Z&m`@qn8ah!7 pps}O{FW?h2S#V6ldBT>d3E(`_n}(ntx)1J-&TP;$!KcGh@gEPuxJm#3 delta 1465 zcmZ`&J4gdT6ueag;}0|!#6}@$M2NKrHr`uq^ku!INM>i}&Aj(6S8IuCON<{JZm$nmmX+SOth>tim9Q(` zj##Wzlj=5>^cKa@h-#0{sGHGz+|Dl-ZTc+P#pTsiXT@Go4?}77D&k&tY;!^#CwEn0 zHlf~<8(v}dVNgw`6Dl$H75qMV@9A$5J3a3{Hb~dUBJ(W5#_&1s&eG&2u>tYIk9+)&C?$L0b zVV`?Se&{DMc?8om5y5;)-_+1XF+hMN9e4qsz-7Tb8qO0oOmzU~nO-*peLXexyE=VA N(|D(QHKE$Y;U6{=bfy3R diff --git a/assets/avatars/face_glasses_01.glb b/assets/avatars/face_glasses_01.glb index e0b2f3c342b17f3e4c53705cdbb590dbda4936c3..59d9111838069ca5d72bc250e8fd8a20635b11a3 100644 GIT binary patch delta 18 acmX>gen5P~4K_yI$v4=nH;b^ZVF3U|)&`XT delta 16 YcmX>gen5P~4YtWQ*{nAUv#(_V06mokXaE2J diff --git a/assets/avatars/hair_ponytail_01.glb b/assets/avatars/hair_ponytail_01.glb index 35573125be4a28b12d47556ad64cadf3e5db9dbb..6cb9e382d7bcc724c84bae1700f334d5beb097a1 100644 GIT binary patch delta 32 ocmX>gzDJxdJtxGCiGhJ(4>tqDAGVErC)p<7V6)sT!oG$D0G!+jrvLx| delta 38 scmdlZen6ZrJtxGCiGhLP05_0e-^h28jZt^gengx%JtxGCiGhLP2sZ-*%SPU_Y>c{-Z?Rc1D<~*z7GYn*0sy%N2~Pk3 delta 31 lcmX>ien6ZzJtxGCiGhLP05_1>$a|J;@@+P&%@XWuSOAMa2)h6P diff --git a/assets/avatars/right_foot_boot_01.glb b/assets/avatars/right_foot_boot_01.glb index 6a0dbe083fe996183916b63b8f8133c70fa88333..a396767cb08ad71310f9046b7db037d7916553ba 100644 GIT binary patch delta 18 acmX>ienfo3Z8k>T$+y{THcPOtVF3U}vj(UD delta 16 YcmX>ienfo3ZMMmG*lae7v#(_V06ts>djJ3c diff --git a/scripts/avatar/limb_animator.gd b/scripts/avatar/limb_animator.gd index d58aa24..5c95894 100644 --- a/scripts/avatar/limb_animator.gd +++ b/scripts/avatar/limb_animator.gd @@ -42,7 +42,7 @@ func setup(skeleton: Skeleton3D) -> void: func _process(delta: float) -> void: - if _skel == null or _idx.is_empty(): + if not is_instance_valid(_skel) or _idx.is_empty(): return var moving := speed > MOVE_EPS diff --git a/scripts/game.gd b/scripts/game.gd index a54dcba..7b6bd9c 100644 --- a/scripts/game.gd +++ b/scripts/game.gd @@ -22,6 +22,7 @@ const CORRECTION_THRESHOLD := 0.6 var _net: NetworkClient = null var _networked := false +var _leaving := false # a scene change is already in flight var _aligned := false # snapped to the server spawn yet? var _remotes: Dictionary = {} # peer_id -> Node3D var _remote_prev: Dictionary = {} # peer_id -> Vector3 (last snapshot position) @@ -34,6 +35,37 @@ func _ready() -> void: _setup_networked() +func _unhandled_input(event: InputEvent) -> void: + # ESC leaves the world back to the browser. Handled here (the scene root + # that owns the connection) rather than in player.gd so the teardown is + # a single ordered path -- doing it from two places raced _on_server_lost + # against the scene change and looked like a crash. + if event.is_action_pressed("ui_cancel"): + get_viewport().set_input_as_handled() + _leave_world() + + +func _leave_world() -> void: + if _leaving: + return + _leaving = true + Input.mouse_mode = Input.MOUSE_MODE_VISIBLE + _teardown_net() + Session.clear() + get_tree().change_scene_to_file("res://scenes/world_list.tscn") + + +## Close the ENet connection and stop it from calling us back mid-teardown. +func _teardown_net() -> void: + if _net == null or not is_instance_valid(_net): + return + if _net.server_disconnected.is_connected(_on_server_lost): + _net.server_disconnected.disconnect(_on_server_lost) + if _net.connection_failed.is_connected(_on_server_lost): + _net.connection_failed.disconnect(_on_server_lost) + _net.disconnect_from_server() + + static func _build_label() -> String: var f := FileAccess.open("res://assets/build_info.json", FileAccess.READ) @@ -51,30 +83,28 @@ func _show_build_stamp() -> void: add_child(layer) var lbl := Label.new() lbl.text = "build %s" % _build_label() - lbl.add_theme_color_override("font_color", Color(1, 1, 0.45)) - lbl.add_theme_color_override("font_outline_color", Color(0, 0, 0, 0.85)) - lbl.add_theme_constant_override("outline_size", 4) - lbl.add_theme_font_size_override("font_size", 14) - # Anchor a full-width strip to the bottom of the screen and left-align the - # text in it. (A bare preset + .position on a zero-size Label collapses the - # rect to negative width and nothing draws -- which is why this never showed.) - lbl.set_anchors_preset(Control.PRESET_BOTTOM_WIDE) + lbl.add_theme_color_override("font_color", Color(1, 1, 0.35)) + lbl.add_theme_color_override("font_outline_color", Color(0, 0, 0, 1)) + lbl.add_theme_constant_override("outline_size", 6) + lbl.add_theme_font_size_override("font_size", 20) + # Full-width strip pinned to the TOP-left. (A bare preset + .position on a + # zero-size Label collapses the rect to negative width and nothing draws -- + # which is why this never showed; explicit offsets fix it.) + lbl.set_anchors_preset(Control.PRESET_TOP_WIDE) lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_LEFT - lbl.vertical_alignment = VERTICAL_ALIGNMENT_BOTTOM - lbl.offset_left = 8 - lbl.offset_right = -8 - lbl.offset_top = -30 - lbl.offset_bottom = -6 + lbl.offset_left = 12 + lbl.offset_right = -12 + lbl.offset_top = 8 + lbl.offset_bottom = 40 lbl.mouse_filter = Control.MOUSE_FILTER_IGNORE layer.add_child(lbl) func _exit_tree() -> void: - # Leaving the world -- close the ENet connection cleanly so the server - # fires peer_disconnected and removes our player (otherwise everyone else - # keeps seeing a frozen "ghost" of us). - if _net != null and is_instance_valid(_net): - _net.disconnect_from_server() + # Backstop for any exit path that didn't go through _leave_world (e.g. the + # app quitting): close the ENet connection so the server fires + # peer_disconnected and drops our player instead of leaving a ghost. + _teardown_net() func _setup_networked() -> void: @@ -205,6 +235,9 @@ func _all_mesh_instances(node: Node, out: Array = []) -> Array: func _on_server_lost() -> void: + if _leaving: + return + _leaving = true _networked = false _show_status("Disconnected from the server. Returning to worlds…") await get_tree().create_timer(1.5).timeout diff --git a/scripts/player.gd b/scripts/player.gd index 6281eeb..3a22dee 100644 --- a/scripts/player.gd +++ b/scripts/player.gd @@ -128,11 +128,8 @@ func _unhandled_input(event: InputEvent) -> void: _update_input_direction(key) elif not key.pressed: _clear_input_direction(key) - # Escape exits the level back to the world browse list, mirroring - # the Back button pattern already used in world_list.gd/main_menu.gd. - if key.pressed and key.keycode == KEY_ESCAPE: - Input.mouse_mode = Input.MOUSE_MODE_VISIBLE - get_tree().change_scene_to_file("res://scenes/world_list.tscn") + # ESC (leave the world) is handled by game.gd, the scene root that + # owns the network connection, so the teardown is a single path. elif event is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED: _handle_mouse_look(event as InputEventMouseMotion) diff --git a/scripts/tools/gen_placeholder_avatars.gd b/scripts/tools/gen_placeholder_avatars.gd index cf02fe6..546b0d5 100644 --- a/scripts/tools/gen_placeholder_avatars.gd +++ b/scripts/tools/gen_placeholder_avatars.gd @@ -17,6 +17,8 @@ const OUT_DIR := "res://assets/avatars/" # Canonical rig -- must match avatar_loader.gd CANONICAL_BONES. Each entry: # name -> [parent_name, global_rest_position] (A-pose, arms down, Y-up, metres) +# The character faces -Z (Godot's node "forward"), so the face, toes and +# front-mounted attachments live at negative Z. const BONES := { "Root": ["", Vector3(0.00, 0.00, 0.00)], "Hips": ["Root", Vector3(0.00, 0.76, 0.00)], @@ -25,9 +27,9 @@ const BONES := { "Neck": ["Chest", Vector3(0.00, 1.50, 0.00)], "Head": ["Neck", Vector3(0.00, 1.58, 0.00)], "HeadTop": ["Head", Vector3(0.00, 2.00, 0.00)], - "Jaw": ["Head", Vector3(0.00, 1.64, 0.16)], - "LeftEye": ["Head", Vector3(0.12, 1.76, 0.27)], - "RightEye": ["Head", Vector3(-0.12, 1.76, 0.27)], + "Jaw": ["Head", Vector3(0.00, 1.64, -0.16)], + "LeftEye": ["Head", Vector3(0.12, 1.76, -0.27)], + "RightEye": ["Head", Vector3(-0.12, 1.76, -0.27)], "LeftShoulder": ["Chest", Vector3(0.42, 1.50, 0.00)], "LeftUpperArm": ["LeftShoulder", Vector3(0.60, 1.44, 0.00)], "LeftLowerArm": ["LeftUpperArm", Vector3(0.60, 1.14, 0.00)], @@ -38,10 +40,10 @@ const BONES := { "RightHand": ["RightLowerArm", Vector3(-0.60, 0.82, 0.00)], "LeftUpperLeg": ["Hips", Vector3(0.21, 0.74, 0.00)], "LeftLowerLeg": ["LeftUpperLeg", Vector3(0.21, 0.38, 0.00)], - "LeftFoot": ["LeftLowerLeg", Vector3(0.21, 0.05, 0.06)], + "LeftFoot": ["LeftLowerLeg", Vector3(0.21, 0.05, -0.06)], "RightUpperLeg": ["Hips", Vector3(-0.21, 0.74, 0.00)], "RightLowerLeg": ["RightUpperLeg", Vector3(-0.21, 0.38, 0.00)], - "RightFoot": ["RightLowerLeg", Vector3(-0.21, 0.05, 0.06)], + "RightFoot": ["RightLowerLeg", Vector3(-0.21, 0.05, -0.06)], } # Order matters: parents before children. @@ -70,10 +72,10 @@ const PARTS := [ ["RightUpperArm", Vector3(-0.58, 1.14, 0.00), Vector3(0.34, 0.78, 0.40), C_SKIN], ["LeftUpperLeg", Vector3(0.21, 0.38, 0.00), Vector3(0.40, 0.78, 0.40), C_PANTS], ["RightUpperLeg", Vector3(-0.21, 0.38, 0.00), Vector3(0.40, 0.78, 0.40), C_PANTS], - # painted-on face -- thin plates on the front of the head (+Z), rigged to Head - ["Head", Vector3(0.13, 1.78, 0.28), Vector3(0.09, 0.12, 0.02), C_FACE], - ["Head", Vector3(-0.13, 1.78, 0.28), Vector3(0.09, 0.12, 0.02), C_FACE], - ["Head", Vector3(0.00, 1.64, 0.28), Vector3(0.32, 0.05, 0.02), C_FACE], + # painted-on face -- thin plates on the front of the head (-Z), rigged to Head + ["Head", Vector3(0.13, 1.78, -0.28), Vector3(0.09, 0.12, 0.02), C_FACE], + ["Head", Vector3(-0.13, 1.78, -0.28), Vector3(0.09, 0.12, 0.02), C_FACE], + ["Head", Vector3(0.00, 1.64, -0.28), Vector3(0.32, 0.05, 0.02), C_FACE], ] # Attachments: id -> [Mesh, local_offset_from_mount_bone, colour] @@ -81,17 +83,17 @@ const PARTS := [ const ATTACHMENTS := { "head_cyber_01": ["head", Vector3(0, 0.04, 0.0), Color(0.20, 0.85, 0.95)], "hair_mohawk_01": ["mohawk", Vector3(0, 0.0, 0.0), Color(0.90, 0.20, 0.35)], - "hair_ponytail_01": ["ponytail", Vector3(0, -0.06, -0.16), Color(0.45, 0.30, 0.15)], - "face_glasses_01": ["glasses", Vector3(0, 0.12, 0.16), Color(0.10, 0.10, 0.12)], + "hair_ponytail_01": ["ponytail", Vector3(0, -0.06, 0.16), Color(0.45, 0.30, 0.15)], + "face_glasses_01": ["glasses", Vector3(0, 0.12, -0.16), Color(0.10, 0.10, 0.12)], "chest_bandit_01": ["torso", Vector3(0, -0.12, 0.0), Color(0.55, 0.20, 0.20)], - "back_backpack_01": ["box", Vector3(0, -0.06, -0.28), Color(0.30, 0.35, 0.25)], + "back_backpack_01": ["box", Vector3(0, -0.06, 0.28), Color(0.30, 0.35, 0.25)], "pants_cargo_01": ["hips", Vector3(0, -0.04, 0.0), Color(0.30, 0.35, 0.45)], "left_hand_glove_01": ["blob", Vector3(0, 0.0, 0.0), Color(0.15, 0.15, 0.18)], "right_hand_glove_01": ["blob", Vector3(0, 0.0, 0.0), Color(0.15, 0.15, 0.18)], - "left_foot_boot_01": ["boot", Vector3(0, -0.02, 0.05), Color(0.12, 0.10, 0.09)], - "right_foot_boot_01": ["boot", Vector3(0, -0.02, 0.05), Color(0.12, 0.10, 0.09)], + "left_foot_boot_01": ["boot", Vector3(0, -0.02, -0.05), Color(0.12, 0.10, 0.09)], + "right_foot_boot_01": ["boot", Vector3(0, -0.02, -0.05), Color(0.12, 0.10, 0.09)], "accessory_belt_01": ["belt", Vector3(0, 0.74, 0.0), Color(0.20, 0.18, 0.12)], - "accessory_necklace_01": ["necklace", Vector3(0, 1.34, 0.10), Color(0.85, 0.75, 0.20)], + "accessory_necklace_01": ["necklace", Vector3(0, 1.34, -0.10), Color(0.85, 0.75, 0.20)], } var _fail := 0