From 99d58d05eefff4b2822a9389b232087fce31514b Mon Sep 17 00:00:00 2001 From: Wickedness Date: Fri, 19 Jun 2026 00:13:27 +0900 Subject: [PATCH] Add White Horse relief camp content --- data/scenarios/010_white_horse_relief.json | 105 ++++++++++++++++++++- tools/smoke_camp_conversation_rewards.gd | 87 +++++++++++++++++ tools/smoke_visual_assets.gd | 58 ++++++++++++ 3 files changed, 249 insertions(+), 1 deletion(-) diff --git a/data/scenarios/010_white_horse_relief.json b/data/scenarios/010_white_horse_relief.json index e39a5c3..22292c9 100644 --- a/data/scenarios/010_white_horse_relief.json +++ b/data/scenarios/010_white_horse_relief.json @@ -32,6 +32,102 @@ "The army marches quickly from Xiapi, hoping speed can strike Yuan Shao's vanguard before it settles." ] } + ], + "camp_conversations": [ + { + "id": "white_horse_decoy_council", + "group": "officer", + "officer_id": "guo_jia", + "label": "Guo Jia - Decoy Line", + "speaker": "Guo Jia", + "summary": "Set the false retreat that will pull Yan Liang away from his supports.", + "lines": [ + { + "speaker": "Guo Jia", + "side": "right", + "text": "Yan Liang will read a bending line as weakness. Let him see exactly enough of what he wants." + }, + { + "speaker": "Cao Cao", + "side": "left", + "text": "A trap that looks too clever warns the prey. This one must look like fear." + } + ] + }, + { + "id": "white_horse_xiahou_dun_false_retreat", + "group": "officer", + "officer_id": "xiahou_dun", + "label": "Xiahou Dun - False Retreat", + "speaker": "Xiahou Dun", + "summary": "Prepare the center line to yield without breaking.", + "lines": [ + { + "speaker": "Xiahou Dun", + "side": "left", + "text": "Men can pretend to retreat, but their backs still know the feeling. I will keep the center from turning real." + }, + { + "speaker": "Guo Jia", + "side": "right", + "text": "Good. Bend the line by measure, not panic. Yan Liang must think he chose the road himself." + } + ] + }, + { + "id": "white_horse_xu_reserve_medicine", + "campaign_flags": { "integrated_xu_province": true }, + "group": "topic", + "label": "Xu Reserve Medicine", + "summary": "Draw field medicine from the quiet rear roads secured after Xiapi.", + "lines": [ + { + "speaker": "Cao Ren", + "side": "left", + "text": "Xu Province is quiet behind us. The reserve carts arrived with dry medicine and no arrows in the canvas." + }, + { + "speaker": "Cao Cao", + "side": "right", + "text": "Then send it to the men who must look beaten without becoming so." + } + ], + "effects": [ + { + "type": "grant_item", + "item_id": "panacea", + "count": 1, + "text": "The Xu reserve medicine adds a Panacea to the field supplies." + } + ] + }, + { + "id": "white_horse_fast_march_beans", + "campaign_flags": { "pressed_northern_campaign": true }, + "group": "topic", + "label": "Fast March Beans", + "summary": "Issue portable food to troops who reached White Horse ahead of the heavy carts.", + "lines": [ + { + "speaker": "Xiahou Yuan", + "side": "right", + "text": "The army came north fast from Xiapi. The riders are fresh enough, but the footmen ate while walking." + }, + { + "speaker": "Guo Jia", + "side": "left", + "text": "Give the center beans before it yields. A false retreat is easier on a full breath." + } + ], + "effects": [ + { + "type": "grant_item", + "item_id": "bean", + "count": 1, + "text": "The fast march beans add a Bean to the field supplies." + } + ] + } ] }, "shop": { @@ -55,7 +151,14 @@ "campaign_flags": { "pressed_northern_campaign": true }, "items": ["war_drum"] } - ] + ], + "merchant": { + "name": "White Horse Road Sutler", + "lines": [ + "Buy before the banners start pretending to run. Once the decoy begins, every cart follows its assigned fear.", + "Beans for the center line, medicine for mistakes, and no heavy baggage where Yan Liang can see it." + ] + } }, "roster": { "max_units": 6, diff --git a/tools/smoke_camp_conversation_rewards.gd b/tools/smoke_camp_conversation_rewards.gd index 5a0b343..5f49755 100644 --- a/tools/smoke_camp_conversation_rewards.gd +++ b/tools/smoke_camp_conversation_rewards.gd @@ -34,6 +34,10 @@ const XIAPI_SCENARIO_ID := "009_xiapi_siege" const XIAPI_SCENARIO_PATH := "res://data/scenarios/009_xiapi_siege.json" const XIAPI_HELD_CONVERSATION_ID := "xiapi_wan_baggage_dressings" const XIAPI_SWIFT_CONVERSATION_ID := "xiapi_forced_march_rations" +const WHITE_HORSE_SCENARIO_ID := "010_white_horse_relief" +const WHITE_HORSE_SCENARIO_PATH := "res://data/scenarios/010_white_horse_relief.json" +const WHITE_HORSE_XU_CONVERSATION_ID := "white_horse_xu_reserve_medicine" +const WHITE_HORSE_PRESSED_CONVERSATION_ID := "white_horse_fast_march_beans" 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" @@ -53,6 +57,7 @@ func _init() -> void: _check_xian_branch_camp_conversations(failures) _check_wan_branch_camp_conversations(failures) _check_xiapi_branch_camp_conversations(failures) + _check_white_horse_branch_camp_conversations(failures) _check_manual_checkpoint_reverts_claim(failures) _check_completed_replay_cannot_claim(failures) _check_conditional_camp_conversations(failures) @@ -586,6 +591,88 @@ func _check_xiapi_branch_camp_conversations(failures: Array[String]) -> void: swift_scene.free() +func _check_white_horse_branch_camp_conversations(failures: Array[String]) -> void: + var base_scene = _new_prebattle_scene_for(failures, "White Horse base camp", WHITE_HORSE_SCENARIO_ID, WHITE_HORSE_SCENARIO_PATH) + if base_scene != null: + if base_scene._camp_conversation_by_id("white_horse_decoy_council").is_empty(): + failures.append("White Horse should expose the decoy council conversation") + if base_scene._camp_conversation_by_id("white_horse_xiahou_dun_false_retreat").is_empty(): + failures.append("White Horse should expose Xiahou Dun's false retreat conversation") + if not base_scene._camp_conversation_by_id(WHITE_HORSE_XU_CONVERSATION_ID).is_empty(): + failures.append("White Horse Xu reserve medicine should be hidden without integrated Xu flag") + if not base_scene._camp_conversation_by_id(WHITE_HORSE_PRESSED_CONVERSATION_ID).is_empty(): + failures.append("White Horse fast march beans should be hidden without pressed north flag") + var merchant: Dictionary = base_scene.state.get_shop_merchant() + if str(merchant.get("name", "")) != "White Horse Road Sutler": + failures.append("White Horse shop merchant name should be present") + var merchant_lines: Array = merchant.get("lines", []) + if merchant_lines.size() < 2: + failures.append("White Horse shop merchant should expose flavor lines") + if base_scene._format_talk_status_text(base_scene._camp_conversation_entries()) != "Camp conversations | 2 topics": + failures.append("White Horse base talk status should summarize two non-supply topics") + base_scene.free() + + var xu_scene = _new_prebattle_scene_for( + failures, + "White Horse integrated Xu camp", + WHITE_HORSE_SCENARIO_ID, + WHITE_HORSE_SCENARIO_PATH, + {"integrated_xu_province": true} + ) + if xu_scene != null: + var medicine: Dictionary = xu_scene._camp_conversation_by_id(WHITE_HORSE_XU_CONVERSATION_ID) + if medicine.is_empty(): + failures.append("integrated Xu flag should expose White Horse reserve medicine") + if not xu_scene._camp_conversation_by_id(WHITE_HORSE_PRESSED_CONVERSATION_ID).is_empty(): + failures.append("integrated Xu flag should not expose White Horse fast march beans") + if not medicine.is_empty(): + if xu_scene._format_talk_status_text(xu_scene._camp_conversation_entries()) != "Camp conversations | 3 topics | 1 supplies ready": + failures.append("White Horse integrated Xu talk status should show one ready supply") + if not xu_scene._format_camp_conversation_button_text(medicine).contains("Supply Panacea"): + failures.append("White Horse Xu reserve medicine button should preview Panacea supply") + var before_panacea := int(xu_scene.campaign_state.get_inventory_snapshot().get("panacea", 0)) + xu_scene._apply_camp_conversation_effects(medicine) + if int(xu_scene.campaign_state.get_inventory_snapshot().get("panacea", 0)) != before_panacea + 1: + failures.append("White Horse Xu reserve medicine should add Panacea to campaign inventory") + if int(xu_scene.state.get_inventory_snapshot().get("panacea", 0)) != before_panacea + 1: + failures.append("White Horse Xu reserve medicine should refresh battle Panacea inventory") + if not xu_scene.campaign_state.has_claimed_camp_conversation_effects(WHITE_HORSE_SCENARIO_ID, WHITE_HORSE_XU_CONVERSATION_ID): + failures.append("White Horse Xu reserve medicine claim should be saved") + if xu_scene._format_talk_status_text(xu_scene._camp_conversation_entries()) != "Camp conversations | 3 topics | 1 claimed": + failures.append("White Horse integrated Xu talk status should show claimed supply after claim") + xu_scene.free() + + var pressed_scene = _new_prebattle_scene_for( + failures, + "White Horse pressed north camp", + WHITE_HORSE_SCENARIO_ID, + WHITE_HORSE_SCENARIO_PATH, + {"pressed_northern_campaign": true} + ) + if pressed_scene != null: + var beans: Dictionary = pressed_scene._camp_conversation_by_id(WHITE_HORSE_PRESSED_CONVERSATION_ID) + if beans.is_empty(): + failures.append("pressed north flag should expose White Horse fast march beans") + if not pressed_scene._camp_conversation_by_id(WHITE_HORSE_XU_CONVERSATION_ID).is_empty(): + failures.append("pressed north flag should not expose White Horse reserve medicine") + if not beans.is_empty(): + if pressed_scene._format_talk_status_text(pressed_scene._camp_conversation_entries()) != "Camp conversations | 3 topics | 1 supplies ready": + failures.append("White Horse pressed north talk status should show one ready supply") + if not pressed_scene._format_camp_conversation_button_text(beans).contains("Supply Bean"): + failures.append("White Horse fast march beans button should preview Bean supply") + var before_bean := int(pressed_scene.campaign_state.get_inventory_snapshot().get("bean", 0)) + pressed_scene._apply_camp_conversation_effects(beans) + if int(pressed_scene.campaign_state.get_inventory_snapshot().get("bean", 0)) != before_bean + 1: + failures.append("White Horse fast march beans should add Bean to campaign inventory") + if int(pressed_scene.state.get_inventory_snapshot().get("bean", 0)) != before_bean + 1: + failures.append("White Horse fast march beans should refresh battle Bean inventory") + if not pressed_scene.campaign_state.has_claimed_camp_conversation_effects(WHITE_HORSE_SCENARIO_ID, WHITE_HORSE_PRESSED_CONVERSATION_ID): + failures.append("White Horse fast march beans claim should be saved") + if pressed_scene._format_talk_status_text(pressed_scene._camp_conversation_entries()) != "Camp conversations | 3 topics | 1 claimed": + failures.append("White Horse pressed north 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 6a546c0..67b32b7 100644 --- a/tools/smoke_visual_assets.gd +++ b/tools/smoke_visual_assets.gd @@ -479,6 +479,17 @@ func _check_battle_visual_data(failures: Array[String]) -> void: if not white_horse_state.load_battle("res://data/scenarios/010_white_horse_relief.json"): failures.append("could not load White Horse Relief shop data") else: + var white_horse_conversations: Array = white_horse_state.get_briefing().get("camp_conversations", []) + if white_horse_conversations.size() != 2: + failures.append("010 base briefing should expose exactly two camp conversations") + else: + _check_camp_conversation(failures, white_horse_conversations[0], "white_horse_decoy_council", "officer", "guo_jia") + _check_camp_conversation(failures, white_horse_conversations[1], "white_horse_xiahou_dun_false_retreat", "officer", "xiahou_dun") + var white_horse_merchant := white_horse_state.get_shop_merchant() + if str(white_horse_merchant.get("name", "")) != "White Horse Road Sutler": + failures.append("010 shop should expose White Horse merchant name") + if (white_horse_merchant.get("lines", []) as Array).size() < 2: + failures.append("010 shop should expose merchant flavor lines") _check_shop_item_visibility(failures, white_horse_state, "imperial_seal", false, "010 base") _check_shop_item_visibility(failures, white_horse_state, "war_drum", false, "010 base") @@ -486,6 +497,19 @@ func _check_battle_visual_data(failures: Array[String]) -> void: if not white_horse_xu_state.load_battle("res://data/scenarios/010_white_horse_relief.json", {}, {}, {"integrated_xu_province": true}): failures.append("could not load White Horse integrated Xu shop data") else: + var medicine: Dictionary = _find_camp_conversation( + white_horse_xu_state.get_briefing().get("camp_conversations", []), + "white_horse_xu_reserve_medicine" + ) + if medicine.is_empty(): + failures.append("010 integrated Xu flag should expose Xu reserve medicine conversation") + else: + _check_camp_conversation_effect(failures, medicine, "panacea", 1) + if not _find_camp_conversation( + white_horse_xu_state.get_briefing().get("camp_conversations", []), + "white_horse_fast_march_beans" + ).is_empty(): + failures.append("010 integrated Xu flag should not expose fast march beans conversation") _check_shop_item_visibility(failures, white_horse_xu_state, "imperial_seal", true, "010 integrated Xu") _check_shop_item_visibility(failures, white_horse_xu_state, "war_drum", false, "010 integrated Xu") @@ -493,9 +517,43 @@ func _check_battle_visual_data(failures: Array[String]) -> void: if not white_horse_pressed_state.load_battle("res://data/scenarios/010_white_horse_relief.json", {}, {}, {"pressed_northern_campaign": true}): failures.append("could not load White Horse pressed north shop data") else: + var beans: Dictionary = _find_camp_conversation( + white_horse_pressed_state.get_briefing().get("camp_conversations", []), + "white_horse_fast_march_beans" + ) + if beans.is_empty(): + failures.append("010 pressed north flag should expose fast march beans conversation") + else: + _check_camp_conversation_effect(failures, beans, "bean", 1) + if not _find_camp_conversation( + white_horse_pressed_state.get_briefing().get("camp_conversations", []), + "white_horse_xu_reserve_medicine" + ).is_empty(): + failures.append("010 pressed north flag should not expose Xu reserve medicine conversation") _check_shop_item_visibility(failures, white_horse_pressed_state, "imperial_seal", false, "010 pressed north") _check_shop_item_visibility(failures, white_horse_pressed_state, "war_drum", true, "010 pressed north") + var yan_ford_state = BattleStateScript.new() + if not yan_ford_state.load_battle("res://data/scenarios/011_yan_ford_pursuit.json"): + failures.append("could not load Yan Ford Pursuit shop data") + else: + _check_shop_item_visibility(failures, yan_ford_state, "imperial_seal", false, "011 base") + _check_shop_item_visibility(failures, yan_ford_state, "war_axe", false, "011 base") + + var yan_ford_fortified_state = BattleStateScript.new() + if not yan_ford_fortified_state.load_battle("res://data/scenarios/011_yan_ford_pursuit.json", {}, {}, {"fortified_white_horse": true}): + failures.append("could not load Yan Ford fortified White Horse shop data") + else: + _check_shop_item_visibility(failures, yan_ford_fortified_state, "imperial_seal", true, "011 fortified White Horse") + _check_shop_item_visibility(failures, yan_ford_fortified_state, "war_axe", false, "011 fortified White Horse") + + var yan_ford_raided_state = BattleStateScript.new() + if not yan_ford_raided_state.load_battle("res://data/scenarios/011_yan_ford_pursuit.json", {}, {}, {"raided_yuan_supplies": true}): + failures.append("could not load Yan Ford raided Yuan supplies shop data") + else: + _check_shop_item_visibility(failures, yan_ford_raided_state, "imperial_seal", false, "011 raided Yuan supplies") + _check_shop_item_visibility(failures, yan_ford_raided_state, "war_axe", true, "011 raided Yuan supplies") + 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():