Add Xian escort camp content

This commit is contained in:
2026-06-18 23:51:56 +09:00
parent 768b38f910
commit 5e9ab7afec
3 changed files with 207 additions and 1 deletions

View File

@@ -38,6 +38,60 @@
"The army arrives quickly after pressing Lu Bu, but the forced march leaves little time to rest." "The army arrives quickly after pressing Lu Bu, but the forced march leaves little time to rest."
] ]
} }
],
"camp_conversations": [
{
"id": "xian_escort_council",
"group": "officer",
"officer_id": "cao_cao",
"label": "Cao Cao - Imperial Escort",
"speaker": "Cao Cao",
"summary": "Set the escort route before rival remnants close in.",
"lines": [
{ "speaker": "Cao Cao", "side": "left", "text": "The Son of Heaven travels with us now. Every cart wheel on this road will sound louder than a battle drum." },
{ "speaker": "Xiahou Dun", "side": "right", "text": "Then we keep the column moving and let the remnants waste their blades on empty dust." }
]
},
{
"id": "xian_dian_wei_envoy_screen",
"group": "officer",
"officer_id": "dian_wei",
"label": "Dian Wei - Envoy Screen",
"speaker": "Dian Wei",
"summary": "Assign Dian Wei to shield the imperial envoy.",
"lines": [
{ "speaker": "Dian Wei", "side": "right", "text": "The envoy is not a soldier, but every spear will look for him once the road narrows." },
{ "speaker": "Cao Cao", "side": "left", "text": "Then stand where the road narrows first. If the carriage halts, the realm itself hesitates." }
]
},
{
"id": "xian_fortified_road_supplies",
"campaign_flags": { "fortified_yan_province": true },
"group": "topic",
"label": "Fortified Road Supplies",
"summary": "Open the food stores kept moving by the fortified Yan Province roads.",
"lines": [
{ "speaker": "Escort Road Sutler", "side": "right", "text": "The fortified roads did their work. I brought beans forward without losing a cart to the dust." },
{ "speaker": "Cao Ren", "side": "left", "text": "Good. Feed the men guarding the carriage before the remnants test the road." }
],
"effects": [
{ "type": "grant_item", "item_id": "bean", "count": 1, "text": "The fortified road supplies add a Bean to the field supplies." }
]
},
{
"id": "xian_forced_march_canteens",
"campaign_flags": { "pressed_lu_bu": true },
"group": "topic",
"label": "Forced March Canteens",
"summary": "Issue wine to soldiers exhausted by the rapid march from Lu Bu's front.",
"lines": [
{ "speaker": "Xiahou Yuan", "side": "right", "text": "We reached the emperor's road quickly, but the men are counting rest by mouthfuls now." },
{ "speaker": "Cao Cao", "side": "left", "text": "Give the vanguard wine before the column moves. Speed bought us this chance; discipline must keep it." }
],
"effects": [
{ "type": "grant_item", "item_id": "wine", "count": 1, "text": "The forced march canteens add Wine to the field supplies." }
]
}
] ]
}, },
"shop": { "shop": {
@@ -61,7 +115,14 @@
"campaign_flags": { "pressed_lu_bu": true }, "campaign_flags": { "pressed_lu_bu": true },
"items": ["war_axe"] "items": ["war_axe"]
} }
] ],
"merchant": {
"name": "Escort Road Sutler",
"lines": [
"No shouting near the carriage. Point at what you need and count the coin quietly.",
"A court escort spends more medicine than arrows. Warlord remnants prefer soft targets."
]
}
}, },
"roster": { "roster": {
"max_units": 5, "max_units": 5,

View File

@@ -22,6 +22,10 @@ const DINGTAO_SCENARIO_ID := "006_dingtao_counterattack"
const DINGTAO_SCENARIO_PATH := "res://data/scenarios/006_dingtao_counterattack.json" const DINGTAO_SCENARIO_PATH := "res://data/scenarios/006_dingtao_counterattack.json"
const DINGTAO_FORTIFIED_CONVERSATION_ID := "dingtao_fortified_reserves" const DINGTAO_FORTIFIED_CONVERSATION_ID := "dingtao_fortified_reserves"
const DINGTAO_PRESSED_CONVERSATION_ID := "dingtao_pressed_vanguard" const DINGTAO_PRESSED_CONVERSATION_ID := "dingtao_pressed_vanguard"
const XIAN_SCENARIO_ID := "007_xian_emperor_escort"
const XIAN_SCENARIO_PATH := "res://data/scenarios/007_xian_emperor_escort.json"
const XIAN_FORTIFIED_CONVERSATION_ID := "xian_fortified_road_supplies"
const XIAN_PRESSED_CONVERSATION_ID := "xian_forced_march_canteens"
const WUCHAO_SCENARIO_ID := "013_wuchao_raid" const WUCHAO_SCENARIO_ID := "013_wuchao_raid"
const WUCHAO_SCENARIO_PATH := "res://data/scenarios/013_wuchao_raid.json" const WUCHAO_SCENARIO_PATH := "res://data/scenarios/013_wuchao_raid.json"
const SAVE_PATH := "user://campaign_save.json" const SAVE_PATH := "user://campaign_save.json"
@@ -38,6 +42,7 @@ func _init() -> void:
_check_sishui_gate_camp_supplies(failures) _check_sishui_gate_camp_supplies(failures)
_check_puyang_raid_camp_supplies(failures) _check_puyang_raid_camp_supplies(failures)
_check_dingtao_branch_camp_conversations(failures) _check_dingtao_branch_camp_conversations(failures)
_check_xian_branch_camp_conversations(failures)
_check_manual_checkpoint_reverts_claim(failures) _check_manual_checkpoint_reverts_claim(failures)
_check_completed_replay_cannot_claim(failures) _check_completed_replay_cannot_claim(failures)
_check_conditional_camp_conversations(failures) _check_conditional_camp_conversations(failures)
@@ -325,6 +330,88 @@ func _check_dingtao_branch_camp_conversations(failures: Array[String]) -> void:
pressed_scene.free() pressed_scene.free()
func _check_xian_branch_camp_conversations(failures: Array[String]) -> void:
var base_scene = _new_prebattle_scene_for(failures, "Xian escort base camp", XIAN_SCENARIO_ID, XIAN_SCENARIO_PATH)
if base_scene != null:
if base_scene._camp_conversation_by_id("xian_escort_council").is_empty():
failures.append("Xian escort should expose the escort council conversation")
if base_scene._camp_conversation_by_id("xian_dian_wei_envoy_screen").is_empty():
failures.append("Xian escort should expose Dian Wei's envoy screen conversation")
if not base_scene._camp_conversation_by_id(XIAN_FORTIFIED_CONVERSATION_ID).is_empty():
failures.append("Xian fortified supplies should be hidden without fortified flag")
if not base_scene._camp_conversation_by_id(XIAN_PRESSED_CONVERSATION_ID).is_empty():
failures.append("Xian pressed supplies should be hidden without pressed flag")
var merchant: Dictionary = base_scene.state.get_shop_merchant()
if str(merchant.get("name", "")) != "Escort Road Sutler":
failures.append("Xian escort shop merchant name should be present")
var merchant_lines: Array = merchant.get("lines", [])
if merchant_lines.size() < 2:
failures.append("Xian escort shop merchant should expose flavor lines")
if base_scene._format_talk_status_text(base_scene._camp_conversation_entries()) != "Camp conversations | 2 topics":
failures.append("Xian escort base talk status should summarize two non-supply topics")
base_scene.free()
var fortified_scene = _new_prebattle_scene_for(
failures,
"Xian escort fortified camp",
XIAN_SCENARIO_ID,
XIAN_SCENARIO_PATH,
{"fortified_yan_province": true}
)
if fortified_scene != null:
var supplies: Dictionary = fortified_scene._camp_conversation_by_id(XIAN_FORTIFIED_CONVERSATION_ID)
if supplies.is_empty():
failures.append("fortified flag should expose Xian fortified road supplies")
if not fortified_scene._camp_conversation_by_id(XIAN_PRESSED_CONVERSATION_ID).is_empty():
failures.append("fortified flag should not expose Xian forced march canteens")
if not supplies.is_empty():
if fortified_scene._format_talk_status_text(fortified_scene._camp_conversation_entries()) != "Camp conversations | 3 topics | 1 supplies ready":
failures.append("Xian fortified talk status should show one ready supply")
if not fortified_scene._format_camp_conversation_button_text(supplies).contains("Supply Bean"):
failures.append("Xian fortified road supplies button should preview Bean supply")
var before_bean := int(fortified_scene.campaign_state.get_inventory_snapshot().get("bean", 0))
fortified_scene._apply_camp_conversation_effects(supplies)
if int(fortified_scene.campaign_state.get_inventory_snapshot().get("bean", 0)) != before_bean + 1:
failures.append("Xian fortified road supplies should add Bean to campaign inventory")
if int(fortified_scene.state.get_inventory_snapshot().get("bean", 0)) != before_bean + 1:
failures.append("Xian fortified road supplies should refresh battle Bean inventory")
if not fortified_scene.campaign_state.has_claimed_camp_conversation_effects(XIAN_SCENARIO_ID, XIAN_FORTIFIED_CONVERSATION_ID):
failures.append("Xian fortified road supplies claim should be saved")
if fortified_scene._format_talk_status_text(fortified_scene._camp_conversation_entries()) != "Camp conversations | 3 topics | 1 claimed":
failures.append("Xian fortified talk status should show claimed supply after claim")
fortified_scene.free()
var pressed_scene = _new_prebattle_scene_for(
failures,
"Xian escort pressed camp",
XIAN_SCENARIO_ID,
XIAN_SCENARIO_PATH,
{"pressed_lu_bu": true}
)
if pressed_scene != null:
var canteens: Dictionary = pressed_scene._camp_conversation_by_id(XIAN_PRESSED_CONVERSATION_ID)
if canteens.is_empty():
failures.append("pressed flag should expose Xian forced march canteens")
if not pressed_scene._camp_conversation_by_id(XIAN_FORTIFIED_CONVERSATION_ID).is_empty():
failures.append("pressed flag should not expose Xian fortified road supplies")
if not canteens.is_empty():
if pressed_scene._format_talk_status_text(pressed_scene._camp_conversation_entries()) != "Camp conversations | 3 topics | 1 supplies ready":
failures.append("Xian pressed talk status should show one ready supply")
if not pressed_scene._format_camp_conversation_button_text(canteens).contains("Supply Wine"):
failures.append("Xian forced march canteens button should preview Wine supply")
var before_wine := int(pressed_scene.campaign_state.get_inventory_snapshot().get("wine", 0))
pressed_scene._apply_camp_conversation_effects(canteens)
if int(pressed_scene.campaign_state.get_inventory_snapshot().get("wine", 0)) != before_wine + 1:
failures.append("Xian forced march canteens should add Wine to campaign inventory")
if int(pressed_scene.state.get_inventory_snapshot().get("wine", 0)) != before_wine + 1:
failures.append("Xian forced march canteens should refresh battle Wine inventory")
if not pressed_scene.campaign_state.has_claimed_camp_conversation_effects(XIAN_SCENARIO_ID, XIAN_PRESSED_CONVERSATION_ID):
failures.append("Xian forced march canteens claim should be saved")
if pressed_scene._format_talk_status_text(pressed_scene._camp_conversation_entries()) != "Camp conversations | 3 topics | 1 claimed":
failures.append("Xian pressed talk status should show claimed supply after claim")
pressed_scene.free()
func _check_manual_checkpoint_reverts_claim(failures: Array[String]) -> void: func _check_manual_checkpoint_reverts_claim(failures: Array[String]) -> void:
var scene = _new_prebattle_scene(failures, "manual checkpoint") var scene = _new_prebattle_scene(failures, "manual checkpoint")
if scene == null: if scene == null:

View File

@@ -301,6 +301,64 @@ func _check_battle_visual_data(failures: Array[String]) -> void:
_check_shop_item_visibility(failures, dingtao_pressed_state, "iron_armor", false, "006 pressed") _check_shop_item_visibility(failures, dingtao_pressed_state, "iron_armor", false, "006 pressed")
_check_shop_item_visibility(failures, dingtao_pressed_state, "war_drum", true, "006 pressed") _check_shop_item_visibility(failures, dingtao_pressed_state, "war_drum", true, "006 pressed")
var xian_state = BattleStateScript.new()
if not xian_state.load_battle("res://data/scenarios/007_xian_emperor_escort.json"):
failures.append("could not load Xian Emperor Escort camp data")
else:
var xian_conversations: Array = xian_state.get_briefing().get("camp_conversations", [])
if xian_conversations.size() != 2:
failures.append("007 base briefing should expose exactly two camp conversations")
else:
_check_camp_conversation(failures, xian_conversations[0], "xian_escort_council", "officer", "cao_cao")
_check_camp_conversation(failures, xian_conversations[1], "xian_dian_wei_envoy_screen", "officer", "dian_wei")
var xian_merchant := xian_state.get_shop_merchant()
if str(xian_merchant.get("name", "")) != "Escort Road Sutler":
failures.append("007 shop should expose escort merchant name")
if (xian_merchant.get("lines", []) as Array).size() < 2:
failures.append("007 shop should expose merchant flavor lines")
_check_shop_item_visibility(failures, xian_state, "iron_armor", false, "007 base")
_check_shop_item_visibility(failures, xian_state, "war_axe", false, "007 base")
var xian_fortified_state = BattleStateScript.new()
if not xian_fortified_state.load_battle("res://data/scenarios/007_xian_emperor_escort.json", {}, {}, {"fortified_yan_province": true}):
failures.append("could not load Xian fortified camp data")
else:
var road_supplies: Dictionary = _find_camp_conversation(
xian_fortified_state.get_briefing().get("camp_conversations", []),
"xian_fortified_road_supplies"
)
if road_supplies.is_empty():
failures.append("007 fortified flag should expose fortified road supplies conversation")
else:
_check_camp_conversation_effect(failures, road_supplies, "bean", 1)
if not _find_camp_conversation(
xian_fortified_state.get_briefing().get("camp_conversations", []),
"xian_forced_march_canteens"
).is_empty():
failures.append("007 fortified flag should not expose forced march canteens conversation")
_check_shop_item_visibility(failures, xian_fortified_state, "iron_armor", true, "007 fortified")
_check_shop_item_visibility(failures, xian_fortified_state, "war_axe", false, "007 fortified")
var xian_pressed_state = BattleStateScript.new()
if not xian_pressed_state.load_battle("res://data/scenarios/007_xian_emperor_escort.json", {}, {}, {"pressed_lu_bu": true}):
failures.append("could not load Xian pressed camp data")
else:
var canteens: Dictionary = _find_camp_conversation(
xian_pressed_state.get_briefing().get("camp_conversations", []),
"xian_forced_march_canteens"
)
if canteens.is_empty():
failures.append("007 pressed flag should expose forced march canteens conversation")
else:
_check_camp_conversation_effect(failures, canteens, "wine", 1)
if not _find_camp_conversation(
xian_pressed_state.get_briefing().get("camp_conversations", []),
"xian_fortified_road_supplies"
).is_empty():
failures.append("007 pressed flag should not expose fortified road supplies conversation")
_check_shop_item_visibility(failures, xian_pressed_state, "iron_armor", false, "007 pressed")
_check_shop_item_visibility(failures, xian_pressed_state, "war_axe", true, "007 pressed")
for item_id in ["bronze_sword", "training_spear", "short_bow", "hand_axe", "iron_armor", "panacea"]: for item_id in ["bronze_sword", "training_spear", "short_bow", "hand_axe", "iron_armor", "panacea"]:
var item := state.get_item_def(item_id) var item := state.get_item_def(item_id)
if item.is_empty(): if item.is_empty():