extends SceneTree const BattleStateScript := preload("res://scripts/core/battle_state.gd") func _init() -> void: var failures: Array[String] = [] var state = BattleStateScript.new() if not state.load_battle("res://data/scenarios/001_yellow_turbans.json"): push_error("Could not load AI priority smoke scenario.") quit(1) return _check_priority_action(state, failures) _check_priority_changes_enemy_targeting(state, failures) _check_reinforcement_priority_events(failures) _check_guard_ai_behavior(failures) _check_sentry_ai_behavior(failures) if failures.is_empty(): print("event ai priority smoke ok") quit(0) return for failure in failures: push_error(failure) quit(1) func _check_priority_action(state, failures: Array[String]) -> void: var logs: Array[String] = [] var feedback: Array[Dictionary] = [] state.log_added.connect(func(message: String) -> void: logs.append(message) ) state.combat_feedback_requested.connect(func(unit_id: String, text: String, kind: String) -> void: feedback.append({"unit_id": unit_id, "text": text, "kind": kind}) ) state._execute_event_action({ "type": "set_ai_target_priority", "unit_id": "xiahou_dun", "priority": 9, "text": "하후돈에게 적 시선이 몰립니다." }) var target: Dictionary = state.get_unit("xiahou_dun") if int(target.get("ai_target_priority", 0)) != 9: failures.append("AI target priority did not update on event action") if not logs.has("하후돈에게 적 시선이 몰립니다."): failures.append("AI priority event log missing") if not _feedback_has(feedback, "xiahou_dun", "표적", "priority"): failures.append("AI priority feedback missing") func _check_priority_changes_enemy_targeting(state, failures: Array[String]) -> void: var enemy: Dictionary = state.get_unit("yellow_turban_1") var target: Dictionary = state._find_nearest_target(enemy, "player") if str(target.get("id", "")) != "xiahou_dun": failures.append("AI priority did not affect nearest target scoring: %s" % str(target.get("id", ""))) func _check_reinforcement_priority_events(failures: Array[String]) -> void: var state = BattleStateScript.new() if not state.load_battle("res://data/scenarios/001_yellow_turbans.json"): failures.append("Could not load stage 1 priority event scenario") return var logs: Array[String] = [] var feedback: Array[Dictionary] = [] state.log_added.connect(func(message: String) -> void: logs.append(message) ) state.combat_feedback_requested.connect(func(unit_id: String, text: String, kind: String) -> void: feedback.append({"unit_id": unit_id, "text": text, "kind": kind}) ) state._run_events("turn_start", "enemy", 4) if int(state.get_unit("xiahou_dun").get("ai_target_priority", 0)) != 9: failures.append("Turn 4 reinforcement did not pressure Xiahou Dun") if not logs.has("동쪽 잔병이 하후돈의 선봉을 노린다."): failures.append("Turn 4 reinforcement priority log missing") if not _feedback_has(feedback, "xiahou_dun", "표적", "priority"): failures.append("Turn 4 reinforcement priority feedback missing") state._run_events("turn_start", "enemy", 7) if int(state.get_unit("cao_cao").get("ai_target_priority", 0)) != 11: failures.append("Turn 7 reinforcement did not pressure Cao Cao") if not logs.has("남쪽 약탈대가 조조의 본진을 노린다."): failures.append("Turn 7 reinforcement priority log missing") if not _feedback_has(feedback, "cao_cao", "표적", "priority"): failures.append("Turn 7 reinforcement priority feedback missing") func _check_guard_ai_behavior(failures: Array[String]) -> void: var state = BattleStateScript.new() if not state.load_battle("res://data/scenarios/001_yellow_turbans.json"): failures.append("Could not load stage 1 guard AI scenario") return var guard: Dictionary = state.get_unit("yellow_turban_7") if str(guard.get("ai_behavior", "")) != "guard": failures.append("castle defender should load guard AI behavior") if guard.get("ai_guard_anchor", Vector2i(-1, -1)) != Vector2i(19, 2): failures.append("castle defender should keep its guard anchor: %s" % str(guard.get("ai_guard_anchor", null))) if int(guard.get("ai_guard_radius", 0)) != 3: failures.append("castle defender should keep its guard radius") var movement_cells: Array[Vector2i] = state._ai_guard_movement_cells(guard, state._movement_range_for_unit(guard)) if state._ai_guard_is_engaged(guard, movement_cells): failures.append("guard should not engage distant opening player positions") state._enemy_take_action(guard) if guard.get("pos", Vector2i.ZERO) != Vector2i(19, 2): failures.append("guard should hold position instead of chasing distant targets: %s" % str(guard.get("pos", null))) guard["pos"] = Vector2i(15, 2) var displaced_return := state._best_ai_guard_return_cell(guard, state._ai_guard_movement_cells(guard, state._movement_range_for_unit(guard))) if state._manhattan(displaced_return, Vector2i(19, 2)) >= state._manhattan(guard["pos"], Vector2i(19, 2)): failures.append("displaced guard should choose a cell closer to its anchor: %s" % str(displaced_return)) state._enemy_take_action(guard) if state._manhattan(guard.get("pos", Vector2i.ZERO), Vector2i(19, 2)) >= 4: failures.append("displaced guard should step back toward its anchor: %s" % str(guard.get("pos", null))) guard["pos"] = Vector2i(19, 2) state.get_unit("cao_cao")["pos"] = Vector2i(18, 2) movement_cells = state._ai_guard_movement_cells(guard, state._movement_range_for_unit(guard)) if not state._ai_guard_is_engaged(guard, movement_cells): failures.append("guard should engage a player unit inside its guard radius") var target: Dictionary = state._find_nearest_ai_guard_target(guard, "player") if str(target.get("id", "")) != "cao_cao": failures.append("guard target search should prefer targets inside the guard zone: %s" % str(target.get("id", ""))) func _check_sentry_ai_behavior(failures: Array[String]) -> void: var state = BattleStateScript.new() if not state.load_battle("res://data/scenarios/001_yellow_turbans.json"): failures.append("Could not load stage 1 sentry AI scenario") return var sentry: Dictionary = state.get_unit("yellow_turban_5") if str(sentry.get("ai_behavior", "")) != "sentry": failures.append("central raider should load sentry AI behavior") if sentry.get("ai_sentry_anchor", Vector2i(-1, -1)) != Vector2i(10, 6): failures.append("central raider should keep its sentry anchor: %s" % str(sentry.get("ai_sentry_anchor", null))) if int(sentry.get("ai_sentry_radius", 0)) != 5: failures.append("central raider should keep its sentry radius") if int(sentry.get("ai_sentry_activation_turn", 0)) != 4: failures.append("central raider should keep delayed activation turn") state._enemy_take_action(sentry) if bool(sentry.get("ai_awake", false)): failures.append("sentry should not wake while opening player positions are distant") if sentry.get("pos", Vector2i.ZERO) != Vector2i(10, 6): failures.append("sleeping sentry should hold its watch post: %s" % str(sentry.get("pos", null))) state.get_unit("cao_cao")["pos"] = Vector2i(5, 6) state._enemy_take_action(sentry) if not bool(sentry.get("ai_awake", false)): failures.append("sentry should wake when a player enters the lure line") if sentry.get("pos", Vector2i.ZERO) == Vector2i(10, 6): failures.append("woken sentry should start pressing the lure-line target") var delayed_state = BattleStateScript.new() if not delayed_state.load_battle("res://data/scenarios/001_yellow_turbans.json"): failures.append("Could not load delayed sentry AI scenario") return var delayed_sentry: Dictionary = delayed_state.get_unit("yellow_turban_2") delayed_state._enemy_take_action(delayed_sentry) if bool(delayed_sentry.get("ai_awake", false)): failures.append("delayed sentry should not wake before its activation turn") delayed_state.turn_number = 5 delayed_state._enemy_take_action(delayed_sentry) if not bool(delayed_sentry.get("ai_awake", false)): failures.append("delayed sentry should wake on its activation turn") func _feedback_has(feedback: Array[Dictionary], expected_unit_id: String, expected_text: String, expected_kind: String) -> bool: for entry in feedback: if ( str(entry.get("unit_id", "")) == expected_unit_id and str(entry.get("text", "")) == expected_text and str(entry.get("kind", "")) == expected_kind ): return true return false