From 3c18072c1e3d45988185cbc96a6319d318afaeee Mon Sep 17 00:00:00 2001 From: Wickedness Date: Wed, 24 Jun 2026 03:12:06 +0900 Subject: [PATCH] Add Tianshui advance sortie flow --- docs/roadmap.md | 8 +- scripts/verify-flow.mjs | 247 ++++++++++++---- .../images/battle/fifty-sixth-battle-map.svg | 58 ++++ src/game/data/battleMapAssets.ts | 4 +- src/game/data/battles.ts | 84 +++++- src/game/data/campaignFlow.ts | 26 +- src/game/data/scenario.ts | 263 ++++++++++++++++++ src/game/scenes/BattleScene.ts | 31 ++- src/game/scenes/CampScene.ts | 173 +++++++++++- src/game/scenes/TitleScene.ts | 7 + src/game/state/campaignState.ts | 5 +- 11 files changed, 834 insertions(+), 72 deletions(-) create mode 100644 src/assets/images/battle/fifty-sixth-battle-map.svg diff --git a/docs/roadmap.md b/docs/roadmap.md index 931b176..ed43be9 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -82,15 +82,17 @@ Build a small complete tactical RPG loop that can grow into a longer Romance of - Post-Nanzhong northern-campaign preparation milestone, where the final pacification camp advances into a Zhuge Liang-led war council, excludes Liu Bei from the preparation roster, and opens northern sortie planning dialogue, granary checks, and memorial-drafting visits - Fifty-fifth battle first northern expedition Qishan road, where Zhuge Liang leads the sortie instead of Liu Bei, the player chooses seven officers from the expanded roster, and a 110x92 mountain-road battlefield mixes scout, cavalry, archer, strategist, ridge-guard, and named Wei officer behaviors - Post-Qishan-road camp events with Zhuge Liang, Zhao Yun, Huang Quan, Ma Liang, Wang Ping, and Ma Dai focusing on route choice, supply books, ridge defense, and long-campaign northern-front preparation +- Fifty-sixth battle Tianshui advance route, where Zhuge Liang leads a seven-officer sortie from the expanded Shu-Han roster, Liu Bei remains excluded, Guo Huai's defense line blocks the 112x94 road to Tianshui, and Jiang Wei appears as a rival/recruitment hook before the Jieting pressure point +- Post-Tianshui camp events with Zhuge Liang, Wang Ping, Zhao Yun, Ma Dai, Ma Liang, Fa Zheng, and Huang Quan focusing on Jieting terrain judgment, flank discipline, Jiang Wei's talent, waterline logistics, and the next northern-front risk - 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 +- Tactical sortie preparation panel with battle-specific sortie limits, required/excluded/recommended officers, expanding recruited-roster selection pressure, 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 first northern expedition and post-Qishan-road 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 Tianshui advance and post-Tianshui 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 next northern-front step toward Tianshui/Jieting pressure, introducing Wei's counterplan and a new recruit or rival hook such as Jiang Wei +1. Build the Jieting placement crisis, including Ma Su/Wang Ping tension, Jiang Wei's recruitment hook, and sortie choices that make terrain readers more valuable than raw attackers 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 92ce78a..3530565 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 !== 55 || + progressTabState.campaignProgress?.totalKnown !== 56 || 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 !== 55 || + postBowangProgressState.campaignProgress?.totalKnown !== 56 || 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 !== 55 || + postChangbanProgressState.campaignProgress?.totalKnown !== 56 || 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 !== 55 || + postJiangdongProgressState.campaignProgress?.totalKnown !== 56 || 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 !== 55 || + postRedCliffsProgressState.campaignProgress?.totalKnown !== 56 || 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 !== 55 || + postFireAttackProgressState.campaignProgress?.totalKnown !== 56 || 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 !== 55 || + postJingzhouProgressState.campaignProgress?.totalKnown !== 56 || 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 !== 55 || + postGuiyangProgressState.campaignProgress?.totalKnown !== 56 || 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 !== 55 || + postWulingProgressState.campaignProgress?.totalKnown !== 56 || 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 !== 55 || + postChangshaProgressState.campaignProgress?.totalKnown !== 56 || 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 !== 55 || + postYizhouProgressState.campaignProgress?.totalKnown !== 56 || 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 !== 55 || + postFuPassProgressState.campaignProgress?.totalKnown !== 56 || 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 !== 55 || + postLuoProgressState.campaignProgress?.totalKnown !== 56 || 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 !== 55 || + postLuofengProgressState.campaignProgress?.totalKnown !== 56 || 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 !== 55 || + postLuoMainProgressState.campaignProgress?.totalKnown !== 56 || 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 !== 55 || + postMianzhuProgressState.campaignProgress?.totalKnown !== 56 || 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 !== 55 || + postChengduProgressState.campaignProgress?.totalKnown !== 56 || 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 !== 55 || + postJiamengProgressState.campaignProgress?.totalKnown !== 56 || 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 !== 55 || + postYangpingProgressState.campaignProgress?.totalKnown !== 56 || 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 !== 55 || + postDingjunProgressState.campaignProgress?.totalKnown !== 56 || 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 !== 55 || + postHanzhongProgressState.campaignProgress?.totalKnown !== 56 || 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 !== 55 || + postKingCouncilProgressState.campaignProgress?.totalKnown !== 56 || postKingCouncilProgressState.campaignProgress?.latestBattleTitle !== '한중 결전' || postKingCouncilProgressState.campaignProgress?.nextBattleTitle !== '촉한 건국 선포' ) { @@ -4812,7 +4812,7 @@ try { if ( postFoundationProgressState?.activeTab !== 'progress' || postFoundationProgressState.campaignProgress?.completedKnown !== 37 || - postFoundationProgressState.campaignProgress?.totalKnown !== 55 || + postFoundationProgressState.campaignProgress?.totalKnown !== 56 || postFoundationProgressState.campaignProgress?.latestBattleTitle !== '한중 결전' || postFoundationProgressState.campaignProgress?.nextBattleTitle !== '형주 방위 전초전' ) { @@ -4963,7 +4963,7 @@ try { if ( postJingDefenseProgressState?.activeTab !== 'progress' || postJingDefenseProgressState.campaignProgress?.completedKnown !== 38 || - postJingDefenseProgressState.campaignProgress?.totalKnown !== 55 || + postJingDefenseProgressState.campaignProgress?.totalKnown !== 56 || postJingDefenseProgressState.campaignProgress?.latestBattleTitle !== '형주 방위 전초전' || postJingDefenseProgressState.campaignProgress?.nextBattleTitle !== '번성 외곽 압박전' ) { @@ -5116,7 +5116,7 @@ try { if ( postFanCastleProgressState?.activeTab !== 'progress' || postFanCastleProgressState.campaignProgress?.completedKnown !== 39 || - postFanCastleProgressState.campaignProgress?.totalKnown !== 55 || + postFanCastleProgressState.campaignProgress?.totalKnown !== 56 || postFanCastleProgressState.campaignProgress?.latestBattleTitle !== '번성 외곽 압박전' || postFanCastleProgressState.campaignProgress?.nextBattleTitle !== '한수 수공' ) { @@ -5270,7 +5270,7 @@ try { if ( postHanRiverProgressState?.activeTab !== 'progress' || postHanRiverProgressState.campaignProgress?.completedKnown !== 40 || - postHanRiverProgressState.campaignProgress?.totalKnown !== 55 || + postHanRiverProgressState.campaignProgress?.totalKnown !== 56 || postHanRiverProgressState.campaignProgress?.latestBattleTitle !== '한수 수공' || postHanRiverProgressState.campaignProgress?.nextBattleTitle !== '번성 공성전' ) { @@ -5424,7 +5424,7 @@ try { if ( postFanSiegeProgressState?.activeTab !== 'progress' || postFanSiegeProgressState.campaignProgress?.completedKnown !== 41 || - postFanSiegeProgressState.campaignProgress?.totalKnown !== 55 || + postFanSiegeProgressState.campaignProgress?.totalKnown !== 56 || postFanSiegeProgressState.campaignProgress?.latestBattleTitle !== '번성 공성전' || postFanSiegeProgressState.campaignProgress?.nextBattleTitle !== '강릉 나루 경계전' ) { @@ -5586,7 +5586,7 @@ try { if ( postJingRearProgressState?.activeTab !== 'progress' || postJingRearProgressState.campaignProgress?.completedKnown !== 42 || - postJingRearProgressState.campaignProgress?.totalKnown !== 55 || + postJingRearProgressState.campaignProgress?.totalKnown !== 56 || postJingRearProgressState.campaignProgress?.latestBattleTitle !== '강릉 나루 경계전' || postJingRearProgressState.campaignProgress?.nextBattleTitle !== '공안 성문 변고' ) { @@ -5741,7 +5741,7 @@ try { if ( postGonganProgressState?.activeTab !== 'progress' || postGonganProgressState.campaignProgress?.completedKnown !== 43 || - postGonganProgressState.campaignProgress?.totalKnown !== 55 || + postGonganProgressState.campaignProgress?.totalKnown !== 56 || postGonganProgressState.campaignProgress?.latestBattleTitle !== '공안 성문 변고' || postGonganProgressState.campaignProgress?.nextBattleTitle !== '맥성 고립전' ) { @@ -5899,7 +5899,7 @@ try { if ( postMaichengProgressState?.activeTab !== 'progress' || postMaichengProgressState.campaignProgress?.completedKnown !== 44 || - postMaichengProgressState.campaignProgress?.totalKnown !== 55 || + postMaichengProgressState.campaignProgress?.totalKnown !== 56 || postMaichengProgressState.campaignProgress?.latestBattleTitle !== '맥성 고립전' || postMaichengProgressState.campaignProgress?.nextBattleTitle !== '이릉 진격로' ) { @@ -6056,7 +6056,7 @@ try { if ( postYilingProgressState?.activeTab !== 'progress' || postYilingProgressState.campaignProgress?.completedKnown !== 45 || - postYilingProgressState.campaignProgress?.totalKnown !== 55 || + postYilingProgressState.campaignProgress?.totalKnown !== 56 || postYilingProgressState.campaignProgress?.latestBattleTitle !== '이릉 진격로' || postYilingProgressState.campaignProgress?.nextBattleTitle !== '이릉 화공전' ) { @@ -6214,7 +6214,7 @@ try { if ( postYilingFireProgressState?.activeTab !== 'progress' || postYilingFireProgressState.campaignProgress?.completedKnown !== 46 || - postYilingFireProgressState.campaignProgress?.totalKnown !== 55 || + postYilingFireProgressState.campaignProgress?.totalKnown !== 56 || postYilingFireProgressState.campaignProgress?.latestBattleTitle !== '이릉 화공전' || postYilingFireProgressState.campaignProgress?.nextBattleTitle !== '백제성 유탁' ) { @@ -6280,7 +6280,7 @@ try { if ( postBaidiProgressState?.activeTab !== 'progress' || postBaidiProgressState.campaignProgress?.completedKnown !== 46 || - postBaidiProgressState.campaignProgress?.totalKnown !== 55 || + postBaidiProgressState.campaignProgress?.totalKnown !== 56 || postBaidiProgressState.campaignProgress?.latestBattleTitle !== '이릉 화공전' || postBaidiProgressState.campaignProgress?.nextBattleTitle !== '남중 안정로' ) { @@ -6441,7 +6441,7 @@ try { if ( postNanzhongProgressState?.activeTab !== 'progress' || postNanzhongProgressState.campaignProgress?.completedKnown !== 47 || - postNanzhongProgressState.campaignProgress?.totalKnown !== 55 || + postNanzhongProgressState.campaignProgress?.totalKnown !== 56 || postNanzhongProgressState.campaignProgress?.latestBattleTitle !== '남중 안정로' || postNanzhongProgressState.campaignProgress?.nextBattleTitle !== '맹획 본대전' ) { @@ -6603,7 +6603,7 @@ try { if ( postMengHuoProgressState?.activeTab !== 'progress' || postMengHuoProgressState.campaignProgress?.completedKnown !== 48 || - postMengHuoProgressState.campaignProgress?.totalKnown !== 55 || + postMengHuoProgressState.campaignProgress?.totalKnown !== 56 || postMengHuoProgressState.campaignProgress?.latestBattleTitle !== '맹획 본대전' || postMengHuoProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 2차전' ) { @@ -6765,7 +6765,7 @@ try { if ( postSecondCaptureProgressState?.activeTab !== 'progress' || postSecondCaptureProgressState.campaignProgress?.completedKnown !== 49 || - postSecondCaptureProgressState.campaignProgress?.totalKnown !== 55 || + postSecondCaptureProgressState.campaignProgress?.totalKnown !== 56 || postSecondCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 2차전' || postSecondCaptureProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 3차전' ) { @@ -6930,7 +6930,7 @@ try { if ( postThirdCaptureProgressState?.activeTab !== 'progress' || postThirdCaptureProgressState.campaignProgress?.completedKnown !== 50 || - postThirdCaptureProgressState.campaignProgress?.totalKnown !== 55 || + postThirdCaptureProgressState.campaignProgress?.totalKnown !== 56 || postThirdCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 3차전' || postThirdCaptureProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 4차전' ) { @@ -7096,7 +7096,7 @@ try { if ( postFourthCaptureProgressState?.activeTab !== 'progress' || postFourthCaptureProgressState.campaignProgress?.completedKnown !== 51 || - postFourthCaptureProgressState.campaignProgress?.totalKnown !== 55 || + postFourthCaptureProgressState.campaignProgress?.totalKnown !== 56 || postFourthCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 4차전' || postFourthCaptureProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 5차전' ) { @@ -7262,7 +7262,7 @@ try { if ( postFifthCaptureProgressState?.activeTab !== 'progress' || postFifthCaptureProgressState.campaignProgress?.completedKnown !== 52 || - postFifthCaptureProgressState.campaignProgress?.totalKnown !== 55 || + postFifthCaptureProgressState.campaignProgress?.totalKnown !== 56 || postFifthCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 5차전' || postFifthCaptureProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 6차전' ) { @@ -7427,7 +7427,7 @@ try { if ( postSixthCaptureProgressState?.activeTab !== 'progress' || postSixthCaptureProgressState.campaignProgress?.completedKnown !== 53 || - postSixthCaptureProgressState.campaignProgress?.totalKnown !== 55 || + postSixthCaptureProgressState.campaignProgress?.totalKnown !== 56 || postSixthCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 6차전' || postSixthCaptureProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 7차전' ) { @@ -7593,7 +7593,7 @@ try { if ( postFinalCaptureProgressState?.activeTab !== 'progress' || postFinalCaptureProgressState.campaignProgress?.completedKnown !== 54 || - postFinalCaptureProgressState.campaignProgress?.totalKnown !== 55 || + postFinalCaptureProgressState.campaignProgress?.totalKnown !== 56 || postFinalCaptureProgressState.campaignProgress?.activeChapter?.id !== 'northern-campaign' || postFinalCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 7차전' || !String(postFinalCaptureProgressState.campaignProgress?.nextBattleTitle ?? '').includes('북벌 준비') @@ -7666,7 +7666,7 @@ try { if ( postNorthernPrepProgressState?.activeTab !== 'progress' || postNorthernPrepProgressState.campaignProgress?.completedKnown !== 54 || - postNorthernPrepProgressState.campaignProgress?.totalKnown !== 55 || + postNorthernPrepProgressState.campaignProgress?.totalKnown !== 56 || postNorthernPrepProgressState.campaignProgress?.activeChapter?.id !== 'northern-campaign' || postNorthernPrepProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 7차전' || postNorthernPrepProgressState.campaignProgress?.nextBattleTitle !== '제1차 북벌 출진로' @@ -7816,13 +7816,164 @@ try { if ( postFiftyFifthProgressState?.activeTab !== 'progress' || postFiftyFifthProgressState.campaignProgress?.completedKnown !== 55 || - postFiftyFifthProgressState.campaignProgress?.totalKnown !== 55 || + postFiftyFifthProgressState.campaignProgress?.totalKnown !== 56 || postFiftyFifthProgressState.campaignProgress?.activeChapter?.id !== 'northern-campaign' || - postFiftyFifthProgressState.campaignProgress?.latestBattleTitle !== '제1차 북벌 출진로' + postFiftyFifthProgressState.campaignProgress?.latestBattleTitle !== '제1차 북벌 출진로' || + postFiftyFifthProgressState.campaignProgress?.nextBattleTitle !== '천수 진군로' ) { - throw new Error(`Expected post-fifty-fifth progress tab to complete the first northern expedition battle: ${JSON.stringify(postFiftyFifthProgressState?.campaignProgress)}`); + throw new Error(`Expected post-fifty-fifth progress tab to complete the first northern expedition battle and point toward Tianshui: ${JSON.stringify(postFiftyFifthProgressState?.campaignProgress)}`); } await page.screenshot({ path: 'dist/verification-post-fifty-fifth-progress.png', fullPage: true }); + + await page.mouse.click(1120, 38); + await page.waitForTimeout(180); + const secondNorthernSortieState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); + if ( + !secondNorthernSortieState?.sortieVisible || + secondNorthernSortieState.campaign?.step !== 'fifty-fifth-camp' || + secondNorthernSortieState.campTitle !== '기산 출진로 후 군영' || + !String(secondNorthernSortieState.sortiePlan?.objectiveLine ?? '').includes('천수 진군로') || + !String(secondNorthernSortieState.sortiePlan?.objectiveLine ?? '').includes('곽회') || + secondNorthernSortieState.sortiePlan?.maxCount !== 7 || + secondNorthernSortieState.sortiePlan?.recommendedTotal !== 7 || + !secondNorthernSortieState.sortieRoster?.some((unit) => unit.id === 'zhuge-liang' && unit.selected && unit.required) || + !secondNorthernSortieState.sortieRoster?.some((unit) => unit.id === 'wang-ping' && unit.recommended) || + !secondNorthernSortieState.sortieRoster?.some((unit) => unit.id === 'zhao-yun' && unit.recommended) || + !secondNorthernSortieState.sortieRoster?.some((unit) => unit.id === 'huang-quan' && unit.recommended) || + !secondNorthernSortieState.sortieRoster?.some((unit) => unit.id === 'ma-liang' && unit.recommended) || + !secondNorthernSortieState.sortieRoster?.some((unit) => unit.id === 'fa-zheng' && unit.recommended) || + !secondNorthernSortieState.sortieRoster?.some((unit) => unit.id === 'ma-dai' && unit.recommended) || + secondNorthernSortieState.sortieRoster?.some((unit) => unit.id === 'liu-bei') + ) { + throw new Error(`Expected second northern expedition sortie to require Zhuge Liang, exclude Liu Bei, and recommend the Tianshui planning roster: ${JSON.stringify(secondNorthernSortieState)}`); + } + assertSortieTacticalRoster(secondNorthernSortieState, [ + '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 secondNorthernPriorityUnits = ['wang-ping', 'zhao-yun', 'huang-quan', 'ma-liang', 'fa-zheng', 'ma-dai']; + for (const unitId of secondNorthernPriorityUnits) { + 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 && !secondNorthernPriorityUnits.includes(unit.id) + ); + if (removable) { + await clickSortieRosterUnit(page, removable.id); + } + await clickSortieRosterUnit(page, unitId); + } + } + + const secondNorthernSortieReadyState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); + if ( + !secondNorthernPriorityUnits.every((unitId) => + secondNorthernSortieReadyState.sortieRoster?.some((unit) => unit.id === unitId && unit.selected) + ) || + !secondNorthernSortieReadyState.sortieRoster?.some((unit) => unit.id === 'zhuge-liang' && unit.selected && unit.required) || + secondNorthernSortieReadyState.sortieRoster?.some((unit) => unit.id === 'liu-bei') || + secondNorthernSortieReadyState.sortiePlan?.selectedCount !== 7 || + secondNorthernSortieReadyState.sortiePlan?.recommendedSelectedCount < 7 + ) { + throw new Error(`Expected Tianshui sortie to deploy Zhuge Liang, Wang Ping, Zhao Yun, Huang Quan, Ma Liang, Fa Zheng, and Ma Dai: ${JSON.stringify(secondNorthernSortieReadyState)}`); + } + await page.screenshot({ path: 'dist/verification-fifty-sixth-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-fifty-sixth-story.png', fullPage: true }); + + for (let i = 0; i < 50; i += 1) { + const enteredFiftySixthBattle = await page.evaluate(() => { + const state = window.__HEROS_DEBUG__?.battle(); + return state?.scene === 'BattleScene' && state?.battleId === 'fifty-sixth-battle-tianshui-advance'; + }); + if (enteredFiftySixthBattle) { + 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-sixth-battle-tianshui-advance' && state?.battleOutcome === null && state?.phase === 'idle'; + }); + await page.screenshot({ path: 'dist/verification-fifty-sixth-battle.png', fullPage: true }); + + const fiftySixthBattleState = await page.evaluate(() => window.__HEROS_DEBUG__?.battle()); + const fiftySixthEnemies = fiftySixthBattleState.units.filter((unit) => unit.faction === 'enemy'); + const fiftySixthAllies = fiftySixthBattleState.units.filter((unit) => unit.faction === 'ally'); + const fiftySixthEnemyBehaviors = new Set(fiftySixthEnemies.map((unit) => unit.ai)); + if ( + fiftySixthBattleState.camera?.mapWidth !== 112 || + fiftySixthBattleState.camera?.mapHeight !== 94 || + fiftySixthBattleState.victoryConditionLabel !== '천수 길목 돌파와 곽회 방어선 격퇴' || + !fiftySixthBattleState.objectives?.some((objective) => objective.id === 'repel-guo-huai' && objective.label === '곽회 방어선 격퇴') || + !fiftySixthBattleState.objectives?.some((objective) => objective.id === 'test-jiang-wei' && objective.label === '강유의 전술 확인') || + !fiftySixthBattleState.objectives?.some((objective) => objective.id === 'jieting-forts' && objective.label === '가정 고개 요새 압박') || + fiftySixthEnemies.length < 42 || + !fiftySixthEnemyBehaviors.has('aggressive') || + !fiftySixthEnemyBehaviors.has('guard') || + !fiftySixthEnemyBehaviors.has('hold') || + !fiftySixthEnemies.some((unit) => unit.id === 'northern-second-leader-guo-huai') || + !fiftySixthEnemies.some((unit) => unit.id === 'northern-second-officer-jiang-wei') || + !fiftySixthEnemies.some((unit) => unit.id === 'northern-second-officer-xiahou-mao') || + !fiftySixthAllies.some((unit) => unit.id === 'zhuge-liang') || + !fiftySixthAllies.some((unit) => unit.id === 'wang-ping') || + !fiftySixthAllies.some((unit) => unit.id === 'zhao-yun') || + !fiftySixthAllies.some((unit) => unit.id === 'huang-quan') || + !fiftySixthAllies.some((unit) => unit.id === 'ma-liang') || + !fiftySixthAllies.some((unit) => unit.id === 'fa-zheng') || + !fiftySixthAllies.some((unit) => unit.id === 'ma-dai') || + fiftySixthAllies.some((unit) => unit.id === 'liu-bei') + ) { + throw new Error(`Expected fifty-sixth battle to use the Tianshui map, Jiang Wei hook objectives, selected recruited officers, and mixed Wei AI: ${JSON.stringify(fiftySixthBattleState)}`); + } + + 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 fiftySixthCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); + if ( + fiftySixthCampState?.campBattleId !== 'fifty-sixth-battle-tianshui-advance' || + fiftySixthCampState.campaign?.step !== 'fifty-sixth-camp' || + fiftySixthCampState.campTitle !== '천수 진군로 후 군영' || + fiftySixthCampState.availableDialogueIds?.length !== 3 || + !fiftySixthCampState.availableDialogueIds.every((id) => id.startsWith('northern-second-')) || + fiftySixthCampState.availableVisitIds?.length !== 2 || + !fiftySixthCampState.availableVisitIds.every((id) => id.startsWith('northern-second-')) || + fiftySixthCampState.rosterCollection?.total < 21 + ) { + throw new Error(`Expected Tianshui advance camp to expose Jiang Wei/Jieting dialogue and visit sets while preserving the deployable officer roster: ${JSON.stringify(fiftySixthCampState)}`); + } + await page.screenshot({ path: 'dist/verification-fifty-sixth-camp.png', fullPage: true }); + + await page.mouse.click(966, 38); + await page.waitForTimeout(180); + const postFiftySixthProgressState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); + if ( + postFiftySixthProgressState?.activeTab !== 'progress' || + postFiftySixthProgressState.campaignProgress?.completedKnown !== 56 || + postFiftySixthProgressState.campaignProgress?.totalKnown !== 56 || + postFiftySixthProgressState.campaignProgress?.activeChapter?.id !== 'northern-campaign' || + postFiftySixthProgressState.campaignProgress?.latestBattleTitle !== '천수 진군로' + ) { + throw new Error(`Expected post-fifty-sixth progress tab to complete the Tianshui advance: ${JSON.stringify(postFiftySixthProgressState?.campaignProgress)}`); + } + await page.screenshot({ path: 'dist/verification-post-fifty-sixth-progress.png', fullPage: true }); + await page.evaluate(() => { const game = window.__HEROS_GAME__; game?.scene.stop('CampScene'); @@ -7854,11 +8005,11 @@ try { activeScenes: window.__HEROS_DEBUG__?.activeScenes() ?? [], camp: window.__HEROS_DEBUG__?.camp?.() ?? null })); - if (!titleContinueState.activeScenes.includes('CampScene') || titleContinueState.camp?.campaign?.step !== 'fifty-fifth-camp') { - throw new Error(`Expected title continue to reopen the first northern expedition camp: ${JSON.stringify(titleContinueState)}`); + if (!titleContinueState.activeScenes.includes('CampScene') || titleContinueState.camp?.campaign?.step !== 'fifty-sixth-camp') { + throw new Error(`Expected title continue to reopen the Tianshui advance camp: ${JSON.stringify(titleContinueState)}`); } - console.log(`Verified title-to-first-northern-expedition 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, final capture, northern campaign preparation, and Qishan road first northern expedition state at ${targetUrl}`); + console.log(`Verified title-to-Tianshui-advance 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, final capture, northern campaign preparation, Qishan road first northern expedition, and Tianshui advance state at ${targetUrl}`); } finally { await browser?.close(); if (serverProcess && !serverProcess.killed) { diff --git a/src/assets/images/battle/fifty-sixth-battle-map.svg b/src/assets/images/battle/fifty-sixth-battle-map.svg new file mode 100644 index 0000000..1fcff2b --- /dev/null +++ b/src/assets/images/battle/fifty-sixth-battle-map.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/game/data/battleMapAssets.ts b/src/game/data/battleMapAssets.ts index a29d529..8ca8c15 100644 --- a/src/game/data/battleMapAssets.ts +++ b/src/game/data/battleMapAssets.ts @@ -7,6 +7,7 @@ import fiftySecondBattleMapUrl from '../../assets/images/battle/fifty-second-bat import fiftyThirdBattleMapUrl from '../../assets/images/battle/fifty-third-battle-map.svg'; import fiftyFourthBattleMapUrl from '../../assets/images/battle/fifty-fourth-battle-map.svg'; import fiftyFifthBattleMapUrl from '../../assets/images/battle/fifty-fifth-battle-map.svg'; +import fiftySixthBattleMapUrl from '../../assets/images/battle/fifty-sixth-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'; @@ -109,5 +110,6 @@ export const battleMapAssets: Record = { 'battle-map-fifty-second': fiftySecondBattleMapUrl, 'battle-map-fifty-third': fiftyThirdBattleMapUrl, 'battle-map-fifty-fourth': fiftyFourthBattleMapUrl, - 'battle-map-fifty-fifth': fiftyFifthBattleMapUrl + 'battle-map-fifty-fifth': fiftyFifthBattleMapUrl, + 'battle-map-fifty-sixth': fiftySixthBattleMapUrl }; diff --git a/src/game/data/battles.ts b/src/game/data/battles.ts index 221a337..9258ad6 100644 --- a/src/game/data/battles.ts +++ b/src/game/data/battles.ts @@ -23,6 +23,10 @@ import { fiftyFifthBattleMap, fiftyFifthBattleUnits, fiftyFifthBattleVictoryPages, + fiftySixthBattleBonds, + fiftySixthBattleMap, + fiftySixthBattleUnits, + fiftySixthBattleVictoryPages, firstBattleBonds, fifteenthBattleBonds, fifteenthBattleMap, @@ -280,7 +284,8 @@ export type BattleScenarioId = | 'fifty-second-battle-meng-huo-fifth-capture' | 'fifty-third-battle-meng-huo-sixth-capture' | 'fifty-fourth-battle-meng-huo-final-capture' - | 'fifty-fifth-battle-northern-qishan-road'; + | 'fifty-fifth-battle-northern-qishan-road' + | 'fifty-sixth-battle-tianshui-advance'; export type BattleObjectiveKind = 'defeat-leader' | 'pacify-leader' | 'keep-unit-alive' | 'secure-terrain' | 'quick-victory'; @@ -3607,6 +3612,80 @@ export const fiftyFifthBattleScenario: BattleScenarioDefinition = { nextCampScene: 'CampScene' }; +export const fiftySixthBattleScenario: BattleScenarioDefinition = { + id: 'fifty-sixth-battle-tianshui-advance', + title: '천수 진군로', + victoryConditionLabel: '천수 길목 돌파와 곽회 방어선 격퇴', + defeatConditionLabel: '제갈량 퇴각', + openingObjectiveLines: [ + '기산 출진로를 열었지만 위군은 천수와 가정 사이에 새 방어선을 폈습니다. 곽회가 본영을 잡고, 강유라는 젊은 장수가 산길의 허점을 살피고 있습니다.', + '이번 전투는 적장을 베는 것만으로 끝나지 않습니다. 천수 길목, 물길, 마을, 가정으로 이어지는 고개를 함께 보아야 북벌이 끊기지 않습니다.', + '곽회를 물러나게 하고 강유의 움직임을 확인하십시오. 다음 전선에서 그 이름이 촉한에 어떤 의미가 될지 여기서 처음 드러납니다.' + ], + map: fiftySixthBattleMap, + units: fiftySixthBattleUnits, + bonds: fiftySixthBattleBonds, + mapTextureKey: 'battle-map-fifty-sixth', + leaderUnitId: 'northern-second-leader-guo-huai', + quickVictoryTurnLimit: 112, + baseVictoryGold: 15480, + objectives: [ + { + id: 'repel-guo-huai', + kind: 'defeat-leader', + label: '곽회 방어선 격퇴', + rewardGold: 10380, + unitId: 'northern-second-leader-guo-huai' + }, + { + id: 'test-jiang-wei', + kind: 'defeat-leader', + label: '강유의 전술 확인', + rewardGold: 4800, + unitId: 'northern-second-officer-jiang-wei' + }, + { + id: 'zhuge-liang', + kind: 'keep-unit-alive', + label: '제갈량 생존', + rewardGold: 2720, + unitId: 'zhuge-liang' + }, + { + id: 'tianshui-road', + kind: 'secure-terrain', + label: '천수 진군로 확보', + rewardGold: 5400, + terrain: 'road' + }, + { + id: 'jieting-forts', + kind: 'secure-terrain', + label: '가정 고개 요새 압박', + rewardGold: 5060, + terrain: 'fort' + }, + { + id: 'waterline-villages', + kind: 'secure-terrain', + label: '물길 마을 안정', + rewardGold: 4740, + terrain: 'village' + }, + { + id: 'quick', + kind: 'quick-victory', + label: '112턴 이내 천수로 확보', + rewardGold: 3480, + maxTurn: 112 + } + ], + defeatConditions: [{ kind: 'unit-defeated', unitId: 'zhuge-liang' }], + itemRewards: ['콩 90', '상처약 74', '탁주 36', '천수 진군 장부 1', '가정 지형 표식 1'], + victoryPages: fiftySixthBattleVictoryPages, + nextCampScene: 'CampScene' +}; + export const defaultBattleScenarioId: BattleScenarioId = firstBattleScenario.id; export const battleScenarios: Record = { @@ -3664,7 +3743,8 @@ export const battleScenarios: Record 'fifty-second-battle-meng-huo-fifth-capture': fiftySecondBattleScenario, 'fifty-third-battle-meng-huo-sixth-capture': fiftyThirdBattleScenario, 'fifty-fourth-battle-meng-huo-final-capture': fiftyFourthBattleScenario, - 'fifty-fifth-battle-northern-qishan-road': fiftyFifthBattleScenario + 'fifty-fifth-battle-northern-qishan-road': fiftyFifthBattleScenario, + 'fifty-sixth-battle-tianshui-advance': fiftySixthBattleScenario }; export const defaultBattleScenario = battleScenarios[defaultBattleScenarioId]; diff --git a/src/game/data/campaignFlow.ts b/src/game/data/campaignFlow.ts index 8f7fa9e..09bd060 100644 --- a/src/game/data/campaignFlow.ts +++ b/src/game/data/campaignFlow.ts @@ -10,6 +10,7 @@ import { fiftyThirdBattleScenario, fiftyFourthBattleScenario, fiftyFifthBattleScenario, + fiftySixthBattleScenario, fortiethBattleScenario, fortyFirstBattleScenario, fortySecondBattleScenario, @@ -77,6 +78,8 @@ import { fiftyFourthBattleVictoryPages, fiftyFifthBattleIntroPages, fiftyFifthBattleVictoryPages, + fiftySixthBattleIntroPages, + fiftySixthBattleVictoryPages, fortiethBattleIntroPages, fortiethBattleVictoryPages, fortyFirstBattleIntroPages, @@ -813,13 +816,24 @@ const sortieFlows: Record = { }, [fiftyFifthBattleScenario.id]: { afterBattleId: fiftyFifthBattleScenario.id, - eyebrow: '다음 장 준비', - title: '기산 출진로 이후', + eyebrow: '다음 전장', + title: fiftySixthBattleScenario.title, description: - '기산 산길과 한중 보급 거점을 확보했습니다. 다음 북벌 전장은 더 넓은 위군 방어선과 천수 방면의 움직임으로 이어질 예정입니다.', - rewardHint: '다음 목표: 천수 방면 진군 준비 / 북벌 장기전 설계', - pages: fiftyFifthBattleVictoryPages, - unavailableNotice: '천수 방면의 다음 북벌 전투는 이어지는 작업에서 확장합니다. 현재는 기산 출진로 후 군영에서 무장 대화와 보급 정비를 진행할 수 있습니다.' + '기산 산길과 한중 보급 거점을 확보했습니다. 이제 천수와 가정 사이에서 곽회와 강유의 대응을 확인하며 북벌의 전선을 넓혀야 합니다.', + rewardHint: `예상 보상: ${fiftySixthBattleScenario.title} 개방 / 천수 진군 장부`, + nextBattleId: fiftySixthBattleScenario.id, + campaignStep: 'fifty-sixth-battle', + pages: [...fiftyFifthBattleVictoryPages, ...fiftySixthBattleIntroPages] + }, + [fiftySixthBattleScenario.id]: { + afterBattleId: fiftySixthBattleScenario.id, + eyebrow: '다음 장 준비', + title: '천수 진군로 이후', + description: + '천수 방면의 방어선을 밀어냈고 강유의 이름이 제갈량의 장부에 남았습니다. 다음 북벌 전장은 가정 방면의 배치 판단과 더 큰 위군 반격으로 이어질 예정입니다.', + rewardHint: '다음 목표: 가정 방면 배치 준비 / 강유 접촉 단서 정리', + pages: fiftySixthBattleVictoryPages, + unavailableNotice: '가정 방면의 다음 북벌 전투는 이어지는 작업에서 확장합니다. 현재는 천수 진군로 후 군영에서 무장 대화와 보급 정비를 진행할 수 있습니다.' }, 'northern-campaign-prep-camp-legacy': { afterBattleId: fiftyFourthBattleScenario.id, diff --git a/src/game/data/scenario.ts b/src/game/data/scenario.ts index 9de67eb..6a025f6 100644 --- a/src/game/data/scenario.ts +++ b/src/game/data/scenario.ts @@ -3589,6 +3589,59 @@ export const fiftyFifthBattleVictoryPages: StoryPage[] = [ } ]; +export const fiftySixthBattleIntroPages: StoryPage[] = [ + { + id: 'fifty-sixth-tianshui-rumor', + bgm: 'story-dark', + chapter: '천수 방면', + background: 'story-militia', + speaker: '마량', + text: '기산 길목을 확보하자 천수와 가정 사이의 성들이 급히 봉화를 올렸습니다. 위군은 조진의 전초대가 물러난 틈을 곽회에게 맡기고, 젊은 장수 강유가 산길의 허점을 살피고 있다고 합니다.' + }, + { + id: 'fifty-sixth-qishan-council', + bgm: 'battle-prep', + chapter: '진군로 재편', + background: 'story-sortie', + speaker: '제갈량', + text: '북벌은 빠르게 밀어붙이는 것보다 끊기지 않는 것이 중요합니다. 조운은 우회로를 보고, 황권과 마량은 마을과 창고를 묶고, 왕평은 가정으로 이어지는 고개를 살펴 주십시오.' + }, + { + id: 'fifty-sixth-jiang-wei-shadow', + bgm: 'battle-prep', + chapter: '젊은 장수의 그림자', + background: 'story-three-heroes', + speaker: '조운', + text: '강유라는 장수는 평범한 수비장이 아닙니다. 정면에서만 치면 곽회의 방어선에 묶이고, 너무 깊이 들어가면 가정 방면의 퇴로가 끊깁니다. 이번 싸움은 적장 하나보다 전선 전체를 읽는 싸움이 되겠습니다.' + } +]; + +export const fiftySixthBattleVictoryPages: StoryPage[] = [ + { + id: 'fifty-sixth-victory-tianshui-road', + bgm: 'militia-theme', + chapter: '천수 진군로 확보', + background: 'story-sortie', + text: '곽회의 방어선은 물러났고, 천수로 향하는 길에는 촉한군의 표식이 세워졌습니다. 그러나 강유의 움직임은 끝내 가볍지 않았고, 위군은 가정 방면으로 더 넓은 방어선을 펼치기 시작했습니다.' + }, + { + id: 'fifty-sixth-victory-jiang-wei', + bgm: 'story-dark', + chapter: '강유의 이름', + background: 'story-militia', + speaker: '제갈량', + text: '강유는 아직 우리의 사람이 아니지만, 그 재능은 전장 위에 분명히 드러났습니다. 억지로 꺾기보다 길을 남겨 두어야 합니다. 다음 전선에서는 사람을 얻는 일과 땅을 얻는 일이 함께 시험받을 것입니다.' + }, + { + id: 'fifty-sixth-next-jieting', + bgm: 'story-dark', + chapter: '가정의 불안', + background: 'story-three-heroes', + speaker: '왕평', + text: '가정으로 이어지는 고개는 생각보다 좁고 물길도 멀리 있습니다. 진영을 어디에 세우느냐에 따라 북벌 전체가 흔들릴 수 있습니다. 다음 전장은 승리보다 배치의 판단이 더 무겁게 다가올 것입니다.' + } +]; + export const firstBattleMap: BattleMap = { width: 20, height: 18, @@ -4064,6 +4117,12 @@ export const fiftyFifthBattleMap: BattleMap = { terrain: createFiftyFifthBattleTerrain() }; +export const fiftySixthBattleMap: BattleMap = { + width: 112, + height: 94, + terrain: createFiftySixthBattleTerrain() +}; + export const firstBattleUnits: UnitData[] = [ { id: 'liu-bei', @@ -16806,6 +16865,95 @@ export const fiftyFifthBattleUnits: UnitData[] = [ createThirtyFirstEnemyUnit('northern-first-leader-cao-zhen', '조진', '위군 대장', 'rebelLeader', 178, 1240, 296, 102, 24) ]; +const fiftySixthBattleAllyPositions: Record = { + 'guan-yu': { x: 16, y: 80 }, + 'zhang-fei': { x: 22, y: 82 }, + 'jian-yong': { x: 10, y: 86 }, + 'mi-zhu': { x: 14, y: 88 }, + 'sun-qian': { x: 19, y: 88 }, + 'zhao-yun': { x: 29, y: 76 }, + 'zhuge-liang': { x: 17, y: 75 }, + 'ma-liang': { x: 21, y: 72 }, + 'yi-ji': { x: 9, y: 90 }, + 'gong-zhi': { x: 25, y: 88 }, + 'huang-zhong': { x: 34, y: 75 }, + 'wei-yan': { x: 40, y: 79 }, + 'pang-tong': { x: 14, y: 70 }, + 'fa-zheng': { x: 25, y: 70 }, + 'wu-yi': { x: 41, y: 86 }, + 'yan-yan': { x: 45, y: 79 }, + 'li-yan': { x: 32, y: 69 }, + 'huang-quan': { x: 11, y: 74 }, + 'ma-chao': { x: 48, y: 75 }, + 'ma-dai': { x: 52, y: 82 }, + 'wang-ping': { x: 36, y: 70 } +}; + +export const fiftySixthBattleUnits: 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, fiftySixthBattleAllyPositions[unit.id] ?? { x: unit.x, y: unit.y })), + createThirtyFirstEnemyUnit('northern-second-scout-a', '천수 척후병', '서쪽 사면 정찰', 'bandit', 176, 824, 238, 48, 72), + createThirtyFirstEnemyUnit('northern-second-scout-b', '천수 척후병', '하곡 정찰', 'bandit', 176, 824, 238, 57, 69), + createThirtyFirstEnemyUnit('northern-second-scout-c', '천수 척후병', '중앙 마을 감시', 'bandit', 177, 836, 240, 68, 62), + createThirtyFirstEnemyUnit('northern-second-scout-d', '천수 척후병', '동쪽 물목 감시', 'bandit', 177, 836, 240, 79, 55), + createThirtyFirstEnemyUnit('northern-second-scout-e', '천수 척후병', '가정 고개 정찰', 'bandit', 178, 848, 242, 91, 48), + createThirtyFirstEnemyUnit('northern-second-scout-f', '천수 척후병', '곽회 본영 정찰', 'bandit', 178, 848, 242, 101, 36), + createThirtyFirstEnemyUnit('northern-second-infantry-a', '위군 천수 보병', '서쪽 보병', 'yellowTurban', 177, 1140, 282, 52, 66), + createThirtyFirstEnemyUnit('northern-second-infantry-b', '위군 천수 보병', '하곡 보병', 'yellowTurban', 177, 1140, 282, 60, 62), + createThirtyFirstEnemyUnit('northern-second-infantry-c', '위군 천수 보병', '마을 길목 보병', 'yellowTurban', 178, 1160, 286, 70, 57), + createThirtyFirstEnemyUnit('northern-second-infantry-d', '위군 천수 보병', '중앙 고개 보병', 'yellowTurban', 178, 1160, 286, 78, 50), + createThirtyFirstEnemyUnit('northern-second-infantry-e', '위군 천수 보병', '동쪽 보병', 'yellowTurban', 179, 1180, 290, 88, 45), + createThirtyFirstEnemyUnit('northern-second-infantry-f', '위군 천수 보병', '가정로 보병', 'yellowTurban', 179, 1180, 290, 96, 38), + createThirtyFirstEnemyUnit('northern-second-infantry-g', '위군 천수 보병', '곽회 본영 보병', 'yellowTurban', 180, 1200, 294, 104, 30), + createThirtyFirstEnemyUnit('northern-second-infantry-h', '위군 천수 보병', '보급 차단 보병', 'yellowTurban', 180, 1200, 294, 92, 70), + createThirtyFirstEnemyUnit('northern-second-archer-a', '위군 천수 궁병', '서쪽 고지 궁병', 'archer', 176, 860, 250, 55, 57), + createThirtyFirstEnemyUnit('northern-second-archer-b', '위군 천수 궁병', '하곡 궁병', 'archer', 177, 872, 252, 64, 53), + createThirtyFirstEnemyUnit('northern-second-archer-c', '위군 천수 궁병', '중앙 마을 궁병', 'archer', 177, 872, 252, 73, 48), + createThirtyFirstEnemyUnit('northern-second-archer-d', '위군 천수 궁병', '물목 궁병', 'archer', 178, 884, 254, 84, 41), + createThirtyFirstEnemyUnit('northern-second-archer-e', '위군 천수 궁병', '가정 고개 궁병', 'archer', 178, 884, 254, 94, 34), + createThirtyFirstEnemyUnit('northern-second-archer-f', '위군 천수 궁병', '곽회 본영 궁병', 'archer', 179, 896, 256, 105, 24), + createThirtyFirstEnemyUnit('northern-second-cavalry-a', '위군 역습 기병', '서쪽 기병', 'cavalry', 177, 1148, 288, 47, 61), + createThirtyFirstEnemyUnit('northern-second-cavalry-b', '위군 역습 기병', '하곡 기병', 'cavalry', 177, 1148, 288, 59, 58), + createThirtyFirstEnemyUnit('northern-second-cavalry-c', '위군 역습 기병', '중앙 역습 기병', 'cavalry', 178, 1170, 292, 72, 51), + createThirtyFirstEnemyUnit('northern-second-cavalry-d', '위군 역습 기병', '물목 우회 기병', 'cavalry', 179, 1192, 296, 84, 45), + createThirtyFirstEnemyUnit('northern-second-cavalry-e', '위군 역습 기병', '가정로 기병', 'cavalry', 179, 1192, 296, 96, 39), + createThirtyFirstEnemyUnit('northern-second-cavalry-f', '위군 역습 기병', '곽회 친위 기병', 'cavalry', 180, 1214, 300, 106, 32), + createThirtyFirstEnemyUnit('northern-second-jieting-guard-a', '가정 고개 수비대', '서쪽 고개 수비', 'yellowTurban', 178, 1180, 290, 43, 49), + createThirtyFirstEnemyUnit('northern-second-jieting-guard-b', '가정 고개 수비대', '중앙 고개 수비', 'yellowTurban', 178, 1180, 290, 55, 43), + createThirtyFirstEnemyUnit('northern-second-jieting-guard-c', '가정 고개 수비대', '물목 고개 수비', 'yellowTurban', 179, 1200, 294, 69, 36), + createThirtyFirstEnemyUnit('northern-second-jieting-guard-d', '가정 고개 수비대', '가정 길목 수비', 'yellowTurban', 179, 1200, 294, 82, 29), + createThirtyFirstEnemyUnit('northern-second-jieting-guard-e', '가정 고개 수비대', '곽회 영채 수비', 'yellowTurban', 180, 1220, 298, 98, 20), + createThirtyFirstEnemyUnit('northern-second-jieting-guard-f', '가정 고개 수비대', '북쪽 절벽 수비', 'yellowTurban', 180, 1220, 298, 106, 18), + createThirtyFirstEnemyUnit('northern-second-strategist-a', '위군 천수 군리', '마을 회유 군리', 'strategist', 178, 928, 262, 67, 46), + createThirtyFirstEnemyUnit('northern-second-strategist-b', '위군 천수 군리', '물목 기록 군리', 'strategist', 179, 940, 264, 82, 38), + createThirtyFirstEnemyUnit('northern-second-strategist-c', '위군 천수 군리', '가정 보급 군리', 'strategist', 179, 940, 264, 94, 52), + createThirtyFirstEnemyUnit('northern-second-strategist-d', '위군 천수 군리', '곽회 본영 군리', 'strategist', 180, 952, 266, 104, 26), + createThirtyFirstEnemyUnit('northern-second-officer-xiahou-mao', '하후무', '천수 방면 장수', 'rebelLeader', 180, 1220, 300, 74, 58), + createThirtyFirstEnemyUnit('northern-second-officer-fei-yao', '비요', '가정 지원 장수', 'rebelLeader', 180, 1220, 300, 95, 58), + createThirtyFirstEnemyUnit('northern-second-officer-jiang-wei', '강유', '천수 젊은 장수', 'rebelLeader', 181, 1260, 306, 86, 43), + createThirtyFirstEnemyUnit('northern-second-officer-ma-zun', '마준', '천수 태수', 'rebelLeader', 180, 1220, 300, 101, 64), + createThirtyFirstEnemyUnit('northern-second-officer-supply', '위군 둔전 장교', '물목 보급 장수', 'rebelLeader', 179, 1200, 296, 75, 70), + createThirtyFirstEnemyUnit('northern-second-leader-guo-huai', '곽회', '위군 북방 지휘관', 'rebelLeader', 182, 1320, 316, 104, 24) +]; + export const firstBattleBonds: BattleBond[] = [ { id: 'liu-bei__guan-yu', @@ -17897,6 +18045,45 @@ export const fiftyFifthBattleBonds: BattleBond[] = [...fiftyFourthBattleBonds, . cloneBattleBondForScenario ); +export const northernSecondBattleBonds: BattleBond[] = [ + { + id: 'zhuge-liang__wang-ping_northern-second', + unitIds: ['zhuge-liang', 'wang-ping'], + title: '가정의 지형 판단', + level: 56, + exp: 0, + description: '제갈량의 큰 전략과 왕평의 산길 판단이 맞물리면 가정으로 이어지는 고개에서 치명적인 배치 실수를 줄일 수 있습니다.' + }, + { + id: 'zhao-yun__ma-dai_northern-second', + unitIds: ['zhao-yun', 'ma-dai'], + title: '우회로의 기동', + level: 54, + exp: 0, + description: '조운의 안정된 기동과 마대의 서량 정찰 감각이 합쳐지면 천수 방면의 우회 기병을 빠르게 끊을 수 있습니다.' + }, + { + id: 'huang-quan__wang-ping_northern-second', + unitIds: ['huang-quan', 'wang-ping'], + title: '물길과 보급선', + level: 52, + exp: 0, + description: '황권의 보급 장부와 왕평의 지형 판단은 물길이 먼 고개에서 군량과 진영 위치를 동시에 지키는 힘이 됩니다.' + }, + { + id: 'ma-liang__fa-zheng_northern-second', + unitIds: ['ma-liang', 'fa-zheng'], + title: '천수 설득의 글', + level: 48, + exp: 0, + description: '마량의 부드러운 문장과 법정의 날카로운 판단이 더해지면 천수 장수들의 흔들리는 마음을 놓치지 않습니다.' + } +]; + +export const fiftySixthBattleBonds: BattleBond[] = [...fiftyFifthBattleBonds, ...northernSecondBattleBonds].map( + cloneBattleBondForScenario +); + function createEighthBattleTerrain(): TerrainType[][] { return Array.from({ length: 22 }, (_, y) => Array.from({ length: 26 }, (_, x): TerrainType => { @@ -20836,6 +21023,82 @@ function createFiftyFifthBattleTerrain(): TerrainType[][] { ); } +function createFiftySixthBattleTerrain(): TerrainType[][] { + return Array.from({ length: 94 }, (_, y) => + Array.from({ length: 112 }, (_, x): TerrainType => { + if ( + (x >= 0 && x <= 111 && y >= 82) || + (x >= 76 && x <= 81 && y >= 0 && y <= 93) || + (x >= 0 && x <= 30 && y >= 73 && y <= 90) || + (x >= 86 && x <= 107 && y >= 69 && y <= 84) + ) { + return 'river'; + } + if ( + (x >= 8 && x <= 108 && y >= 74 && y <= 80) || + (x >= 29 && x <= 88 && y >= 58 && y <= 66) || + (x >= 70 && x <= 108 && y >= 50 && y <= 58) || + (x >= 44 && x <= 51 && y >= 50 && y <= 82) || + (x >= 86 && x <= 92 && y >= 54 && y <= 90) || + (x >= 54 && x <= 75 && y >= 62 && y <= 69) || + (x >= 63 && x <= 86 && y >= 43 && y <= 52) || + (x >= 90 && x <= 108 && y >= 25 && y <= 35) + ) { + return 'road'; + } + if ( + (x >= 11 && x <= 72 && y >= 55 && y <= 82) || + (x >= 60 && x <= 105 && y >= 47 && y <= 76) || + (x >= 2 && x <= 42 && y >= 61 && y <= 82) || + (x >= 88 && x <= 111 && y >= 53 && y <= 78) + ) { + return 'forest'; + } + if ( + (x >= 0 && x <= 42 && y <= 47) || + (x >= 38 && x <= 80 && y <= 38) || + (x >= 82 && x <= 111 && y <= 48) || + (x >= 96 && x <= 111 && y >= 34 && y <= 65) + ) { + return 'hill'; + } + if ( + (x <= 2 && y <= 82) || + (x >= 109 && y <= 79) || + (x >= 33 && x <= 39 && y >= 0 && y <= 53) || + (x >= 80 && x <= 85 && y >= 0 && y <= 42) || + (x >= 52 && x <= 58 && y >= 74 && y <= 93) + ) { + return 'cliff'; + } + if ( + (x >= 14 && x <= 28 && y >= 70 && y <= 81) || + (x >= 49 && x <= 63 && y >= 58 && y <= 68) || + (x >= 84 && x <= 99 && y >= 50 && y <= 61) || + (x >= 94 && x <= 106 && y >= 30 && y <= 41) + ) { + return 'village'; + } + if ( + (x >= 24 && x <= 43 && y >= 64 && y <= 74) || + (x >= 67 && x <= 84 && y >= 60 && y <= 72) || + (x >= 88 && x <= 106 && y >= 42 && y <= 55) + ) { + return 'camp'; + } + if ( + (x >= 58 && x <= 72 && y >= 36 && y <= 48) || + (x >= 80 && x <= 98 && y >= 33 && y <= 47) || + (x >= 94 && x <= 108 && y >= 45 && y <= 58) || + (x >= 96 && x <= 109 && y >= 18 && y <= 31) + ) { + 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 66c5399..4de7479 100644 --- a/src/game/scenes/BattleScene.ts +++ b/src/game/scenes/BattleScene.ts @@ -6363,22 +6363,31 @@ export class BattleScene extends Phaser.Scene { private enemyBehavior(enemy: UnitData): EnemyAiBehavior { if ( enemy.id.startsWith('northern-first-scout') || + enemy.id.startsWith('northern-second-scout') || enemy.id.startsWith('northern-first-cavalry') || - enemy.id === 'northern-first-officer-zhang-he' + enemy.id.startsWith('northern-second-cavalry') || + enemy.id === 'northern-first-officer-zhang-he' || + enemy.id === 'northern-second-officer-jiang-wei' ) { return 'aggressive'; } if ( enemy.id.startsWith('northern-first-archer') || + enemy.id.startsWith('northern-second-archer') || enemy.id.startsWith('northern-first-strategist') || - enemy.id === 'northern-first-leader-cao-zhen' + enemy.id.startsWith('northern-second-strategist') || + enemy.id === 'northern-first-leader-cao-zhen' || + enemy.id === 'northern-second-leader-guo-huai' ) { return 'hold'; } if ( enemy.id.startsWith('northern-first-infantry') || + enemy.id.startsWith('northern-second-infantry') || enemy.id.startsWith('northern-first-ridge-guard') || - enemy.id.startsWith('northern-first-officer') + enemy.id.startsWith('northern-second-jieting-guard') || + enemy.id.startsWith('northern-first-officer') || + enemy.id.startsWith('northern-second-officer') ) { return 'guard'; } @@ -7859,13 +7868,23 @@ export class BattleScene extends Phaser.Scene { } private unitTextureKey(unit: UnitData) { - if (unit.id.startsWith('northern-first-cavalry')) { + if (unit.id.startsWith('northern-first-cavalry') || unit.id.startsWith('northern-second-cavalry')) { return 'unit-rebel-cavalry'; } - if (unit.id.startsWith('northern-first-archer') || unit.id.startsWith('northern-first-strategist')) { + if ( + unit.id.startsWith('northern-first-archer') || + unit.id.startsWith('northern-second-archer') || + unit.id.startsWith('northern-first-strategist') || + unit.id.startsWith('northern-second-strategist') + ) { return 'unit-rebel-archer'; } - if (unit.id.startsWith('northern-first-officer') || unit.id === 'northern-first-leader-cao-zhen') { + if ( + unit.id.startsWith('northern-first-officer') || + unit.id.startsWith('northern-second-officer') || + unit.id === 'northern-first-leader-cao-zhen' || + unit.id === 'northern-second-leader-guo-huai' + ) { return 'unit-rebel-leader'; } return unitTexture[unit.id] ?? unitTextureByClass[unit.classKey] ?? 'unit-rebel'; diff --git a/src/game/scenes/CampScene.ts b/src/game/scenes/CampScene.ts index 734d764..6f84501 100644 --- a/src/game/scenes/CampScene.ts +++ b/src/game/scenes/CampScene.ts @@ -50,6 +50,7 @@ import { mengHuoSecondCaptureBonds, nanzhongRecoveryBonds, northernFirstBattleBonds, + northernSecondBattleBonds, wulingRecruitBonds, wulingRecruitUnits, xuzhouRecruitBonds, @@ -432,8 +433,8 @@ const campaignTimelineChapters: CampaignTimelineChapter[] = [ title: '북벌 준비', period: '출사표를 향해', description: '남중 평정으로 뒤를 안정시킨 뒤, 한중 창고와 출전 무장 편성을 정비해 첫 북벌을 준비하는 장입니다.', - battleIds: ['fifty-fifth-battle-northern-qishan-road'], - nextHints: ['한중 창고 정비', '출전 무장 재편', '기산 출진로', '장기전 보급'] + battleIds: ['fifty-fifth-battle-northern-qishan-road', 'fifty-sixth-battle-tianshui-advance'], + nextHints: ['한중 창고 정비', '출전 무장 재편', '기산 출진로', '천수·가정 방면'] } ]; @@ -492,7 +493,8 @@ const campBattleIds = { fiftySecond: 'fifty-second-battle-meng-huo-fifth-capture', fiftyThird: 'fifty-third-battle-meng-huo-sixth-capture', fiftyFourth: 'fifty-fourth-battle-meng-huo-final-capture', - fiftyFifth: 'fifty-fifth-battle-northern-qishan-road' + fiftyFifth: 'fifty-fifth-battle-northern-qishan-road', + fiftySixth: 'fifty-sixth-battle-tianshui-advance' } as const; const defaultRequiredSortieUnitIds = ['liu-bei']; @@ -1166,6 +1168,21 @@ const sortieRulesByBattleId: Partial