feat: Implement a new lobby system with configurable match duration, game over screen, and core game state management.
This commit is contained in:
+203
-2
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user