feat: Introduce modular player system with dedicated managers for movement, race, input, playerboard, actions, special tiles, and powerups, along with a new main scene and documentation.
This commit is contained in:
+116
-18
@@ -267,6 +267,11 @@ func _start_game():
|
||||
# Start the goals cycle timer
|
||||
if goals_cycle_manager:
|
||||
goals_cycle_manager.start_cycle()
|
||||
|
||||
# Initialize leaderboard with all players
|
||||
if ui_manager:
|
||||
var all_players = get_tree().get_nodes_in_group("Players")
|
||||
ui_manager.initialize_leaderboard_with_players(all_players)
|
||||
|
||||
# =============================================================================
|
||||
# Player Management
|
||||
@@ -378,6 +383,9 @@ func sync_game_start(player_list: Array, is_turn_based: bool):
|
||||
GameStateManager.players = player_list
|
||||
TurnManager.turn_based_mode = is_turn_based
|
||||
GameStateManager.start_game()
|
||||
|
||||
# Initialize leaderboard for all peers (after a delay to ensure players loaded)
|
||||
call_deferred("_deferred_init_leaderboard")
|
||||
|
||||
# =============================================================================
|
||||
# UI / Action State Management
|
||||
@@ -731,15 +739,14 @@ func sync_grid_item(x: int, y: int, z: int, item: int):
|
||||
# =============================================================================
|
||||
|
||||
func _on_timer_updated(time_remaining: float):
|
||||
# Update timer display on all player goal panels
|
||||
var time_text = "%02d:%02d" % [int(time_remaining) / 60, int(time_remaining) % 60]
|
||||
# Update standalone timer display
|
||||
var time_text = str(int(time_remaining))
|
||||
|
||||
for i in range($AllPlayerGoals.get_child_count()):
|
||||
var panel = $AllPlayerGoals.get_child(i)
|
||||
if panel.visible:
|
||||
var timer_label = panel.get_node_or_null("TimerLabel")
|
||||
if timer_label:
|
||||
timer_label.text = time_text
|
||||
var timer_panel = get_node_or_null("GoalsTimer")
|
||||
if timer_panel:
|
||||
var timer_label = timer_panel.get_node_or_null("VBox/TimerLabel")
|
||||
if timer_label:
|
||||
timer_label.text = time_text
|
||||
|
||||
func _on_score_updated(peer_id: int, new_score: int):
|
||||
# Update player's score display
|
||||
@@ -751,26 +758,117 @@ func _on_score_updated(peer_id: int, new_score: int):
|
||||
_update_leaderboard_display()
|
||||
|
||||
func _on_leaderboard_updated(sorted_scores: Array):
|
||||
# Update the leaderboard panel
|
||||
# Update the leaderboard panel locally
|
||||
_update_leaderboard_display()
|
||||
|
||||
# Server broadcasts updated leaderboard to all clients
|
||||
if multiplayer.is_server():
|
||||
var player_data = []
|
||||
for p in get_tree().get_nodes_in_group("Players"):
|
||||
player_data.append({
|
||||
"peer_id": p.get_multiplayer_authority(),
|
||||
"name": p.name,
|
||||
"score": goals_cycle_manager.get_player_score(p.get_multiplayer_authority()) if goals_cycle_manager else 0
|
||||
})
|
||||
rpc("sync_leaderboard_data", player_data)
|
||||
|
||||
func _deferred_init_leaderboard():
|
||||
"""Initialize leaderboard after a delay to ensure all players are loaded."""
|
||||
# Longer delay ensures players are synced
|
||||
await get_tree().create_timer(1.5).timeout
|
||||
|
||||
# Request leaderboard sync from server for accurate data
|
||||
if not multiplayer.is_server():
|
||||
rpc_id(1, "request_leaderboard_sync")
|
||||
else:
|
||||
# Server can update directly
|
||||
_update_leaderboard_display()
|
||||
|
||||
@rpc("any_peer")
|
||||
func request_leaderboard_sync():
|
||||
"""Client requests leaderboard data from server."""
|
||||
if multiplayer.is_server():
|
||||
var sender_id = multiplayer.get_remote_sender_id()
|
||||
# Build player list with peer_ids and names
|
||||
var player_data = []
|
||||
for p in get_tree().get_nodes_in_group("Players"):
|
||||
player_data.append({
|
||||
"peer_id": p.get_multiplayer_authority(),
|
||||
"name": p.name,
|
||||
"score": goals_cycle_manager.get_player_score(p.get_multiplayer_authority()) if goals_cycle_manager else 0
|
||||
})
|
||||
rpc_id(sender_id, "sync_leaderboard_data", player_data)
|
||||
|
||||
@rpc("authority", "call_local", "reliable")
|
||||
func sync_leaderboard_data(player_data: Array):
|
||||
"""Receive leaderboard data from server and update UI."""
|
||||
var leaderboard_panel = get_node_or_null("LeaderboardPanel")
|
||||
if not leaderboard_panel:
|
||||
return
|
||||
|
||||
var vbox = leaderboard_panel.get_node_or_null("MarginContainer/VBox")
|
||||
if not vbox:
|
||||
return
|
||||
|
||||
# Sort by score descending
|
||||
player_data.sort_custom(func(a, b): return a.score > b.score)
|
||||
|
||||
# Update entries
|
||||
for i in range(4):
|
||||
var entry = vbox.get_node_or_null("Entry" + str(i + 1))
|
||||
if not entry:
|
||||
continue
|
||||
|
||||
if i < player_data.size():
|
||||
var data = player_data[i]
|
||||
var rank_label = entry.get_node_or_null("RankLabel")
|
||||
var name_label = entry.get_node_or_null("NameLabel")
|
||||
var score_label = entry.get_node_or_null("ScoreLabel")
|
||||
|
||||
if rank_label:
|
||||
rank_label.text = _get_ordinal(i + 1)
|
||||
if name_label:
|
||||
name_label.text = "Player " + str(data.name)
|
||||
if score_label:
|
||||
score_label.text = str(data.score)
|
||||
|
||||
entry.visible = true
|
||||
else:
|
||||
entry.visible = false
|
||||
|
||||
func _update_leaderboard_display():
|
||||
var leaderboard_panel = get_node_or_null("LeaderboardPanel")
|
||||
if not leaderboard_panel:
|
||||
return
|
||||
|
||||
var sorted_scores = goals_cycle_manager.get_leaderboard() if goals_cycle_manager else []
|
||||
# Try both possible paths for vbox
|
||||
var vbox = leaderboard_panel.get_node_or_null("MarginContainer/VBox")
|
||||
if not vbox:
|
||||
vbox = leaderboard_panel.get_node_or_null("VBox")
|
||||
if not vbox:
|
||||
return
|
||||
|
||||
# Get player names and update entries
|
||||
# Get all players in game
|
||||
var all_players = get_tree().get_nodes_in_group("Players")
|
||||
|
||||
# Build scores array with all players
|
||||
var player_data = []
|
||||
for p in all_players:
|
||||
var peer_id = p.get_multiplayer_authority()
|
||||
var score = goals_cycle_manager.get_player_score(peer_id) if goals_cycle_manager else 0
|
||||
player_data.append({"peer_id": peer_id, "name": p.name, "score": score})
|
||||
|
||||
# Sort by score descending
|
||||
player_data.sort_custom(func(a, b): return a.score > b.score)
|
||||
|
||||
# Update entries
|
||||
for i in range(4): # Max 4 entries
|
||||
var entry = leaderboard_panel.get_node_or_null("Entry" + str(i + 1))
|
||||
var entry = vbox.get_node_or_null("Entry" + str(i + 1))
|
||||
if not entry:
|
||||
continue
|
||||
|
||||
if i < sorted_scores.size():
|
||||
var score_data = sorted_scores[i]
|
||||
var player = get_node_or_null(str(score_data.peer_id))
|
||||
var player_name = player.name if player else str(score_data.peer_id)
|
||||
if i < player_data.size():
|
||||
var data = player_data[i]
|
||||
|
||||
var rank_label = entry.get_node_or_null("RankLabel")
|
||||
var name_label = entry.get_node_or_null("NameLabel")
|
||||
@@ -779,9 +877,9 @@ func _update_leaderboard_display():
|
||||
if rank_label:
|
||||
rank_label.text = _get_ordinal(i + 1)
|
||||
if name_label:
|
||||
name_label.text = player_name
|
||||
name_label.text = str(data.name)
|
||||
if score_label:
|
||||
score_label.text = str(score_data.score)
|
||||
score_label.text = str(data.score)
|
||||
|
||||
entry.visible = true
|
||||
else:
|
||||
|
||||
@@ -9324,6 +9324,185 @@ theme_override_constants/margin_bottom = 5
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 4
|
||||
|
||||
[node name="PowerUpBar" type="PanelContainer" parent="."]
|
||||
anchors_preset = 5
|
||||
anchor_left = 0.5
|
||||
anchor_right = 0.5
|
||||
offset_left = -110.0
|
||||
offset_top = 125.0
|
||||
offset_right = 110.0
|
||||
offset_bottom = 161.0
|
||||
grow_horizontal = 2
|
||||
|
||||
[node name="HBox" type="HBoxContainer" parent="PowerUpBar"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 4
|
||||
|
||||
[node name="PowerLabel" type="Label" parent="PowerUpBar/HBox"]
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 14
|
||||
text = "POWER "
|
||||
|
||||
[node name="Segment0" type="Panel" parent="PowerUpBar/HBox"]
|
||||
custom_minimum_size = Vector2(36, 20)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Segment1" type="Panel" parent="PowerUpBar/HBox"]
|
||||
custom_minimum_size = Vector2(36, 20)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Segment2" type="Panel" parent="PowerUpBar/HBox"]
|
||||
custom_minimum_size = Vector2(36, 20)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Segment3" type="Panel" parent="PowerUpBar/HBox"]
|
||||
custom_minimum_size = Vector2(36, 20)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="LeaderboardPanel" type="PanelContainer" parent="."]
|
||||
anchors_preset = 1
|
||||
anchor_left = 1.0
|
||||
anchor_right = 1.0
|
||||
offset_left = -210.0
|
||||
offset_top = 80.0
|
||||
offset_right = -10.0
|
||||
offset_bottom = 280.0
|
||||
grow_horizontal = 0
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="LeaderboardPanel"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 8
|
||||
theme_override_constants/margin_top = 8
|
||||
theme_override_constants/margin_right = 8
|
||||
theme_override_constants/margin_bottom = 8
|
||||
|
||||
[node name="VBox" type="VBoxContainer" parent="LeaderboardPanel/MarginContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 4
|
||||
|
||||
[node name="Title" type="Label" parent="LeaderboardPanel/MarginContainer/VBox"]
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 16
|
||||
text = "🏆 LEADERBOARD"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="Separator" type="HSeparator" parent="LeaderboardPanel/MarginContainer/VBox"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Entry1" type="HBoxContainer" parent="LeaderboardPanel/MarginContainer/VBox"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="RankLabel" type="Label" parent="LeaderboardPanel/MarginContainer/VBox/Entry1"]
|
||||
custom_minimum_size = Vector2(35, 0)
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 14
|
||||
text = "1st"
|
||||
|
||||
[node name="NameLabel" type="Label" parent="LeaderboardPanel/MarginContainer/VBox/Entry1"]
|
||||
custom_minimum_size = Vector2(100, 0)
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 14
|
||||
text = "Player 1"
|
||||
clip_text = true
|
||||
|
||||
[node name="ScoreLabel" type="Label" parent="LeaderboardPanel/MarginContainer/VBox/Entry1"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_font_sizes/font_size = 14
|
||||
text = "0"
|
||||
horizontal_alignment = 2
|
||||
|
||||
[node name="Entry2" type="HBoxContainer" parent="LeaderboardPanel/MarginContainer/VBox"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="RankLabel" type="Label" parent="LeaderboardPanel/MarginContainer/VBox/Entry2"]
|
||||
custom_minimum_size = Vector2(35, 0)
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 14
|
||||
text = "2nd"
|
||||
|
||||
[node name="NameLabel" type="Label" parent="LeaderboardPanel/MarginContainer/VBox/Entry2"]
|
||||
custom_minimum_size = Vector2(100, 0)
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 14
|
||||
text = "Player 2"
|
||||
clip_text = true
|
||||
|
||||
[node name="ScoreLabel" type="Label" parent="LeaderboardPanel/MarginContainer/VBox/Entry2"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_font_sizes/font_size = 14
|
||||
text = "0"
|
||||
horizontal_alignment = 2
|
||||
|
||||
[node name="Entry3" type="HBoxContainer" parent="LeaderboardPanel/MarginContainer/VBox"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="RankLabel" type="Label" parent="LeaderboardPanel/MarginContainer/VBox/Entry3"]
|
||||
custom_minimum_size = Vector2(35, 0)
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 14
|
||||
text = "3rd"
|
||||
|
||||
[node name="NameLabel" type="Label" parent="LeaderboardPanel/MarginContainer/VBox/Entry3"]
|
||||
custom_minimum_size = Vector2(100, 0)
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 14
|
||||
text = "Player 3"
|
||||
clip_text = true
|
||||
|
||||
[node name="ScoreLabel" type="Label" parent="LeaderboardPanel/MarginContainer/VBox/Entry3"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_font_sizes/font_size = 14
|
||||
text = "0"
|
||||
horizontal_alignment = 2
|
||||
|
||||
[node name="Entry4" type="HBoxContainer" parent="LeaderboardPanel/MarginContainer/VBox"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="RankLabel" type="Label" parent="LeaderboardPanel/MarginContainer/VBox/Entry4"]
|
||||
custom_minimum_size = Vector2(35, 0)
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 14
|
||||
text = "4th"
|
||||
|
||||
[node name="NameLabel" type="Label" parent="LeaderboardPanel/MarginContainer/VBox/Entry4"]
|
||||
custom_minimum_size = Vector2(100, 0)
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 14
|
||||
text = "Player 4"
|
||||
clip_text = true
|
||||
|
||||
[node name="ScoreLabel" type="Label" parent="LeaderboardPanel/MarginContainer/VBox/Entry4"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_font_sizes/font_size = 14
|
||||
text = "0"
|
||||
horizontal_alignment = 2
|
||||
|
||||
[node name="GoalsTimer" type="PanelContainer" parent="."]
|
||||
offset_left = 20.0
|
||||
offset_top = 20.0
|
||||
offset_right = 120.0
|
||||
offset_bottom = 90.0
|
||||
|
||||
[node name="VBox" type="VBoxContainer" parent="GoalsTimer"]
|
||||
layout_mode = 2
|
||||
alignment = 1
|
||||
|
||||
[node name="TimerLabel" type="Label" parent="GoalsTimer/VBox"]
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 32
|
||||
text = "60"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="SuffixLabel" type="Label" parent="GoalsTimer/VBox"]
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 12
|
||||
text = "seconds"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[connection signal="pressed" from="Menu/Host" to="." method="_on_host_pressed"]
|
||||
[connection signal="pressed" from="Menu/Join" to="." method="_on_join_pressed"]
|
||||
[connection signal="text_submitted" from="MessageInput" to="." method="_on_message_input_text_submitted"]
|
||||
|
||||
Reference in New Issue
Block a user