Add story beats to Sishui Gate
This commit is contained in:
@@ -238,8 +238,9 @@
|
||||
{
|
||||
"type": "dialogue",
|
||||
"lines": [
|
||||
{ "speaker": "Cao Cao", "display_speaker": "조조", "text": "화웅의 선봉은 제후 연합의 담력을 시험하고 있다. 한 치도 땅을 내줄 수 없다." },
|
||||
{ "speaker": "Xiahou Yuan", "display_speaker": "하후연", "text": "적 궁병이 능선을 잡았습니다. 제가 옆구리를 눌러 두겠습니다." }
|
||||
{ "speaker": "Cao Cao", "display_speaker": "조조", "side": "left", "text": "연합의 큰 군기는 뒤에 서 있다. 오늘 앞에 선 깃발은 우리 것이다. 관문 입구를 먼저 열어라." },
|
||||
{ "speaker": "Hua Xiong", "display_speaker": "화웅", "side": "right", "text": "제후들이 사람을 고르다 겨우 조조의 작은 선봉을 내보냈나? 사수관 앞에서 담력을 꺾어 주마." },
|
||||
{ "speaker": "Xiahou Yuan", "display_speaker": "하후연", "side": "right", "text": "적 궁병이 능선을 잡았습니다. 제가 옆구리를 눌러 두겠습니다." }
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -250,6 +251,13 @@
|
||||
"when": { "type": "turn_start", "team": "enemy", "turn": 2 },
|
||||
"actions": [
|
||||
{ "type": "log", "text": "화웅의 병사들이 진형을 조여 왔다." },
|
||||
{
|
||||
"type": "dialogue",
|
||||
"lines": [
|
||||
{ "speaker": "Scout", "display_speaker": "척후", "side": "right", "text": "관문 안쪽 북소리가 빨라졌습니다. 창병과 기병이 입구를 좁히고 있습니다." },
|
||||
{ "speaker": "Cao Cao", "display_speaker": "조조", "side": "left", "text": "저들이 좁힌 길은 우리에게도 칼끝을 모을 곳이다. 관문 입구를 빼앗아 사기를 눌러라." }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "set_objective",
|
||||
"victory": "관문 입구를 장악하고 화웅의 선봉과 원군을 격파하라.",
|
||||
@@ -286,6 +294,14 @@
|
||||
"when": { "type": "turn_start", "team": "enemy", "turn": 5 },
|
||||
"actions": [
|
||||
{ "type": "log", "text": "북쪽 능선에서 화웅의 정찰대가 전장으로 내려온다." },
|
||||
{
|
||||
"type": "dialogue",
|
||||
"lines": [
|
||||
{ "speaker": "Xiahou Yuan", "display_speaker": "하후연", "side": "right", "text": "능선 뒤에서 말발굽과 활시위 소리가 겹칩니다. 저들이 높은 길을 타고 내려옵니다." },
|
||||
{ "speaker": "Hua Xiong", "display_speaker": "화웅", "side": "right", "text": "위에서 누르면 좁은 길의 선봉은 몸을 돌릴 틈도 없다. 조조의 깃발을 몰아붙여라!" },
|
||||
{ "speaker": "Cao Cao", "display_speaker": "조조", "side": "left", "text": "하후돈은 중앙을 붙들고, 하후연은 능선의 눈을 흐려라. 어느 쪽도 관문 앞에서 물러서지 않는다." }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "spawn_deployments",
|
||||
"deployments": [
|
||||
@@ -332,6 +348,18 @@
|
||||
"when": { "type": "turn_start", "team": "enemy", "turn": 8 },
|
||||
"actions": [
|
||||
{ "type": "log", "text": "관문 뒤편에서 화웅의 예비대가 마지막으로 밀려 나온다." },
|
||||
{
|
||||
"type": "dialogue",
|
||||
"lines": [
|
||||
{ "speaker": "Hua Xiong", "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": "관문 입구를 장악한 채 화웅의 마지막 예비대까지 모두 격파하라."
|
||||
},
|
||||
{
|
||||
"type": "spawn_deployments",
|
||||
"deployments": [
|
||||
@@ -371,6 +399,24 @@
|
||||
"text": "화웅의 예비대가 조조의 선봉 깃발을 노린다."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "hua_xiong_vanguard_falls",
|
||||
"once": true,
|
||||
"when": { "type": "unit_defeated", "team": "enemy", "unit_ids": ["hua_xiong_vanguard"] },
|
||||
"actions": [
|
||||
{
|
||||
"type": "dialogue",
|
||||
"lines": [
|
||||
{ "speaker": "Hua Xiong", "display_speaker": "화웅", "side": "right", "text": "내 선봉이 조조의 작은 군에 밀렸다고? 관문 뒤 병력을 앞으로 세워라!" },
|
||||
{ "speaker": "Cao Cao", "display_speaker": "조조", "side": "left", "text": "선봉의 기세가 꺾였다. 그러나 화웅은 아직 문 안에서 병력을 밀어 낼 것이다. 진형을 흐트리지 마라." }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "set_objective",
|
||||
"victory": "화웅의 선봉을 꺾었다. 관문 입구를 장악한 채 남은 예비대까지 격파하라."
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"post_battle_choices": [
|
||||
|
||||
@@ -125,6 +125,7 @@ const SPEAKER_DISPLAY_NAMES := {
|
||||
"Cao Ang": "조앙",
|
||||
"Xu Rong": "서영",
|
||||
"Li Jue": "이각",
|
||||
"Hua Xiong": "화웅",
|
||||
"Zhang Mancheng": "장만성",
|
||||
"Chen Gong": "진궁",
|
||||
"Gao Shun": "고순",
|
||||
@@ -174,6 +175,7 @@ const SPEAKER_CANONICAL_NAMES := {
|
||||
"조앙": "Cao Ang",
|
||||
"서영": "Xu Rong",
|
||||
"이각": "Li Jue",
|
||||
"화웅": "Hua Xiong",
|
||||
"장만성": "Zhang Mancheng",
|
||||
"진궁": "Chen Gong",
|
||||
"고순": "Gao Shun",
|
||||
@@ -213,6 +215,7 @@ const DIALOGUE_RIGHT_SIDE_SPEAKERS := {
|
||||
"Yuan Scout": true,
|
||||
"Imperial Envoy": true,
|
||||
"Imperial Guard": true,
|
||||
"Hua Xiong": true,
|
||||
"Zhang Mancheng": true,
|
||||
"Xu Rong": true,
|
||||
"Li Jue": true,
|
||||
|
||||
@@ -24,7 +24,9 @@ func _init() -> void:
|
||||
_check_dialogue_localization_and_side(failures)
|
||||
_check_event_dialogue_side_passthrough(failures)
|
||||
_check_opening_battle_event_dialogue_structure(failures)
|
||||
_check_sishui_gate_event_dialogue_structure(failures)
|
||||
_check_opening_battle_story_beats(failures)
|
||||
_check_sishui_gate_story_beats(failures)
|
||||
_check_pixel_unit_helpers(failures)
|
||||
|
||||
if failures.is_empty():
|
||||
@@ -109,9 +111,10 @@ func _check_dialogue_localization_and_side(failures: Array[String]) -> void:
|
||||
{"speaker": "Cao Cao", "text": "전열을 세워라."},
|
||||
{"speaker": "Lu Bu", "text": "조조를 짓밟아라."},
|
||||
{"speaker": "Qingzhou Chief", "text": "마을 곡식을 내놓아라."},
|
||||
{"speaker": "Camp Merchant", "text": "군막에 물자가 남았습니다."}
|
||||
{"speaker": "Camp Merchant", "text": "군막에 물자가 남았습니다."},
|
||||
{"speaker": "Hua Xiong", "text": "사수관을 지켜라."}
|
||||
])
|
||||
if lines.size() != 4:
|
||||
if lines.size() != 5:
|
||||
failures.append("dialogue normalization should keep all valid chapter one lines")
|
||||
scene.free()
|
||||
return
|
||||
@@ -123,6 +126,8 @@ func _check_dialogue_localization_and_side(failures: Array[String]) -> void:
|
||||
failures.append("chapter one named enemy dialogue should be localized and right-sided")
|
||||
if lines[3].get("speaker", "") != "군막 상인" or lines[3].get("side", "") != "right":
|
||||
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")
|
||||
scene.free()
|
||||
|
||||
|
||||
@@ -178,6 +183,34 @@ func _check_opening_battle_event_dialogue_structure(failures: Array[String]) ->
|
||||
failures.append("Zhang Mancheng defeat event should watch yellow_turban_1")
|
||||
|
||||
|
||||
func _check_sishui_gate_event_dialogue_structure(failures: Array[String]) -> void:
|
||||
var state = BattleStateScript.new()
|
||||
if not state.load_battle("res://data/scenarios/002_sishui_gate.json"):
|
||||
failures.append("could not load Sishui Gate for event dialogue structure")
|
||||
return
|
||||
for event_id in [
|
||||
"opening_dialogue",
|
||||
"turn_2_warning",
|
||||
"turn_5_ridge_patrol",
|
||||
"turn_8_gate_reserve",
|
||||
"hua_xiong_vanguard_falls"
|
||||
]:
|
||||
var event := _event_by_id(state, event_id)
|
||||
if event.is_empty():
|
||||
failures.append("Sishui Gate missing event: %s" % event_id)
|
||||
continue
|
||||
if not _event_has_dialogue_action(event):
|
||||
failures.append("Sishui Gate event should include contextual dialogue: %s" % event_id)
|
||||
var vanguard_event := _event_by_id(state, "hua_xiong_vanguard_falls")
|
||||
if not vanguard_event.is_empty():
|
||||
var when: Dictionary = vanguard_event.get("when", {})
|
||||
var unit_ids: Array = when.get("unit_ids", [])
|
||||
if not unit_ids.has("hua_xiong_vanguard"):
|
||||
failures.append("Hua Xiong vanguard defeat event should watch hua_xiong_vanguard")
|
||||
if not _event_has_set_objective_action(vanguard_event):
|
||||
failures.append("Hua Xiong vanguard defeat event should clarify the next 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"):
|
||||
@@ -203,6 +236,30 @@ func _check_opening_battle_story_beats(failures: Array[String]) -> void:
|
||||
failures.append("opening battle final objective gate should open after the turn 9 story beat")
|
||||
|
||||
|
||||
func _check_sishui_gate_story_beats(failures: Array[String]) -> void:
|
||||
var state = BattleStateScript.new()
|
||||
if not state.load_battle("res://data/scenarios/002_sishui_gate.json"):
|
||||
failures.append("could not load Sishui Gate for story beat checks")
|
||||
return
|
||||
|
||||
var dialogue_batches := []
|
||||
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_ENEMY, 5)
|
||||
state._run_events("turn_start", BattleStateScript.TEAM_ENEMY, 8)
|
||||
|
||||
if dialogue_batches.size() < 3:
|
||||
failures.append("Sishui Gate should add dialogue beats for each major reserve phase")
|
||||
var victory_text := str(state.objectives.get("victory", ""))
|
||||
if not victory_text.contains("마지막 예비대"):
|
||||
failures.append("Sishui Gate turn 8 event should update the victory objective after the final reserve arrives")
|
||||
if not state._condition_gate_open({"after_event": "turn_8_gate_reserve"}):
|
||||
failures.append("Sishui Gate final objective gate should open after the turn 8 story beat")
|
||||
|
||||
|
||||
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:
|
||||
@@ -218,6 +275,14 @@ func _event_has_dialogue_action(event: Dictionary) -> bool:
|
||||
return false
|
||||
|
||||
|
||||
func _event_has_set_objective_action(event: Dictionary) -> bool:
|
||||
var actions: Array = event.get("actions", [])
|
||||
for action in actions:
|
||||
if typeof(action) == TYPE_DICTIONARY and str(action.get("type", "")) == "set_objective":
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func _check_pixel_unit_helpers(failures: Array[String]) -> void:
|
||||
var scene = BattleSceneScript.new()
|
||||
for method_name in [
|
||||
|
||||
Reference in New Issue
Block a user