feat: implement core lobby management system with Nakama integration, player state, and game settings.

This commit is contained in:
2026-03-13 03:46:04 +08:00
parent a06e04e14b
commit 74a81425c5
7 changed files with 203 additions and 18 deletions
+106 -6
View File
@@ -29,6 +29,8 @@ func _ready():
# Connect to multiplayer signals
multiplayer.peer_connected.connect(_on_peer_connected)
multiplayer.peer_disconnected.connect(_on_peer_disconnected)
LobbyManager.host_disconnected.connect(_on_host_disconnected)
LobbyManager.game_starting.connect(_on_rematch_starting)
# Connect to Nakama signals
NakamaManager.match_joined.connect(_on_match_joined)
@@ -1200,11 +1202,73 @@ func add_newly_connected_player_character(new_peer_id: int):
func _on_peer_disconnected(peer_id: int):
if multiplayer.is_server():
GameStateManager.remove_player(peer_id)
if GameStateManager.enable_bots:
var next_id = PlayerManager.get_next_available_bot_id(GameStateManager.max_players, GameStateManager.players)
if next_id != -1:
_add_bot(next_id)
print("[Main] Peer %d disconnected. Checking for bot replacement..." % peer_id)
var player_node = get_node_or_null(str(peer_id))
if player_node and not player_node.is_bot:
# Cache state before removing
var pos = player_node.current_position
var p_score = player_node.score
var p_goals = player_node.goals.duplicate()
var p_char = player_node.selected_character
# Remove human player
GameStateManager.remove_player(peer_id)
player_node.queue_free()
# Add replacement bot
if GameStateManager.enable_bots:
var next_bot_id = PlayerManager.get_next_available_bot_id(GameStateManager.max_players, GameStateManager.players)
if next_bot_id != -1:
print("[Main] Replacing Player %d with Bot %d" % [peer_id, next_bot_id])
_replace_player_with_bot(next_bot_id, pos, p_score, p_goals, p_char)
else:
GameStateManager.remove_player(peer_id)
func _replace_player_with_bot(bot_id: int, pos: Vector2i, p_score: int, p_goals: Array, p_char: String):
"""Creates a bot to replace a disconnected player and restores their state."""
rpc("create_bot_with_state", bot_id, pos, p_score, p_goals, p_char)
@rpc("call_local")
func create_bot_with_state(bot_id: int, pos: Vector2i, p_score: int, p_goals: Array, p_char: String):
if not GameStateManager.enable_bots:
return
if has_node(str(bot_id)):
return
var bot_character = PlayerManager.create_bot(bot_id)
call_deferred("add_child", bot_character)
bot_character.add_to_group("Players", true)
bot_character.add_to_group("Bots", true)
# Apply transferred state
bot_character.current_position = pos
bot_character.score = p_score
bot_character.goals = p_goals
bot_character.selected_character = p_char
if multiplayer.is_server():
GameStateManager.add_bot(bot_id)
# Ensure position is synced
bot_character.update_player_position(pos)
func _on_host_disconnected():
"""Called when the host leaves. Returns clients to the main menu."""
print("[Main] Host disconnected. Match terminated. Returning to lobby...")
get_tree().change_scene_to_file("res://scenes/lobby.tscn")
func _on_rematch_starting():
"""Called when a rematch is triggered. Reloads the game scene."""
print("[Main] Rematch starting! Resetting state and reloading scene...")
# Reset singletons/managers that persist across scene reloads
GameStateManager.reset()
GoalManager.reset()
TurnManager.reset()
is_match_ended = false
get_tree().change_scene_to_file("res://scenes/main.tscn")
# =============================================================================
# Turn Management (RPC Handlers)
@@ -1716,6 +1780,10 @@ func sync_game_end_portal_mode(winner_id: int):
func _on_match_ended():
"""Called when the global match timer ends - show game over screen."""
if is_match_ended:
return
is_match_ended = true
print("[Main] Match ended! Showing game over screen...")
# Disable player controls
@@ -1927,7 +1995,39 @@ func _show_game_over_panel():
# Add local player entry
leaderboard_container.add_child(create_entry.call(local_player_rank))
# Back to Menu button
# 3. Rematch Option
var rematch_container = HBoxContainer.new()
rematch_container.alignment = BoxContainer.ALIGNMENT_CENTER
rematch_container.add_theme_constant_override("separation", 20)
inner_vbox.add_child(rematch_container)
var rematch_btn = Button.new()
rematch_btn.name = "RematchBtn"
rematch_btn.text = "REMATCH"
rematch_btn.custom_minimum_size = Vector2(200, 60)
rematch_btn.add_theme_font_size_override("font_size", 20)
rematch_btn.pressed.connect(func():
rematch_btn.disabled = true
rematch_btn.text = "VOTED"
LobbyManager.request_rematch.rpc(multiplayer.get_unique_id())
)
rematch_container.add_child(rematch_btn)
var rematch_label = Label.new()
rematch_label.name = "RematchVoteLabel"
rematch_label.text = "0/2"
rematch_label.add_theme_font_size_override("font_size", 24)
rematch_label.add_theme_color_override("font_color", Color(0.7, 0.7, 0.7))
rematch_container.add_child(rematch_label)
LobbyManager.rematch_votes_updated.connect(func(count, required):
if is_instance_valid(rematch_label):
rematch_label.text = "%d/%d" % [count, required]
if count >= required:
rematch_label.add_theme_color_override("font_color", Color.GREEN)
)
# 4. Back to Menu button
var back_btn = Button.new()
back_btn.name = "BackToMenuBtn"
back_btn.text = "BACK TO MAIN MENU"