320 lines
13 KiB
GDScript
320 lines
13 KiB
GDScript
extends SceneTree
|
||
|
||
const BattleStateScript := preload("res://scripts/core/battle_state.gd")
|
||
const BattleSceneScript := preload("res://scripts/scenes/battle_scene.gd")
|
||
|
||
|
||
func _init() -> void:
|
||
var failures: Array[String] = []
|
||
|
||
_check_contains(
|
||
failures,
|
||
"001 HUD progress",
|
||
_progress_text("res://data/scenarios/001_yellow_turbans.json", false),
|
||
"적군 격파 0/"
|
||
)
|
||
_check_contains(
|
||
failures,
|
||
"001 castle capture progress",
|
||
_progress_text("res://data/scenarios/001_yellow_turbans.json", false),
|
||
"성채 장악"
|
||
)
|
||
_check_contains(
|
||
failures,
|
||
"001 commander defeat risk",
|
||
_defeat_text("res://data/scenarios/001_yellow_turbans.json"),
|
||
"조조 건재"
|
||
)
|
||
_check_contains(
|
||
failures,
|
||
"001 turn pressure risk",
|
||
_defeat_text("res://data/scenarios/001_yellow_turbans.json"),
|
||
"잔여 군령 15"
|
||
)
|
||
_check_contains(
|
||
failures,
|
||
"002 gated victory progress",
|
||
_progress_text("res://data/scenarios/002_sishui_gate.json", false),
|
||
"제2군령에 전공 갱신"
|
||
)
|
||
_check_contains(
|
||
failures,
|
||
"002 pending enemy progress",
|
||
_progress_text("res://data/scenarios/002_sishui_gate.json", false),
|
||
"적군 격파 0/"
|
||
)
|
||
_check_contains(
|
||
failures,
|
||
"007 destination progress",
|
||
_progress_text("res://data/scenarios/007_xian_emperor_escort.json", false),
|
||
"전장 표식 (14, 5) · 미도달"
|
||
)
|
||
_check_contains(
|
||
failures,
|
||
"007 protected-unit risk progress",
|
||
_defeat_text("res://data/scenarios/007_xian_emperor_escort.json"),
|
||
"천자 사자 건재"
|
||
)
|
||
_check_contains(
|
||
failures,
|
||
"045 gated destination progress",
|
||
_progress_text("res://data/scenarios/045_fancheng_relief.json", false),
|
||
"전장 표식 (10, 5) · 미도달"
|
||
)
|
||
_check_event_gated_objective_markers(failures)
|
||
_check_event_gated_multi_cell_objective_markers(failures)
|
||
_check_turn_gated_objective_does_not_create_marker(failures)
|
||
_check_opening_battle_requires_castle_capture(failures)
|
||
_check_objective_update(failures)
|
||
_check_objective_notice(failures)
|
||
_check_terrain_recovery(failures)
|
||
_check_briefing_battlefield_overview(failures)
|
||
_check_opening_board_avoids_side_panel(failures)
|
||
|
||
if failures.is_empty():
|
||
print("objective progress smoke ok")
|
||
quit(0)
|
||
return
|
||
|
||
for failure in failures:
|
||
push_error(failure)
|
||
quit(1)
|
||
|
||
|
||
func _progress_text(path: String, include_defeat_risks: bool) -> String:
|
||
var state = BattleStateScript.new()
|
||
if not state.load_battle(path):
|
||
return "<load failed: %s>" % path
|
||
return state.get_objective_progress_text(include_defeat_risks)
|
||
|
||
|
||
func _defeat_text(path: String) -> String:
|
||
var state = BattleStateScript.new()
|
||
if not state.load_battle(path):
|
||
return "<load failed: %s>" % path
|
||
return state.get_defeat_progress_text()
|
||
|
||
|
||
func _check_objective_update(failures: Array[String]) -> void:
|
||
var state = BattleStateScript.new()
|
||
if not state.load_battle("res://data/scenarios/001_yellow_turbans.json"):
|
||
failures.append("objective update smoke could not load scenario")
|
||
return
|
||
|
||
var signal_values := []
|
||
var logs := []
|
||
state.objective_updated.connect(func(victory: String, defeat: String) -> void:
|
||
signal_values.append({"victory": victory, "defeat": defeat})
|
||
)
|
||
state.log_added.connect(func(message: String) -> void:
|
||
logs.append(message)
|
||
)
|
||
state._apply_objective_event({
|
||
"victory": "Defeat the new vanguard.",
|
||
"defeat": "Cao Cao is defeated."
|
||
})
|
||
if signal_values.size() != 1:
|
||
failures.append("objective update signal expected 1 emission, got %d" % signal_values.size())
|
||
else:
|
||
var signal_value: Dictionary = signal_values[0]
|
||
if str(signal_value.get("victory", "")) != "Defeat the new vanguard.":
|
||
failures.append("objective update signal victory mismatch: %s" % str(signal_value.get("victory", "")))
|
||
if str(signal_value.get("defeat", "")) != "Cao Cao is defeated.":
|
||
failures.append("objective update signal defeat mismatch: %s" % str(signal_value.get("defeat", "")))
|
||
_check_log_contains(failures, logs, "군령 갱신: Defeat the new vanguard.")
|
||
_check_log_contains(failures, logs, "패배 조건 갱신: Cao Cao is defeated.")
|
||
|
||
signal_values.clear()
|
||
logs.clear()
|
||
state._apply_objective_event({"defeat": "Supply train is lost."})
|
||
if signal_values.size() != 1:
|
||
failures.append("defeat-only objective signal expected 1 emission, got %d" % signal_values.size())
|
||
else:
|
||
var defeat_signal_value: Dictionary = signal_values[0]
|
||
if not str(defeat_signal_value.get("victory", "")).is_empty():
|
||
failures.append("defeat-only objective signal should not include victory: %s" % str(defeat_signal_value.get("victory", "")))
|
||
if str(defeat_signal_value.get("defeat", "")) != "Supply train is lost.":
|
||
failures.append("defeat-only objective signal mismatch: %s" % str(defeat_signal_value.get("defeat", "")))
|
||
_check_log_contains(failures, logs, "패배 조건 갱신: Supply train is lost.")
|
||
|
||
|
||
func _check_event_gated_objective_markers(failures: Array[String]) -> void:
|
||
var state = BattleStateScript.new()
|
||
if not state.load_battle("res://data/scenarios/013_wuchao_raid.json"):
|
||
failures.append("event-gated objective marker smoke could not load Wuchao raid")
|
||
return
|
||
|
||
var depot_cell := Vector2i(14, 4)
|
||
var initial_cells := state.get_objective_cells()
|
||
if not initial_cells.has(depot_cell):
|
||
failures.append("Wuchao depot marker missing before event opens victory condition: %s" % str(initial_cells))
|
||
|
||
var cao_cao: Dictionary = state.get_unit("cao_cao_ch13")
|
||
if cao_cao.is_empty():
|
||
failures.append("event-gated objective marker smoke missing Cao Cao")
|
||
return
|
||
cao_cao["pos"] = depot_cell
|
||
state._run_events("unit_reaches_tile", "player", 1, cao_cao)
|
||
var opened_cells := state.get_objective_cells()
|
||
if opened_cells.has(depot_cell):
|
||
failures.append("Wuchao depot marker should clear after event opens defeat-all objective: %s" % str(opened_cells))
|
||
|
||
|
||
func _check_event_gated_multi_cell_objective_markers(failures: Array[String]) -> void:
|
||
var state = BattleStateScript.new()
|
||
if not state.load_battle("res://data/scenarios/001_yellow_turbans.json"):
|
||
failures.append("multi-cell event-gated objective marker smoke could not load scenario")
|
||
return
|
||
|
||
state.battle_conditions["victory"] = {
|
||
"type": "all_units_defeated",
|
||
"team": "enemy",
|
||
"after_event": "multi_cell_gate"
|
||
}
|
||
state.battle_events.clear()
|
||
state.battle_events.append({
|
||
"id": "multi_cell_gate",
|
||
"once": true,
|
||
"when": {"type": "unit_reaches_tile", "team": "player", "cells": [[3, 2], [3, 3]]},
|
||
"actions": [{"type": "log", "text": "Gate opened."}]
|
||
})
|
||
|
||
var marker_cells := state.get_objective_cells()
|
||
if not marker_cells.has(Vector2i(3, 2)) or not marker_cells.has(Vector2i(3, 3)):
|
||
failures.append("multi-cell event-gated marker should include every event cell: %s" % str(marker_cells))
|
||
|
||
|
||
func _check_turn_gated_objective_does_not_create_marker(failures: Array[String]) -> void:
|
||
var state = BattleStateScript.new()
|
||
if not state.load_battle("res://data/scenarios/001_yellow_turbans.json"):
|
||
failures.append("turn-gated objective marker smoke could not load scenario")
|
||
return
|
||
|
||
state.battle_conditions["victory"] = {
|
||
"type": "all_units_defeated",
|
||
"team": "enemy",
|
||
"after_event": "turn_gate"
|
||
}
|
||
state.battle_events.clear()
|
||
state.battle_events.append({
|
||
"id": "turn_gate",
|
||
"once": true,
|
||
"when": {"type": "turn_start", "team": "player", "turn": 2},
|
||
"actions": [{"type": "log", "text": "Turn gate opened."}]
|
||
})
|
||
|
||
var marker_cells := state.get_objective_cells()
|
||
if not marker_cells.is_empty():
|
||
failures.append("turn-gated objective should not create map markers: %s" % str(marker_cells))
|
||
|
||
|
||
func _check_opening_battle_requires_castle_capture(failures: Array[String]) -> void:
|
||
var state = BattleStateScript.new()
|
||
if not state.load_battle("res://data/scenarios/001_yellow_turbans.json"):
|
||
failures.append("opening castle objective smoke could not load scenario")
|
||
return
|
||
var marker_cells := state.get_objective_cells()
|
||
for castle_cell in [Vector2i(12, 0), Vector2i(13, 0), Vector2i(12, 1), Vector2i(13, 1)]:
|
||
if not marker_cells.has(castle_cell):
|
||
failures.append("opening battle should mark castle capture cell %s: %s" % [str(castle_cell), str(marker_cells)])
|
||
if state.get_objective_cell_label(castle_cell) != "성채 장악":
|
||
failures.append("opening battle castle marker should expose label at %s" % str(castle_cell))
|
||
for enemy in state.get_living_units(BattleStateScript.TEAM_ENEMY):
|
||
enemy["alive"] = false
|
||
state._check_battle_status()
|
||
if str(state.battle_status) != BattleStateScript.STATUS_ACTIVE:
|
||
failures.append("opening battle should not end before castle capture, got %s" % str(state.battle_status))
|
||
var cao_cao: Dictionary = state.get_unit("cao_cao")
|
||
cao_cao["pos"] = Vector2i(12, 1)
|
||
state._check_battle_status()
|
||
if str(state.battle_status) != BattleStateScript.STATUS_VICTORY:
|
||
failures.append("opening battle should end after enemies fall and castle is captured, got %s" % str(state.battle_status))
|
||
|
||
|
||
func _check_log_contains(failures: Array[String], logs: Array, expected: String) -> void:
|
||
for log_entry in logs:
|
||
if str(log_entry) == expected:
|
||
return
|
||
failures.append("expected log `%s` in `%s`" % [expected, logs])
|
||
|
||
|
||
func _check_terrain_recovery(failures: Array[String]) -> void:
|
||
var state = BattleStateScript.new()
|
||
if not state.load_battle("res://data/scenarios/001_yellow_turbans.json"):
|
||
failures.append("terrain recovery smoke could not load scenario")
|
||
return
|
||
var cao_cao: Dictionary = state.get_unit("cao_cao")
|
||
cao_cao["pos"] = Vector2i(6, 5)
|
||
cao_cao["hp"] = 10
|
||
state._reset_team_actions(BattleStateScript.TEAM_PLAYER)
|
||
if int(cao_cao.get("hp", 0)) != 16:
|
||
failures.append("village terrain should recover 6 hp at phase start, got %d" % int(cao_cao.get("hp", 0)))
|
||
var boss: Dictionary = state.get_unit("yellow_turban_1")
|
||
boss["hp"] = 20
|
||
state._reset_team_actions(BattleStateScript.TEAM_ENEMY)
|
||
if int(boss.get("hp", 0)) != 28:
|
||
failures.append("castle terrain should recover 8 hp at phase start, got %d" % int(boss.get("hp", 0)))
|
||
|
||
|
||
func _check_briefing_battlefield_overview(failures: Array[String]) -> void:
|
||
var scene = BattleSceneScript.new()
|
||
if not scene.state.load_battle("res://data/scenarios/001_yellow_turbans.json"):
|
||
failures.append("briefing battlefield overview smoke could not load scenario")
|
||
scene.free()
|
||
return
|
||
var overview := scene._format_briefing_battlefield_overview_text()
|
||
_check_contains(failures, "001 briefing map size", overview, "14칸 x 10칸")
|
||
_check_contains(failures, "001 briefing enemy count", overview, "적세 8명")
|
||
_check_contains(failures, "001 briefing enemy classes", overview, "보병")
|
||
_check_contains(failures, "001 briefing village recovery", overview, "마을 4칸 +6")
|
||
_check_contains(failures, "001 briefing castle recovery", overview, "성채 4칸 +8")
|
||
scene.free()
|
||
|
||
|
||
func _check_opening_board_avoids_side_panel(failures: Array[String]) -> void:
|
||
var state = BattleStateScript.new()
|
||
if not state.load_battle("res://data/scenarios/001_yellow_turbans.json"):
|
||
failures.append("opening board layout smoke could not load scenario")
|
||
return
|
||
var board_rect := Rect2(
|
||
BattleSceneScript.BOARD_OFFSET,
|
||
Vector2(state.get_map_size()) * BattleSceneScript.TILE_SIZE
|
||
)
|
||
var side_rect := Rect2(BattleSceneScript.SIDE_PANEL_POSITION, BattleSceneScript.SIDE_PANEL_SIZE)
|
||
if board_rect.intersects(side_rect):
|
||
failures.append("opening board should not overlap side HUD: board %s side %s" % [str(board_rect), str(side_rect)])
|
||
if board_rect.end.y > 720.0:
|
||
failures.append("opening board should fit within the default viewport height: %s" % str(board_rect))
|
||
|
||
|
||
func _check_objective_notice(failures: Array[String]) -> void:
|
||
var scene = BattleSceneScript.new()
|
||
scene._create_hud()
|
||
scene._on_objective_updated("Defeat the new vanguard.", "")
|
||
if scene.objective_notice_panel == null or not scene.objective_notice_panel.visible:
|
||
failures.append("objective notice panel should be visible after objective update")
|
||
elif not scene.objective_notice_label.text.contains("군령 갱신") or not scene.objective_notice_label.text.contains("목표|Defeat the new vanguard."):
|
||
failures.append("objective notice label should use Korean order wording: %s" % scene.objective_notice_label.text)
|
||
scene._on_objective_updated("", "Supply train is lost.")
|
||
if scene.objective_notice_panel == null or not scene.objective_notice_panel.visible:
|
||
failures.append("objective notice panel should be visible after defeat update")
|
||
elif not scene.objective_notice_label.text.contains("군령 갱신") or not scene.objective_notice_label.text.contains("주의|Supply train is lost."):
|
||
failures.append("objective notice label should use Korean risk wording: %s" % scene.objective_notice_label.text)
|
||
if scene._format_log_entry_text("Objective updated: Defeat the new vanguard.") != "군령 갱신: Defeat the new vanguard.":
|
||
failures.append("objective log display should use Korean order wording")
|
||
if scene._format_log_entry_text("Defeat condition updated: Supply train is lost.") != "패배 조건 갱신: Supply train is lost.":
|
||
failures.append("defeat log display should use Korean risk wording")
|
||
if scene._format_log_entry_text("군령 갱신: Defeat the new vanguard.") != "군령 갱신: Defeat the new vanguard.":
|
||
failures.append("localized objective log should pass through")
|
||
if scene._format_log_entry_text("패배 조건 갱신: Supply train is lost.") != "패배 조건 갱신: Supply train is lost.":
|
||
failures.append("localized defeat log should pass through")
|
||
if scene.objective_notice_timer <= 0.0:
|
||
failures.append("objective notice timer should be active")
|
||
scene.free()
|
||
|
||
|
||
func _check_contains(failures: Array[String], label: String, text: String, expected: String) -> void:
|
||
if text.contains(expected):
|
||
return
|
||
failures.append("%s expected `%s` in `%s`" % [label, expected, text])
|