feat: Implement a new lobby system with configurable match duration, game over screen, and core game state management.

This commit is contained in:
2025-12-20 01:10:49 +08:00
parent 75eb398649
commit b0d45d4569
12 changed files with 1241 additions and 338 deletions
+211 -178
View File
@@ -14,22 +14,44 @@ extends Control
@onready var join_btn = $RoomListPanel/VBoxContainer/ButtonContainer/JoinBtn
@onready var back_btn = $RoomListPanel/VBoxContainer/ButtonContainer/BackBtn
# UI References - Lobby
# UI References - Lobby Panel
@onready var lobby_panel = $LobbyPanel
@onready var room_name_header = $LobbyPanel/VBoxContainer/RoomNameHeader
@onready var match_id_display = $LobbyPanel/VBoxContainer/MatchIdContainer/MatchIdDisplay
@onready var copy_id_btn = $LobbyPanel/VBoxContainer/MatchIdContainer/CopyIdBtn
@onready var player_list = $LobbyPanel/VBoxContainer/PlayerList
@onready var status_label = $LobbyPanel/VBoxContainer/StatusLabel
@onready var ready_btn = $LobbyPanel/VBoxContainer/ButtonContainer/ReadyBtn
@onready var start_game_btn = $LobbyPanel/VBoxContainer/ButtonContainer/StartGameBtn
@onready var leave_btn = $LobbyPanel/VBoxContainer/ButtonContainer/LeaveBtn
@onready var room_name_header = $LobbyPanel/RoomNameHeader
@onready var host_banner = $LobbyPanel/HostBanner
@onready var host_banner_label = $LobbyPanel/HostBanner/HostBannerLabel
# UI References - Top Bar
@onready var profile_btn = $LobbyPanel/TopBar/ProfileSection/ProfileBtn
@onready var logout_btn = $LobbyPanel/TopBar/ProfileSection/LogoutBtn
@onready var match_id_display = $LobbyPanel/TopBar/MatchIdContainer/MatchIdDisplay
@onready var copy_id_btn = $LobbyPanel/TopBar/MatchIdContainer/CopyIdBtn
@onready var duration_option = $LobbyPanel/TopBar/SettingsSection/DurationOption
@onready var duration_text_label = $LobbyPanel/TopBar/SettingsSection/DurationTextLabel
# UI References - Player Slots
@onready var players_container = $LobbyPanel/PlayersContainer
@onready var player_slots: Array[Control] = []
# UI References - Area Selector
@onready var area_selector = $LobbyPanel/AreaSelector
@onready var area_left_btn = $LobbyPanel/AreaSelector/AreaLeftBtn
@onready var area_name_label = $LobbyPanel/AreaSelector/AreaName
@onready var area_right_btn = $LobbyPanel/AreaSelector/AreaRightBtn
# UI References - Bottom Bar
@onready var leave_btn = $LobbyPanel/BottomBar/LeaveBtn
@onready var ready_btn = $LobbyPanel/BottomBar/ReadyBtn
@onready var start_game_btn = $LobbyPanel/BottomBar/StartGameBtn
@onready var status_label = $LobbyPanel/StatusLabel
# UI References - Status
@onready var connection_status = $StatusBar/ConnectionStatus
# UI References - User Profile Bar (will be added to scene)
var user_profile_bar: Control
# Character preview textures
var character_textures: Dictionary = {}
# Profile panel instance
var profile_panel_instance: Control
var admin_panel_instance: Control
@@ -39,27 +61,38 @@ var current_match_id: String = ""
func _ready():
# Check if user is authenticated
if not AuthManager.is_logged_in():
# Redirect to login screen - must use deferred call during _ready()
call_deferred("_go_to_login")
return
# Initialize user profile bar
_setup_user_profile_bar()
# Load character textures
_load_character_textures()
# Get player slot references
_setup_player_slots()
# Set player name from profile
if player_name_input:
player_name_input.text = UserProfileManager.get_display_name()
# Connect button signals
# Connect button signals - Main Menu
create_room_btn.pressed.connect(_on_create_room_pressed)
browse_rooms_btn.pressed.connect(_on_browse_rooms_pressed)
# Connect button signals - Room List
refresh_btn.pressed.connect(_on_refresh_pressed)
join_btn.pressed.connect(_on_join_pressed)
back_btn.pressed.connect(_on_back_pressed)
# Connect button signals - Lobby
profile_btn.pressed.connect(_on_profile_btn_pressed)
logout_btn.pressed.connect(_on_logout_pressed)
copy_id_btn.pressed.connect(_on_copy_id_pressed)
duration_option.item_selected.connect(_on_duration_selected)
area_left_btn.pressed.connect(func(): LobbyManager.cycle_area(-1))
area_right_btn.pressed.connect(func(): LobbyManager.cycle_area(1))
leave_btn.pressed.connect(_on_leave_pressed)
ready_btn.toggled.connect(_on_ready_toggled)
start_game_btn.pressed.connect(_on_start_game_pressed)
leave_btn.pressed.connect(_on_leave_pressed)
copy_id_btn.pressed.connect(_on_copy_id_pressed)
# Connect LobbyManager signals
LobbyManager.room_list_updated.connect(_on_room_list_updated)
@@ -70,6 +103,10 @@ func _ready():
LobbyManager.ready_state_changed.connect(_on_ready_state_changed)
LobbyManager.all_players_ready.connect(_on_all_players_ready)
LobbyManager.game_starting.connect(_on_game_starting)
LobbyManager.match_duration_changed.connect(_on_match_duration_changed)
LobbyManager.character_changed.connect(_on_character_changed)
LobbyManager.area_changed.connect(_on_area_changed)
LobbyManager.player_list_changed.connect(_update_player_slots)
# Connect NakamaManager signals
NakamaManager.connected_to_nakama.connect(_on_connected_to_nakama)
@@ -77,144 +114,35 @@ func _ready():
# Show main menu initially
_show_panel("main_menu")
_update_profile_bar()
# =============================================================================
# User Profile Bar
# Setup
# =============================================================================
func _setup_user_profile_bar() -> void:
# Create profile bar dynamically (or get reference if in scene)
user_profile_bar = _create_profile_bar()
add_child(user_profile_bar)
func _load_character_textures() -> void:
"""Load character expression textures for preview."""
var characters = ["Bob", "Gatot", "Masbro", "Oldpop"]
for char_name in characters:
var tex_path = "res://assets/characters/%s_%s-expression.png" % [char_name, char_name.to_lower()]
if ResourceLoader.exists(tex_path):
character_textures[char_name] = load(tex_path)
else:
print("[Lobby] Character texture not found: ", tex_path)
func _create_profile_bar() -> Control:
var bar := HBoxContainer.new()
bar.name = "UserProfileBar"
bar.set_anchors_preset(Control.PRESET_TOP_WIDE)
bar.offset_top = 5
bar.offset_bottom = 45
bar.offset_left = 10
bar.offset_right = -10
# Avatar
var avatar := TextureRect.new()
avatar.name = "Avatar"
avatar.custom_minimum_size = Vector2(35, 35)
avatar.expand_mode = TextureRect.EXPAND_FIT_WIDTH
avatar.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
bar.add_child(avatar)
# Display name
var name_label := Label.new()
name_label.name = "DisplayName"
name_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
name_label.add_theme_color_override("font_color", Color(0, 0.831, 1))
bar.add_child(name_label)
# Account type badge
var badge := Label.new()
badge.name = "AccountBadge"
badge.add_theme_font_size_override("font_size", 10)
badge.add_theme_color_override("font_color", Color(0.5, 0.5, 0.6))
bar.add_child(badge)
# Profile button
var profile_btn := Button.new()
profile_btn.name = "ProfileBtn"
profile_btn.text = "Profile"
profile_btn.pressed.connect(_on_profile_btn_pressed)
bar.add_child(profile_btn)
# Admin button (only visible for admins/hosts)
var admin_btn := Button.new()
admin_btn.name = "AdminBtn"
admin_btn.text = "Admin"
admin_btn.visible = false # Hidden by default, shown if user is admin
admin_btn.pressed.connect(_on_admin_btn_pressed)
bar.add_child(admin_btn)
# Logout button
var logout_btn := Button.new()
logout_btn.name = "LogoutBtn"
logout_btn.text = "Logout"
logout_btn.pressed.connect(_on_logout_pressed)
bar.add_child(logout_btn)
return bar
func _update_profile_bar() -> void:
if not user_profile_bar:
return
var name_label := user_profile_bar.get_node_or_null("DisplayName") as Label
if name_label:
name_label.text = UserProfileManager.get_display_name()
var badge := user_profile_bar.get_node_or_null("AccountBadge") as Label
if badge:
badge.text = "[Guest]" if AuthManager.is_guest else "[Registered]"
var avatar := user_profile_bar.get_node_or_null("Avatar") as TextureRect
if avatar:
var avatar_url := UserProfileManager.get_avatar_url()
if ResourceLoader.exists(avatar_url):
avatar.texture = load(avatar_url)
# Show admin button if user is admin or host
var admin_btn := user_profile_bar.get_node_or_null("AdminBtn") as Button
if admin_btn:
# Check if user is admin (you can define admin check logic here)
var is_admin = _check_if_admin()
admin_btn.visible = is_admin
func _on_profile_btn_pressed() -> void:
# Show profile panel
if not profile_panel_instance:
var profile_panel_scene := load("res://scenes/ui/profile_panel.tscn")
profile_panel_instance = profile_panel_scene.instantiate()
profile_panel_instance.closed.connect(func(): profile_panel_instance.hide())
profile_panel_instance.profile_updated.connect(_update_profile_bar)
add_child(profile_panel_instance)
profile_panel_instance.show_panel()
# Center the panel
profile_panel_instance.position = (get_viewport_rect().size - profile_panel_instance.size) / 2
func _on_logout_pressed() -> void:
AuthManager.logout()
_go_to_login()
func _go_to_login() -> void:
if get_tree():
get_tree().change_scene_to_file("res://scenes/ui/login_screen.tscn")
func _on_admin_btn_pressed() -> void:
# Show admin panel
if not admin_panel_instance:
var admin_panel_scene := load("res://scenes/ui/admin_panel.tscn")
admin_panel_instance = admin_panel_scene.instantiate()
# Connect close signal if available
if admin_panel_instance.has_signal("closed"):
admin_panel_instance.closed.connect(func(): admin_panel_instance.hide())
add_child(admin_panel_instance)
admin_panel_instance.show()
# Center the panel
admin_panel_instance.position = (get_viewport_rect().size - admin_panel_instance.size) / 2
func _check_if_admin() -> bool:
# Check if user is admin - can be host or have specific admin role
# You can extend this to check Nakama user metadata or roles
if LobbyManager.is_host:
return true
# Check if user has admin role in their profile (optional)
var user_id = AuthManager.get_user_id() if AuthManager.has_method("get_user_id") else ""
# Add your admin user IDs here or check from Nakama metadata
var admin_user_ids = ["admin_user_id_1", "admin_user_id_2"] # Configure as needed
return user_id in admin_user_ids
func _setup_player_slots() -> void:
"""Get references to all player slot nodes."""
player_slots.clear()
for i in range(1, 5):
var slot = players_container.get_node_or_null("PlayerSlot%d" % i)
if slot:
player_slots.append(slot)
# Connect character navigation buttons for all slots
var left_btn = slot.get_node_or_null("CharacterNav%d/CharLeftBtn%d" % [i, i])
var right_btn = slot.get_node_or_null("CharacterNav%d/CharRightBtn%d" % [i, i])
if left_btn:
left_btn.pressed.connect(func(): LobbyManager.cycle_character(-1))
if right_btn:
right_btn.pressed.connect(func(): LobbyManager.cycle_character(1))
# =============================================================================
# Panel Management
@@ -230,13 +158,11 @@ func _show_panel(panel_name: String) -> void:
# =============================================================================
func _on_create_room_pressed() -> void:
# Set player name
LobbyManager.local_player_name = player_name_input.text.strip_edges()
if LobbyManager.local_player_name.is_empty():
LobbyManager.local_player_name = "Host"
connection_status.text = "Creating room..."
# Room name auto-generated since Nakama doesn't support custom names
LobbyManager.create_room("Room %d" % randi_range(1000, 9999))
func _on_browse_rooms_pressed() -> void:
@@ -254,11 +180,9 @@ func _on_refresh_pressed() -> void:
LobbyManager.refresh_room_list()
func _on_join_pressed() -> void:
# First check if there's a match ID entered
var match_id = match_id_input.text.strip_edges()
if match_id.is_empty():
# Try to use selected room from list
var selected_items = room_list.get_selected_items()
if selected_items.size() == 0:
connection_status.text = "Please select a room or enter Match ID"
@@ -272,7 +196,6 @@ func _on_join_pressed() -> void:
connection_status.text = "No room selected"
return
# Set player name
LobbyManager.local_player_name = player_name_input.text.strip_edges()
if LobbyManager.local_player_name.is_empty():
LobbyManager.local_player_name = "Player"
@@ -303,7 +226,33 @@ func _on_leave_pressed() -> void:
func _on_copy_id_pressed() -> void:
DisplayServer.clipboard_set(current_match_id)
connection_status.text = "Match ID copied to clipboard!"
status_label.text = "Match ID copied!"
func _on_duration_selected(index: int) -> void:
if not LobbyManager.is_host:
return
var durations = [60, 120, 180, 300, 600]
if index >= 0 and index < durations.size():
LobbyManager.set_match_duration(durations[index])
func _on_profile_btn_pressed() -> void:
if not profile_panel_instance:
var profile_panel_scene := load("res://scenes/ui/profile_panel.tscn")
profile_panel_instance = profile_panel_scene.instantiate()
profile_panel_instance.closed.connect(func(): profile_panel_instance.hide())
add_child(profile_panel_instance)
profile_panel_instance.show_panel()
profile_panel_instance.position = (get_viewport_rect().size - profile_panel_instance.size) / 2
func _on_logout_pressed() -> void:
AuthManager.logout()
_go_to_login()
func _go_to_login() -> void:
if get_tree():
get_tree().change_scene_to_file("res://scenes/ui/login_screen.tscn")
# =============================================================================
# LobbyManager Signal Handlers
@@ -327,12 +276,25 @@ func _on_room_joined(room_data: Dictionary) -> void:
_show_panel("lobby")
current_match_id = room_data.get("match_id", "")
room_name_header.text = "ROOM: %s" % room_data.get("room_name", "Unknown")
match_id_display.text = "Match ID: %s " % current_match_id
match_id_display.text = "ID: %s" % _truncate_id(current_match_id)
# Update start button visibility (host only)
start_game_btn.visible = LobbyManager.is_host
# Configure host-specific UI
var is_host = LobbyManager.is_host
host_banner.visible = is_host
start_game_btn.visible = is_host
_update_player_list()
# Duration: host sees dropdown, clients see text
duration_option.visible = is_host
duration_text_label.visible = not is_host
if not is_host:
_update_duration_text_label(LobbyManager.get_match_duration())
# Area selector: only host can interact
area_left_btn.disabled = not is_host
area_right_btn.disabled = not is_host
area_name_label.text = LobbyManager.get_selected_area()
_update_player_slots()
connection_status.text = "Connected to room"
func _on_room_left() -> void:
@@ -340,33 +302,38 @@ func _on_room_left() -> void:
connection_status.text = "Left room"
func _on_player_joined(player_data: Dictionary) -> void:
_update_player_list()
_update_player_slots()
status_label.text = "%s joined!" % player_data.get("name", "Player")
func _on_player_left(_player_id: int) -> void:
_update_player_list()
_update_player_slots()
status_label.text = "A player left"
func _on_ready_state_changed(_player_id: int, _is_ready: bool) -> void:
_update_player_list()
_update_player_slots()
_update_status()
func _on_all_players_ready() -> void:
if LobbyManager.is_host:
start_game_btn.disabled = false
status_label.text = "All players ready! Host can start."
status_label.text = "All ready! Start the match!"
else:
status_label.text = "All players ready! Waiting for host..."
status_label.text = "All ready! Waiting for host..."
func _on_game_starting() -> void:
connection_status.text = "Starting game..."
# Small delay for visual feedback
await get_tree().create_timer(0.5).timeout
get_tree().change_scene_to_file("res://scenes/main.tscn")
# =============================================================================
# NakamaManager Signal Handlers
# =============================================================================
func _on_match_duration_changed(duration_seconds: int) -> void:
if not LobbyManager.is_host:
_update_duration_text_label(duration_seconds)
func _on_character_changed(_player_id: int, _character_name: String) -> void:
_update_player_slots()
func _on_area_changed(area_name: String) -> void:
area_name_label.text = area_name
func _on_connected_to_nakama() -> void:
connection_status.text = "Connected to server"
@@ -376,18 +343,64 @@ func _on_connection_failed(error_message: String) -> void:
_show_panel("main_menu")
# =============================================================================
# Helper Functions
# Player Slot Updates
# =============================================================================
func _update_player_list() -> void:
player_list.clear()
func _update_player_slots() -> void:
"""Update all player slot visuals based on current player list."""
var players = LobbyManager.get_players()
for player in players:
var player_name = player.get("name", "Unknown")
var is_ready = player.get("is_ready", false)
var ready_icon = "" if is_ready else ""
var host_tag = " (Host)" if player.get("id") == 1 else ""
player_list.add_item("%s%s%s" % [player_name, host_tag, ready_icon])
var my_id = multiplayer.get_unique_id()
for i in range(player_slots.size()):
var slot = player_slots[i]
var slot_num = i + 1
if i < players.size():
var player = players[i]
slot.visible = true
# Update player name
var name_label = slot.get_node_or_null("PlayerName%d" % slot_num)
if name_label:
var display_name = player.get("name", "Player %d" % slot_num)
if player.get("id") == 1:
display_name += " (Host)"
name_label.text = display_name
# Update character preview
var char_preview = slot.get_node_or_null("CharacterPreview%d" % slot_num)
var char_name = player.get("character", "Bob")
if char_preview and character_textures.has(char_name):
char_preview.texture = character_textures[char_name]
# Check if this is the local player
var is_local_player = player.get("id") == my_id
# Update character name in nav (inside CharacterNav)
var char_name_in_nav = slot.get_node_or_null("CharacterNav%d/CharacterName%d" % [slot_num, slot_num])
if char_name_in_nav:
char_name_in_nav.text = char_name
# Update character name label (outside nav, for non-local players)
var char_name_label = slot.get_node_or_null("CharacterNameLabel%d" % slot_num)
if char_name_label:
char_name_label.text = char_name
char_name_label.visible = not is_local_player
# Show/hide character navigation (only for local player)
var char_nav = slot.get_node_or_null("CharacterNav%d" % slot_num)
if char_nav:
char_nav.visible = is_local_player
# Update ready status
var ready_label = slot.get_node_or_null("ReadyStatus%d" % slot_num)
if ready_label:
var is_ready = player.get("is_ready", false)
ready_label.text = "READY ✓" if is_ready else "NOT READY"
ready_label.add_theme_color_override("font_color",
Color(0.4, 0.8, 0.4) if is_ready else Color(0.6, 0.6, 0.6))
else:
slot.visible = false
func _update_status() -> void:
var players = LobbyManager.get_players()
@@ -400,3 +413,23 @@ func _update_status() -> void:
if LobbyManager.is_host:
start_game_btn.disabled = not LobbyManager.is_all_ready()
# =============================================================================
# Helper Functions
# =============================================================================
func _update_duration_text_label(duration_seconds: int) -> void:
var duration_text: String
match duration_seconds:
60: duration_text = "1 min"
120: duration_text = "2 min"
180: duration_text = "3 min"
300: duration_text = "5 min"
600: duration_text = "10 min"
_: duration_text = "%d sec" % duration_seconds
duration_text_label.text = duration_text
func _truncate_id(id: String) -> String:
if id.length() > 16:
return id.substr(0, 8) + "..." + id.substr(-4)
return id
+479 -59
View File
@@ -20,7 +20,7 @@ anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
color = Color(0.12, 0.1, 0.08, 1)
color = Color(0.08, 0.12, 0.18, 1)
[node name="MainMenuPanel" type="PanelContainer" parent="."]
layout_mode = 1
@@ -165,90 +165,507 @@ custom_minimum_size = Vector2(110, 44)
layout_mode = 2
text = "BACK"
[node name="LobbyPanel" type="PanelContainer" parent="."]
[node name="LobbyPanel" type="Control" parent="."]
visible = false
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="LobbyBackground" type="ColorRect" parent="LobbyPanel"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
color = Color(0.06, 0.08, 0.12, 1)
[node name="TopBar" type="HBoxContainer" parent="LobbyPanel"]
layout_mode = 1
anchors_preset = 10
anchor_right = 1.0
offset_left = 20.0
offset_top = 10.0
offset_right = -20.0
offset_bottom = 50.0
grow_horizontal = 2
theme_override_constants/separation = 20
[node name="ProfileSection" type="HBoxContainer" parent="LobbyPanel/TopBar"]
layout_mode = 2
theme_override_constants/separation = 10
[node name="ProfileBtn" type="Button" parent="LobbyPanel/TopBar/ProfileSection"]
custom_minimum_size = Vector2(80, 32)
layout_mode = 2
theme_override_font_sizes/font_size = 11
text = "Profile"
[node name="LogoutBtn" type="Button" parent="LobbyPanel/TopBar/ProfileSection"]
custom_minimum_size = Vector2(70, 32)
layout_mode = 2
theme_override_font_sizes/font_size = 11
text = "Logout"
[node name="Spacer" type="Control" parent="LobbyPanel/TopBar"]
layout_mode = 2
size_flags_horizontal = 3
[node name="MatchIdContainer" type="HBoxContainer" parent="LobbyPanel/TopBar"]
layout_mode = 2
theme_override_constants/separation = 8
[node name="MatchIdDisplay" type="Label" parent="LobbyPanel/TopBar/MatchIdContainer"]
layout_mode = 2
theme_override_colors/font_color = Color(0.5, 0.5, 0.55, 1)
theme_override_font_sizes/font_size = 10
text = "Match ID: "
[node name="CopyIdBtn" type="Button" parent="LobbyPanel/TopBar/MatchIdContainer"]
custom_minimum_size = Vector2(60, 28)
layout_mode = 2
theme_override_font_sizes/font_size = 10
text = "COPY"
[node name="Spacer2" type="Control" parent="LobbyPanel/TopBar"]
layout_mode = 2
size_flags_horizontal = 3
[node name="SettingsSection" type="HBoxContainer" parent="LobbyPanel/TopBar"]
layout_mode = 2
theme_override_constants/separation = 8
[node name="DurationLabel" type="Label" parent="LobbyPanel/TopBar/SettingsSection"]
layout_mode = 2
theme_override_colors/font_color = Color(0.69, 0.529, 0.357, 1)
theme_override_font_sizes/font_size = 11
text = "Duration:"
[node name="DurationOption" type="OptionButton" parent="LobbyPanel/TopBar/SettingsSection"]
custom_minimum_size = Vector2(90, 28)
layout_mode = 2
theme_override_font_sizes/font_size = 11
selected = 2
item_count = 5
popup/item_0/text = "1 min"
popup/item_0/id = 0
popup/item_1/text = "2 min"
popup/item_1/id = 1
popup/item_2/text = "3 min"
popup/item_2/id = 2
popup/item_3/text = "5 min"
popup/item_3/id = 3
popup/item_4/text = "10 min"
popup/item_4/id = 4
[node name="DurationTextLabel" type="Label" parent="LobbyPanel/TopBar/SettingsSection"]
visible = false
layout_mode = 2
theme_override_colors/font_color = Color(0.647, 0.996, 0.224, 1)
theme_override_font_sizes/font_size = 11
text = "3 min"
[node name="HostBanner" type="PanelContainer" parent="LobbyPanel"]
layout_mode = 1
anchors_preset = 5
anchor_left = 0.5
anchor_right = 0.5
offset_left = -150.0
offset_top = 99.0
offset_right = 150.0
offset_bottom = 159.0
grow_horizontal = 2
[node name="HostBannerLabel" type="Label" parent="LobbyPanel/HostBanner"]
layout_mode = 2
theme_override_colors/font_color = Color(0.1, 0.1, 0.1, 1)
theme_override_font_sizes/font_size = 20
text = "HOST"
horizontal_alignment = 1
vertical_alignment = 1
[node name="RoomNameHeader" type="Label" parent="LobbyPanel"]
layout_mode = 1
anchors_preset = 5
anchor_left = 0.5
anchor_right = 0.5
offset_left = -200.0
offset_top = 55.0
offset_right = 200.0
offset_bottom = 95.0
grow_horizontal = 2
theme_override_colors/font_color = Color(0.992, 0.796, 0.047, 1)
theme_override_font_sizes/font_size = 20
horizontal_alignment = 1
vertical_alignment = 1
[node name="PlayersContainer" type="HBoxContainer" parent="LobbyPanel"]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -300.0
offset_top = -260.0
offset_right = 300.0
offset_bottom = 260.0
offset_left = -450.0
offset_top = -120.0
offset_right = 450.0
offset_bottom = 200.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/separation = 30
alignment = 1
[node name="VBoxContainer" type="VBoxContainer" parent="LobbyPanel"]
[node name="PlayerSlot1" type="VBoxContainer" parent="LobbyPanel/PlayersContainer"]
custom_minimum_size = Vector2(180, 0)
layout_mode = 2
theme_override_constants/separation = 14
theme_override_constants/separation = 8
alignment = 1
[node name="RoomNameHeader" type="Label" parent="LobbyPanel/VBoxContainer"]
[node name="PlayerName1" type="Label" parent="LobbyPanel/PlayersContainer/PlayerSlot1"]
layout_mode = 2
theme_override_colors/font_color = Color(0.647, 0.996, 0.224, 1)
theme_override_font_sizes/font_size = 26
text = "ROOM: "
theme_override_colors/font_color = Color(1, 1, 1, 1)
theme_override_font_sizes/font_size = 16
text = "Player 1"
horizontal_alignment = 1
[node name="MatchIdContainer" type="HBoxContainer" parent="LobbyPanel/VBoxContainer"]
[node name="CharacterPreview1" type="TextureRect" parent="LobbyPanel/PlayersContainer/PlayerSlot1"]
custom_minimum_size = Vector2(140, 140)
layout_mode = 2
size_flags_horizontal = 4
expand_mode = 1
stretch_mode = 5
[node name="CharacterNav1" type="HBoxContainer" parent="LobbyPanel/PlayersContainer/PlayerSlot1"]
layout_mode = 2
alignment = 1
[node name="MatchIdDisplay" type="Label" parent="LobbyPanel/VBoxContainer/MatchIdContainer"]
[node name="CharLeftBtn1" type="Button" parent="LobbyPanel/PlayersContainer/PlayerSlot1/CharacterNav1"]
custom_minimum_size = Vector2(40, 36)
layout_mode = 2
theme_override_colors/font_color = Color(0.5, 0.5, 0.55, 1)
theme_override_font_sizes/font_size = 10
text = "Match ID: "
theme_override_font_sizes/font_size = 18
text = "◀"
[node name="CopyIdBtn" type="Button" parent="LobbyPanel/VBoxContainer/MatchIdContainer"]
custom_minimum_size = Vector2(80, 32)
layout_mode = 2
theme_override_font_sizes/font_size = 10
text = "COPY"
[node name="HSeparator" type="HSeparator" parent="LobbyPanel/VBoxContainer"]
layout_mode = 2
[node name="PlayersLabel" type="Label" parent="LobbyPanel/VBoxContainer"]
layout_mode = 2
theme_override_colors/font_color = Color(0.69, 0.529, 0.357, 1)
theme_override_font_sizes/font_size = 13
text = "PLAYERS"
[node name="PlayerList" type="ItemList" parent="LobbyPanel/VBoxContainer"]
custom_minimum_size = Vector2(0, 160)
layout_mode = 2
allow_reselect = true
[node name="StatusLabel" type="Label" parent="LobbyPanel/VBoxContainer"]
[node name="CharacterName1" type="Label" parent="LobbyPanel/PlayersContainer/PlayerSlot1/CharacterNav1"]
custom_minimum_size = Vector2(80, 0)
layout_mode = 2
theme_override_colors/font_color = Color(0.992, 0.796, 0.047, 1)
theme_override_font_sizes/font_size = 14
text = "Waiting for players..."
text = "Bob"
horizontal_alignment = 1
vertical_alignment = 1
[node name="CharRightBtn1" type="Button" parent="LobbyPanel/PlayersContainer/PlayerSlot1/CharacterNav1"]
custom_minimum_size = Vector2(40, 36)
layout_mode = 2
theme_override_font_sizes/font_size = 18
text = "▶"
[node name="CharacterNameLabel1" type="Label" parent="LobbyPanel/PlayersContainer/PlayerSlot1"]
custom_minimum_size = Vector2(80, 0)
layout_mode = 2
theme_override_colors/font_color = Color(0.992, 0.796, 0.047, 1)
theme_override_font_sizes/font_size = 14
text = "Bob"
horizontal_alignment = 1
[node name="ButtonContainer" type="HBoxContainer" parent="LobbyPanel/VBoxContainer"]
[node name="ReadyStatus1" type="Label" parent="LobbyPanel/PlayersContainer/PlayerSlot1"]
layout_mode = 2
theme_override_constants/separation = 14
theme_override_colors/font_color = Color(0.6, 0.6, 0.6, 1)
theme_override_font_sizes/font_size = 12
text = "NOT READY"
horizontal_alignment = 1
[node name="PlayerSlot2" type="VBoxContainer" parent="LobbyPanel/PlayersContainer"]
visible = false
custom_minimum_size = Vector2(180, 0)
layout_mode = 2
theme_override_constants/separation = 8
alignment = 1
[node name="ReadyBtn" type="Button" parent="LobbyPanel/VBoxContainer/ButtonContainer"]
custom_minimum_size = Vector2(110, 48)
[node name="PlayerName2" type="Label" parent="LobbyPanel/PlayersContainer/PlayerSlot2"]
layout_mode = 2
theme_override_colors/font_color = Color(1, 1, 1, 1)
theme_override_font_sizes/font_size = 16
text = "Player 2"
horizontal_alignment = 1
[node name="CharacterPreview2" type="TextureRect" parent="LobbyPanel/PlayersContainer/PlayerSlot2"]
custom_minimum_size = Vector2(140, 140)
layout_mode = 2
size_flags_horizontal = 4
expand_mode = 1
stretch_mode = 5
[node name="CharacterNav2" type="HBoxContainer" parent="LobbyPanel/PlayersContainer/PlayerSlot2"]
visible = false
layout_mode = 2
alignment = 1
[node name="CharLeftBtn2" type="Button" parent="LobbyPanel/PlayersContainer/PlayerSlot2/CharacterNav2"]
custom_minimum_size = Vector2(40, 36)
layout_mode = 2
theme_override_font_sizes/font_size = 18
text = "◀"
[node name="CharacterName2" type="Label" parent="LobbyPanel/PlayersContainer/PlayerSlot2/CharacterNav2"]
custom_minimum_size = Vector2(80, 0)
layout_mode = 2
theme_override_colors/font_color = Color(0.992, 0.796, 0.047, 1)
theme_override_font_sizes/font_size = 14
text = "Bob"
horizontal_alignment = 1
vertical_alignment = 1
[node name="CharRightBtn2" type="Button" parent="LobbyPanel/PlayersContainer/PlayerSlot2/CharacterNav2"]
custom_minimum_size = Vector2(40, 36)
layout_mode = 2
theme_override_font_sizes/font_size = 18
text = "▶"
[node name="CharacterNameLabel2" type="Label" parent="LobbyPanel/PlayersContainer/PlayerSlot2"]
custom_minimum_size = Vector2(80, 0)
layout_mode = 2
theme_override_colors/font_color = Color(0.992, 0.796, 0.047, 1)
theme_override_font_sizes/font_size = 14
text = "Bob"
horizontal_alignment = 1
[node name="ReadyStatus2" type="Label" parent="LobbyPanel/PlayersContainer/PlayerSlot2"]
layout_mode = 2
theme_override_colors/font_color = Color(0.6, 0.6, 0.6, 1)
theme_override_font_sizes/font_size = 12
text = "NOT READY"
horizontal_alignment = 1
[node name="PlayerSlot3" type="VBoxContainer" parent="LobbyPanel/PlayersContainer"]
visible = false
custom_minimum_size = Vector2(180, 0)
layout_mode = 2
theme_override_constants/separation = 8
alignment = 1
[node name="PlayerName3" type="Label" parent="LobbyPanel/PlayersContainer/PlayerSlot3"]
layout_mode = 2
theme_override_colors/font_color = Color(1, 1, 1, 1)
theme_override_font_sizes/font_size = 16
text = "Player 3"
horizontal_alignment = 1
[node name="CharacterPreview3" type="TextureRect" parent="LobbyPanel/PlayersContainer/PlayerSlot3"]
custom_minimum_size = Vector2(140, 140)
layout_mode = 2
size_flags_horizontal = 4
expand_mode = 1
stretch_mode = 5
[node name="CharacterNav3" type="HBoxContainer" parent="LobbyPanel/PlayersContainer/PlayerSlot3"]
visible = false
layout_mode = 2
alignment = 1
[node name="CharLeftBtn3" type="Button" parent="LobbyPanel/PlayersContainer/PlayerSlot3/CharacterNav3"]
custom_minimum_size = Vector2(40, 36)
layout_mode = 2
theme_override_font_sizes/font_size = 18
text = "◀"
[node name="CharacterName3" type="Label" parent="LobbyPanel/PlayersContainer/PlayerSlot3/CharacterNav3"]
custom_minimum_size = Vector2(80, 0)
layout_mode = 2
theme_override_colors/font_color = Color(0.992, 0.796, 0.047, 1)
theme_override_font_sizes/font_size = 14
text = "Bob"
horizontal_alignment = 1
vertical_alignment = 1
[node name="CharRightBtn3" type="Button" parent="LobbyPanel/PlayersContainer/PlayerSlot3/CharacterNav3"]
custom_minimum_size = Vector2(40, 36)
layout_mode = 2
theme_override_font_sizes/font_size = 18
text = "▶"
[node name="CharacterNameLabel3" type="Label" parent="LobbyPanel/PlayersContainer/PlayerSlot3"]
custom_minimum_size = Vector2(80, 0)
layout_mode = 2
theme_override_colors/font_color = Color(0.992, 0.796, 0.047, 1)
theme_override_font_sizes/font_size = 14
text = "Bob"
horizontal_alignment = 1
[node name="ReadyStatus3" type="Label" parent="LobbyPanel/PlayersContainer/PlayerSlot3"]
layout_mode = 2
theme_override_colors/font_color = Color(0.6, 0.6, 0.6, 1)
theme_override_font_sizes/font_size = 12
text = "NOT READY"
horizontal_alignment = 1
[node name="PlayerSlot4" type="VBoxContainer" parent="LobbyPanel/PlayersContainer"]
visible = false
custom_minimum_size = Vector2(180, 0)
layout_mode = 2
theme_override_constants/separation = 8
alignment = 1
[node name="PlayerName4" type="Label" parent="LobbyPanel/PlayersContainer/PlayerSlot4"]
layout_mode = 2
theme_override_colors/font_color = Color(1, 1, 1, 1)
theme_override_font_sizes/font_size = 16
text = "Player 4"
horizontal_alignment = 1
[node name="CharacterPreview4" type="TextureRect" parent="LobbyPanel/PlayersContainer/PlayerSlot4"]
custom_minimum_size = Vector2(140, 140)
layout_mode = 2
size_flags_horizontal = 4
expand_mode = 1
stretch_mode = 5
[node name="CharacterNav4" type="HBoxContainer" parent="LobbyPanel/PlayersContainer/PlayerSlot4"]
visible = false
layout_mode = 2
alignment = 1
[node name="CharLeftBtn4" type="Button" parent="LobbyPanel/PlayersContainer/PlayerSlot4/CharacterNav4"]
custom_minimum_size = Vector2(40, 36)
layout_mode = 2
theme_override_font_sizes/font_size = 18
text = "◀"
[node name="CharacterName4" type="Label" parent="LobbyPanel/PlayersContainer/PlayerSlot4/CharacterNav4"]
custom_minimum_size = Vector2(80, 0)
layout_mode = 2
theme_override_colors/font_color = Color(0.992, 0.796, 0.047, 1)
theme_override_font_sizes/font_size = 14
text = "Bob"
horizontal_alignment = 1
vertical_alignment = 1
[node name="CharRightBtn4" type="Button" parent="LobbyPanel/PlayersContainer/PlayerSlot4/CharacterNav4"]
custom_minimum_size = Vector2(40, 36)
layout_mode = 2
theme_override_font_sizes/font_size = 18
text = "▶"
[node name="CharacterNameLabel4" type="Label" parent="LobbyPanel/PlayersContainer/PlayerSlot4"]
custom_minimum_size = Vector2(80, 0)
layout_mode = 2
theme_override_colors/font_color = Color(0.992, 0.796, 0.047, 1)
theme_override_font_sizes/font_size = 14
text = "Bob"
horizontal_alignment = 1
[node name="ReadyStatus4" type="Label" parent="LobbyPanel/PlayersContainer/PlayerSlot4"]
layout_mode = 2
theme_override_colors/font_color = Color(0.6, 0.6, 0.6, 1)
theme_override_font_sizes/font_size = 12
text = "NOT READY"
horizontal_alignment = 1
[node name="AreaSelector" type="HBoxContainer" parent="LobbyPanel"]
clip_contents = true
layout_mode = 1
anchors_preset = 7
anchor_left = 0.5
anchor_top = 1.0
anchor_right = 0.5
anchor_bottom = 1.0
offset_left = -660.0
offset_top = -137.0
offset_right = -412.0
offset_bottom = -87.0
grow_horizontal = 2
grow_vertical = 0
[node name="AreaLabel" type="Label" parent="LobbyPanel/AreaSelector"]
layout_mode = 2
theme_override_colors/font_color = Color(0.69, 0.529, 0.357, 1)
theme_override_font_sizes/font_size = 13
text = "AREA: "
[node name="AreaLeftBtn" type="Button" parent="LobbyPanel/AreaSelector"]
custom_minimum_size = Vector2(40, 36)
layout_mode = 2
theme_override_font_sizes/font_size = 18
text = "◀"
[node name="AreaName" type="Label" parent="LobbyPanel/AreaSelector"]
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
theme_override_colors/font_color = Color(0.647, 0.996, 0.224, 1)
theme_override_font_sizes/font_size = 16
text = "Desert"
horizontal_alignment = 1
vertical_alignment = 1
[node name="AreaRightBtn" type="Button" parent="LobbyPanel/AreaSelector"]
custom_minimum_size = Vector2(40, 36)
layout_mode = 2
theme_override_font_sizes/font_size = 18
text = "▶"
[node name="BottomBar" type="HBoxContainer" parent="LobbyPanel"]
layout_mode = 1
anchors_preset = 12
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 20.0
offset_top = -70.0
offset_right = -20.0
offset_bottom = -20.0
grow_horizontal = 2
grow_vertical = 0
theme_override_constants/separation = 20
[node name="LeaveBtn" type="Button" parent="LobbyPanel/BottomBar"]
custom_minimum_size = Vector2(120, 44)
layout_mode = 2
theme_override_font_sizes/font_size = 14
text = "LEAVE"
[node name="Spacer" type="Control" parent="LobbyPanel/BottomBar"]
layout_mode = 2
size_flags_horizontal = 3
[node name="ReadyBtn" type="Button" parent="LobbyPanel/BottomBar"]
custom_minimum_size = Vector2(140, 50)
layout_mode = 2
theme_override_font_sizes/font_size = 16
toggle_mode = true
text = "READY"
[node name="StartGameBtn" type="Button" parent="LobbyPanel/VBoxContainer/ButtonContainer"]
custom_minimum_size = Vector2(140, 48)
[node name="StartGameBtn" type="Button" parent="LobbyPanel/BottomBar"]
custom_minimum_size = Vector2(140, 50)
layout_mode = 2
theme_override_font_sizes/font_size = 16
disabled = true
text = "START GAME"
text = "MATCH"
[node name="LeaveBtn" type="Button" parent="LobbyPanel/VBoxContainer/ButtonContainer"]
custom_minimum_size = Vector2(110, 48)
layout_mode = 2
text = "LEAVE"
[node name="StatusLabel" type="Label" parent="LobbyPanel"]
layout_mode = 1
anchors_preset = 7
anchor_left = 0.5
anchor_top = 1.0
anchor_right = 0.5
anchor_bottom = 1.0
offset_left = -150.0
offset_top = -90.0
offset_right = 150.0
offset_bottom = -75.0
grow_horizontal = 2
grow_vertical = 0
theme_override_colors/font_color = Color(0.992, 0.796, 0.047, 1)
theme_override_font_sizes/font_size = 12
horizontal_alignment = 1
[node name="PlayerList" type="ItemList" parent="LobbyPanel"]
visible = false
layout_mode = 0
offset_right = 40.0
offset_bottom = 40.0
[node name="StatusBar" type="PanelContainer" parent="."]
layout_mode = 1
@@ -256,10 +673,10 @@ anchors_preset = 12
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 24.0
offset_top = -72.0
offset_right = -24.0
offset_bottom = -24.0
offset_left = 464.0
offset_top = -70.0
offset_right = -461.0
offset_bottom = -21.0
grow_horizontal = 2
grow_vertical = 0
@@ -272,14 +689,17 @@ horizontal_alignment = 1
[node name="VersionLabel" type="Label" parent="."]
layout_mode = 1
anchors_preset = 1
anchors_preset = 3
anchor_left = 1.0
anchor_top = 1.0
anchor_right = 1.0
offset_left = -130.0
offset_top = 20.0
offset_right = -20.0
offset_bottom = 40.0
anchor_bottom = 1.0
offset_left = -124.0
offset_top = -95.0
offset_right = -14.0
offset_bottom = -75.0
grow_horizontal = 0
grow_vertical = 0
theme_override_colors/font_color = Color(0.6, 0.6, 0.6, 0.6)
theme_override_font_sizes/font_size = 11
text = "v0.1.0 ALPHA"
+203 -2
View File
@@ -30,6 +30,7 @@ func _ready():
ui_manager.setup_leaderboard_ui(self)
ui_manager.setup_powerup_bar_ui(self)
_setup_obstacle_ui()
_setup_global_match_timer_ui()
# Auto-start game if coming from lobby (already connected to match)
if NakamaManager.is_connected_to_nakama() and multiplayer.get_unique_id() != 0:
@@ -59,6 +60,8 @@ func _init_managers():
goals_cycle_manager.timer_updated.connect(_on_timer_updated)
goals_cycle_manager.score_updated.connect(_on_score_updated)
goals_cycle_manager.leaderboard_updated.connect(_on_leaderboard_updated)
goals_cycle_manager.global_timer_updated.connect(_on_global_timer_updated)
goals_cycle_manager.match_ended.connect(_on_match_ended)
# Message Bar Configuration
const MAX_MESSAGES := 5
@@ -181,6 +184,54 @@ func _setup_obstacle_ui():
)
$ActionMenu/ActionButtonContainer.add_child(type_button)
func _setup_global_match_timer_ui():
"""Create the global match timer display at the top of the screen."""
var existing = get_node_or_null("GlobalMatchTimer")
if existing:
return
# Create timer panel
var panel = PanelContainer.new()
panel.name = "GlobalMatchTimer"
# Position at top center
panel.set_anchors_preset(Control.PRESET_CENTER_TOP)
panel.offset_left = -80
panel.offset_right = 80
panel.offset_top = 10
panel.offset_bottom = 60
# Style
var style = StyleBoxFlat.new()
style.bg_color = Color(0.1, 0.1, 0.15, 0.9)
style.border_width_left = 2
style.border_width_top = 2
style.border_width_right = 2
style.border_width_bottom = 2
style.border_color = Color(0.647, 0.996, 0.224, 0.8)
style.corner_radius_top_left = 8
style.corner_radius_top_right = 8
style.corner_radius_bottom_right = 8
style.corner_radius_bottom_left = 8
panel.add_theme_stylebox_override("panel", style)
# VBox for content
var vbox = VBoxContainer.new()
vbox.name = "VBox"
vbox.alignment = BoxContainer.ALIGNMENT_CENTER
panel.add_child(vbox)
# Label
var label = Label.new()
label.name = "TimerLabel"
label.text = "3:00"
label.add_theme_font_size_override("font_size", 28)
label.add_theme_color_override("font_color", Color(0.647, 0.996, 0.224))
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
vbox.add_child(label)
add_child(panel)
func _process(delta):
if multiplayer.is_server() and GameStateManager.is_game_started():
if TurnManager.turn_based_mode:
@@ -322,9 +373,10 @@ func _start_game():
var next_player = TurnManager.next_turn(GameStateManager.players)
rpc("set_current_turn", next_player)
# Start the goals cycle timer
# Start the global match timer (this also starts the first cycle)
if goals_cycle_manager:
goals_cycle_manager.start_cycle()
var match_duration = LobbyManager.get_match_duration()
goals_cycle_manager.start_match(float(match_duration))
# Initialize leaderboard with all players
if ui_manager:
@@ -830,6 +882,155 @@ func _on_leaderboard_updated(sorted_scores: Array):
})
rpc("sync_leaderboard_data", player_data)
func _on_global_timer_updated(time_remaining: float):
"""Update the global match timer display."""
var global_timer_panel = get_node_or_null("GlobalMatchTimer")
if global_timer_panel:
var timer_label = global_timer_panel.get_node_or_null("VBox/TimerLabel")
if timer_label:
var minutes = int(time_remaining) / 60
var seconds = int(time_remaining) % 60
timer_label.text = "%d:%02d" % [minutes, seconds]
func _on_match_ended():
"""Called when the global match timer ends - show game over screen."""
print("[Main] Match ended! Showing game over screen...")
# Disable player controls
var local_player = GameStateManager.local_player_character
if local_player:
local_player.action_points = 0
# Show game over overlay
_show_game_over_panel()
func _show_game_over_panel():
"""Create and display the game over panel with final leaderboard."""
# Check if panel already exists
var existing_panel = get_node_or_null("GameOverPanel")
if existing_panel:
existing_panel.show()
return
# Create game over panel
var panel = PanelContainer.new()
panel.name = "GameOverPanel"
panel.set_anchors_preset(Control.PRESET_FULL_RECT)
# Semi-transparent dark background
var style = StyleBoxFlat.new()
style.bg_color = Color(0.0, 0.0, 0.0, 0.85)
panel.add_theme_stylebox_override("panel", style)
# Content container
var vbox = VBoxContainer.new()
vbox.name = "VBox"
vbox.set_anchors_preset(Control.PRESET_CENTER)
vbox.add_theme_constant_override("separation", 20)
vbox.alignment = BoxContainer.ALIGNMENT_CENTER
panel.add_child(vbox)
# Center the vbox
var margin = MarginContainer.new()
margin.set_anchors_preset(Control.PRESET_FULL_RECT)
margin.add_theme_constant_override("margin_left", 200)
margin.add_theme_constant_override("margin_right", 200)
margin.add_theme_constant_override("margin_top", 100)
margin.add_theme_constant_override("margin_bottom", 100)
panel.add_child(margin)
var inner_vbox = VBoxContainer.new()
inner_vbox.add_theme_constant_override("separation", 30)
inner_vbox.alignment = BoxContainer.ALIGNMENT_CENTER
margin.add_child(inner_vbox)
# Title
var title = Label.new()
title.text = "⏱️ TIME'S UP!"
title.add_theme_font_size_override("font_size", 64)
title.add_theme_color_override("font_color", Color(0.992, 0.796, 0.047))
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
inner_vbox.add_child(title)
# Subtitle
var subtitle = Label.new()
subtitle.text = "FINAL STANDINGS"
subtitle.add_theme_font_size_override("font_size", 24)
subtitle.add_theme_color_override("font_color", Color(0.7, 0.7, 0.7))
subtitle.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
inner_vbox.add_child(subtitle)
# Leaderboard container
var leaderboard_container = VBoxContainer.new()
leaderboard_container.add_theme_constant_override("separation", 15)
inner_vbox.add_child(leaderboard_container)
# Get final scores
var player_scores = []
for p in get_tree().get_nodes_in_group("Players"):
player_scores.append({
"name": p.name,
"score": goals_cycle_manager.get_player_score(p.get_multiplayer_authority()) if goals_cycle_manager else 0
})
player_scores.sort_custom(func(a, b): return a.score > b.score)
# Display each player
for i in range(min(player_scores.size(), 4)):
var entry = HBoxContainer.new()
entry.add_theme_constant_override("separation", 20)
var rank_colors = [Color(1.0, 0.84, 0.0), Color(0.75, 0.75, 0.75), Color(0.8, 0.5, 0.2), Color(0.5, 0.5, 0.5)]
var rank_emojis = ["🥇", "🥈", "🥉", "4th"]
var rank_label = Label.new()
rank_label.text = rank_emojis[i]
rank_label.add_theme_font_size_override("font_size", 32)
entry.add_child(rank_label)
var name_label = Label.new()
name_label.text = "Player %s" % player_scores[i].name
name_label.add_theme_font_size_override("font_size", 28)
name_label.add_theme_color_override("font_color", rank_colors[i])
name_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
entry.add_child(name_label)
var score_label = Label.new()
score_label.text = str(player_scores[i].score)
score_label.add_theme_font_size_override("font_size", 28)
score_label.add_theme_color_override("font_color", Color(0.4, 1.0, 0.4))
entry.add_child(score_label)
leaderboard_container.add_child(entry)
# Back to Menu button
var back_btn = Button.new()
back_btn.name = "BackToMenuBtn"
back_btn.text = "BACK TO MAIN MENU"
back_btn.custom_minimum_size = Vector2(300, 60)
back_btn.add_theme_font_size_override("font_size", 20)
back_btn.pressed.connect(_on_back_to_menu_pressed)
inner_vbox.add_child(back_btn)
# Center the button
var btn_container = HBoxContainer.new()
btn_container.alignment = BoxContainer.ALIGNMENT_CENTER
btn_container.add_child(back_btn)
inner_vbox.add_child(btn_container)
add_child(panel)
func _on_back_to_menu_pressed():
"""Return to lobby/main menu and clean up game state."""
print("[Main] Returning to lobby...")
# Clean up game state
GameStateManager.end_game()
LobbyManager.reset()
# Go back to lobby
if get_tree():
get_tree().change_scene_to_file("res://scenes/lobby.tscn")
func _deferred_init_leaderboard():
"""Initialize leaderboard after a delay to ensure all players are loaded."""
# Longer delay ensures players are synced
+88 -82
View File
@@ -23,6 +23,22 @@
[ext_resource type="StyleBox" uid="uid://cdhnwvcklbyl8" path="res://assets/styles/ribbon_hovered_gui.tres" id="19_w1rqq"]
[ext_resource type="StyleBox" uid="uid://3yog1weaqhxb" path="res://assets/styles/ribbon_unselected_gui.tres" id="20_q6bc1"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_playerboard"]
content_margin_left = 8.0
content_margin_top = 8.0
content_margin_right = 8.0
content_margin_bottom = 8.0
bg_color = Color(0.08, 0.08, 0.12, 0.9)
border_width_left = 1
border_width_top = 1
border_width_right = 1
border_width_bottom = 1
border_color = Color(0.3, 0.3, 0.4, 0.8)
corner_radius_top_left = 8
corner_radius_top_right = 8
corner_radius_bottom_right = 8
corner_radius_bottom_left = 8
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_1cewu"]
[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_5oeq4"]
@@ -30,22 +46,6 @@ texture = ExtResource("13_ahjgs")
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_s1l63"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_playerboard"]
bg_color = Color(0.08, 0.08, 0.12, 0.9)
border_color = Color(0.3, 0.3, 0.4, 0.8)
border_width_left = 1
border_width_top = 1
border_width_right = 1
border_width_bottom = 1
corner_radius_top_left = 8
corner_radius_top_right = 8
corner_radius_bottom_right = 8
corner_radius_bottom_left = 8
content_margin_left = 8.0
content_margin_top = 8.0
content_margin_right = 8.0
content_margin_bottom = 8.0
[node name="Main" type="Node3D"]
script = ExtResource("1_xcpe3")
@@ -77,26 +77,31 @@ fov = 35.5
size = 23.0
[node name="NetworkPanel" type="Panel" parent="."]
anchors_preset = 4
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = 1208.0
offset_top = 280.0
offset_right = 1344.0
offset_bottom = 333.0
grow_vertical = 2
anchors_preset = 5
anchor_left = 0.5
anchor_right = 0.5
offset_left = -185.0
offset_top = 25.0
offset_right = 185.0
offset_bottom = 78.0
grow_horizontal = 2
theme_override_styles/panel = ExtResource("5_dvx6y")
[node name="NetworkInfo" type="VBoxContainer" parent="NetworkPanel"]
layout_mode = 0
offset_left = 8.0
offset_right = 124.0
offset_bottom = 50.0
[node name="NetworkInfo" type="HBoxContainer" parent="NetworkPanel"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/separation = 50
alignment = 1
[node name="NetworkSideDisplay" type="Label" parent="NetworkPanel/NetworkInfo"]
layout_mode = 2
text = "Network Side"
horizontal_alignment = 1
vertical_alignment = 1
[node name="UniquePeerID" type="Label" parent="NetworkPanel/NetworkInfo"]
layout_mode = 2
@@ -108,10 +113,10 @@ vertical_alignment = 1
anchors_preset = 4
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = 10.0
offset_top = -120.0
offset_right = 216.0
offset_bottom = 120.0
offset_left = 19.0
offset_top = -233.0
offset_right = 232.0
offset_bottom = 7.0
grow_vertical = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_playerboard")
@@ -120,10 +125,10 @@ clip_contents = true
anchors_preset = 4
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = 18.0
offset_top = -88.0
offset_right = 208.0
offset_bottom = 102.0
offset_left = 28.0
offset_top = -199.0
offset_right = 224.0
offset_bottom = -3.0
grow_vertical = 2
size_flags_horizontal = 3
columns = 5
@@ -978,7 +983,43 @@ grow_horizontal = 2
grow_vertical = 2
texture = ExtResource("9_6gcb6")
[node name="PowerUpBar" type="PanelContainer" parent="."]
anchors_preset = 4
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = 29.0
offset_top = -228.0
offset_right = 149.0
offset_bottom = -202.0
grow_vertical = 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 = 18
text = "⚡"
[node name="Segment0" type="Panel" parent="PowerUpBar/HBox"]
custom_minimum_size = Vector2(20, 16)
layout_mode = 2
[node name="Segment1" type="Panel" parent="PowerUpBar/HBox"]
custom_minimum_size = Vector2(20, 16)
layout_mode = 2
[node name="Segment2" type="Panel" parent="PowerUpBar/HBox"]
custom_minimum_size = Vector2(20, 16)
layout_mode = 2
[node name="Segment3" type="Panel" parent="PowerUpBar/HBox"]
custom_minimum_size = Vector2(20, 16)
layout_mode = 2
[node name="ActionMenu" type="Control" parent="."]
visible = false
layout_mode = 3
anchors_preset = 3
anchor_left = 1.0
@@ -9334,9 +9375,9 @@ anchor_top = 1.0
anchor_right = 0.5
anchor_bottom = 1.0
offset_left = -200.0
offset_top = -99.0
offset_top = -87.0
offset_right = 200.0
offset_bottom = -41.0
offset_bottom = -53.0
grow_horizontal = 2
grow_vertical = 0
theme_override_styles/panel = ExtResource("5_dvx6y")
@@ -9352,57 +9393,22 @@ theme_override_constants/margin_bottom = 5
layout_mode = 2
theme_override_constants/separation = 4
[node name="PowerUpBar" type="PanelContainer" parent="."]
anchors_preset = 4
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = 18.0
offset_top = -115.0
offset_right = 110.0
offset_bottom = -92.0
grow_vertical = 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 = 18
text = "⚡"
[node name="Segment0" type="Panel" parent="PowerUpBar/HBox"]
custom_minimum_size = Vector2(20, 16)
layout_mode = 2
[node name="Segment1" type="Panel" parent="PowerUpBar/HBox"]
custom_minimum_size = Vector2(20, 16)
layout_mode = 2
[node name="Segment2" type="Panel" parent="PowerUpBar/HBox"]
custom_minimum_size = Vector2(20, 16)
layout_mode = 2
[node name="Segment3" type="Panel" parent="PowerUpBar/HBox"]
custom_minimum_size = Vector2(20, 16)
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
offset_left = -218.0
offset_top = 15.0
offset_right = -18.0
offset_bottom = 215.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
theme_override_constants/margin_left = 18
theme_override_constants/margin_top = 18
theme_override_constants/margin_right = 18
theme_override_constants/margin_bottom = 18
[node name="VBox" type="VBoxContainer" parent="LeaderboardPanel/MarginContainer"]
layout_mode = 2