fix(network): prevent null multiplayer peer crashes offline/teardown and fix Nakama RPC session validation

This commit is contained in:
2026-07-10 17:36:26 +08:00
parent 2b4c9d9dcb
commit b30709de3d
84 changed files with 5291 additions and 982 deletions
+23 -17
View File
@@ -63,7 +63,7 @@ func _ready():
# Auto-start game if coming from lobby (already connected to match)
# Works for both Nakama mode and LAN direct mode (ENet).
var is_lan_connected = LobbyManager.is_lan_mode and multiplayer.has_multiplayer_peer() and multiplayer.multiplayer_peer.get_connection_status() == MultiplayerPeer.CONNECTION_CONNECTED
if (NakamaManager.is_connected_to_nakama() or is_lan_connected) and multiplayer.get_unique_id() != 0:
if multiplayer.has_multiplayer_peer() and (NakamaManager.is_connected_to_nakama() or is_lan_connected) and multiplayer.get_unique_id() != 0:
print("Coming from lobby - auto-starting game...")
await get_tree().process_frame
_auto_start_from_lobby()
@@ -221,7 +221,7 @@ func _setup_effect_elevation():
func _on_goal_count_updated(peer_id: int, count: int):
# Only update for local player
if peer_id == multiplayer.get_unique_id():
if multiplayer.has_multiplayer_peer() and peer_id == multiplayer.get_unique_id():
ui_manager.update_goal_count_label(count)
func _init_managers():
@@ -564,15 +564,16 @@ func _process(delta):
func _on_match_joined(match_id: String):
var network_panel = get_node_or_null("PauseMenu/Panel/NetworkPanel")
if network_panel:
network_panel.get_node("NetworkInfo/UniquePeerID").text = str(multiplayer.get_unique_id())
if multiplayer.is_server():
network_panel.get_node("NetworkInfo/UniquePeerID").text = str(multiplayer.get_unique_id() if multiplayer and multiplayer.has_multiplayer_peer() else 0)
if multiplayer and multiplayer.has_multiplayer_peer() and multiplayer.is_server():
network_panel.get_node("NetworkInfo/NetworkSideDisplay").text = "Server (Match: %s)" % match_id
_setup_host_game()
else:
network_panel.get_node("NetworkInfo/NetworkSideDisplay").text = "Client"
_setup_client_game()
else:
if multiplayer.is_server():
# If the UI isn't ready yet, still need to setup logic based on mode
if multiplayer.has_multiplayer_peer() and multiplayer.is_server():
_setup_host_game()
else:
_setup_client_game()
@@ -683,7 +684,9 @@ func _spawn_lobby_client_sync(peer_id: int):
func _setup_client_game():
"""Setup client when transitioning from lobby."""
var my_id = multiplayer.get_unique_id()
var my_id = 0
if multiplayer and multiplayer.has_multiplayer_peer():
my_id = multiplayer.get_unique_id()
print("Client setup - my peer ID: ", my_id)
# INITIALIZE ARENA SIZE for Stop n Go locally to prevent out-of-bounds before sync arrives
@@ -766,8 +769,8 @@ func _auto_start_from_lobby():
# Update NetworkPanel in PauseMenu (if exists)
var network_panel = get_node_or_null("PauseMenu/Panel/NetworkPanel")
if network_panel:
network_panel.get_node("NetworkInfo/UniquePeerID").text = str(multiplayer.get_unique_id())
if multiplayer.is_server():
network_panel.get_node("NetworkInfo/UniquePeerID").text = str(multiplayer.get_unique_id() if multiplayer and multiplayer.has_multiplayer_peer() else 0)
if multiplayer and multiplayer.has_multiplayer_peer() and multiplayer.is_server():
network_panel.get_node("NetworkInfo/NetworkSideDisplay").text = "Host (Match: %s)" % short_id
else:
network_panel.get_node("NetworkInfo/NetworkSideDisplay").text = "Client (Match: %s)" % short_id
@@ -1385,7 +1388,7 @@ func add_player_character(peer_id: int, is_bot: bool = false):
print("[Main] Set player %d name to %s from Lobby data" % [peer_id, p_name])
break
if peer_id == multiplayer.get_unique_id():
if multiplayer.has_multiplayer_peer() and peer_id == multiplayer.get_unique_id():
GameStateManager.local_player_character = player_character
ui_manager.set_local_player(player_character)
if touch_controls:
@@ -1608,7 +1611,7 @@ func sync_playerboard(player_id: int, new_playerboard: Array):
player.playerboard = new_playerboard.duplicate()
# Update UI for local player
if player_id == multiplayer.get_unique_id() and GameStateManager.local_player_character:
if multiplayer.has_multiplayer_peer() and player_id == multiplayer.get_unique_id() and GameStateManager.local_player_character:
ui_manager.update_playerboard_ui()
@rpc("any_peer", "call_local", "reliable")
@@ -1628,7 +1631,7 @@ func sync_playerboard_slot(player_id: int, slot_index: int, item_id: int):
return
# Update UI for local player only
if player_id == multiplayer.get_unique_id() and GameStateManager.local_player_character:
if multiplayer.has_multiplayer_peer() and player_id == multiplayer.get_unique_id() and GameStateManager.local_player_character:
ui_manager.update_playerboard_ui()
@@ -1649,7 +1652,7 @@ func verify_all_connections():
@rpc
func connection_verify(expected_players: Array):
for peer_id in expected_players:
if peer_id != multiplayer.get_unique_id() and not has_node(str(peer_id)):
if multiplayer.has_multiplayer_peer() and peer_id != multiplayer.get_unique_id() and not has_node(str(peer_id)):
rpc_id(1, "request_specific_player_data", peer_id)
@rpc("any_peer")
@@ -1721,9 +1724,9 @@ func create_specific_player(data: Dictionary):
# Force collision update
if player_character.has_node("CollisionShape3D"):
player_character.get_node("CollisionShape3D").disabled = false
# Wait for player managers to initialize (player.gd has 0.1s await in _ready)
# Check if this is the local player (client's own player)
var is_local_player = (peer_id == multiplayer.get_unique_id())
var is_local_player = multiplayer.has_multiplayer_peer() and (peer_id == multiplayer.get_unique_id())
if is_local_player and GameStateManager.local_player_character == null:
GameStateManager.local_player_character = player_character
ui_manager.set_local_player(player_character)
@@ -2326,7 +2329,7 @@ func _show_game_over_panel():
canvas_layer.add_child(panel)
# Populate data
var local_peer_id = multiplayer.get_unique_id()
var local_peer_id = multiplayer.get_unique_id() if multiplayer.has_multiplayer_peer() else 0
panel.setup(all_player_scores, local_peer_id)
# --- REPORT NAKAMA MATCH STATS ---
@@ -2351,7 +2354,8 @@ func _show_game_over_panel():
# Connect signals
panel.back_pressed.connect(_on_back_to_menu_pressed)
panel.rematch_pressed.connect(func():
LobbyManager.request_rematch.rpc(multiplayer.get_unique_id())
if multiplayer.has_multiplayer_peer():
LobbyManager.request_rematch.rpc(multiplayer.get_unique_id())
)
LobbyManager.rematch_votes_updated.connect(func(count, required):
@@ -2479,7 +2483,9 @@ func _render_leaderboard_entries(sorted_player_data: Array):
if not vbox: vbox = leaderboard_panel.get_node_or_null("VBox")
if not vbox: return
var my_id = multiplayer.get_unique_id()
var my_id = 0
if multiplayer and multiplayer.has_multiplayer_peer():
my_id = multiplayer.get_unique_id()
var my_index = -1
for i in range(sorted_player_data.size()):
if sorted_player_data[i].peer_id == my_id: