From a3f56bb787d1d56af48fa2900bbe236855cc91b6 Mon Sep 17 00:00:00 2001 From: Wickedness Date: Wed, 24 Jun 2026 03:41:36 +0900 Subject: [PATCH] Add Jieting sortie and Jiang Wei recruitment flow --- docs/roadmap.md | 6 +- scripts/verify-flow.mjs | 253 +++++++++++--- .../battle/fifty-seventh-battle-map.svg | 85 +++++ src/game/data/battleMapAssets.ts | 2 + src/game/data/battles.ts | 94 +++++- src/game/data/campaignFlow.ts | 26 +- src/game/data/scenario.ts | 316 ++++++++++++++++++ src/game/scenes/BattleScene.ts | 29 +- src/game/scenes/CampScene.ts | 183 +++++++++- src/game/scenes/TitleScene.ts | 7 + src/game/state/campaignState.ts | 5 +- 11 files changed, 934 insertions(+), 72 deletions(-) create mode 100644 src/assets/images/battle/fifty-seventh-battle-map.svg diff --git a/docs/roadmap.md b/docs/roadmap.md index ed43be9..64a8553 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -84,15 +84,17 @@ Build a small complete tactical RPG loop that can grow into a longer Romance of - 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 +- Fifty-seventh battle Jieting placement crisis, where Zhuge Liang leads a seven-officer sortie across a 114x96 battlefield, Wang Ping's waterline judgment becomes a survival objective, Zhang He pressures the ridge, and Jiang Wei shifts from rival hook into a recruitable Shu-Han officer after victory +- Post-Jieting camp events with Zhuge Liang, Wang Ping, Ma Liang, Jiang Wei, Zhao Yun, and Ma Dai focusing on waterline doctrine, Jiang Wei's first role, cavalry retreat discipline, and the harder sortie choices created by one more strong officer joining the roster - 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, 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 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 +- Flow verification script from title through the Jieting placement crisis and post-Jieting camp with Jiang Wei joining, 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 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 +1. Build the next northern-front step after Jiang Wei joins, making his first selectable sortie compete with Zhao Yun, Ma Dai, Wang Ping, and the strategists instead of simply increasing raw party strength 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 3530565..4563c6e 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 !== 56 || + progressTabState.campaignProgress?.totalKnown !== 57 || 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 !== 56 || + postBowangProgressState.campaignProgress?.totalKnown !== 57 || 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 !== 56 || + postChangbanProgressState.campaignProgress?.totalKnown !== 57 || 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 !== 56 || + postJiangdongProgressState.campaignProgress?.totalKnown !== 57 || 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 !== 56 || + postRedCliffsProgressState.campaignProgress?.totalKnown !== 57 || 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 !== 56 || + postFireAttackProgressState.campaignProgress?.totalKnown !== 57 || 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 !== 56 || + postJingzhouProgressState.campaignProgress?.totalKnown !== 57 || 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 !== 56 || + postGuiyangProgressState.campaignProgress?.totalKnown !== 57 || 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 !== 56 || + postWulingProgressState.campaignProgress?.totalKnown !== 57 || 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 !== 56 || + postChangshaProgressState.campaignProgress?.totalKnown !== 57 || 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 !== 56 || + postYizhouProgressState.campaignProgress?.totalKnown !== 57 || 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 !== 56 || + postFuPassProgressState.campaignProgress?.totalKnown !== 57 || 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 !== 56 || + postLuoProgressState.campaignProgress?.totalKnown !== 57 || 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 !== 56 || + postLuofengProgressState.campaignProgress?.totalKnown !== 57 || 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 !== 56 || + postLuoMainProgressState.campaignProgress?.totalKnown !== 57 || 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 !== 56 || + postMianzhuProgressState.campaignProgress?.totalKnown !== 57 || 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 !== 56 || + postChengduProgressState.campaignProgress?.totalKnown !== 57 || 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 !== 56 || + postJiamengProgressState.campaignProgress?.totalKnown !== 57 || 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 !== 56 || + postYangpingProgressState.campaignProgress?.totalKnown !== 57 || 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 !== 56 || + postDingjunProgressState.campaignProgress?.totalKnown !== 57 || 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 !== 56 || + postHanzhongProgressState.campaignProgress?.totalKnown !== 57 || 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 !== 56 || + postKingCouncilProgressState.campaignProgress?.totalKnown !== 57 || postKingCouncilProgressState.campaignProgress?.latestBattleTitle !== '한중 결전' || postKingCouncilProgressState.campaignProgress?.nextBattleTitle !== '촉한 건국 선포' ) { @@ -4812,7 +4812,7 @@ try { if ( postFoundationProgressState?.activeTab !== 'progress' || postFoundationProgressState.campaignProgress?.completedKnown !== 37 || - postFoundationProgressState.campaignProgress?.totalKnown !== 56 || + postFoundationProgressState.campaignProgress?.totalKnown !== 57 || postFoundationProgressState.campaignProgress?.latestBattleTitle !== '한중 결전' || postFoundationProgressState.campaignProgress?.nextBattleTitle !== '형주 방위 전초전' ) { @@ -4963,7 +4963,7 @@ try { if ( postJingDefenseProgressState?.activeTab !== 'progress' || postJingDefenseProgressState.campaignProgress?.completedKnown !== 38 || - postJingDefenseProgressState.campaignProgress?.totalKnown !== 56 || + postJingDefenseProgressState.campaignProgress?.totalKnown !== 57 || postJingDefenseProgressState.campaignProgress?.latestBattleTitle !== '형주 방위 전초전' || postJingDefenseProgressState.campaignProgress?.nextBattleTitle !== '번성 외곽 압박전' ) { @@ -5116,7 +5116,7 @@ try { if ( postFanCastleProgressState?.activeTab !== 'progress' || postFanCastleProgressState.campaignProgress?.completedKnown !== 39 || - postFanCastleProgressState.campaignProgress?.totalKnown !== 56 || + postFanCastleProgressState.campaignProgress?.totalKnown !== 57 || postFanCastleProgressState.campaignProgress?.latestBattleTitle !== '번성 외곽 압박전' || postFanCastleProgressState.campaignProgress?.nextBattleTitle !== '한수 수공' ) { @@ -5270,7 +5270,7 @@ try { if ( postHanRiverProgressState?.activeTab !== 'progress' || postHanRiverProgressState.campaignProgress?.completedKnown !== 40 || - postHanRiverProgressState.campaignProgress?.totalKnown !== 56 || + postHanRiverProgressState.campaignProgress?.totalKnown !== 57 || postHanRiverProgressState.campaignProgress?.latestBattleTitle !== '한수 수공' || postHanRiverProgressState.campaignProgress?.nextBattleTitle !== '번성 공성전' ) { @@ -5424,7 +5424,7 @@ try { if ( postFanSiegeProgressState?.activeTab !== 'progress' || postFanSiegeProgressState.campaignProgress?.completedKnown !== 41 || - postFanSiegeProgressState.campaignProgress?.totalKnown !== 56 || + postFanSiegeProgressState.campaignProgress?.totalKnown !== 57 || postFanSiegeProgressState.campaignProgress?.latestBattleTitle !== '번성 공성전' || postFanSiegeProgressState.campaignProgress?.nextBattleTitle !== '강릉 나루 경계전' ) { @@ -5586,7 +5586,7 @@ try { if ( postJingRearProgressState?.activeTab !== 'progress' || postJingRearProgressState.campaignProgress?.completedKnown !== 42 || - postJingRearProgressState.campaignProgress?.totalKnown !== 56 || + postJingRearProgressState.campaignProgress?.totalKnown !== 57 || postJingRearProgressState.campaignProgress?.latestBattleTitle !== '강릉 나루 경계전' || postJingRearProgressState.campaignProgress?.nextBattleTitle !== '공안 성문 변고' ) { @@ -5741,7 +5741,7 @@ try { if ( postGonganProgressState?.activeTab !== 'progress' || postGonganProgressState.campaignProgress?.completedKnown !== 43 || - postGonganProgressState.campaignProgress?.totalKnown !== 56 || + postGonganProgressState.campaignProgress?.totalKnown !== 57 || postGonganProgressState.campaignProgress?.latestBattleTitle !== '공안 성문 변고' || postGonganProgressState.campaignProgress?.nextBattleTitle !== '맥성 고립전' ) { @@ -5899,7 +5899,7 @@ try { if ( postMaichengProgressState?.activeTab !== 'progress' || postMaichengProgressState.campaignProgress?.completedKnown !== 44 || - postMaichengProgressState.campaignProgress?.totalKnown !== 56 || + postMaichengProgressState.campaignProgress?.totalKnown !== 57 || postMaichengProgressState.campaignProgress?.latestBattleTitle !== '맥성 고립전' || postMaichengProgressState.campaignProgress?.nextBattleTitle !== '이릉 진격로' ) { @@ -6056,7 +6056,7 @@ try { if ( postYilingProgressState?.activeTab !== 'progress' || postYilingProgressState.campaignProgress?.completedKnown !== 45 || - postYilingProgressState.campaignProgress?.totalKnown !== 56 || + postYilingProgressState.campaignProgress?.totalKnown !== 57 || postYilingProgressState.campaignProgress?.latestBattleTitle !== '이릉 진격로' || postYilingProgressState.campaignProgress?.nextBattleTitle !== '이릉 화공전' ) { @@ -6214,7 +6214,7 @@ try { if ( postYilingFireProgressState?.activeTab !== 'progress' || postYilingFireProgressState.campaignProgress?.completedKnown !== 46 || - postYilingFireProgressState.campaignProgress?.totalKnown !== 56 || + postYilingFireProgressState.campaignProgress?.totalKnown !== 57 || postYilingFireProgressState.campaignProgress?.latestBattleTitle !== '이릉 화공전' || postYilingFireProgressState.campaignProgress?.nextBattleTitle !== '백제성 유탁' ) { @@ -6280,7 +6280,7 @@ try { if ( postBaidiProgressState?.activeTab !== 'progress' || postBaidiProgressState.campaignProgress?.completedKnown !== 46 || - postBaidiProgressState.campaignProgress?.totalKnown !== 56 || + postBaidiProgressState.campaignProgress?.totalKnown !== 57 || postBaidiProgressState.campaignProgress?.latestBattleTitle !== '이릉 화공전' || postBaidiProgressState.campaignProgress?.nextBattleTitle !== '남중 안정로' ) { @@ -6441,7 +6441,7 @@ try { if ( postNanzhongProgressState?.activeTab !== 'progress' || postNanzhongProgressState.campaignProgress?.completedKnown !== 47 || - postNanzhongProgressState.campaignProgress?.totalKnown !== 56 || + postNanzhongProgressState.campaignProgress?.totalKnown !== 57 || postNanzhongProgressState.campaignProgress?.latestBattleTitle !== '남중 안정로' || postNanzhongProgressState.campaignProgress?.nextBattleTitle !== '맹획 본대전' ) { @@ -6603,7 +6603,7 @@ try { if ( postMengHuoProgressState?.activeTab !== 'progress' || postMengHuoProgressState.campaignProgress?.completedKnown !== 48 || - postMengHuoProgressState.campaignProgress?.totalKnown !== 56 || + postMengHuoProgressState.campaignProgress?.totalKnown !== 57 || postMengHuoProgressState.campaignProgress?.latestBattleTitle !== '맹획 본대전' || postMengHuoProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 2차전' ) { @@ -6765,7 +6765,7 @@ try { if ( postSecondCaptureProgressState?.activeTab !== 'progress' || postSecondCaptureProgressState.campaignProgress?.completedKnown !== 49 || - postSecondCaptureProgressState.campaignProgress?.totalKnown !== 56 || + postSecondCaptureProgressState.campaignProgress?.totalKnown !== 57 || postSecondCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 2차전' || postSecondCaptureProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 3차전' ) { @@ -6930,7 +6930,7 @@ try { if ( postThirdCaptureProgressState?.activeTab !== 'progress' || postThirdCaptureProgressState.campaignProgress?.completedKnown !== 50 || - postThirdCaptureProgressState.campaignProgress?.totalKnown !== 56 || + postThirdCaptureProgressState.campaignProgress?.totalKnown !== 57 || postThirdCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 3차전' || postThirdCaptureProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 4차전' ) { @@ -7096,7 +7096,7 @@ try { if ( postFourthCaptureProgressState?.activeTab !== 'progress' || postFourthCaptureProgressState.campaignProgress?.completedKnown !== 51 || - postFourthCaptureProgressState.campaignProgress?.totalKnown !== 56 || + postFourthCaptureProgressState.campaignProgress?.totalKnown !== 57 || postFourthCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 4차전' || postFourthCaptureProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 5차전' ) { @@ -7262,7 +7262,7 @@ try { if ( postFifthCaptureProgressState?.activeTab !== 'progress' || postFifthCaptureProgressState.campaignProgress?.completedKnown !== 52 || - postFifthCaptureProgressState.campaignProgress?.totalKnown !== 56 || + postFifthCaptureProgressState.campaignProgress?.totalKnown !== 57 || postFifthCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 5차전' || postFifthCaptureProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 6차전' ) { @@ -7427,7 +7427,7 @@ try { if ( postSixthCaptureProgressState?.activeTab !== 'progress' || postSixthCaptureProgressState.campaignProgress?.completedKnown !== 53 || - postSixthCaptureProgressState.campaignProgress?.totalKnown !== 56 || + postSixthCaptureProgressState.campaignProgress?.totalKnown !== 57 || postSixthCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 6차전' || postSixthCaptureProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 7차전' ) { @@ -7593,7 +7593,7 @@ try { if ( postFinalCaptureProgressState?.activeTab !== 'progress' || postFinalCaptureProgressState.campaignProgress?.completedKnown !== 54 || - postFinalCaptureProgressState.campaignProgress?.totalKnown !== 56 || + postFinalCaptureProgressState.campaignProgress?.totalKnown !== 57 || 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 !== 56 || + postNorthernPrepProgressState.campaignProgress?.totalKnown !== 57 || postNorthernPrepProgressState.campaignProgress?.activeChapter?.id !== 'northern-campaign' || postNorthernPrepProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 7차전' || postNorthernPrepProgressState.campaignProgress?.nextBattleTitle !== '제1차 북벌 출진로' @@ -7816,7 +7816,7 @@ try { if ( postFiftyFifthProgressState?.activeTab !== 'progress' || postFiftyFifthProgressState.campaignProgress?.completedKnown !== 55 || - postFiftyFifthProgressState.campaignProgress?.totalKnown !== 56 || + postFiftyFifthProgressState.campaignProgress?.totalKnown !== 57 || postFiftyFifthProgressState.campaignProgress?.activeChapter?.id !== 'northern-campaign' || postFiftyFifthProgressState.campaignProgress?.latestBattleTitle !== '제1차 북벌 출진로' || postFiftyFifthProgressState.campaignProgress?.nextBattleTitle !== '천수 진군로' @@ -7966,14 +7966,169 @@ try { if ( postFiftySixthProgressState?.activeTab !== 'progress' || postFiftySixthProgressState.campaignProgress?.completedKnown !== 56 || - postFiftySixthProgressState.campaignProgress?.totalKnown !== 56 || + postFiftySixthProgressState.campaignProgress?.totalKnown !== 57 || postFiftySixthProgressState.campaignProgress?.activeChapter?.id !== 'northern-campaign' || - postFiftySixthProgressState.campaignProgress?.latestBattleTitle !== '천수 진군로' + postFiftySixthProgressState.campaignProgress?.latestBattleTitle !== '천수 진군로' || + postFiftySixthProgressState.campaignProgress?.nextBattleTitle !== '가정 배치 위기' ) { - throw new Error(`Expected post-fifty-sixth progress tab to complete the Tianshui advance: ${JSON.stringify(postFiftySixthProgressState?.campaignProgress)}`); + throw new Error(`Expected post-fifty-sixth progress tab to complete the Tianshui advance and point toward Jieting: ${JSON.stringify(postFiftySixthProgressState?.campaignProgress)}`); } await page.screenshot({ path: 'dist/verification-post-fifty-sixth-progress.png', fullPage: true }); + await page.mouse.click(1120, 38); + await page.waitForTimeout(180); + const jietingSortieState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); + if ( + !jietingSortieState?.sortieVisible || + jietingSortieState.campaign?.step !== 'fifty-sixth-camp' || + jietingSortieState.campTitle !== '천수 진군로 후 군영' || + !String(jietingSortieState.sortiePlan?.objectiveLine ?? '').includes('가정 배치 위기') || + !String(jietingSortieState.sortiePlan?.objectiveLine ?? '').includes('장합') || + jietingSortieState.sortiePlan?.maxCount !== 7 || + jietingSortieState.sortiePlan?.recommendedTotal !== 7 || + !jietingSortieState.sortieRoster?.some((unit) => unit.id === 'zhuge-liang' && unit.selected && unit.required) || + !jietingSortieState.sortieRoster?.some((unit) => unit.id === 'wang-ping' && unit.recommended) || + !jietingSortieState.sortieRoster?.some((unit) => unit.id === 'ma-liang' && unit.recommended) || + !jietingSortieState.sortieRoster?.some((unit) => unit.id === 'huang-quan' && unit.recommended) || + !jietingSortieState.sortieRoster?.some((unit) => unit.id === 'fa-zheng' && unit.recommended) || + !jietingSortieState.sortieRoster?.some((unit) => unit.id === 'zhao-yun' && unit.recommended) || + !jietingSortieState.sortieRoster?.some((unit) => unit.id === 'ma-dai' && unit.recommended) || + jietingSortieState.sortieRoster?.some((unit) => unit.id === 'liu-bei') || + jietingSortieState.sortieRoster?.some((unit) => unit.id === 'jiang-wei') + ) { + throw new Error(`Expected Jieting sortie to require Zhuge Liang, exclude Liu Bei, and make terrain readers central before Jiang Wei joins: ${JSON.stringify(jietingSortieState)}`); + } + assertSortieTacticalRoster(jietingSortieState, [ + '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 jietingPriorityUnits = ['wang-ping', 'ma-liang', 'huang-quan', 'fa-zheng', 'zhao-yun', 'ma-dai']; + for (const unitId of jietingPriorityUnits) { + 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 && !jietingPriorityUnits.includes(unit.id) + ); + if (removable) { + await clickSortieRosterUnit(page, removable.id); + } + await clickSortieRosterUnit(page, unitId); + } + } + + const jietingSortieReadyState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); + if ( + !jietingPriorityUnits.every((unitId) => + jietingSortieReadyState.sortieRoster?.some((unit) => unit.id === unitId && unit.selected) + ) || + !jietingSortieReadyState.sortieRoster?.some((unit) => unit.id === 'zhuge-liang' && unit.selected && unit.required) || + jietingSortieReadyState.sortieRoster?.some((unit) => unit.id === 'liu-bei') || + jietingSortieReadyState.sortieRoster?.some((unit) => unit.id === 'jiang-wei') || + jietingSortieReadyState.sortiePlan?.selectedCount !== 7 || + jietingSortieReadyState.sortiePlan?.recommendedSelectedCount < 7 + ) { + throw new Error(`Expected Jieting sortie to deploy Zhuge Liang, Wang Ping, Ma Liang, Huang Quan, Fa Zheng, Zhao Yun, and Ma Dai: ${JSON.stringify(jietingSortieReadyState)}`); + } + await page.screenshot({ path: 'dist/verification-fifty-seventh-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-seventh-story.png', fullPage: true }); + + for (let i = 0; i < 50; i += 1) { + const enteredFiftySeventhBattle = await page.evaluate(() => { + const state = window.__HEROS_DEBUG__?.battle(); + return state?.scene === 'BattleScene' && state?.battleId === 'fifty-seventh-battle-jieting-crisis'; + }); + if (enteredFiftySeventhBattle) { + 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-seventh-battle-jieting-crisis' && state?.battleOutcome === null && state?.phase === 'idle'; + }); + await page.screenshot({ path: 'dist/verification-fifty-seventh-battle.png', fullPage: true }); + + const fiftySeventhBattleState = await page.evaluate(() => window.__HEROS_DEBUG__?.battle()); + const fiftySeventhEnemies = fiftySeventhBattleState.units.filter((unit) => unit.faction === 'enemy'); + const fiftySeventhAllies = fiftySeventhBattleState.units.filter((unit) => unit.faction === 'ally'); + const fiftySeventhEnemyBehaviors = new Set(fiftySeventhEnemies.map((unit) => unit.ai)); + if ( + fiftySeventhBattleState.camera?.mapWidth !== 114 || + fiftySeventhBattleState.camera?.mapHeight !== 96 || + fiftySeventhBattleState.victoryConditionLabel !== '가정 고지와 물길 확보 및 장합 선봉 저지' || + !fiftySeventhBattleState.objectives?.some((objective) => objective.id === 'repel-zhang-he' && objective.label === '장합 선봉 저지') || + !fiftySeventhBattleState.objectives?.some((objective) => objective.id === 'contact-jiang-wei' && objective.label === '강유 접촉로 확보') || + !fiftySeventhBattleState.objectives?.some((objective) => objective.id === 'wang-ping' && objective.label === '왕평 생존') || + !fiftySeventhBattleState.objectives?.some((objective) => objective.id === 'jieting-waterline' && objective.label === '가정 물길 확보') || + fiftySeventhEnemies.length < 42 || + !fiftySeventhEnemyBehaviors.has('aggressive') || + !fiftySeventhEnemyBehaviors.has('guard') || + !fiftySeventhEnemyBehaviors.has('hold') || + !fiftySeventhEnemies.some((unit) => unit.id === 'northern-third-leader-zhang-he') || + !fiftySeventhEnemies.some((unit) => unit.id === 'northern-third-officer-jiang-wei') || + !fiftySeventhEnemies.some((unit) => unit.id === 'northern-third-officer-masu-pressure') || + !fiftySeventhAllies.some((unit) => unit.id === 'zhuge-liang') || + !fiftySeventhAllies.some((unit) => unit.id === 'wang-ping') || + !fiftySeventhAllies.some((unit) => unit.id === 'ma-liang') || + !fiftySeventhAllies.some((unit) => unit.id === 'huang-quan') || + !fiftySeventhAllies.some((unit) => unit.id === 'fa-zheng') || + !fiftySeventhAllies.some((unit) => unit.id === 'zhao-yun') || + !fiftySeventhAllies.some((unit) => unit.id === 'ma-dai') || + fiftySeventhAllies.some((unit) => unit.id === 'liu-bei') || + fiftySeventhAllies.some((unit) => unit.id === 'jiang-wei') + ) { + throw new Error(`Expected fifty-seventh battle to use the Jieting map, Wang Ping waterline objectives, Jiang Wei contact hook, and mixed Wei AI: ${JSON.stringify(fiftySeventhBattleState)}`); + } + + 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 fiftySeventhCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); + if ( + fiftySeventhCampState?.campBattleId !== 'fifty-seventh-battle-jieting-crisis' || + fiftySeventhCampState.campaign?.step !== 'fifty-seventh-camp' || + fiftySeventhCampState.campTitle !== '가정 배치 위기 후 군영' || + fiftySeventhCampState.availableDialogueIds?.length !== 3 || + !fiftySeventhCampState.availableDialogueIds.every((id) => id.startsWith('northern-third-')) || + fiftySeventhCampState.availableVisitIds?.length !== 2 || + !fiftySeventhCampState.availableVisitIds.every((id) => id.startsWith('northern-third-')) || + fiftySeventhCampState.rosterCollection?.total < 22 || + !fiftySeventhCampState.campaign?.roster?.some((unit) => unit.id === 'jiang-wei') + ) { + throw new Error(`Expected Jieting aftermath camp to add Jiang Wei and expose Jieting recovery events: ${JSON.stringify(fiftySeventhCampState)}`); + } + await page.screenshot({ path: 'dist/verification-fifty-seventh-camp.png', fullPage: true }); + + await page.mouse.click(966, 38); + await page.waitForTimeout(180); + const postFiftySeventhProgressState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); + if ( + postFiftySeventhProgressState?.activeTab !== 'progress' || + postFiftySeventhProgressState.campaignProgress?.completedKnown !== 57 || + postFiftySeventhProgressState.campaignProgress?.totalKnown !== 57 || + postFiftySeventhProgressState.campaignProgress?.activeChapter?.id !== 'northern-campaign' || + postFiftySeventhProgressState.campaignProgress?.latestBattleTitle !== '가정 배치 위기' + ) { + throw new Error(`Expected post-fifty-seventh progress tab to complete the Jieting crisis and count Jiang Wei's join point: ${JSON.stringify(postFiftySeventhProgressState?.campaignProgress)}`); + } + await page.screenshot({ path: 'dist/verification-post-fifty-seventh-progress.png', fullPage: true }); + await page.evaluate(() => { const game = window.__HEROS_GAME__; game?.scene.stop('CampScene'); @@ -8005,11 +8160,11 @@ try { activeScenes: window.__HEROS_DEBUG__?.activeScenes() ?? [], camp: window.__HEROS_DEBUG__?.camp?.() ?? null })); - 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)}`); + if (!titleContinueState.activeScenes.includes('CampScene') || titleContinueState.camp?.campaign?.step !== 'fifty-seventh-camp') { + throw new Error(`Expected title continue to reopen the Jieting aftermath camp: ${JSON.stringify(titleContinueState)}`); } - 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}`); + console.log(`Verified title-to-Jieting-crisis 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, Tianshui advance, Jieting crisis, and Jiang Wei joining state at ${targetUrl}`); } finally { await browser?.close(); if (serverProcess && !serverProcess.killed) { diff --git a/src/assets/images/battle/fifty-seventh-battle-map.svg b/src/assets/images/battle/fifty-seventh-battle-map.svg new file mode 100644 index 0000000..723b3a4 --- /dev/null +++ b/src/assets/images/battle/fifty-seventh-battle-map.svg @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/game/data/battleMapAssets.ts b/src/game/data/battleMapAssets.ts index 8ca8c15..792c4b7 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 fiftySeventhBattleMapUrl from '../../assets/images/battle/fifty-seventh-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'; @@ -111,5 +112,6 @@ export const battleMapAssets: Record = { 'battle-map-fifty-third': fiftyThirdBattleMapUrl, 'battle-map-fifty-fourth': fiftyFourthBattleMapUrl, 'battle-map-fifty-fifth': fiftyFifthBattleMapUrl, + 'battle-map-fifty-seventh': fiftySeventhBattleMapUrl, 'battle-map-fifty-sixth': fiftySixthBattleMapUrl }; diff --git a/src/game/data/battles.ts b/src/game/data/battles.ts index 9258ad6..8ef18f1 100644 --- a/src/game/data/battles.ts +++ b/src/game/data/battles.ts @@ -27,6 +27,10 @@ import { fiftySixthBattleMap, fiftySixthBattleUnits, fiftySixthBattleVictoryPages, + fiftySeventhBattleBonds, + fiftySeventhBattleMap, + fiftySeventhBattleUnits, + fiftySeventhBattleVictoryPages, firstBattleBonds, fifteenthBattleBonds, fifteenthBattleMap, @@ -285,7 +289,8 @@ export type BattleScenarioId = | 'fifty-third-battle-meng-huo-sixth-capture' | 'fifty-fourth-battle-meng-huo-final-capture' | 'fifty-fifth-battle-northern-qishan-road' - | 'fifty-sixth-battle-tianshui-advance'; + | 'fifty-sixth-battle-tianshui-advance' + | 'fifty-seventh-battle-jieting-crisis'; export type BattleObjectiveKind = 'defeat-leader' | 'pacify-leader' | 'keep-unit-alive' | 'secure-terrain' | 'quick-victory'; @@ -3686,6 +3691,90 @@ export const fiftySixthBattleScenario: BattleScenarioDefinition = { nextCampScene: 'CampScene' }; +export const fiftySeventhBattleScenario: BattleScenarioDefinition = { + id: 'fifty-seventh-battle-jieting-crisis', + title: '가정 배치 위기', + victoryConditionLabel: '가정 고지와 물길 확보 및 장합 선봉 저지', + defeatConditionLabel: '제갈량 또는 왕평 퇴각', + openingObjectiveLines: [ + '천수 방면에서 길을 열었지만 가정의 배치가 흔들리면 북벌군의 퇴로와 군량이 동시에 끊깁니다.', + '마속은 높은 곳을 주장하고, 왕평은 물길과 수레 길을 먼저 보아야 한다고 말합니다. 이번 전투는 고지를 차지하는 것보다 버틸 수 있는 진영을 만드는 일이 더 중요합니다.', + '장합의 선봉을 막고 물길을 확보하십시오. 강유에게 남긴 길도 여기서 시험받습니다.' + ], + map: fiftySeventhBattleMap, + units: fiftySeventhBattleUnits, + bonds: fiftySeventhBattleBonds, + mapTextureKey: 'battle-map-fifty-seventh', + leaderUnitId: 'northern-third-leader-zhang-he', + quickVictoryTurnLimit: 114, + baseVictoryGold: 16140, + objectives: [ + { + id: 'repel-zhang-he', + kind: 'defeat-leader', + label: '장합 선봉 저지', + rewardGold: 10840, + unitId: 'northern-third-leader-zhang-he' + }, + { + id: 'contact-jiang-wei', + kind: 'defeat-leader', + label: '강유 접촉로 확보', + rewardGold: 5200, + unitId: 'northern-third-officer-jiang-wei' + }, + { + id: 'zhuge-liang', + kind: 'keep-unit-alive', + label: '제갈량 생존', + rewardGold: 2840, + unitId: 'zhuge-liang' + }, + { + id: 'wang-ping', + kind: 'keep-unit-alive', + label: '왕평 생존', + rewardGold: 2860, + unitId: 'wang-ping' + }, + { + id: 'jieting-waterline', + kind: 'secure-terrain', + label: '가정 물길 확보', + rewardGold: 5600, + terrain: 'river' + }, + { + id: 'ridge-camps', + kind: 'secure-terrain', + label: '고지 아래 진영 유지', + rewardGold: 5360, + terrain: 'camp' + }, + { + id: 'ridge-forts', + kind: 'secure-terrain', + label: '가정 고지 압박', + rewardGold: 5240, + terrain: 'fort' + }, + { + id: 'quick', + kind: 'quick-victory', + label: '114턴 이내 가정 수습', + rewardGold: 3600, + maxTurn: 114 + } + ], + defeatConditions: [ + { kind: 'unit-defeated', unitId: 'zhuge-liang' }, + { kind: 'unit-defeated', unitId: 'wang-ping' } + ], + itemRewards: ['콩 94', '상처약 78', '탁주 38', '가정 물길 장부 1', '강유 합류'], + victoryPages: fiftySeventhBattleVictoryPages, + nextCampScene: 'CampScene' +}; + export const defaultBattleScenarioId: BattleScenarioId = firstBattleScenario.id; export const battleScenarios: Record = { @@ -3744,7 +3833,8 @@ export const battleScenarios: Record 'fifty-third-battle-meng-huo-sixth-capture': fiftyThirdBattleScenario, 'fifty-fourth-battle-meng-huo-final-capture': fiftyFourthBattleScenario, 'fifty-fifth-battle-northern-qishan-road': fiftyFifthBattleScenario, - 'fifty-sixth-battle-tianshui-advance': fiftySixthBattleScenario + 'fifty-sixth-battle-tianshui-advance': fiftySixthBattleScenario, + 'fifty-seventh-battle-jieting-crisis': fiftySeventhBattleScenario }; export const defaultBattleScenario = battleScenarios[defaultBattleScenarioId]; diff --git a/src/game/data/campaignFlow.ts b/src/game/data/campaignFlow.ts index 09bd060..0c1f9a1 100644 --- a/src/game/data/campaignFlow.ts +++ b/src/game/data/campaignFlow.ts @@ -11,6 +11,7 @@ import { fiftyFourthBattleScenario, fiftyFifthBattleScenario, fiftySixthBattleScenario, + fiftySeventhBattleScenario, fortiethBattleScenario, fortyFirstBattleScenario, fortySecondBattleScenario, @@ -80,6 +81,8 @@ import { fiftyFifthBattleVictoryPages, fiftySixthBattleIntroPages, fiftySixthBattleVictoryPages, + fiftySeventhBattleIntroPages, + fiftySeventhBattleVictoryPages, fortiethBattleIntroPages, fortiethBattleVictoryPages, fortyFirstBattleIntroPages, @@ -827,13 +830,24 @@ const sortieFlows: Record = { }, [fiftySixthBattleScenario.id]: { afterBattleId: fiftySixthBattleScenario.id, - eyebrow: '다음 장 준비', - title: '천수 진군로 이후', + eyebrow: '다음 전장', + title: fiftySeventhBattleScenario.title, description: - '천수 방면의 방어선을 밀어냈고 강유의 이름이 제갈량의 장부에 남았습니다. 다음 북벌 전장은 가정 방면의 배치 판단과 더 큰 위군 반격으로 이어질 예정입니다.', - rewardHint: '다음 목표: 가정 방면 배치 준비 / 강유 접촉 단서 정리', - pages: fiftySixthBattleVictoryPages, - unavailableNotice: '가정 방면의 다음 북벌 전투는 이어지는 작업에서 확장합니다. 현재는 천수 진군로 후 군영에서 무장 대화와 보급 정비를 진행할 수 있습니다.' + '천수 방면의 방어선을 밀어냈고 강유의 이름이 제갈량의 장부에 남았습니다. 이제 가정 방면의 진영 배치와 장합의 반격을 막아 북벌의 퇴로를 지켜야 합니다.', + rewardHint: `예상 보상: ${fiftySeventhBattleScenario.title} 개방 / 강유 합류 단서`, + nextBattleId: fiftySeventhBattleScenario.id, + campaignStep: 'fifty-seventh-battle', + pages: [...fiftySixthBattleVictoryPages, ...fiftySeventhBattleIntroPages] + }, + [fiftySeventhBattleScenario.id]: { + afterBattleId: fiftySeventhBattleScenario.id, + eyebrow: '다음 장 준비', + title: '가정 배치 위기 이후', + description: + '가정의 물길과 고지는 가까스로 지켰고 강유가 촉한의 새 장수로 합류했습니다. 다음 북벌에서는 새로 늘어난 장수진 안에서 더 엄격한 출전 선택과 보급 판단이 필요합니다.', + rewardHint: '다음 목표: 강유 편성 훈련 / 다음 북벌 전선 준비', + pages: fiftySeventhBattleVictoryPages, + unavailableNotice: '가정 이후의 다음 북벌 전투는 이어지는 작업에서 확장합니다. 현재는 강유 합류 후 군영에서 무장 대화, 보급 정비, 출전 편성 방향을 확인할 수 있습니다.' }, 'northern-campaign-prep-camp-legacy': { afterBattleId: fiftyFourthBattleScenario.id, diff --git a/src/game/data/scenario.ts b/src/game/data/scenario.ts index 6a025f6..06d8f58 100644 --- a/src/game/data/scenario.ts +++ b/src/game/data/scenario.ts @@ -3642,6 +3642,59 @@ export const fiftySixthBattleVictoryPages: StoryPage[] = [ } ]; +export const fiftySeventhBattleIntroPages: StoryPage[] = [ + { + id: 'fifty-seventh-jieting-order', + bgm: 'story-dark', + chapter: '가정 방면', + background: 'story-militia', + speaker: '제갈량', + text: '천수 길목은 열렸으나 가정의 고개가 흔들리면 북벌의 군량과 퇴로가 한꺼번에 끊깁니다. 높은 곳만 바라보는 진영은 오래 버티지 못합니다. 물길과 길목을 함께 지키십시오.' + }, + { + id: 'fifty-seventh-ma-su-insists', + bgm: 'battle-prep', + chapter: '마속의 주장', + background: 'story-sortie', + speaker: '왕평', + text: '마속은 고지를 잡으면 적이 감히 오르지 못한다고 말합니다. 그러나 고개 아래 물길과 수레 길을 잃으면 위군은 우리를 치지 않고도 말릴 수 있습니다. 이번 출전은 강한 장수보다 지형을 읽을 장수가 필요합니다.' + }, + { + id: 'fifty-seventh-jiang-wei-contact', + bgm: 'battle-prep', + chapter: '강유 접촉', + background: 'story-three-heroes', + speaker: '마량', + text: '천수에서 남긴 글이 강유에게 닿았습니다. 그는 아직 위군의 장수로 서 있지만, 싸움이 명분을 잃는 순간 마음이 흔들릴 것입니다. 가정의 길을 지키며 강유에게 돌아올 길도 남겨 두어야 합니다.' + } +]; + +export const fiftySeventhBattleVictoryPages: StoryPage[] = [ + { + id: 'fifty-seventh-victory-jieting-held', + bgm: 'militia-theme', + chapter: '가정 방면 수습', + background: 'story-sortie', + text: '장합의 선봉은 고개 아래에서 멈췄고, 촉한군은 가정의 물길과 수레 길을 간신히 잇는 데 성공했습니다. 마속의 고집은 큰 위험을 남겼지만, 왕평의 지형 판단이 북벌의 퇴로를 붙잡았습니다.' + }, + { + id: 'fifty-seventh-victory-jiang-wei-joins', + bgm: 'story-dark', + chapter: '새 장수', + background: 'story-militia', + speaker: '강유', + text: '강유는 위군의 의심과 백성의 말 사이에서 끝내 마음을 정했습니다. 오늘부터 그는 촉한의 깃발 아래 서지만, 새로 얻은 장수 하나가 곧바로 모든 길을 열어 주지는 않습니다. 다음 출전부터는 더 넓어진 장수진 안에서 누구를 고를지 판단해야 합니다.' + }, + { + id: 'fifty-seventh-next-front', + bgm: 'story-dark', + chapter: '북벌의 다음 부담', + background: 'story-three-heroes', + speaker: '제갈량', + text: '가정의 위기는 수습되었으나 위군은 이제 우리의 길과 사람을 모두 보았습니다. 강유를 얻은 기쁨과 마속의 실책을 함께 품고, 다음 북벌에서는 더 엄격한 편성과 보급 판단이 필요할 것입니다.' + } +]; + export const firstBattleMap: BattleMap = { width: 20, height: 18, @@ -4123,6 +4176,12 @@ export const fiftySixthBattleMap: BattleMap = { terrain: createFiftySixthBattleTerrain() }; +export const fiftySeventhBattleMap: BattleMap = { + width: 114, + height: 96, + terrain: createFiftySeventhBattleTerrain() +}; + export const firstBattleUnits: UnitData[] = [ { id: 'liu-bei', @@ -6412,6 +6471,30 @@ export const hanzhongMainRecruitUnits: UnitData[] = [ } ]; +export const jiangWeiRecruitUnits: UnitData[] = [ + { + id: 'jiang-wei', + name: '강유', + faction: 'ally', + className: '천수 기병', + classKey: 'cavalry', + level: 27, + exp: 0, + hp: 56, + maxHp: 56, + attack: 19, + move: 6, + stats: { might: 84, intelligence: 88, leadership: 82, agility: 80, luck: 70 }, + equipment: { + weapon: { itemId: 'western-cavalry-spear', level: 4, exp: 20 }, + armor: { itemId: 'reinforced-lamellar', level: 4, exp: 18 }, + accessory: { itemId: 'mountain-guide-scroll', level: 3, exp: 12 } + }, + x: 18, + y: 38 + } +]; + const eighthBattleAllyPositions: Record = { 'liu-bei': { x: 2, y: 17 }, 'guan-yu': { x: 3, y: 17 }, @@ -16954,6 +17037,95 @@ export const fiftySixthBattleUnits: UnitData[] = [ createThirtyFirstEnemyUnit('northern-second-leader-guo-huai', '곽회', '위군 북방 지휘관', 'rebelLeader', 182, 1320, 316, 104, 24) ]; +const fiftySeventhBattleAllyPositions: Record = { + 'guan-yu': { x: 18, y: 83 }, + 'zhang-fei': { x: 24, y: 84 }, + 'jian-yong': { x: 11, y: 90 }, + 'mi-zhu': { x: 15, y: 91 }, + 'sun-qian': { x: 20, y: 91 }, + 'zhao-yun': { x: 31, y: 80 }, + 'zhuge-liang': { x: 17, y: 79 }, + 'ma-liang': { x: 22, y: 76 }, + 'yi-ji': { x: 10, y: 93 }, + 'gong-zhi': { x: 26, y: 91 }, + 'huang-zhong': { x: 37, y: 79 }, + 'wei-yan': { x: 43, y: 82 }, + 'pang-tong': { x: 14, y: 75 }, + 'fa-zheng': { x: 26, y: 74 }, + 'wu-yi': { x: 44, y: 89 }, + 'yan-yan': { x: 48, y: 82 }, + 'li-yan': { x: 34, y: 73 }, + 'huang-quan': { x: 11, y: 78 }, + 'ma-chao': { x: 51, y: 78 }, + 'ma-dai': { x: 55, y: 85 }, + 'wang-ping': { x: 39, y: 73 } +}; + +export const fiftySeventhBattleUnits: 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, fiftySeventhBattleAllyPositions[unit.id] ?? { x: unit.x, y: unit.y })), + createThirtyFirstEnemyUnit('northern-third-scout-a', '가정 척후병', '서쪽 물길 정찰', 'bandit', 181, 872, 250, 52, 74), + createThirtyFirstEnemyUnit('northern-third-scout-b', '가정 척후병', '하곡 정찰', 'bandit', 181, 872, 250, 61, 70), + createThirtyFirstEnemyUnit('northern-third-scout-c', '가정 척후병', '중앙 고개 정찰', 'bandit', 182, 884, 252, 74, 63), + createThirtyFirstEnemyUnit('northern-third-scout-d', '가정 척후병', '동쪽 고지 정찰', 'bandit', 182, 884, 252, 86, 56), + createThirtyFirstEnemyUnit('northern-third-scout-e', '가정 척후병', '장합 선봉 정찰', 'bandit', 183, 896, 254, 99, 48), + createThirtyFirstEnemyUnit('northern-third-scout-f', '가정 척후병', '북쪽 절벽 정찰', 'bandit', 183, 896, 254, 109, 36), + createThirtyFirstEnemyUnit('northern-third-infantry-a', '위군 가정 보병', '서쪽 하곡 보병', 'yellowTurban', 182, 1200, 294, 54, 68), + createThirtyFirstEnemyUnit('northern-third-infantry-b', '위군 가정 보병', '물길 보병', 'yellowTurban', 182, 1200, 294, 63, 64), + createThirtyFirstEnemyUnit('northern-third-infantry-c', '위군 가정 보병', '중앙 고개 보병', 'yellowTurban', 183, 1220, 298, 74, 58), + createThirtyFirstEnemyUnit('northern-third-infantry-d', '위군 가정 보병', '고지 아래 보병', 'yellowTurban', 183, 1220, 298, 84, 52), + createThirtyFirstEnemyUnit('northern-third-infantry-e', '위군 가정 보병', '동쪽 수레길 보병', 'yellowTurban', 184, 1240, 302, 94, 46), + createThirtyFirstEnemyUnit('northern-third-infantry-f', '위군 가정 보병', '장합 본대 보병', 'yellowTurban', 184, 1240, 302, 104, 38), + createThirtyFirstEnemyUnit('northern-third-infantry-g', '위군 가정 보병', '북쪽 길목 보병', 'yellowTurban', 185, 1260, 306, 110, 28), + createThirtyFirstEnemyUnit('northern-third-infantry-h', '위군 가정 보병', '보급 차단 보병', 'yellowTurban', 185, 1260, 306, 93, 72), + createThirtyFirstEnemyUnit('northern-third-archer-a', '위군 가정 궁병', '하곡 궁병', 'archer', 182, 908, 262, 58, 59), + createThirtyFirstEnemyUnit('northern-third-archer-b', '위군 가정 궁병', '물길 궁병', 'archer', 182, 908, 262, 68, 55), + createThirtyFirstEnemyUnit('northern-third-archer-c', '위군 가정 궁병', '중앙 고지 궁병', 'archer', 183, 920, 264, 80, 49), + createThirtyFirstEnemyUnit('northern-third-archer-d', '위군 가정 궁병', '수레길 궁병', 'archer', 184, 932, 266, 91, 42), + createThirtyFirstEnemyUnit('northern-third-archer-e', '위군 가정 궁병', '장합 본영 궁병', 'archer', 184, 932, 266, 103, 34), + createThirtyFirstEnemyUnit('northern-third-archer-f', '위군 가정 궁병', '북쪽 절벽 궁병', 'archer', 185, 944, 268, 111, 24), + createThirtyFirstEnemyUnit('northern-third-cavalry-a', '장합 추격 기병', '서쪽 추격 기병', 'cavalry', 183, 1236, 306, 50, 61), + createThirtyFirstEnemyUnit('northern-third-cavalry-b', '장합 추격 기병', '물길 차단 기병', 'cavalry', 183, 1236, 306, 64, 58), + createThirtyFirstEnemyUnit('northern-third-cavalry-c', '장합 추격 기병', '중앙 우회 기병', 'cavalry', 184, 1258, 310, 78, 51), + createThirtyFirstEnemyUnit('northern-third-cavalry-d', '장합 추격 기병', '고지 우회 기병', 'cavalry', 185, 1280, 314, 91, 45), + createThirtyFirstEnemyUnit('northern-third-cavalry-e', '장합 추격 기병', '동쪽 역습 기병', 'cavalry', 185, 1280, 314, 103, 39), + createThirtyFirstEnemyUnit('northern-third-cavalry-f', '장합 추격 기병', '장합 친위 기병', 'cavalry', 186, 1302, 318, 110, 31), + createThirtyFirstEnemyUnit('northern-third-ridge-guard-a', '가정 고지 수비대', '서쪽 고개 수비', 'yellowTurban', 183, 1240, 304, 45, 50), + createThirtyFirstEnemyUnit('northern-third-ridge-guard-b', '가정 고지 수비대', '물길 고개 수비', 'yellowTurban', 183, 1240, 304, 57, 44), + createThirtyFirstEnemyUnit('northern-third-ridge-guard-c', '가정 고지 수비대', '중앙 고지 수비', 'yellowTurban', 184, 1260, 308, 72, 37), + createThirtyFirstEnemyUnit('northern-third-ridge-guard-d', '가정 고지 수비대', '고지 진영 수비', 'yellowTurban', 184, 1260, 308, 86, 30), + createThirtyFirstEnemyUnit('northern-third-ridge-guard-e', '가정 고지 수비대', '장합 압박 수비', 'yellowTurban', 185, 1280, 312, 101, 22), + createThirtyFirstEnemyUnit('northern-third-ridge-guard-f', '가정 고지 수비대', '북쪽 절벽 수비', 'yellowTurban', 185, 1280, 312, 110, 18), + createThirtyFirstEnemyUnit('northern-third-strategist-a', '위군 가정 군리', '물길 차단 군리', 'strategist', 183, 976, 274, 70, 48), + createThirtyFirstEnemyUnit('northern-third-strategist-b', '위군 가정 군리', '고지 기록 군리', 'strategist', 184, 988, 276, 84, 39), + createThirtyFirstEnemyUnit('northern-third-strategist-c', '위군 가정 군리', '보급 차단 군리', 'strategist', 184, 988, 276, 96, 54), + createThirtyFirstEnemyUnit('northern-third-strategist-d', '위군 가정 군리', '장합 본영 군리', 'strategist', 185, 1000, 278, 108, 26), + createThirtyFirstEnemyUnit('northern-third-officer-jiang-wei', '강유', '흔들리는 천수 장수', 'rebelLeader', 186, 1320, 322, 87, 43), + createThirtyFirstEnemyUnit('northern-third-officer-fei-yao', '비요', '장합 지원 장수', 'rebelLeader', 185, 1300, 318, 99, 57), + createThirtyFirstEnemyUnit('northern-third-officer-ma-zun', '마준', '천수 태수', 'rebelLeader', 185, 1300, 316, 105, 64), + createThirtyFirstEnemyUnit('northern-third-officer-supply', '위군 보급 장교', '물길 차단 장수', 'rebelLeader', 184, 1280, 312, 77, 71), + createThirtyFirstEnemyUnit('northern-third-officer-masu-pressure', '가정 압박대', '마속 진영 압박', 'rebelLeader', 184, 1280, 312, 92, 35), + createThirtyFirstEnemyUnit('northern-third-leader-zhang-he', '장합', '위군 역습 총대장', 'rebelLeader', 187, 1400, 332, 109, 24) +]; + export const firstBattleBonds: BattleBond[] = [ { id: 'liu-bei__guan-yu', @@ -17440,6 +17612,33 @@ export const hanzhongMainRecruitBonds: BattleBond[] = [ } ]; +export const jiangWeiRecruitBonds: BattleBond[] = [ + { + id: 'zhuge-liang__jiang-wei', + unitIds: ['zhuge-liang', 'jiang-wei'], + title: '천수의 새 별', + level: 34, + exp: 0, + description: '제갈량이 강유의 재능을 받아들이면 북벌의 계책과 기동 판단이 더 넓어집니다.' + }, + { + id: 'ma-liang__jiang-wei', + unitIds: ['ma-liang', 'jiang-wei'], + title: '설득의 문장', + level: 30, + exp: 0, + description: '마량이 남긴 글과 강유의 결단이 이어져 새 장수가 촉한의 명분을 빠르게 이해합니다.' + }, + { + id: 'wang-ping__jiang-wei', + unitIds: ['wang-ping', 'jiang-wei'], + title: '길을 아는 두 장수', + level: 28, + exp: 0, + description: '왕평의 실제 산길 판단과 강유의 천수 지식이 만나 북방 전선의 지형 대응력이 높아집니다.' + } +]; + export const hanzhongDecisiveBonds: BattleBond[] = [ { id: 'liu-bei__wang-ping', @@ -18084,6 +18283,46 @@ export const fiftySixthBattleBonds: BattleBond[] = [...fiftyFifthBattleBonds, .. cloneBattleBondForScenario ); +export const northernThirdBattleBonds: BattleBond[] = [ + { + id: 'zhuge-liang__wang-ping_northern-third', + unitIds: ['zhuge-liang', 'wang-ping'], + title: '가정의 실제 길', + level: 60, + exp: 0, + description: '제갈량의 큰 전략과 왕평의 실제 지형 판단이 함께 움직이면 가정의 물길과 퇴로를 동시에 지킬 수 있습니다.' + }, + { + id: 'wang-ping__ma-liang_northern-third', + unitIds: ['wang-ping', 'ma-liang'], + title: '말과 길의 설득', + level: 54, + exp: 0, + description: '마량의 설득과 왕평의 길 표식이 맞물리면 흔들리는 병사와 장수에게 물러설 명분을 남길 수 있습니다.' + }, + { + id: 'huang-quan__fa-zheng_northern-third', + unitIds: ['huang-quan', 'fa-zheng'], + title: '장부와 계책', + level: 50, + exp: 0, + description: '황권의 보급 장부와 법정의 책략 판단이 합쳐지면 위군의 차단선을 피할 작은 틈을 찾습니다.' + }, + { + id: 'zhao-yun__ma-dai_northern-third', + unitIds: ['zhao-yun', 'ma-dai'], + title: '퇴로의 기병', + level: 56, + exp: 0, + description: '조운과 마대가 함께 움직이면 가정 고개에서 지나치게 깊이 들어간 부대를 빠르게 회수할 수 있습니다.' + } +]; + +export const fiftySeventhBattleBonds: BattleBond[] = [ + ...fiftySixthBattleBonds, + ...northernThirdBattleBonds +].map(cloneBattleBondForScenario); + function createEighthBattleTerrain(): TerrainType[][] { return Array.from({ length: 22 }, (_, y) => Array.from({ length: 26 }, (_, x): TerrainType => { @@ -21099,6 +21338,83 @@ function createFiftySixthBattleTerrain(): TerrainType[][] { ); } +function createFiftySeventhBattleTerrain(): TerrainType[][] { + return Array.from({ length: 96 }, (_, y) => + Array.from({ length: 114 }, (_, x): TerrainType => { + if ( + (x >= 0 && x <= 113 && y >= 84) || + (x >= 30 && x <= 36 && y >= 0 && y <= 94) || + (x >= 78 && x <= 84 && y >= 0 && y <= 88) || + (x >= 88 && x <= 113 && y >= 70 && y <= 88) + ) { + return 'river'; + } + if ( + (x >= 8 && x <= 110 && y >= 78 && y <= 83) || + (x >= 28 && x <= 91 && y >= 62 && y <= 70) || + (x >= 64 && x <= 110 && y >= 53 && y <= 61) || + (x >= 46 && x <= 53 && y >= 54 && y <= 84) || + (x >= 86 && x <= 94 && y >= 55 && y <= 91) || + (x >= 58 && x <= 78 && y >= 66 && y <= 73) || + (x >= 68 && x <= 90 && y >= 44 && y <= 54) || + (x >= 92 && x <= 110 && y >= 26 && y <= 36) + ) { + return 'road'; + } + if ( + (x >= 10 && x <= 70 && y >= 58 && y <= 84) || + (x >= 58 && x <= 108 && y >= 49 && y <= 78) || + (x >= 0 && x <= 44 && y >= 62 && y <= 84) || + (x >= 90 && x <= 113 && y >= 52 && y <= 80) + ) { + return 'forest'; + } + if ( + (x >= 0 && x <= 44 && y <= 50) || + (x >= 40 && x <= 82 && y <= 40) || + (x >= 84 && x <= 113 && y <= 50) || + (x >= 96 && x <= 113 && y >= 35 && y <= 67) + ) { + return 'hill'; + } + if ( + (x <= 2 && y <= 84) || + (x >= 111 && y <= 80) || + (x >= 36 && x <= 42 && y >= 0 && y <= 56) || + (x >= 84 && x <= 89 && y >= 0 && y <= 44) || + (x >= 54 && x <= 60 && y >= 76 && y <= 95) + ) { + return 'cliff'; + } + if ( + (x >= 14 && x <= 29 && y >= 72 && y <= 83) || + (x >= 50 && x <= 65 && y >= 61 && y <= 71) || + (x >= 86 && x <= 101 && y >= 51 && y <= 63) || + (x >= 96 && x <= 108 && y >= 31 && y <= 43) + ) { + return 'village'; + } + if ( + (x >= 24 && x <= 44 && y >= 66 && y <= 76) || + (x >= 67 && x <= 85 && y >= 62 && y <= 74) || + (x >= 88 && x <= 108 && y >= 43 && y <= 56) || + (x >= 82 && x <= 98 && y >= 31 && y <= 42) + ) { + return 'camp'; + } + if ( + (x >= 58 && x <= 74 && y >= 37 && y <= 50) || + (x >= 80 && x <= 100 && y >= 34 && y <= 48) || + (x >= 95 && x <= 110 && y >= 46 && y <= 60) || + (x >= 98 && x <= 111 && y >= 18 && y <= 32) + ) { + 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 4de7479..4fb4ee1 100644 --- a/src/game/scenes/BattleScene.ts +++ b/src/game/scenes/BattleScene.ts @@ -6364,30 +6364,39 @@ export class BattleScene extends Phaser.Scene { if ( enemy.id.startsWith('northern-first-scout') || enemy.id.startsWith('northern-second-scout') || + enemy.id.startsWith('northern-third-scout') || enemy.id.startsWith('northern-first-cavalry') || enemy.id.startsWith('northern-second-cavalry') || + enemy.id.startsWith('northern-third-cavalry') || enemy.id === 'northern-first-officer-zhang-he' || - enemy.id === 'northern-second-officer-jiang-wei' + enemy.id === 'northern-second-officer-jiang-wei' || + enemy.id === 'northern-third-leader-zhang-he' ) { return 'aggressive'; } if ( enemy.id.startsWith('northern-first-archer') || enemy.id.startsWith('northern-second-archer') || + enemy.id.startsWith('northern-third-archer') || enemy.id.startsWith('northern-first-strategist') || enemy.id.startsWith('northern-second-strategist') || + enemy.id.startsWith('northern-third-strategist') || enemy.id === 'northern-first-leader-cao-zhen' || - enemy.id === 'northern-second-leader-guo-huai' + enemy.id === 'northern-second-leader-guo-huai' || + enemy.id === 'northern-third-officer-jiang-wei' ) { return 'hold'; } if ( enemy.id.startsWith('northern-first-infantry') || enemy.id.startsWith('northern-second-infantry') || + enemy.id.startsWith('northern-third-infantry') || enemy.id.startsWith('northern-first-ridge-guard') || enemy.id.startsWith('northern-second-jieting-guard') || + enemy.id.startsWith('northern-third-ridge-guard') || enemy.id.startsWith('northern-first-officer') || - enemy.id.startsWith('northern-second-officer') + enemy.id.startsWith('northern-second-officer') || + enemy.id.startsWith('northern-third-officer') ) { return 'guard'; } @@ -7868,22 +7877,30 @@ export class BattleScene extends Phaser.Scene { } private unitTextureKey(unit: UnitData) { - if (unit.id.startsWith('northern-first-cavalry') || unit.id.startsWith('northern-second-cavalry')) { + if ( + unit.id.startsWith('northern-first-cavalry') || + unit.id.startsWith('northern-second-cavalry') || + unit.id.startsWith('northern-third-cavalry') + ) { return 'unit-rebel-cavalry'; } if ( unit.id.startsWith('northern-first-archer') || unit.id.startsWith('northern-second-archer') || + unit.id.startsWith('northern-third-archer') || unit.id.startsWith('northern-first-strategist') || - unit.id.startsWith('northern-second-strategist') + unit.id.startsWith('northern-second-strategist') || + unit.id.startsWith('northern-third-strategist') ) { return 'unit-rebel-archer'; } if ( unit.id.startsWith('northern-first-officer') || unit.id.startsWith('northern-second-officer') || + unit.id.startsWith('northern-third-officer') || unit.id === 'northern-first-leader-cao-zhen' || - unit.id === 'northern-second-leader-guo-huai' + unit.id === 'northern-second-leader-guo-huai' || + unit.id === 'northern-third-leader-zhang-he' ) { return 'unit-rebel-leader'; } diff --git a/src/game/scenes/CampScene.ts b/src/game/scenes/CampScene.ts index 6f84501..3708d00 100644 --- a/src/game/scenes/CampScene.ts +++ b/src/game/scenes/CampScene.ts @@ -27,6 +27,8 @@ import { hanzhongMainRecruitUnits, hanzhongScoutRecruitBonds, hanzhongScoutRecruitUnits, + jiangWeiRecruitBonds, + jiangWeiRecruitUnits, mengHuoFourthCaptureBonds, mengHuoFifthCaptureBonds, mengHuoSixthCaptureBonds, @@ -51,6 +53,7 @@ import { nanzhongRecoveryBonds, northernFirstBattleBonds, northernSecondBattleBonds, + northernThirdBattleBonds, wulingRecruitBonds, wulingRecruitUnits, xuzhouRecruitBonds, @@ -433,8 +436,12 @@ const campaignTimelineChapters: CampaignTimelineChapter[] = [ title: '북벌 준비', period: '출사표를 향해', description: '남중 평정으로 뒤를 안정시킨 뒤, 한중 창고와 출전 무장 편성을 정비해 첫 북벌을 준비하는 장입니다.', - battleIds: ['fifty-fifth-battle-northern-qishan-road', 'fifty-sixth-battle-tianshui-advance'], - nextHints: ['한중 창고 정비', '출전 무장 재편', '기산 출진로', '천수·가정 방면'] + battleIds: [ + 'fifty-fifth-battle-northern-qishan-road', + 'fifty-sixth-battle-tianshui-advance', + 'fifty-seventh-battle-jieting-crisis' + ], + nextHints: ['한중 창고 정비', '출전 무장 재편', '기산 출진로', '천수·가정 방면', '강유 합류'] } ]; @@ -494,7 +501,8 @@ const campBattleIds = { fiftyThird: 'fifty-third-battle-meng-huo-sixth-capture', fiftyFourth: 'fifty-fourth-battle-meng-huo-final-capture', fiftyFifth: 'fifty-fifth-battle-northern-qishan-road', - fiftySixth: 'fifty-sixth-battle-tianshui-advance' + fiftySixth: 'fifty-sixth-battle-tianshui-advance', + fiftySeventh: 'fifty-seventh-battle-jieting-crisis' } as const; const defaultRequiredSortieUnitIds = ['liu-bei']; @@ -1183,6 +1191,21 @@ const sortieRulesByBattleId: Partial