Add Wuchao camp conversations
This commit is contained in:
@@ -32,9 +32,70 @@
|
|||||||
"The reinforced Guandu camps can withstand a longer absence, but the raid must still finish before dawn."
|
"The reinforced Guandu camps can withstand a longer absence, but the raid must still finish before dawn."
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"camp_conversations": [
|
||||||
|
{
|
||||||
|
"id": "wuchao_night_raid_council",
|
||||||
|
"group": "topic",
|
||||||
|
"label": "Night Raid Council",
|
||||||
|
"speaker": "Guo Jia",
|
||||||
|
"summary": "Review the depot objective and escape route.",
|
||||||
|
"lines": [
|
||||||
|
{ "speaker": "Guo Jia", "side": "right", "text": "Wuchao feeds Yuan Shao's whole line. Fire there will travel faster than any messenger." },
|
||||||
|
{ "speaker": "Cao Cao", "side": "left", "text": "Then the first objective is silence. The second is flame. The third is getting back through the panic." },
|
||||||
|
{ "speaker": "Dian Wei", "side": "right", "text": "Silence, flame, then panic. I can remember that order." }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "wuchao_guo_jia_empty_bowl",
|
||||||
|
"group": "officer",
|
||||||
|
"officer_id": "guo_jia",
|
||||||
|
"label": "Guo Jia - Empty Bowl",
|
||||||
|
"speaker": "Guo Jia",
|
||||||
|
"summary": "Weigh fire against formation.",
|
||||||
|
"lines": [
|
||||||
|
{ "speaker": "Guo Jia", "side": "right", "text": "A burned granary wins nothing if our line scatters in the smoke." },
|
||||||
|
{ "speaker": "Cao Cao", "side": "left", "text": "So we cut the bowl from Yuan Shao's hand, then march out as one blade." }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "wuchao_fast_raid_supplies",
|
||||||
|
"group": "topic",
|
||||||
|
"label": "Fast Raid Stores",
|
||||||
|
"summary": "Claim scout-carried field supplies.",
|
||||||
|
"campaign_flags": { "prepared_wuchao_raid": true },
|
||||||
|
"lines": [
|
||||||
|
{ "speaker": "Xiahou Yuan", "side": "right", "text": "The scouts hid dry rations beside the narrow track. Fast riders can carry them without slowing the column." },
|
||||||
|
{ "speaker": "Cao Cao", "side": "left", "text": "Good. Speed is only useful if it still has breath at the far end." }
|
||||||
|
],
|
||||||
|
"effects": [
|
||||||
|
{ "type": "grant_item", "item_id": "bean", "count": 1, "text": "The raid scouts add a Bean to the field supplies." }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "wuchao_guandu_reserve_wagon",
|
||||||
|
"group": "topic",
|
||||||
|
"label": "Guandu Reserve Wagon",
|
||||||
|
"summary": "Claim medicine from the reinforced camp line.",
|
||||||
|
"campaign_flags": { "strengthened_guandu_defense": true },
|
||||||
|
"lines": [
|
||||||
|
{ "speaker": "Cao Ren", "side": "right", "text": "The reinforced camps sent a reserve wagon after us. It carries medicine, not speed." },
|
||||||
|
{ "speaker": "Guo Jia", "side": "right", "text": "Medicine is speed after the first arrow lands." }
|
||||||
|
],
|
||||||
|
"effects": [
|
||||||
|
{ "type": "grant_item", "item_id": "panacea", "count": 1, "text": "The Guandu reserve wagon adds a Panacea to the field supplies." }
|
||||||
|
]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"shop": {
|
"shop": {
|
||||||
|
"merchant": {
|
||||||
|
"name": "Wuchao Night Trader",
|
||||||
|
"lines": [
|
||||||
|
"No lanterns, my lord. I wrapped the beans and wine in felt so they make no sound.",
|
||||||
|
"If you buy armor tonight, wear it before the moon drops. Dawn will make merchants honest and arrows accurate."
|
||||||
|
]
|
||||||
|
},
|
||||||
"items": [
|
"items": [
|
||||||
"bean",
|
"bean",
|
||||||
"wine",
|
"wine",
|
||||||
|
|||||||
@@ -3541,7 +3541,7 @@ func _rebuild_talk_menu() -> void:
|
|||||||
_clear_talk_list()
|
_clear_talk_list()
|
||||||
var conversations := _camp_conversation_entries()
|
var conversations := _camp_conversation_entries()
|
||||||
if talk_status_label != null:
|
if talk_status_label != null:
|
||||||
talk_status_label.text = "Camp conversations | %d available" % conversations.size()
|
talk_status_label.text = _format_talk_status_text(conversations)
|
||||||
if conversations.is_empty():
|
if conversations.is_empty():
|
||||||
var empty_label := Label.new()
|
var empty_label := Label.new()
|
||||||
empty_label.text = "No camp conversations"
|
empty_label.text = "No camp conversations"
|
||||||
@@ -3555,6 +3555,35 @@ func _rebuild_talk_menu() -> void:
|
|||||||
talk_list.add_child(button)
|
talk_list.add_child(button)
|
||||||
|
|
||||||
|
|
||||||
|
func _format_talk_status_text(conversations: Array) -> String:
|
||||||
|
var supply_available := 0
|
||||||
|
var supply_claimed := 0
|
||||||
|
for conversation in conversations:
|
||||||
|
if typeof(conversation) != TYPE_DICTIONARY:
|
||||||
|
continue
|
||||||
|
var effects: Array = conversation.get("effects", [])
|
||||||
|
if effects.is_empty():
|
||||||
|
continue
|
||||||
|
var has_supply := false
|
||||||
|
for effect in effects:
|
||||||
|
if typeof(effect) == TYPE_DICTIONARY and str(effect.get("type", "")) == "grant_item":
|
||||||
|
has_supply = true
|
||||||
|
break
|
||||||
|
if not has_supply:
|
||||||
|
continue
|
||||||
|
var conversation_id := str(conversation.get("id", ""))
|
||||||
|
if campaign_state.has_claimed_camp_conversation_effects(active_scenario_id, conversation_id):
|
||||||
|
supply_claimed += 1
|
||||||
|
else:
|
||||||
|
supply_available += 1
|
||||||
|
var parts := ["Camp conversations", "%d topics" % conversations.size()]
|
||||||
|
if supply_available > 0:
|
||||||
|
parts.append("%d supplies ready" % supply_available)
|
||||||
|
if supply_claimed > 0:
|
||||||
|
parts.append("%d claimed" % supply_claimed)
|
||||||
|
return _join_strings(parts, " | ")
|
||||||
|
|
||||||
|
|
||||||
func _clear_talk_list() -> void:
|
func _clear_talk_list() -> void:
|
||||||
for child in talk_list.get_children():
|
for child in talk_list.get_children():
|
||||||
talk_list.remove_child(child)
|
talk_list.remove_child(child)
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ const SCENARIO_PATH := "res://data/scenarios/001_yellow_turbans.json"
|
|||||||
const CONVERSATION_ID := "northern_woods_cache"
|
const CONVERSATION_ID := "northern_woods_cache"
|
||||||
const BRANCH_SCENARIO_ID := "003_xingyang_ambush"
|
const BRANCH_SCENARIO_ID := "003_xingyang_ambush"
|
||||||
const BRANCH_SCENARIO_PATH := "res://data/scenarios/003_xingyang_ambush.json"
|
const BRANCH_SCENARIO_PATH := "res://data/scenarios/003_xingyang_ambush.json"
|
||||||
|
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"
|
const SAVE_PATH := "user://campaign_save.json"
|
||||||
const MANUAL_SAVE_PATH := "user://campaign_manual_save.json"
|
const MANUAL_SAVE_PATH := "user://campaign_manual_save.json"
|
||||||
|
|
||||||
@@ -22,6 +24,7 @@ func _init() -> void:
|
|||||||
_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)
|
||||||
|
_check_wuchao_branch_camp_conversations(failures)
|
||||||
|
|
||||||
if not _restore_user_file(SAVE_PATH, save_backup):
|
if not _restore_user_file(SAVE_PATH, save_backup):
|
||||||
failures.append("could not restore automatic campaign save")
|
failures.append("could not restore automatic campaign save")
|
||||||
@@ -197,6 +200,86 @@ func _check_conditional_camp_conversations(failures: Array[String]) -> void:
|
|||||||
regroup_scene.free()
|
regroup_scene.free()
|
||||||
|
|
||||||
|
|
||||||
|
func _check_wuchao_branch_camp_conversations(failures: Array[String]) -> void:
|
||||||
|
var base_scene = _new_prebattle_scene_for(failures, "Wuchao base camp", WUCHAO_SCENARIO_ID, WUCHAO_SCENARIO_PATH)
|
||||||
|
if base_scene != null:
|
||||||
|
if base_scene._camp_conversation_by_id("wuchao_night_raid_council").is_empty():
|
||||||
|
failures.append("Wuchao should expose the night raid council conversation")
|
||||||
|
if base_scene._camp_conversation_by_id("wuchao_guo_jia_empty_bowl").is_empty():
|
||||||
|
failures.append("Wuchao should expose Guo Jia's officer conversation")
|
||||||
|
if not base_scene._camp_conversation_by_id("wuchao_fast_raid_supplies").is_empty():
|
||||||
|
failures.append("Wuchao fast raid supplies should be hidden without the prepared raid flag")
|
||||||
|
if not base_scene._camp_conversation_by_id("wuchao_guandu_reserve_wagon").is_empty():
|
||||||
|
failures.append("Wuchao reserve wagon should be hidden without the strengthened defense flag")
|
||||||
|
var merchant: Dictionary = base_scene.state.get_shop_merchant()
|
||||||
|
if str(merchant.get("name", "")) != "Wuchao Night Trader":
|
||||||
|
failures.append("Wuchao shop merchant name should be present")
|
||||||
|
var merchant_lines: Array = merchant.get("lines", [])
|
||||||
|
if merchant_lines.size() < 2:
|
||||||
|
failures.append("Wuchao shop merchant should expose flavor lines")
|
||||||
|
if base_scene._format_talk_status_text(base_scene._camp_conversation_entries()) != "Camp conversations | 2 topics":
|
||||||
|
failures.append("Wuchao base talk status should summarize two non-supply topics")
|
||||||
|
base_scene.free()
|
||||||
|
|
||||||
|
var raid_scene = _new_prebattle_scene_for(
|
||||||
|
failures,
|
||||||
|
"Wuchao prepared raid camp",
|
||||||
|
WUCHAO_SCENARIO_ID,
|
||||||
|
WUCHAO_SCENARIO_PATH,
|
||||||
|
{"prepared_wuchao_raid": true}
|
||||||
|
)
|
||||||
|
if raid_scene != null:
|
||||||
|
var fast_supplies: Dictionary = raid_scene._camp_conversation_by_id("wuchao_fast_raid_supplies")
|
||||||
|
if fast_supplies.is_empty():
|
||||||
|
failures.append("prepared raid flag should expose Wuchao fast supplies")
|
||||||
|
if not raid_scene._camp_conversation_by_id("wuchao_guandu_reserve_wagon").is_empty():
|
||||||
|
failures.append("prepared raid flag should not expose Wuchao reserve wagon")
|
||||||
|
if not fast_supplies.is_empty():
|
||||||
|
if raid_scene._format_talk_status_text(raid_scene._camp_conversation_entries()) != "Camp conversations | 3 topics | 1 supplies ready":
|
||||||
|
failures.append("Wuchao prepared raid talk status should show one ready supply")
|
||||||
|
if not raid_scene._format_camp_conversation_button_text(fast_supplies).contains("Supply Bean"):
|
||||||
|
failures.append("Wuchao fast supplies button should show Bean supply")
|
||||||
|
var before_bean := int(raid_scene.campaign_state.get_inventory_snapshot().get("bean", 0))
|
||||||
|
raid_scene._apply_camp_conversation_effects(fast_supplies)
|
||||||
|
if int(raid_scene.campaign_state.get_inventory_snapshot().get("bean", 0)) != before_bean + 1:
|
||||||
|
failures.append("Wuchao fast supplies should add Bean to campaign inventory")
|
||||||
|
if int(raid_scene.state.get_inventory_snapshot().get("bean", 0)) != before_bean + 1:
|
||||||
|
failures.append("Wuchao fast supplies should refresh battle Bean inventory")
|
||||||
|
if not raid_scene.campaign_state.has_claimed_camp_conversation_effects(WUCHAO_SCENARIO_ID, "wuchao_fast_raid_supplies"):
|
||||||
|
failures.append("Wuchao fast supplies claim should be saved")
|
||||||
|
if raid_scene._format_talk_status_text(raid_scene._camp_conversation_entries()) != "Camp conversations | 3 topics | 1 claimed":
|
||||||
|
failures.append("Wuchao prepared raid talk status should show claimed supply after claim")
|
||||||
|
raid_scene.free()
|
||||||
|
|
||||||
|
var defense_scene = _new_prebattle_scene_for(
|
||||||
|
failures,
|
||||||
|
"Wuchao strengthened defense camp",
|
||||||
|
WUCHAO_SCENARIO_ID,
|
||||||
|
WUCHAO_SCENARIO_PATH,
|
||||||
|
{"strengthened_guandu_defense": true}
|
||||||
|
)
|
||||||
|
if defense_scene != null:
|
||||||
|
var reserve_wagon: Dictionary = defense_scene._camp_conversation_by_id("wuchao_guandu_reserve_wagon")
|
||||||
|
if reserve_wagon.is_empty():
|
||||||
|
failures.append("strengthened defense flag should expose Wuchao reserve wagon")
|
||||||
|
if not defense_scene._camp_conversation_by_id("wuchao_fast_raid_supplies").is_empty():
|
||||||
|
failures.append("strengthened defense flag should not expose Wuchao fast supplies")
|
||||||
|
if not reserve_wagon.is_empty():
|
||||||
|
if defense_scene._format_talk_status_text(defense_scene._camp_conversation_entries()) != "Camp conversations | 3 topics | 1 supplies ready":
|
||||||
|
failures.append("Wuchao strengthened defense talk status should show one ready supply")
|
||||||
|
if not defense_scene._format_camp_conversation_button_text(reserve_wagon).contains("Supply Panacea"):
|
||||||
|
failures.append("Wuchao reserve wagon button should show Panacea supply")
|
||||||
|
var before_panacea := int(defense_scene.campaign_state.get_inventory_snapshot().get("panacea", 0))
|
||||||
|
defense_scene._apply_camp_conversation_effects(reserve_wagon)
|
||||||
|
if int(defense_scene.campaign_state.get_inventory_snapshot().get("panacea", 0)) != before_panacea + 1:
|
||||||
|
failures.append("Wuchao reserve wagon should add Panacea to campaign inventory")
|
||||||
|
if int(defense_scene.state.get_inventory_snapshot().get("panacea", 0)) != before_panacea + 1:
|
||||||
|
failures.append("Wuchao reserve wagon should refresh battle Panacea inventory")
|
||||||
|
if not defense_scene.campaign_state.has_claimed_camp_conversation_effects(WUCHAO_SCENARIO_ID, "wuchao_guandu_reserve_wagon"):
|
||||||
|
failures.append("Wuchao reserve wagon claim should be saved")
|
||||||
|
defense_scene.free()
|
||||||
|
|
||||||
|
|
||||||
func _new_prebattle_scene(failures: Array[String], label: String):
|
func _new_prebattle_scene(failures: Array[String], label: String):
|
||||||
return _new_prebattle_scene_for(failures, label, SCENARIO_ID, SCENARIO_PATH)
|
return _new_prebattle_scene_for(failures, label, SCENARIO_ID, SCENARIO_PATH)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user