extends Control class_name MatchSummary ## The end of a match: who won, why, and what everyone did. ## ## There was nothing here before. The clock reached zero, `match_active` went ## false, the timer froze at 00:00, and the players kept shooting each other in a ## match that had stopped counting. A mode without an ending is a mode without ## stakes — everything a player does in the last minute only matters if there is ## a last minute. ## ## Built from the theme's own parts, so it reads as the same game as the menu it ## came from and the HUD it covers. signal play_again signal to_menu var _result: Dictionary = {} func show_result(result: Dictionary) -> void: _result = result for c in get_children(): c.queue_free() _build() visible = true # The summary takes the mouse, which the match had captured. Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) func _build() -> void: set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) mouse_filter = Control.MOUSE_FILTER_STOP var wash := ColorRect.new() wash.color = Color(UITheme.INK.r, UITheme.INK.g, UITheme.INK.b, 0.86) wash.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) wash.mouse_filter = Control.MOUSE_FILTER_IGNORE add_child(wash) var centre := CenterContainer.new() centre.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) add_child(centre) var card := UITheme.card() card.custom_minimum_size = Vector2(880, 0) centre.add_child(card) var col := VBoxContainer.new() col.add_theme_constant_override("separation", 10) card.add_child(col) # ── Who won, and why ──────────────────────────────────────────────────── var winner := String(_result.get("name", "DRAW")) var title := UITheme.title(winner if winner == "DRAW" else winner + " WINS", 68, -2.0) title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER # A winning TEAM's name is written in that team's colour. The wordmark # treatment is papaya by default, which on "MAGENTA TEAM WINS" says the # opposite of what the words do. var win_team := int(_result.get("team", 0)) if win_team > 0: title.add_theme_color_override("font_color", GameMode.team_color(win_team)) col.add_child(title) # The REASON, not just the result. "Time" and "frag limit" are different # stories about the same scoreline and the player was in one of them. var reason := "TIME" if String(_result.get("reason", "")) == "score": reason = "%s LIMIT REACHED" % GameMode.score_noun( String(_result.get("mode", GameMode.DEATHMATCH))) var sub := UITheme.caption("%s • %s" % [ GameMode.display_name(String(_result.get("mode", GameMode.DEATHMATCH))), reason], 22) sub.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER col.add_child(sub) var rule := UITheme.divider(0.42) rule.custom_minimum_size = Vector2(0, 20) col.add_child(rule) # ── Team scores, if this was a team mode ──────────────────────────────── var mode := String(_result.get("mode", GameMode.DEATHMATCH)) var stats: Dictionary = _result.get("stats", {}) if GameMode.is_team_mode(mode): var totals := GameMode.team_scores(mode, stats) var row := HBoxContainer.new() row.alignment = BoxContainer.ALIGNMENT_CENTER row.add_theme_constant_override("separation", 46) col.add_child(row) for t in totals: var box := VBoxContainer.new() var nm := UITheme.heading(GameMode.team_name(t), 24) nm.add_theme_color_override("font_color", GameMode.team_color(t)) nm.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER box.add_child(nm) var sc := UITheme.heading(str(totals[t]), 54) sc.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER box.add_child(sc) row.add_child(box) # ── The standings ─────────────────────────────────────────────────────── var grid := GridContainer.new() grid.columns = 6 grid.add_theme_constant_override("h_separation", 34) grid.add_theme_constant_override("v_separation", 6) col.add_child(grid) for h in ["#", "PLAYER", GameMode.score_noun(mode), "K", "D", "BEST RUN"]: var lbl := UITheme.caption(h, 20) grid.add_child(lbl) var order: Array = _result.get("standings", []) for i in order.size(): var pid = order[i] var s: Dictionary = stats.get(pid, {}) var place := i + 1 # First place gets the volt. It is the only place in this UI outside the # moment of input that volt appears, and winning is worth the exception. var tint: Color = UITheme.VOLT if place == 1 else UITheme.PAPER grid.add_child(_cell(str(place), tint, place == 1)) var who := _cell(String(s.get("username", "Player")), tint, place == 1) if int(s.get("team", 0)) > 0: who.add_theme_color_override("font_color", GameMode.team_color(int(s.get("team", 0)))) grid.add_child(who) grid.add_child(_cell(str(s.get("score", 0)), tint, place == 1)) grid.add_child(_cell(str(s.get("kills", 0)), tint, place == 1)) grid.add_child(_cell(str(s.get("deaths", 0)), tint, place == 1)) grid.add_child(_cell("x%d" % int(s.get("best_streak", 0)), tint, place == 1)) col.add_child(UITheme.divider(0.6)) # ── Out ───────────────────────────────────────────────────────────────── var buttons := HBoxContainer.new() buttons.alignment = BoxContainer.ALIGNMENT_CENTER buttons.add_theme_constant_override("separation", 18) col.add_child(buttons) var again := UITheme.primary_button("PLAY AGAIN", 34) again.pressed.connect(func(): play_again.emit()) buttons.add_child(again) var menu := Button.new() menu.text = "Main Menu" menu.add_theme_font_size_override("font_size", 28) menu.pressed.connect(func(): to_menu.emit()) buttons.add_child(menu) # Only the host may restart a running server; everyone else waits for them. var nm = get_tree().root.get_node_or_null("NetworkManager") if nm and nm.multiplayer.has_multiplayer_peer() \ and not nm.multiplayer.multiplayer_peer is OfflineMultiplayerPeer \ and not nm.multiplayer.is_server(): again.disabled = true again.text = "WAITING FOR HOST" UITheme.wire_sounds(self) again.grab_focus() func _cell(text: String, tint: Color, bold: bool) -> Label: var l := Label.new() l.text = text l.add_theme_font_size_override("font_size", 30 if bold else 26) l.add_theme_color_override("font_color", tint) l.add_theme_color_override("font_outline_color", UITheme.INK) l.add_theme_constant_override("outline_size", 6) return l