diff --git a/src/game/engine/scenes/menus/end_game_summary.tscn b/src/game/engine/scenes/menus/end_game_summary.tscn new file mode 100644 index 00000000..85d97676 --- /dev/null +++ b/src/game/engine/scenes/menus/end_game_summary.tscn @@ -0,0 +1,202 @@ +[gd_scene load_steps=2 format=3 uid="uid://cendgamesummaryp248"] + +[ext_resource type="Script" path="res://engine/scenes/menus/end_game_summary.gd" id="1_endgame"] + +[node name="EndGameSummary" 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_endgame") + +[node name="Backdrop" 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.03, 0.035, 0.05, 0.92) + +[node name="Margin" 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 = 36 +theme_override_constants/margin_top = 24 +theme_override_constants/margin_right = 36 +theme_override_constants/margin_bottom = 24 + +[node name="Root" type="VBoxContainer" parent="Margin"] +layout_mode = 2 +theme_override_constants/separation = 14 + +[node name="BannerLabel" type="Label" parent="Margin/Root"] +unique_name_in_owner = true +layout_mode = 2 +theme_override_font_sizes/font_size = 32 +text = "Game Over" +horizontal_alignment = 1 + +[node name="HeroStrip" type="HBoxContainer" parent="Margin/Root"] +layout_mode = 2 +theme_override_constants/separation = 16 +alignment = 1 + +[node name="WinnerCard" type="PanelContainer" parent="Margin/Root/HeroStrip"] +unique_name_in_owner = true +custom_minimum_size = Vector2(280, 80) +layout_mode = 2 + +[node name="WinnerVBox" type="VBoxContainer" parent="Margin/Root/HeroStrip/WinnerCard"] +layout_mode = 2 +theme_override_constants/separation = 4 + +[node name="WinnerTitle" type="Label" parent="Margin/Root/HeroStrip/WinnerCard/WinnerVBox"] +layout_mode = 2 +theme_override_font_sizes/font_size = 13 +theme_override_colors/font_color = Color(0.85, 0.76, 0.52, 1) +text = "Winner" +horizontal_alignment = 1 + +[node name="WinnerNameLabel" type="Label" parent="Margin/Root/HeroStrip/WinnerCard/WinnerVBox"] +unique_name_in_owner = true +layout_mode = 2 +theme_override_font_sizes/font_size = 20 +horizontal_alignment = 1 + +[node name="PlayerCard" type="PanelContainer" parent="Margin/Root/HeroStrip"] +unique_name_in_owner = true +custom_minimum_size = Vector2(280, 80) +layout_mode = 2 + +[node name="PlayerVBox" type="VBoxContainer" parent="Margin/Root/HeroStrip/PlayerCard"] +layout_mode = 2 +theme_override_constants/separation = 4 + +[node name="PlayerTitle" type="Label" parent="Margin/Root/HeroStrip/PlayerCard/PlayerVBox"] +layout_mode = 2 +theme_override_font_sizes/font_size = 13 +theme_override_colors/font_color = Color(0.6, 0.72, 0.85, 1) +text = "Your Clan" +horizontal_alignment = 1 + +[node name="PlayerNameLabel" type="Label" parent="Margin/Root/HeroStrip/PlayerCard/PlayerVBox"] +unique_name_in_owner = true +layout_mode = 2 +theme_override_font_sizes/font_size = 20 +horizontal_alignment = 1 + +[node name="Body" type="HBoxContainer" parent="Margin/Root"] +layout_mode = 2 +size_flags_vertical = 3 +theme_override_constants/separation = 18 + +[node name="LeftColumn" type="VBoxContainer" parent="Margin/Root/Body"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_constants/separation = 8 + +[node name="StandingsTitle" type="Label" parent="Margin/Root/Body/LeftColumn"] +layout_mode = 2 +theme_override_font_sizes/font_size = 16 +theme_override_colors/font_color = Color(0.9, 0.84, 0.65, 1) +text = "Final Standings" + +[node name="StandingsScroll" type="ScrollContainer" parent="Margin/Root/Body/LeftColumn"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="StandingsContainer" type="VBoxContainer" parent="Margin/Root/Body/LeftColumn/StandingsScroll"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_constants/separation = 2 + +[node name="GraphTitle" type="Label" parent="Margin/Root/Body/LeftColumn"] +layout_mode = 2 +theme_override_font_sizes/font_size = 16 +theme_override_colors/font_color = Color(0.9, 0.84, 0.65, 1) +text = "Score Over Time" + +[node name="SummaryGraphArea" type="Control" parent="Margin/Root/Body/LeftColumn"] +unique_name_in_owner = true +custom_minimum_size = Vector2(0, 180) +layout_mode = 2 + +[node name="RightColumn" type="VBoxContainer" parent="Margin/Root/Body"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_constants/separation = 8 + +[node name="AwardsTitle" type="Label" parent="Margin/Root/Body/RightColumn"] +layout_mode = 2 +theme_override_font_sizes/font_size = 16 +theme_override_colors/font_color = Color(0.9, 0.84, 0.65, 1) +text = "Awards" + +[node name="AwardsScroll" type="ScrollContainer" parent="Margin/Root/Body/RightColumn"] +layout_mode = 2 +custom_minimum_size = Vector2(0, 100) +horizontal_scroll_mode = 2 + +[node name="AwardsContainer" type="HBoxContainer" parent="Margin/Root/Body/RightColumn/AwardsScroll"] +unique_name_in_owner = true +layout_mode = 2 +theme_override_constants/separation = 10 + +[node name="TimelineTitle" type="Label" parent="Margin/Root/Body/RightColumn"] +layout_mode = 2 +theme_override_font_sizes/font_size = 16 +theme_override_colors/font_color = Color(0.9, 0.84, 0.65, 1) +text = "Timeline" + +[node name="TimelineScroll" type="ScrollContainer" parent="Margin/Root/Body/RightColumn"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="TimelineContainer" type="VBoxContainer" parent="Margin/Root/Body/RightColumn/TimelineScroll"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_constants/separation = 2 + +[node name="Footer" type="HBoxContainer" parent="Margin/Root"] +layout_mode = 2 +theme_override_constants/separation = 10 +alignment = 2 + +[node name="ViewMapButton" type="Button" parent="Margin/Root/Footer"] +unique_name_in_owner = true +custom_minimum_size = Vector2(140, 38) +layout_mode = 2 +text = "View Map" + +[node name="WatchReplayButton" type="Button" parent="Margin/Root/Footer"] +unique_name_in_owner = true +custom_minimum_size = Vector2(140, 38) +layout_mode = 2 +text = "Watch Replay" + +[node name="SaveArchiveButton" type="Button" parent="Margin/Root/Footer"] +unique_name_in_owner = true +custom_minimum_size = Vector2(140, 38) +layout_mode = 2 +text = "Save to Archive" + +[node name="ExportJsonButton" type="Button" parent="Margin/Root/Footer"] +unique_name_in_owner = true +custom_minimum_size = Vector2(140, 38) +layout_mode = 2 +text = "Export JSON" + +[node name="MainMenuButton" type="Button" parent="Margin/Root/Footer"] +unique_name_in_owner = true +custom_minimum_size = Vector2(140, 38) +layout_mode = 2 +text = "Main Menu" diff --git a/src/game/engine/scenes/statistics/statistics.tscn b/src/game/engine/scenes/statistics/statistics.tscn new file mode 100644 index 00000000..9991651c --- /dev/null +++ b/src/game/engine/scenes/statistics/statistics.tscn @@ -0,0 +1,12 @@ +[gd_scene load_steps=2 format=3 uid="uid://cstatsmodalp247"] + +[ext_resource type="Script" path="res://engine/scenes/statistics/statistics.gd" id="1_stats"] + +[node name="StatisticsModal" 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_stats") diff --git a/src/game/engine/scenes/tests/end_game_summary_proof.gd b/src/game/engine/scenes/tests/end_game_summary_proof.gd index a4505b08..c6055fbd 100644 --- a/src/game/engine/scenes/tests/end_game_summary_proof.gd +++ b/src/game/engine/scenes/tests/end_game_summary_proof.gd @@ -1,189 +1,145 @@ -extends Node2D -## End-game summary proof — renders the post-victory chronicle that -## shows score breakdown, key events timeline, and per-player win -## conditions. Distinct from victory_screen which is the immediate -## announcement; this is the full standings recap. +extends Node +## p2-48a — End-game summary proof scene. +## +## Boots the REAL end_game_summary.tscn (NOT a _draw mockup) once per +## GameOverReason variant — LastSurvivor, ConditionMet, TurnLimit, Resigned — +## seeds a fixture clan roster + StatsTracker history, calls _on_game_over for +## each reason, and captures one screenshot per variant. Proves the orphaned +## EndGameSummary scene now renders end-to-end for every game-over path. +## +## Rust note: production `game_over` emission lives in mc-turn (separate lane); +## this proof drives EventBus.game_over directly, exactly as the GUT tests do, +## fully exercising the GDScript receiver. + +const EndGameSummaryScene: PackedScene = preload( + "res://engine/scenes/menus/end_game_summary.tscn" +) +const PlayerScript: GDScript = preload("res://engine/src/entities/player.gd") const OUTPUT_DIR: String = "user://screenshots" const VIEWPORT_SIZE: Vector2i = Vector2i(1280, 720) -const CAPTURE_DELAY: float = 0.6 +const SETTLE_FRAMES: int = 4 +const CAPTURE_DELAY: float = 0.4 -const COLOR_BG: Color = Color(0.05, 0.06, 0.09) -const COLOR_PANEL: Color = Color(0.10, 0.12, 0.16) -const COLOR_PANEL_BORDER: Color = Color(0.28, 0.31, 0.40) -const COLOR_TITLE: Color = Color(0.92, 0.78, 0.32) -const COLOR_TEXT: Color = Color(0.92, 0.93, 0.96) -const COLOR_DIM: Color = Color(0.62, 0.65, 0.72) -const COLOR_WIN: Color = Color(0.95, 0.78, 0.32) -const COLOR_EVENT: Color = Color(0.45, 0.75, 0.95) - -const PLAYERS: Array[Dictionary] = [ - { - "name": "Karak Ankor", - "color": Color(0.95, 0.65, 0.15), - "score": 107, - "pop": 11, - "cities": 3, - "techs": 12, - "wonders": 1, - "win": "DOMINATION", - }, - { - "name": "Goldvein", - "color": Color(0.95, 0.85, 0.25), - "score": 97, - "pop": 9, - "cities": 2, - "techs": 11, - "wonders": 0, - "win": "", - }, - { - "name": "Blackhammer", - "color": Color(0.45, 0.25, 0.85), - "score": 87, - "pop": 7, - "cities": 1, - "techs": 9, - "wonders": 0, - "win": "", - }, +## Each variant: (winner_index, reason). winner_index -1 = stalemate (Resigned). +const VARIANTS: Array[Dictionary] = [ + {"winner": 0, "reason": "LastSurvivor"}, + {"winner": 1, "reason": "ConditionMet"}, + {"winner": 2, "reason": "TurnLimit"}, + {"winner": -1, "reason": "Resigned"}, ] -const EVENTS: Array[Dictionary] = [ - {"turn": 1, "text": "Karak Ankor founded — Capital of the Iron Clan", "color": "founding"}, - {"turn": 7, "text": "Goldvein discovers Mining", "color": "tech"}, - {"turn": 15, "text": "Blackhammer raids Karak outpost", "color": "combat"}, - {"turn": 22, "text": "Goldvein founds Mithril Hold", "color": "founding"}, - {"turn": 38, "text": "Karak completes the Great Anvil (Tier 4 wonder)", "color": "wonder"}, - {"turn": 52, "text": "War declared: Karak Ankor → Blackhammer", "color": "diplomacy"}, - {"turn": 71, "text": "Blackhammer's capital falls to Karak siege", "color": "combat"}, - {"turn": 87, "text": "Domination victory — Karak Ankor controls every founding capital", "color": "win"}, +const FIXTURE_CLANS: Array[Dictionary] = [ + {"name": "Karak Ankor", "color": Color(0.95, 0.65, 0.15)}, + {"name": "Goldvein", "color": Color(0.95, 0.85, 0.25)}, + {"name": "Blackhammer", "color": Color(0.45, 0.25, 0.85)}, ] -const EVENT_COLOR_MAP: Dictionary = { - "founding": Color(0.55, 0.85, 0.45), - "tech": Color(0.45, 0.65, 0.95), - "combat": Color(0.85, 0.45, 0.35), - "wonder": Color(0.92, 0.78, 0.32), - "diplomacy": Color(0.75, 0.50, 0.95), - "win": Color(1.00, 0.85, 0.32), -} -var _captured: bool = false +var _layer: CanvasLayer = null +var _summary: Control = null + + +func _make_player(idx: int, pname: String, c: Color, human: bool) -> Player: + var p: Player = PlayerScript.new(idx, pname) + p.color = c + p.is_human = human + return p func _ready() -> void: get_viewport().size = VIEWPORT_SIZE DisplayServer.window_set_size(VIEWPORT_SIZE) - RenderingServer.set_default_clear_color(COLOR_BG) + RenderingServer.set_default_clear_color(Color(0.03, 0.035, 0.05)) DataLoader.load_theme("age-of-dwarves") ThemeAssets.set_theme("age-of-dwarves") - queue_redraw() - await get_tree().create_timer(CAPTURE_DELAY).timeout - _capture_and_quit() + ThemeVocabulary.load_vocabulary("age-of-dwarves") + _seed_fixture() + _run_variants() -func _draw() -> void: - var font: Font = ThemeDB.fallback_font - draw_string( - font, Vector2(40, 50), - "End Game Summary — Turn 87", - HORIZONTAL_ALIGNMENT_LEFT, -1, 26, COLOR_TITLE, - ) - draw_string( - font, Vector2(40, 76), - "Seed 000042 — Continents, Standard, 3 Players", - HORIZONTAL_ALIGNMENT_LEFT, -1, 14, COLOR_DIM, - ) - - # ── Standings table ───────────────────────────────────────────────── - var table_y: int = 120 - var col_xs: PackedInt32Array = [60, 280, 380, 480, 580, 700, 880] - var headers: PackedStringArray = [ - "Player", "Score", "Pop", "Cities", "Techs", "Wonders", "Win Condition", - ] - draw_rect(Rect2(Vector2(40, table_y - 8), Vector2(1100, 36)), COLOR_PANEL) - for i: int in range(headers.size()): - draw_string( - font, Vector2(col_xs[i], table_y + 18), - headers[i], - HORIZONTAL_ALIGNMENT_LEFT, -1, 14, COLOR_TITLE, - ) - # Rows. - var row_y: int = table_y + 56 - for i: int in range(PLAYERS.size()): - var p: Dictionary = PLAYERS[i] - # Color dot. - draw_circle(Vector2(45, row_y + 6), 10, p["color"] as Color) - var winning: bool = String(p["win"]).length() > 0 - var name_color: Color = COLOR_WIN if winning else COLOR_TEXT - draw_string( - font, Vector2(col_xs[0] + 8, row_y + 12), - String(p["name"]), - HORIZONTAL_ALIGNMENT_LEFT, -1, 18, name_color, - ) - draw_string(font, Vector2(col_xs[1], row_y + 12), str(int(p["score"])), - HORIZONTAL_ALIGNMENT_LEFT, -1, 18, name_color) - draw_string(font, Vector2(col_xs[2], row_y + 12), str(int(p["pop"])), - HORIZONTAL_ALIGNMENT_LEFT, -1, 16, COLOR_TEXT) - draw_string(font, Vector2(col_xs[3], row_y + 12), str(int(p["cities"])), - HORIZONTAL_ALIGNMENT_LEFT, -1, 16, COLOR_TEXT) - draw_string(font, Vector2(col_xs[4], row_y + 12), str(int(p["techs"])), - HORIZONTAL_ALIGNMENT_LEFT, -1, 16, COLOR_TEXT) - draw_string(font, Vector2(col_xs[5], row_y + 12), str(int(p["wonders"])), - HORIZONTAL_ALIGNMENT_LEFT, -1, 16, COLOR_TEXT) - draw_string(font, Vector2(col_xs[6], row_y + 12), String(p["win"]), - HORIZONTAL_ALIGNMENT_LEFT, -1, 16, COLOR_WIN) - row_y += 36 - - # ── Event timeline ────────────────────────────────────────────────── - var timeline_y: int = row_y + 30 - draw_string( - font, Vector2(40, timeline_y), - "Chronicle of the Age", - HORIZONTAL_ALIGNMENT_LEFT, -1, 18, COLOR_TITLE, - ) - var ev_y: int = timeline_y + 28 - for e: Dictionary in EVENTS: - var dot_color: Color = EVENT_COLOR_MAP.get(String(e["color"]), COLOR_DIM) as Color - draw_circle(Vector2(58, ev_y + 8), 6, dot_color) - draw_string( - font, Vector2(80, ev_y + 12), - "Turn %d" % int(e["turn"]), - HORIZONTAL_ALIGNMENT_LEFT, -1, 14, COLOR_DIM, - ) - draw_string( - font, Vector2(160, ev_y + 12), - String(e["text"]), - HORIZONTAL_ALIGNMENT_LEFT, -1, 14, COLOR_TEXT, - ) - ev_y += 24 +func _seed_fixture() -> void: + ## Player roster — player 0 is the human so victory/defeat banners resolve. + var players: Array = [] + for i: int in FIXTURE_CLANS.size(): + var clan: Dictionary = FIXTURE_CLANS[i] + players.append(_make_player( + i, clan["name"] as String, clan["color"] as Color, i == 0)) + GameState.players = players + GameState.current_player_index = 0 + ## Seed a short score history so standings/graph/timeline render. + _seed_stats_history() -func _capture_and_quit() -> void: - if _captured: +func _seed_stats_history() -> void: + ## Push fixture snapshots straight into StatsTracker's history (the same + ## shape _on_turn_ended produces) so Standings / Graph / Timeline render. + if not _has_node_singleton("StatsTracker"): return - _captured = true + StatsTracker.reset() + for turn: int in range(1, 9): + var snap_players: Array = [] + for i: int in FIXTURE_CLANS.size(): + snap_players.append({ + "index": i, + "score": 40 + turn * (6 - i), + "population": 3 + turn + i, + "military": turn, + "cities": 1 + turn / 3, + "techs": turn / 2, + "tech_count": turn / 2, + "wonders": 1 if (turn > 4 and i == 0) else 0, + "wonder_count": 1 if (turn > 4 and i == 0) else 0, + }) + StatsTracker._history.append({"turn": turn, "players": snap_players}) + + +func _has_node_singleton(node_name: String) -> bool: + var tree: SceneTree = get_tree() + return tree != null and tree.root != null and tree.root.has_node(node_name) + + +func _run_variants() -> void: + for variant: Dictionary in VARIANTS: + await _capture_variant( + int(variant["winner"]), variant["reason"] as String) + get_tree().quit() + + +func _capture_variant(winner_index: int, reason: String) -> void: + _layer = CanvasLayer.new() + _layer.layer = 25 + add_child(_layer) + _summary = EndGameSummaryScene.instantiate() as Control + _layer.add_child(_summary) + await get_tree().process_frame + ## Drive the EventBus path the production receiver uses. + _summary._on_game_over(winner_index, reason) + + for _i: int in SETTLE_FRAMES: + await get_tree().process_frame + await get_tree().create_timer(CAPTURE_DELAY).timeout + + _save_screenshot(reason) + + get_tree().paused = false + _layer.queue_free() + _summary = null + _layer = null + await get_tree().process_frame + + +func _save_screenshot(reason: String) -> void: DirAccess.make_dir_recursive_absolute( - ProjectSettings.globalize_path(OUTPUT_DIR) - ) + ProjectSettings.globalize_path(OUTPUT_DIR)) var image: Image = get_viewport().get_texture().get_image() if image == null: push_error("end_game_summary_proof: viewport image null") - get_tree().quit(1) return - var timestamp: String = Time.get_datetime_string_from_system().replace( - ":", "-" - ).replace("T", "_") - var path: String = "%s/end_game_summary_proof_%s.png" % [OUTPUT_DIR, timestamp] + var path: String = "%s/end_game_summary_proof_%s.png" % [OUTPUT_DIR, reason] var abs_path: String = ProjectSettings.globalize_path(path) var err: Error = image.save_png(abs_path) if err == OK: print("SCREENSHOT_PATH:%s" % abs_path) - print("end_game_summary_proof: %dx%d saved" % [ - image.get_width(), image.get_height() - ]) else: push_error("end_game_summary_proof: save failed: %s" % error_string(err)) - get_tree().quit() diff --git a/src/game/engine/scenes/tests/end_game_summary_proof.tscn b/src/game/engine/scenes/tests/end_game_summary_proof.tscn index f50e5f9d..33231a3e 100644 --- a/src/game/engine/scenes/tests/end_game_summary_proof.tscn +++ b/src/game/engine/scenes/tests/end_game_summary_proof.tscn @@ -2,5 +2,5 @@ [ext_resource type="Script" path="res://engine/scenes/tests/end_game_summary_proof.gd" id="1"] -[node name="EndGameSummaryProof" type="Node2D"] +[node name="EndGameSummaryProof" type="Node"] script = ExtResource("1") diff --git a/src/game/engine/scenes/tests/statistics_proof.gd b/src/game/engine/scenes/tests/statistics_proof.gd new file mode 100644 index 00000000..613a5a9d --- /dev/null +++ b/src/game/engine/scenes/tests/statistics_proof.gd @@ -0,0 +1,129 @@ +extends Node +## p2-47 — Statistics modal proof scene. +## +## Boots the REAL statistics.tscn (the thin wrapper over the 709-line +## self-building StatisticsModal) with a fixture clan roster + StatsTracker +## history, then captures one screenshot per tab (Demographics, Graphs, +## Rankings, Replay, Histories). Proves the modal that F9 / the info button / +## the Stats menu open now renders end-to-end against a missing-resource-free +## scene asset. +## +## Live data (Demographics/Graphs/Rankings) comes from StatsTracker here; +## the GdGameHistory bridge (Histories tab) is Rust-pending and shows its +## pending notice, exactly as in-game. + +const StatisticsScene: PackedScene = preload( + "res://engine/scenes/statistics/statistics.tscn" +) +const PlayerScript: GDScript = preload("res://engine/src/entities/player.gd") + +const OUTPUT_DIR: String = "user://screenshots" +const VIEWPORT_SIZE: Vector2i = Vector2i(1280, 720) +const SETTLE_FRAMES: int = 4 +const CAPTURE_DELAY: float = 0.4 + +const TAB_NAMES: Array[String] = [ + "demographics", "graphs", "rankings", "replay", "histories", +] + +const FIXTURE_CLANS: Array[Dictionary] = [ + {"name": "Karak Ankor", "color": Color(0.95, 0.65, 0.15)}, + {"name": "Goldvein", "color": Color(0.95, 0.85, 0.25)}, + {"name": "Blackhammer", "color": Color(0.45, 0.25, 0.85)}, + {"name": "Ironforge", "color": Color(0.30, 0.60, 0.90)}, +] + + +var _layer: CanvasLayer = null +var _modal: Control = null + + +func _make_player(idx: int, pname: String, c: Color) -> Player: + var p: Player = PlayerScript.new(idx, pname) + p.color = c + return p + + +func _ready() -> void: + get_viewport().size = VIEWPORT_SIZE + DisplayServer.window_set_size(VIEWPORT_SIZE) + RenderingServer.set_default_clear_color(Color(0.05, 0.05, 0.05)) + DataLoader.load_theme("age-of-dwarves") + ThemeAssets.set_theme("age-of-dwarves") + ThemeVocabulary.load_vocabulary("age-of-dwarves") + ## StatsTracker caches its category labels at autoload-init (before the proof + ## loads the theme above). Rebuild them now so the Demographics/Graphs/Rankings + ## column + metric labels resolve to vocabulary copy, not title-case keys. + ## In-game the theme is loaded before StatsTracker, so this ordering is + ## proof-only. + if _has_node_singleton("StatsTracker") and StatsTracker.has_method("_rebuild_labels"): + StatsTracker._rebuild_labels() + _seed_fixture() + await _run() + + +func _seed_fixture() -> void: + var players: Array = [] + for i: int in FIXTURE_CLANS.size(): + var clan: Dictionary = FIXTURE_CLANS[i] + players.append(_make_player( + i, clan["name"] as String, clan["color"] as Color)) + GameState.players = players + GameState.current_player_index = 0 + if _has_node_singleton("StatsTracker"): + StatsTracker.reset() + for turn: int in range(1, 13): + var snap_players: Array = [] + for i: int in FIXTURE_CLANS.size(): + snap_players.append({ + "index": i, + "score": 30 + turn * (7 - i) + i * 2, + "population": 2 + turn + i, + "military": turn / 2 + i, + "cities": 1 + turn / 4, + "techs": turn / 2, + "wonders": 1 if (turn > 6 and i == 0) else 0, + }) + StatsTracker._history.append({"turn": turn, "players": snap_players}) + + +func _has_node_singleton(node_name: String) -> bool: + var tree: SceneTree = get_tree() + return tree != null and tree.root != null and tree.root.has_node(node_name) + + +func _run() -> void: + _layer = CanvasLayer.new() + _layer.layer = 25 + add_child(_layer) + _modal = StatisticsScene.instantiate() as Control + _layer.add_child(_modal) + await get_tree().process_frame + + for tab: int in TAB_NAMES.size(): + ## Keep the TabBar highlight in sync with the panel we drive directly + ## (in-game the player clicks the TabBar, which does this for us). + _modal._tab_bar.current_tab = tab + _modal._on_tab_changed(tab) + for _i: int in SETTLE_FRAMES: + await get_tree().process_frame + await get_tree().create_timer(CAPTURE_DELAY).timeout + _save_screenshot(TAB_NAMES[tab]) + + get_tree().quit() + + +func _save_screenshot(tab_name: String) -> void: + DirAccess.make_dir_recursive_absolute( + ProjectSettings.globalize_path(OUTPUT_DIR)) + var image: Image = get_viewport().get_texture().get_image() + if image == null: + push_error("statistics_proof: viewport image null") + return + var path: String = "%s/statistics_proof_%s.png" % [OUTPUT_DIR, tab_name] + var abs_path: String = ProjectSettings.globalize_path(path) + var err: Error = image.save_png(abs_path) + if err == OK: + print("SCREENSHOT_PATH:%s" % abs_path) + else: + push_error("statistics_proof: save failed: %s" % error_string(err)) diff --git a/src/game/engine/scenes/tests/statistics_proof.tscn b/src/game/engine/scenes/tests/statistics_proof.tscn new file mode 100644 index 00000000..0914d8dc --- /dev/null +++ b/src/game/engine/scenes/tests/statistics_proof.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3] + +[ext_resource type="Script" path="res://engine/scenes/tests/statistics_proof.gd" id="1"] + +[node name="StatisticsProof" type="Node"] +script = ExtResource("1")