Improve White Horse relief battle pacing
This commit is contained in:
@@ -2,14 +2,27 @@
|
||||
"id": "010_white_horse_relief",
|
||||
"name": "백마 구원전",
|
||||
"objectives": {
|
||||
"victory": "유인 진형이 펼쳐진 뒤 안량 선봉을 격파하라.",
|
||||
"defeat": "조조가 퇴각하거나 제12군령에 이르면 패한다."
|
||||
"victory": "백마 유인선을 붙들고 원소군의 최종 압박을 격파하라.",
|
||||
"defeat": "조조가 퇴각하거나 제18군령이 시작되면 패한다."
|
||||
},
|
||||
"conditions": {
|
||||
"victory": { "type": "all_units_defeated", "team": "enemy", "after_event": "turn_3_decoy_line" },
|
||||
"victory": {
|
||||
"type": "all",
|
||||
"conditions": [
|
||||
{ "type": "all_units_defeated", "team": "enemy", "after_event": "turn_9_yuan_main_pressure" },
|
||||
{
|
||||
"type": "unit_reaches_tile",
|
||||
"label": "백마 유인선",
|
||||
"team": "player",
|
||||
"unit_ids": ["cao_cao_ch10", "guo_jia_ch10", "xiahou_dun_ch10", "dian_wei_ch10", "xiahou_yuan_ch10", "cao_ren_ch10"],
|
||||
"officer_ids": ["cao_cao", "guo_jia", "xiahou_dun", "dian_wei", "xiahou_yuan", "cao_ren"],
|
||||
"cells": [[8, 4], [9, 4], [8, 5], [9, 5]]
|
||||
}
|
||||
]
|
||||
},
|
||||
"defeat": [
|
||||
{ "type": "any_officer_defeated", "team": "player", "officer_ids": ["cao_cao"] },
|
||||
{ "type": "turn_reached", "turn": 12, "team": "player" }
|
||||
{ "type": "turn_reached", "turn": 18, "team": "player" }
|
||||
]
|
||||
},
|
||||
"briefing": {
|
||||
@@ -17,7 +30,8 @@
|
||||
"location": "백마, 건안 오년",
|
||||
"lines": [
|
||||
"원소의 전군이 안량을 앞세워 백마 나루를 짓누르고 있다.",
|
||||
"곽가는 거짓 퇴각을 청하였다. 선봉을 길 한복판으로 끌어낸 뒤 양익이 길게 벌어진 틈을 칠 계책이다."
|
||||
"곽가는 거짓 퇴각을 청하였다. 선봉을 길 한복판으로 끌어낸 뒤 양익이 길게 벌어진 틈을 칠 계책이다.",
|
||||
"제9군령에 원소의 본대 압박이 닿을 때까지 백마 유인선을 무너뜨리지 말고 안량의 선봉을 끊어라."
|
||||
],
|
||||
"conditional_lines": [
|
||||
{
|
||||
@@ -162,7 +176,7 @@
|
||||
},
|
||||
"roster": {
|
||||
"max_units": 6,
|
||||
"required_officers": ["cao_cao", "guo_jia"]
|
||||
"required_officers": ["cao_cao", "guo_jia", "xiahou_dun"]
|
||||
},
|
||||
"formation": {
|
||||
"cells": [[1, 3], [1, 4], [1, 5], [2, 2], [2, 3], [2, 4], [2, 5], [2, 6], [3, 3], [3, 4]]
|
||||
@@ -175,11 +189,11 @@
|
||||
"GGGGGGHHGGGGGGG",
|
||||
"GGFFGGHHGGGFFGG",
|
||||
"GGFFRRRRRRGFFGG",
|
||||
"GGGGGRGGGRGGGGG",
|
||||
"GGGGGRGGGRCCGGG",
|
||||
"RRRRRRRRRRRRRRR",
|
||||
"GGGGGRGGGRGGGGG",
|
||||
"GGHHGRRRGGFFFGG",
|
||||
"GGHHGFFFGGFFFGG",
|
||||
"GGGGGRGGGRCCGGG",
|
||||
"GGHHTTRRGGFFFGG",
|
||||
"GGHHTTFFGGFFFGG",
|
||||
"GGGGGFFFGGWWGGG",
|
||||
"GGGGGGGGGGWWGGG"
|
||||
]
|
||||
@@ -279,6 +293,33 @@
|
||||
"pos": [12, 5],
|
||||
"skills": ["blaze"],
|
||||
"base": { "hp": 38, "mp": 22, "atk": 6, "def": 6, "int": 14 }
|
||||
},
|
||||
{
|
||||
"unit_id": "white_horse_spear_support",
|
||||
"name": "백마 창병",
|
||||
"class_id": "infantry",
|
||||
"team": "enemy",
|
||||
"level": 9,
|
||||
"pos": [8, 3],
|
||||
"base": { "hp": 44, "atk": 15, "def": 9 }
|
||||
},
|
||||
{
|
||||
"unit_id": "white_horse_rider_screen",
|
||||
"name": "백마 기병",
|
||||
"class_id": "cavalry",
|
||||
"team": "enemy",
|
||||
"level": 9,
|
||||
"pos": [9, 6],
|
||||
"base": { "hp": 42, "atk": 16, "def": 8, "agi": 11 }
|
||||
},
|
||||
{
|
||||
"unit_id": "yuan_scout_archer",
|
||||
"name": "원소 척후궁병",
|
||||
"class_id": "archer",
|
||||
"team": "enemy",
|
||||
"level": 9,
|
||||
"pos": [11, 7],
|
||||
"base": { "hp": 34, "atk": 14, "def": 6 }
|
||||
}
|
||||
],
|
||||
"events": [
|
||||
@@ -356,8 +397,8 @@
|
||||
},
|
||||
{
|
||||
"type": "set_objective",
|
||||
"victory": "안량과 고립된 선봉 전부를 격파하라.",
|
||||
"defeat": "조조가 퇴각하거나 제12군령에 이르면 패한다."
|
||||
"victory": "백마 유인선을 붙들고 원소군의 본대 압박을 기다려라.",
|
||||
"defeat": "조조가 퇴각하거나 제18군령이 시작되면 패한다."
|
||||
},
|
||||
{
|
||||
"type": "spawn_deployments",
|
||||
@@ -451,6 +492,117 @@
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "turn_6_yuan_wings_close",
|
||||
"once": true,
|
||||
"when": { "type": "turn_start", "team": "enemy", "turn": 6 },
|
||||
"actions": [
|
||||
{ "type": "log", "text": "원소군 양익이 백마 유인선의 가장자리로 밀려든다." },
|
||||
{
|
||||
"type": "dialogue",
|
||||
"lines": [
|
||||
{ "speaker": "Yuan Scout", "display_speaker": "원소 척후", "text": "안량 장군의 선봉이 너무 깊습니다. 양익을 좁혀 조조의 중군을 물어라!" },
|
||||
{ "speaker": "Xiahou Dun", "display_speaker": "하후돈", "text": "거짓 퇴각이 참패로 보일 만큼만 흔들린다. 더는 내주지 마라." }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "spawn_deployments",
|
||||
"deployments": [
|
||||
{
|
||||
"unit_id": "yuan_wing_rider",
|
||||
"name": "원소 양익기병",
|
||||
"class_id": "cavalry",
|
||||
"team": "enemy",
|
||||
"level": 9,
|
||||
"pos": [6, 1],
|
||||
"base": { "hp": 42, "atk": 16, "def": 8, "agi": 11 }
|
||||
},
|
||||
{
|
||||
"unit_id": "yuan_wing_guard",
|
||||
"name": "원소 양익창병",
|
||||
"class_id": "infantry",
|
||||
"team": "enemy",
|
||||
"level": 9,
|
||||
"pos": [6, 8],
|
||||
"base": { "hp": 44, "atk": 15, "def": 9 }
|
||||
},
|
||||
{
|
||||
"unit_id": "yuan_wing_archer",
|
||||
"name": "원소 양익궁병",
|
||||
"class_id": "archer",
|
||||
"team": "enemy",
|
||||
"level": 9,
|
||||
"pos": [9, 8],
|
||||
"base": { "hp": 34, "atk": 14, "def": 6 }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "set_ai_target_priority",
|
||||
"unit_id": "xiahou_dun_ch10",
|
||||
"priority": 10,
|
||||
"text": "원소 양익이 하후돈의 거짓 퇴각선을 노린다."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "turn_9_yuan_main_pressure",
|
||||
"once": true,
|
||||
"when": { "type": "turn_start", "team": "enemy", "turn": 9 },
|
||||
"actions": [
|
||||
{
|
||||
"type": "set_objective",
|
||||
"victory": "백마 유인선을 붙들고 원소군의 최종 압박을 격파하라.",
|
||||
"defeat": "조조가 퇴각하거나 제18군령이 시작되면 패한다."
|
||||
},
|
||||
{ "type": "log", "text": "원소군 본대의 압박이 백마 나루로 밀려든다." },
|
||||
{
|
||||
"type": "dialogue",
|
||||
"lines": [
|
||||
{ "speaker": "Yan Liang", "display_speaker": "안량", "text": "조조의 물러섬은 겁이 아니었나. 그렇다면 힘으로 그 꾀까지 부숴 주겠다!" },
|
||||
{ "speaker": "Guo Jia", "display_speaker": "곽가", "text": "이제 안량은 돌아갈 엄호보다 앞에 있습니다. 유인선이 닫히는 순간입니다." }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "spawn_deployments",
|
||||
"deployments": [
|
||||
{
|
||||
"unit_id": "yuan_main_rider",
|
||||
"name": "원소 본대기병",
|
||||
"class_id": "cavalry",
|
||||
"team": "enemy",
|
||||
"level": 10,
|
||||
"pos": [13, 3],
|
||||
"base": { "hp": 48, "atk": 17, "def": 8, "agi": 12 }
|
||||
},
|
||||
{
|
||||
"unit_id": "yuan_main_guard",
|
||||
"name": "원소 본대창병",
|
||||
"class_id": "infantry",
|
||||
"team": "enemy",
|
||||
"level": 10,
|
||||
"pos": [13, 5],
|
||||
"base": { "hp": 48, "atk": 16, "def": 10 }
|
||||
},
|
||||
{
|
||||
"unit_id": "yuan_main_archer",
|
||||
"name": "원소 본대궁병",
|
||||
"class_id": "archer",
|
||||
"team": "enemy",
|
||||
"level": 10,
|
||||
"pos": [13, 7],
|
||||
"base": { "hp": 36, "atk": 14, "def": 6 }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "set_ai_target_priority",
|
||||
"unit_ids": ["cao_cao_ch10", "xiahou_dun_ch10"],
|
||||
"priority": 13,
|
||||
"text": "원소군 본대가 조조와 하후돈을 함께 노린다."
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"rewards": {
|
||||
|
||||
@@ -510,9 +510,14 @@ func _joined_officer_lookup(source) -> Dictionary:
|
||||
func _is_deployment_available(unit: Dictionary) -> bool:
|
||||
if unit.get("team", "") != TEAM_PLAYER:
|
||||
return true
|
||||
var required_officers: Array = deployment_rules.get("required_officers", [])
|
||||
var required_units: Array = deployment_rules.get("required_units", [])
|
||||
var officer_id := str(unit.get("officer_id", ""))
|
||||
var unit_id := str(unit.get("id", ""))
|
||||
if required_units.has(unit_id) or required_officers.has(officer_id):
|
||||
return true
|
||||
if not bool(unit.get("requires_joined", false)):
|
||||
return true
|
||||
var officer_id := str(unit.get("officer_id", ""))
|
||||
return not officer_id.is_empty() and campaign_joined_officers.has(officer_id)
|
||||
|
||||
|
||||
|
||||
@@ -151,6 +151,18 @@ func _init() -> void:
|
||||
_progress_text("res://data/scenarios/009_xiapi_siege.json", false),
|
||||
"하비 포위선"
|
||||
)
|
||||
_check_contains(
|
||||
failures,
|
||||
"010 gated victory progress",
|
||||
_progress_text("res://data/scenarios/010_white_horse_relief.json", false),
|
||||
"제9군령에 전공 갱신"
|
||||
)
|
||||
_check_contains(
|
||||
failures,
|
||||
"010 decoy line progress",
|
||||
_progress_text("res://data/scenarios/010_white_horse_relief.json", false),
|
||||
"백마 유인선"
|
||||
)
|
||||
_check_contains(
|
||||
failures,
|
||||
"045 gated destination progress",
|
||||
@@ -169,6 +181,7 @@ func _init() -> void:
|
||||
_check_xian_emperor_escort_extended_pressure(failures)
|
||||
_check_wan_castle_escape_extended_pressure(failures)
|
||||
_check_xiapi_siege_extended_pressure(failures)
|
||||
_check_white_horse_relief_extended_pressure(failures)
|
||||
_check_objective_update(failures)
|
||||
_check_objective_notice(failures)
|
||||
_check_terrain_recovery(failures)
|
||||
@@ -1016,6 +1029,111 @@ func _check_xiapi_siege_extended_pressure(failures: Array[String]) -> void:
|
||||
scene.free()
|
||||
|
||||
|
||||
func _check_white_horse_relief_extended_pressure(failures: Array[String]) -> void:
|
||||
var state = BattleStateScript.new()
|
||||
if not state.load_battle("res://data/scenarios/010_white_horse_relief.json"):
|
||||
failures.append("White Horse Relief pressure smoke could not load scenario")
|
||||
return
|
||||
if state.get_turn_limit() != 17:
|
||||
failures.append("White Horse Relief should allow play through turn 17, got limit %d" % state.get_turn_limit())
|
||||
if state.get_living_units(BattleStateScript.TEAM_ENEMY).size() != 9:
|
||||
failures.append("White Horse Relief should start with 9 enemies, got %d" % state.get_living_units(BattleStateScript.TEAM_ENEMY).size())
|
||||
_assert_required_unit(failures, state, "guo_jia_ch10", "White Horse Relief Guo Jia")
|
||||
_assert_required_unit(failures, state, "xiahou_dun_ch10", "White Horse Relief Xiahou Dun")
|
||||
if state._condition_gate_open({"after_event": "turn_9_yuan_main_pressure"}):
|
||||
failures.append("White Horse Relief final defeat-all condition should be gated until turn 9 Yuan main pressure")
|
||||
var early_state = BattleStateScript.new()
|
||||
if not early_state.load_battle("res://data/scenarios/010_white_horse_relief.json"):
|
||||
failures.append("White Horse Relief early victory smoke could not load scenario")
|
||||
return
|
||||
var early_cao_cao: Dictionary = early_state.get_unit("cao_cao_ch10")
|
||||
early_cao_cao["pos"] = Vector2i(8, 4)
|
||||
for enemy in early_state.get_living_units(BattleStateScript.TEAM_ENEMY):
|
||||
enemy["alive"] = false
|
||||
early_state._check_battle_status()
|
||||
if str(early_state.battle_status) != BattleStateScript.STATUS_ACTIVE:
|
||||
failures.append("White Horse Relief should not end before turn 9 Yuan main pressure, got %s" % str(early_state.battle_status))
|
||||
var marker_cells := state.get_objective_cells()
|
||||
for decoy_cell in [Vector2i(8, 4), Vector2i(9, 4), Vector2i(8, 5), Vector2i(9, 5)]:
|
||||
if not marker_cells.has(decoy_cell):
|
||||
failures.append("White Horse Relief should mark decoy line cell %s: %s" % [str(decoy_cell), str(marker_cells)])
|
||||
if state.get_objective_cell_label(decoy_cell) != "백마 유인선":
|
||||
failures.append("White Horse Relief marker should expose decoy line label at %s" % str(decoy_cell))
|
||||
var recovery_summaries := state.get_recovery_terrain_summaries()
|
||||
var has_gate_recovery := false
|
||||
var has_village_recovery := false
|
||||
for summary in recovery_summaries:
|
||||
if str(summary.get("name", "")) == "성채" and int(summary.get("count", 0)) == 4 and int(summary.get("heal", 0)) == 8:
|
||||
has_gate_recovery = true
|
||||
if str(summary.get("name", "")) == "마을" and int(summary.get("count", 0)) == 4 and int(summary.get("heal", 0)) == 6:
|
||||
has_village_recovery = true
|
||||
if not has_gate_recovery:
|
||||
failures.append("White Horse Relief should expose ferry recovery terrain: %s" % str(recovery_summaries))
|
||||
if not has_village_recovery:
|
||||
failures.append("White Horse Relief should expose reserve camp recovery terrain: %s" % str(recovery_summaries))
|
||||
state._run_events("turn_start", BattleStateScript.TEAM_ENEMY, 2)
|
||||
_assert_unit_at(failures, state, "yan_liang_rider_north", Vector2i(8, 2), "White Horse Relief turn 2 north rider")
|
||||
_assert_unit_at(failures, state, "yan_liang_rider_south", Vector2i(8, 6), "White Horse Relief turn 2 south rider")
|
||||
state._run_events("turn_start", BattleStateScript.TEAM_PLAYER, 3)
|
||||
if not state._condition_gate_open({"after_event": "turn_3_decoy_line"}):
|
||||
failures.append("White Horse Relief decoy line event should fire on turn 3")
|
||||
_assert_unit_at(failures, state, "decoy_cutoff_guard", Vector2i(7, 4), "White Horse Relief turn 3 cutoff guard")
|
||||
_assert_unit_at(failures, state, "decoy_cutoff_archer", Vector2i(7, 5), "White Horse Relief turn 3 cutoff archer")
|
||||
if state._condition_gate_open({"after_event": "turn_9_yuan_main_pressure"}):
|
||||
failures.append("White Horse Relief final gate should still be closed after decoy line event")
|
||||
state._run_events("turn_start", BattleStateScript.TEAM_ENEMY, 6)
|
||||
if int(state.get_unit("xiahou_dun_ch10").get("ai_target_priority", 0)) != 10:
|
||||
failures.append("White Horse Relief turn 6 pressure should target Xiahou Dun")
|
||||
_assert_unit_at(failures, state, "yuan_wing_rider", Vector2i(6, 1), "White Horse Relief turn 6 rider")
|
||||
_assert_unit_at(failures, state, "yuan_wing_guard", Vector2i(6, 8), "White Horse Relief turn 6 guard")
|
||||
_assert_unit_at(failures, state, "yuan_wing_archer", Vector2i(9, 8), "White Horse Relief turn 6 archer")
|
||||
state._run_events("turn_start", BattleStateScript.TEAM_ENEMY, 9)
|
||||
if not state._condition_gate_open({"after_event": "turn_9_yuan_main_pressure"}):
|
||||
failures.append("White Horse Relief final gate should open after turn 9 Yuan main pressure")
|
||||
_assert_unit_at(failures, state, "yuan_main_rider", Vector2i(13, 3), "White Horse Relief turn 9 rider")
|
||||
_assert_unit_at(failures, state, "yuan_main_guard", Vector2i(13, 5), "White Horse Relief turn 9 guard")
|
||||
_assert_unit_at(failures, state, "yuan_main_archer", Vector2i(13, 7), "White Horse Relief turn 9 archer")
|
||||
if state.get_living_units(BattleStateScript.TEAM_ENEMY).size() != 19:
|
||||
failures.append("White Horse Relief should field 19 enemies after Yuan main pressure, got %d" % state.get_living_units(BattleStateScript.TEAM_ENEMY).size())
|
||||
if int(state.get_unit("cao_cao_ch10").get("ai_target_priority", 0)) != 13:
|
||||
failures.append("White Horse Relief turn 9 Yuan main pressure should target Cao Cao")
|
||||
if int(state.get_unit("xiahou_dun_ch10").get("ai_target_priority", 0)) != 13:
|
||||
failures.append("White Horse Relief turn 9 Yuan main pressure should target Xiahou Dun")
|
||||
for enemy in state.get_living_units(BattleStateScript.TEAM_ENEMY):
|
||||
enemy["alive"] = false
|
||||
var cao_cao: Dictionary = state.get_unit("cao_cao_ch10")
|
||||
cao_cao["pos"] = Vector2i(7, 4)
|
||||
state._check_battle_status()
|
||||
if str(state.battle_status) != BattleStateScript.STATUS_ACTIVE:
|
||||
failures.append("White Horse Relief should not end before the decoy line is held, got %s" % str(state.battle_status))
|
||||
cao_cao["pos"] = Vector2i(8, 4)
|
||||
state._check_battle_status()
|
||||
if str(state.battle_status) != BattleStateScript.STATUS_VICTORY:
|
||||
failures.append("White Horse Relief should end after Yuan pressure falls and decoy line is held, got %s" % str(state.battle_status))
|
||||
var pressed_state = BattleStateScript.new()
|
||||
if not pressed_state.load_battle("res://data/scenarios/010_white_horse_relief.json", {}, {}, {"pressed_northern_campaign": true}):
|
||||
failures.append("White Horse Relief pressed branch smoke could not load scenario")
|
||||
return
|
||||
pressed_state._run_events("turn_start", BattleStateScript.TEAM_ENEMY, 2)
|
||||
pressed_state._run_events("turn_start", BattleStateScript.TEAM_PLAYER, 3)
|
||||
pressed_state._run_events("turn_start", BattleStateScript.TEAM_ENEMY, 4)
|
||||
pressed_state._run_events("turn_start", BattleStateScript.TEAM_ENEMY, 6)
|
||||
pressed_state._run_events("turn_start", BattleStateScript.TEAM_ENEMY, 9)
|
||||
if pressed_state.get_living_units(BattleStateScript.TEAM_ENEMY).size() != 20:
|
||||
failures.append("White Horse Relief pressed branch should field 20 enemies after Yuan main pressure, got %d" % pressed_state.get_living_units(BattleStateScript.TEAM_ENEMY).size())
|
||||
var scene = BattleSceneScript.new()
|
||||
if not scene.state.load_battle("res://data/scenarios/010_white_horse_relief.json"):
|
||||
failures.append("White Horse Relief briefing smoke could not load scenario")
|
||||
scene.free()
|
||||
return
|
||||
var overview := scene._format_briefing_battlefield_overview_text()
|
||||
_check_contains(failures, "010 briefing map size", overview, "15칸 x 10칸")
|
||||
_check_contains(failures, "010 briefing enemy count", overview, "적세 9명")
|
||||
_check_contains(failures, "010 briefing village recovery", overview, "마을 4칸 +6")
|
||||
_check_contains(failures, "010 briefing ferry recovery", overview, "성채 4칸 +8")
|
||||
scene.free()
|
||||
|
||||
|
||||
func _assert_unit_at(failures: Array[String], state, unit_id: String, expected_cell: Vector2i, label: String) -> void:
|
||||
var unit: Dictionary = state.get_unit(unit_id)
|
||||
if unit.is_empty() or not bool(unit.get("alive", false)) or not bool(unit.get("deployed", true)):
|
||||
@@ -1025,6 +1143,15 @@ func _assert_unit_at(failures: Array[String], state, unit_id: String, expected_c
|
||||
failures.append("%s should be at %s, got %s" % [label, str(expected_cell), str(unit.get("pos", Vector2i(-999, -999)))])
|
||||
|
||||
|
||||
func _assert_required_unit(failures: Array[String], state, unit_id: String, label: String) -> void:
|
||||
var unit: Dictionary = state.get_unit(unit_id)
|
||||
if unit.is_empty() or not bool(unit.get("alive", false)) or not bool(unit.get("deployed", true)):
|
||||
failures.append("%s should deploy %s" % [label, unit_id])
|
||||
return
|
||||
if not bool(unit.get("required_deployment", false)):
|
||||
failures.append("%s should be required deployment: %s" % [label, unit_id])
|
||||
|
||||
|
||||
func _check_opening_board_avoids_side_panel(failures: Array[String]) -> void:
|
||||
var state = BattleStateScript.new()
|
||||
if not state.load_battle("res://data/scenarios/001_yellow_turbans.json"):
|
||||
|
||||
Reference in New Issue
Block a user