diff --git a/docs/roadmap.md b/docs/roadmap.md index 51532d8..58bf091 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -44,16 +44,17 @@ Build a small complete tactical RPG loop that can grow into a longer Romance of - Twentieth battle Jiangdong envoy route, shifting sortie choice toward protecting Zhuge Liang and Sun Qian while opening the diplomatic path to Sun Quan and Zhou Yu - Twenty-first battle Red Cliffs vanguard route, opening the first allied river-line clash with Cai Mao, six-officer sortie pressure, and post-battle fire-attack preparation events - Twenty-second battle Red Cliffs fire attack, turning Huang Gai's bitter-flesh plan and Zhuge Liang's wind timing into the decisive fire battle against Cao Cao's chained fleet +- Twenty-third battle Jing Province south entry route, opening the post-Red-Cliffs land campaign with Xing Daorong, village stabilization, a larger 46x32 battlefield, and Ma Liang recruitment after victory - 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, 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, 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 twenty-second battle victory, recruit sortie selection, Liu Biao visit rewards, Zhuge Liang recruitment, Bowang/Changban/Jiangdong/Red Cliffs fire camp states, campaign timeline state, and camp save state +- Flow verification script from title through the twenty-third battle victory, recruit sortie selection, Liu Biao visit rewards, Zhuge Liang recruitment, Bowang/Changban/Jiangdong/Red Cliffs/Jing Province camp states, campaign timeline state, and camp save state ## Next Steps -1. Continue from Red Cliffs victory into Jing Province southern commandery acquisition and the first post-Red-Cliffs land campaign -2. Add more recruitable officers as the campaign moves toward Red Cliffs, Jing Province, Yi Province, and Shu Han +1. Continue Jing Province south from the first entry battle into Guiyang, Wuling, and Changsha acquisition +2. Add more recruitable officers as the campaign moves through Jing Province toward Huang Zhong, Wei Yan, Yi Province, and Shu Han 3. Expand reserve training into explicit drill choices, class practice, and bond-focused camp assignments 4. Add a dedicated treasure/equipment management view so players can compare special effects and growth 5. Keep expanding scenarios through the long campaign instead of treating the current slice as an ending diff --git a/scripts/verify-flow.mjs b/scripts/verify-flow.mjs index 16a589f..2dfb144 100644 --- a/scripts/verify-flow.mjs +++ b/scripts/verify-flow.mjs @@ -1736,7 +1736,7 @@ try { if ( progressTabState?.activeTab !== 'progress' || progressTabState.campaignProgress?.completedKnown !== 17 || - progressTabState.campaignProgress?.totalKnown !== 22 || + progressTabState.campaignProgress?.totalKnown !== 23 || progressTabState.campaignProgress?.activeChapter?.title !== '적벽대전' || progressTabState.campaignProgress?.latestBattleTitle !== '융중 방문로' || progressTabState.campaignProgress?.nextBattleTitle !== '박망파 매복전' @@ -1888,7 +1888,7 @@ try { if ( postBowangProgressState?.activeTab !== 'progress' || postBowangProgressState.campaignProgress?.completedKnown !== 18 || - postBowangProgressState.campaignProgress?.totalKnown !== 22 || + postBowangProgressState.campaignProgress?.totalKnown !== 23 || postBowangProgressState.campaignProgress?.activeChapter?.title !== '적벽대전' || postBowangProgressState.campaignProgress?.latestBattleTitle !== '박망파 매복전' || postBowangProgressState.campaignProgress?.nextBattleTitle !== '장판파 피난로' @@ -2026,7 +2026,7 @@ try { if ( postChangbanProgressState?.activeTab !== 'progress' || postChangbanProgressState.campaignProgress?.completedKnown !== 19 || - postChangbanProgressState.campaignProgress?.totalKnown !== 22 || + postChangbanProgressState.campaignProgress?.totalKnown !== 23 || postChangbanProgressState.campaignProgress?.activeChapter?.title !== '적벽대전' || postChangbanProgressState.campaignProgress?.latestBattleTitle !== '장판파 피난로' || postChangbanProgressState.campaignProgress?.nextBattleTitle !== '강동 사절로' @@ -2164,7 +2164,7 @@ try { if ( postJiangdongProgressState?.activeTab !== 'progress' || postJiangdongProgressState.campaignProgress?.completedKnown !== 20 || - postJiangdongProgressState.campaignProgress?.totalKnown !== 22 || + postJiangdongProgressState.campaignProgress?.totalKnown !== 23 || postJiangdongProgressState.campaignProgress?.activeChapter?.title !== '적벽대전' || postJiangdongProgressState.campaignProgress?.latestBattleTitle !== '강동 사절로' || postJiangdongProgressState.campaignProgress?.nextBattleTitle !== '적벽 전초전' @@ -2304,7 +2304,7 @@ try { if ( postRedCliffsProgressState?.activeTab !== 'progress' || postRedCliffsProgressState.campaignProgress?.completedKnown !== 21 || - postRedCliffsProgressState.campaignProgress?.totalKnown !== 22 || + postRedCliffsProgressState.campaignProgress?.totalKnown !== 23 || postRedCliffsProgressState.campaignProgress?.activeChapter?.title !== '적벽대전' || postRedCliffsProgressState.campaignProgress?.latestBattleTitle !== '적벽 전초전' || postRedCliffsProgressState.campaignProgress?.nextBattleTitle !== '적벽 화공전' @@ -2444,15 +2444,158 @@ try { if ( postFireAttackProgressState?.activeTab !== 'progress' || postFireAttackProgressState.campaignProgress?.completedKnown !== 22 || - postFireAttackProgressState.campaignProgress?.totalKnown !== 22 || - postFireAttackProgressState.campaignProgress?.activeChapter?.title !== '적벽대전' || + postFireAttackProgressState.campaignProgress?.totalKnown !== 23 || + postFireAttackProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' || postFireAttackProgressState.campaignProgress?.latestBattleTitle !== '적벽 화공전' || - postFireAttackProgressState.campaignProgress?.nextBattleTitle !== '준비 중' + postFireAttackProgressState.campaignProgress?.nextBattleTitle !== '형주 남부 진입전' ) { throw new Error(`Expected post-Red Cliffs fire progress tab to complete known battles and point toward Jing Province preparation: ${JSON.stringify(postFireAttackProgressState?.campaignProgress)}`); } await page.screenshot({ path: 'dist/verification-post-redcliffs-fire-progress.png', fullPage: true }); + await page.mouse.click(1120, 38); + await page.waitForTimeout(180); + const jingzhouSortieState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); + if ( + !jingzhouSortieState?.sortieVisible || + !jingzhouSortieState.sortiePlan?.objectiveLine?.includes('형주 남부 진입전') || + !jingzhouSortieState.sortieRoster?.some((unit) => unit.id === 'zhuge-liang' && unit.recruited && unit.recommended) || + !jingzhouSortieState.sortieRoster?.some((unit) => unit.id === 'guan-yu' && unit.recommended) || + !jingzhouSortieState.sortieRoster?.some((unit) => unit.id === 'zhao-yun' && unit.recruited && unit.recommended) || + !jingzhouSortieState.sortieRoster?.some((unit) => unit.id === 'sun-qian' && unit.recruited && unit.recommended) || + !jingzhouSortieState.sortieRoster?.some((unit) => unit.id === 'mi-zhu' && unit.recruited && unit.recommended) || + jingzhouSortieState.sortieRoster?.some((unit) => unit.id === 'ma-liang') || + jingzhouSortieState.sortieRoster?.length < 8 || + jingzhouSortieState.sortiePlan?.maxCount !== 6 + ) { + throw new Error(`Expected twenty-second camp sortie prep to target Jing Province entry with existing recruited officers and no Ma Liang before victory: ${JSON.stringify(jingzhouSortieState)}`); + } + assertSortieTacticalRoster(jingzhouSortieState, [ + 'liu-bei', + 'guan-yu', + 'zhang-fei', + 'jian-yong', + 'mi-zhu', + 'sun-qian', + 'zhao-yun', + 'zhuge-liang' + ]); + + const jingzhouPriorityUnits = ['zhuge-liang', 'guan-yu', 'zhao-yun', 'sun-qian', 'mi-zhu']; + for (const unitId of jingzhouPriorityUnits) { + 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.id !== 'liu-bei' && !jingzhouPriorityUnits.includes(unit.id) + ); + if (removable) { + await clickSortieRosterUnit(page, removable.id); + } + await clickSortieRosterUnit(page, unitId); + } + } + + const jingzhouSortieReadyState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); + if ( + !jingzhouPriorityUnits.every((unitId) => + jingzhouSortieReadyState.sortieRoster?.some((unit) => unit.id === unitId && unit.selected) + ) || + jingzhouSortieReadyState.sortiePlan?.selectedCount !== 6 || + jingzhouSortieReadyState.sortiePlan?.recommendedSelectedCount < 6 + ) { + throw new Error(`Expected Jing Province entry sortie to deploy all recommended priority officers while preserving six-officer pressure: ${JSON.stringify(jingzhouSortieReadyState)}`); + } + await page.screenshot({ path: 'dist/verification-jingzhou-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-jingzhou-story.png', fullPage: true }); + + for (let i = 0; i < 40; i += 1) { + const enteredTwentyThirdBattle = await page.evaluate(() => { + const state = window.__HEROS_DEBUG__?.battle(); + return state?.scene === 'BattleScene' && state?.battleId === 'twenty-third-battle-jingzhou-south-entry'; + }); + if (enteredTwentyThirdBattle) { + 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 === 'twenty-third-battle-jingzhou-south-entry' && state?.battleOutcome === null && state?.phase === 'idle'; + }); + await page.screenshot({ path: 'dist/verification-twenty-third-battle.png', fullPage: true }); + + const twentyThirdBattleState = await page.evaluate(() => window.__HEROS_DEBUG__?.battle()); + const twentyThirdEnemies = twentyThirdBattleState.units.filter((unit) => unit.faction === 'enemy'); + const twentyThirdAllies = twentyThirdBattleState.units.filter((unit) => unit.faction === 'ally'); + const twentyThirdEnemyBehaviors = new Set(twentyThirdEnemies.map((unit) => unit.ai)); + if ( + twentyThirdBattleState.camera?.mapWidth !== 46 || + twentyThirdBattleState.camera?.mapHeight !== 32 || + twentyThirdBattleState.victoryConditionLabel !== '형도영 퇴각' || + twentyThirdEnemies.length < 17 || + !twentyThirdEnemyBehaviors.has('aggressive') || + !twentyThirdEnemyBehaviors.has('guard') || + !twentyThirdEnemyBehaviors.has('hold') || + !twentyThirdEnemies.some((unit) => unit.id === 'jing-south-leader-xing-daorong') || + !twentyThirdAllies.some((unit) => unit.id === 'zhuge-liang') || + !twentyThirdAllies.some((unit) => unit.id === 'zhao-yun') || + !twentyThirdAllies.some((unit) => unit.id === 'sun-qian') || + twentyThirdAllies.some((unit) => unit.id === 'ma-liang') + ) { + throw new Error(`Expected twenty-third battle to use Jing Province south map, Xing Daorong objective, mixed AI, selected allied officers, and no Ma Liang before victory: ${JSON.stringify(twentyThirdBattleState)}`); + } + + 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 twentyThirdCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); + if ( + twentyThirdCampState?.campBattleId !== 'twenty-third-battle-jingzhou-south-entry' || + twentyThirdCampState.campTitle !== '형주 남부 진입 후 군영' || + twentyThirdCampState.availableDialogueIds?.length !== 3 || + !twentyThirdCampState.availableDialogueIds.every((id) => id.endsWith('jingzhou-south-entry')) || + twentyThirdCampState.availableVisitIds?.length !== 2 || + !twentyThirdCampState.campaign?.roster?.some((unit) => unit.id === 'ma-liang') || + !twentyThirdCampState.sortieRoster?.some((unit) => unit.id === 'ma-liang' && unit.recruited) || + twentyThirdCampState.rosterCollection?.total < 9 || + twentyThirdCampState.rosterCollection?.reserveTrainingCount < 2 || + twentyThirdCampState.rosterCollection?.reserveTrainingExp < 12 + ) { + throw new Error(`Expected twenty-third camp to recruit Ma Liang and expose Jing Province dialogue/visit sets: ${JSON.stringify(twentyThirdCampState)}`); + } + await page.screenshot({ path: 'dist/verification-jingzhou-camp.png', fullPage: true }); + + await page.mouse.click(966, 38); + await page.waitForTimeout(180); + const postJingzhouProgressState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); + if ( + postJingzhouProgressState?.activeTab !== 'progress' || + postJingzhouProgressState.campaignProgress?.completedKnown !== 23 || + postJingzhouProgressState.campaignProgress?.totalKnown !== 23 || + postJingzhouProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' || + postJingzhouProgressState.campaignProgress?.latestBattleTitle !== '형주 남부 진입전' || + postJingzhouProgressState.campaignProgress?.nextBattleTitle !== '준비 중' + ) { + throw new Error(`Expected post-Jing Province progress tab to complete the new chapter opening and pause on next commandery preparation: ${JSON.stringify(postJingzhouProgressState?.campaignProgress)}`); + } + await page.screenshot({ path: 'dist/verification-post-jingzhou-progress.png', fullPage: true }); + await page.evaluate(() => window.__HEROS_GAME__?.scene.start('TitleScene')); await page.waitForFunction(() => { const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; @@ -2464,7 +2607,7 @@ try { return activeScenes.includes('CampScene'); }); - console.log(`Verified title-to-twenty-second-battle flow, recruit sortie selection, result states, and debug API at ${targetUrl}`); + console.log(`Verified title-to-twenty-third-battle flow, recruited officer sortie selection, Ma Liang join, result states, and debug API at ${targetUrl}`); } finally { await browser?.close(); if (serverProcess && !serverProcess.killed) { diff --git a/src/assets/images/battle/twenty-third-battle-map.svg b/src/assets/images/battle/twenty-third-battle-map.svg new file mode 100644 index 0000000..2e57ac6 --- /dev/null +++ b/src/assets/images/battle/twenty-third-battle-map.svg @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/game/data/battles.ts b/src/game/data/battles.ts index 86624f3..573bbe9 100644 --- a/src/game/data/battles.ts +++ b/src/game/data/battles.ts @@ -47,6 +47,10 @@ import { twentySecondBattleMap, twentySecondBattleUnits, twentySecondBattleVictoryPages, + twentyThirdBattleBonds, + twentyThirdBattleMap, + twentyThirdBattleUnits, + twentyThirdBattleVictoryPages, twentiethBattleBonds, twentiethBattleMap, twentiethBattleUnits, @@ -115,7 +119,8 @@ export type BattleScenarioId = | 'nineteenth-battle-changban-refuge' | 'twentieth-battle-jiangdong-envoy' | 'twenty-first-battle-red-cliffs-vanguard' - | 'twenty-second-battle-red-cliffs-fire'; + | 'twenty-second-battle-red-cliffs-fire' + | 'twenty-third-battle-jingzhou-south-entry'; export type BattleObjectiveKind = 'defeat-leader' | 'keep-unit-alive' | 'secure-terrain' | 'quick-victory'; @@ -1361,6 +1366,69 @@ export const twentySecondBattleScenario: BattleScenarioDefinition = { nextCampScene: 'CampScene' }; +export const twentyThirdBattleScenario: BattleScenarioDefinition = { + id: 'twenty-third-battle-jingzhou-south-entry', + title: '형주 남부 진입전', + victoryConditionLabel: '형도영 퇴각', + defeatConditionLabel: '유비 또는 제갈량 퇴각', + openingObjectiveLines: [ + '적벽의 불길로 조조의 기세가 꺾인 지금, 유비군은 형주 남부의 길과 민심을 열어야 합니다.', + '형도영의 가병은 영릉으로 이어지는 길목과 마을을 붙잡고 있습니다. 성채만 몰아치면 마을이 흔들리니 보급 거점을 함께 지키십시오.', + '유비와 제갈량을 지키며 형도영을 물리치고, 남부 마을을 안정시키면 마량이 군영에 합류합니다.' + ], + map: twentyThirdBattleMap, + units: twentyThirdBattleUnits, + bonds: twentyThirdBattleBonds, + mapTextureKey: 'battle-map-twenty-third', + leaderUnitId: 'jing-south-leader-xing-daorong', + quickVictoryTurnLimit: 32, + baseVictoryGold: 3020, + objectives: [ + { + id: 'leader', + kind: 'defeat-leader', + label: '형도영 퇴각', + rewardGold: 2000, + unitId: 'jing-south-leader-xing-daorong' + }, + { + id: 'liu-bei', + kind: 'keep-unit-alive', + label: '유비 생존', + rewardGold: 600, + unitId: 'liu-bei' + }, + { + id: 'zhuge-liang', + kind: 'keep-unit-alive', + label: '제갈량 생존', + rewardGold: 900, + unitId: 'zhuge-liang' + }, + { + id: 'southern-village', + kind: 'secure-terrain', + label: '남부 마을 안정', + rewardGold: 980, + terrain: 'village' + }, + { + id: 'quick', + kind: 'quick-victory', + label: '32턴 이내 승리', + rewardGold: 760, + maxTurn: 32 + } + ], + defeatConditions: [ + { kind: 'unit-defeated', unitId: 'liu-bei' }, + { kind: 'unit-defeated', unitId: 'zhuge-liang' } + ], + itemRewards: ['콩 9', '상처약 7', '탁주 4', '형주 진입 문서 +1', '마량 합류'], + victoryPages: twentyThirdBattleVictoryPages, + nextCampScene: 'CampScene' +}; + export const defaultBattleScenarioId: BattleScenarioId = firstBattleScenario.id; export const battleScenarios: Record = { @@ -1385,7 +1453,8 @@ export const battleScenarios: Record 'nineteenth-battle-changban-refuge': nineteenthBattleScenario, 'twentieth-battle-jiangdong-envoy': twentiethBattleScenario, 'twenty-first-battle-red-cliffs-vanguard': twentyFirstBattleScenario, - 'twenty-second-battle-red-cliffs-fire': twentySecondBattleScenario + 'twenty-second-battle-red-cliffs-fire': twentySecondBattleScenario, + 'twenty-third-battle-jingzhou-south-entry': twentyThirdBattleScenario }; export const defaultBattleScenario = battleScenarios[defaultBattleScenarioId]; diff --git a/src/game/data/campaignFlow.ts b/src/game/data/campaignFlow.ts index 4ca39b3..469220a 100644 --- a/src/game/data/campaignFlow.ts +++ b/src/game/data/campaignFlow.ts @@ -19,6 +19,7 @@ import { thirteenthBattleScenario, twentyFirstBattleScenario, twentySecondBattleScenario, + twentyThirdBattleScenario, twentiethBattleScenario, twelfthBattleScenario, thirdBattleScenario, @@ -62,6 +63,8 @@ import { twentyFirstBattleVictoryPages, twentySecondBattleIntroPages, twentySecondBattleVictoryPages, + twentyThirdBattleIntroPages, + twentyThirdBattleVictoryPages, twentiethBattleIntroPages, twentiethBattleVictoryPages, twelfthBattleIntroPages, @@ -305,13 +308,24 @@ const sortieFlows: Record = { }, [twentySecondBattleScenario.id]: { afterBattleId: twentySecondBattleScenario.id, - eyebrow: '다음 장 준비', - title: '형주 확보 준비', + eyebrow: '다음 전장', + title: twentyThirdBattleScenario.title, description: - '적벽의 불길로 조조의 남하가 꺾였습니다. 이제 유비군은 머물 땅을 마련하기 위해 형주 남부와 익주로 이어질 근거지를 준비해야 합니다.', - rewardHint: '다음 장: 형주 남부 확보 준비 중', - pages: twentySecondBattleVictoryPages, - unavailableNotice: '형주 남부 확보 장은 다음 작업에서 이어집니다. 군영에서 전후 수습, 공명, 보급을 정비하십시오.' + '적벽의 불길로 조조의 남하가 꺾였습니다. 유비군은 이제 형주 남부의 길목을 열고, 백성을 먼저 안정시키며 머물 땅을 마련해야 합니다.', + rewardHint: `예상 보상: ${twentyThirdBattleScenario.title} 개방 / 마량 합류`, + nextBattleId: twentyThirdBattleScenario.id, + campaignStep: 'twenty-third-battle', + pages: [...twentySecondBattleVictoryPages, ...twentyThirdBattleIntroPages] + }, + [twentyThirdBattleScenario.id]: { + afterBattleId: twentyThirdBattleScenario.id, + eyebrow: '다음 장 준비', + title: '영릉 안정과 다음 군현', + description: + '형주 남부의 첫 길목을 열고 마량이 합류했습니다. 이제 계양, 무릉, 장사까지 차례로 설득하고 지켜야 형주·익주 확보의 기반이 단단해집니다.', + rewardHint: '다음 장: 계양·무릉·장사 확보 준비 중', + pages: twentyThirdBattleVictoryPages, + unavailableNotice: '다음 형주 남부 군현 확보 장은 다음 작업에서 이어집니다. 군영에서 마량과 대화하고 출전 후보를 정비하십시오.' } }; diff --git a/src/game/data/scenario.ts b/src/game/data/scenario.ts index 3c7be50..6695f33 100644 --- a/src/game/data/scenario.ts +++ b/src/game/data/scenario.ts @@ -1448,6 +1448,68 @@ export const twentySecondBattleVictoryPages: StoryPage[] = [ } ]; +export const twentyThirdBattleIntroPages: StoryPage[] = [ + { + id: 'twenty-third-jingzhou-cause', + bgm: 'militia-theme', + chapter: '형주 남부의 문', + background: 'story-militia', + speaker: '유비', + portrait: 'liuBei', + text: '적벽의 불길은 조조를 막았지만 백성이 기대어 살 땅은 아직 없소. 형주 남부의 군현을 힘으로 짓밟지 말고, 먼저 길과 민심을 열어야 하오.' + }, + { + id: 'twenty-third-zhuge-plan', + bgm: 'battle-prep', + chapter: '군현을 여는 계책', + background: 'story-three-heroes', + speaker: '제갈량', + text: '영릉의 유도는 아직 마음을 정하지 못했고, 형도영은 길목을 막아 자신의 힘을 보이려 합니다. 그를 물리치되 백성을 다치게 하지 않으면 형주 남부가 움직일 것입니다.' + }, + { + id: 'twenty-third-ma-liang-rumor', + bgm: 'story-dark', + chapter: '백미의 소문', + background: 'story-liu-bei', + speaker: '손건', + text: '형주 선비들 사이에 마량이라는 이름이 오갑니다. 눈썹이 희어 백미라 불리는 사람인데, 남부 군현의 민심과 길을 누구보다 잘 안다고 합니다.' + }, + { + id: 'twenty-third-jingzhou-sortie', + bgm: 'battle-prep', + chapter: '형주 남부 진입전', + background: 'story-sortie', + speaker: '제갈량', + text: '형도영의 가병이 남부 길목과 마을을 막고 있습니다. 성급히 몰아붙이면 백성이 흩어지니, 전열을 세우고 마을을 지키며 길목을 확보하십시오.' + } +]; + +export const twentyThirdBattleVictoryPages: StoryPage[] = [ + { + id: 'twenty-third-victory-southern-road', + bgm: 'militia-theme', + chapter: '남부 길목이 열리다', + background: 'story-sortie', + text: '형도영이 물러서자 남부 군현으로 이어지는 길이 열렸습니다. 유비군은 약탈 없이 마을을 안정시키며 적벽 이후 처음으로 머물 땅의 윤곽을 얻었습니다.' + }, + { + id: 'twenty-third-ma-liang-joins', + bgm: 'battle-prep', + chapter: '마량의 합류', + background: 'story-militia', + speaker: '마량', + text: '주공께서 백성을 먼저 살피셨다는 말을 들었습니다. 형주 남부의 길과 사람을 잇는 일이라면, 제 작은 글과 말도 보탬이 될 것입니다.' + }, + { + id: 'twenty-third-next-commandery', + bgm: 'story-dark', + chapter: '군현을 차례로', + background: 'story-three-heroes', + speaker: '제갈량', + text: '영릉의 문은 열렸지만 형주 남부 전체가 움직인 것은 아닙니다. 계양, 무릉, 장사까지 차례로 설득하고 지켜야 비로소 근거지가 됩니다.' + } +]; + export const firstBattleMap: BattleMap = { width: 20, height: 18, @@ -1725,6 +1787,12 @@ export const twentySecondBattleMap: BattleMap = { terrain: createTwentySecondBattleTerrain() }; +export const twentyThirdBattleMap: BattleMap = { + width: 46, + height: 32, + terrain: createTwentyThirdBattleTerrain() +}; + export const firstBattleUnits: UnitData[] = [ { id: 'liu-bei', @@ -3681,6 +3749,30 @@ export const zhugeRecruitUnits: UnitData[] = [ } ]; +export const jingzhouRecruitUnits: UnitData[] = [ + { + id: 'ma-liang', + name: '마량', + faction: 'ally', + className: '백미 책사', + classKey: 'strategist', + level: 11, + exp: 0, + hp: 30, + maxHp: 30, + attack: 8, + move: 4, + stats: { might: 36, intelligence: 92, leadership: 80, agility: 70, luck: 88 }, + equipment: { + weapon: { itemId: 'white-feather-fan', level: 1, exp: 10 }, + armor: { itemId: 'cloth-armor', level: 2, exp: 8 }, + accessory: { itemId: 'war-manual', level: 1, exp: 0 } + }, + x: 7, + y: 22 + } +]; + const eighthBattleAllyPositions: Record = { 'liu-bei': { x: 2, y: 17 }, 'guan-yu': { x: 3, y: 17 }, @@ -8600,6 +8692,384 @@ export const twentySecondBattleUnits: UnitData[] = [ } ]; +const twentyThirdBattleAllyPositions: Record = { + 'liu-bei': { x: 4, y: 25 }, + 'guan-yu': { x: 6, y: 23 }, + 'zhang-fei': { x: 6, y: 27 }, + 'jian-yong': { x: 3, y: 28 }, + 'mi-zhu': { x: 5, y: 29 }, + 'sun-qian': { x: 8, y: 25 }, + 'zhao-yun': { x: 8, y: 22 }, + 'zhuge-liang': { x: 6, y: 24 } +}; + +export const twentyThirdBattleUnits: UnitData[] = [ + ...[ + ...firstBattleUnits.filter((unit) => unit.faction === 'ally'), + ...xuzhouRecruitUnits, + ...caoBreakRecruitUnits, + ...liuBiaoRecruitUnits, + ...zhugeRecruitUnits + ].map((unit) => placeScenarioUnit(unit, twentyThirdBattleAllyPositions[unit.id] ?? { x: unit.x, y: unit.y })), + { + id: 'jing-south-scout-a', + name: '형주 남부 척후', + faction: 'enemy', + className: '남부 척후', + classKey: 'bandit', + level: 30, + exp: 48, + hp: 82, + maxHp: 82, + attack: 33, + move: 4, + stats: { might: 114, intelligence: 84, leadership: 90, agility: 110, luck: 76 }, + equipment: { + weapon: { itemId: 'yellow-turban-saber', level: 4, exp: 18 }, + armor: { itemId: 'rebel-vest', level: 4, exp: 8 }, + accessory: { itemId: 'grain-pouch', level: 1, exp: 0 } + }, + x: 13, + y: 24 + }, + { + id: 'jing-south-scout-b', + name: '형주 남부 척후', + faction: 'enemy', + className: '마을 척후', + classKey: 'bandit', + level: 30, + exp: 48, + hp: 82, + maxHp: 82, + attack: 33, + move: 4, + stats: { might: 114, intelligence: 84, leadership: 90, agility: 110, luck: 76 }, + equipment: { + weapon: { itemId: 'yellow-turban-saber', level: 4, exp: 18 }, + armor: { itemId: 'rebel-vest', level: 4, exp: 8 }, + accessory: { itemId: 'grain-pouch', level: 1, exp: 0 } + }, + x: 14, + y: 28 + }, + { + id: 'jing-south-infantry-a', + name: '영릉 가병', + faction: 'enemy', + className: '영릉 가병', + classKey: 'yellowTurban', + level: 31, + exp: 50, + hp: 108, + maxHp: 108, + attack: 35, + move: 3, + stats: { might: 124, intelligence: 84, leadership: 104, agility: 92, luck: 76 }, + equipment: { + weapon: { itemId: 'iron-spear', level: 4, exp: 30 }, + armor: { itemId: 'lamellar-armor', level: 4, exp: 18 }, + accessory: { itemId: 'war-manual', level: 1, exp: 0 } + }, + x: 18, + y: 22 + }, + { + id: 'jing-south-infantry-b', + name: '영릉 가병', + faction: 'enemy', + className: '마을 가병', + classKey: 'yellowTurban', + level: 31, + exp: 50, + hp: 108, + maxHp: 108, + attack: 35, + move: 3, + stats: { might: 124, intelligence: 84, leadership: 104, agility: 92, luck: 76 }, + equipment: { + weapon: { itemId: 'iron-spear', level: 4, exp: 30 }, + armor: { itemId: 'lamellar-armor', level: 4, exp: 18 }, + accessory: { itemId: 'war-manual', level: 1, exp: 0 } + }, + x: 20, + y: 25 + }, + { + id: 'jing-south-infantry-c', + name: '영릉 가병', + faction: 'enemy', + className: '관문 가병', + classKey: 'yellowTurban', + level: 31, + exp: 52, + hp: 110, + maxHp: 110, + attack: 35, + move: 3, + stats: { might: 125, intelligence: 85, leadership: 106, agility: 92, luck: 76 }, + equipment: { + weapon: { itemId: 'iron-spear', level: 4, exp: 34 }, + armor: { itemId: 'reinforced-lamellar', level: 4, exp: 20 }, + accessory: { itemId: 'war-manual', level: 1, exp: 0 } + }, + x: 28, + y: 18 + }, + { + id: 'jing-south-infantry-d', + name: '영릉 가병', + faction: 'enemy', + className: '관문 가병', + classKey: 'yellowTurban', + level: 31, + exp: 52, + hp: 110, + maxHp: 110, + attack: 35, + move: 3, + stats: { might: 125, intelligence: 85, leadership: 106, agility: 92, luck: 76 }, + equipment: { + weapon: { itemId: 'iron-spear', level: 4, exp: 34 }, + armor: { itemId: 'reinforced-lamellar', level: 4, exp: 20 }, + accessory: { itemId: 'war-manual', level: 1, exp: 0 } + }, + x: 30, + y: 21 + }, + { + id: 'jing-south-archer-a', + name: '영릉 궁병', + faction: 'enemy', + className: '마을 궁병', + classKey: 'archer', + level: 30, + exp: 48, + hp: 80, + maxHp: 80, + attack: 34, + move: 3, + stats: { might: 108, intelligence: 98, leadership: 94, agility: 106, luck: 78 }, + equipment: { + weapon: { itemId: 'short-bow', level: 4, exp: 26 }, + armor: { itemId: 'cloth-armor', level: 4, exp: 16 }, + accessory: { itemId: 'wind-quiver', level: 1, exp: 0 } + }, + x: 18, + y: 18 + }, + { + id: 'jing-south-archer-b', + name: '영릉 궁병', + faction: 'enemy', + className: '언덕 궁병', + classKey: 'archer', + level: 31, + exp: 50, + hp: 82, + maxHp: 82, + attack: 35, + move: 3, + stats: { might: 110, intelligence: 100, leadership: 96, agility: 108, luck: 78 }, + equipment: { + weapon: { itemId: 'short-bow', level: 4, exp: 32 }, + armor: { itemId: 'cloth-armor', level: 4, exp: 20 }, + accessory: { itemId: 'wind-quiver', level: 1, exp: 0 } + }, + x: 25, + y: 16 + }, + { + id: 'jing-south-archer-c', + name: '영릉 궁병', + faction: 'enemy', + className: '성채 궁병', + classKey: 'archer', + level: 31, + exp: 52, + hp: 84, + maxHp: 84, + attack: 35, + move: 3, + stats: { might: 110, intelligence: 100, leadership: 96, agility: 108, luck: 78 }, + equipment: { + weapon: { itemId: 'short-bow', level: 4, exp: 36 }, + armor: { itemId: 'cloth-armor', level: 4, exp: 22 }, + accessory: { itemId: 'wind-quiver', level: 1, exp: 0 } + }, + x: 36, + y: 14 + }, + { + id: 'jing-south-cavalry-a', + name: '영릉 기병', + faction: 'enemy', + className: '남부 기병', + classKey: 'cavalry', + level: 31, + exp: 52, + hp: 116, + maxHp: 116, + attack: 37, + move: 5, + stats: { might: 130, intelligence: 84, leadership: 104, agility: 122, luck: 78 }, + equipment: { + weapon: { itemId: 'iron-spear', level: 4, exp: 40 }, + armor: { itemId: 'lamellar-armor', level: 4, exp: 24 }, + accessory: { itemId: 'bravery-token', level: 1, exp: 0 } + }, + x: 17, + y: 29 + }, + { + id: 'jing-south-cavalry-b', + name: '영릉 기병', + faction: 'enemy', + className: '남부 기병', + classKey: 'cavalry', + level: 31, + exp: 52, + hp: 116, + maxHp: 116, + attack: 37, + move: 5, + stats: { might: 130, intelligence: 84, leadership: 104, agility: 122, luck: 78 }, + equipment: { + weapon: { itemId: 'iron-spear', level: 4, exp: 40 }, + armor: { itemId: 'lamellar-armor', level: 4, exp: 24 }, + accessory: { itemId: 'bravery-token', level: 1, exp: 0 } + }, + x: 26, + y: 27 + }, + { + id: 'jing-south-cavalry-c', + name: '영릉 기병', + faction: 'enemy', + className: '성채 기병', + classKey: 'cavalry', + level: 32, + exp: 54, + hp: 120, + maxHp: 120, + attack: 38, + move: 5, + stats: { might: 132, intelligence: 86, leadership: 106, agility: 124, luck: 79 }, + equipment: { + weapon: { itemId: 'iron-spear', level: 4, exp: 46 }, + armor: { itemId: 'reinforced-lamellar', level: 4, exp: 28 }, + accessory: { itemId: 'bravery-token', level: 1, exp: 0 } + }, + x: 35, + y: 23 + }, + { + id: 'jing-south-guard-a', + name: '형도영 친위', + faction: 'enemy', + className: '영릉 친위', + classKey: 'yellowTurban', + level: 32, + exp: 56, + hp: 122, + maxHp: 122, + attack: 38, + move: 3, + stats: { might: 132, intelligence: 88, leadership: 112, agility: 96, luck: 80 }, + equipment: { + weapon: { itemId: 'iron-spear', level: 4, exp: 52 }, + armor: { itemId: 'reinforced-lamellar', level: 4, exp: 34 }, + accessory: { itemId: 'war-manual', level: 1, exp: 0 } + }, + x: 38, + y: 16 + }, + { + id: 'jing-south-guard-b', + name: '형도영 친위', + faction: 'enemy', + className: '영릉 친위', + classKey: 'yellowTurban', + level: 32, + exp: 56, + hp: 122, + maxHp: 122, + attack: 38, + move: 3, + stats: { might: 132, intelligence: 88, leadership: 112, agility: 96, luck: 80 }, + equipment: { + weapon: { itemId: 'iron-spear', level: 4, exp: 52 }, + armor: { itemId: 'reinforced-lamellar', level: 4, exp: 34 }, + accessory: { itemId: 'war-manual', level: 1, exp: 0 } + }, + x: 39, + y: 19 + }, + { + id: 'jing-south-strategist-a', + name: '영릉 문객', + faction: 'enemy', + className: '남부 문객', + classKey: 'archer', + level: 31, + exp: 52, + hp: 86, + maxHp: 86, + attack: 34, + move: 3, + stats: { might: 84, intelligence: 106, leadership: 100, agility: 92, luck: 80 }, + equipment: { + weapon: { itemId: 'short-bow', level: 4, exp: 44 }, + armor: { itemId: 'cloth-armor', level: 4, exp: 28 }, + accessory: { itemId: 'war-manual', level: 1, exp: 0 } + }, + x: 36, + y: 13 + }, + { + id: 'jing-south-strategist-b', + name: '영릉 문객', + faction: 'enemy', + className: '남부 문객', + classKey: 'archer', + level: 31, + exp: 52, + hp: 86, + maxHp: 86, + attack: 34, + move: 3, + stats: { might: 84, intelligence: 106, leadership: 100, agility: 92, luck: 80 }, + equipment: { + weapon: { itemId: 'short-bow', level: 4, exp: 44 }, + armor: { itemId: 'cloth-armor', level: 4, exp: 28 }, + accessory: { itemId: 'war-manual', level: 1, exp: 0 } + }, + x: 40, + y: 14 + }, + { + id: 'jing-south-leader-xing-daorong', + name: '형도영', + faction: 'enemy', + className: '영릉 맹장', + classKey: 'rebelLeader', + level: 33, + exp: 58, + hp: 164, + maxHp: 164, + attack: 41, + move: 4, + stats: { might: 136, intelligence: 76, leadership: 118, agility: 96, luck: 80 }, + equipment: { + weapon: { itemId: 'leader-axe', level: 4, exp: 58 }, + armor: { itemId: 'reinforced-lamellar', level: 4, exp: 40 }, + accessory: { itemId: 'bravery-token', level: 1, exp: 0 } + }, + x: 42, + y: 17 + } +]; + export const firstBattleBonds: BattleBond[] = [ { id: 'liu-bei__guan-yu', @@ -8727,6 +9197,33 @@ export const zhugeRecruitBonds: BattleBond[] = [ } ]; +export const jingzhouRecruitBonds: BattleBond[] = [ + { + id: 'liu-bei__ma-liang', + unitIds: ['liu-bei', 'ma-liang'], + title: '백미의 조언', + level: 36, + exp: 0, + description: '유비의 인의와 마량의 형주 식견이 만나 남부 군현의 민심을 차분히 묶어 냅니다.' + }, + { + id: 'zhuge-liang__ma-liang', + unitIds: ['zhuge-liang', 'ma-liang'], + title: '형주 문맥', + level: 34, + exp: 0, + description: '제갈량의 큰 계책과 마량의 현지 정보가 맞물려 형주 남부의 길과 사람을 읽어냅니다.' + }, + { + id: 'sun-qian__ma-liang', + unitIds: ['sun-qian', 'ma-liang'], + title: '군현 문서', + level: 30, + exp: 0, + description: '손건의 외교 문서와 마량의 남부 군현 기록이 합쳐져 설득과 보급의 실마리를 만듭니다.' + } +]; + export const secondBattleBonds: BattleBond[] = firstBattleBonds.map(cloneBattleBondForScenario); export const thirdBattleBonds: BattleBond[] = firstBattleBonds.map(cloneBattleBondForScenario); export const fourthBattleBonds: BattleBond[] = firstBattleBonds.map(cloneBattleBondForScenario); @@ -8754,6 +9251,7 @@ export const nineteenthBattleBonds: BattleBond[] = [...eighteenthBattleBonds].ma export const twentiethBattleBonds: BattleBond[] = [...nineteenthBattleBonds].map(cloneBattleBondForScenario); export const twentyFirstBattleBonds: BattleBond[] = [...twentiethBattleBonds].map(cloneBattleBondForScenario); export const twentySecondBattleBonds: BattleBond[] = [...twentyFirstBattleBonds].map(cloneBattleBondForScenario); +export const twentyThirdBattleBonds: BattleBond[] = [...twentySecondBattleBonds].map(cloneBattleBondForScenario); function createEighthBattleTerrain(): TerrainType[][] { return Array.from({ length: 22 }, (_, y) => @@ -9488,6 +9986,69 @@ function createTwentySecondBattleTerrain(): TerrainType[][] { ); } +function createTwentyThirdBattleTerrain(): TerrainType[][] { + return Array.from({ length: 32 }, (_, y) => + Array.from({ length: 46 }, (_, x): TerrainType => { + if ((x <= 4 && y >= 24 && y <= 30) || (x >= 5 && x <= 9 && y >= 27 && y <= 31)) { + return 'camp'; + } + if ((x >= 38 && x <= 45 && y >= 14 && y <= 22) || (x >= 34 && x <= 38 && y >= 16 && y <= 20)) { + return 'fort'; + } + if ( + (x >= 12 && x <= 15 && y >= 23 && y <= 25) || + (x >= 24 && x <= 27 && y >= 18 && y <= 20) || + (x >= 33 && x <= 35 && y >= 15 && y <= 17) + ) { + return 'village'; + } + if ( + (y === 25 && x >= 3 && x <= 18) || + (y === 22 && x >= 16 && x <= 29) || + (y === 18 && x >= 28 && x <= 40) || + (x === 38 && y >= 14 && y <= 24) + ) { + return 'road'; + } + if ( + (x >= 7 && x <= 18 && y === 29 - Math.floor(x / 2)) || + (x >= 19 && x <= 36 && y === Math.floor(x / 2) + 8) || + (x >= 31 && x <= 39 && y === 18) + ) { + return 'road'; + } + if ((x === 20 || x === 21) && y >= 0 && y <= 31) { + return 'river'; + } + if ((x === 29 || x === 30) && y >= 0 && y <= 20) { + return 'river'; + } + if ((x === 25 || x === 26) && y >= 24 && y <= 31) { + return 'river'; + } + if ( + (x >= 4 && x <= 15 && y >= 8 && y <= 19) || + (x >= 15 && x <= 21 && y >= 25 && y <= 31) || + (x <= 9 && y <= 8) || + (x >= 31 && x <= 44 && y <= 10) + ) { + return 'forest'; + } + if ( + (x >= 10 && x <= 19 && y >= 2 && y <= 7) || + (x >= 30 && x <= 39 && y >= 24 && y <= 31) || + (x >= 37 && x <= 45 && y >= 4 && y <= 9) + ) { + return 'hill'; + } + if ((x <= 1 && y <= 15) || (x >= 44 && y <= 12) || (x >= 44 && y >= 24)) { + return 'cliff'; + } + 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 8396173..2e3e09d 100644 --- a/src/game/scenes/BattleScene.ts +++ b/src/game/scenes/BattleScene.ts @@ -29,6 +29,7 @@ const unitTexture: Record = { 'zhang-fei': 'unit-zhang-fei', 'zhao-yun': 'unit-rebel-cavalry', 'zhuge-liang': 'unit-liu-bei', + 'ma-liang': 'unit-liu-bei', 'rebel-a': 'unit-rebel', 'rebel-b': 'unit-rebel', 'rebel-c': 'unit-rebel-archer', @@ -240,7 +241,24 @@ const unitTexture: Record = { 'redcliff-fire-guard-b': 'unit-rebel', 'redcliff-fire-strategist-a': 'unit-rebel-archer', 'redcliff-fire-strategist-b': 'unit-rebel-archer', - 'redcliff-fire-leader-cao-cao': 'unit-rebel-leader' + 'redcliff-fire-leader-cao-cao': 'unit-rebel-leader', + 'jing-south-scout-a': 'unit-rebel', + 'jing-south-scout-b': 'unit-rebel', + 'jing-south-infantry-a': 'unit-rebel', + 'jing-south-infantry-b': 'unit-rebel', + 'jing-south-infantry-c': 'unit-rebel', + 'jing-south-infantry-d': 'unit-rebel', + 'jing-south-archer-a': 'unit-rebel-archer', + 'jing-south-archer-b': 'unit-rebel-archer', + 'jing-south-archer-c': 'unit-rebel-archer', + 'jing-south-cavalry-a': 'unit-rebel-cavalry', + 'jing-south-cavalry-b': 'unit-rebel-cavalry', + 'jing-south-cavalry-c': 'unit-rebel-cavalry', + 'jing-south-guard-a': 'unit-rebel', + 'jing-south-guard-b': 'unit-rebel', + 'jing-south-strategist-a': 'unit-rebel-archer', + 'jing-south-strategist-b': 'unit-rebel-archer', + 'jing-south-leader-xing-daorong': 'unit-rebel-leader' }; const unitTextureByClass: Partial> = { @@ -661,7 +679,8 @@ const unitStrategyIds: Record = { 'jian-yong': ['aid', 'encourage', 'fireTactic'], 'mi-zhu': ['aid', 'encourage'], 'sun-qian': ['aid', 'encourage', 'fireTactic'], - 'zhuge-liang': ['aid', 'encourage', 'fireTactic'] + 'zhuge-liang': ['aid', 'encourage', 'fireTactic'], + 'ma-liang': ['aid', 'encourage', 'fireTactic'] }; const initialItemStocks: Record> = { @@ -672,7 +691,8 @@ const initialItemStocks: Record> = { 'mi-zhu': { bean: 2, salve: 1 }, 'sun-qian': { bean: 1, salve: 1 }, 'zhao-yun': { bean: 1, wine: 1 }, - 'zhuge-liang': { bean: 1, wine: 1 } + 'zhuge-liang': { bean: 1, wine: 1 }, + 'ma-liang': { bean: 1, salve: 1 } }; const attackRangeByClass: Partial> = { @@ -936,7 +956,24 @@ const enemyAiByUnitId: Record = { 'redcliff-fire-guard-b': 'guard', 'redcliff-fire-strategist-a': 'hold', 'redcliff-fire-strategist-b': 'hold', - 'redcliff-fire-leader-cao-cao': 'guard' + 'redcliff-fire-leader-cao-cao': 'guard', + 'jing-south-scout-a': 'aggressive', + 'jing-south-scout-b': 'aggressive', + 'jing-south-infantry-a': 'guard', + 'jing-south-infantry-b': 'guard', + 'jing-south-infantry-c': 'guard', + 'jing-south-infantry-d': 'guard', + 'jing-south-archer-a': 'hold', + 'jing-south-archer-b': 'hold', + 'jing-south-archer-c': 'hold', + 'jing-south-cavalry-a': 'aggressive', + 'jing-south-cavalry-b': 'aggressive', + 'jing-south-cavalry-c': 'aggressive', + 'jing-south-guard-a': 'guard', + 'jing-south-guard-b': 'guard', + 'jing-south-strategist-a': 'hold', + 'jing-south-strategist-b': 'hold', + 'jing-south-leader-xing-daorong': 'guard' }; const defaultEnemyAiByClass: Partial> = { diff --git a/src/game/scenes/BootScene.ts b/src/game/scenes/BootScene.ts index 17d7ac2..ef7249c 100644 --- a/src/game/scenes/BootScene.ts +++ b/src/game/scenes/BootScene.ts @@ -19,6 +19,7 @@ import thirteenthBattleMapUrl from '../../assets/images/battle/thirteenth-battle import thirdBattleMapUrl from '../../assets/images/battle/third-battle-map.svg'; import twentyFirstBattleMapUrl from '../../assets/images/battle/twenty-first-battle-map.svg'; import twentySecondBattleMapUrl from '../../assets/images/battle/twenty-second-battle-map.svg'; +import twentyThirdBattleMapUrl from '../../assets/images/battle/twenty-third-battle-map.svg'; import twentiethBattleMapUrl from '../../assets/images/battle/twentieth-battle-map.svg'; import twelfthBattleMapUrl from '../../assets/images/battle/twelfth-battle-map.svg'; import guanYuPortraitUrl from '../../assets/images/portraits/guan-yu.png'; @@ -102,6 +103,7 @@ export class BootScene extends Phaser.Scene { this.load.image('battle-map-twentieth', twentiethBattleMapUrl); this.load.image('battle-map-twenty-first', twentyFirstBattleMapUrl); this.load.image('battle-map-twenty-second', twentySecondBattleMapUrl); + this.load.image('battle-map-twenty-third', twentyThirdBattleMapUrl); this.load.image('portrait-liu-bei', liuBeiPortraitUrl); this.load.image('portrait-guan-yu', guanYuPortraitUrl); this.load.image('portrait-zhang-fei', zhangFeiPortraitUrl); diff --git a/src/game/scenes/CampScene.ts b/src/game/scenes/CampScene.ts index 89bebd2..238e7c5 100644 --- a/src/game/scenes/CampScene.ts +++ b/src/game/scenes/CampScene.ts @@ -9,6 +9,8 @@ import { caoBreakRecruitUnits, firstBattleBonds, firstBattleUnits, + jingzhouRecruitBonds, + jingzhouRecruitUnits, liuBiaoRecruitBonds, liuBiaoRecruitUnits, xuzhouRecruitBonds, @@ -295,7 +297,7 @@ const campaignTimelineChapters: CampaignTimelineChapter[] = [ title: '형주·익주 확보', period: '근본을 세우다', description: '형주와 익주를 기반으로 삼아 유비군이 유랑군에서 국가의 뿌리로 바뀌는 장입니다.', - battleIds: [], + battleIds: ['twenty-third-battle-jingzhou-south-entry'], nextHints: ['형주 방위', '익주 진입', '장수 대거 합류'] }, { @@ -330,7 +332,8 @@ const campBattleIds = { nineteenth: 'nineteenth-battle-changban-refuge', twentieth: 'twentieth-battle-jiangdong-envoy', twentyFirst: 'twenty-first-battle-red-cliffs-vanguard', - twentySecond: 'twenty-second-battle-red-cliffs-fire' + twentySecond: 'twenty-second-battle-red-cliffs-fire', + twentyThird: 'twenty-third-battle-jingzhou-south-entry' } as const; const requiredSortieUnitIds = new Set(['liu-bei']); @@ -569,6 +572,18 @@ const sortieRulesByBattleId: Partial