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:
@@ -0,0 +1,68 @@
|
|||||||
|
# [ ADT's Report ]
|
||||||
|
## Date: 2025-12-16
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ main.tscn - Changes
|
||||||
|
- Added **PowerUpBar** GUI panel (center-top, 4 battery segments)
|
||||||
|
- Added **LeaderboardPanel** GUI panel (right side, 4 player entries)
|
||||||
|
- Added **GoalsTimer** GUI panel (top-left, standalone timer with background)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ main.gd - Changes
|
||||||
|
- Updated `_on_timer_updated()` to use standalone GoalsTimer
|
||||||
|
- Added `_deferred_init_leaderboard()` for delayed leaderboard initialization
|
||||||
|
- Added `request_leaderboard_sync()` RPC for client-server leaderboard sync
|
||||||
|
- Added `sync_leaderboard_data()` RPC to receive leaderboard from server
|
||||||
|
- Updated `_on_leaderboard_updated()` to broadcast leaderboard to all clients
|
||||||
|
- Updated `sync_game_start()` to call deferred leaderboard init
|
||||||
|
- Added `initialize_leaderboard_with_players()` call in `_start_game()`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ ui_manager.gd - Changes
|
||||||
|
- Changed `setup_powerup_bar_ui()` to get node reference instead of creating
|
||||||
|
- Changed `setup_leaderboard_ui()` to get node reference instead of creating
|
||||||
|
- Added `_connect_powerup_manager_deferred()` with 0.3s delay for signal connection
|
||||||
|
- Updated `setup_timer_labels()` to style standalone GoalsTimer
|
||||||
|
- Fixed `powerup_bar` variable type from `HBoxContainer` to `PanelContainer`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ playerboard_manager.gd - Changes
|
||||||
|
- Added `_normalize_tile()` helper (converts holo tiles 11-14 → 7-10)
|
||||||
|
- Applied normalization to `find_best_goal_slot_for_item()`
|
||||||
|
- Applied normalization to `auto_put_item()` for junk detection
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ player_race_manager.gd - Changes
|
||||||
|
- Added `_normalize_tile()` helper for holo tile equivalence
|
||||||
|
- Applied normalization to `check_3x3_section()` for goal completion detection
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ goals_cycle_manager.gd - Changes
|
||||||
|
- Added `_initialize_player_scores()` to init all players with score 0 at cycle start
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary of Features
|
||||||
|
|
||||||
|
### 1. Power-up Bar & Leaderboard GUI
|
||||||
|
- Moved from script-instantiated to scene-based nodes
|
||||||
|
- Power-up bar displays 4 battery segments (12 points max)
|
||||||
|
- Leaderboard shows 4 player entries with rank, name, score
|
||||||
|
|
||||||
|
### 2. Standalone Timer
|
||||||
|
- Timer moved from PlayerGoals panels to top-left corner
|
||||||
|
- Styled with gold border and large font
|
||||||
|
|
||||||
|
### 3. Holo Tile Equivalence
|
||||||
|
- Holo tiles (11-14) now treated same as normal tiles (7-10)
|
||||||
|
- Works for auto-grab, auto-put, and goal completion
|
||||||
|
|
||||||
|
### 4. Leaderboard Client Sync Fix
|
||||||
|
- Server now broadcasts leaderboard data via RPC
|
||||||
|
- Clients request and receive authoritative player list from server
|
||||||
+116
-18
@@ -267,6 +267,11 @@ func _start_game():
|
|||||||
# Start the goals cycle timer
|
# Start the goals cycle timer
|
||||||
if goals_cycle_manager:
|
if goals_cycle_manager:
|
||||||
goals_cycle_manager.start_cycle()
|
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
|
# Player Management
|
||||||
@@ -378,6 +383,9 @@ func sync_game_start(player_list: Array, is_turn_based: bool):
|
|||||||
GameStateManager.players = player_list
|
GameStateManager.players = player_list
|
||||||
TurnManager.turn_based_mode = is_turn_based
|
TurnManager.turn_based_mode = is_turn_based
|
||||||
GameStateManager.start_game()
|
GameStateManager.start_game()
|
||||||
|
|
||||||
|
# Initialize leaderboard for all peers (after a delay to ensure players loaded)
|
||||||
|
call_deferred("_deferred_init_leaderboard")
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# UI / Action State Management
|
# 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):
|
func _on_timer_updated(time_remaining: float):
|
||||||
# Update timer display on all player goal panels
|
# Update standalone timer display
|
||||||
var time_text = "%02d:%02d" % [int(time_remaining) / 60, int(time_remaining) % 60]
|
var time_text = str(int(time_remaining))
|
||||||
|
|
||||||
for i in range($AllPlayerGoals.get_child_count()):
|
var timer_panel = get_node_or_null("GoalsTimer")
|
||||||
var panel = $AllPlayerGoals.get_child(i)
|
if timer_panel:
|
||||||
if panel.visible:
|
var timer_label = timer_panel.get_node_or_null("VBox/TimerLabel")
|
||||||
var timer_label = panel.get_node_or_null("TimerLabel")
|
if timer_label:
|
||||||
if timer_label:
|
timer_label.text = time_text
|
||||||
timer_label.text = time_text
|
|
||||||
|
|
||||||
func _on_score_updated(peer_id: int, new_score: int):
|
func _on_score_updated(peer_id: int, new_score: int):
|
||||||
# Update player's score display
|
# Update player's score display
|
||||||
@@ -751,26 +758,117 @@ func _on_score_updated(peer_id: int, new_score: int):
|
|||||||
_update_leaderboard_display()
|
_update_leaderboard_display()
|
||||||
|
|
||||||
func _on_leaderboard_updated(sorted_scores: Array):
|
func _on_leaderboard_updated(sorted_scores: Array):
|
||||||
# Update the leaderboard panel
|
# Update the leaderboard panel locally
|
||||||
_update_leaderboard_display()
|
_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():
|
func _update_leaderboard_display():
|
||||||
var leaderboard_panel = get_node_or_null("LeaderboardPanel")
|
var leaderboard_panel = get_node_or_null("LeaderboardPanel")
|
||||||
if not leaderboard_panel:
|
if not leaderboard_panel:
|
||||||
return
|
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
|
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:
|
if not entry:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if i < sorted_scores.size():
|
if i < player_data.size():
|
||||||
var score_data = sorted_scores[i]
|
var data = player_data[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)
|
|
||||||
|
|
||||||
var rank_label = entry.get_node_or_null("RankLabel")
|
var rank_label = entry.get_node_or_null("RankLabel")
|
||||||
var name_label = entry.get_node_or_null("NameLabel")
|
var name_label = entry.get_node_or_null("NameLabel")
|
||||||
@@ -779,9 +877,9 @@ func _update_leaderboard_display():
|
|||||||
if rank_label:
|
if rank_label:
|
||||||
rank_label.text = _get_ordinal(i + 1)
|
rank_label.text = _get_ordinal(i + 1)
|
||||||
if name_label:
|
if name_label:
|
||||||
name_label.text = player_name
|
name_label.text = str(data.name)
|
||||||
if score_label:
|
if score_label:
|
||||||
score_label.text = str(score_data.score)
|
score_label.text = str(data.score)
|
||||||
|
|
||||||
entry.visible = true
|
entry.visible = true
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -9324,6 +9324,185 @@ theme_override_constants/margin_bottom = 5
|
|||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
theme_override_constants/separation = 4
|
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/Host" to="." method="_on_host_pressed"]
|
||||||
[connection signal="pressed" from="Menu/Join" to="." method="_on_join_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"]
|
[connection signal="text_submitted" from="MessageInput" to="." method="_on_message_input_text_submitted"]
|
||||||
|
|||||||
@@ -55,8 +55,19 @@ func start_cycle():
|
|||||||
emit_signal("cycle_started")
|
emit_signal("cycle_started")
|
||||||
|
|
||||||
if multiplayer.is_server():
|
if multiplayer.is_server():
|
||||||
|
# Initialize scores for all connected players
|
||||||
|
_initialize_player_scores()
|
||||||
rpc("sync_cycle_start")
|
rpc("sync_cycle_start")
|
||||||
|
|
||||||
|
func _initialize_player_scores():
|
||||||
|
"""Initialize scores for all connected players to 0."""
|
||||||
|
var all_players = get_tree().get_nodes_in_group("Players")
|
||||||
|
for player in all_players:
|
||||||
|
var peer_id = player.get_multiplayer_authority()
|
||||||
|
if not player_scores.has(peer_id):
|
||||||
|
player_scores[peer_id] = 0
|
||||||
|
_update_leaderboard()
|
||||||
|
|
||||||
@rpc("authority", "call_local", "reliable")
|
@rpc("authority", "call_local", "reliable")
|
||||||
func sync_cycle_start():
|
func sync_cycle_start():
|
||||||
current_cycle_timer = CYCLE_DURATION
|
current_cycle_timer = CYCLE_DURATION
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://c6ip5g2kja65e
|
||||||
@@ -34,6 +34,12 @@ func get_ordinal_string(number: int) -> String:
|
|||||||
# Goal Pattern Matching (Core Functionality)
|
# Goal Pattern Matching (Core Functionality)
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
|
func _normalize_tile(tile: int) -> int:
|
||||||
|
"""Convert holo tiles (11-14) to normal tiles (7-10) for goal comparison."""
|
||||||
|
if tile >= 11 and tile <= 14:
|
||||||
|
return tile - 4
|
||||||
|
return tile
|
||||||
|
|
||||||
func check_3x3_section(board: Array, goals_pattern: Array, start_row: int, start_col: int) -> bool:
|
func check_3x3_section(board: Array, goals_pattern: Array, start_row: int, start_col: int) -> bool:
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
for j in range(3):
|
for j in range(3):
|
||||||
@@ -45,7 +51,9 @@ func check_3x3_section(board: Array, goals_pattern: Array, start_row: int, start
|
|||||||
for i in range(3):
|
for i in range(3):
|
||||||
for j in range(3):
|
for j in range(3):
|
||||||
if goals_pattern[i][j] != -1:
|
if goals_pattern[i][j] != -1:
|
||||||
if board[start_row + i][start_col + j] != goals_pattern[i][j]:
|
# Normalize board value to treat holo tiles (11-14) same as normal (7-10)
|
||||||
|
var normalized_board = _normalize_tile(board[start_row + i][start_col + j])
|
||||||
|
if normalized_board != goals_pattern[i][j]:
|
||||||
return false
|
return false
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,12 @@ func initialize(p_player: Node3D, p_gridmap: Node):
|
|||||||
player = p_player
|
player = p_player
|
||||||
enhanced_gridmap = p_gridmap
|
enhanced_gridmap = p_gridmap
|
||||||
|
|
||||||
|
func _normalize_tile(tile: int) -> int:
|
||||||
|
"""Convert holo tiles (11-14) to normal tiles (7-10) for goal comparison."""
|
||||||
|
if tile >= 11 and tile <= 14:
|
||||||
|
return tile - 4
|
||||||
|
return tile
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# GRAB Operations
|
# GRAB Operations
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -230,8 +236,10 @@ func auto_put_item() -> bool:
|
|||||||
if current_item == -1:
|
if current_item == -1:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Normalize item for goal comparison (holo tiles 11-14 = normal tiles 7-10)
|
||||||
|
var normalized_item = _normalize_tile(current_item)
|
||||||
# Item is not in goals at all → definitely junk, put this first
|
# Item is not in goals at all → definitely junk, put this first
|
||||||
if not current_item in player.goals:
|
if not normalized_item in player.goals:
|
||||||
put_slot = i
|
put_slot = i
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -253,8 +261,10 @@ func auto_put_item() -> bool:
|
|||||||
var goal_row = row - 1
|
var goal_row = row - 1
|
||||||
var goal_col = col - 1
|
var goal_col = col - 1
|
||||||
var expected_goal = player.goals[goal_row * 3 + goal_col]
|
var expected_goal = player.goals[goal_row * 3 + goal_col]
|
||||||
|
# Normalize for comparison (holo tiles 11-14 = normal tiles 7-10)
|
||||||
|
var normalized_board = _normalize_tile(board_item)
|
||||||
# If the item doesn't match what should be in this goal position, it's misplaced
|
# If the item doesn't match what should be in this goal position, it's misplaced
|
||||||
if board_item != expected_goal and expected_goal != -1:
|
if normalized_board != expected_goal and expected_goal != -1:
|
||||||
put_slot = i
|
put_slot = i
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -390,6 +400,9 @@ func find_best_goal_slot_for_item(item: int) -> int:
|
|||||||
if item == -1:
|
if item == -1:
|
||||||
return -1
|
return -1
|
||||||
|
|
||||||
|
# Normalize item - treat holo tiles (11-14) the same as normal tiles (7-10)
|
||||||
|
var normalized_item = _normalize_tile(item)
|
||||||
|
|
||||||
# Convert goals to 2D (3x3)
|
# Convert goals to 2D (3x3)
|
||||||
var goals_2d = []
|
var goals_2d = []
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
@@ -401,7 +414,7 @@ func find_best_goal_slot_for_item(item: int) -> int:
|
|||||||
# Search for where this item should go in the central 3x3 (mapped to 5x5 board)
|
# Search for where this item should go in the central 3x3 (mapped to 5x5 board)
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
for j in range(3):
|
for j in range(3):
|
||||||
if goals_2d[i][j] == item:
|
if goals_2d[i][j] == normalized_item:
|
||||||
var board_row = i + 1 # offset to center in 5x5
|
var board_row = i + 1 # offset to center in 5x5
|
||||||
var board_col = j + 1
|
var board_col = j + 1
|
||||||
var slot_index = board_row * 5 + board_col
|
var slot_index = board_row * 5 + board_col
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://dhm40qesad4cm
|
||||||
+93
-124
@@ -137,60 +137,52 @@ func update_button_states():
|
|||||||
func set_local_player(player):
|
func set_local_player(player):
|
||||||
local_player_character = player
|
local_player_character = player
|
||||||
|
|
||||||
# Connect to powerup signals
|
# Connect to powerup signals with deferred call (manager needs time to initialize)
|
||||||
|
_connect_powerup_manager_deferred(player)
|
||||||
|
|
||||||
|
func _connect_powerup_manager_deferred(player):
|
||||||
|
"""Wait for PowerUpManager to be initialized before connecting."""
|
||||||
|
await player.get_tree().create_timer(0.3).timeout
|
||||||
|
|
||||||
var powerup_manager = player.get_node_or_null("PowerUpManager")
|
var powerup_manager = player.get_node_or_null("PowerUpManager")
|
||||||
if powerup_manager:
|
if powerup_manager:
|
||||||
powerup_manager.points_changed.connect(_on_powerup_points_changed)
|
if not powerup_manager.points_changed.is_connected(_on_powerup_points_changed):
|
||||||
|
powerup_manager.points_changed.connect(_on_powerup_points_changed)
|
||||||
|
# Initialize bar with current values
|
||||||
|
update_powerup_bar(powerup_manager.get_points(), powerup_manager.get_max_points())
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Power-Up Bar UI (Battery Style)
|
# Power-Up Bar UI (Battery Style)
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
var powerup_bar: HBoxContainer
|
var powerup_bar: PanelContainer
|
||||||
var powerup_segments: Array = []
|
var powerup_segments: Array = []
|
||||||
|
|
||||||
func setup_powerup_bar_ui(main_node):
|
func setup_powerup_bar_ui(main_node):
|
||||||
"""Create battery-style power-up bar with 4 segments."""
|
"""Get reference to existing PowerUpBar in scene and cache segment references."""
|
||||||
var parent = main_node.get_node_or_null("PlayerboardUI")
|
powerup_bar = main_node.get_node_or_null("PowerUpBar")
|
||||||
if not parent:
|
if not powerup_bar:
|
||||||
parent = main_node
|
push_warning("PowerUpBar node not found in scene")
|
||||||
|
return
|
||||||
|
|
||||||
# Create container
|
# Get segment references from scene
|
||||||
powerup_bar = HBoxContainer.new()
|
|
||||||
powerup_bar.name = "PowerUpBar"
|
|
||||||
powerup_bar.custom_minimum_size = Vector2(200, 30)
|
|
||||||
|
|
||||||
# Position above playerboard
|
|
||||||
powerup_bar.position = Vector2(0, -40)
|
|
||||||
|
|
||||||
# Create label
|
|
||||||
var label = Label.new()
|
|
||||||
label.text = "POWER: "
|
|
||||||
label.add_theme_font_size_override("font_size", 14)
|
|
||||||
powerup_bar.add_child(label)
|
|
||||||
|
|
||||||
# Create 4 battery segments
|
|
||||||
powerup_segments.clear()
|
powerup_segments.clear()
|
||||||
for i in range(4):
|
var hbox = powerup_bar.get_node_or_null("HBox")
|
||||||
var segment = Panel.new()
|
if hbox:
|
||||||
segment.custom_minimum_size = Vector2(40, 24)
|
for i in range(4):
|
||||||
segment.name = "Segment" + str(i)
|
var segment = hbox.get_node_or_null("Segment" + str(i))
|
||||||
|
if segment:
|
||||||
# Style the segment
|
# Apply initial empty style
|
||||||
var style = StyleBoxFlat.new()
|
var style = StyleBoxFlat.new()
|
||||||
style.bg_color = Color(0.2, 0.2, 0.2, 0.8) # Dark empty
|
style.bg_color = Color(0.15, 0.15, 0.15, 1.0)
|
||||||
style.border_color = Color(0.4, 0.8, 0.4, 1.0) # Green border
|
style.border_color = Color(0.3, 0.7, 0.3, 1.0)
|
||||||
style.set_border_width_all(2)
|
style.set_border_width_all(2)
|
||||||
style.corner_radius_top_left = 4 if i == 0 else 0
|
style.corner_radius_top_left = 4 if i == 0 else 0
|
||||||
style.corner_radius_bottom_left = 4 if i == 0 else 0
|
style.corner_radius_bottom_left = 4 if i == 0 else 0
|
||||||
style.corner_radius_top_right = 4 if i == 3 else 0
|
style.corner_radius_top_right = 4 if i == 3 else 0
|
||||||
style.corner_radius_bottom_right = 4 if i == 3 else 0
|
style.corner_radius_bottom_right = 4 if i == 3 else 0
|
||||||
segment.add_theme_stylebox_override("panel", style)
|
segment.add_theme_stylebox_override("panel", style)
|
||||||
|
powerup_segments.append(segment)
|
||||||
powerup_bar.add_child(segment)
|
|
||||||
powerup_segments.append(segment)
|
|
||||||
|
|
||||||
parent.add_child(powerup_bar)
|
|
||||||
|
|
||||||
func update_powerup_bar(current_points: int, max_points: int):
|
func update_powerup_bar(current_points: int, max_points: int):
|
||||||
"""Update battery segments based on current power-up points."""
|
"""Update battery segments based on current power-up points."""
|
||||||
@@ -219,75 +211,22 @@ func _on_powerup_points_changed(current: int, max_points: int):
|
|||||||
var leaderboard_panel: PanelContainer
|
var leaderboard_panel: PanelContainer
|
||||||
|
|
||||||
func setup_leaderboard_ui(main_node):
|
func setup_leaderboard_ui(main_node):
|
||||||
"""Create leaderboard panel on right side of screen."""
|
"""Get reference to existing LeaderboardPanel in scene."""
|
||||||
leaderboard_panel = PanelContainer.new()
|
leaderboard_panel = main_node.get_node_or_null("LeaderboardPanel")
|
||||||
leaderboard_panel.name = "LeaderboardPanel"
|
if not leaderboard_panel:
|
||||||
leaderboard_panel.custom_minimum_size = Vector2(180, 180)
|
push_warning("LeaderboardPanel node not found in scene")
|
||||||
|
return
|
||||||
|
|
||||||
# Position on right side
|
# Apply styling to the panel
|
||||||
leaderboard_panel.set_anchors_preset(Control.PRESET_TOP_RIGHT)
|
|
||||||
leaderboard_panel.position = Vector2(-200, 100)
|
|
||||||
|
|
||||||
# Style the panel
|
|
||||||
var style = StyleBoxFlat.new()
|
var style = StyleBoxFlat.new()
|
||||||
style.bg_color = Color(0.1, 0.1, 0.1, 0.85)
|
style.bg_color = Color(0.08, 0.08, 0.12, 0.92)
|
||||||
style.border_color = Color(0.8, 0.7, 0.2, 1.0) # Gold border
|
style.border_color = Color(0.85, 0.75, 0.25, 1.0) # Gold border
|
||||||
style.set_border_width_all(2)
|
style.set_border_width_all(2)
|
||||||
style.corner_radius_top_left = 8
|
style.corner_radius_top_left = 8
|
||||||
style.corner_radius_top_right = 8
|
style.corner_radius_top_right = 8
|
||||||
style.corner_radius_bottom_left = 8
|
style.corner_radius_bottom_left = 8
|
||||||
style.corner_radius_bottom_right = 8
|
style.corner_radius_bottom_right = 8
|
||||||
leaderboard_panel.add_theme_stylebox_override("panel", style)
|
leaderboard_panel.add_theme_stylebox_override("panel", style)
|
||||||
|
|
||||||
var vbox = VBoxContainer.new()
|
|
||||||
vbox.name = "VBox"
|
|
||||||
|
|
||||||
# Title
|
|
||||||
var title = Label.new()
|
|
||||||
title.text = "LEADERBOARD"
|
|
||||||
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
||||||
title.add_theme_font_size_override("font_size", 16)
|
|
||||||
title.add_theme_color_override("font_color", Color(0.9, 0.8, 0.2))
|
|
||||||
vbox.add_child(title)
|
|
||||||
|
|
||||||
# Separator
|
|
||||||
var sep = HSeparator.new()
|
|
||||||
vbox.add_child(sep)
|
|
||||||
|
|
||||||
# Create 4 player entries
|
|
||||||
for i in range(4):
|
|
||||||
var entry = HBoxContainer.new()
|
|
||||||
entry.name = "Entry" + str(i + 1)
|
|
||||||
entry.visible = false
|
|
||||||
|
|
||||||
var rank_label = Label.new()
|
|
||||||
rank_label.name = "RankLabel"
|
|
||||||
rank_label.custom_minimum_size = Vector2(40, 0)
|
|
||||||
rank_label.text = _get_rank_text(i + 1)
|
|
||||||
rank_label.add_theme_font_size_override("font_size", 14)
|
|
||||||
entry.add_child(rank_label)
|
|
||||||
|
|
||||||
var name_label = Label.new()
|
|
||||||
name_label.name = "NameLabel"
|
|
||||||
name_label.custom_minimum_size = Vector2(80, 0)
|
|
||||||
name_label.clip_text = true
|
|
||||||
name_label.text = "---"
|
|
||||||
name_label.add_theme_font_size_override("font_size", 14)
|
|
||||||
entry.add_child(name_label)
|
|
||||||
|
|
||||||
var score_label = Label.new()
|
|
||||||
score_label.name = "ScoreLabel"
|
|
||||||
score_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
|
||||||
score_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
||||||
score_label.text = "0"
|
|
||||||
score_label.add_theme_font_size_override("font_size", 14)
|
|
||||||
score_label.add_theme_color_override("font_color", Color(0.8, 1.0, 0.8))
|
|
||||||
entry.add_child(score_label)
|
|
||||||
|
|
||||||
vbox.add_child(entry)
|
|
||||||
|
|
||||||
leaderboard_panel.add_child(vbox)
|
|
||||||
main_node.add_child(leaderboard_panel)
|
|
||||||
|
|
||||||
func _get_rank_text(rank: int) -> String:
|
func _get_rank_text(rank: int) -> String:
|
||||||
match rank:
|
match rank:
|
||||||
@@ -302,29 +241,59 @@ func _get_rank_text(rank: int) -> String:
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
func setup_timer_labels(main_node):
|
func setup_timer_labels(main_node):
|
||||||
"""Add timer labels to each player goals panel."""
|
"""Apply styling to standalone GoalsTimer in scene."""
|
||||||
var all_player_goals = main_node.get_node_or_null("AllPlayerGoals")
|
var goals_timer = main_node.get_node_or_null("GoalsTimer")
|
||||||
if not all_player_goals:
|
if not goals_timer:
|
||||||
|
push_warning("GoalsTimer node not found in scene")
|
||||||
return
|
return
|
||||||
|
|
||||||
for i in range(all_player_goals.get_child_count()):
|
# Apply dark background style
|
||||||
var panel = all_player_goals.get_child(i)
|
var style = StyleBoxFlat.new()
|
||||||
|
style.bg_color = Color(0.1, 0.1, 0.15, 0.9)
|
||||||
# Skip if timer already exists
|
style.border_color = Color(1.0, 0.85, 0.2, 1.0) # Gold border
|
||||||
if panel.get_node_or_null("TimerLabel"):
|
style.set_border_width_all(2)
|
||||||
|
style.corner_radius_top_left = 8
|
||||||
|
style.corner_radius_top_right = 8
|
||||||
|
style.corner_radius_bottom_left = 8
|
||||||
|
style.corner_radius_bottom_right = 8
|
||||||
|
goals_timer.add_theme_stylebox_override("panel", style)
|
||||||
|
|
||||||
|
# Style the timer label
|
||||||
|
var timer_label = goals_timer.get_node_or_null("VBox/TimerLabel")
|
||||||
|
if timer_label:
|
||||||
|
timer_label.add_theme_color_override("font_color", Color(1.0, 0.85, 0.2))
|
||||||
|
|
||||||
|
var suffix_label = goals_timer.get_node_or_null("VBox/SuffixLabel")
|
||||||
|
if suffix_label:
|
||||||
|
suffix_label.add_theme_color_override("font_color", Color(0.7, 0.7, 0.7))
|
||||||
|
|
||||||
|
# Method to update leaderboard with all players in match
|
||||||
|
func initialize_leaderboard_with_players(players: Array):
|
||||||
|
"""Initialize leaderboard showing all players with score 0."""
|
||||||
|
if not leaderboard_panel:
|
||||||
|
return
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
for i in range(4):
|
||||||
|
var entry = vbox.get_node_or_null("Entry" + str(i + 1))
|
||||||
|
if not entry:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
var timer_label = Label.new()
|
if i < players.size():
|
||||||
timer_label.name = "TimerLabel"
|
var player = players[i]
|
||||||
timer_label.text = "01:00"
|
var name_label = entry.get_node_or_null("NameLabel")
|
||||||
timer_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
var score_label = entry.get_node_or_null("ScoreLabel")
|
||||||
timer_label.add_theme_font_size_override("font_size", 18)
|
|
||||||
timer_label.add_theme_color_override("font_color", Color(1.0, 0.9, 0.3))
|
if name_label:
|
||||||
|
name_label.text = str(player.name) if player else "Player " + str(i + 1)
|
||||||
# Add at the top of the panel
|
if score_label:
|
||||||
var margin = panel.get_node_or_null("MarginContainer")
|
score_label.text = str(player.score) if player and player.get("score") else "0"
|
||||||
if margin:
|
|
||||||
margin.add_child(timer_label)
|
entry.visible = true
|
||||||
margin.move_child(timer_label, 0)
|
|
||||||
else:
|
else:
|
||||||
panel.add_child(timer_label)
|
entry.visible = false
|
||||||
|
|||||||
Reference in New Issue
Block a user