Add story beats to Xiapi Siege
This commit is contained in:
@@ -360,6 +360,11 @@
|
||||
{ "speaker": "Chen Gong", "text": "전열이 휘면 장수를 치십시오. 수공의 계책도 그와 함께 죽습니다." }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "set_objective",
|
||||
"victory": "첫 돌격을 버티고 수문 신호가 오를 때까지 하비 포위선을 유지하라.",
|
||||
"defeat": "조조가 퇴각하거나 제18군령이 시작되면 패한다."
|
||||
},
|
||||
{
|
||||
"type": "spawn_deployments",
|
||||
"deployments": [
|
||||
@@ -499,6 +504,10 @@
|
||||
{ "speaker": "Xiahou Dun", "display_speaker": "하후돈", "text": "남은 길이 좁을수록 좋습니다. 좁은 곳에서는 겁먹은 말보다 사람이 먼저 섭니다." }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "set_objective",
|
||||
"victory": "포위선 역습을 막고 여포의 마지막 돌파가 드러날 때까지 하비 포위선을 조여라."
|
||||
},
|
||||
{
|
||||
"type": "spawn_deployments",
|
||||
"deployments": [
|
||||
@@ -596,6 +605,82 @@
|
||||
"text": "여포의 마지막 돌파가 조조와 하후돈을 노린다."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "gao_shun_trap_line_falls",
|
||||
"once": true,
|
||||
"when": { "type": "unit_defeated", "team": "enemy", "unit_ids": ["gao_shun_trap_line"] },
|
||||
"actions": [
|
||||
{
|
||||
"type": "dialogue",
|
||||
"lines": [
|
||||
{ "speaker": "Gao Shun", "display_speaker": "고순", "side": "right", "text": "함진의 방진이 물러서다니... 이 진흙 속에서도 조조군의 포위가 흔들리지 않는구나." },
|
||||
{ "speaker": "Xiahou Dun", "display_speaker": "하후돈", "side": "left", "text": "고순의 방진이 무너졌습니다. 성문 앞의 적도 더는 마음대로 돌파하지 못합니다." },
|
||||
{ "speaker": "Cao Cao", "display_speaker": "조조", "side": "left", "text": "잘 버텼다. 하비 포위선을 그대로 조이고 진궁의 계책과 여포의 돌파를 차례로 꺾어라." }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "set_objective",
|
||||
"victory": "고순의 방진을 꺾었다. 하비 포위선을 유지하며 진궁의 계책과 여포의 최종 돌파를 격파하라."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "chen_gong_strategist_falls",
|
||||
"once": true,
|
||||
"when": { "type": "unit_defeated", "team": "enemy", "unit_ids": ["chen_gong_strategist"] },
|
||||
"actions": [
|
||||
{
|
||||
"type": "dialogue",
|
||||
"lines": [
|
||||
{ "speaker": "Chen Gong", "display_speaker": "진궁", "side": "right", "text": "물이 길을 삼켰고, 계책도 더는 말발굽을 붙들지 못하는군요." },
|
||||
{ "speaker": "Cao Cao", "display_speaker": "조조", "side": "left", "text": "진궁의 전열이 흔들렸다. 하비 포위선을 조이고 여포의 마지막 돌파를 기다려라." },
|
||||
{ "speaker": "Dian Wei", "display_speaker": "전위", "side": "right", "text": "계책이 멎으면 남는 것은 힘입니다. 그 힘도 여기서 받겠습니다." }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "set_objective",
|
||||
"victory": "진궁의 계책을 꺾었다. 하비 포위선을 유지하며 여포의 최종 돌파를 격파하라."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "lu_bu_xiapi_falls",
|
||||
"once": true,
|
||||
"when": { "type": "unit_defeated", "team": "enemy", "unit_ids": ["lu_bu_xiapi"] },
|
||||
"actions": [
|
||||
{
|
||||
"type": "dialogue",
|
||||
"lines": [
|
||||
{ "speaker": "Lu Bu", "display_speaker": "여포", "side": "right", "text": "이 물과 진흙이... 내 길을 여기서 끊는단 말이냐." },
|
||||
{ "speaker": "Xiahou Dun", "display_speaker": "하후돈", "side": "right", "text": "여포의 본대가 무너졌습니다. 포위선 안의 남은 돌파병도 흔들립니다." },
|
||||
{ "speaker": "Cao Cao", "display_speaker": "조조", "side": "left", "text": "여포를 꺾었다. 방심하지 말고 하비 포위선을 붙들어 남은 수비대를 정리하라." }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "set_objective",
|
||||
"victory": "여포를 꺾었다. 하비 포위선을 붙들고 남은 수비대를 격파하라."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "lu_bu_final_breakout_rider_falls",
|
||||
"once": true,
|
||||
"when": { "type": "unit_defeated", "team": "enemy", "unit_ids": ["lu_bu_final_breakout_rider"] },
|
||||
"actions": [
|
||||
{
|
||||
"type": "dialogue",
|
||||
"lines": [
|
||||
{ "speaker": "Lu Bu", "display_speaker": "여포", "side": "right", "text": "마지막 돌파마저 막히다니... 아직 성문은 완전히 닫히지 않았다!" },
|
||||
{ "speaker": "Cao Ren", "display_speaker": "조인", "side": "right", "text": "최종 돌파의 앞줄이 꺾였습니다. 남은 병력이 성벽 쪽으로 밀립니다." },
|
||||
{ "speaker": "Cao Cao", "display_speaker": "조조", "side": "left", "text": "마지막 돌파를 끊었다. 하비 포위선을 지키며 남은 적을 완전히 거두어라." }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "set_objective",
|
||||
"victory": "여포의 최종 돌파 선봉을 꺾었다. 하비 포위선을 지키며 남은 적을 격파하라."
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"rewards": {
|
||||
|
||||
@@ -31,6 +31,7 @@ func _init() -> void:
|
||||
_check_dingtao_counterattack_event_dialogue_structure(failures)
|
||||
_check_xian_emperor_escort_event_dialogue_structure(failures)
|
||||
_check_wan_castle_escape_event_dialogue_structure(failures)
|
||||
_check_xiapi_siege_event_dialogue_structure(failures)
|
||||
_check_opening_battle_story_beats(failures)
|
||||
_check_sishui_gate_story_beats(failures)
|
||||
_check_xingyang_ambush_story_beats(failures)
|
||||
@@ -39,6 +40,7 @@ func _init() -> void:
|
||||
_check_dingtao_counterattack_story_beats(failures)
|
||||
_check_xian_emperor_escort_story_beats(failures)
|
||||
_check_wan_castle_escape_story_beats(failures)
|
||||
_check_xiapi_siege_story_beats(failures)
|
||||
_check_pixel_unit_helpers(failures)
|
||||
|
||||
if failures.is_empty():
|
||||
@@ -627,6 +629,82 @@ func _check_wan_castle_escape_event_dialogue_structure(failures: Array[String])
|
||||
failures.append("Wan Castle Escape event should update the active objective: %s" % objective_event_id)
|
||||
|
||||
|
||||
func _check_xiapi_siege_event_dialogue_structure(failures: Array[String]) -> void:
|
||||
var state = BattleStateScript.new()
|
||||
if not state.load_battle("res://data/scenarios/009_xiapi_siege.json"):
|
||||
failures.append("could not load Xiapi Siege for event dialogue structure")
|
||||
return
|
||||
for event_id in [
|
||||
"opening_dialogue",
|
||||
"turn_2_lu_bu_charge",
|
||||
"turn_3_flood_gates",
|
||||
"wan_gate_baggage_support",
|
||||
"swift_wan_counterraid",
|
||||
"turn_6_lu_bu_cornered",
|
||||
"turn_6_siege_line_counter",
|
||||
"turn_9_lu_bu_final_breakout",
|
||||
"gao_shun_trap_line_falls",
|
||||
"chen_gong_strategist_falls",
|
||||
"lu_bu_xiapi_falls",
|
||||
"lu_bu_final_breakout_rider_falls"
|
||||
]:
|
||||
var event := _event_by_id(state, event_id)
|
||||
if event.is_empty():
|
||||
failures.append("Xiapi Siege missing event: %s" % event_id)
|
||||
continue
|
||||
if not _event_has_dialogue_action(event):
|
||||
failures.append("Xiapi Siege event should include contextual dialogue: %s" % event_id)
|
||||
var gao_shun_event := _event_by_id(state, "gao_shun_trap_line_falls")
|
||||
if not gao_shun_event.is_empty():
|
||||
var when: Dictionary = gao_shun_event.get("when", {})
|
||||
if str(when.get("type", "")) != "unit_defeated":
|
||||
failures.append("Gao Shun trap line defeat event should trigger when the unit is defeated")
|
||||
var unit_ids: Array = when.get("unit_ids", [])
|
||||
if not unit_ids.has("gao_shun_trap_line"):
|
||||
failures.append("Gao Shun trap line defeat event should watch gao_shun_trap_line")
|
||||
if not _event_has_set_objective_action(gao_shun_event):
|
||||
failures.append("Gao Shun trap line defeat event should clarify the siege objective")
|
||||
var chen_gong_event := _event_by_id(state, "chen_gong_strategist_falls")
|
||||
if not chen_gong_event.is_empty():
|
||||
var when: Dictionary = chen_gong_event.get("when", {})
|
||||
if str(when.get("type", "")) != "unit_defeated":
|
||||
failures.append("Chen Gong defeat event should trigger when the unit is defeated")
|
||||
var unit_ids: Array = when.get("unit_ids", [])
|
||||
if not unit_ids.has("chen_gong_strategist"):
|
||||
failures.append("Chen Gong defeat event should watch chen_gong_strategist")
|
||||
if not _event_has_set_objective_action(chen_gong_event):
|
||||
failures.append("Chen Gong defeat event should clarify the siege objective")
|
||||
var lu_bu_event := _event_by_id(state, "lu_bu_xiapi_falls")
|
||||
if not lu_bu_event.is_empty():
|
||||
var when: Dictionary = lu_bu_event.get("when", {})
|
||||
if str(when.get("type", "")) != "unit_defeated":
|
||||
failures.append("Lu Bu Xiapi defeat event should trigger when the unit is defeated")
|
||||
var unit_ids: Array = when.get("unit_ids", [])
|
||||
if not unit_ids.has("lu_bu_xiapi"):
|
||||
failures.append("Lu Bu Xiapi defeat event should watch lu_bu_xiapi")
|
||||
if not _event_has_set_objective_action(lu_bu_event):
|
||||
failures.append("Lu Bu Xiapi defeat event should clarify the siege objective")
|
||||
var final_breakout_event := _event_by_id(state, "lu_bu_final_breakout_rider_falls")
|
||||
if not final_breakout_event.is_empty():
|
||||
var when: Dictionary = final_breakout_event.get("when", {})
|
||||
if str(when.get("type", "")) != "unit_defeated":
|
||||
failures.append("Lu Bu final breakout rider event should trigger when the unit is defeated")
|
||||
var unit_ids: Array = when.get("unit_ids", [])
|
||||
if not unit_ids.has("lu_bu_final_breakout_rider"):
|
||||
failures.append("Lu Bu final breakout rider event should watch lu_bu_final_breakout_rider")
|
||||
if not _event_has_set_objective_action(final_breakout_event):
|
||||
failures.append("Lu Bu final breakout rider event should clarify the final siege objective")
|
||||
for objective_event_id in [
|
||||
"turn_2_lu_bu_charge",
|
||||
"turn_3_flood_gates",
|
||||
"turn_6_siege_line_counter",
|
||||
"turn_9_lu_bu_final_breakout"
|
||||
]:
|
||||
var objective_event := _event_by_id(state, objective_event_id)
|
||||
if not objective_event.is_empty() and not _event_has_set_objective_action(objective_event):
|
||||
failures.append("Xiapi Siege event should update the active objective: %s" % objective_event_id)
|
||||
|
||||
|
||||
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"):
|
||||
@@ -998,6 +1076,84 @@ func _check_wan_castle_escape_story_beats(failures: Array[String]) -> void:
|
||||
failures.append("West gate hunt rider defeat objective should still point to the escape road and remaining pursuers")
|
||||
|
||||
|
||||
func _check_xiapi_siege_story_beats(failures: Array[String]) -> void:
|
||||
var state = BattleStateScript.new()
|
||||
if not state.load_battle("res://data/scenarios/009_xiapi_siege.json"):
|
||||
failures.append("could not load Xiapi Siege for story beat checks")
|
||||
return
|
||||
|
||||
var dialogue_batches: Array = []
|
||||
state.dialogue_requested.connect(func(lines: Array) -> void:
|
||||
dialogue_batches.append(lines)
|
||||
)
|
||||
|
||||
state._run_events("turn_start", BattleStateScript.TEAM_ENEMY, 2)
|
||||
state._run_events("turn_start", BattleStateScript.TEAM_PLAYER, 3)
|
||||
state._run_events("turn_start", BattleStateScript.TEAM_PLAYER, 6)
|
||||
state._run_events("turn_start", BattleStateScript.TEAM_ENEMY, 6)
|
||||
state._run_events("turn_start", BattleStateScript.TEAM_ENEMY, 9)
|
||||
|
||||
if dialogue_batches.size() < 5:
|
||||
failures.append("Xiapi Siege should add dialogue beats for each major flood siege phase")
|
||||
var victory_text := str(state.objectives.get("victory", ""))
|
||||
if not victory_text.contains("하비 포위선") or not victory_text.contains("마지막 돌파"):
|
||||
failures.append("Xiapi Siege turn 9 event should update the victory objective for Lu Bu final breakout")
|
||||
if not state._condition_gate_open({"after_event": "turn_3_flood_gates"}):
|
||||
failures.append("Xiapi Siege flood gate story beat should open the flood gate event")
|
||||
if not state._condition_gate_open({"after_event": "turn_9_lu_bu_final_breakout"}):
|
||||
failures.append("Xiapi Siege final objective gate should open after the turn 9 final breakout")
|
||||
if bool(state.get_unit("lu_bu_flank_rider_north").get("deployed", true)):
|
||||
failures.append("Xiapi Siege flood gate story beat should withdraw the north flank rider")
|
||||
|
||||
var gao_shun: Dictionary = state.get_unit("gao_shun_trap_line")
|
||||
if gao_shun.is_empty():
|
||||
failures.append("Xiapi Siege should include Gao Shun trap line for the siege line collapse story beat")
|
||||
else:
|
||||
gao_shun["alive"] = false
|
||||
state._run_events("unit_defeated", BattleStateScript.TEAM_ENEMY, 9, gao_shun)
|
||||
if dialogue_batches.size() < 6:
|
||||
failures.append("Xiapi Siege should show dialogue when Gao Shun trap line falls")
|
||||
var gao_shun_objective_text := str(state.objectives.get("victory", ""))
|
||||
if not gao_shun_objective_text.contains("하비 포위선") or not gao_shun_objective_text.contains("진궁"):
|
||||
failures.append("Gao Shun trap line defeat objective should still point to the siege line and Chen Gong")
|
||||
|
||||
var chen_gong: Dictionary = state.get_unit("chen_gong_strategist")
|
||||
if chen_gong.is_empty():
|
||||
failures.append("Xiapi Siege should include Chen Gong for the scheme collapse story beat")
|
||||
else:
|
||||
chen_gong["alive"] = false
|
||||
state._run_events("unit_defeated", BattleStateScript.TEAM_ENEMY, 9, chen_gong)
|
||||
if dialogue_batches.size() < 7:
|
||||
failures.append("Xiapi Siege should show dialogue when Chen Gong falls")
|
||||
var chen_gong_objective_text := str(state.objectives.get("victory", ""))
|
||||
if not chen_gong_objective_text.contains("하비 포위선") or not chen_gong_objective_text.contains("최종 돌파"):
|
||||
failures.append("Chen Gong defeat objective should still point to the siege line and final breakout")
|
||||
|
||||
var lu_bu: Dictionary = state.get_unit("lu_bu_xiapi")
|
||||
if lu_bu.is_empty():
|
||||
failures.append("Xiapi Siege should include Lu Bu for the chapter climax story beat")
|
||||
else:
|
||||
lu_bu["alive"] = false
|
||||
state._run_events("unit_defeated", BattleStateScript.TEAM_ENEMY, 9, lu_bu)
|
||||
if dialogue_batches.size() < 8:
|
||||
failures.append("Xiapi Siege should show dialogue when Lu Bu falls")
|
||||
var lu_bu_objective_text := str(state.objectives.get("victory", ""))
|
||||
if not lu_bu_objective_text.contains("하비 포위선") or not lu_bu_objective_text.contains("남은"):
|
||||
failures.append("Lu Bu defeat objective should still point to the siege line and remaining defenders")
|
||||
|
||||
var final_breakout: Dictionary = state.get_unit("lu_bu_final_breakout_rider")
|
||||
if final_breakout.is_empty():
|
||||
failures.append("Xiapi Siege should include Lu Bu final breakout rider for the final pressure story beat")
|
||||
else:
|
||||
final_breakout["alive"] = false
|
||||
state._run_events("unit_defeated", BattleStateScript.TEAM_ENEMY, 9, final_breakout)
|
||||
if dialogue_batches.size() < 9:
|
||||
failures.append("Xiapi Siege should show dialogue when Lu Bu final breakout rider falls")
|
||||
var final_objective_text := str(state.objectives.get("victory", ""))
|
||||
if not final_objective_text.contains("하비 포위선") or not final_objective_text.contains("남은"):
|
||||
failures.append("Lu Bu final breakout rider objective should still point to the siege line and remaining enemies")
|
||||
|
||||
|
||||
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