Show status effect durations

This commit is contained in:
2026-06-18 15:23:13 +09:00
parent e005b82bbb
commit 5a3e5e1b64
6 changed files with 223 additions and 11 deletions

View File

@@ -0,0 +1,115 @@
extends SceneTree
const BattleStateScript := preload("res://scripts/core/battle_state.gd")
const CampaignStateScript := preload("res://scripts/core/campaign_state.gd")
const CAMPAIGN_PATH := "res://data/campaign/campaign.json"
const SAVE_PATH := "user://campaign_save.json"
func _init() -> void:
var failures: Array[String] = []
var save_existed := FileAccess.file_exists(SAVE_PATH)
var save_text := FileAccess.get_file_as_string(SAVE_PATH) if save_existed else ""
_check_unequipped_loadout_persists_after_victory(failures)
_restore_save(save_existed, save_text, failures)
if failures.is_empty():
print("equipment campaign persistence smoke ok")
quit(0)
return
for failure in failures:
push_error(failure)
quit(1)
func _check_unequipped_loadout_persists_after_victory(failures: Array[String]) -> void:
var campaign_state = CampaignStateScript.new()
if not campaign_state.load_campaign(CAMPAIGN_PATH):
failures.append("could not load campaign data")
return
campaign_state.start_new(campaign_state.get_start_scenario_id())
var state = BattleStateScript.new()
if not state.load_battle(
campaign_state.get_scenario_path("001_yellow_turbans"),
campaign_state.get_roster_overrides(),
campaign_state.get_inventory_snapshot(),
campaign_state.get_flags_snapshot(),
campaign_state.get_joined_officers_snapshot()
):
failures.append("could not load opening battle")
return
if not state.try_unequip_item("cao_cao", "weapon"):
failures.append("Cao Cao should be able to unequip Bronze Sword before victory")
return
var battle_inventory: Dictionary = state.get_inventory_snapshot()
if int(battle_inventory.get("bronze_sword", 0)) != 1:
failures.append("battle inventory should receive unequipped Bronze Sword")
var battle_equipment: Dictionary = state.get_equipment_snapshot("cao_cao")
if battle_equipment.has("weapon"):
failures.append("battle equipment should keep Cao Cao weapon empty before save")
state.battle_status = "victory"
var result: Dictionary = campaign_state.apply_battle_result(state)
if not bool(result.get("saved", false)):
failures.append("campaign save should succeed after opening victory")
if not campaign_state.completed_scenarios.has("001_yellow_turbans"):
failures.append("opening scenario should be completed after victory")
if campaign_state.current_scenario_id != "002_sishui_gate":
failures.append("campaign should advance to Sishui Gate, got %s" % campaign_state.current_scenario_id)
var campaign_roster: Dictionary = campaign_state.get_roster_overrides()
var cao_cao_roster: Dictionary = campaign_roster.get("cao_cao", {})
var saved_equipment: Dictionary = cao_cao_roster.get("equipment", {})
if saved_equipment.has("weapon"):
failures.append("campaign roster should persist empty Cao Cao weapon slot: %s" % str(saved_equipment))
var campaign_inventory: Dictionary = campaign_state.get_inventory_snapshot()
if int(campaign_inventory.get("bronze_sword", 0)) != 1:
failures.append("campaign inventory should keep returned Bronze Sword")
if int(campaign_inventory.get("bean", 0)) != 1:
failures.append("campaign inventory should include opening victory Bean reward")
var next_state = BattleStateScript.new()
if not next_state.load_battle(
campaign_state.get_scenario_path("002_sishui_gate"),
campaign_state.get_roster_overrides(),
campaign_state.get_inventory_snapshot(),
campaign_state.get_flags_snapshot(),
campaign_state.get_joined_officers_snapshot()
):
failures.append("could not load next battle from saved campaign state")
return
var next_equipment: Dictionary = next_state.get_equipment_snapshot("cao_cao_ch2")
if next_equipment.has("weapon"):
failures.append("next scenario should rehydrate Cao Cao without weapon: %s" % str(next_equipment))
if int(next_state.get_inventory_snapshot().get("bronze_sword", 0)) != 1:
failures.append("next scenario battle inventory should include returned Bronze Sword")
var before_replay_inventory: Dictionary = campaign_state.get_inventory_snapshot()
var before_replay_roster: Dictionary = campaign_state.get_roster_overrides()
var replay_result: Dictionary = campaign_state.apply_battle_result(state)
if not bool(replay_result.get("already_completed", false)):
failures.append("second apply should be treated as completed replay")
if campaign_state.get_inventory_snapshot() != before_replay_inventory:
failures.append("completed replay should not mutate campaign inventory")
if campaign_state.get_roster_overrides() != before_replay_roster:
failures.append("completed replay should not mutate campaign roster")
func _restore_save(save_existed: bool, save_text: String, failures: Array[String]) -> void:
if save_existed:
var file := FileAccess.open(SAVE_PATH, FileAccess.WRITE)
if file == null:
failures.append("could not restore previous campaign save")
return
file.store_string(save_text)
return
if FileAccess.file_exists(SAVE_PATH):
var error := DirAccess.remove_absolute(ProjectSettings.globalize_path(SAVE_PATH))
if error != OK:
failures.append("could not remove temporary campaign save")

View File

@@ -0,0 +1,85 @@
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_status_summary_duration_text(failures)
_check_hud_reuses_status_summary(failures)
if failures.is_empty():
print("status effect summary smoke ok")
quit(0)
return
for failure in failures:
push_error(failure)
quit(1)
func _check_status_summary_duration_text(failures: Array[String]) -> void:
var state = _loaded_state(failures, "status summary")
if state == null:
return
var cao_cao: Dictionary = state.get_unit("cao_cao")
cao_cao["status_effects"] = [
{
"type": "stat_bonus",
"stat": "atk",
"amount": 3,
"remaining_phases": 2
},
{
"type": "damage_over_time",
"status": "poison",
"amount": 4,
"remaining_phases": 1
},
{
"type": "action_lock",
"status": "seal",
"action": "skill",
"remaining_phases": 3
}
]
var summary: String = state.get_status_effect_summary("cao_cao")
_check_contains(failures, "stat duration", summary, "ATK +3 (2 phases)")
_check_contains(failures, "poison duration", summary, "Poison -4 HP (1 phase)")
_check_contains(failures, "seal duration", summary, "Seal no tactics (3 phases)")
func _check_hud_reuses_status_summary(failures: Array[String]) -> void:
var scene = BattleSceneScript.new()
scene._create_hud()
scene.state.load_battle("res://data/scenarios/001_yellow_turbans.json")
var cao_cao: Dictionary = scene.state.get_unit("cao_cao")
cao_cao["status_effects"] = [
{
"type": "stat_bonus",
"stat": "def",
"amount": -2,
"remaining_phases": 2
}
]
scene.state.selected_unit_id = "cao_cao"
scene.hover_cell = Vector2i(1, 3)
scene._update_hud()
_check_contains(failures, "selected HUD status", scene.selected_label.text, "Effects: DEF -2 (2 phases)")
_check_contains(failures, "hover tile status", scene.cell_info_label.text, "Effects: DEF -2 (2 phases)")
scene.free()
func _loaded_state(failures: Array[String], label: String):
var state = BattleStateScript.new()
if not state.load_battle("res://data/scenarios/001_yellow_turbans.json"):
failures.append("%s could not load opening scenario" % label)
return null
return state
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])