Five screen titles in the main menu were white on a black outline and the HUD used Color.BLACK outlines and a hardcoded grey. That was the old comic theme's contrast trick, and next to a papaya wordmark it read as a different game's menu. They take UITheme.PAPAYA, INK and PAPER_DIM now, so every screen — main menu, pause menu, character picker, settings, HUD and rig lab — is one palette with one source. Co-Authored-By: Claude Opus 5 <[email protected]>
216 lines
6.9 KiB
GDScript
216 lines
6.9 KiB
GDScript
extends CanvasLayer
|
|
|
|
var _nm: Node
|
|
|
|
# Top HUD
|
|
var _time_label: Label
|
|
var _local_kills_label: Label
|
|
var _leader_label: Label
|
|
|
|
# Killfeed
|
|
var _killfeed_vbox: VBoxContainer
|
|
|
|
# Scoreboard
|
|
var _scoreboard_panel: PanelContainer
|
|
var _scoreboard_grid: GridContainer
|
|
|
|
func _ready() -> void:
|
|
_nm = get_node_or_null("/root/NetworkManager")
|
|
# Ensure the comic theme is active even when a level is loaded directly
|
|
# (tests, debug runs) without passing through the main menu.
|
|
UITheme.apply_global(get_tree())
|
|
|
|
# Top HUD
|
|
var top_hbox = HBoxContainer.new()
|
|
top_hbox.set_anchors_preset(Control.PRESET_TOP_WIDE)
|
|
top_hbox.alignment = BoxContainer.ALIGNMENT_CENTER
|
|
top_hbox.add_theme_constant_override("separation", 50)
|
|
var margin = MarginContainer.new()
|
|
margin.add_theme_constant_override("margin_top", 20)
|
|
margin.set_anchors_preset(Control.PRESET_TOP_WIDE)
|
|
margin.add_child(top_hbox)
|
|
add_child(margin)
|
|
|
|
_time_label = Label.new()
|
|
_time_label.add_theme_font_size_override("font_size", 48)
|
|
_time_label.add_theme_color_override("font_outline_color", UITheme.INK)
|
|
_time_label.add_theme_constant_override("outline_size", 8)
|
|
top_hbox.add_child(_time_label)
|
|
|
|
_local_kills_label = Label.new()
|
|
_local_kills_label.text = "0 Kills"
|
|
_local_kills_label.add_theme_font_size_override("font_size", 64)
|
|
_local_kills_label.add_theme_color_override("font_color", UITheme.PAPAYA)
|
|
_local_kills_label.add_theme_color_override("font_outline_color", UITheme.INK)
|
|
_local_kills_label.add_theme_constant_override("outline_size", 12)
|
|
top_hbox.add_child(_local_kills_label)
|
|
|
|
_leader_label = Label.new()
|
|
_leader_label.text = "Leader: None"
|
|
_leader_label.add_theme_font_size_override("font_size", 32)
|
|
_leader_label.add_theme_color_override("font_outline_color", UITheme.INK)
|
|
_leader_label.add_theme_constant_override("outline_size", 8)
|
|
top_hbox.add_child(_leader_label)
|
|
|
|
# Killfeed
|
|
var kf_margin = MarginContainer.new()
|
|
kf_margin.set_anchors_and_offsets_preset(Control.PRESET_TOP_RIGHT)
|
|
kf_margin.grow_horizontal = Control.GROW_DIRECTION_BEGIN
|
|
kf_margin.add_theme_constant_override("margin_top", 20)
|
|
kf_margin.add_theme_constant_override("margin_right", 20)
|
|
add_child(kf_margin)
|
|
|
|
_killfeed_vbox = VBoxContainer.new()
|
|
_killfeed_vbox.alignment = BoxContainer.ALIGNMENT_END
|
|
kf_margin.add_child(_killfeed_vbox)
|
|
|
|
# Scoreboard Overlay
|
|
var center_container = CenterContainer.new()
|
|
center_container.set_anchors_preset(Control.PRESET_FULL_RECT)
|
|
add_child(center_container)
|
|
|
|
_scoreboard_panel = PanelContainer.new()
|
|
_scoreboard_panel.custom_minimum_size = Vector2(800, 600)
|
|
# Styled by the shared UITheme PanelContainer stylebox (ink-border card)
|
|
_scoreboard_panel.hide()
|
|
center_container.add_child(_scoreboard_panel)
|
|
|
|
var sb_vbox = VBoxContainer.new()
|
|
var sb_margin = MarginContainer.new()
|
|
sb_margin.add_theme_constant_override("margin_left", 20)
|
|
sb_margin.add_theme_constant_override("margin_top", 20)
|
|
sb_margin.add_theme_constant_override("margin_right", 20)
|
|
sb_margin.add_theme_constant_override("margin_bottom", 20)
|
|
sb_margin.add_child(sb_vbox)
|
|
_scoreboard_panel.add_child(sb_margin)
|
|
|
|
var sb_title = Label.new()
|
|
sb_title.text = "Scoreboard"
|
|
sb_title.add_theme_font_size_override("font_size", 48)
|
|
sb_title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
sb_vbox.add_child(sb_title)
|
|
|
|
_scoreboard_grid = GridContainer.new()
|
|
_scoreboard_grid.columns = 5
|
|
_scoreboard_grid.add_theme_constant_override("h_separation", 50)
|
|
_scoreboard_grid.add_theme_constant_override("v_separation", 10)
|
|
sb_vbox.add_child(_scoreboard_grid)
|
|
|
|
_populate_scoreboard_headers()
|
|
|
|
# Signals
|
|
if _nm:
|
|
_nm.stats_updated.connect(_update_hud)
|
|
_nm.killfeed_event.connect(_on_killfeed_event)
|
|
_nm.match_state_updated.connect(_update_timer)
|
|
_update_hud()
|
|
|
|
func _process(_delta: float) -> void:
|
|
if Input.is_action_just_pressed("scoreboard"):
|
|
_scoreboard_panel.show()
|
|
_update_hud()
|
|
elif Input.is_action_just_released("scoreboard"):
|
|
_scoreboard_panel.hide()
|
|
|
|
if _nm and _nm.match_active:
|
|
_update_timer()
|
|
|
|
func _update_timer() -> void:
|
|
if not _nm: return
|
|
var t = int(_nm.match_time_remaining)
|
|
var m = int(t / 60.0)
|
|
var s = t % 60
|
|
_time_label.text = "%02d:%02d" % [m, s]
|
|
|
|
func _populate_scoreboard_headers() -> void:
|
|
for child in _scoreboard_grid.get_children():
|
|
child.queue_free()
|
|
|
|
var headers = ["Player", "Kills", "Deaths", "Assists", "Ping"]
|
|
for h in headers:
|
|
var l = Label.new()
|
|
l.text = h
|
|
l.add_theme_font_size_override("font_size", 24)
|
|
l.add_theme_color_override("font_color", UITheme.PAPER_DIM)
|
|
_scoreboard_grid.add_child(l)
|
|
|
|
func _update_hud() -> void:
|
|
if not _nm: return
|
|
|
|
var my_id = multiplayer.get_unique_id()
|
|
|
|
var leader_name = "None"
|
|
var max_kills = -1
|
|
|
|
var stats = _nm.player_stats
|
|
|
|
# Sort players by kills for scoreboard
|
|
var players = stats.keys()
|
|
players.sort_custom(func(a, b): return stats[a].get("kills", 0) > stats[b].get("kills", 0))
|
|
|
|
_populate_scoreboard_headers()
|
|
|
|
for pid in players:
|
|
var p_data = stats[pid]
|
|
var p_kills = p_data.get("kills", 0)
|
|
|
|
# Find leader
|
|
if p_kills > max_kills:
|
|
max_kills = p_kills
|
|
leader_name = p_data.get("username", "Player " + str(pid))
|
|
|
|
# Update local kills
|
|
if pid == my_id:
|
|
_local_kills_label.text = str(p_kills) + " Kills"
|
|
|
|
# Build Scoreboard Row
|
|
var c_name = Label.new()
|
|
c_name.text = p_data.get("username", "Player")
|
|
c_name.add_theme_font_size_override("font_size", 24)
|
|
c_name.add_theme_color_override("font_color", Color(p_data.get("color", "cccccc")))
|
|
_scoreboard_grid.add_child(c_name)
|
|
|
|
var c_k = Label.new()
|
|
c_k.text = str(p_kills)
|
|
c_k.add_theme_font_size_override("font_size", 24)
|
|
_scoreboard_grid.add_child(c_k)
|
|
|
|
var c_d = Label.new()
|
|
c_d.text = str(p_data.get("deaths", 0))
|
|
c_d.add_theme_font_size_override("font_size", 24)
|
|
_scoreboard_grid.add_child(c_d)
|
|
|
|
var c_a = Label.new()
|
|
c_a.text = str(p_data.get("assists", 0))
|
|
c_a.add_theme_font_size_override("font_size", 24)
|
|
_scoreboard_grid.add_child(c_a)
|
|
|
|
var c_p = Label.new()
|
|
c_p.text = str(p_data.get("ping", 0)) + "ms"
|
|
c_p.add_theme_font_size_override("font_size", 24)
|
|
_scoreboard_grid.add_child(c_p)
|
|
|
|
_leader_label.text = "Leader: %s (%s Kills)" % [leader_name, max_kills]
|
|
|
|
func _on_killfeed_event(victim: String, killer: String, weapon: String, v_color: String = "cccccc", k_color: String = "cccccc") -> void:
|
|
var l = RichTextLabel.new()
|
|
l.bbcode_enabled = true
|
|
l.fit_content = true
|
|
l.autowrap_mode = TextServer.AUTOWRAP_OFF
|
|
|
|
if killer == "":
|
|
l.text = "[color=#%s]%s[/color] died." % [v_color, victim]
|
|
else:
|
|
l.text = "[color=#%s]%s[/color] [%s] [color=#%s]%s[/color]" % [k_color, killer, weapon, v_color, victim]
|
|
|
|
l.add_theme_font_size_override("normal_font_size", 20)
|
|
l.add_theme_color_override("font_outline_color", UITheme.INK)
|
|
l.add_theme_constant_override("outline_size", 4)
|
|
_killfeed_vbox.add_child(l)
|
|
|
|
# Fade out and queue_free after 5 seconds
|
|
var t = get_tree().create_tween()
|
|
t.tween_interval(4.0)
|
|
t.tween_property(l, "modulate:a", 0.0, 1.0)
|
|
t.tween_callback(l.queue_free)
|