From 10aa309148f572450007f96931502a54a3cb56bd Mon Sep 17 00:00:00 2001 From: Yogi Wiguna Date: Fri, 6 Feb 2026 13:48:18 +0800 Subject: [PATCH] feat: Introduce lobby functionality with UI, server selection, and game state management. --- scenes/lobby.gd | 30 ++- scenes/lobby.tscn | 278 +++++++++++++++++++++++-- scenes/main.gd | 110 ++++++---- scripts/managers/game_state_manager.gd | 2 +- 4 files changed, 352 insertions(+), 68 deletions(-) diff --git a/scenes/lobby.gd b/scenes/lobby.gd index 15a1c85..27bb39d 100644 --- a/scenes/lobby.gd +++ b/scenes/lobby.gd @@ -41,6 +41,7 @@ extends Control # UI References - Player Slots @onready var players_container = $LobbyPanel/PlayersContainer +@onready var players_container2 = $LobbyPanel/PlayersContainer2 @onready var player_slots: Array[Control] = [] # UI References - Area Selector @@ -224,23 +225,34 @@ func _setup_scarcity_ui() -> void: # Add to scene settings_section.add_child(scarcity_option) settings_section.add_child(scarcity_label) - - # Initial visibility update will handle showing correct one func _setup_player_slots() -> void: """Get references to all player slot nodes.""" player_slots.clear() + + # Slots 1-4 in Container 1 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)) + _connect_slot_signals(slot, i) + + # Slots 5-8 in Container 2 + if players_container2: + for i in range(5, 9): + var slot = players_container2.get_node_or_null("PlayerSlot%d" % i) + if slot: + player_slots.append(slot) + _connect_slot_signals(slot, i) + +func _connect_slot_signals(slot: Control, i: int): + # 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 diff --git a/scenes/lobby.tscn b/scenes/lobby.tscn index 8b2f8b9..2a505a5 100644 --- a/scenes/lobby.tscn +++ b/scenes/lobby.tscn @@ -220,7 +220,6 @@ layout_mode = 2 text = "PROFILE" [node name="LobbyPanel" type="Control" parent="." unique_id=1745714811] -visible = false layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 @@ -382,10 +381,10 @@ 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 +offset_left = 363.0 +offset_top = 84.0 +offset_right = 663.0 +offset_bottom = 144.0 grow_horizontal = 2 [node name="HostBannerLabel" type="Label" parent="LobbyPanel/HostBanner" unique_id=1011182924] @@ -418,10 +417,10 @@ anchor_left = 0.5 anchor_top = 0.5 anchor_right = 0.5 anchor_bottom = 0.5 -offset_left = -450.0 -offset_top = -120.0 -offset_right = 450.0 -offset_bottom = 200.0 +offset_left = -678.0 +offset_top = -299.0 +offset_right = 222.0 +offset_bottom = 21.0 grow_horizontal = 2 grow_vertical = 2 theme_override_constants/separation = 30 @@ -488,7 +487,6 @@ text = "NOT READY" horizontal_alignment = 1 [node name="PlayerSlot2" type="VBoxContainer" parent="LobbyPanel/PlayersContainer" unique_id=1012954493] -visible = false custom_minimum_size = Vector2(180, 0) layout_mode = 2 theme_override_constants/separation = 8 @@ -550,7 +548,6 @@ text = "NOT READY" horizontal_alignment = 1 [node name="PlayerSlot3" type="VBoxContainer" parent="LobbyPanel/PlayersContainer" unique_id=1033758492] -visible = false custom_minimum_size = Vector2(180, 0) layout_mode = 2 theme_override_constants/separation = 8 @@ -612,7 +609,6 @@ text = "NOT READY" horizontal_alignment = 1 [node name="PlayerSlot4" type="VBoxContainer" parent="LobbyPanel/PlayersContainer" unique_id=62963750] -visible = false custom_minimum_size = Vector2(180, 0) layout_mode = 2 theme_override_constants/separation = 8 @@ -673,6 +669,264 @@ theme_override_font_sizes/font_size = 12 text = "NOT READY" horizontal_alignment = 1 +[node name="PlayersContainer2" type="HBoxContainer" parent="LobbyPanel" unique_id=76585227] +layout_mode = 1 +anchors_preset = 3 +anchor_left = 1.0 +anchor_top = 1.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = -770.0 +offset_top = -356.0 +offset_right = -38.0 +offset_bottom = -132.0 +grow_horizontal = 0 +grow_vertical = 0 + +[node name="PlayerSlot5" type="VBoxContainer" parent="LobbyPanel/PlayersContainer2" unique_id=1217356581] +custom_minimum_size = Vector2(180, 0) +layout_mode = 2 +theme_override_constants/separation = 8 +alignment = 1 + +[node name="PlayerName5" type="Label" parent="LobbyPanel/PlayersContainer2/PlayerSlot5" unique_id=1274835923] +layout_mode = 2 +theme_override_colors/font_color = Color(1, 1, 1, 1) +theme_override_font_sizes/font_size = 16 +text = "Player 5" +horizontal_alignment = 1 + +[node name="CharacterPreview5" type="TextureRect" parent="LobbyPanel/PlayersContainer2/PlayerSlot5" unique_id=2028052012] +custom_minimum_size = Vector2(140, 140) +layout_mode = 2 +size_flags_horizontal = 4 +expand_mode = 1 +stretch_mode = 5 + +[node name="CharacterNav5" type="HBoxContainer" parent="LobbyPanel/PlayersContainer2/PlayerSlot5" unique_id=955633493] +visible = false +layout_mode = 2 +alignment = 1 + +[node name="CharLeftBtn5" type="Button" parent="LobbyPanel/PlayersContainer2/PlayerSlot5/CharacterNav5" unique_id=1774553781] +custom_minimum_size = Vector2(40, 36) +layout_mode = 2 +theme_override_font_sizes/font_size = 18 +text = "◀" + +[node name="CharacterName5" type="Label" parent="LobbyPanel/PlayersContainer2/PlayerSlot5/CharacterNav5" unique_id=1718870722] +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="CharRightBtn5" type="Button" parent="LobbyPanel/PlayersContainer2/PlayerSlot5/CharacterNav5" unique_id=766449735] +custom_minimum_size = Vector2(40, 36) +layout_mode = 2 +theme_override_font_sizes/font_size = 18 +text = "▶" + +[node name="CharacterNameLabel5" type="Label" parent="LobbyPanel/PlayersContainer2/PlayerSlot5" unique_id=1436179077] +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="ReadyStatus5" type="Label" parent="LobbyPanel/PlayersContainer2/PlayerSlot5" unique_id=1075029896] +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="PlayerSlot6" type="VBoxContainer" parent="LobbyPanel/PlayersContainer2" unique_id=1038068961] +custom_minimum_size = Vector2(180, 0) +layout_mode = 2 +theme_override_constants/separation = 8 +alignment = 1 + +[node name="PlayerName6" type="Label" parent="LobbyPanel/PlayersContainer2/PlayerSlot6" unique_id=2146003972] +layout_mode = 2 +theme_override_colors/font_color = Color(1, 1, 1, 1) +theme_override_font_sizes/font_size = 16 +text = "Player 6" +horizontal_alignment = 1 + +[node name="CharacterPreview6" type="TextureRect" parent="LobbyPanel/PlayersContainer2/PlayerSlot6" unique_id=786683689] +custom_minimum_size = Vector2(140, 140) +layout_mode = 2 +size_flags_horizontal = 4 +expand_mode = 1 +stretch_mode = 5 + +[node name="CharacterNav6" type="HBoxContainer" parent="LobbyPanel/PlayersContainer2/PlayerSlot6" unique_id=344279994] +visible = false +layout_mode = 2 +alignment = 1 + +[node name="CharLeftBtn6" type="Button" parent="LobbyPanel/PlayersContainer2/PlayerSlot6/CharacterNav6" unique_id=1348787091] +custom_minimum_size = Vector2(40, 36) +layout_mode = 2 +theme_override_font_sizes/font_size = 18 +text = "◀" + +[node name="CharacterName6" type="Label" parent="LobbyPanel/PlayersContainer2/PlayerSlot6/CharacterNav6" unique_id=816635166] +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="CharRightBtn6" type="Button" parent="LobbyPanel/PlayersContainer2/PlayerSlot6/CharacterNav6" unique_id=989121448] +custom_minimum_size = Vector2(40, 36) +layout_mode = 2 +theme_override_font_sizes/font_size = 18 +text = "▶" + +[node name="CharacterNameLabel6" type="Label" parent="LobbyPanel/PlayersContainer2/PlayerSlot6" unique_id=2048468168] +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="ReadyStatus6" type="Label" parent="LobbyPanel/PlayersContainer2/PlayerSlot6" unique_id=1888412229] +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="PlayerSlot7" type="VBoxContainer" parent="LobbyPanel/PlayersContainer2" unique_id=1161417031] +custom_minimum_size = Vector2(180, 0) +layout_mode = 2 +theme_override_constants/separation = 8 +alignment = 1 + +[node name="PlayerName7" type="Label" parent="LobbyPanel/PlayersContainer2/PlayerSlot7" unique_id=705333888] +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="CharacterPreview7" type="TextureRect" parent="LobbyPanel/PlayersContainer2/PlayerSlot7" unique_id=2051783230] +custom_minimum_size = Vector2(140, 140) +layout_mode = 2 +size_flags_horizontal = 4 +expand_mode = 1 +stretch_mode = 5 + +[node name="CharacterNav7" type="HBoxContainer" parent="LobbyPanel/PlayersContainer2/PlayerSlot7" unique_id=154400222] +visible = false +layout_mode = 2 +alignment = 1 + +[node name="CharLeftBtn7" type="Button" parent="LobbyPanel/PlayersContainer2/PlayerSlot7/CharacterNav7" unique_id=767340905] +custom_minimum_size = Vector2(40, 36) +layout_mode = 2 +theme_override_font_sizes/font_size = 18 +text = "◀" + +[node name="CharacterName7" type="Label" parent="LobbyPanel/PlayersContainer2/PlayerSlot7/CharacterNav7" unique_id=1897622173] +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="CharRightBtn7" type="Button" parent="LobbyPanel/PlayersContainer2/PlayerSlot7/CharacterNav7" unique_id=981330220] +custom_minimum_size = Vector2(40, 36) +layout_mode = 2 +theme_override_font_sizes/font_size = 18 +text = "▶" + +[node name="CharacterNameLabel7" type="Label" parent="LobbyPanel/PlayersContainer2/PlayerSlot7" unique_id=1783984199] +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="ReadyStatus7" type="Label" parent="LobbyPanel/PlayersContainer2/PlayerSlot7" unique_id=2098796095] +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="PlayerSlot8" type="VBoxContainer" parent="LobbyPanel/PlayersContainer2" unique_id=2019344700] +custom_minimum_size = Vector2(180, 0) +layout_mode = 2 +theme_override_constants/separation = 8 +alignment = 1 + +[node name="PlayerName8" type="Label" parent="LobbyPanel/PlayersContainer2/PlayerSlot8" unique_id=13133480] +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="CharacterPreview8" type="TextureRect" parent="LobbyPanel/PlayersContainer2/PlayerSlot8" unique_id=2109465785] +custom_minimum_size = Vector2(140, 140) +layout_mode = 2 +size_flags_horizontal = 4 +expand_mode = 1 +stretch_mode = 5 + +[node name="CharacterNav8" type="HBoxContainer" parent="LobbyPanel/PlayersContainer2/PlayerSlot8" unique_id=429359375] +visible = false +layout_mode = 2 +alignment = 1 + +[node name="CharLeftBtn8" type="Button" parent="LobbyPanel/PlayersContainer2/PlayerSlot8/CharacterNav8" unique_id=1934659357] +custom_minimum_size = Vector2(40, 36) +layout_mode = 2 +theme_override_font_sizes/font_size = 18 +text = "◀" + +[node name="CharacterName8" type="Label" parent="LobbyPanel/PlayersContainer2/PlayerSlot8/CharacterNav8" unique_id=1417755564] +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="CharRightBtn8" type="Button" parent="LobbyPanel/PlayersContainer2/PlayerSlot8/CharacterNav8" unique_id=513653330] +custom_minimum_size = Vector2(40, 36) +layout_mode = 2 +theme_override_font_sizes/font_size = 18 +text = "▶" + +[node name="CharacterNameLabel8" type="Label" parent="LobbyPanel/PlayersContainer2/PlayerSlot8" unique_id=594360558] +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="ReadyStatus8" type="Label" parent="LobbyPanel/PlayersContainer2/PlayerSlot8" unique_id=434057613] +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" unique_id=696178336] clip_contents = true layout_mode = 1 diff --git a/scenes/main.gd b/scenes/main.gd index 60d5501..895a96f 100644 --- a/scenes/main.gd +++ b/scenes/main.gd @@ -1234,12 +1234,21 @@ func _show_game_over_panel(): player_scores.sort_custom(func(a, b): return a.score > b.score) # Display each player - for i in range(min(player_scores.size(), 4)): + for i in range(min(player_scores.size(), 8)): 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_colors = [ + Color(1.0, 0.84, 0.0), # Gold + Color(0.75, 0.75, 0.75), # Silver + Color(0.8, 0.5, 0.2), # Bronze + Color(0.5, 0.5, 0.5), # 4th + Color(0.5, 0.5, 0.5), # 5th + Color(0.5, 0.5, 0.5), # 6th + Color(0.5, 0.5, 0.5), # 7th + Color(0.5, 0.5, 0.5) # 8th + ] + var rank_emojis = ["🥇", "🥈", "🥉", "4th", "5th", "6th", "7th", "8th"] var rank_label = Label.new() rank_label.text = rank_emojis[i] @@ -1349,33 +1358,7 @@ func sync_leaderboard_data(player_data: Array): 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 = str(data.name) - if score_label: - score_label.text = str(data.score) - - entry.visible = true - - # Highlight check - if data.peer_id == multiplayer.get_unique_id(): - entry.modulate = Color(1.0, 1.0, 0.0) # Yellow - else: - entry.modulate = Color.WHITE - else: - entry.visible = false + _render_leaderboard_entries(player_data) func _update_leaderboard_display(): var leaderboard_panel = get_node_or_null("LeaderboardPanel") @@ -1402,30 +1385,66 @@ func _update_leaderboard_display(): # Sort by score descending player_data.sort_custom(func(a, b): return a.score > b.score) - # Update entries - for i in range(4): # Max 4 entries - var entry = vbox.get_node_or_null("Entry" + str(i + 1)) - if not entry: - continue + _render_leaderboard_entries(player_data) + +func _render_leaderboard_entries(sorted_player_data: Array): + 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: vbox = leaderboard_panel.get_node_or_null("VBox") + if not vbox: return + + var my_id = multiplayer.get_unique_id() + var my_index = -1 + for i in range(sorted_player_data.size()): + if sorted_player_data[i].peer_id == my_id: + my_index = i + break + + # Determine items to display (Max 4 slots for HUD) + var items_to_display = [] + + # add top 3 + for i in range(min(3, sorted_player_data.size())): + items_to_display.append({"data": sorted_player_data[i], "rank": i + 1}) - if i < player_data.size(): - var data = player_data[i] + # add 4th slot (Smart Slot) + if sorted_player_data.size() >= 4: + # If local player is outside top 3 (index > 2), show them in 4th slot + # But if they are exactly 4th (index 3), it's the same as showing 4th place. + # If they are 5th (index 4) or worse, we replace 4th place with them. + + if my_index > 3: + # Show local player + items_to_display.append({"data": sorted_player_data[my_index], "rank": my_index + 1}) + else: + # Show standard 4th place + items_to_display.append({"data": sorted_player_data[3], "rank": 4}) + + # Render + # We assume the HUD has 4 slots max, but code iterates up to 8 just in case UI has more and we want to hide them. + for i in range(8): + var entry = vbox.get_node_or_null("Entry" + str(i + 1)) + if not entry: continue + + # Only show up to 4 entries in this new "Smart" mode + if i < items_to_display.size() and i < 4: + var item = items_to_display[i] + var data = item.data + var rank = item.rank 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 = str(data.name) - if score_label: - score_label.text = str(data.score) + if rank_label: rank_label.text = _get_ordinal(rank) + if name_label: name_label.text = str(data.name) + if score_label: score_label.text = str(data.score) entry.visible = true - # Highlight check - if data.peer_id == multiplayer.get_unique_id(): + if data.peer_id == my_id: entry.modulate = Color(1.0, 1.0, 0.0) # Yellow else: entry.modulate = Color.WHITE @@ -1437,7 +1456,6 @@ func _get_ordinal(n: int) -> String: 1: return "1st" 2: return "2nd" 3: return "3rd" - 4: return "4th" _: return str(n) + "th" # ============================================================================= diff --git a/scripts/managers/game_state_manager.gd b/scripts/managers/game_state_manager.gd index 4445969..8b43449 100644 --- a/scripts/managers/game_state_manager.gd +++ b/scripts/managers/game_state_manager.gd @@ -6,7 +6,7 @@ signal game_started() signal game_state_changed() @export var enable_bots: bool = true -@export var max_players: int = 4 +@export var max_players: int = 8 var players: Array = [] var bots: Array = []