Add story beats to Xingyang Ambush
This commit is contained in:
@@ -420,6 +420,14 @@
|
||||
"when": { "type": "turn_start", "team": "enemy", "turn": 6 },
|
||||
"actions": [
|
||||
{ "type": "log", "text": "숲 뒤 비탈에서 서영의 궁병대가 퇴로를 끊으려 내려온다." },
|
||||
{
|
||||
"type": "dialogue",
|
||||
"lines": [
|
||||
{ "speaker": "Xu Rong", "display_speaker": "서영", "side": "right", "text": "선봉은 이미 숲 안으로 들어왔다. 비탈 궁병은 하후돈을 묶고, 창병은 길목을 좁혀라." },
|
||||
{ "speaker": "Xiahou Dun", "display_speaker": "하후돈", "side": "left", "text": "저들이 제 선봉을 노립니다. 제가 버티는 동안 본대는 동쪽 길을 잃지 마십시오." },
|
||||
{ "speaker": "Cao Cao", "display_speaker": "조조", "side": "left", "text": "하후돈을 홀로 두지 마라. 비탈의 궁병을 꺾어야 이 숲을 빠져나갈 수 있다." }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "spawn_deployments",
|
||||
"deployments": [
|
||||
@@ -466,6 +474,14 @@
|
||||
"when": { "type": "turn_start", "team": "enemy", "turn": 9 },
|
||||
"actions": [
|
||||
{ "type": "log", "text": "동쪽 길목에서 서영의 마지막 봉쇄병이 나타난다." },
|
||||
{
|
||||
"type": "dialogue",
|
||||
"lines": [
|
||||
{ "speaker": "Xu Rong", "display_speaker": "서영", "side": "right", "text": "여기서 더 물러서면 낙양의 불길이 헛것이 된다. 동쪽 길목을 닫고 조조의 깃발을 끊어라." },
|
||||
{ "speaker": "Cao Ren", "display_speaker": "조인", "side": "left", "text": "길목의 끝에 봉쇄병이 버티고 있습니다. 저들을 남겨 두면 추격로가 다시 막힙니다." },
|
||||
{ "speaker": "Cao Cao", "display_speaker": "조조", "side": "left", "text": "마지막 길을 연다. 길목을 장악하고 남은 봉쇄병까지 모두 쓸어내라." }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "set_objective",
|
||||
"victory": "마지막 봉쇄병을 격파하고 동쪽 길목을 장악하라.",
|
||||
@@ -511,6 +527,24 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "xu_rong_rearguard_falls",
|
||||
"once": true,
|
||||
"when": { "type": "unit_defeated", "team": "enemy", "unit_ids": ["xu_rong_rearguard"] },
|
||||
"actions": [
|
||||
{
|
||||
"type": "dialogue",
|
||||
"lines": [
|
||||
{ "speaker": "Xu Rong", "display_speaker": "서영", "side": "right", "text": "후군이 무너졌다고 끝난 것이 아니다. 숲 뒤 봉쇄병이 조조의 발을 붙잡을 것이다." },
|
||||
{ "speaker": "Cao Cao", "display_speaker": "조조", "side": "left", "text": "서영의 중심은 꺾였다. 방심하지 말고 동쪽 길목까지 진형을 밀어 붙여라." }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "set_objective",
|
||||
"victory": "서영의 후군을 꺾었다. 동쪽 길목을 장악하고 뒤따르는 봉쇄병까지 정리하라."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "turn_5_regroup_pressure",
|
||||
"once": true,
|
||||
|
||||
@@ -25,8 +25,10 @@ func _init() -> void:
|
||||
_check_event_dialogue_side_passthrough(failures)
|
||||
_check_opening_battle_event_dialogue_structure(failures)
|
||||
_check_sishui_gate_event_dialogue_structure(failures)
|
||||
_check_xingyang_ambush_event_dialogue_structure(failures)
|
||||
_check_opening_battle_story_beats(failures)
|
||||
_check_sishui_gate_story_beats(failures)
|
||||
_check_xingyang_ambush_story_beats(failures)
|
||||
_check_pixel_unit_helpers(failures)
|
||||
|
||||
if failures.is_empty():
|
||||
@@ -112,9 +114,10 @@ func _check_dialogue_localization_and_side(failures: Array[String]) -> void:
|
||||
{"speaker": "Lu Bu", "text": "조조를 짓밟아라."},
|
||||
{"speaker": "Qingzhou Chief", "text": "마을 곡식을 내놓아라."},
|
||||
{"speaker": "Camp Merchant", "text": "군막에 물자가 남았습니다."},
|
||||
{"speaker": "Hua Xiong", "text": "사수관을 지켜라."}
|
||||
{"speaker": "Hua Xiong", "text": "사수관을 지켜라."},
|
||||
{"speaker": "Xu Rong", "text": "동쪽 길목을 닫아라."}
|
||||
])
|
||||
if lines.size() != 5:
|
||||
if lines.size() != 6:
|
||||
failures.append("dialogue normalization should keep all valid chapter one lines")
|
||||
scene.free()
|
||||
return
|
||||
@@ -128,6 +131,8 @@ func _check_dialogue_localization_and_side(failures: Array[String]) -> void:
|
||||
failures.append("camp merchant dialogue should be localized and right-sided by default")
|
||||
if lines[4].get("speaker", "") != "화웅" or lines[4].get("side", "") != "right":
|
||||
failures.append("Sishui Gate enemy commander dialogue should localize to Korean on the right")
|
||||
if lines[5].get("speaker", "") != "서영" or lines[5].get("side", "") != "right":
|
||||
failures.append("Xingyang Ambush enemy commander dialogue should localize to Korean on the right")
|
||||
scene.free()
|
||||
|
||||
|
||||
@@ -211,6 +216,41 @@ func _check_sishui_gate_event_dialogue_structure(failures: Array[String]) -> voi
|
||||
failures.append("Hua Xiong vanguard defeat event should clarify the next objective")
|
||||
|
||||
|
||||
func _check_xingyang_ambush_event_dialogue_structure(failures: Array[String]) -> void:
|
||||
var state = BattleStateScript.new()
|
||||
if not state.load_battle("res://data/scenarios/003_xingyang_ambush.json", {}, {}, {"pursued_dong_zhuo": true}):
|
||||
failures.append("could not load Xingyang Ambush for event dialogue structure")
|
||||
return
|
||||
for event_id in [
|
||||
"opening_dialogue_pursuit",
|
||||
"opening_dialogue_regroup",
|
||||
"turn_3_ambush_pursuit",
|
||||
"turn_3_ambush_regroup",
|
||||
"turn_5_pressure",
|
||||
"turn_5_regroup_pressure",
|
||||
"turn_6_slope_archers",
|
||||
"turn_9_forest_blockade",
|
||||
"xu_rong_rearguard_falls"
|
||||
]:
|
||||
var event := _event_by_id(state, event_id)
|
||||
if event.is_empty():
|
||||
failures.append("Xingyang Ambush missing event: %s" % event_id)
|
||||
continue
|
||||
if not _event_has_dialogue_action(event):
|
||||
failures.append("Xingyang Ambush event should include contextual dialogue: %s" % event_id)
|
||||
var rearguard_event := _event_by_id(state, "xu_rong_rearguard_falls")
|
||||
if not rearguard_event.is_empty():
|
||||
var when: Dictionary = rearguard_event.get("when", {})
|
||||
var unit_ids: Array = when.get("unit_ids", [])
|
||||
if not unit_ids.has("xu_rong_rearguard"):
|
||||
failures.append("Xu Rong rearguard defeat event should watch xu_rong_rearguard")
|
||||
if not _event_has_set_objective_action(rearguard_event):
|
||||
failures.append("Xu Rong rearguard defeat event should clarify the next objective")
|
||||
var blockade_event := _event_by_id(state, "turn_9_forest_blockade")
|
||||
if not blockade_event.is_empty() and not _event_has_set_objective_action(blockade_event):
|
||||
failures.append("Xingyang Ambush blockade event should clarify the final objective")
|
||||
|
||||
|
||||
func _check_opening_battle_story_beats(failures: Array[String]) -> void:
|
||||
var state = BattleStateScript.new()
|
||||
if not state.load_battle("res://data/scenarios/001_yellow_turbans.json"):
|
||||
@@ -260,6 +300,60 @@ func _check_sishui_gate_story_beats(failures: Array[String]) -> void:
|
||||
failures.append("Sishui Gate final objective gate should open after the turn 8 story beat")
|
||||
|
||||
|
||||
func _check_xingyang_ambush_story_beats(failures: Array[String]) -> void:
|
||||
var pursuit_state = BattleStateScript.new()
|
||||
if not pursuit_state.load_battle("res://data/scenarios/003_xingyang_ambush.json", {}, {}, {"pursued_dong_zhuo": true}):
|
||||
failures.append("could not load Xingyang Ambush pursuit branch for story beat checks")
|
||||
return
|
||||
|
||||
var pursuit_dialogue_batches := []
|
||||
pursuit_state.dialogue_requested.connect(func(lines: Array) -> void:
|
||||
pursuit_dialogue_batches.append(lines)
|
||||
)
|
||||
|
||||
pursuit_state._run_events("turn_start", BattleStateScript.TEAM_ENEMY, 3)
|
||||
pursuit_state._run_events("turn_start", BattleStateScript.TEAM_PLAYER, 5)
|
||||
pursuit_state._run_events("turn_start", BattleStateScript.TEAM_ENEMY, 6)
|
||||
pursuit_state._run_events("turn_start", BattleStateScript.TEAM_ENEMY, 9)
|
||||
|
||||
if pursuit_dialogue_batches.size() < 4:
|
||||
failures.append("Xingyang Ambush pursuit branch should add dialogue beats for each major ambush phase")
|
||||
var pursuit_victory_text := str(pursuit_state.objectives.get("victory", ""))
|
||||
if not pursuit_victory_text.contains("마지막 봉쇄병"):
|
||||
failures.append("Xingyang Ambush turn 9 event should update the victory objective after the blockade arrives")
|
||||
if not pursuit_state._condition_gate_open({"after_event": "turn_9_forest_blockade"}):
|
||||
failures.append("Xingyang Ambush final objective gate should open after the turn 9 blockade")
|
||||
var xu_rong := pursuit_state.get_unit("xu_rong_rearguard")
|
||||
if xu_rong.is_empty():
|
||||
failures.append("Xingyang Ambush should include Xu Rong rearguard unit for defeat story beat")
|
||||
else:
|
||||
xu_rong["alive"] = false
|
||||
pursuit_state._run_events("unit_defeated", BattleStateScript.TEAM_ENEMY, 9, xu_rong)
|
||||
if pursuit_dialogue_batches.size() < 5:
|
||||
failures.append("Xingyang Ambush should show dialogue when Xu Rong rearguard falls")
|
||||
var rearguard_objective_text := str(pursuit_state.objectives.get("victory", ""))
|
||||
if not rearguard_objective_text.contains("동쪽 길목") or not rearguard_objective_text.contains("봉쇄병"):
|
||||
failures.append("Xingyang Ambush Xu Rong defeat objective should still point to the road and blockade")
|
||||
|
||||
var regroup_state = BattleStateScript.new()
|
||||
if not regroup_state.load_battle("res://data/scenarios/003_xingyang_ambush.json", {}, {}, {"regrouped_after_sishui": true}):
|
||||
failures.append("could not load Xingyang Ambush regroup branch for story beat checks")
|
||||
return
|
||||
|
||||
var regroup_dialogue_batches := []
|
||||
regroup_state.dialogue_requested.connect(func(lines: Array) -> void:
|
||||
regroup_dialogue_batches.append(lines)
|
||||
)
|
||||
|
||||
regroup_state._run_events("turn_start", BattleStateScript.TEAM_ENEMY, 3)
|
||||
regroup_state._run_events("turn_start", BattleStateScript.TEAM_PLAYER, 5)
|
||||
regroup_state._run_events("turn_start", BattleStateScript.TEAM_ENEMY, 6)
|
||||
regroup_state._run_events("turn_start", BattleStateScript.TEAM_ENEMY, 9)
|
||||
|
||||
if regroup_dialogue_batches.size() < 4:
|
||||
failures.append("Xingyang Ambush regroup branch should keep dialogue beats for scouted ambush pressure")
|
||||
|
||||
|
||||
func _event_by_id(state, event_id: String) -> Dictionary:
|
||||
for event in state.battle_events:
|
||||
if typeof(event) == TYPE_DICTIONARY and str(event.get("id", "")) == event_id:
|
||||
|
||||
Reference in New Issue
Block a user