From 5e9ab7afec5871263c0072cf876dd0e2c9f6d1c7 Mon Sep 17 00:00:00 2001 From: Wickedness Date: Thu, 18 Jun 2026 23:51:56 +0900 Subject: [PATCH] Add Xian escort camp content --- data/scenarios/007_xian_emperor_escort.json | 63 ++++++++++++++- tools/smoke_camp_conversation_rewards.gd | 87 +++++++++++++++++++++ tools/smoke_visual_assets.gd | 58 ++++++++++++++ 3 files changed, 207 insertions(+), 1 deletion(-) diff --git a/data/scenarios/007_xian_emperor_escort.json b/data/scenarios/007_xian_emperor_escort.json index 1ec4d0a..3b1356f 100644 --- a/data/scenarios/007_xian_emperor_escort.json +++ b/data/scenarios/007_xian_emperor_escort.json @@ -38,6 +38,60 @@ "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": { @@ -61,7 +115,14 @@ "campaign_flags": { "pressed_lu_bu": true }, "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": { "max_units": 5, diff --git a/tools/smoke_camp_conversation_rewards.gd b/tools/smoke_camp_conversation_rewards.gd index 8be32bb..ea63802 100644 --- a/tools/smoke_camp_conversation_rewards.gd +++ b/tools/smoke_camp_conversation_rewards.gd @@ -22,6 +22,10 @@ const DINGTAO_SCENARIO_ID := "006_dingtao_counterattack" const DINGTAO_SCENARIO_PATH := "res://data/scenarios/006_dingtao_counterattack.json" const DINGTAO_FORTIFIED_CONVERSATION_ID := "dingtao_fortified_reserves" 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_PATH := "res://data/scenarios/013_wuchao_raid.json" const SAVE_PATH := "user://campaign_save.json" @@ -38,6 +42,7 @@ func _init() -> void: _check_sishui_gate_camp_supplies(failures) _check_puyang_raid_camp_supplies(failures) _check_dingtao_branch_camp_conversations(failures) + _check_xian_branch_camp_conversations(failures) _check_manual_checkpoint_reverts_claim(failures) _check_completed_replay_cannot_claim(failures) _check_conditional_camp_conversations(failures) @@ -325,6 +330,88 @@ func _check_dingtao_branch_camp_conversations(failures: Array[String]) -> void: 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: var scene = _new_prebattle_scene(failures, "manual checkpoint") if scene == null: diff --git a/tools/smoke_visual_assets.gd b/tools/smoke_visual_assets.gd index 68d04bc..e054c23 100644 --- a/tools/smoke_visual_assets.gd +++ b/tools/smoke_visual_assets.gd @@ -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, "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"]: var item := state.get_item_def(item_id) if item.is_empty():