feat : Refactored Game Over UI from programmatic to scene-based architecture and resolved critical multiplayer/spawn bugs
This commit is contained in:
+74
-192
@@ -318,19 +318,19 @@ func add_message_to_bar(player_name: String, message: String, type: int = Messag
|
|||||||
var color = Color.WHITE
|
var color = Color.WHITE
|
||||||
match type:
|
match type:
|
||||||
MessageType.POWERUP:
|
MessageType.POWERUP:
|
||||||
icon = "⚡ "
|
icon = "âš¡ "
|
||||||
color = Color(0.4, 1.0, 0.4) # Bright green
|
color = Color(0.4, 1.0, 0.4) # Bright green
|
||||||
MessageType.GOAL:
|
MessageType.GOAL:
|
||||||
icon = "🎯 "
|
icon = "🎯 "
|
||||||
color = Color(1.0, 0.85, 0.2) # Gold
|
color = Color(1.0, 0.85, 0.2) # Gold
|
||||||
MessageType.CYCLE:
|
MessageType.CYCLE:
|
||||||
icon = "⏱️ "
|
icon = "â±ï¸ "
|
||||||
color = Color(0.4, 0.8, 1.0) # Light blue
|
color = Color(0.4, 0.8, 1.0) # Light blue
|
||||||
MessageType.WARNING:
|
MessageType.WARNING:
|
||||||
icon = "⚠️ "
|
icon = "âš ï¸ "
|
||||||
color = Color(1.0, 0.5, 0.3) # Orange
|
color = Color(1.0, 0.5, 0.3) # Orange
|
||||||
_:
|
_:
|
||||||
icon = "💬 "
|
icon = "💬 "
|
||||||
color = Color(0.9, 0.9, 0.9) # Light gray
|
color = Color(0.9, 0.9, 0.9) # Light gray
|
||||||
|
|
||||||
# Include player name in message if provided
|
# Include player name in message if provided
|
||||||
@@ -966,7 +966,7 @@ func _assign_random_spawn_positions():
|
|||||||
print("Assigned spawn %s to player %s" % [assigned_pos, player.name])
|
print("Assigned spawn %s to player %s" % [assigned_pos, player.name])
|
||||||
|
|
||||||
func _assign_stop_n_go_spawn_positions(all_players: Array):
|
func _assign_stop_n_go_spawn_positions(all_players: Array):
|
||||||
"""Assigns spawns on column 0, starting from row 1 for Stop N Go mode."""
|
"""Assigns spawns in the left columns of the Stop N Go arena, validating walkability."""
|
||||||
# Sort players for deterministic assignment based on ID
|
# Sort players for deterministic assignment based on ID
|
||||||
all_players.sort_custom(func(a, b): return a.name.to_int() < b.name.to_int())
|
all_players.sort_custom(func(a, b): return a.name.to_int() < b.name.to_int())
|
||||||
|
|
||||||
@@ -974,12 +974,24 @@ func _assign_stop_n_go_spawn_positions(all_players: Array):
|
|||||||
if not enhanced_gridmap:
|
if not enhanced_gridmap:
|
||||||
return
|
return
|
||||||
|
|
||||||
var col = 0
|
# Collect valid walkable spawn positions from the leftmost columns
|
||||||
var current_row = 3
|
var valid_spawns: Array[Vector2i] = []
|
||||||
|
for col in range(0, min(5, enhanced_gridmap.columns)): # Check first 5 columns
|
||||||
|
for row in range(enhanced_gridmap.rows):
|
||||||
|
var tile = enhanced_gridmap.get_cell_item(Vector3i(col, 0, row))
|
||||||
|
if tile == 0 or tile == 3: # Walkable or Start
|
||||||
|
valid_spawns.append(Vector2i(col, row))
|
||||||
|
if valid_spawns.size() >= all_players.size():
|
||||||
|
break
|
||||||
|
|
||||||
for player in all_players:
|
# Fallback: if somehow no valid spawns found, use old logic
|
||||||
var assigned_pos = Vector2i(col, current_row)
|
if valid_spawns.is_empty():
|
||||||
current_row += 1
|
for row in range(3, 3 + all_players.size()):
|
||||||
|
valid_spawns.append(Vector2i(0, row))
|
||||||
|
|
||||||
|
for i in range(all_players.size()):
|
||||||
|
var player = all_players[i]
|
||||||
|
var assigned_pos = valid_spawns[i % valid_spawns.size()]
|
||||||
|
|
||||||
# Ensure immediate sync
|
# Ensure immediate sync
|
||||||
player.position = player.grid_to_world(assigned_pos)
|
player.position = player.grid_to_world(assigned_pos)
|
||||||
@@ -1903,6 +1915,13 @@ func sync_full_grid_data(data: PackedInt32Array):
|
|||||||
add_child(portal_mode_manager)
|
add_child(portal_mode_manager)
|
||||||
portal_mode_manager.initialize(self , enhanced_gridmap)
|
portal_mode_manager.initialize(self , enhanced_gridmap)
|
||||||
portal_mode_manager.setup_arena_locally()
|
portal_mode_manager.setup_arena_locally()
|
||||||
|
else:
|
||||||
|
# Freemode: Ensure Floor 0 is entirely walkable (reset stale state from previous modes)
|
||||||
|
for x in range(enhanced_gridmap.columns):
|
||||||
|
for z in range(enhanced_gridmap.rows):
|
||||||
|
var f0_item = enhanced_gridmap.get_cell_item(Vector3i(x, 0, z))
|
||||||
|
if f0_item == -1 or (f0_item >= 7 and f0_item <= 20):
|
||||||
|
enhanced_gridmap.set_cell_item(Vector3i(x, 0, z), 0)
|
||||||
|
|
||||||
# Apply the synced data to Floor 1
|
# Apply the synced data to Floor 1
|
||||||
enhanced_gridmap.set_floor_data(1, data)
|
enhanced_gridmap.set_floor_data(1, data)
|
||||||
@@ -2089,13 +2108,13 @@ func _freeze_node_recursive(node: Node):
|
|||||||
_freeze_node_recursive(child)
|
_freeze_node_recursive(child)
|
||||||
|
|
||||||
func _show_game_over_panel():
|
func _show_game_over_panel():
|
||||||
"""Create and display the game over panel with final leaderboard."""
|
"""Instantiate and display the game over panel scene."""
|
||||||
# Check if panel already exists
|
# Check if panel already exists
|
||||||
var existing_panel = get_node_or_null("GameOverPanel")
|
var existing_layer = get_node_or_null("GameOverLayer")
|
||||||
if existing_panel:
|
if existing_layer:
|
||||||
existing_panel.show()
|
existing_layer.show()
|
||||||
return
|
return
|
||||||
|
|
||||||
# Hide Gameplay UI
|
# Hide Gameplay UI
|
||||||
var actions_btn = get_node_or_null("TouchControls/TouchControls/ActionsBtn")
|
var actions_btn = get_node_or_null("TouchControls/TouchControls/ActionsBtn")
|
||||||
if actions_btn: actions_btn.hide()
|
if actions_btn: actions_btn.hide()
|
||||||
@@ -2108,209 +2127,72 @@ func _show_game_over_panel():
|
|||||||
|
|
||||||
if stop_n_go_manager and stop_n_go_manager.hud_layer:
|
if stop_n_go_manager and stop_n_go_manager.hud_layer:
|
||||||
stop_n_go_manager.hud_layer.hide()
|
stop_n_go_manager.hud_layer.hide()
|
||||||
|
|
||||||
if portal_mode_manager and portal_mode_manager.hud_layer:
|
if portal_mode_manager and portal_mode_manager.hud_layer:
|
||||||
portal_mode_manager.hud_layer.hide()
|
portal_mode_manager.hud_layer.hide()
|
||||||
|
|
||||||
# Create game over panel
|
# =========================================================================
|
||||||
var panel = PanelContainer.new()
|
# Gather + sort player data
|
||||||
panel.name = "GameOverPanel"
|
# =========================================================================
|
||||||
panel.set_anchors_preset(Control.PRESET_FULL_RECT)
|
var all_player_scores = []
|
||||||
|
|
||||||
# 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 VBOX
|
|
||||||
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)
|
|
||||||
|
|
||||||
var player_scores = []
|
|
||||||
for p in get_tree().get_nodes_in_group("Players"):
|
for p in get_tree().get_nodes_in_group("Players"):
|
||||||
player_scores.append({
|
var pid = p.name.to_int()
|
||||||
"peer_id": p.name.to_int(),
|
all_player_scores.append({
|
||||||
|
"peer_id": pid,
|
||||||
"name": p.display_name if not p.display_name.is_empty() else str(p.name),
|
"name": p.display_name if not p.display_name.is_empty() else str(p.name),
|
||||||
"score": goals_cycle_manager.get_player_score(p.name.to_int()) if goals_cycle_manager else 0
|
"score": goals_cycle_manager.get_player_score(pid) if goals_cycle_manager else 0,
|
||||||
|
"goal_count": goals_cycle_manager.player_goal_counts.get(pid, 0) if goals_cycle_manager else 0,
|
||||||
|
"character": p._selected_character if "_selected_character" in p else "Masbro"
|
||||||
})
|
})
|
||||||
|
|
||||||
# Custom Sort for Stop n Go and Tekton Doors: Winner always first
|
# Sort players by score (with winner priority for special modes)
|
||||||
if LobbyManager.game_mode == "Stop n Go" and stop_n_go_winner_id != -1:
|
if LobbyManager.game_mode == "Stop n Go" and stop_n_go_winner_id != -1:
|
||||||
player_scores.sort_custom(func(a, b):
|
all_player_scores.sort_custom(func(a, b):
|
||||||
if a.peer_id == stop_n_go_winner_id: return true
|
if a.peer_id == stop_n_go_winner_id: return true
|
||||||
if b.peer_id == stop_n_go_winner_id: return false
|
if b.peer_id == stop_n_go_winner_id: return false
|
||||||
return a.score > b.score
|
return a.score > b.score
|
||||||
)
|
)
|
||||||
elif LobbyManager.game_mode == "Tekton Doors" and portal_mode_winner_id != -1:
|
elif LobbyManager.game_mode == "Tekton Doors" and portal_mode_winner_id != -1:
|
||||||
player_scores.sort_custom(func(a, b):
|
all_player_scores.sort_custom(func(a, b):
|
||||||
if a.peer_id == portal_mode_winner_id: return true
|
if a.peer_id == portal_mode_winner_id: return true
|
||||||
if b.peer_id == portal_mode_winner_id: return false
|
if b.peer_id == portal_mode_winner_id: return false
|
||||||
return a.score > b.score
|
return a.score > b.score
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
player_scores.sort_custom(func(a, b): return a.score > b.score)
|
all_player_scores.sort_custom(func(a, b): return a.score > b.score)
|
||||||
|
|
||||||
# Display each player
|
# =========================================================================
|
||||||
|
# Instantiate the scene
|
||||||
|
# =========================================================================
|
||||||
|
var panel_scene = load("res://scenes/ui/game_over_panel.tscn")
|
||||||
|
if not panel_scene:
|
||||||
|
push_error("[Main] Failed to load game_over_panel.tscn")
|
||||||
|
return
|
||||||
|
|
||||||
|
# CanvasLayer for proper z-ordering
|
||||||
|
var canvas_layer = CanvasLayer.new()
|
||||||
|
canvas_layer.name = "GameOverLayer"
|
||||||
|
canvas_layer.layer = 50
|
||||||
|
add_child(canvas_layer)
|
||||||
|
|
||||||
|
var panel = panel_scene.instantiate()
|
||||||
|
canvas_layer.add_child(panel)
|
||||||
|
|
||||||
|
# Populate data
|
||||||
var local_peer_id = multiplayer.get_unique_id()
|
var local_peer_id = multiplayer.get_unique_id()
|
||||||
var local_player_rank = -1
|
panel.setup(all_player_scores, local_peer_id)
|
||||||
|
|
||||||
# Find local player rank in sorted list
|
# Connect signals
|
||||||
for i in range(player_scores.size()):
|
panel.back_pressed.connect(_on_back_to_menu_pressed)
|
||||||
if player_scores[i].peer_id == local_peer_id:
|
panel.rematch_pressed.connect(func():
|
||||||
local_player_rank = i
|
|
||||||
break
|
|
||||||
|
|
||||||
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
|
|
||||||
]
|
|
||||||
var rank_emojis = ["🥇", "🥈", "🥉"]
|
|
||||||
|
|
||||||
# Helper to create a leaderboard entry HBox
|
|
||||||
var create_entry = func(rank_idx: int):
|
|
||||||
var entry = HBoxContainer.new()
|
|
||||||
entry.add_theme_constant_override("separation", 20)
|
|
||||||
|
|
||||||
var score_data = player_scores[rank_idx]
|
|
||||||
var is_local = score_data.peer_id == local_peer_id
|
|
||||||
|
|
||||||
var rank_label = Label.new()
|
|
||||||
if rank_idx < 3:
|
|
||||||
rank_label.text = rank_emojis[rank_idx]
|
|
||||||
else:
|
|
||||||
# Ordinal rank (4th, 5th, etc.)
|
|
||||||
var n = rank_idx + 1
|
|
||||||
var suffix = "th"
|
|
||||||
if n % 10 == 1 and n % 100 != 11: suffix = "st"
|
|
||||||
elif n % 10 == 2 and n % 100 != 12: suffix = "nd"
|
|
||||||
elif n % 10 == 3 and n % 100 != 13: suffix = "rd"
|
|
||||||
rank_label.text = str(n) + suffix
|
|
||||||
|
|
||||||
rank_label.add_theme_font_size_override("font_size", 32)
|
|
||||||
entry.add_child(rank_label)
|
|
||||||
|
|
||||||
var name_label = Label.new()
|
|
||||||
name_label.text = score_data.name + (" (YOU)" if is_local else "")
|
|
||||||
name_label.add_theme_font_size_override("font_size", 28)
|
|
||||||
if rank_idx < 3:
|
|
||||||
name_label.add_theme_color_override("font_color", rank_colors[rank_idx])
|
|
||||||
elif is_local:
|
|
||||||
name_label.add_theme_color_override("font_color", Color(0.4, 0.7, 1.0)) # Blue for local player
|
|
||||||
|
|
||||||
name_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
||||||
entry.add_child(name_label)
|
|
||||||
|
|
||||||
var score_label = Label.new()
|
|
||||||
score_label.text = str(score_data.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)
|
|
||||||
|
|
||||||
return entry
|
|
||||||
|
|
||||||
# 1. Show Top 3
|
|
||||||
for i in range(min(player_scores.size(), 3)):
|
|
||||||
leaderboard_container.add_child(create_entry.call(i))
|
|
||||||
|
|
||||||
# 2. Show Local Player if NOT in Top 3
|
|
||||||
if local_player_rank >= 3:
|
|
||||||
# Add a separator
|
|
||||||
var separator = ColorRect.new()
|
|
||||||
separator.custom_minimum_size = Vector2(0, 2)
|
|
||||||
separator.color = Color(1, 1, 1, 0.2)
|
|
||||||
leaderboard_container.add_child(separator)
|
|
||||||
|
|
||||||
# Add local player entry
|
|
||||||
leaderboard_container.add_child(create_entry.call(local_player_rank))
|
|
||||||
|
|
||||||
# 3. Rematch Option
|
|
||||||
var rematch_container = HBoxContainer.new()
|
|
||||||
rematch_container.alignment = BoxContainer.ALIGNMENT_CENTER
|
|
||||||
rematch_container.add_theme_constant_override("separation", 20)
|
|
||||||
inner_vbox.add_child(rematch_container)
|
|
||||||
|
|
||||||
var rematch_btn = Button.new()
|
|
||||||
rematch_btn.name = "RematchBtn"
|
|
||||||
rematch_btn.text = "REMATCH"
|
|
||||||
rematch_btn.custom_minimum_size = Vector2(200, 60)
|
|
||||||
rematch_btn.add_theme_font_size_override("font_size", 20)
|
|
||||||
rematch_btn.pressed.connect(func():
|
|
||||||
rematch_btn.disabled = true
|
|
||||||
rematch_btn.text = "VOTED"
|
|
||||||
LobbyManager.request_rematch.rpc(multiplayer.get_unique_id())
|
LobbyManager.request_rematch.rpc(multiplayer.get_unique_id())
|
||||||
)
|
)
|
||||||
rematch_container.add_child(rematch_btn)
|
|
||||||
|
|
||||||
var rematch_label = Label.new()
|
|
||||||
rematch_label.name = "RematchVoteLabel"
|
|
||||||
rematch_label.text = "0/2"
|
|
||||||
rematch_label.add_theme_font_size_override("font_size", 24)
|
|
||||||
rematch_label.add_theme_color_override("font_color", Color(0.7, 0.7, 0.7))
|
|
||||||
rematch_container.add_child(rematch_label)
|
|
||||||
|
|
||||||
LobbyManager.rematch_votes_updated.connect(func(count, required):
|
LobbyManager.rematch_votes_updated.connect(func(count, required):
|
||||||
if is_instance_valid(rematch_label):
|
if is_instance_valid(panel):
|
||||||
rematch_label.text = "%d/%d" % [count, required]
|
panel.update_rematch_votes(count, required)
|
||||||
if count >= required:
|
|
||||||
rematch_label.add_theme_color_override("font_color", Color.GREEN)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# 4. 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)
|
|
||||||
|
|
||||||
# Center the button in a container
|
|
||||||
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():
|
func _on_back_to_menu_pressed():
|
||||||
"""Return to lobby/main menu and clean up game state."""
|
"""Return to lobby/main menu and clean up game state."""
|
||||||
print("[Main] Returning to lobby...")
|
print("[Main] Returning to lobby...")
|
||||||
|
|||||||
@@ -0,0 +1,504 @@
|
|||||||
|
[gd_scene format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://scripts/ui/game_over_panel.gd" id="1_script"]
|
||||||
|
[ext_resource type="FontFile" uid="uid://xnjx058n4tsw" path="res://assets/fonts/Nougat-ExtraBlack.ttf" id="2_font"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://ejeamn0pyey4" path="res://assets/characters/Bob.glb" id="3_bob"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://d4cul3w3wem5w" path="res://assets/characters/Gatot.glb" id="4_gatot"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://1vk0mjnwkngi" path="res://assets/characters/Masbro.glb" id="5_masbro"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://bmln7v6v5kvxg" path="res://assets/characters/Oldpop.glb" id="6_oldpop"]
|
||||||
|
[ext_resource type="AnimationLibrary" uid="uid://c3pyopnwibckj" path="res://assets/characters/animations/animation-pack.res" id="7_animlib"]
|
||||||
|
|
||||||
|
[sub_resource type="Environment" id="Environment_preview"]
|
||||||
|
background_mode = 1
|
||||||
|
background_color = Color(0.06, 0.07, 0.1, 0)
|
||||||
|
ambient_light_source = 2
|
||||||
|
ambient_light_color = Color(0.5, 0.55, 0.75, 1)
|
||||||
|
ambient_light_energy = 0.7
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_header"]
|
||||||
|
bg_color = Color(0.95, 0.75, 0.1, 1)
|
||||||
|
corner_radius_top_left = 8
|
||||||
|
corner_radius_top_right = 8
|
||||||
|
content_margin_left = 16.0
|
||||||
|
content_margin_top = 8.0
|
||||||
|
content_margin_right = 16.0
|
||||||
|
content_margin_bottom = 8.0
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_content"]
|
||||||
|
bg_color = Color(0.08, 0.1, 0.2, 0.95)
|
||||||
|
corner_radius_bottom_left = 8
|
||||||
|
content_margin_left = 20.0
|
||||||
|
content_margin_top = 16.0
|
||||||
|
content_margin_right = 20.0
|
||||||
|
content_margin_bottom = 16.0
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_back_normal"]
|
||||||
|
bg_color = Color(0.75, 0.15, 0.15, 1)
|
||||||
|
corner_radius_top_left = 6
|
||||||
|
corner_radius_top_right = 6
|
||||||
|
corner_radius_bottom_right = 6
|
||||||
|
corner_radius_bottom_left = 6
|
||||||
|
content_margin_left = 16.0
|
||||||
|
content_margin_top = 8.0
|
||||||
|
content_margin_right = 16.0
|
||||||
|
content_margin_bottom = 8.0
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_back_hover"]
|
||||||
|
bg_color = Color(0.9, 0.2, 0.2, 1)
|
||||||
|
corner_radius_top_left = 6
|
||||||
|
corner_radius_top_right = 6
|
||||||
|
corner_radius_bottom_right = 6
|
||||||
|
corner_radius_bottom_left = 6
|
||||||
|
content_margin_left = 16.0
|
||||||
|
content_margin_top = 8.0
|
||||||
|
content_margin_right = 16.0
|
||||||
|
content_margin_bottom = 8.0
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_back_pressed"]
|
||||||
|
bg_color = Color(0.6, 0.1, 0.1, 1)
|
||||||
|
corner_radius_top_left = 6
|
||||||
|
corner_radius_top_right = 6
|
||||||
|
corner_radius_bottom_right = 6
|
||||||
|
corner_radius_bottom_left = 6
|
||||||
|
content_margin_left = 16.0
|
||||||
|
content_margin_top = 8.0
|
||||||
|
content_margin_right = 16.0
|
||||||
|
content_margin_bottom = 8.0
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_rematch_normal"]
|
||||||
|
bg_color = Color(0.15, 0.7, 0.15, 1)
|
||||||
|
corner_radius_top_left = 6
|
||||||
|
corner_radius_top_right = 6
|
||||||
|
corner_radius_bottom_right = 6
|
||||||
|
corner_radius_bottom_left = 6
|
||||||
|
content_margin_left = 16.0
|
||||||
|
content_margin_top = 8.0
|
||||||
|
content_margin_right = 16.0
|
||||||
|
content_margin_bottom = 8.0
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_rematch_hover"]
|
||||||
|
bg_color = Color(0.2, 0.85, 0.2, 1)
|
||||||
|
corner_radius_top_left = 6
|
||||||
|
corner_radius_top_right = 6
|
||||||
|
corner_radius_bottom_right = 6
|
||||||
|
corner_radius_bottom_left = 6
|
||||||
|
content_margin_left = 16.0
|
||||||
|
content_margin_top = 8.0
|
||||||
|
content_margin_right = 16.0
|
||||||
|
content_margin_bottom = 8.0
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_rematch_pressed"]
|
||||||
|
bg_color = Color(0.1, 0.55, 0.1, 1)
|
||||||
|
corner_radius_top_left = 6
|
||||||
|
corner_radius_top_right = 6
|
||||||
|
corner_radius_bottom_right = 6
|
||||||
|
corner_radius_bottom_left = 6
|
||||||
|
content_margin_left = 16.0
|
||||||
|
content_margin_top = 8.0
|
||||||
|
content_margin_right = 16.0
|
||||||
|
content_margin_bottom = 8.0
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_xp_bg"]
|
||||||
|
bg_color = Color(0.15, 0.15, 0.15, 1)
|
||||||
|
corner_radius_top_left = 4
|
||||||
|
corner_radius_top_right = 4
|
||||||
|
corner_radius_bottom_right = 4
|
||||||
|
corner_radius_bottom_left = 4
|
||||||
|
|
||||||
|
[node name="GameOverPanel" type="Control"]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
script = ExtResource("1_script")
|
||||||
|
|
||||||
|
[node name="Background" type="ColorRect" parent="."]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
color = Color(0.22, 0.22, 0.22, 0.95)
|
||||||
|
|
||||||
|
[node name="MainMargin" type="MarginContainer" parent="."]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
theme_override_constants/margin_left = 30
|
||||||
|
theme_override_constants/margin_top = 20
|
||||||
|
theme_override_constants/margin_right = 30
|
||||||
|
theme_override_constants/margin_bottom = 20
|
||||||
|
|
||||||
|
[node name="MainHBox" type="HBoxContainer" parent="MainMargin"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 20
|
||||||
|
|
||||||
|
[node name="LeftSide" type="VBoxContainer" parent="MainMargin/MainHBox"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_stretch_ratio = 1.4
|
||||||
|
theme_override_constants/separation = 0
|
||||||
|
|
||||||
|
[node name="HeaderPanel" type="PanelContainer" parent="MainMargin/MainHBox/LeftSide"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_styles/panel = SubResource("StyleBoxFlat_header")
|
||||||
|
|
||||||
|
[node name="HeaderHBox" type="HBoxContainer" parent="MainMargin/MainHBox/LeftSide/HeaderPanel"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 10
|
||||||
|
|
||||||
|
[node name="TabTitleLabel" type="Label" parent="MainMargin/MainHBox/LeftSide/HeaderPanel/HeaderHBox"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_colors/font_color = Color(0.15, 0.1, 0, 1)
|
||||||
|
theme_override_fonts/font = ExtResource("2_font")
|
||||||
|
theme_override_font_sizes/font_size = 28
|
||||||
|
text = "RACE RESULT"
|
||||||
|
|
||||||
|
[node name="BodyHBox" type="HBoxContainer" parent="MainMargin/MainHBox/LeftSide"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
theme_override_constants/separation = 8
|
||||||
|
|
||||||
|
[node name="ContentPanel" type="PanelContainer" parent="MainMargin/MainHBox/LeftSide/BodyHBox"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
theme_override_styles/panel = SubResource("StyleBoxFlat_content")
|
||||||
|
|
||||||
|
[node name="ContentStack" type="Control" parent="MainMargin/MainHBox/LeftSide/BodyHBox/ContentPanel"]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
|
||||||
|
[node name="RaceResultContent" type="VBoxContainer" parent="MainMargin/MainHBox/LeftSide/BodyHBox/ContentPanel/ContentStack"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
theme_override_constants/separation = 12
|
||||||
|
|
||||||
|
[node name="PlayerInfoHBox" type="HBoxContainer" parent="MainMargin/MainHBox/LeftSide/BodyHBox/ContentPanel/ContentStack/RaceResultContent"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 12
|
||||||
|
|
||||||
|
[node name="PlayerRankLabel" type="Label" parent="MainMargin/MainHBox/LeftSide/BodyHBox/ContentPanel/ContentStack/RaceResultContent/PlayerInfoHBox"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_color = Color(0.95, 0.75, 0.1, 1)
|
||||||
|
theme_override_fonts/font = ExtResource("2_font")
|
||||||
|
theme_override_font_sizes/font_size = 32
|
||||||
|
text = "1st"
|
||||||
|
|
||||||
|
[node name="PlayerNameLabel" type="Label" parent="MainMargin/MainHBox/LeftSide/BodyHBox/ContentPanel/ContentStack/RaceResultContent/PlayerInfoHBox"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_color = Color(1, 1, 1, 1)
|
||||||
|
theme_override_fonts/font = ExtResource("2_font")
|
||||||
|
theme_override_font_sizes/font_size = 32
|
||||||
|
text = "Player"
|
||||||
|
|
||||||
|
[node name="PlayerScoreLabel" type="Label" parent="MainMargin/MainHBox/LeftSide/BodyHBox/ContentPanel/ContentStack/RaceResultContent/PlayerInfoHBox"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_colors/font_color = Color(1, 1, 1, 1)
|
||||||
|
theme_override_fonts/font = ExtResource("2_font")
|
||||||
|
theme_override_font_sizes/font_size = 40
|
||||||
|
text = "0"
|
||||||
|
horizontal_alignment = 2
|
||||||
|
|
||||||
|
[node name="Sep1" type="HSeparator" parent="MainMargin/MainHBox/LeftSide/BodyHBox/ContentPanel/ContentStack/RaceResultContent"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 4
|
||||||
|
|
||||||
|
[node name="CompletionRow" type="HBoxContainer" parent="MainMargin/MainHBox/LeftSide/BodyHBox/ContentPanel/ContentStack/RaceResultContent"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 20
|
||||||
|
|
||||||
|
[node name="CompletionLabel" type="Label" parent="MainMargin/MainHBox/LeftSide/BodyHBox/ContentPanel/ContentStack/RaceResultContent/CompletionRow"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_colors/font_color = Color(0.8, 0.8, 0.8, 1)
|
||||||
|
theme_override_fonts/font = ExtResource("2_font")
|
||||||
|
theme_override_font_sizes/font_size = 24
|
||||||
|
text = "COMPLETION"
|
||||||
|
|
||||||
|
[node name="CompletionValue" type="Label" parent="MainMargin/MainHBox/LeftSide/BodyHBox/ContentPanel/ContentStack/RaceResultContent/CompletionRow"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_color = Color(1, 1, 1, 1)
|
||||||
|
theme_override_fonts/font = ExtResource("2_font")
|
||||||
|
theme_override_font_sizes/font_size = 24
|
||||||
|
text = "0x"
|
||||||
|
horizontal_alignment = 2
|
||||||
|
|
||||||
|
[node name="ScoreRow" type="HBoxContainer" parent="MainMargin/MainHBox/LeftSide/BodyHBox/ContentPanel/ContentStack/RaceResultContent"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 20
|
||||||
|
|
||||||
|
[node name="ScoreLabel" type="Label" parent="MainMargin/MainHBox/LeftSide/BodyHBox/ContentPanel/ContentStack/RaceResultContent/ScoreRow"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_colors/font_color = Color(0.8, 0.8, 0.8, 1)
|
||||||
|
theme_override_fonts/font = ExtResource("2_font")
|
||||||
|
theme_override_font_sizes/font_size = 24
|
||||||
|
text = "SCORE"
|
||||||
|
|
||||||
|
[node name="ScoreValue" type="Label" parent="MainMargin/MainHBox/LeftSide/BodyHBox/ContentPanel/ContentStack/RaceResultContent/ScoreRow"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_color = Color(1, 1, 1, 1)
|
||||||
|
theme_override_fonts/font = ExtResource("2_font")
|
||||||
|
theme_override_font_sizes/font_size = 24
|
||||||
|
text = "0"
|
||||||
|
horizontal_alignment = 2
|
||||||
|
|
||||||
|
[node name="RankRow" type="HBoxContainer" parent="MainMargin/MainHBox/LeftSide/BodyHBox/ContentPanel/ContentStack/RaceResultContent"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 20
|
||||||
|
|
||||||
|
[node name="RankLabel" type="Label" parent="MainMargin/MainHBox/LeftSide/BodyHBox/ContentPanel/ContentStack/RaceResultContent/RankRow"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_colors/font_color = Color(0.8, 0.8, 0.8, 1)
|
||||||
|
theme_override_fonts/font = ExtResource("2_font")
|
||||||
|
theme_override_font_sizes/font_size = 24
|
||||||
|
text = "RANK"
|
||||||
|
|
||||||
|
[node name="RankValue" type="Label" parent="MainMargin/MainHBox/LeftSide/BodyHBox/ContentPanel/ContentStack/RaceResultContent/RankRow"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_color = Color(0.95, 0.75, 0.1, 1)
|
||||||
|
theme_override_fonts/font = ExtResource("2_font")
|
||||||
|
theme_override_font_sizes/font_size = 52
|
||||||
|
text = "1st"
|
||||||
|
horizontal_alignment = 2
|
||||||
|
|
||||||
|
[node name="RankListContent" type="VBoxContainer" parent="MainMargin/MainHBox/LeftSide/BodyHBox/ContentPanel/ContentStack"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
visible = false
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
theme_override_constants/separation = 6
|
||||||
|
|
||||||
|
[node name="WinnerHBox" type="HBoxContainer" parent="MainMargin/MainHBox/LeftSide/BodyHBox/ContentPanel/ContentStack/RankListContent"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 12
|
||||||
|
|
||||||
|
[node name="WinnerRankLabel" type="Label" parent="MainMargin/MainHBox/LeftSide/BodyHBox/ContentPanel/ContentStack/RankListContent/WinnerHBox"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_color = Color(0.95, 0.75, 0.1, 1)
|
||||||
|
theme_override_fonts/font = ExtResource("2_font")
|
||||||
|
theme_override_font_sizes/font_size = 30
|
||||||
|
text = "1st"
|
||||||
|
|
||||||
|
[node name="WinnerNameLabel" type="Label" parent="MainMargin/MainHBox/LeftSide/BodyHBox/ContentPanel/ContentStack/RankListContent/WinnerHBox"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_colors/font_color = Color(1, 1, 1, 1)
|
||||||
|
theme_override_fonts/font = ExtResource("2_font")
|
||||||
|
theme_override_font_sizes/font_size = 30
|
||||||
|
text = "Player"
|
||||||
|
|
||||||
|
[node name="WinnerScoreLabel" type="Label" parent="MainMargin/MainHBox/LeftSide/BodyHBox/ContentPanel/ContentStack/RankListContent/WinnerHBox"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_color = Color(1, 1, 1, 1)
|
||||||
|
theme_override_fonts/font = ExtResource("2_font")
|
||||||
|
theme_override_font_sizes/font_size = 38
|
||||||
|
text = "0"
|
||||||
|
horizontal_alignment = 2
|
||||||
|
|
||||||
|
[node name="Sep2" type="HSeparator" parent="MainMargin/MainHBox/LeftSide/BodyHBox/ContentPanel/ContentStack/RankListContent"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 4
|
||||||
|
|
||||||
|
[node name="RankScroll" type="ScrollContainer" parent="MainMargin/MainHBox/LeftSide/BodyHBox/ContentPanel/ContentStack/RankListContent"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
|
||||||
|
[node name="RankListVBox" type="VBoxContainer" parent="MainMargin/MainHBox/LeftSide/BodyHBox/ContentPanel/ContentStack/RankListContent/RankScroll"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_override_constants/separation = 4
|
||||||
|
|
||||||
|
[node name="TabBtnVBox" type="VBoxContainer" parent="MainMargin/MainHBox/LeftSide/BodyHBox"]
|
||||||
|
custom_minimum_size = Vector2(90, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 8
|
||||||
|
|
||||||
|
[node name="TabSpacer" type="Control" parent="MainMargin/MainHBox/LeftSide/BodyHBox/TabBtnVBox"]
|
||||||
|
custom_minimum_size = Vector2(0, 30)
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="RaceResultTabBtn" type="Button" parent="MainMargin/MainHBox/LeftSide/BodyHBox/TabBtnVBox"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
custom_minimum_size = Vector2(85, 50)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_fonts/font = ExtResource("2_font")
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
|
text = "RACE
|
||||||
|
RESULT"
|
||||||
|
|
||||||
|
[node name="RankListTabBtn" type="Button" parent="MainMargin/MainHBox/LeftSide/BodyHBox/TabBtnVBox"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
custom_minimum_size = Vector2(85, 50)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_fonts/font = ExtResource("2_font")
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
|
text = "RANK
|
||||||
|
LIST"
|
||||||
|
|
||||||
|
[node name="BottomBar" type="HBoxContainer" parent="MainMargin/MainHBox/LeftSide"]
|
||||||
|
custom_minimum_size = Vector2(0, 50)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 20
|
||||||
|
|
||||||
|
[node name="BackBtn" type="Button" parent="MainMargin/MainHBox/LeftSide/BottomBar"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
custom_minimum_size = Vector2(120, 48)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_fonts/font = ExtResource("2_font")
|
||||||
|
theme_override_font_sizes/font_size = 20
|
||||||
|
theme_override_styles/normal = SubResource("StyleBoxFlat_back_normal")
|
||||||
|
theme_override_styles/hover = SubResource("StyleBoxFlat_back_hover")
|
||||||
|
theme_override_styles/pressed = SubResource("StyleBoxFlat_back_pressed")
|
||||||
|
text = "BACK"
|
||||||
|
|
||||||
|
[node name="BottomSpacer" type="Control" parent="MainMargin/MainHBox/LeftSide/BottomBar"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
|
||||||
|
[node name="RightSide" type="VBoxContainer" parent="MainMargin/MainHBox"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_stretch_ratio = 1.0
|
||||||
|
theme_override_constants/separation = 8
|
||||||
|
alignment = 1
|
||||||
|
|
||||||
|
[node name="ViewportContainer" type="SubViewportContainer" parent="MainMargin/MainHBox/RightSide"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
stretch = true
|
||||||
|
|
||||||
|
[node name="PreviewViewport" type="SubViewport" parent="MainMargin/MainHBox/RightSide/ViewportContainer"]
|
||||||
|
transparent_bg = true
|
||||||
|
handle_input_locally = false
|
||||||
|
size = Vector2i(400, 500)
|
||||||
|
render_target_update_mode = 4
|
||||||
|
|
||||||
|
[node name="WorldEnvironment" type="WorldEnvironment" parent="MainMargin/MainHBox/RightSide/ViewportContainer/PreviewViewport"]
|
||||||
|
environment = SubResource("Environment_preview")
|
||||||
|
|
||||||
|
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="MainMargin/MainHBox/RightSide/ViewportContainer/PreviewViewport"]
|
||||||
|
transform = Transform3D(0.866025, -0.25, 0.433013, 0, 0.866025, 0.5, -0.5, -0.433013, 0.75, 0, 4, 0)
|
||||||
|
light_energy = 1.4
|
||||||
|
|
||||||
|
[node name="FillLight" type="OmniLight3D" parent="MainMargin/MainHBox/RightSide/ViewportContainer/PreviewViewport"]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2, 2, 2)
|
||||||
|
light_color = Color(0.4, 0.5, 1, 1)
|
||||||
|
light_energy = 0.5
|
||||||
|
omni_range = 8.0
|
||||||
|
|
||||||
|
[node name="PreviewCamera" type="Camera3D" parent="MainMargin/MainHBox/RightSide/ViewportContainer/PreviewViewport"]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 0.9659259, 0.25881898, 0, -0.25881898, 0.9659259, 0, 0.8380414, 3.2)
|
||||||
|
current = true
|
||||||
|
fov = 40.0
|
||||||
|
|
||||||
|
[node name="CharacterRoot" type="Node3D" parent="MainMargin/MainHBox/RightSide/ViewportContainer/PreviewViewport"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
|
||||||
|
[node name="Masbro" parent="MainMargin/MainHBox/RightSide/ViewportContainer/PreviewViewport/CharacterRoot" instance=ExtResource("5_masbro")]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.485, 0)
|
||||||
|
visible = false
|
||||||
|
|
||||||
|
[node name="Bob" parent="MainMargin/MainHBox/RightSide/ViewportContainer/PreviewViewport/CharacterRoot" instance=ExtResource("3_bob")]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.485, 0)
|
||||||
|
visible = false
|
||||||
|
|
||||||
|
[node name="Gatot" parent="MainMargin/MainHBox/RightSide/ViewportContainer/PreviewViewport/CharacterRoot" instance=ExtResource("4_gatot")]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.485, 0)
|
||||||
|
visible = false
|
||||||
|
|
||||||
|
[node name="Oldpop" parent="MainMargin/MainHBox/RightSide/ViewportContainer/PreviewViewport/CharacterRoot" instance=ExtResource("6_oldpop")]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.485, 0)
|
||||||
|
|
||||||
|
[node name="AnimationPlayer" type="AnimationPlayer" parent="MainMargin/MainHBox/RightSide/ViewportContainer/PreviewViewport/CharacterRoot"]
|
||||||
|
root_node = NodePath("../Oldpop")
|
||||||
|
libraries/animation-pack = ExtResource("7_animlib")
|
||||||
|
autoplay = &"animation-pack/idle"
|
||||||
|
|
||||||
|
[node name="CharNameLabel" type="Label" parent="MainMargin/MainHBox/RightSide"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_color = Color(1, 1, 1, 1)
|
||||||
|
theme_override_fonts/font = ExtResource("2_font")
|
||||||
|
theme_override_font_sizes/font_size = 26
|
||||||
|
text = "PLAYER"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="XPBarHBox" type="HBoxContainer" parent="MainMargin/MainHBox/RightSide"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 8
|
||||||
|
alignment = 1
|
||||||
|
|
||||||
|
[node name="XPIcon" type="ColorRect" parent="MainMargin/MainHBox/RightSide/XPBarHBox"]
|
||||||
|
custom_minimum_size = Vector2(24, 24)
|
||||||
|
layout_mode = 2
|
||||||
|
color = Color(0.85, 0.65, 0.1, 1)
|
||||||
|
|
||||||
|
[node name="XPBarBg" type="PanelContainer" parent="MainMargin/MainHBox/RightSide/XPBarHBox"]
|
||||||
|
custom_minimum_size = Vector2(180, 20)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_styles/panel = SubResource("StyleBoxFlat_xp_bg")
|
||||||
|
|
||||||
|
[node name="XPFill" type="ColorRect" parent="MainMargin/MainHBox/RightSide/XPBarHBox/XPBarBg"]
|
||||||
|
custom_minimum_size = Vector2(120, 16)
|
||||||
|
layout_mode = 2
|
||||||
|
color = Color(0.85, 0.65, 0.1, 1)
|
||||||
|
|
||||||
|
[node name="RematchHBox" type="HBoxContainer" parent="MainMargin/MainHBox/RightSide"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 10
|
||||||
|
alignment = 2
|
||||||
|
|
||||||
|
[node name="RematchBtn" type="Button" parent="MainMargin/MainHBox/RightSide/RematchHBox"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
custom_minimum_size = Vector2(160, 48)
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_fonts/font = ExtResource("2_font")
|
||||||
|
theme_override_font_sizes/font_size = 20
|
||||||
|
theme_override_styles/normal = SubResource("StyleBoxFlat_rematch_normal")
|
||||||
|
theme_override_styles/hover = SubResource("StyleBoxFlat_rematch_hover")
|
||||||
|
theme_override_styles/pressed = SubResource("StyleBoxFlat_rematch_pressed")
|
||||||
|
text = "REMATCH"
|
||||||
|
|
||||||
|
[node name="RematchVoteLabel" type="Label" parent="MainMargin/MainHBox/RightSide/RematchHBox"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_color = Color(0.7, 0.7, 0.7, 1)
|
||||||
|
theme_override_fonts/font = ExtResource("2_font")
|
||||||
|
theme_override_font_sizes/font_size = 18
|
||||||
@@ -844,20 +844,26 @@ func _on_server_disconnected() -> void:
|
|||||||
# Rematch Logic
|
# Rematch Logic
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
|
func _get_connected_human_count() -> int:
|
||||||
|
"""Returns the number of connected real human players (peers + host, excluding bots)."""
|
||||||
|
if multiplayer.has_multiplayer_peer():
|
||||||
|
return multiplayer.get_peers().size() + 1 # peers + self (host)
|
||||||
|
return max(1, players_in_room.size()) # Fallback to lobby list
|
||||||
|
|
||||||
func reset_rematch_votes() -> void:
|
func reset_rematch_votes() -> void:
|
||||||
rematch_votes.clear()
|
rematch_votes.clear()
|
||||||
var required = max(1, ceili(players_in_room.size() / 2.0))
|
var required = max(1, ceili(_get_connected_human_count() / 2.0))
|
||||||
emit_signal("rematch_votes_updated", 0, required)
|
emit_signal("rematch_votes_updated", 0, required)
|
||||||
|
|
||||||
@rpc("any_peer", "call_local", "reliable")
|
@rpc("any_peer", "call_local", "reliable")
|
||||||
func request_rematch(player_id: int) -> void:
|
func request_rematch(player_id: int) -> void:
|
||||||
"""Client requests a rematch. Votes needed depend on connected real players."""
|
"""Client requests a rematch. Votes needed depend on connected real players (excluding bots)."""
|
||||||
if not multiplayer.is_server():
|
if not multiplayer.is_server():
|
||||||
return
|
return
|
||||||
|
|
||||||
if player_id not in rematch_votes:
|
if player_id not in rematch_votes:
|
||||||
rematch_votes.append(player_id)
|
rematch_votes.append(player_id)
|
||||||
var required = max(1, ceili(players_in_room.size() / 2.0))
|
var required = max(1, ceili(_get_connected_human_count() / 2.0))
|
||||||
print("[LobbyManager] Rematch vote from %d. Total: %d/%d" % [player_id, rematch_votes.size(), required])
|
print("[LobbyManager] Rematch vote from %d. Total: %d/%d" % [player_id, rematch_votes.size(), required])
|
||||||
|
|
||||||
# Sync vote count to all clients
|
# Sync vote count to all clients
|
||||||
|
|||||||
@@ -0,0 +1,228 @@
|
|||||||
|
extends Control
|
||||||
|
## End-of-game modal panel with tabbed Race Result / Rank List and 3D character preview.
|
||||||
|
## Instantiated by main.gd at match end. Call setup() to populate data.
|
||||||
|
|
||||||
|
signal back_pressed
|
||||||
|
signal rematch_pressed
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# UI References (unique names from .tscn)
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Header
|
||||||
|
@onready var tab_title_label := %TabTitleLabel as Label
|
||||||
|
|
||||||
|
# Race Result tab
|
||||||
|
@onready var race_result_content := %RaceResultContent as VBoxContainer
|
||||||
|
@onready var player_rank_label := %PlayerRankLabel as Label
|
||||||
|
@onready var player_name_label := %PlayerNameLabel as Label
|
||||||
|
@onready var player_score_label := %PlayerScoreLabel as Label
|
||||||
|
@onready var completion_value := %CompletionValue as Label
|
||||||
|
@onready var score_value := %ScoreValue as Label
|
||||||
|
@onready var rank_value := %RankValue as Label
|
||||||
|
|
||||||
|
# Rank List tab
|
||||||
|
@onready var rank_list_content := %RankListContent as VBoxContainer
|
||||||
|
@onready var winner_rank_label := %WinnerRankLabel as Label
|
||||||
|
@onready var winner_name_label := %WinnerNameLabel as Label
|
||||||
|
@onready var winner_score_label := %WinnerScoreLabel as Label
|
||||||
|
@onready var rank_list_vbox := %RankListVBox as VBoxContainer
|
||||||
|
|
||||||
|
# Tab Buttons
|
||||||
|
@onready var race_result_tab_btn := %RaceResultTabBtn as Button
|
||||||
|
@onready var rank_list_tab_btn := %RankListTabBtn as Button
|
||||||
|
|
||||||
|
# Action Buttons
|
||||||
|
@onready var back_btn := %BackBtn as Button
|
||||||
|
@onready var rematch_btn := %RematchBtn as Button
|
||||||
|
@onready var rematch_vote_label := %RematchVoteLabel as Label
|
||||||
|
|
||||||
|
# 3D Preview
|
||||||
|
@onready var character_root := %CharacterRoot as Node3D
|
||||||
|
@onready var char_name_label := %CharNameLabel as Label
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# State
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
var _anim_player: AnimationPlayer
|
||||||
|
var _nougat_font: Font
|
||||||
|
|
||||||
|
# Maps lobby character name -> GLB node name in the SubViewport
|
||||||
|
const CHAR_NODE_MAP: Dictionary = {
|
||||||
|
"Copper": "Oldpop",
|
||||||
|
"Dabro": "Masbro",
|
||||||
|
"Gatot": "Gatot",
|
||||||
|
"Pip": "Bob"
|
||||||
|
}
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Lifecycle
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
_nougat_font = load("res://assets/fonts/Nougat-ExtraBlack.ttf")
|
||||||
|
|
||||||
|
# Tab button connections
|
||||||
|
race_result_tab_btn.pressed.connect(func(): _switch_tab(true))
|
||||||
|
rank_list_tab_btn.pressed.connect(func(): _switch_tab(false))
|
||||||
|
|
||||||
|
# Action button connections
|
||||||
|
back_btn.pressed.connect(func(): emit_signal("back_pressed"))
|
||||||
|
rematch_btn.pressed.connect(func():
|
||||||
|
rematch_btn.disabled = true
|
||||||
|
rematch_btn.text = "VOTED"
|
||||||
|
emit_signal("rematch_pressed")
|
||||||
|
)
|
||||||
|
|
||||||
|
# Setup 3D preview
|
||||||
|
_setup_3d_preview()
|
||||||
|
|
||||||
|
# Default tab: Race Result
|
||||||
|
_switch_tab(true)
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Tab Switching
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func _switch_tab(show_race_result: bool) -> void:
|
||||||
|
race_result_content.visible = show_race_result
|
||||||
|
rank_list_content.visible = not show_race_result
|
||||||
|
tab_title_label.text = "RACE RESULT" if show_race_result else "RANK LIST"
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Data Population (called by main.gd)
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func setup(player_scores: Array, local_peer_id: int) -> void:
|
||||||
|
"""Populate all UI data. player_scores is a sorted Array of Dicts:
|
||||||
|
{peer_id, name, score, goal_count, character}"""
|
||||||
|
|
||||||
|
# Find local player in sorted list
|
||||||
|
var local_rank := 0
|
||||||
|
var local_score := 0
|
||||||
|
var local_goal_count := 0
|
||||||
|
var local_name := "Player"
|
||||||
|
var local_character := "Masbro"
|
||||||
|
|
||||||
|
for i in range(player_scores.size()):
|
||||||
|
if player_scores[i].peer_id == local_peer_id:
|
||||||
|
local_rank = i
|
||||||
|
local_score = player_scores[i].score
|
||||||
|
local_goal_count = player_scores[i].get("goal_count", 0)
|
||||||
|
local_name = player_scores[i].name
|
||||||
|
local_character = player_scores[i].get("character", "Masbro")
|
||||||
|
break
|
||||||
|
|
||||||
|
# --- Race Result tab ---
|
||||||
|
player_rank_label.text = _get_ordinal(local_rank + 1)
|
||||||
|
player_name_label.text = local_name
|
||||||
|
player_score_label.text = str(local_score)
|
||||||
|
completion_value.text = "%dx" % local_goal_count
|
||||||
|
score_value.text = str(local_score)
|
||||||
|
rank_value.text = _get_ordinal(local_rank + 1)
|
||||||
|
|
||||||
|
# Rank color
|
||||||
|
var rank_color := Color(0.95, 0.75, 0.1) # Gold
|
||||||
|
if local_rank == 1: rank_color = Color(0.75, 0.75, 0.75) # Silver
|
||||||
|
elif local_rank == 2: rank_color = Color(0.8, 0.5, 0.2) # Bronze
|
||||||
|
elif local_rank > 2: rank_color = Color(0.6, 0.6, 0.6)
|
||||||
|
rank_value.add_theme_color_override("font_color", rank_color)
|
||||||
|
player_rank_label.add_theme_color_override("font_color", rank_color)
|
||||||
|
|
||||||
|
# --- Rank List tab ---
|
||||||
|
if player_scores.size() > 0:
|
||||||
|
winner_rank_label.text = "1st"
|
||||||
|
winner_name_label.text = player_scores[0].name
|
||||||
|
winner_score_label.text = str(player_scores[0].score)
|
||||||
|
|
||||||
|
# Populate rank list rows
|
||||||
|
for child in rank_list_vbox.get_children():
|
||||||
|
child.queue_free()
|
||||||
|
|
||||||
|
for i in range(player_scores.size()):
|
||||||
|
var pdata = player_scores[i]
|
||||||
|
var is_local = pdata.peer_id == local_peer_id
|
||||||
|
_add_rank_list_entry(i, pdata, is_local)
|
||||||
|
|
||||||
|
# --- 3D Character Preview ---
|
||||||
|
_update_3d_preview(local_character)
|
||||||
|
char_name_label.text = local_name.to_upper()
|
||||||
|
|
||||||
|
func _add_rank_list_entry(index: int, pdata: Dictionary, is_local: bool) -> void:
|
||||||
|
var row = HBoxContainer.new()
|
||||||
|
row.add_theme_constant_override("separation", 16)
|
||||||
|
|
||||||
|
var num_lbl = Label.new()
|
||||||
|
num_lbl.text = str(index + 1)
|
||||||
|
num_lbl.custom_minimum_size = Vector2(30, 0)
|
||||||
|
num_lbl.add_theme_font_size_override("font_size", 20)
|
||||||
|
num_lbl.add_theme_color_override("font_color", Color(0.8, 0.8, 0.8))
|
||||||
|
if _nougat_font: num_lbl.add_theme_font_override("font", _nougat_font)
|
||||||
|
row.add_child(num_lbl)
|
||||||
|
|
||||||
|
var pname_lbl = Label.new()
|
||||||
|
pname_lbl.text = pdata.name
|
||||||
|
pname_lbl.add_theme_font_size_override("font_size", 20)
|
||||||
|
if is_local:
|
||||||
|
pname_lbl.add_theme_color_override("font_color", Color(0.4, 0.8, 1.0))
|
||||||
|
else:
|
||||||
|
pname_lbl.add_theme_color_override("font_color", Color.WHITE)
|
||||||
|
if _nougat_font: pname_lbl.add_theme_font_override("font", _nougat_font)
|
||||||
|
pname_lbl.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
|
row.add_child(pname_lbl)
|
||||||
|
|
||||||
|
var pscore_lbl = Label.new()
|
||||||
|
pscore_lbl.text = str(pdata.score)
|
||||||
|
pscore_lbl.add_theme_font_size_override("font_size", 20)
|
||||||
|
pscore_lbl.add_theme_color_override("font_color", Color.WHITE)
|
||||||
|
if _nougat_font: pscore_lbl.add_theme_font_override("font", _nougat_font)
|
||||||
|
pscore_lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
||||||
|
row.add_child(pscore_lbl)
|
||||||
|
|
||||||
|
rank_list_vbox.add_child(row)
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Rematch Vote Update (called by main.gd)
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func update_rematch_votes(count: int, required: int) -> void:
|
||||||
|
if is_instance_valid(rematch_vote_label):
|
||||||
|
rematch_vote_label.text = "%d/%d" % [count, required]
|
||||||
|
if count >= required:
|
||||||
|
rematch_vote_label.add_theme_color_override("font_color", Color.GREEN)
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# 3D Preview
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func _setup_3d_preview() -> void:
|
||||||
|
if not character_root:
|
||||||
|
return
|
||||||
|
_anim_player = character_root.get_node_or_null("AnimationPlayer")
|
||||||
|
|
||||||
|
func _update_3d_preview(character_name: String) -> void:
|
||||||
|
if not character_root:
|
||||||
|
return
|
||||||
|
var node_name: String = CHAR_NODE_MAP.get(character_name, character_name)
|
||||||
|
for child in character_root.get_children():
|
||||||
|
if child is Node3D:
|
||||||
|
child.visible = (child.name == node_name)
|
||||||
|
if _anim_player:
|
||||||
|
var new_root := character_root.get_node_or_null(node_name)
|
||||||
|
if new_root:
|
||||||
|
_anim_player.root_node = _anim_player.get_path_to(new_root)
|
||||||
|
if _anim_player.has_animation("animation-pack/idle"):
|
||||||
|
_anim_player.play("animation-pack/idle")
|
||||||
|
elif _anim_player.get_animation_list().size() > 0:
|
||||||
|
_anim_player.play(_anim_player.get_animation_list()[0])
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Helpers
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func _get_ordinal(n: int) -> String:
|
||||||
|
match n:
|
||||||
|
1: return "1st"
|
||||||
|
2: return "2nd"
|
||||||
|
3: return "3rd"
|
||||||
|
_: return str(n) + "th"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://b100tjl67tbh7
|
||||||
Reference in New Issue
Block a user