From 1c2658350803c4718b4977cef876b1d082aa4f89 Mon Sep 17 00:00:00 2001 From: Wickedness Date: Wed, 24 Jun 2026 00:35:04 +0900 Subject: [PATCH] Add final Meng Huo capture battle --- docs/roadmap.md | 5 +- scripts/verify-flow.mjs | 255 ++++++++++++++---- .../images/battle/fifty-fourth-battle-map.svg | 86 ++++++ src/game/data/battleMapAssets.ts | 4 +- src/game/data/battles.ts | 77 +++++- src/game/data/campaignFlow.ts | 26 +- src/game/data/scenario.ts | 253 +++++++++++++++++ src/game/scenes/BattleScene.ts | 58 +++- src/game/scenes/CampScene.ts | 170 +++++++++++- src/game/scenes/TitleScene.ts | 7 + src/game/state/campaignState.ts | 5 +- 11 files changed, 883 insertions(+), 63 deletions(-) create mode 100644 src/assets/images/battle/fifty-fourth-battle-map.svg diff --git a/docs/roadmap.md b/docs/roadmap.md index 1c1e738..d7168ea 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -78,15 +78,16 @@ Build a small complete tactical RPG loop that can grow into a longer Romance of - Fifty-first battle Meng Huo fourth-capture route, where prior clan-persuasion camp choices become battlefield goals through an amnesty-route objective, fire-fort suppression, a 102x84 poison-river battlefield, and Ma Chao/Ma Dai pursuit-control sortie recommendations - Fifty-second battle Meng Huo fifth-capture route, where hardline clans split from wavering clans across a 104x86 bamboo-ravine battlefield, with hardliner-separation, village-protection, and fort-suppression objectives plus post-battle ledger/banner camp events - Fifty-third battle Meng Huo sixth-capture route, where Meng Huo's remaining pride meets a 106x88 deep-ravine battlefield, with village-trust, trust-road, and hardliner-remnant objectives plus post-battle trust-ledger/final-persuasion camp events +- Fifty-fourth battle Meng Huo final-capture route, where the Seven Captures arc closes on a 108x90 council-ground battlefield with final pacify, council-road, village-witness, and remnant-fort objectives plus Nanzhong pacification camp events - Camp progress timeline tab that summarizes Liu Bei's long campaign arc, completed battles, current chapter, latest battle, and next major chapter - Tactical sortie preparation panel with battle-specific sortie limits, recommended officers with reasons, class role, named equipment, core stats, bond partner, next-map terrain suitability, focused officer analysis, deployment preview, formation roles, active bond count, recruited-officer count, reserve roster summary, and readiness advice for each deployable officer - Officer collection support in camp, including full roster status, selected/reserve/recommended markers, selectable reserve drill focus, and post-battle reserve training growth for benched officers - Treasure equipment effects wired into battle previews and resolution, including named weapon damage bonuses, defensive treasure mitigation, support recovery bonuses, turn-start charm recovery, and equipment-specific growth bonuses -- Flow verification script from title through the sixth Meng Huo capture camp, King of Hanzhong council milestone, Shu-Han foundation milestone, recruit sortie selection, Liu Biao visit rewards, Zhuge Liang recruitment, Bowang/Changban/Jiangdong/Red Cliffs/Jing Province/Guiyang/Wuling/Changsha/Yi Province/Fu Pass/Luo Castle/Luofeng/Luo main gate/Mianzhu/Chengdu/Jiameng/Yangping/Dingjun/Hanzhong/Jing defense/Fan Castle/Han River/Fan siege/Jing rear/Gongan collapse/Maicheng isolation/Yiling vanguard/Yiling fire/Baidi entrustment/Nanzhong stabilization/Meng Huo pacification camp states, campaign timeline state, and camp save state +- Flow verification script from title through the final Meng Huo capture and Nanzhong pacification camp, King of Hanzhong council milestone, Shu-Han foundation milestone, recruit sortie selection, Liu Biao visit rewards, Zhuge Liang recruitment, Bowang/Changban/Jiangdong/Red Cliffs/Jing Province/Guiyang/Wuling/Changsha/Yi Province/Fu Pass/Luo Castle/Luofeng/Luo main gate/Mianzhu/Chengdu/Jiameng/Yangping/Dingjun/Hanzhong/Jing defense/Fan Castle/Han River/Fan siege/Jing rear/Gongan collapse/Maicheng isolation/Yiling vanguard/Yiling fire/Baidi entrustment/Nanzhong stabilization/Meng Huo pacification camp states, campaign timeline state, and camp save state ## Next Steps -1. Build the seventh Seven Captures Nanman battle so Meng Huo's final surrender can close the Nanzhong pacification arc without treating it as the campaign ending +1. Build the post-Nanzhong northern-campaign preparation milestone so the pacified rear opens Zhuge Liang's next long-term objective instead of ending the campaign 2. Expand post-Baidi and post-Nanzhong camp consequences with grief checks, recovery policy choices, village reassurance, captive handling, and regency decisions that affect sortie recommendations 3. Expand reserve planning into individual officer assignments, including targeted class practice, paired bond training, and camp task slots 4. Add a dedicated treasure/equipment management view so players can compare special effects and growth diff --git a/scripts/verify-flow.mjs b/scripts/verify-flow.mjs index c4e2c4b..119cbe2 100644 --- a/scripts/verify-flow.mjs +++ b/scripts/verify-flow.mjs @@ -1739,7 +1739,7 @@ try { if ( progressTabState?.activeTab !== 'progress' || progressTabState.campaignProgress?.completedKnown !== 17 || - progressTabState.campaignProgress?.totalKnown !== 53 || + progressTabState.campaignProgress?.totalKnown !== 54 || progressTabState.campaignProgress?.activeChapter?.title !== '적벽대전' || progressTabState.campaignProgress?.latestBattleTitle !== '융중 방문로' || progressTabState.campaignProgress?.nextBattleTitle !== '박망파 매복전' @@ -1891,7 +1891,7 @@ try { if ( postBowangProgressState?.activeTab !== 'progress' || postBowangProgressState.campaignProgress?.completedKnown !== 18 || - postBowangProgressState.campaignProgress?.totalKnown !== 53 || + postBowangProgressState.campaignProgress?.totalKnown !== 54 || postBowangProgressState.campaignProgress?.activeChapter?.title !== '적벽대전' || postBowangProgressState.campaignProgress?.latestBattleTitle !== '박망파 매복전' || postBowangProgressState.campaignProgress?.nextBattleTitle !== '장판파 피난로' @@ -2029,7 +2029,7 @@ try { if ( postChangbanProgressState?.activeTab !== 'progress' || postChangbanProgressState.campaignProgress?.completedKnown !== 19 || - postChangbanProgressState.campaignProgress?.totalKnown !== 53 || + postChangbanProgressState.campaignProgress?.totalKnown !== 54 || postChangbanProgressState.campaignProgress?.activeChapter?.title !== '적벽대전' || postChangbanProgressState.campaignProgress?.latestBattleTitle !== '장판파 피난로' || postChangbanProgressState.campaignProgress?.nextBattleTitle !== '강동 사절로' @@ -2167,7 +2167,7 @@ try { if ( postJiangdongProgressState?.activeTab !== 'progress' || postJiangdongProgressState.campaignProgress?.completedKnown !== 20 || - postJiangdongProgressState.campaignProgress?.totalKnown !== 53 || + postJiangdongProgressState.campaignProgress?.totalKnown !== 54 || postJiangdongProgressState.campaignProgress?.activeChapter?.title !== '적벽대전' || postJiangdongProgressState.campaignProgress?.latestBattleTitle !== '강동 사절로' || postJiangdongProgressState.campaignProgress?.nextBattleTitle !== '적벽 전초전' @@ -2307,7 +2307,7 @@ try { if ( postRedCliffsProgressState?.activeTab !== 'progress' || postRedCliffsProgressState.campaignProgress?.completedKnown !== 21 || - postRedCliffsProgressState.campaignProgress?.totalKnown !== 53 || + postRedCliffsProgressState.campaignProgress?.totalKnown !== 54 || postRedCliffsProgressState.campaignProgress?.activeChapter?.title !== '적벽대전' || postRedCliffsProgressState.campaignProgress?.latestBattleTitle !== '적벽 전초전' || postRedCliffsProgressState.campaignProgress?.nextBattleTitle !== '적벽 화공전' @@ -2447,7 +2447,7 @@ try { if ( postFireAttackProgressState?.activeTab !== 'progress' || postFireAttackProgressState.campaignProgress?.completedKnown !== 22 || - postFireAttackProgressState.campaignProgress?.totalKnown !== 53 || + postFireAttackProgressState.campaignProgress?.totalKnown !== 54 || postFireAttackProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' || postFireAttackProgressState.campaignProgress?.latestBattleTitle !== '적벽 화공전' || postFireAttackProgressState.campaignProgress?.nextBattleTitle !== '형주 남부 진입전' @@ -2590,7 +2590,7 @@ try { if ( postJingzhouProgressState?.activeTab !== 'progress' || postJingzhouProgressState.campaignProgress?.completedKnown !== 23 || - postJingzhouProgressState.campaignProgress?.totalKnown !== 53 || + postJingzhouProgressState.campaignProgress?.totalKnown !== 54 || postJingzhouProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' || postJingzhouProgressState.campaignProgress?.latestBattleTitle !== '형주 남부 진입전' || postJingzhouProgressState.campaignProgress?.nextBattleTitle !== '계양 설득전' @@ -2732,7 +2732,7 @@ try { if ( postGuiyangProgressState?.activeTab !== 'progress' || postGuiyangProgressState.campaignProgress?.completedKnown !== 24 || - postGuiyangProgressState.campaignProgress?.totalKnown !== 53 || + postGuiyangProgressState.campaignProgress?.totalKnown !== 54 || postGuiyangProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' || postGuiyangProgressState.campaignProgress?.latestBattleTitle !== '계양 설득전' || postGuiyangProgressState.campaignProgress?.nextBattleTitle !== '무릉 산길 확보전' @@ -2875,7 +2875,7 @@ try { if ( postWulingProgressState?.activeTab !== 'progress' || postWulingProgressState.campaignProgress?.completedKnown !== 25 || - postWulingProgressState.campaignProgress?.totalKnown !== 53 || + postWulingProgressState.campaignProgress?.totalKnown !== 54 || postWulingProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' || postWulingProgressState.campaignProgress?.latestBattleTitle !== '무릉 산길 확보전' || postWulingProgressState.campaignProgress?.nextBattleTitle !== '장사 노장 대면전' @@ -3024,7 +3024,7 @@ try { if ( postChangshaProgressState?.activeTab !== 'progress' || postChangshaProgressState.campaignProgress?.completedKnown !== 26 || - postChangshaProgressState.campaignProgress?.totalKnown !== 53 || + postChangshaProgressState.campaignProgress?.totalKnown !== 54 || postChangshaProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' || postChangshaProgressState.campaignProgress?.latestBattleTitle !== '장사 노장 대면전' || postChangshaProgressState.campaignProgress?.nextBattleTitle !== '익주 원군로' @@ -3169,7 +3169,7 @@ try { if ( postYizhouProgressState?.activeTab !== 'progress' || postYizhouProgressState.campaignProgress?.completedKnown !== 27 || - postYizhouProgressState.campaignProgress?.totalKnown !== 53 || + postYizhouProgressState.campaignProgress?.totalKnown !== 54 || postYizhouProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' || postYizhouProgressState.campaignProgress?.latestBattleTitle !== '익주 원군로' || postYizhouProgressState.campaignProgress?.nextBattleTitle !== '부수관 진입전' @@ -3316,7 +3316,7 @@ try { if ( postFuPassProgressState?.activeTab !== 'progress' || postFuPassProgressState.campaignProgress?.completedKnown !== 28 || - postFuPassProgressState.campaignProgress?.totalKnown !== 53 || + postFuPassProgressState.campaignProgress?.totalKnown !== 54 || postFuPassProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' || postFuPassProgressState.campaignProgress?.latestBattleTitle !== '부수관 진입전' || postFuPassProgressState.campaignProgress?.nextBattleTitle !== '낙성 외곽전' @@ -3465,7 +3465,7 @@ try { if ( postLuoProgressState?.activeTab !== 'progress' || postLuoProgressState.campaignProgress?.completedKnown !== 29 || - postLuoProgressState.campaignProgress?.totalKnown !== 53 || + postLuoProgressState.campaignProgress?.totalKnown !== 54 || postLuoProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' || postLuoProgressState.campaignProgress?.latestBattleTitle !== '낙성 외곽전' || postLuoProgressState.campaignProgress?.nextBattleTitle !== '낙봉파 매복전' @@ -3611,7 +3611,7 @@ try { if ( postLuofengProgressState?.activeTab !== 'progress' || postLuofengProgressState.campaignProgress?.completedKnown !== 30 || - postLuofengProgressState.campaignProgress?.totalKnown !== 53 || + postLuofengProgressState.campaignProgress?.totalKnown !== 54 || postLuofengProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' || postLuofengProgressState.campaignProgress?.latestBattleTitle !== '낙봉파 매복전' || postLuofengProgressState.campaignProgress?.nextBattleTitle !== '낙성 본성 공략' @@ -3763,7 +3763,7 @@ try { if ( postLuoMainProgressState?.activeTab !== 'progress' || postLuoMainProgressState.campaignProgress?.completedKnown !== 31 || - postLuoMainProgressState.campaignProgress?.totalKnown !== 53 || + postLuoMainProgressState.campaignProgress?.totalKnown !== 54 || postLuoMainProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' || postLuoMainProgressState.campaignProgress?.latestBattleTitle !== '낙성 본성 공략' || postLuoMainProgressState.campaignProgress?.nextBattleTitle !== '면죽관 압박전' @@ -3916,7 +3916,7 @@ try { if ( postMianzhuProgressState?.activeTab !== 'progress' || postMianzhuProgressState.campaignProgress?.completedKnown !== 32 || - postMianzhuProgressState.campaignProgress?.totalKnown !== 53 || + postMianzhuProgressState.campaignProgress?.totalKnown !== 54 || postMianzhuProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' || postMianzhuProgressState.campaignProgress?.latestBattleTitle !== '면죽관 압박전' || postMianzhuProgressState.campaignProgress?.nextBattleTitle !== '성도 항복 권고전' @@ -4070,7 +4070,7 @@ try { if ( postChengduProgressState?.activeTab !== 'progress' || postChengduProgressState.campaignProgress?.completedKnown !== 33 || - postChengduProgressState.campaignProgress?.totalKnown !== 53 || + postChengduProgressState.campaignProgress?.totalKnown !== 54 || postChengduProgressState.campaignProgress?.activeChapter?.title !== '촉한 건국' || postChengduProgressState.campaignProgress?.latestBattleTitle !== '성도 항복 권고전' || postChengduProgressState.campaignProgress?.nextBattleTitle !== '가맹관 마초 대면전' @@ -4225,7 +4225,7 @@ try { if ( postJiamengProgressState?.activeTab !== 'progress' || postJiamengProgressState.campaignProgress?.completedKnown !== 34 || - postJiamengProgressState.campaignProgress?.totalKnown !== 53 || + postJiamengProgressState.campaignProgress?.totalKnown !== 54 || postJiamengProgressState.campaignProgress?.activeChapter?.title !== '촉한 건국' || postJiamengProgressState.campaignProgress?.latestBattleTitle !== '가맹관 마초 대면전' || postJiamengProgressState.campaignProgress?.nextBattleTitle !== '양평관 정찰전' @@ -4381,7 +4381,7 @@ try { if ( postYangpingProgressState?.activeTab !== 'progress' || postYangpingProgressState.campaignProgress?.completedKnown !== 35 || - postYangpingProgressState.campaignProgress?.totalKnown !== 53 || + postYangpingProgressState.campaignProgress?.totalKnown !== 54 || postYangpingProgressState.campaignProgress?.activeChapter?.title !== '촉한 건국' || postYangpingProgressState.campaignProgress?.latestBattleTitle !== '양평관 정찰전' || postYangpingProgressState.campaignProgress?.nextBattleTitle !== '정군산 전초전' @@ -4538,7 +4538,7 @@ try { if ( postDingjunProgressState?.activeTab !== 'progress' || postDingjunProgressState.campaignProgress?.completedKnown !== 36 || - postDingjunProgressState.campaignProgress?.totalKnown !== 53 || + postDingjunProgressState.campaignProgress?.totalKnown !== 54 || postDingjunProgressState.campaignProgress?.activeChapter?.title !== '촉한 건국' || postDingjunProgressState.campaignProgress?.latestBattleTitle !== '정군산 전초전' || postDingjunProgressState.campaignProgress?.nextBattleTitle !== '한중 결전' @@ -4691,7 +4691,7 @@ try { if ( postHanzhongProgressState?.activeTab !== 'progress' || postHanzhongProgressState.campaignProgress?.completedKnown !== 37 || - postHanzhongProgressState.campaignProgress?.totalKnown !== 53 || + postHanzhongProgressState.campaignProgress?.totalKnown !== 54 || postHanzhongProgressState.campaignProgress?.activeChapter?.title !== '촉한 건국' || postHanzhongProgressState.campaignProgress?.latestBattleTitle !== '한중 결전' || postHanzhongProgressState.campaignProgress?.nextBattleTitle !== '한중왕 즉위 준비' @@ -4752,7 +4752,7 @@ try { if ( postKingCouncilProgressState?.activeTab !== 'progress' || postKingCouncilProgressState.campaignProgress?.completedKnown !== 37 || - postKingCouncilProgressState.campaignProgress?.totalKnown !== 53 || + postKingCouncilProgressState.campaignProgress?.totalKnown !== 54 || postKingCouncilProgressState.campaignProgress?.latestBattleTitle !== '한중 결전' || postKingCouncilProgressState.campaignProgress?.nextBattleTitle !== '촉한 건국 선포' ) { @@ -4812,7 +4812,7 @@ try { if ( postFoundationProgressState?.activeTab !== 'progress' || postFoundationProgressState.campaignProgress?.completedKnown !== 37 || - postFoundationProgressState.campaignProgress?.totalKnown !== 53 || + postFoundationProgressState.campaignProgress?.totalKnown !== 54 || postFoundationProgressState.campaignProgress?.latestBattleTitle !== '한중 결전' || postFoundationProgressState.campaignProgress?.nextBattleTitle !== '형주 방위 전초전' ) { @@ -4963,7 +4963,7 @@ try { if ( postJingDefenseProgressState?.activeTab !== 'progress' || postJingDefenseProgressState.campaignProgress?.completedKnown !== 38 || - postJingDefenseProgressState.campaignProgress?.totalKnown !== 53 || + postJingDefenseProgressState.campaignProgress?.totalKnown !== 54 || postJingDefenseProgressState.campaignProgress?.latestBattleTitle !== '형주 방위 전초전' || postJingDefenseProgressState.campaignProgress?.nextBattleTitle !== '번성 외곽 압박전' ) { @@ -5116,7 +5116,7 @@ try { if ( postFanCastleProgressState?.activeTab !== 'progress' || postFanCastleProgressState.campaignProgress?.completedKnown !== 39 || - postFanCastleProgressState.campaignProgress?.totalKnown !== 53 || + postFanCastleProgressState.campaignProgress?.totalKnown !== 54 || postFanCastleProgressState.campaignProgress?.latestBattleTitle !== '번성 외곽 압박전' || postFanCastleProgressState.campaignProgress?.nextBattleTitle !== '한수 수공' ) { @@ -5270,7 +5270,7 @@ try { if ( postHanRiverProgressState?.activeTab !== 'progress' || postHanRiverProgressState.campaignProgress?.completedKnown !== 40 || - postHanRiverProgressState.campaignProgress?.totalKnown !== 53 || + postHanRiverProgressState.campaignProgress?.totalKnown !== 54 || postHanRiverProgressState.campaignProgress?.latestBattleTitle !== '한수 수공' || postHanRiverProgressState.campaignProgress?.nextBattleTitle !== '번성 공성전' ) { @@ -5424,7 +5424,7 @@ try { if ( postFanSiegeProgressState?.activeTab !== 'progress' || postFanSiegeProgressState.campaignProgress?.completedKnown !== 41 || - postFanSiegeProgressState.campaignProgress?.totalKnown !== 53 || + postFanSiegeProgressState.campaignProgress?.totalKnown !== 54 || postFanSiegeProgressState.campaignProgress?.latestBattleTitle !== '번성 공성전' || postFanSiegeProgressState.campaignProgress?.nextBattleTitle !== '강릉 나루 경계전' ) { @@ -5586,7 +5586,7 @@ try { if ( postJingRearProgressState?.activeTab !== 'progress' || postJingRearProgressState.campaignProgress?.completedKnown !== 42 || - postJingRearProgressState.campaignProgress?.totalKnown !== 53 || + postJingRearProgressState.campaignProgress?.totalKnown !== 54 || postJingRearProgressState.campaignProgress?.latestBattleTitle !== '강릉 나루 경계전' || postJingRearProgressState.campaignProgress?.nextBattleTitle !== '공안 성문 변고' ) { @@ -5741,7 +5741,7 @@ try { if ( postGonganProgressState?.activeTab !== 'progress' || postGonganProgressState.campaignProgress?.completedKnown !== 43 || - postGonganProgressState.campaignProgress?.totalKnown !== 53 || + postGonganProgressState.campaignProgress?.totalKnown !== 54 || postGonganProgressState.campaignProgress?.latestBattleTitle !== '공안 성문 변고' || postGonganProgressState.campaignProgress?.nextBattleTitle !== '맥성 고립전' ) { @@ -5899,7 +5899,7 @@ try { if ( postMaichengProgressState?.activeTab !== 'progress' || postMaichengProgressState.campaignProgress?.completedKnown !== 44 || - postMaichengProgressState.campaignProgress?.totalKnown !== 53 || + postMaichengProgressState.campaignProgress?.totalKnown !== 54 || postMaichengProgressState.campaignProgress?.latestBattleTitle !== '맥성 고립전' || postMaichengProgressState.campaignProgress?.nextBattleTitle !== '이릉 진격로' ) { @@ -6056,7 +6056,7 @@ try { if ( postYilingProgressState?.activeTab !== 'progress' || postYilingProgressState.campaignProgress?.completedKnown !== 45 || - postYilingProgressState.campaignProgress?.totalKnown !== 53 || + postYilingProgressState.campaignProgress?.totalKnown !== 54 || postYilingProgressState.campaignProgress?.latestBattleTitle !== '이릉 진격로' || postYilingProgressState.campaignProgress?.nextBattleTitle !== '이릉 화공전' ) { @@ -6214,7 +6214,7 @@ try { if ( postYilingFireProgressState?.activeTab !== 'progress' || postYilingFireProgressState.campaignProgress?.completedKnown !== 46 || - postYilingFireProgressState.campaignProgress?.totalKnown !== 53 || + postYilingFireProgressState.campaignProgress?.totalKnown !== 54 || postYilingFireProgressState.campaignProgress?.latestBattleTitle !== '이릉 화공전' || postYilingFireProgressState.campaignProgress?.nextBattleTitle !== '백제성 유탁' ) { @@ -6280,7 +6280,7 @@ try { if ( postBaidiProgressState?.activeTab !== 'progress' || postBaidiProgressState.campaignProgress?.completedKnown !== 46 || - postBaidiProgressState.campaignProgress?.totalKnown !== 53 || + postBaidiProgressState.campaignProgress?.totalKnown !== 54 || postBaidiProgressState.campaignProgress?.latestBattleTitle !== '이릉 화공전' || postBaidiProgressState.campaignProgress?.nextBattleTitle !== '남중 안정로' ) { @@ -6441,7 +6441,7 @@ try { if ( postNanzhongProgressState?.activeTab !== 'progress' || postNanzhongProgressState.campaignProgress?.completedKnown !== 47 || - postNanzhongProgressState.campaignProgress?.totalKnown !== 53 || + postNanzhongProgressState.campaignProgress?.totalKnown !== 54 || postNanzhongProgressState.campaignProgress?.latestBattleTitle !== '남중 안정로' || postNanzhongProgressState.campaignProgress?.nextBattleTitle !== '맹획 본대전' ) { @@ -6603,7 +6603,7 @@ try { if ( postMengHuoProgressState?.activeTab !== 'progress' || postMengHuoProgressState.campaignProgress?.completedKnown !== 48 || - postMengHuoProgressState.campaignProgress?.totalKnown !== 53 || + postMengHuoProgressState.campaignProgress?.totalKnown !== 54 || postMengHuoProgressState.campaignProgress?.latestBattleTitle !== '맹획 본대전' || postMengHuoProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 2차전' ) { @@ -6765,7 +6765,7 @@ try { if ( postSecondCaptureProgressState?.activeTab !== 'progress' || postSecondCaptureProgressState.campaignProgress?.completedKnown !== 49 || - postSecondCaptureProgressState.campaignProgress?.totalKnown !== 53 || + postSecondCaptureProgressState.campaignProgress?.totalKnown !== 54 || postSecondCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 2차전' || postSecondCaptureProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 3차전' ) { @@ -6930,7 +6930,7 @@ try { if ( postThirdCaptureProgressState?.activeTab !== 'progress' || postThirdCaptureProgressState.campaignProgress?.completedKnown !== 50 || - postThirdCaptureProgressState.campaignProgress?.totalKnown !== 53 || + postThirdCaptureProgressState.campaignProgress?.totalKnown !== 54 || postThirdCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 3차전' || postThirdCaptureProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 4차전' ) { @@ -7096,7 +7096,7 @@ try { if ( postFourthCaptureProgressState?.activeTab !== 'progress' || postFourthCaptureProgressState.campaignProgress?.completedKnown !== 51 || - postFourthCaptureProgressState.campaignProgress?.totalKnown !== 53 || + postFourthCaptureProgressState.campaignProgress?.totalKnown !== 54 || postFourthCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 4차전' || postFourthCaptureProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 5차전' ) { @@ -7262,7 +7262,7 @@ try { if ( postFifthCaptureProgressState?.activeTab !== 'progress' || postFifthCaptureProgressState.campaignProgress?.completedKnown !== 52 || - postFifthCaptureProgressState.campaignProgress?.totalKnown !== 53 || + postFifthCaptureProgressState.campaignProgress?.totalKnown !== 54 || postFifthCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 5차전' || postFifthCaptureProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 6차전' ) { @@ -7427,14 +7427,179 @@ try { if ( postSixthCaptureProgressState?.activeTab !== 'progress' || postSixthCaptureProgressState.campaignProgress?.completedKnown !== 53 || - postSixthCaptureProgressState.campaignProgress?.totalKnown !== 53 || + postSixthCaptureProgressState.campaignProgress?.totalKnown !== 54 || postSixthCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 6차전' || - postSixthCaptureProgressState.campaignProgress?.nextBattleTitle !== '준비 중' + postSixthCaptureProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 7차전' ) { - throw new Error(`Expected post-sixth-capture progress tab to complete the fifty-third battle and pause before the final capture arc: ${JSON.stringify(postSixthCaptureProgressState?.campaignProgress)}`); + throw new Error(`Expected post-sixth-capture progress tab to complete the fifty-third battle and point to the final capture arc: ${JSON.stringify(postSixthCaptureProgressState?.campaignProgress)}`); } await page.screenshot({ path: 'dist/verification-post-sixth-capture-progress.png', fullPage: true }); + await page.mouse.click(1120, 38); + await page.waitForTimeout(180); + const finalCaptureSortieState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); + if ( + !finalCaptureSortieState?.sortieVisible || + finalCaptureSortieState.campaign?.step !== 'fifty-third-camp' || + finalCaptureSortieState.campTitle !== '칠종칠금 6차전 후 군영' || + finalCaptureSortieState.sortiePlan?.objectiveLine !== '칠종칠금 7차전 · 맹획 마지막 생포와 남중 회의장 보호' || + finalCaptureSortieState.sortiePlan?.maxCount !== 7 || + finalCaptureSortieState.sortiePlan?.recommendedTotal !== 7 || + !finalCaptureSortieState.sortieRoster?.some((unit) => unit.id === 'zhuge-liang' && unit.selected && unit.required) || + !finalCaptureSortieState.sortieRoster?.some((unit) => unit.id === 'huang-quan' && unit.recommended) || + !finalCaptureSortieState.sortieRoster?.some((unit) => unit.id === 'ma-liang' && unit.recommended) || + !finalCaptureSortieState.sortieRoster?.some((unit) => unit.id === 'ma-dai' && unit.recommended) || + finalCaptureSortieState.sortieRoster?.some((unit) => unit.id === 'liu-bei') + ) { + throw new Error(`Expected final Meng Huo sortie prep to expose council-protection recommendations, Zhuge Liang lock, and the expanded recruited roster: ${JSON.stringify(finalCaptureSortieState)}`); + } + assertSortieTacticalRoster(finalCaptureSortieState, [ + 'guan-yu', + 'zhang-fei', + 'jian-yong', + 'mi-zhu', + 'sun-qian', + 'zhao-yun', + 'zhuge-liang', + 'ma-liang', + 'yi-ji', + 'gong-zhi', + 'huang-zhong', + 'wei-yan', + 'pang-tong', + 'fa-zheng', + 'wu-yi', + 'yan-yan', + 'li-yan', + 'huang-quan', + 'ma-chao', + 'ma-dai', + 'wang-ping' + ]); + + const finalCapturePriorityUnits = ['huang-quan', 'ma-liang', 'wang-ping', 'zhao-yun', 'ma-dai', 'huang-zhong']; + for (const unitId of finalCapturePriorityUnits) { + const currentSortieState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); + if (!currentSortieState.sortieRoster?.some((unit) => unit.id === unitId && unit.selected)) { + const removable = currentSortieState.sortieRoster?.find( + (unit) => unit.selected && !unit.required && !finalCapturePriorityUnits.includes(unit.id) + ); + if (removable) { + await clickSortieRosterUnit(page, removable.id); + } + await clickSortieRosterUnit(page, unitId); + } + } + + const finalCaptureSortieReadyState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); + if ( + !finalCapturePriorityUnits.every((unitId) => + finalCaptureSortieReadyState.sortieRoster?.some((unit) => unit.id === unitId && unit.selected) + ) || + !finalCaptureSortieReadyState.sortieRoster?.some((unit) => unit.id === 'zhuge-liang' && unit.selected && unit.required) || + finalCaptureSortieReadyState.sortieRoster?.some((unit) => unit.id === 'liu-bei') || + finalCaptureSortieReadyState.sortiePlan?.selectedCount !== 7 || + finalCaptureSortieReadyState.sortiePlan?.recommendedSelectedCount < 7 + ) { + throw new Error(`Expected final Meng Huo sortie to deploy Zhuge Liang, Huang Quan, Ma Liang, Wang Ping, Zhao Yun, Ma Dai, and Huang Zhong: ${JSON.stringify(finalCaptureSortieReadyState)}`); + } + await page.screenshot({ path: 'dist/verification-final-capture-sortie.png', fullPage: true }); + + await page.mouse.click(1068, 646); + await page.waitForFunction(() => { + const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; + return activeScenes.includes('StoryScene'); + }); + await page.screenshot({ path: 'dist/verification-final-capture-story.png', fullPage: true }); + + for (let i = 0; i < 70; i += 1) { + const enteredFiftyFourthBattle = await page.evaluate(() => { + const state = window.__HEROS_DEBUG__?.battle(); + return state?.scene === 'BattleScene' && state?.battleId === 'fifty-fourth-battle-meng-huo-final-capture'; + }); + if (enteredFiftyFourthBattle) { + break; + } + await page.keyboard.press('Space'); + await page.waitForTimeout(320); + } + await page.waitForFunction(() => { + const state = window.__HEROS_DEBUG__?.battle(); + return state?.scene === 'BattleScene' && state?.battleId === 'fifty-fourth-battle-meng-huo-final-capture' && state?.battleOutcome === null && state?.phase === 'idle'; + }); + await page.screenshot({ path: 'dist/verification-fifty-fourth-battle.png', fullPage: true }); + + const fiftyFourthBattleState = await page.evaluate(() => window.__HEROS_DEBUG__?.battle()); + const fiftyFourthEnemies = fiftyFourthBattleState.units.filter((unit) => unit.faction === 'enemy'); + const fiftyFourthAllies = fiftyFourthBattleState.units.filter((unit) => unit.faction === 'ally'); + const fiftyFourthEnemyBehaviors = new Set(fiftyFourthEnemies.map((unit) => unit.ai)); + if ( + fiftyFourthBattleState.camera?.mapWidth !== 108 || + fiftyFourthBattleState.camera?.mapHeight !== 90 || + fiftyFourthBattleState.victoryConditionLabel !== '맹획 마지막 생포와 남중 회의장 보호' || + !fiftyFourthBattleState.objectives?.some((objective) => objective.id === 'pacify-meng-huo-final' && objective.label === '맹획 일곱 번째 생포') || + !fiftyFourthBattleState.objectives?.some((objective) => objective.id === 'village-witnesses' && objective.label === '촌장 증언 마을 보호') || + fiftyFourthEnemies.length < 43 || + !fiftyFourthEnemyBehaviors.has('aggressive') || + !fiftyFourthEnemyBehaviors.has('guard') || + !fiftyFourthEnemyBehaviors.has('hold') || + !fiftyFourthEnemies.some((unit) => unit.id === 'menghuo-final-leader') || + !fiftyFourthEnemies.some((unit) => unit.id === 'menghuo-final-officer-ahuinan') || + !fiftyFourthEnemies.some((unit) => unit.id === 'menghuo-final-officer-zhurong') || + !fiftyFourthEnemies.some((unit) => unit.id === 'menghuo-final-officer-mulu') || + !fiftyFourthEnemies.some((unit) => unit.id === 'menghuo-final-officer-duosi') || + !fiftyFourthAllies.some((unit) => unit.id === 'zhuge-liang') || + !fiftyFourthAllies.some((unit) => unit.id === 'huang-quan') || + !fiftyFourthAllies.some((unit) => unit.id === 'ma-liang') || + !fiftyFourthAllies.some((unit) => unit.id === 'wang-ping') || + !fiftyFourthAllies.some((unit) => unit.id === 'zhao-yun') || + !fiftyFourthAllies.some((unit) => unit.id === 'ma-dai') || + !fiftyFourthAllies.some((unit) => unit.id === 'huang-zhong') || + fiftyFourthAllies.some((unit) => unit.id === 'liu-bei') + ) { + throw new Error(`Expected fifty-fourth battle to use the final Meng Huo map, council/village objectives, selected recruited officers, and mixed clan AI: ${JSON.stringify(fiftyFourthBattleState)}`); + } + + await page.evaluate(() => window.__HEROS_DEBUG__?.forceBattleOutcome('victory')); + await page.waitForFunction(() => { + const state = window.__HEROS_DEBUG__?.battle(); + return state?.battleOutcome === 'victory' && state?.phase === 'resolved' && state?.resultVisible === true; + }); + + await page.mouse.click(738, 642); + await page.waitForFunction(() => { + const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; + return activeScenes.includes('CampScene'); + }); + + const fiftyFourthCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); + if ( + fiftyFourthCampState?.campBattleId !== 'fifty-fourth-battle-meng-huo-final-capture' || + fiftyFourthCampState.campTitle !== '남중 평정 후 군영' || + fiftyFourthCampState.availableDialogueIds?.length !== 3 || + !fiftyFourthCampState.availableDialogueIds.every((id) => id.includes('final-capture')) || + fiftyFourthCampState.availableVisitIds?.length !== 2 || + !fiftyFourthCampState.availableVisitIds.every((id) => id.includes('final-capture')) || + fiftyFourthCampState.rosterCollection?.total < 21 + ) { + throw new Error(`Expected final capture camp to expose Nanzhong pacification dialogue/visit sets and preserve the deployable officer roster: ${JSON.stringify(fiftyFourthCampState)}`); + } + await page.screenshot({ path: 'dist/verification-final-capture-camp.png', fullPage: true }); + + await page.mouse.click(966, 38); + await page.waitForTimeout(180); + const postFinalCaptureProgressState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); + if ( + postFinalCaptureProgressState?.activeTab !== 'progress' || + postFinalCaptureProgressState.campaignProgress?.completedKnown !== 54 || + postFinalCaptureProgressState.campaignProgress?.totalKnown !== 54 || + postFinalCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 7차전' || + postFinalCaptureProgressState.campaignProgress?.nextBattleTitle !== '준비 중' + ) { + throw new Error(`Expected post-final-capture progress tab to complete the fifty-fourth battle and pause before the northern campaign setup: ${JSON.stringify(postFinalCaptureProgressState?.campaignProgress)}`); + } + await page.screenshot({ path: 'dist/verification-post-final-capture-progress.png', fullPage: true }); + await page.evaluate(() => { const game = window.__HEROS_GAME__; game?.scene.stop('CampScene'); @@ -7466,11 +7631,11 @@ try { activeScenes: window.__HEROS_DEBUG__?.activeScenes() ?? [], camp: window.__HEROS_DEBUG__?.camp?.() ?? null })); - if (!titleContinueState.activeScenes.includes('CampScene') || titleContinueState.camp?.campaign?.step !== 'fifty-third-camp') { - throw new Error(`Expected title continue to reopen the latest sixth Meng Huo capture camp: ${JSON.stringify(titleContinueState)}`); + if (!titleContinueState.activeScenes.includes('CampScene') || titleContinueState.camp?.campaign?.step !== 'fifty-fourth-camp') { + throw new Error(`Expected title continue to reopen the Nanzhong pacification camp: ${JSON.stringify(titleContinueState)}`); } - console.log(`Verified title-to-sixth-Meng-Huo-capture flow, recruited officer sortie selection, Ma Liang, Yi Ji, Gong Zhi, Huang Zhong, Wei Yan, Pang Tong, Fa Zheng, Wu Yi, Yan Yan, Li Yan, Huang Quan, Ma Chao, Ma Dai, Wang Ping joins, Luofeng, Luo main gate, Mianzhu, Chengdu surrender, Jiameng, Yangping, Dingjun, Hanzhong decisive battle, King of Hanzhong council, Shu-Han foundation state, Jing Province defense vanguard, Fan Castle vanguard, Han River flood, Fan siege, Jing rear crisis, Gongan collapse, Maicheng isolation, Yiling vanguard, Yiling fire attack, Baidi entrustment, Nanzhong stabilization, Meng Huo pacification, second capture, third capture, fourth capture, fifth capture, and sixth capture state at ${targetUrl}`); + console.log(`Verified title-to-final-Meng-Huo-capture flow, recruited officer sortie selection, Ma Liang, Yi Ji, Gong Zhi, Huang Zhong, Wei Yan, Pang Tong, Fa Zheng, Wu Yi, Yan Yan, Li Yan, Huang Quan, Ma Chao, Ma Dai, Wang Ping joins, Luofeng, Luo main gate, Mianzhu, Chengdu surrender, Jiameng, Yangping, Dingjun, Hanzhong decisive battle, King of Hanzhong council, Shu-Han foundation state, Jing Province defense vanguard, Fan Castle vanguard, Han River flood, Fan siege, Jing rear crisis, Gongan collapse, Maicheng isolation, Yiling vanguard, Yiling fire attack, Baidi entrustment, Nanzhong stabilization, Meng Huo pacification, second capture, third capture, fourth capture, fifth capture, sixth capture, and Nanzhong pacification state at ${targetUrl}`); } finally { await browser?.close(); if (serverProcess && !serverProcess.killed) { diff --git a/src/assets/images/battle/fifty-fourth-battle-map.svg b/src/assets/images/battle/fifty-fourth-battle-map.svg new file mode 100644 index 0000000..1585724 --- /dev/null +++ b/src/assets/images/battle/fifty-fourth-battle-map.svg @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/game/data/battleMapAssets.ts b/src/game/data/battleMapAssets.ts index 4324152..7a623cb 100644 --- a/src/game/data/battleMapAssets.ts +++ b/src/game/data/battleMapAssets.ts @@ -5,6 +5,7 @@ import fiftiethBattleMapUrl from '../../assets/images/battle/fiftieth-battle-map import fiftyFirstBattleMapUrl from '../../assets/images/battle/fifty-first-battle-map.svg'; import fiftySecondBattleMapUrl from '../../assets/images/battle/fifty-second-battle-map.svg'; import fiftyThirdBattleMapUrl from '../../assets/images/battle/fifty-third-battle-map.svg'; +import fiftyFourthBattleMapUrl from '../../assets/images/battle/fifty-fourth-battle-map.svg'; import fifthBattleMapUrl from '../../assets/images/battle/fifth-battle-map.svg'; import fifteenthBattleMapUrl from '../../assets/images/battle/fifteenth-battle-map.svg'; import firstBattleMapUrl from '../../assets/images/battle/first-battle-map.png'; @@ -105,5 +106,6 @@ export const battleMapAssets: Record = { 'battle-map-fiftieth': fiftiethBattleMapUrl, 'battle-map-fifty-first': fiftyFirstBattleMapUrl, 'battle-map-fifty-second': fiftySecondBattleMapUrl, - 'battle-map-fifty-third': fiftyThirdBattleMapUrl + 'battle-map-fifty-third': fiftyThirdBattleMapUrl, + 'battle-map-fifty-fourth': fiftyFourthBattleMapUrl }; diff --git a/src/game/data/battles.ts b/src/game/data/battles.ts index 447e1ab..e3be490 100644 --- a/src/game/data/battles.ts +++ b/src/game/data/battles.ts @@ -15,6 +15,10 @@ import { fiftyThirdBattleMap, fiftyThirdBattleUnits, fiftyThirdBattleVictoryPages, + fiftyFourthBattleBonds, + fiftyFourthBattleMap, + fiftyFourthBattleUnits, + fiftyFourthBattleVictoryPages, firstBattleBonds, fifteenthBattleBonds, fifteenthBattleMap, @@ -270,7 +274,8 @@ export type BattleScenarioId = | 'fiftieth-battle-meng-huo-third-capture' | 'fifty-first-battle-meng-huo-fourth-capture' | 'fifty-second-battle-meng-huo-fifth-capture' - | 'fifty-third-battle-meng-huo-sixth-capture'; + | 'fifty-third-battle-meng-huo-sixth-capture' + | 'fifty-fourth-battle-meng-huo-final-capture'; export type BattleObjectiveKind = 'defeat-leader' | 'pacify-leader' | 'keep-unit-alive' | 'secure-terrain' | 'quick-victory'; @@ -3463,6 +3468,73 @@ export const fiftyThirdBattleScenario: BattleScenarioDefinition = { nextCampScene: 'CampScene' }; +export const fiftyFourthBattleScenario: BattleScenarioDefinition = { + id: 'fifty-fourth-battle-meng-huo-final-capture', + title: '칠종칠금 7차전', + victoryConditionLabel: '맹획 마지막 생포와 남중 회의장 보호', + defeatConditionLabel: '제갈량 퇴각', + openingObjectiveLines: [ + '맹획은 일곱 번째이자 마지막 대결을 남중의 촌장들이 보는 앞에서 열었습니다. 이번 전투는 승리뿐 아니라 군율과 약속을 증명하는 자리입니다.', + '회의장과 마을, 항복로가 무너지면 맹획을 잡아도 남중의 마음은 닫힙니다. 잔당 요새를 누르고, 물러설 자의 길과 촌장들의 증언을 지키십시오.', + '제갈량은 반드시 생존해야 합니다. 황권은 약속 장부를, 마량과 왕평은 회의장과 마을길을, 조운과 마대는 항복로 차단선을 맡기 좋습니다.' + ], + map: fiftyFourthBattleMap, + units: fiftyFourthBattleUnits, + bonds: fiftyFourthBattleBonds, + mapTextureKey: 'battle-map-fifty-fourth', + leaderUnitId: 'menghuo-final-leader', + quickVictoryTurnLimit: 106, + baseVictoryGold: 14360, + objectives: [ + { + id: 'pacify-meng-huo-final', + kind: 'pacify-leader', + label: '맹획 일곱 번째 생포', + rewardGold: 9640, + unitId: 'menghuo-final-leader' + }, + { + id: 'zhuge-liang', + kind: 'keep-unit-alive', + label: '제갈량 생존', + rewardGold: 2420, + unitId: 'zhuge-liang' + }, + { + id: 'council-road', + kind: 'secure-terrain', + label: '항복 회의장 길 확보', + rewardGold: 4820, + terrain: 'road' + }, + { + id: 'village-witnesses', + kind: 'secure-terrain', + label: '촌장 증언 마을 보호', + rewardGold: 4480, + terrain: 'village' + }, + { + id: 'final-remnant-forts', + kind: 'secure-terrain', + label: '최후 잔당 요새 제압', + rewardGold: 4260, + terrain: 'fort' + }, + { + id: 'quick', + kind: 'quick-victory', + label: '106턴 이내 마지막 생포', + rewardGold: 3220, + maxTurn: 106 + } + ], + defeatConditions: [{ kind: 'unit-defeated', unitId: 'zhuge-liang' }], + itemRewards: ['콩 80', '상처약 64', '탁주 30', '남중 평정 장부 1', '맹획 항복 서약 1'], + victoryPages: fiftyFourthBattleVictoryPages, + nextCampScene: 'CampScene' +}; + export const defaultBattleScenarioId: BattleScenarioId = firstBattleScenario.id; export const battleScenarios: Record = { @@ -3518,7 +3590,8 @@ export const battleScenarios: Record 'fiftieth-battle-meng-huo-third-capture': fiftiethBattleScenario, 'fifty-first-battle-meng-huo-fourth-capture': fiftyFirstBattleScenario, 'fifty-second-battle-meng-huo-fifth-capture': fiftySecondBattleScenario, - 'fifty-third-battle-meng-huo-sixth-capture': fiftyThirdBattleScenario + 'fifty-third-battle-meng-huo-sixth-capture': fiftyThirdBattleScenario, + 'fifty-fourth-battle-meng-huo-final-capture': fiftyFourthBattleScenario }; export const defaultBattleScenario = battleScenarios[defaultBattleScenarioId]; diff --git a/src/game/data/campaignFlow.ts b/src/game/data/campaignFlow.ts index 10c9632..6a9e347 100644 --- a/src/game/data/campaignFlow.ts +++ b/src/game/data/campaignFlow.ts @@ -8,6 +8,7 @@ import { fiftyFirstBattleScenario, fiftySecondBattleScenario, fiftyThirdBattleScenario, + fiftyFourthBattleScenario, fortiethBattleScenario, fortyFirstBattleScenario, fortySecondBattleScenario, @@ -71,6 +72,8 @@ import { fiftySecondBattleVictoryPages, fiftyThirdBattleIntroPages, fiftyThirdBattleVictoryPages, + fiftyFourthBattleIntroPages, + fiftyFourthBattleVictoryPages, fortiethBattleIntroPages, fortiethBattleVictoryPages, fortyFirstBattleIntroPages, @@ -773,13 +776,24 @@ const sortieFlows: Record = { }, [fiftyThirdBattleScenario.id]: { afterBattleId: fiftyThirdBattleScenario.id, - eyebrow: '다음 장 준비', - title: '칠종칠금의 마지막 문턱', + eyebrow: '다음 전장', + title: fiftyFourthBattleScenario.title, description: - '맹획은 여섯 번째로 붙잡혔고, 남중의 마을들은 촉한군의 약속을 직접 보았습니다. 다음 장에서는 칠종칠금의 마지막 설득과 남중 안정의 결말을 준비합니다.', - rewardHint: '다음 장: 칠종칠금 7차전과 남중 완전 회유 준비 중', - pages: fiftyThirdBattleVictoryPages, - unavailableNotice: '칠종칠금 7차전은 다음 작업에서 이어집니다. 지금은 여섯 번째 생포 뒤의 신뢰 장부와 계곡 회유 표식을 정비할 수 있습니다.' + '맹획은 여섯 번째로 붙잡혔고, 남중의 마을들은 촉한군의 약속을 직접 보았습니다. 이제 마지막 회의장 앞에서 맹획의 고집과 남중의 마음을 함께 마주합니다.', + rewardHint: `예상 보상: ${fiftyFourthBattleScenario.title} 개방 / 남중 평정 장부`, + nextBattleId: fiftyFourthBattleScenario.id, + campaignStep: 'fifty-fourth-battle', + pages: [...fiftyThirdBattleVictoryPages, ...fiftyFourthBattleIntroPages] + }, + [fiftyFourthBattleScenario.id]: { + afterBattleId: fiftyFourthBattleScenario.id, + eyebrow: '다음 장 준비', + title: '남중 평정 이후', + description: + '맹획은 일곱 번의 생포 끝에 항복했고, 남중은 촉한의 약속을 받아들였습니다. 이제 제갈량은 뒤를 안정시킨 뒤 북쪽을 향한 다음 장기전을 준비해야 합니다.', + rewardHint: '다음 장: 북벌 준비와 촉한 내부 정비 준비 중', + pages: fiftyFourthBattleVictoryPages, + unavailableNotice: '북벌 준비 장은 다음 작업에서 이어집니다. 지금은 남중 평정 장부와 맹획 항복 서약을 정비할 수 있습니다.' } }; diff --git a/src/game/data/scenario.ts b/src/game/data/scenario.ts index 201b767..9f76d81 100644 --- a/src/game/data/scenario.ts +++ b/src/game/data/scenario.ts @@ -3456,6 +3456,59 @@ export const fiftyThirdBattleVictoryPages: StoryPage[] = [ } ]; +export const fiftyFourthBattleIntroPages: StoryPage[] = [ + { + id: 'fifty-fourth-final-council', + bgm: 'story-dark', + chapter: '마지막 문턱', + background: 'story-militia', + speaker: '마량', + text: '여섯 번째 생포 뒤 남중의 촌장들은 더는 맹획의 말만 듣지 않았습니다. 그러나 맹획은 마지막 자존심을 걸고, 모든 부족 앞에서 다시 한 번 싸워 보자고 외쳤습니다.' + }, + { + id: 'fifty-fourth-seventh-capture-plan', + bgm: 'battle-prep', + chapter: '일곱 번째 생포', + background: 'story-three-heroes', + speaker: '제갈량', + text: '이번 싸움은 남중을 꺾는 전투가 아니라 남중이 스스로 고개를 끄덕이게 만드는 전투입니다. 맹획을 마지막으로 생포하되, 회의장과 마을, 신뢰 장부를 모두 지키십시오.' + }, + { + id: 'fifty-fourth-sortie', + bgm: 'battle-prep', + chapter: '칠종칠금 7차전', + background: 'story-sortie', + speaker: '왕평', + text: '마지막 전장은 길이 많아 보이지만, 잘못 밀고 들어가면 촌장들이 보는 앞에서 군율을 잃게 됩니다. 빠른 장수는 추격을 멈추고, 문사는 약속을 남기며, 전열은 회의장을 지켜야 합니다.' + } +]; + +export const fiftyFourthBattleVictoryPages: StoryPage[] = [ + { + id: 'fifty-fourth-victory-seventh-capture', + bgm: 'militia-theme', + chapter: '일곱 번째 생포', + background: 'story-sortie', + text: '맹획은 일곱 번째로 포위되었습니다. 그러나 이번에는 포로장의 침묵이 아니라, 촌장들의 눈앞에서 지켜진 약속과 무너진 고집이 함께 남았습니다.' + }, + { + id: 'fifty-fourth-victory-meng-huo-yields', + bgm: 'story-dark', + chapter: '맹획의 항복', + background: 'story-three-heroes', + speaker: '맹획', + text: '일곱 번 잡히고 일곱 번 풀려도 그대들은 남중의 백성을 짓밟지 않았다. 이제는 내가 졌음을 인정하겠다. 남중의 길은 촉한의 길과 함께하겠다.' + }, + { + id: 'fifty-fourth-nanzhong-pacified', + bgm: 'story-dark', + chapter: '남중 평정', + background: 'story-militia', + speaker: '제갈량', + text: '남중은 힘으로만 묶인 땅이 아닙니다. 약속을 지킨 뒤 얻은 땅입니다. 이제 촉한은 뒤를 안정시키고, 북쪽을 바라볼 준비를 시작해야 합니다.' + } +]; + export const firstBattleMap: BattleMap = { width: 20, height: 18, @@ -3919,6 +3972,12 @@ export const fiftyThirdBattleMap: BattleMap = { terrain: createFiftyThirdBattleTerrain() }; +export const fiftyFourthBattleMap: BattleMap = { + width: 108, + height: 90, + terrain: createFiftyFourthBattleTerrain() +}; + export const firstBattleUnits: UnitData[] = [ { id: 'liu-bei', @@ -16482,6 +16541,96 @@ export const fiftyThirdBattleUnits: UnitData[] = [ createThirtyFirstEnemyUnit('menghuo-sixth-leader', '맹획', '남만왕', 'rebelLeader', 168, 1140, 280, 100, 47) ]; +const fiftyFourthBattleAllyPositions: Record = { + 'guan-yu': { x: 17, y: 69 }, + 'zhang-fei': { x: 22, y: 71 }, + 'jian-yong': { x: 13, y: 76 }, + 'mi-zhu': { x: 15, y: 74 }, + 'sun-qian': { x: 19, y: 78 }, + 'zhao-yun': { x: 27, y: 66 }, + 'zhuge-liang': { x: 16, y: 66 }, + 'ma-liang': { x: 20, y: 63 }, + 'yi-ji': { x: 13, y: 81 }, + 'gong-zhi': { x: 23, y: 79 }, + 'huang-zhong': { x: 30, y: 67 }, + 'wei-yan': { x: 35, y: 71 }, + 'pang-tong': { x: 18, y: 60 }, + 'fa-zheng': { x: 24, y: 61 }, + 'wu-yi': { x: 37, y: 76 }, + 'yan-yan': { x: 40, y: 69 }, + 'li-yan': { x: 29, y: 59 }, + 'huang-quan': { x: 13, y: 64 }, + 'ma-chao': { x: 42, y: 66 }, + 'ma-dai': { x: 45, y: 71 }, + 'wang-ping': { x: 32, y: 62 } +}; + +export const fiftyFourthBattleUnits: UnitData[] = [ + ...[ + ...firstBattleUnits.filter((unit) => unit.faction === 'ally' && unit.id !== 'liu-bei'), + ...xuzhouRecruitUnits, + ...caoBreakRecruitUnits, + ...liuBiaoRecruitUnits, + ...zhugeRecruitUnits, + ...jingzhouRecruitUnits, + ...guiyangRecruitUnits, + ...wulingRecruitUnits, + ...changshaRecruitUnits, + ...yizhouRecruitUnits, + ...fuPassRecruitUnits, + ...luoCastleRecruitUnits, + ...luoCastleProperRecruitUnits, + ...chengduPressureRecruitUnits, + ...chengduSurrenderRecruitUnits, + ...hanzhongOpeningRecruitUnits, + ...hanzhongScoutRecruitUnits, + ...hanzhongMainRecruitUnits + ].map((unit) => placeScenarioUnit(unit, fiftyFourthBattleAllyPositions[unit.id] ?? { x: unit.x, y: unit.y })), + createThirtyFirstEnemyUnit('menghuo-final-scout-a', '남만 최후 척후', '서쪽 회의장 정찰', 'bandit', 166, 748, 222, 50, 42), + createThirtyFirstEnemyUnit('menghuo-final-scout-b', '남만 최후 척후', '물길 정찰', 'bandit', 166, 748, 222, 66, 40), + createThirtyFirstEnemyUnit('menghuo-final-scout-c', '남만 최후 척후', '마을 감시', 'bandit', 167, 756, 224, 82, 57), + createThirtyFirstEnemyUnit('menghuo-final-scout-d', '남만 최후 척후', '동쪽 산문 감시', 'bandit', 167, 756, 224, 98, 72), + createThirtyFirstEnemyUnit('menghuo-final-scout-e', '남만 최후 척후', '북쪽 절벽 감시', 'bandit', 168, 764, 226, 59, 30), + createThirtyFirstEnemyUnit('menghuo-final-scout-f', '남만 최후 척후', '항복로 감시', 'bandit', 168, 764, 226, 74, 78), + createThirtyFirstEnemyUnit('menghuo-final-vine-a', '최후 등갑병', '회의장 등갑병', 'yellowTurban', 167, 1044, 260, 56, 51), + createThirtyFirstEnemyUnit('menghuo-final-vine-b', '최후 등갑병', '불씨 등갑병', 'yellowTurban', 168, 1056, 262, 69, 54), + createThirtyFirstEnemyUnit('menghuo-final-vine-c', '최후 등갑병', '마을 등갑병', 'yellowTurban', 168, 1056, 262, 82, 64), + createThirtyFirstEnemyUnit('menghuo-final-vine-d', '최후 등갑병', '본영 등갑병', 'yellowTurban', 169, 1068, 264, 94, 56), + createThirtyFirstEnemyUnit('menghuo-final-vine-e', '최후 등갑병', '후위 등갑병', 'yellowTurban', 169, 1068, 264, 102, 48), + createThirtyFirstEnemyUnit('menghuo-final-vine-f', '최후 등갑병', '산문 등갑병', 'yellowTurban', 170, 1080, 266, 90, 37), + createThirtyFirstEnemyUnit('menghuo-final-archer-a', '남만 회의장 궁병', '독천 궁병', 'archer', 166, 800, 234, 52, 34), + createThirtyFirstEnemyUnit('menghuo-final-archer-b', '남만 회의장 궁병', '절벽 궁병', 'archer', 167, 808, 236, 68, 32), + createThirtyFirstEnemyUnit('menghuo-final-archer-c', '남만 회의장 궁병', '마을 궁병', 'archer', 167, 808, 236, 80, 50), + createThirtyFirstEnemyUnit('menghuo-final-archer-d', '남만 회의장 궁병', '물길 궁병', 'archer', 168, 816, 238, 92, 68), + createThirtyFirstEnemyUnit('menghuo-final-archer-e', '남만 회의장 궁병', '본영 궁병', 'archer', 168, 816, 238, 101, 61), + createThirtyFirstEnemyUnit('menghuo-final-beast-a', '남만 최후 맹수대', '강가 맹수대', 'cavalry', 168, 1060, 264, 62, 44), + createThirtyFirstEnemyUnit('menghuo-final-beast-b', '남만 최후 맹수대', '물길 맹수대', 'cavalry', 168, 1060, 264, 75, 46), + createThirtyFirstEnemyUnit('menghuo-final-beast-c', '남만 최후 맹수대', '산채 맹수대', 'cavalry', 169, 1072, 266, 87, 59), + createThirtyFirstEnemyUnit('menghuo-final-beast-d', '남만 최후 맹수대', '추격 맹수대', 'cavalry', 169, 1072, 266, 99, 65), + createThirtyFirstEnemyUnit('menghuo-final-beast-e', '남만 최후 맹수대', '항복로 차단대', 'cavalry', 170, 1084, 268, 73, 74), + createThirtyFirstEnemyUnit('menghuo-final-ambusher-a', '최후 매복병', '회의장 매복병', 'bandit', 167, 768, 228, 43, 57), + createThirtyFirstEnemyUnit('menghuo-final-ambusher-b', '최후 매복병', '숲속 매복병', 'bandit', 168, 776, 230, 59, 60), + createThirtyFirstEnemyUnit('menghuo-final-ambusher-c', '최후 매복병', '강가 매복병', 'bandit', 168, 776, 230, 72, 73), + createThirtyFirstEnemyUnit('menghuo-final-ambusher-d', '최후 매복병', '후방 차단대', 'bandit', 169, 784, 232, 92, 80), + createThirtyFirstEnemyUnit('menghuo-final-ambusher-e', '최후 매복병', '서쪽 차단대', 'bandit', 169, 784, 232, 38, 70), + createThirtyFirstEnemyUnit('menghuo-final-ambusher-f', '최후 매복병', '항복로 감시대', 'bandit', 170, 792, 234, 79, 78), + createThirtyFirstEnemyUnit('menghuo-final-guard-a', '맹획 최후 친위', '포로장 친위대', 'yellowTurban', 170, 1096, 270, 76, 48), + createThirtyFirstEnemyUnit('menghuo-final-guard-b', '맹획 최후 친위', '산채 친위대', 'yellowTurban', 170, 1096, 270, 89, 54), + createThirtyFirstEnemyUnit('menghuo-final-guard-c', '맹획 최후 친위', '본영 친위대', 'yellowTurban', 171, 1108, 272, 100, 51), + createThirtyFirstEnemyUnit('menghuo-final-guard-d', '맹획 최후 친위', '회의장 친위대', 'yellowTurban', 171, 1108, 272, 95, 41), + createThirtyFirstEnemyUnit('menghuo-final-shaman-a', '마지막 독천 제사장', '물길 제사장', 'strategist', 169, 848, 246, 68, 42), + createThirtyFirstEnemyUnit('menghuo-final-shaman-b', '마지막 독천 제사장', '강가 제사장', 'strategist', 170, 856, 248, 83, 55), + createThirtyFirstEnemyUnit('menghuo-final-shaman-c', '마지막 독천 제사장', '산채 제사장', 'strategist', 170, 856, 248, 94, 44), + createThirtyFirstEnemyUnit('menghuo-final-shaman-d', '마지막 독천 제사장', '회의장 제사장', 'strategist', 171, 864, 250, 88, 63), + createThirtyFirstEnemyUnit('menghuo-final-officer-zhurong', '축융 부인', '남중 화전장', 'rebelLeader', 172, 1138, 280, 86, 71), + createThirtyFirstEnemyUnit('menghuo-final-officer-mulu', '목록대왕', '맹수대왕', 'rebelLeader', 172, 1138, 280, 92, 43), + createThirtyFirstEnemyUnit('menghuo-final-officer-duosi', '타사대왕', '독천 호족장', 'rebelLeader', 171, 1118, 274, 76, 66), + createThirtyFirstEnemyUnit('menghuo-final-officer-mengyou', '맹우', '맹획의 아우', 'rebelLeader', 171, 1118, 274, 72, 58), + createThirtyFirstEnemyUnit('menghuo-final-officer-dailai', '대래동주', '깊은 산채 호족', 'rebelLeader', 171, 1118, 274, 85, 73), + createThirtyFirstEnemyUnit('menghuo-final-officer-ahuinan', '아회남', '흔들린 호족 대표', 'rebelLeader', 170, 1098, 270, 74, 70), + createThirtyFirstEnemyUnit('menghuo-final-leader', '맹획', '남만왕', 'rebelLeader', 174, 1180, 288, 102, 49) +]; + export const firstBattleBonds: BattleBond[] = [ { id: 'liu-bei__guan-yu', @@ -17504,6 +17653,35 @@ export const mengHuoSixthCaptureBonds: BattleBond[] = [ export const fiftyThirdBattleBonds: BattleBond[] = [...fiftySecondBattleBonds, ...mengHuoSixthCaptureBonds].map( cloneBattleBondForScenario ); +export const mengHuoFinalCaptureBonds: BattleBond[] = [ + { + id: 'zhuge-liang__huang-quan_final-capture', + unitIds: ['zhuge-liang', 'huang-quan'], + title: '일곱 번의 약속', + level: 60, + exp: 0, + description: '제갈량의 마지막 회유와 황권의 약속 장부는 맹획의 항복이 굴욕이 아니라 남중의 새 질서가 되게 한다.' + }, + { + id: 'ma-liang__wang-ping_final-capture', + unitIds: ['ma-liang', 'wang-ping'], + title: '마을과 회의장', + level: 56, + exp: 0, + description: '마량의 말과 왕평의 길 표식은 촌장들이 보는 앞에서도 촉한군의 군율이 흔들리지 않게 한다.' + }, + { + id: 'zhao-yun__ma-dai_final-capture', + unitIds: ['zhao-yun', 'ma-dai'], + title: '멈추는 추격', + level: 50, + exp: 0, + description: '조운과 마대는 빠른 기동으로 잔당을 끊되, 항복로를 밟지 않는 절제의 선을 함께 지킨다.' + } +]; +export const fiftyFourthBattleBonds: BattleBond[] = [...fiftyThirdBattleBonds, ...mengHuoFinalCaptureBonds].map( + cloneBattleBondForScenario +); function createEighthBattleTerrain(): TerrainType[][] { return Array.from({ length: 22 }, (_, y) => @@ -20292,6 +20470,81 @@ function createFiftyThirdBattleTerrain(): TerrainType[][] { ); } +function createFiftyFourthBattleTerrain(): TerrainType[][] { + return Array.from({ length: 90 }, (_, y) => + Array.from({ length: 108 }, (_, x): TerrainType => { + if ( + (x >= 0 && x <= 107 && y >= 82) || + (x >= 69 && x <= 75 && y >= 0 && y <= 84) || + (x >= 7 && x <= 36 && y >= 73 && y <= 89) || + (x >= 82 && x <= 100 && y >= 64 && y <= 82) + ) { + return 'river'; + } + if ( + (x >= 10 && x <= 103 && y >= 69 && y <= 75) || + (x >= 30 && x <= 80 && y >= 46 && y <= 54) || + (x >= 75 && x <= 103 && y >= 38 && y <= 44) || + (x >= 41 && x <= 50 && y >= 43 && y <= 78) || + (x >= 82 && x <= 88 && y >= 52 && y <= 86) || + (x >= 18 && x <= 56 && y >= 61 && y <= 65) || + (x >= 54 && x <= 69 && y >= 55 && y <= 61) || + (x >= 60 && x <= 76 && y >= 45 && y <= 53) + ) { + return 'road'; + } + if ( + (x >= 18 && x <= 78 && y >= 23 && y <= 80) || + (x >= 58 && x <= 102 && y >= 31 && y <= 76) || + (x >= 2 && x <= 43 && y >= 54 && y <= 80) || + (x >= 84 && x <= 107 && y >= 45 && y <= 79) + ) { + return 'forest'; + } + if ( + (x >= 0 && x <= 34 && y <= 42) || + (x >= 36 && x <= 74 && y <= 32) || + (x >= 77 && x <= 107 && y <= 41) || + (x >= 92 && x <= 107 && y >= 37 && y <= 61) + ) { + return 'hill'; + } + if ( + (x <= 2 && y <= 80) || + (x >= 105 && y <= 76) || + (x >= 33 && x <= 39 && y >= 0 && y <= 44) || + (x >= 74 && x <= 80 && y >= 0 && y <= 38) || + (x >= 48 && x <= 54 && y >= 70 && y <= 89) + ) { + return 'cliff'; + } + if ( + (x >= 13 && x <= 30 && y >= 65 && y <= 77) || + (x >= 42 && x <= 60 && y >= 48 && y <= 62) || + (x >= 81 && x <= 97 && y >= 54 && y <= 67) || + (x >= 86 && x <= 100 && y >= 36 && y <= 49) + ) { + return 'village'; + } + if ( + (x >= 26 && x <= 42 && y >= 57 && y <= 68) || + (x >= 69 && x <= 84 && y >= 62 && y <= 74) || + (x >= 87 && x <= 102 && y >= 43 && y <= 57) + ) { + return 'camp'; + } + if ( + (x >= 51 && x <= 69 && y >= 35 && y <= 46) || + (x >= 78 && x <= 99 && y >= 35 && y <= 50) || + (x >= 90 && x <= 103 && y >= 48 && y <= 60) + ) { + return 'fort'; + } + return 'plain'; + }) + ); +} + function placeScenarioUnit(unit: UnitData, position: { x: number; y: number }): UnitData { return { ...cloneUnitForScenario(unit), diff --git a/src/game/scenes/BattleScene.ts b/src/game/scenes/BattleScene.ts index 98ec4e6..0e66bac 100644 --- a/src/game/scenes/BattleScene.ts +++ b/src/game/scenes/BattleScene.ts @@ -902,7 +902,18 @@ const unitTexture: Record = { 'menghuo-sixth-officer-duosi': 'unit-rebel-leader', 'menghuo-sixth-officer-mengyou': 'unit-rebel-leader', 'menghuo-sixth-officer-dailai': 'unit-rebel-leader', - 'menghuo-sixth-leader': 'unit-rebel-leader' + 'menghuo-sixth-leader': 'unit-rebel-leader', + 'menghuo-final-shaman-a': 'unit-rebel-archer', + 'menghuo-final-shaman-b': 'unit-rebel-archer', + 'menghuo-final-shaman-c': 'unit-rebel-archer', + 'menghuo-final-shaman-d': 'unit-rebel-archer', + 'menghuo-final-officer-zhurong': 'unit-rebel-leader', + 'menghuo-final-officer-mulu': 'unit-rebel-leader', + 'menghuo-final-officer-duosi': 'unit-rebel-leader', + 'menghuo-final-officer-mengyou': 'unit-rebel-leader', + 'menghuo-final-officer-dailai': 'unit-rebel-leader', + 'menghuo-final-officer-ahuinan': 'unit-rebel-leader', + 'menghuo-final-leader': 'unit-rebel-leader' }; const unitTextureByClass: Partial> = { @@ -2524,7 +2535,50 @@ const enemyAiByUnitId: Record = { 'menghuo-sixth-officer-duosi': 'guard', 'menghuo-sixth-officer-mengyou': 'guard', 'menghuo-sixth-officer-dailai': 'aggressive', - 'menghuo-sixth-leader': 'hold' + 'menghuo-sixth-leader': 'hold', + 'menghuo-final-scout-a': 'aggressive', + 'menghuo-final-scout-b': 'aggressive', + 'menghuo-final-scout-c': 'aggressive', + 'menghuo-final-scout-d': 'aggressive', + 'menghuo-final-scout-e': 'aggressive', + 'menghuo-final-scout-f': 'aggressive', + 'menghuo-final-vine-a': 'guard', + 'menghuo-final-vine-b': 'guard', + 'menghuo-final-vine-c': 'guard', + 'menghuo-final-vine-d': 'guard', + 'menghuo-final-vine-e': 'guard', + 'menghuo-final-vine-f': 'guard', + 'menghuo-final-archer-a': 'hold', + 'menghuo-final-archer-b': 'hold', + 'menghuo-final-archer-c': 'hold', + 'menghuo-final-archer-d': 'hold', + 'menghuo-final-archer-e': 'hold', + 'menghuo-final-beast-a': 'aggressive', + 'menghuo-final-beast-b': 'aggressive', + 'menghuo-final-beast-c': 'aggressive', + 'menghuo-final-beast-d': 'aggressive', + 'menghuo-final-beast-e': 'aggressive', + 'menghuo-final-ambusher-a': 'aggressive', + 'menghuo-final-ambusher-b': 'aggressive', + 'menghuo-final-ambusher-c': 'aggressive', + 'menghuo-final-ambusher-d': 'aggressive', + 'menghuo-final-ambusher-e': 'aggressive', + 'menghuo-final-ambusher-f': 'aggressive', + 'menghuo-final-guard-a': 'guard', + 'menghuo-final-guard-b': 'guard', + 'menghuo-final-guard-c': 'guard', + 'menghuo-final-guard-d': 'guard', + 'menghuo-final-shaman-a': 'hold', + 'menghuo-final-shaman-b': 'hold', + 'menghuo-final-shaman-c': 'hold', + 'menghuo-final-shaman-d': 'hold', + 'menghuo-final-officer-zhurong': 'aggressive', + 'menghuo-final-officer-mulu': 'aggressive', + 'menghuo-final-officer-duosi': 'guard', + 'menghuo-final-officer-mengyou': 'guard', + 'menghuo-final-officer-dailai': 'aggressive', + 'menghuo-final-officer-ahuinan': 'guard', + 'menghuo-final-leader': 'hold' }; const defaultEnemyAiByClass: Partial> = { diff --git a/src/game/scenes/CampScene.ts b/src/game/scenes/CampScene.ts index 51010f4..1ffd830 100644 --- a/src/game/scenes/CampScene.ts +++ b/src/game/scenes/CampScene.ts @@ -30,6 +30,7 @@ import { mengHuoFourthCaptureBonds, mengHuoFifthCaptureBonds, mengHuoSixthCaptureBonds, + mengHuoFinalCaptureBonds, mengHuoThirdCaptureBonds, fanCastleBonds, fanCastleSiegeBonds, @@ -419,7 +420,8 @@ const campaignTimelineChapters: CampaignTimelineChapter[] = [ 'fiftieth-battle-meng-huo-third-capture', 'fifty-first-battle-meng-huo-fourth-capture', 'fifty-second-battle-meng-huo-fifth-capture', - 'fifty-third-battle-meng-huo-sixth-capture' + 'fifty-third-battle-meng-huo-sixth-capture', + 'fifty-fourth-battle-meng-huo-final-capture' ], nextHints: ['왕업 선언', '번성 포위', '형주 위기', '이릉 화공', '남중 안정', '맹획 회유', '칠종칠금'] } @@ -478,7 +480,8 @@ const campBattleIds = { fiftieth: 'fiftieth-battle-meng-huo-third-capture', fiftyFirst: 'fifty-first-battle-meng-huo-fourth-capture', fiftySecond: 'fifty-second-battle-meng-huo-fifth-capture', - fiftyThird: 'fifty-third-battle-meng-huo-sixth-capture' + fiftyThird: 'fifty-third-battle-meng-huo-sixth-capture', + fiftyFourth: 'fifty-fourth-battle-meng-huo-final-capture' } as const; const defaultRequiredSortieUnitIds = ['liu-bei']; @@ -1123,6 +1126,20 @@ const sortieRulesByBattleId: Partial