feat(@projects/@magic-civilization): ✨ add strategic stockpile and wonder proof tests
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
9405415f7d
commit
eecb0aabff
5 changed files with 344 additions and 2 deletions
|
|
@ -138,9 +138,9 @@ Status legend:
|
|||
| # | File | Source | Proves | Status |
|
||||
|---|---|---|---|---|
|
||||
| 77 | `magic_civ_demo_action_civics_buildings.png` | `proof_civics_buildings` | Civic-axis × buildings cross-grant table | ✅ |
|
||||
| 78 | *civics panel 3 axes* | new proof | Authority/Labor/Economy picker | 🔴 |
|
||||
| 78 | `magic_civ_demo_ui_civics.png` | `civics_panel_proof` (new) | 3 axis cards (Authority=Monarchy, Labor=Guilds, Economy=Mercantilism) + per-axis descriptions, ANARCHY banner with "3/5 turns remaining" 60%-fill bar | ✅ |
|
||||
| 79 | *anarchy warning dialog* | extend | Switching civic prompt | 🔴 |
|
||||
| 80 | *anarchy HUD countdown* | new proof | 5-turn anarchy timer | 🔴 |
|
||||
| 80 | *anarchy HUD countdown* | extend | 5-turn anarchy timer (already shown on civics_panel — extract as standalone if needed) | 🟡 covered |
|
||||
| 81 | *post-anarchy active* | extend | New civic + zeroed counter | 🔴 |
|
||||
|
||||
## K. Procedural visuals (6 slots)
|
||||
|
|
|
|||
177
src/game/engine/scenes/tests/strategic_stockpile_proof.gd
Normal file
177
src/game/engine/scenes/tests/strategic_stockpile_proof.gd
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
extends Node2D
|
||||
## Strategic stockpile HUD proof — renders the resource-bar overlay a
|
||||
## player sees on the world map's top HUD: iron, wood, grain, stone,
|
||||
## gold, plus their per-turn deltas and tooltip-style supply chains.
|
||||
## Self-contained mock state; no GameState dependency.
|
||||
|
||||
const OUTPUT_DIR: String = "user://screenshots"
|
||||
const VIEWPORT_SIZE: Vector2i = Vector2i(1280, 720)
|
||||
const CAPTURE_DELAY: float = 0.6
|
||||
|
||||
const COLOR_BG: Color = Color(0.07, 0.08, 0.11)
|
||||
const COLOR_PANEL: Color = Color(0.13, 0.15, 0.19)
|
||||
const COLOR_PANEL_BORDER: Color = Color(0.30, 0.33, 0.42)
|
||||
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_POSITIVE: Color = Color(0.55, 0.85, 0.45)
|
||||
const COLOR_NEGATIVE: Color = Color(0.90, 0.45, 0.35)
|
||||
|
||||
const RESOURCES: Array[Dictionary] = [
|
||||
{
|
||||
"name": "Iron Ore",
|
||||
"icon_color": Color(0.66, 0.66, 0.72),
|
||||
"amount": 14,
|
||||
"per_turn": 2,
|
||||
"source": "2× iron deposit, 1× smelter",
|
||||
},
|
||||
{
|
||||
"name": "Wood",
|
||||
"icon_color": Color(0.55, 0.40, 0.20),
|
||||
"amount": 38,
|
||||
"per_turn": 5,
|
||||
"source": "Forest tiles ×3, lumber mill ×1",
|
||||
},
|
||||
{
|
||||
"name": "Grain",
|
||||
"icon_color": Color(0.85, 0.75, 0.32),
|
||||
"amount": 22,
|
||||
"per_turn": -1,
|
||||
"source": "Granary surplus, 4 mouths to feed",
|
||||
},
|
||||
{
|
||||
"name": "Stone",
|
||||
"icon_color": Color(0.70, 0.70, 0.65),
|
||||
"amount": 9,
|
||||
"per_turn": 1,
|
||||
"source": "Quarry on plains-mountain edge",
|
||||
},
|
||||
{
|
||||
"name": "Gold",
|
||||
"icon_color": Color(0.93, 0.80, 0.28),
|
||||
"amount": 142,
|
||||
"per_turn": 8,
|
||||
"source": "Market + trade, less unit upkeep",
|
||||
},
|
||||
]
|
||||
|
||||
var _captured: bool = false
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
get_viewport().size = VIEWPORT_SIZE
|
||||
DisplayServer.window_set_size(VIEWPORT_SIZE)
|
||||
RenderingServer.set_default_clear_color(COLOR_BG)
|
||||
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()
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
var font: Font = ThemeDB.fallback_font
|
||||
draw_string(
|
||||
font, Vector2(40, 48),
|
||||
"Strategic Stockpile — Empire HUD",
|
||||
HORIZONTAL_ALIGNMENT_LEFT, -1, 26, COLOR_TITLE,
|
||||
)
|
||||
draw_string(
|
||||
font, Vector2(40, 74),
|
||||
"Per-resource amount + per-turn delta + supply source",
|
||||
HORIZONTAL_ALIGNMENT_LEFT, -1, 14, COLOR_DIM,
|
||||
)
|
||||
|
||||
# ── HUD row (compact, mirrors top-bar layout) ────────────────────────
|
||||
var hud_y: int = 110
|
||||
var hud_h: int = 56
|
||||
draw_rect(Rect2(Vector2(40, hud_y), Vector2(1200, hud_h)), COLOR_PANEL)
|
||||
draw_rect(
|
||||
Rect2(Vector2(40, hud_y), Vector2(1200, hud_h)),
|
||||
COLOR_PANEL_BORDER, false, 2.0,
|
||||
)
|
||||
var cell_w: int = 1200 / RESOURCES.size()
|
||||
for i: int in range(RESOURCES.size()):
|
||||
var r: Dictionary = RESOURCES[i]
|
||||
var x: int = 40 + i * cell_w + 16
|
||||
var icon_pos: Vector2 = Vector2(x, hud_y + hud_h * 0.5)
|
||||
draw_circle(icon_pos, 14, r["icon_color"] as Color)
|
||||
draw_string(
|
||||
font, Vector2(x + 26, hud_y + 24),
|
||||
str(r["amount"]),
|
||||
HORIZONTAL_ALIGNMENT_LEFT, -1, 20, COLOR_TEXT,
|
||||
)
|
||||
var delta: int = int(r["per_turn"])
|
||||
var delta_text: String = ("+%d" % delta) if delta >= 0 else str(delta)
|
||||
var delta_color: Color = COLOR_POSITIVE if delta >= 0 else COLOR_NEGATIVE
|
||||
draw_string(
|
||||
font, Vector2(x + 26, hud_y + 46),
|
||||
delta_text + "/turn",
|
||||
HORIZONTAL_ALIGNMENT_LEFT, -1, 13, delta_color,
|
||||
)
|
||||
|
||||
# ── Tooltip-style detail rows ────────────────────────────────────────
|
||||
var detail_y: int = 210
|
||||
draw_string(
|
||||
font, Vector2(40, detail_y - 12),
|
||||
"Supply chains (hover-tooltip detail)",
|
||||
HORIZONTAL_ALIGNMENT_LEFT, -1, 16, COLOR_TITLE,
|
||||
)
|
||||
var row_h: int = 60
|
||||
for i: int in range(RESOURCES.size()):
|
||||
var r2: Dictionary = RESOURCES[i]
|
||||
var ry: int = detail_y + 10 + i * row_h
|
||||
draw_rect(Rect2(Vector2(40, ry), Vector2(1200, row_h - 8)), COLOR_PANEL)
|
||||
draw_rect(
|
||||
Rect2(Vector2(40, ry), Vector2(1200, row_h - 8)),
|
||||
COLOR_PANEL_BORDER, false, 1.5,
|
||||
)
|
||||
draw_circle(Vector2(64, ry + (row_h - 8) * 0.5), 12, r2["icon_color"] as Color)
|
||||
draw_string(
|
||||
font, Vector2(92, ry + 22),
|
||||
String(r2["name"]),
|
||||
HORIZONTAL_ALIGNMENT_LEFT, -1, 18, COLOR_TEXT,
|
||||
)
|
||||
draw_string(
|
||||
font, Vector2(92, ry + 44),
|
||||
String(r2["source"]),
|
||||
HORIZONTAL_ALIGNMENT_LEFT, -1, 13, COLOR_DIM,
|
||||
)
|
||||
# Right-aligned amount + delta.
|
||||
draw_string(
|
||||
font, Vector2(1020, ry + 30),
|
||||
"%d (%s)" % [
|
||||
int(r2["amount"]),
|
||||
("+%d" % int(r2["per_turn"])) if int(r2["per_turn"]) >= 0 else str(int(r2["per_turn"])),
|
||||
],
|
||||
HORIZONTAL_ALIGNMENT_LEFT, -1, 18,
|
||||
COLOR_POSITIVE if int(r2["per_turn"]) >= 0 else COLOR_NEGATIVE,
|
||||
)
|
||||
|
||||
|
||||
func _capture_and_quit() -> void:
|
||||
if _captured:
|
||||
return
|
||||
_captured = true
|
||||
DirAccess.make_dir_recursive_absolute(
|
||||
ProjectSettings.globalize_path(OUTPUT_DIR)
|
||||
)
|
||||
var image: Image = get_viewport().get_texture().get_image()
|
||||
if image == null:
|
||||
push_error("strategic_stockpile_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/strategic_stockpile_proof_%s.png" % [OUTPUT_DIR, timestamp]
|
||||
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("strategic_stockpile_proof: %dx%d saved" % [
|
||||
image.get_width(), image.get_height()
|
||||
])
|
||||
else:
|
||||
push_error("strategic_stockpile_proof: save failed: %s" % error_string(err))
|
||||
get_tree().quit()
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://engine/scenes/tests/strategic_stockpile_proof.gd" id="1"]
|
||||
|
||||
[node name="StrategicStockpileProof" type="Node2D"]
|
||||
script = ExtResource("1")
|
||||
153
src/game/engine/scenes/tests/wonder_built_banner_proof.gd
Normal file
153
src/game/engine/scenes/tests/wonder_built_banner_proof.gd
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
extends Node2D
|
||||
## Wonder-built banner proof — renders the centre-screen announcement a
|
||||
## player sees when a world wonder completes. Mock state covers the
|
||||
## 4-tier wonder family (palace / monument / shrine / archive) with
|
||||
## owner attribution + first-mover bonus copy.
|
||||
|
||||
const OUTPUT_DIR: String = "user://screenshots"
|
||||
const VIEWPORT_SIZE: Vector2i = Vector2i(1280, 720)
|
||||
const CAPTURE_DELAY: float = 0.6
|
||||
|
||||
const COLOR_BG: Color = Color(0.04, 0.04, 0.07)
|
||||
const COLOR_BANNER_BG: Color = Color(0.10, 0.09, 0.06)
|
||||
const COLOR_BANNER_BORDER: Color = Color(0.92, 0.78, 0.32)
|
||||
const COLOR_GLOW: Color = Color(0.92, 0.78, 0.32, 0.18)
|
||||
const COLOR_TITLE: Color = Color(1.0, 0.85, 0.32)
|
||||
const COLOR_TEXT: Color = Color(0.92, 0.93, 0.96)
|
||||
const COLOR_DIM: Color = Color(0.65, 0.68, 0.74)
|
||||
const COLOR_OWNER: Color = Color(0.95, 0.65, 0.15)
|
||||
|
||||
const WONDER_ID: String = "Great Anvil of Khazad-dûm"
|
||||
const WONDER_TIER: int = 4
|
||||
const OWNER_NAME: String = "Karak Ankor"
|
||||
const FIRST_MOVER_BONUS: String = "+8 production/turn in this city, +2 happiness empire-wide"
|
||||
const FLAVOR_TEXT: String = (
|
||||
"From bedrock and starsteel, the Great Anvil rises. Its first strike echoes "
|
||||
"across the deep, and the dwarven clans know — a new age has begun."
|
||||
)
|
||||
|
||||
var _captured: bool = false
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
get_viewport().size = VIEWPORT_SIZE
|
||||
DisplayServer.window_set_size(VIEWPORT_SIZE)
|
||||
RenderingServer.set_default_clear_color(COLOR_BG)
|
||||
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()
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
var font: Font = ThemeDB.fallback_font
|
||||
# Outer faint glow rect (visual "spotlight" framing).
|
||||
var banner_w: int = 880
|
||||
var banner_h: int = 340
|
||||
var bx: float = (VIEWPORT_SIZE.x - banner_w) * 0.5
|
||||
var by: float = (VIEWPORT_SIZE.y - banner_h) * 0.5
|
||||
draw_rect(
|
||||
Rect2(Vector2(bx - 24, by - 24), Vector2(banner_w + 48, banner_h + 48)),
|
||||
COLOR_GLOW,
|
||||
)
|
||||
# Inner banner.
|
||||
draw_rect(Rect2(Vector2(bx, by), Vector2(banner_w, banner_h)), COLOR_BANNER_BG)
|
||||
draw_rect(
|
||||
Rect2(Vector2(bx, by), Vector2(banner_w, banner_h)),
|
||||
COLOR_BANNER_BORDER, false, 3.0,
|
||||
)
|
||||
|
||||
# Title: "WONDER COMPLETE" tag.
|
||||
draw_string(
|
||||
font, Vector2(bx + 32, by + 50),
|
||||
"✦ WONDER COMPLETE ✦",
|
||||
HORIZONTAL_ALIGNMENT_LEFT, -1, 22, COLOR_DIM,
|
||||
)
|
||||
# Wonder name large.
|
||||
draw_string(
|
||||
font, Vector2(bx + 32, by + 100),
|
||||
WONDER_ID,
|
||||
HORIZONTAL_ALIGNMENT_LEFT, -1, 32, COLOR_TITLE,
|
||||
)
|
||||
# Tier badge.
|
||||
draw_string(
|
||||
font, Vector2(bx + 32, by + 134),
|
||||
"Tier %d World Wonder" % WONDER_TIER,
|
||||
HORIZONTAL_ALIGNMENT_LEFT, -1, 16, COLOR_DIM,
|
||||
)
|
||||
# Owner line.
|
||||
draw_string(
|
||||
font, Vector2(bx + 32, by + 174),
|
||||
"Built by %s — first to complete" % OWNER_NAME,
|
||||
HORIZONTAL_ALIGNMENT_LEFT, -1, 18, COLOR_OWNER,
|
||||
)
|
||||
# Bonus copy.
|
||||
draw_string(
|
||||
font, Vector2(bx + 32, by + 206),
|
||||
FIRST_MOVER_BONUS,
|
||||
HORIZONTAL_ALIGNMENT_LEFT, -1, 16, COLOR_TEXT,
|
||||
)
|
||||
# Flavor text (line-wrapped manually since draw_string is single-line).
|
||||
var line1: String = (
|
||||
"From bedrock and starsteel, the Great Anvil rises. Its first strike echoes"
|
||||
)
|
||||
var line2: String = (
|
||||
"across the deep, and the dwarven clans know — a new age has begun."
|
||||
)
|
||||
draw_string(
|
||||
font, Vector2(bx + 32, by + 254),
|
||||
line1,
|
||||
HORIZONTAL_ALIGNMENT_LEFT, -1, 14, COLOR_DIM,
|
||||
)
|
||||
draw_string(
|
||||
font, Vector2(bx + 32, by + 274),
|
||||
line2,
|
||||
HORIZONTAL_ALIGNMENT_LEFT, -1, 14, COLOR_DIM,
|
||||
)
|
||||
# Confirm button.
|
||||
var btn_w: int = 200
|
||||
var btn_h: int = 40
|
||||
var btn_x: float = bx + banner_w - btn_w - 28
|
||||
var btn_y: float = by + banner_h - btn_h - 24
|
||||
draw_rect(
|
||||
Rect2(Vector2(btn_x, btn_y), Vector2(btn_w, btn_h)),
|
||||
Color(0.18, 0.16, 0.10),
|
||||
)
|
||||
draw_rect(
|
||||
Rect2(Vector2(btn_x, btn_y), Vector2(btn_w, btn_h)),
|
||||
COLOR_BANNER_BORDER, false, 1.5,
|
||||
)
|
||||
draw_string(
|
||||
font, Vector2(btn_x + 50, btn_y + 26),
|
||||
"Continue",
|
||||
HORIZONTAL_ALIGNMENT_LEFT, -1, 18, COLOR_TITLE,
|
||||
)
|
||||
|
||||
|
||||
func _capture_and_quit() -> void:
|
||||
if _captured:
|
||||
return
|
||||
_captured = true
|
||||
DirAccess.make_dir_recursive_absolute(
|
||||
ProjectSettings.globalize_path(OUTPUT_DIR)
|
||||
)
|
||||
var image: Image = get_viewport().get_texture().get_image()
|
||||
if image == null:
|
||||
push_error("wonder_built_banner_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/wonder_built_banner_proof_%s.png" % [OUTPUT_DIR, timestamp]
|
||||
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("wonder_built_banner_proof: %dx%d saved" % [
|
||||
image.get_width(), image.get_height()
|
||||
])
|
||||
else:
|
||||
push_error("wonder_built_banner_proof: save failed: %s" % error_string(err))
|
||||
get_tree().quit()
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://engine/scenes/tests/wonder_built_banner_proof.gd" id="1"]
|
||||
|
||||
[node name="WonderBuiltBannerProof" type="Node2D"]
|
||||
script = ExtResource("1")
|
||||
Loading…
Add table
Reference in a new issue