Add Dingtao branch camp content

This commit is contained in:
2026-06-18 23:45:35 +09:00
parent 201adb3bbc
commit 768b38f910
3 changed files with 214 additions and 2 deletions

View File

@@ -32,6 +32,60 @@
"Cao Cao's pressure has kept Lu Bu off balance, but the army enters Dingtao with little rest." "Cao Cao's pressure has kept Lu Bu off balance, but the army enters Dingtao with little rest."
] ]
} }
],
"camp_conversations": [
{
"id": "dingtao_countercharge_council",
"group": "officer",
"officer_id": "cao_cao",
"label": "Cao Cao - Countercharge",
"speaker": "Cao Cao",
"summary": "Set the line before Lu Bu's cavalry reaches Dingtao.",
"lines": [
{ "speaker": "Cao Cao", "side": "left", "text": "Lu Bu answers the Puyang raid with speed. That is his habit, and habits can be prepared for." },
{ "speaker": "Cao Ren", "side": "right", "text": "The ridges will break his charge if our line does not break first. I will keep the reserves close." }
]
},
{
"id": "dingtao_dian_wei_roadblock",
"group": "officer",
"officer_id": "dian_wei",
"label": "Dian Wei - Roadblock",
"speaker": "Dian Wei",
"summary": "Let Dian Wei anchor the road against Lu Bu's riders.",
"lines": [
{ "speaker": "Dian Wei", "side": "right", "text": "A horse needs road and courage. I cannot take the horse's road away, but I can test the rider's courage." },
{ "speaker": "Xiahou Dun", "side": "left", "text": "Stand where the road narrows. If Lu Bu wants the banner, he must spend men to reach it." }
]
},
{
"id": "dingtao_fortified_reserves",
"campaign_flags": { "fortified_yan_province": true },
"group": "topic",
"label": "Fortified Reserves",
"summary": "Distribute medicine from the secured Yan Province supply road.",
"lines": [
{ "speaker": "Dingtao Field Sutler", "side": "right", "text": "The fortified roads kept the physician's chest whole. I brought the clean bundle forward before the riders closed." },
{ "speaker": "Cao Cao", "side": "left", "text": "Then the choice to fortify already pays us back. Keep it near the center line." }
],
"effects": [
{ "type": "grant_item", "item_id": "panacea", "count": 1, "text": "The fortified reserves add a Panacea to the field supplies." }
]
},
{
"id": "dingtao_pressed_vanguard",
"campaign_flags": { "pressed_lu_bu": true },
"group": "topic",
"label": "Pressed Vanguard",
"summary": "Issue wine to the soldiers who kept pressure on Lu Bu after Puyang.",
"lines": [
{ "speaker": "Xiahou Yuan", "side": "right", "text": "The vanguard has not slept enough to dream, but they still reached Dingtao before Lu Bu expected." },
{ "speaker": "Dian Wei", "side": "left", "text": "Give them wine, then give them a short order. Tired men do best when the road ahead is simple." }
],
"effects": [
{ "type": "grant_item", "item_id": "wine", "count": 1, "text": "The pressed vanguard adds Wine to the field supplies." }
]
}
] ]
}, },
"shop": { "shop": {
@@ -53,9 +107,16 @@
}, },
{ {
"campaign_flags": { "pressed_lu_bu": true }, "campaign_flags": { "pressed_lu_bu": true },
"items": ["war_axe"] "items": ["war_drum"]
} }
] ],
"merchant": {
"name": "Dingtao Field Sutler",
"lines": [
"Buy before the dust rises. Once Lu Bu's riders arrive, even a good price will not stand still.",
"Fortified roads bring armor; forced marches bring drums. Either way, keep one hand free for medicine."
]
}
}, },
"roster": { "roster": {
"max_units": 4, "max_units": 4,

View File

@@ -18,6 +18,10 @@ const PUYANG_SCENARIO_PATH := "res://data/scenarios/005_puyang_raid.json"
const PUYANG_CONVERSATION_ID := "puyang_raider_stores" const PUYANG_CONVERSATION_ID := "puyang_raider_stores"
const PUYANG_PURSUIT_CONVERSATION_ID := "puyang_hardened_vanguard" const PUYANG_PURSUIT_CONVERSATION_ID := "puyang_hardened_vanguard"
const PUYANG_REGROUP_CONVERSATION_ID := "puyang_qingzhou_reserve_wagon" const PUYANG_REGROUP_CONVERSATION_ID := "puyang_qingzhou_reserve_wagon"
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 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"
@@ -33,6 +37,7 @@ func _init() -> void:
_check_shop_and_armory_preserve_claim(failures) _check_shop_and_armory_preserve_claim(failures)
_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_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)
@@ -238,6 +243,88 @@ func _check_puyang_raid_camp_supplies(failures: Array[String]) -> void:
regroup_scene.free() regroup_scene.free()
func _check_dingtao_branch_camp_conversations(failures: Array[String]) -> void:
var base_scene = _new_prebattle_scene_for(failures, "Dingtao base camp", DINGTAO_SCENARIO_ID, DINGTAO_SCENARIO_PATH)
if base_scene != null:
if base_scene._camp_conversation_by_id("dingtao_countercharge_council").is_empty():
failures.append("Dingtao should expose the countercharge council conversation")
if base_scene._camp_conversation_by_id("dingtao_dian_wei_roadblock").is_empty():
failures.append("Dingtao should expose Dian Wei's roadblock conversation")
if not base_scene._camp_conversation_by_id(DINGTAO_FORTIFIED_CONVERSATION_ID).is_empty():
failures.append("Dingtao fortified supplies should be hidden without fortified flag")
if not base_scene._camp_conversation_by_id(DINGTAO_PRESSED_CONVERSATION_ID).is_empty():
failures.append("Dingtao pressed supplies should be hidden without pressed flag")
var merchant: Dictionary = base_scene.state.get_shop_merchant()
if str(merchant.get("name", "")) != "Dingtao Field Sutler":
failures.append("Dingtao shop merchant name should be present")
var merchant_lines: Array = merchant.get("lines", [])
if merchant_lines.size() < 2:
failures.append("Dingtao shop merchant should expose flavor lines")
if base_scene._format_talk_status_text(base_scene._camp_conversation_entries()) != "Camp conversations | 2 topics":
failures.append("Dingtao base talk status should summarize two non-supply topics")
base_scene.free()
var fortified_scene = _new_prebattle_scene_for(
failures,
"Dingtao fortified camp",
DINGTAO_SCENARIO_ID,
DINGTAO_SCENARIO_PATH,
{"fortified_yan_province": true}
)
if fortified_scene != null:
var reserves: Dictionary = fortified_scene._camp_conversation_by_id(DINGTAO_FORTIFIED_CONVERSATION_ID)
if reserves.is_empty():
failures.append("fortified flag should expose Dingtao fortified reserves")
if not fortified_scene._camp_conversation_by_id(DINGTAO_PRESSED_CONVERSATION_ID).is_empty():
failures.append("fortified flag should not expose Dingtao pressed vanguard")
if not reserves.is_empty():
if fortified_scene._format_talk_status_text(fortified_scene._camp_conversation_entries()) != "Camp conversations | 3 topics | 1 supplies ready":
failures.append("Dingtao fortified talk status should show one ready supply")
if not fortified_scene._format_camp_conversation_button_text(reserves).contains("Supply Panacea"):
failures.append("Dingtao fortified reserves button should preview Panacea supply")
var before_panacea := int(fortified_scene.campaign_state.get_inventory_snapshot().get("panacea", 0))
fortified_scene._apply_camp_conversation_effects(reserves)
if int(fortified_scene.campaign_state.get_inventory_snapshot().get("panacea", 0)) != before_panacea + 1:
failures.append("Dingtao fortified reserves should add Panacea to campaign inventory")
if int(fortified_scene.state.get_inventory_snapshot().get("panacea", 0)) != before_panacea + 1:
failures.append("Dingtao fortified reserves should refresh battle Panacea inventory")
if not fortified_scene.campaign_state.has_claimed_camp_conversation_effects(DINGTAO_SCENARIO_ID, DINGTAO_FORTIFIED_CONVERSATION_ID):
failures.append("Dingtao fortified reserves claim should be saved")
if fortified_scene._format_talk_status_text(fortified_scene._camp_conversation_entries()) != "Camp conversations | 3 topics | 1 claimed":
failures.append("Dingtao fortified talk status should show claimed supply after claim")
fortified_scene.free()
var pressed_scene = _new_prebattle_scene_for(
failures,
"Dingtao pressed camp",
DINGTAO_SCENARIO_ID,
DINGTAO_SCENARIO_PATH,
{"pressed_lu_bu": true}
)
if pressed_scene != null:
var vanguard: Dictionary = pressed_scene._camp_conversation_by_id(DINGTAO_PRESSED_CONVERSATION_ID)
if vanguard.is_empty():
failures.append("pressed flag should expose Dingtao pressed vanguard")
if not pressed_scene._camp_conversation_by_id(DINGTAO_FORTIFIED_CONVERSATION_ID).is_empty():
failures.append("pressed flag should not expose Dingtao fortified reserves")
if not vanguard.is_empty():
if pressed_scene._format_talk_status_text(pressed_scene._camp_conversation_entries()) != "Camp conversations | 3 topics | 1 supplies ready":
failures.append("Dingtao pressed talk status should show one ready supply")
if not pressed_scene._format_camp_conversation_button_text(vanguard).contains("Supply Wine"):
failures.append("Dingtao pressed vanguard 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(vanguard)
if int(pressed_scene.campaign_state.get_inventory_snapshot().get("wine", 0)) != before_wine + 1:
failures.append("Dingtao pressed vanguard should add Wine to campaign inventory")
if int(pressed_scene.state.get_inventory_snapshot().get("wine", 0)) != before_wine + 1:
failures.append("Dingtao pressed vanguard should refresh battle Wine inventory")
if not pressed_scene.campaign_state.has_claimed_camp_conversation_effects(DINGTAO_SCENARIO_ID, DINGTAO_PRESSED_CONVERSATION_ID):
failures.append("Dingtao pressed vanguard claim should be saved")
if pressed_scene._format_talk_status_text(pressed_scene._camp_conversation_entries()) != "Camp conversations | 3 topics | 1 claimed":
failures.append("Dingtao 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

@@ -243,6 +243,64 @@ func _check_battle_visual_data(failures: Array[String]) -> void:
).is_empty(): ).is_empty():
failures.append("005 regroup flag should not expose hardened vanguard conversation") failures.append("005 regroup flag should not expose hardened vanguard conversation")
var dingtao_state = BattleStateScript.new()
if not dingtao_state.load_battle("res://data/scenarios/006_dingtao_counterattack.json"):
failures.append("could not load Dingtao Counterattack camp data")
else:
var dingtao_conversations: Array = dingtao_state.get_briefing().get("camp_conversations", [])
if dingtao_conversations.size() != 2:
failures.append("006 base briefing should expose exactly two camp conversations")
else:
_check_camp_conversation(failures, dingtao_conversations[0], "dingtao_countercharge_council", "officer", "cao_cao")
_check_camp_conversation(failures, dingtao_conversations[1], "dingtao_dian_wei_roadblock", "officer", "dian_wei")
var dingtao_merchant := dingtao_state.get_shop_merchant()
if str(dingtao_merchant.get("name", "")) != "Dingtao Field Sutler":
failures.append("006 shop should expose Dingtao merchant name")
if (dingtao_merchant.get("lines", []) as Array).size() < 2:
failures.append("006 shop should expose merchant flavor lines")
_check_shop_item_visibility(failures, dingtao_state, "iron_armor", false, "006 base")
_check_shop_item_visibility(failures, dingtao_state, "war_drum", false, "006 base")
var dingtao_fortified_state = BattleStateScript.new()
if not dingtao_fortified_state.load_battle("res://data/scenarios/006_dingtao_counterattack.json", {}, {}, {"fortified_yan_province": true}):
failures.append("could not load Dingtao fortified camp data")
else:
var fortified: Dictionary = _find_camp_conversation(
dingtao_fortified_state.get_briefing().get("camp_conversations", []),
"dingtao_fortified_reserves"
)
if fortified.is_empty():
failures.append("006 fortified flag should expose fortified reserves conversation")
else:
_check_camp_conversation_effect(failures, fortified, "panacea", 1)
if not _find_camp_conversation(
dingtao_fortified_state.get_briefing().get("camp_conversations", []),
"dingtao_pressed_vanguard"
).is_empty():
failures.append("006 fortified flag should not expose pressed vanguard conversation")
_check_shop_item_visibility(failures, dingtao_fortified_state, "iron_armor", true, "006 fortified")
_check_shop_item_visibility(failures, dingtao_fortified_state, "war_drum", false, "006 fortified")
var dingtao_pressed_state = BattleStateScript.new()
if not dingtao_pressed_state.load_battle("res://data/scenarios/006_dingtao_counterattack.json", {}, {}, {"pressed_lu_bu": true}):
failures.append("could not load Dingtao pressed camp data")
else:
var pressed: Dictionary = _find_camp_conversation(
dingtao_pressed_state.get_briefing().get("camp_conversations", []),
"dingtao_pressed_vanguard"
)
if pressed.is_empty():
failures.append("006 pressed flag should expose pressed vanguard conversation")
else:
_check_camp_conversation_effect(failures, pressed, "wine", 1)
if not _find_camp_conversation(
dingtao_pressed_state.get_briefing().get("camp_conversations", []),
"dingtao_fortified_reserves"
).is_empty():
failures.append("006 pressed flag should not expose fortified reserves conversation")
_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")
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():
@@ -835,6 +893,12 @@ func _find_camp_conversation(conversations: Array, conversation_id: String) -> D
return {} return {}
func _check_shop_item_visibility(failures: Array[String], state, item_id: String, expected_visible: bool, context: String) -> void:
var visible: bool = state.get_shop_item_ids().has(item_id)
if visible != expected_visible:
failures.append("%s shop visibility for %s should be %s" % [context, item_id, str(expected_visible)])
func _check_camp_conversation_effect(failures: Array[String], conversation: Dictionary, expected_item_id: String, expected_count: int) -> void: func _check_camp_conversation_effect(failures: Array[String], conversation: Dictionary, expected_item_id: String, expected_count: int) -> void:
var effects: Array = conversation.get("effects", []) var effects: Array = conversation.get("effects", [])
if effects.is_empty(): if effects.is_empty():