Add Xiaopei supply road battle
This commit is contained in:
@@ -27,14 +27,15 @@ Build a small complete tactical RPG loop that can grow into a longer Romance of
|
||||
- Seventh battle Xu Province rescue route against Cao Cao's vanguard, with Tao Qian entrusting Xu Province as the next endpoint
|
||||
- Sortie preparation selection in camp so Liu Bei is required and optional allied officers can be deployed or benched before battle
|
||||
- Xu Province camp recruitment for Jian Yong and Mi Zhu, including new strategist/quartermaster roles, bond dialogue, and selectable sortie participation
|
||||
- Flow verification script from title through the seventh battle victory, sortie selection, and camp save state
|
||||
- Eighth battle Xiaopei supply-road defense, where the expanded Xu Province roster can be selected into battle and Lu Bu's refuge arc is foreshadowed
|
||||
- Flow verification script from title through the eighth battle victory, recruit sortie selection, and camp save state
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. Add the Tao Qian handoff story and first Xu Province governance camp
|
||||
2. Use the expanded Xu Province roster in the next Lu Bu pressure arc so sortie selection has direct battle consequences
|
||||
3. Turn story-only endpoint bridges into a more explicit chapter selection/progress view
|
||||
4. Apply treasure equipment effects to damage, defense, recovery, and command rules
|
||||
1. Continue into the Lu Bu refuge and Xu Province loss chapter
|
||||
2. Add a chapter/progress view so long campaign arcs are easier to read between battles
|
||||
3. Apply treasure equipment effects to damage, defense, recovery, and command rules
|
||||
4. Add more recruitable officers as the campaign moves toward Cao Cao, Yuan Shao, Liu Biao, and Zhuge Liang
|
||||
5. Keep expanding scenarios through the long campaign instead of treating the current slice as an ending
|
||||
|
||||
## Content Direction
|
||||
|
||||
@@ -869,22 +869,67 @@ try {
|
||||
});
|
||||
await page.screenshot({ path: 'dist/verification-xuzhou-entrust-story.png', fullPage: true });
|
||||
|
||||
for (let i = 0; i < 8; i += 1) {
|
||||
const returnedToCamp = await page.evaluate(() => {
|
||||
const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? [];
|
||||
return activeScenes.includes('CampScene');
|
||||
for (let i = 0; i < 10; i += 1) {
|
||||
const enteredEighthBattle = await page.evaluate(() => {
|
||||
const state = window.__HEROS_DEBUG__?.battle();
|
||||
return state?.scene === 'BattleScene' && state?.battleId === 'eighth-battle-xiaopei-supply-road';
|
||||
});
|
||||
if (returnedToCamp) {
|
||||
if (enteredEighthBattle) {
|
||||
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 === 'eighth-battle-xiaopei-supply-road' && state?.battleOutcome === null && state?.phase === 'idle';
|
||||
});
|
||||
await page.screenshot({ path: 'dist/verification-eighth-battle.png', fullPage: true });
|
||||
|
||||
const eighthBattleState = await page.evaluate(() => window.__HEROS_DEBUG__?.battle());
|
||||
const eighthEnemies = eighthBattleState.units.filter((unit) => unit.faction === 'enemy');
|
||||
const eighthAllies = eighthBattleState.units.filter((unit) => unit.faction === 'ally');
|
||||
const eighthEnemyBehaviors = new Set(eighthEnemies.map((unit) => unit.ai));
|
||||
if (
|
||||
eighthBattleState.camera?.mapWidth !== 26 ||
|
||||
eighthBattleState.camera?.mapHeight !== 22 ||
|
||||
eighthBattleState.victoryConditionLabel !== '고순 격파' ||
|
||||
eighthEnemies.length < 11 ||
|
||||
!eighthEnemyBehaviors.has('aggressive') ||
|
||||
!eighthEnemyBehaviors.has('guard') ||
|
||||
!eighthEnemyBehaviors.has('hold') ||
|
||||
eighthAllies.some((unit) => unit.id === 'guan-yu') ||
|
||||
!eighthAllies.some((unit) => unit.id === 'liu-bei') ||
|
||||
!eighthAllies.some((unit) => unit.id === 'zhang-fei') ||
|
||||
!eighthAllies.some((unit) => unit.id === 'jian-yong')
|
||||
) {
|
||||
throw new Error(`Expected eighth battle to use expanded map, mixed AI, and selected recruit sortie: ${JSON.stringify(eighthBattleState)}`);
|
||||
}
|
||||
|
||||
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 eighthCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
|
||||
if (
|
||||
eighthCampState?.campBattleId !== 'eighth-battle-xiaopei-supply-road' ||
|
||||
eighthCampState.campTitle !== '소패 방위 군영' ||
|
||||
eighthCampState.availableDialogueIds?.length !== 3 ||
|
||||
!eighthCampState.availableDialogueIds.every((id) => id.endsWith('xiaopei')) ||
|
||||
!eighthCampState.campaign?.roster?.some((unit) => unit.id === 'jian-yong') ||
|
||||
!eighthCampState.campaign?.roster?.some((unit) => unit.id === 'mi-zhu')
|
||||
) {
|
||||
throw new Error(`Expected eighth camp to expose Xiaopei dialogue set and preserve recruits: ${JSON.stringify(eighthCampState)}`);
|
||||
}
|
||||
|
||||
await page.evaluate(() => window.__HEROS_GAME__?.scene.start('TitleScene'));
|
||||
await page.waitForFunction(() => {
|
||||
const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? [];
|
||||
@@ -896,7 +941,7 @@ try {
|
||||
return activeScenes.includes('CampScene');
|
||||
});
|
||||
|
||||
console.log(`Verified title-to-seventh-battle flow, sortie selection, result states, and debug API at ${targetUrl}`);
|
||||
console.log(`Verified title-to-eighth-battle flow, recruit sortie selection, result states, and debug API at ${targetUrl}`);
|
||||
} finally {
|
||||
await browser?.close();
|
||||
if (serverProcess && !serverProcess.killed) {
|
||||
|
||||
96
src/assets/images/battle/eighth-battle-map.svg
Normal file
96
src/assets/images/battle/eighth-battle-map.svg
Normal file
@@ -0,0 +1,96 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2600 2200" width="2600" height="2200">
|
||||
<defs>
|
||||
<filter id="grain">
|
||||
<feTurbulence type="fractalNoise" baseFrequency="0.02" numOctaves="4" seed="821" />
|
||||
<feColorMatrix type="saturate" values="0.62" />
|
||||
<feBlend mode="multiply" in2="SourceGraphic" />
|
||||
</filter>
|
||||
<pattern id="grass" width="96" height="96" patternUnits="userSpaceOnUse">
|
||||
<rect width="96" height="96" fill="#61764b" />
|
||||
<path d="M8 28h28M48 58h30M24 76l20-14M66 18l16 12" stroke="#b7c077" stroke-width="3" opacity="0.24" />
|
||||
<path d="M12 62l14-24M40 34l13-18M70 80l9-25" stroke="#2f4b2d" stroke-width="2" opacity="0.36" />
|
||||
<circle cx="20" cy="44" r="3" fill="#7f9258" opacity="0.42" />
|
||||
<circle cx="68" cy="39" r="4" fill="#3f5f35" opacity="0.32" />
|
||||
</pattern>
|
||||
<pattern id="earth" width="104" height="104" patternUnits="userSpaceOnUse">
|
||||
<rect width="104" height="104" fill="#9b7749" />
|
||||
<path d="M0 26c38-18 70 12 104-8M0 70c42-18 72 14 104-4" stroke="#c9a069" stroke-width="7" opacity="0.25" />
|
||||
<path d="M20 86l18-24M62 42l24-16M78 82l14-9" stroke="#57422c" stroke-width="3" opacity="0.34" />
|
||||
</pattern>
|
||||
<pattern id="forest" width="126" height="110" patternUnits="userSpaceOnUse">
|
||||
<rect width="126" height="110" fill="#31482e" />
|
||||
<circle cx="26" cy="42" r="30" fill="#203823" />
|
||||
<circle cx="62" cy="32" r="35" fill="#587448" />
|
||||
<circle cx="96" cy="50" r="31" fill="#294328" />
|
||||
<circle cx="52" cy="82" r="27" fill="#69844f" />
|
||||
<path d="M14 62c34 18 74 16 104-8M36 26c22 18 54 16 72-6" stroke="#142615" stroke-width="5" opacity="0.36" />
|
||||
</pattern>
|
||||
<pattern id="hill" width="132" height="98" patternUnits="userSpaceOnUse">
|
||||
<rect width="132" height="98" fill="#77704b" />
|
||||
<path d="M-10 84c48-62 92-82 134-64c36 15 56 44 74 80" fill="#55543a" opacity="0.72" />
|
||||
<path d="M18 76c36-40 72-50 108-32" fill="none" stroke="#aa9d66" stroke-width="10" opacity="0.3" />
|
||||
<path d="M40 36l30 20M82 22l22 22" stroke="#393828" stroke-width="4" opacity="0.34" />
|
||||
</pattern>
|
||||
<linearGradient id="river" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0" stop-color="#61a4bd" />
|
||||
<stop offset="0.48" stop-color="#2c6687" />
|
||||
<stop offset="1" stop-color="#17384f" />
|
||||
</linearGradient>
|
||||
<linearGradient id="wall" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#907a5f" />
|
||||
<stop offset="1" stop-color="#493728" />
|
||||
</linearGradient>
|
||||
<linearGradient id="roof" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#d79a41" />
|
||||
<stop offset="1" stop-color="#7a4d1d" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<rect width="2600" height="2200" fill="url(#grass)" />
|
||||
<g filter="url(#grain)">
|
||||
<path d="M-80 2020C210 1740 386 1542 604 1322C836 1086 1056 1018 1260 1014C1508 1008 1740 888 2040 708C2268 570 2440 484 2680 438" fill="none" stroke="#a77e4d" stroke-width="272" stroke-linecap="round" opacity="0.96" />
|
||||
<path d="M-80 2020C210 1740 386 1542 604 1322C836 1086 1056 1018 1260 1014C1508 1008 1740 888 2040 708C2268 570 2440 484 2680 438" fill="none" stroke="url(#earth)" stroke-width="178" stroke-linecap="round" opacity="0.96" />
|
||||
<path d="M300 2220C350 1830 304 1600 430 1382C560 1158 860 1110 1160 1028C1446 950 1788 764 2180 458" fill="none" stroke="#a77e4d" stroke-width="190" stroke-linecap="round" opacity="0.9" />
|
||||
<path d="M300 2220C350 1830 304 1600 430 1382C560 1158 860 1110 1160 1028C1446 950 1788 764 2180 458" fill="none" stroke="url(#earth)" stroke-width="114" stroke-linecap="round" opacity="0.86" />
|
||||
|
||||
<path d="M1460 -80c-88 250-58 420 72 552c158 160 194 350 106 568c-76 190-52 354 72 492c128 142 170 326 88 594" fill="none" stroke="#173c52" stroke-width="280" stroke-linecap="round" opacity="0.96" />
|
||||
<path d="M1460 -80c-88 250-58 420 72 552c158 160 194 350 106 568c-76 190-52 354 72 492c128 142 170 326 88 594" fill="none" stroke="url(#river)" stroke-width="184" stroke-linecap="round" opacity="0.98" />
|
||||
<path d="M1508 146c36 132 48 250-16 384M1638 782c88 108 102 238 34 386M1808 1560c58 108 68 216 24 332" fill="none" stroke="#c0eef5" stroke-width="14" opacity="0.34" />
|
||||
|
||||
<path d="M-40 40c260-76 518-42 760 106c40 146-54 282-276 408c-238-22-402-104-492-250Z" fill="url(#forest)" opacity="0.84" />
|
||||
<path d="M520 320c236-98 474-70 710 78c14 152-88 268-306 346c-246-32-382-170-404-424Z" fill="url(#forest)" opacity="0.82" />
|
||||
<path d="M18 762c314-118 610-64 872 156c-70 226-330 350-774 366C-16 1118-42 950 18 762Z" fill="url(#forest)" opacity="0.84" />
|
||||
<path d="M1800 1710c260-96 520-66 760 88v360c-298 76-572 18-820-176c-50-112-30-202 60-272Z" fill="url(#forest)" opacity="0.82" />
|
||||
<path d="M910 1540c236-84 432-58 590 72c-42 220-190 366-448 438c-148-76-206-246-142-510Z" fill="url(#hill)" opacity="0.84" />
|
||||
<path d="M2240 760c150-38 286-12 410 82v464c-170 64-322 36-454-82c-58-160-42-314 44-464Z" fill="url(#hill)" opacity="0.86" />
|
||||
|
||||
<g transform="translate(2020 230)">
|
||||
<rect x="-46" y="-26" width="520" height="318" rx="20" fill="#241d16" opacity="0.55" />
|
||||
<rect x="0" y="0" width="450" height="260" rx="16" fill="url(#wall)" />
|
||||
<rect x="42" y="50" width="366" height="170" fill="#765f43" stroke="#241d15" stroke-width="12" />
|
||||
<path d="M42 50h366M42 220h366M42 50v170M408 50v170" stroke="#35291e" stroke-width="10" />
|
||||
<rect x="82" y="104" width="74" height="84" fill="#98704a" />
|
||||
<rect x="198" y="78" width="88" height="110" fill="#9e7650" />
|
||||
<rect x="330" y="104" width="58" height="84" fill="#8c6846" />
|
||||
<path d="M62 104l58-48l62 48M184 78l60-52l70 52M318 104l42-38l54 38" fill="url(#roof)" stroke="#302316" stroke-width="9" />
|
||||
<path d="M220 188v-60h46v60" fill="#1f1712" />
|
||||
</g>
|
||||
|
||||
<g transform="translate(760 1220)">
|
||||
<rect x="0" y="0" width="230" height="130" rx="10" fill="#6d563b" stroke="#2a2119" stroke-width="10" />
|
||||
<path d="M-16 12L116-74l132 86Z" fill="url(#roof)" stroke="#342313" stroke-width="10" />
|
||||
<path d="M42 130v-58h46v58M140 130v-70h46v70" fill="#2a2119" opacity="0.8" />
|
||||
</g>
|
||||
<g transform="translate(1710 770)">
|
||||
<rect x="0" y="0" width="220" height="126" rx="10" fill="#6d563b" stroke="#2a2119" stroke-width="10" />
|
||||
<path d="M-14 14L110-62l128 76Z" fill="url(#roof)" stroke="#342313" stroke-width="10" />
|
||||
<path d="M78 126v-56h52v56" fill="#2a2119" opacity="0.8" />
|
||||
</g>
|
||||
|
||||
<g transform="translate(60 1630)">
|
||||
<rect x="0" y="0" width="315" height="230" rx="16" fill="#4f5c53" stroke="#222820" stroke-width="14" />
|
||||
<path d="M20 56h270M20 116h270M20 176h270M74 20v190M154 20v190M234 20v190" stroke="#263124" stroke-width="9" opacity="0.68" />
|
||||
<rect x="72" y="-34" width="168" height="76" fill="url(#roof)" stroke="#2c2115" stroke-width="10" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.7 KiB |
@@ -1,5 +1,9 @@
|
||||
import {
|
||||
firstBattleBonds,
|
||||
eighthBattleBonds,
|
||||
eighthBattleMap,
|
||||
eighthBattleUnits,
|
||||
eighthBattleVictoryPages,
|
||||
firstBattleMap,
|
||||
firstBattleUnits,
|
||||
firstBattleVictoryPages,
|
||||
@@ -40,7 +44,8 @@ export type BattleScenarioId =
|
||||
| 'fourth-battle-guangzong-camp'
|
||||
| 'fifth-battle-sishui-vanguard'
|
||||
| 'sixth-battle-jieqiao-relief'
|
||||
| 'seventh-battle-xuzhou-rescue';
|
||||
| 'seventh-battle-xuzhou-rescue'
|
||||
| 'eighth-battle-xiaopei-supply-road';
|
||||
|
||||
export type BattleObjectiveKind = 'defeat-leader' | 'keep-unit-alive' | 'secure-terrain' | 'quick-victory';
|
||||
|
||||
@@ -450,6 +455,59 @@ export const seventhBattleScenario: BattleScenarioDefinition = {
|
||||
nextCampScene: 'CampScene'
|
||||
};
|
||||
|
||||
export const eighthBattleScenario: BattleScenarioDefinition = {
|
||||
id: 'eighth-battle-xiaopei-supply-road',
|
||||
title: '소패 보급로 방위전',
|
||||
victoryConditionLabel: '고순 격파',
|
||||
defeatConditionLabel: '유비 퇴각',
|
||||
openingObjectiveLines: [
|
||||
'여포군의 척후와 습격병이 소패 보급로를 흔들고 있습니다. 고순을 격파하면 서주의 첫 방위선을 지킬 수 있습니다.',
|
||||
'기병은 길을 타고 빠르게 파고들고, 궁병은 강 건너와 언덕에서 사거리를 잡습니다. 마을과 창고 지형을 이용해 버티십시오.',
|
||||
'18턴 이내 승리하면 서주 보급망과 새 합류 무장의 명성이 크게 오릅니다.'
|
||||
],
|
||||
map: eighthBattleMap,
|
||||
units: eighthBattleUnits,
|
||||
bonds: eighthBattleBonds,
|
||||
mapTextureKey: 'battle-map-eighth',
|
||||
leaderUnitId: 'xiaopei-leader-gao-shun',
|
||||
quickVictoryTurnLimit: 18,
|
||||
baseVictoryGold: 1080,
|
||||
objectives: [
|
||||
{
|
||||
id: 'leader',
|
||||
kind: 'defeat-leader',
|
||||
label: '고순 격파',
|
||||
rewardGold: 640,
|
||||
unitId: 'xiaopei-leader-gao-shun'
|
||||
},
|
||||
{
|
||||
id: 'liu-bei',
|
||||
kind: 'keep-unit-alive',
|
||||
label: '유비 생존',
|
||||
rewardGold: 250,
|
||||
unitId: 'liu-bei'
|
||||
},
|
||||
{
|
||||
id: 'village',
|
||||
kind: 'secure-terrain',
|
||||
label: '소패 보급촌 확보',
|
||||
rewardGold: 360,
|
||||
terrain: 'village'
|
||||
},
|
||||
{
|
||||
id: 'quick',
|
||||
kind: 'quick-victory',
|
||||
label: '18턴 이내 승리',
|
||||
rewardGold: 300,
|
||||
maxTurn: 18
|
||||
}
|
||||
],
|
||||
defeatConditions: [{ kind: 'unit-defeated', unitId: 'liu-bei' }],
|
||||
itemRewards: ['콩 3', '상처약 2', '탁주 1', '서주 보급망 +1'],
|
||||
victoryPages: eighthBattleVictoryPages,
|
||||
nextCampScene: 'CampScene'
|
||||
};
|
||||
|
||||
export const defaultBattleScenarioId: BattleScenarioId = firstBattleScenario.id;
|
||||
|
||||
export const battleScenarios: Record<BattleScenarioId, BattleScenarioDefinition> = {
|
||||
@@ -459,7 +517,8 @@ export const battleScenarios: Record<BattleScenarioId, BattleScenarioDefinition>
|
||||
'fourth-battle-guangzong-camp': fourthBattleScenario,
|
||||
'fifth-battle-sishui-vanguard': fifthBattleScenario,
|
||||
'sixth-battle-jieqiao-relief': sixthBattleScenario,
|
||||
'seventh-battle-xuzhou-rescue': seventhBattleScenario
|
||||
'seventh-battle-xuzhou-rescue': seventhBattleScenario,
|
||||
'eighth-battle-xiaopei-supply-road': eighthBattleScenario
|
||||
};
|
||||
|
||||
export const defaultBattleScenario = battleScenarios[defaultBattleScenarioId];
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { CampaignStep } from '../state/campaignState';
|
||||
import {
|
||||
defaultBattleScenario,
|
||||
eighthBattleScenario,
|
||||
fifthBattleScenario,
|
||||
fourthBattleScenario,
|
||||
secondBattleScenario,
|
||||
@@ -10,6 +11,8 @@ import {
|
||||
type BattleScenarioId
|
||||
} from './battles';
|
||||
import {
|
||||
eighthBattleIntroPages,
|
||||
eighthBattleVictoryPages,
|
||||
fifthBattleIntroPages,
|
||||
fifthBattleVictoryPages,
|
||||
firstBattleVictoryPages,
|
||||
@@ -101,12 +104,22 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
},
|
||||
[seventhBattleScenario.id]: {
|
||||
afterBattleId: seventhBattleScenario.id,
|
||||
eyebrow: '다음 전장',
|
||||
title: eighthBattleScenario.title,
|
||||
description: '도겸에게 서주를 맡은 뒤 첫 과제는 소패 보급로를 지키는 일입니다. 여포군의 그림자가 드리우기 전에 새로 합류한 장수들과 방위선을 세워야 합니다.',
|
||||
rewardHint: `예상 보상: ${eighthBattleScenario.title} 개방`,
|
||||
nextBattleId: eighthBattleScenario.id,
|
||||
campaignStep: 'eighth-battle',
|
||||
pages: [...seventhBattleVictoryPages, ...eighthBattleIntroPages]
|
||||
},
|
||||
[eighthBattleScenario.id]: {
|
||||
afterBattleId: eighthBattleScenario.id,
|
||||
eyebrow: '다음 장 준비',
|
||||
title: '도겸의 서주',
|
||||
description: '서주 구원전 뒤 도겸은 유비에게 서주의 장래를 맡아 달라 청합니다. 이제 서주 인수와 여포의 그림자가 다음 줄기를 만듭니다.',
|
||||
rewardHint: '다음 장: 도겸에게 서주 인수 준비 중',
|
||||
pages: seventhBattleVictoryPages,
|
||||
unavailableNotice: '도겸의 서주 인수 흐름은 다음 장으로 이어집니다. 군영에서 성장 상태와 출전 구성을 정비하세요.'
|
||||
title: '여포의 의탁',
|
||||
description: '소패 보급로를 지켜냈지만 여포가 몸을 의탁하겠다는 전갈이 도착합니다. 받아들이는 선택은 곧 서주 상실의 갈등으로 이어질 것입니다.',
|
||||
rewardHint: '다음 장: 여포 의탁과 서주 내부 불안 준비 중',
|
||||
pages: eighthBattleVictoryPages,
|
||||
unavailableNotice: '여포 의탁 이후의 서주 상실 흐름은 다음 작업에서 이어집니다. 군영에서 성장과 출전 구성을 정비하십시오.'
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -540,6 +540,60 @@ export const seventhBattleVictoryPages: StoryPage[] = [
|
||||
}
|
||||
];
|
||||
|
||||
export const eighthBattleIntroPages: StoryPage[] = [
|
||||
{
|
||||
id: 'eighth-xuzhou-handover',
|
||||
bgm: 'militia-theme',
|
||||
chapter: '서주의 새 군영',
|
||||
background: 'story-militia',
|
||||
text: '도겸은 병든 몸을 일으켜 서주의 장부와 군량 창고를 유비에게 넘겼다. 간옹과 미축은 백성의 청원과 보급로를 정리하며 새 군영의 기초를 세웠다.'
|
||||
},
|
||||
{
|
||||
id: 'eighth-lu-bu-shadow',
|
||||
bgm: 'battle-prep',
|
||||
chapter: '여포의 그림자',
|
||||
background: 'story-liu-bei',
|
||||
speaker: '유비',
|
||||
portrait: 'liuBei',
|
||||
text: '여포가 떠돌고 있다는 소식은 가볍지 않다. 그 이름 하나만으로도 여러 군세가 흔들린다. 그러나 지금은 서주의 길과 백성을 먼저 지켜야 한다.'
|
||||
},
|
||||
{
|
||||
id: 'eighth-xiaopei-road',
|
||||
bgm: 'battle-prep',
|
||||
chapter: '소패 보급로',
|
||||
background: 'story-sortie',
|
||||
speaker: '미축',
|
||||
text: '소패로 이어지는 창고 길목이 습격받고 있습니다. 군량이 끊기면 서주를 안정시킬 수 없습니다. 보급로와 마을을 동시에 지켜야 합니다.'
|
||||
},
|
||||
{
|
||||
id: 'eighth-sortie-choice',
|
||||
bgm: 'battle-prep',
|
||||
chapter: '첫 방위전',
|
||||
background: 'story-three-heroes',
|
||||
speaker: '간옹',
|
||||
text: '현덕, 이제 함께 갈 장수를 고르는 일도 전술이 되었네. 칼이 필요한 곳과 말이 필요한 곳, 그리고 말보다 장부가 필요한 곳을 구분해야 하네.'
|
||||
}
|
||||
];
|
||||
|
||||
export const eighthBattleVictoryPages: StoryPage[] = [
|
||||
{
|
||||
id: 'eighth-victory-supply-road',
|
||||
bgm: 'militia-theme',
|
||||
chapter: '지켜낸 보급로',
|
||||
background: 'story-militia',
|
||||
text: '소패의 보급로가 다시 열리자 서주의 창고와 마을은 겨우 숨을 돌렸다. 새로 합류한 장수들은 각자의 방식으로 유비군의 빈틈을 메웠다.'
|
||||
},
|
||||
{
|
||||
id: 'eighth-lu-bu-arrival',
|
||||
bgm: 'battle-prep',
|
||||
chapter: '뜻밖의 손님',
|
||||
background: 'story-liu-bei',
|
||||
speaker: '유비',
|
||||
portrait: 'liuBei',
|
||||
text: '전투가 끝난 뒤, 여포가 몸을 의탁할 곳을 구한다는 전갈이 왔다. 그를 받아들이는 일은 의로울 수 있으나, 서주에 또 다른 불씨를 들이는 일이기도 했다.'
|
||||
}
|
||||
];
|
||||
|
||||
export const firstBattleMap: BattleMap = {
|
||||
width: 20,
|
||||
height: 18,
|
||||
@@ -727,6 +781,12 @@ export const seventhBattleMap: BattleMap = {
|
||||
]
|
||||
};
|
||||
|
||||
export const eighthBattleMap: BattleMap = {
|
||||
width: 26,
|
||||
height: 22,
|
||||
terrain: createEighthBattleTerrain()
|
||||
};
|
||||
|
||||
export const firstBattleUnits: UnitData[] = [
|
||||
{
|
||||
id: 'liu-bei',
|
||||
@@ -2611,6 +2671,251 @@ export const xuzhouRecruitUnits: UnitData[] = [
|
||||
}
|
||||
];
|
||||
|
||||
const eighthBattleAllyPositions: Record<string, { x: number; y: number }> = {
|
||||
'liu-bei': { x: 2, y: 17 },
|
||||
'guan-yu': { x: 3, y: 17 },
|
||||
'zhang-fei': { x: 2, y: 18 },
|
||||
'jian-yong': { x: 3, y: 18 },
|
||||
'mi-zhu': { x: 1, y: 18 }
|
||||
};
|
||||
|
||||
export const eighthBattleUnits: UnitData[] = [
|
||||
...[...firstBattleUnits.filter((unit) => unit.faction === 'ally'), ...xuzhouRecruitUnits].map((unit) =>
|
||||
placeScenarioUnit(unit, eighthBattleAllyPositions[unit.id] ?? { x: unit.x, y: unit.y })
|
||||
),
|
||||
{
|
||||
id: 'xiaopei-raider-a',
|
||||
name: '여포군 척후',
|
||||
faction: 'enemy',
|
||||
className: '기동 척후',
|
||||
classKey: 'cavalry',
|
||||
level: 8,
|
||||
exp: 52,
|
||||
hp: 43,
|
||||
maxHp: 43,
|
||||
attack: 15,
|
||||
move: 5,
|
||||
stats: { might: 82, intelligence: 50, leadership: 62, agility: 86, luck: 55 },
|
||||
equipment: {
|
||||
weapon: { itemId: 'yellow-turban-saber', level: 2, exp: 28 },
|
||||
armor: { itemId: 'rebel-vest', level: 2, exp: 14 },
|
||||
accessory: { itemId: 'grain-pouch', level: 1, exp: 0 }
|
||||
},
|
||||
x: 17,
|
||||
y: 9
|
||||
},
|
||||
{
|
||||
id: 'xiaopei-raider-b',
|
||||
name: '여포군 척후',
|
||||
faction: 'enemy',
|
||||
className: '기동 척후',
|
||||
classKey: 'cavalry',
|
||||
level: 8,
|
||||
exp: 54,
|
||||
hp: 44,
|
||||
maxHp: 44,
|
||||
attack: 15,
|
||||
move: 5,
|
||||
stats: { might: 83, intelligence: 50, leadership: 62, agility: 86, luck: 55 },
|
||||
equipment: {
|
||||
weapon: { itemId: 'yellow-turban-saber', level: 2, exp: 30 },
|
||||
armor: { itemId: 'rebel-vest', level: 2, exp: 14 },
|
||||
accessory: { itemId: 'grain-pouch', level: 1, exp: 0 }
|
||||
},
|
||||
x: 19,
|
||||
y: 10
|
||||
},
|
||||
{
|
||||
id: 'xiaopei-infantry-a',
|
||||
name: '소패 침투병',
|
||||
faction: 'enemy',
|
||||
className: '여포군 보병',
|
||||
classKey: 'yellowTurban',
|
||||
level: 8,
|
||||
exp: 50,
|
||||
hp: 42,
|
||||
maxHp: 42,
|
||||
attack: 13,
|
||||
move: 3,
|
||||
stats: { might: 78, intelligence: 50, leadership: 66, agility: 61, luck: 53 },
|
||||
equipment: {
|
||||
weapon: { itemId: 'yellow-turban-saber', level: 2, exp: 26 },
|
||||
armor: { itemId: 'lamellar-armor', level: 2, exp: 12 },
|
||||
accessory: { itemId: 'yellow-scarf-charm', level: 1, exp: 0 }
|
||||
},
|
||||
x: 11,
|
||||
y: 12
|
||||
},
|
||||
{
|
||||
id: 'xiaopei-infantry-b',
|
||||
name: '소패 침투병',
|
||||
faction: 'enemy',
|
||||
className: '여포군 보병',
|
||||
classKey: 'yellowTurban',
|
||||
level: 8,
|
||||
exp: 52,
|
||||
hp: 43,
|
||||
maxHp: 43,
|
||||
attack: 13,
|
||||
move: 3,
|
||||
stats: { might: 79, intelligence: 50, leadership: 67, agility: 62, luck: 53 },
|
||||
equipment: {
|
||||
weapon: { itemId: 'yellow-turban-saber', level: 2, exp: 28 },
|
||||
armor: { itemId: 'lamellar-armor', level: 2, exp: 12 },
|
||||
accessory: { itemId: 'yellow-scarf-charm', level: 1, exp: 0 }
|
||||
},
|
||||
x: 12,
|
||||
y: 14
|
||||
},
|
||||
{
|
||||
id: 'xiaopei-archer-a',
|
||||
name: '소패 궁병',
|
||||
faction: 'enemy',
|
||||
className: '여포군 궁병',
|
||||
classKey: 'archer',
|
||||
level: 8,
|
||||
exp: 52,
|
||||
hp: 34,
|
||||
maxHp: 34,
|
||||
attack: 14,
|
||||
move: 3,
|
||||
stats: { might: 70, intelligence: 62, leadership: 60, agility: 71, luck: 54 },
|
||||
equipment: {
|
||||
weapon: { itemId: 'short-bow', level: 2, exp: 28 },
|
||||
armor: { itemId: 'cloth-armor', level: 2, exp: 12 },
|
||||
accessory: { itemId: 'wind-quiver', level: 1, exp: 0 }
|
||||
},
|
||||
x: 18,
|
||||
y: 7
|
||||
},
|
||||
{
|
||||
id: 'xiaopei-archer-b',
|
||||
name: '소패 궁병',
|
||||
faction: 'enemy',
|
||||
className: '여포군 궁병',
|
||||
classKey: 'archer',
|
||||
level: 9,
|
||||
exp: 54,
|
||||
hp: 35,
|
||||
maxHp: 35,
|
||||
attack: 15,
|
||||
move: 3,
|
||||
stats: { might: 72, intelligence: 63, leadership: 61, agility: 72, luck: 55 },
|
||||
equipment: {
|
||||
weapon: { itemId: 'short-bow', level: 2, exp: 30 },
|
||||
armor: { itemId: 'cloth-armor', level: 2, exp: 12 },
|
||||
accessory: { itemId: 'wind-quiver', level: 1, exp: 0 }
|
||||
},
|
||||
x: 21,
|
||||
y: 14
|
||||
},
|
||||
{
|
||||
id: 'xiaopei-guard-a',
|
||||
name: '창고 습격병',
|
||||
faction: 'enemy',
|
||||
className: '여포군 보병',
|
||||
classKey: 'bandit',
|
||||
level: 8,
|
||||
exp: 50,
|
||||
hp: 38,
|
||||
maxHp: 38,
|
||||
attack: 14,
|
||||
move: 4,
|
||||
stats: { might: 79, intelligence: 48, leadership: 58, agility: 72, luck: 52 },
|
||||
equipment: {
|
||||
weapon: { itemId: 'yellow-turban-saber', level: 2, exp: 26 },
|
||||
armor: { itemId: 'rebel-vest', level: 2, exp: 12 },
|
||||
accessory: { itemId: 'grain-pouch', level: 1, exp: 0 }
|
||||
},
|
||||
x: 8,
|
||||
y: 11
|
||||
},
|
||||
{
|
||||
id: 'xiaopei-guard-b',
|
||||
name: '창고 습격병',
|
||||
faction: 'enemy',
|
||||
className: '여포군 보병',
|
||||
classKey: 'bandit',
|
||||
level: 8,
|
||||
exp: 52,
|
||||
hp: 39,
|
||||
maxHp: 39,
|
||||
attack: 14,
|
||||
move: 4,
|
||||
stats: { might: 80, intelligence: 48, leadership: 58, agility: 73, luck: 52 },
|
||||
equipment: {
|
||||
weapon: { itemId: 'yellow-turban-saber', level: 2, exp: 28 },
|
||||
armor: { itemId: 'rebel-vest', level: 2, exp: 12 },
|
||||
accessory: { itemId: 'grain-pouch', level: 1, exp: 0 }
|
||||
},
|
||||
x: 9,
|
||||
y: 14
|
||||
},
|
||||
{
|
||||
id: 'xiaopei-cavalry-a',
|
||||
name: '여포군 기병',
|
||||
faction: 'enemy',
|
||||
className: '여포군 기병',
|
||||
classKey: 'cavalry',
|
||||
level: 9,
|
||||
exp: 56,
|
||||
hp: 46,
|
||||
maxHp: 46,
|
||||
attack: 16,
|
||||
move: 5,
|
||||
stats: { might: 86, intelligence: 50, leadership: 66, agility: 88, luck: 56 },
|
||||
equipment: {
|
||||
weapon: { itemId: 'yellow-turban-saber', level: 2, exp: 32 },
|
||||
armor: { itemId: 'rebel-vest', level: 2, exp: 16 },
|
||||
accessory: { itemId: 'grain-pouch', level: 1, exp: 0 }
|
||||
},
|
||||
x: 23,
|
||||
y: 6
|
||||
},
|
||||
{
|
||||
id: 'xiaopei-cavalry-b',
|
||||
name: '여포군 기병',
|
||||
faction: 'enemy',
|
||||
className: '여포군 기병',
|
||||
classKey: 'cavalry',
|
||||
level: 9,
|
||||
exp: 58,
|
||||
hp: 47,
|
||||
maxHp: 47,
|
||||
attack: 16,
|
||||
move: 5,
|
||||
stats: { might: 87, intelligence: 50, leadership: 66, agility: 89, luck: 56 },
|
||||
equipment: {
|
||||
weapon: { itemId: 'yellow-turban-saber', level: 2, exp: 34 },
|
||||
armor: { itemId: 'rebel-vest', level: 2, exp: 16 },
|
||||
accessory: { itemId: 'grain-pouch', level: 1, exp: 0 }
|
||||
},
|
||||
x: 24,
|
||||
y: 15
|
||||
},
|
||||
{
|
||||
id: 'xiaopei-leader-gao-shun',
|
||||
name: '고순',
|
||||
faction: 'enemy',
|
||||
className: '여포군 선봉장',
|
||||
classKey: 'rebelLeader',
|
||||
level: 10,
|
||||
exp: 62,
|
||||
hp: 62,
|
||||
maxHp: 62,
|
||||
attack: 17,
|
||||
move: 4,
|
||||
stats: { might: 88, intelligence: 66, leadership: 86, agility: 68, luck: 58 },
|
||||
equipment: {
|
||||
weapon: { itemId: 'leader-axe', level: 2, exp: 34 },
|
||||
armor: { itemId: 'lamellar-armor', level: 2, exp: 18 },
|
||||
accessory: { itemId: 'war-manual', level: 1, exp: 0 }
|
||||
},
|
||||
x: 22,
|
||||
y: 3
|
||||
}
|
||||
];
|
||||
|
||||
export const firstBattleBonds: BattleBond[] = [
|
||||
{
|
||||
id: 'liu-bei__guan-yu',
|
||||
@@ -2663,6 +2968,39 @@ export const fourthBattleBonds: BattleBond[] = firstBattleBonds.map(cloneBattleB
|
||||
export const fifthBattleBonds: BattleBond[] = firstBattleBonds.map(cloneBattleBondForScenario);
|
||||
export const sixthBattleBonds: BattleBond[] = firstBattleBonds.map(cloneBattleBondForScenario);
|
||||
export const seventhBattleBonds: BattleBond[] = firstBattleBonds.map(cloneBattleBondForScenario);
|
||||
export const eighthBattleBonds: BattleBond[] = [...firstBattleBonds, ...xuzhouRecruitBonds].map(cloneBattleBondForScenario);
|
||||
|
||||
function createEighthBattleTerrain(): TerrainType[][] {
|
||||
return Array.from({ length: 22 }, (_, y) =>
|
||||
Array.from({ length: 26 }, (_, x): TerrainType => {
|
||||
if (x <= 1 && y >= 16 && y <= 20) {
|
||||
return 'camp';
|
||||
}
|
||||
if ((x === 2 && y >= 15 && y <= 20) || (x === 3 && y >= 18 && y <= 20)) {
|
||||
return 'camp';
|
||||
}
|
||||
if ((x >= 20 && x <= 23 && y >= 2 && y <= 5) || (x >= 21 && x <= 24 && y >= 13 && y <= 16)) {
|
||||
return 'fort';
|
||||
}
|
||||
if ((x >= 17 && x <= 18 && y >= 7 && y <= 9) || (x >= 8 && x <= 9 && y >= 12 && y <= 14)) {
|
||||
return 'village';
|
||||
}
|
||||
if ((x === 14 || x === 15) && y <= 18 && y >= 0) {
|
||||
return 'river';
|
||||
}
|
||||
if (x === y - 12 || x === y - 13 || (y >= 7 && y <= 10 && x >= 2 && x <= 18) || (x >= 4 && x <= 22 && y === 10)) {
|
||||
return 'road';
|
||||
}
|
||||
if ((x <= 6 && y <= 4) || (x >= 5 && x <= 10 && y >= 3 && y <= 7) || (x >= 2 && x <= 6 && y >= 8 && y <= 12) || (x >= 18 && x <= 25 && y >= 17)) {
|
||||
return 'forest';
|
||||
}
|
||||
if ((x >= 18 && y <= 2) || (x >= 22 && y >= 7 && y <= 12) || (x >= 10 && x <= 13 && y >= 15 && y <= 20) || (x <= 4 && y >= 4 && y <= 7)) {
|
||||
return 'hill';
|
||||
}
|
||||
return 'plain';
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function placeScenarioUnit(unit: UnitData, position: { x: number; y: number }): UnitData {
|
||||
return {
|
||||
|
||||
@@ -524,7 +524,18 @@ const enemyAiByUnitId: Record<string, EnemyAiBehavior> = {
|
||||
'xuzhou-cavalry-c': 'aggressive',
|
||||
'xuzhou-guard-b': 'guard',
|
||||
'xuzhou-archer-c': 'hold',
|
||||
'xuzhou-leader-xiahou-dun': 'guard'
|
||||
'xuzhou-leader-xiahou-dun': 'guard',
|
||||
'xiaopei-raider-a': 'aggressive',
|
||||
'xiaopei-raider-b': 'aggressive',
|
||||
'xiaopei-infantry-a': 'guard',
|
||||
'xiaopei-infantry-b': 'guard',
|
||||
'xiaopei-archer-a': 'hold',
|
||||
'xiaopei-archer-b': 'hold',
|
||||
'xiaopei-guard-a': 'aggressive',
|
||||
'xiaopei-guard-b': 'aggressive',
|
||||
'xiaopei-cavalry-a': 'aggressive',
|
||||
'xiaopei-cavalry-b': 'aggressive',
|
||||
'xiaopei-leader-gao-shun': 'guard'
|
||||
};
|
||||
|
||||
const defaultEnemyAiByClass: Partial<Record<UnitClassKey, EnemyAiBehavior>> = {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import Phaser from 'phaser';
|
||||
import eighthBattleMapUrl from '../../assets/images/battle/eighth-battle-map.svg';
|
||||
import fifthBattleMapUrl from '../../assets/images/battle/fifth-battle-map.svg';
|
||||
import firstBattleMapUrl from '../../assets/images/battle/first-battle-map.png';
|
||||
import fourthBattleMapUrl from '../../assets/images/battle/fourth-battle-map.svg';
|
||||
@@ -72,6 +73,7 @@ export class BootScene extends Phaser.Scene {
|
||||
this.load.image('battle-map-fifth', fifthBattleMapUrl);
|
||||
this.load.image('battle-map-sixth', sixthBattleMapUrl);
|
||||
this.load.image('battle-map-seventh', seventhBattleMapUrl);
|
||||
this.load.image('battle-map-eighth', eighthBattleMapUrl);
|
||||
this.load.image('portrait-liu-bei', liuBeiPortraitUrl);
|
||||
this.load.image('portrait-guan-yu', guanYuPortraitUrl);
|
||||
this.load.image('portrait-zhang-fei', zhangFeiPortraitUrl);
|
||||
|
||||
@@ -130,7 +130,8 @@ const campBattleIds = {
|
||||
fourth: 'fourth-battle-guangzong-camp',
|
||||
fifth: 'fifth-battle-sishui-vanguard',
|
||||
sixth: 'sixth-battle-jieqiao-relief',
|
||||
seventh: 'seventh-battle-xuzhou-rescue'
|
||||
seventh: 'seventh-battle-xuzhou-rescue',
|
||||
eighth: 'eighth-battle-xiaopei-supply-road'
|
||||
} as const;
|
||||
|
||||
const requiredSortieUnitIds = new Set(['liu-bei']);
|
||||
@@ -757,6 +758,87 @@ const campDialogues: CampDialogue[] = [
|
||||
rewardExp: 7
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'liu-mi-zhu-after-xiaopei',
|
||||
title: '지켜낸 보급로',
|
||||
availableAfterBattleIds: [campBattleIds.eighth],
|
||||
unitIds: ['liu-bei', 'mi-zhu'],
|
||||
bondId: 'liu-bei__mi-zhu',
|
||||
rewardExp: 20,
|
||||
lines: [
|
||||
'미축: 소패 보급로는 지켰지만, 서주는 아직 안정되었다고 보기 어렵습니다. 창고를 지켜도 사람의 마음이 흩어지면 다시 흔들립니다.',
|
||||
'유비: 군량은 칼보다 조용하지만, 백성을 살리는 힘이 있소. 그 일을 계속 맡아 주시오.',
|
||||
'미축: 맡겨 주신다면 상단과 호족을 설득해 서주의 숨통을 조금 더 넓히겠습니다.'
|
||||
],
|
||||
choices: [
|
||||
{
|
||||
id: 'trust-supply-network',
|
||||
label: '보급망을 믿고 맡긴다',
|
||||
response: '미축은 유비가 실무를 믿고 맡기자 서주의 창고와 상단을 더 단단히 묶겠다고 답했다.',
|
||||
rewardExp: 8
|
||||
},
|
||||
{
|
||||
id: 'share-grain-openly',
|
||||
label: '군량 배분을 투명하게 한다',
|
||||
response: '유비가 백성 앞에서 군량 배분을 숨기지 않겠다고 하자 미축의 표정이 한결 밝아졌다.',
|
||||
rewardExp: 9
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'liu-jian-yong-after-xiaopei',
|
||||
title: '여포를 맞을 것인가',
|
||||
availableAfterBattleIds: [campBattleIds.eighth],
|
||||
unitIds: ['liu-bei', 'jian-yong'],
|
||||
bondId: 'liu-bei__jian-yong',
|
||||
rewardExp: 20,
|
||||
lines: [
|
||||
'간옹: 여포를 받아들이면 의롭다는 말은 들을 걸세. 하지만 사람들은 그 뒤에 칼집이 몇 개인지도 세어 볼 거야.',
|
||||
'유비: 곤궁한 이를 외면할 수는 없소. 그러나 서주 백성의 근심도 가볍게 여길 수 없소.',
|
||||
'간옹: 그래서 선택에는 늘 값을 치르는 법이지. 적어도 그 값을 알고 받아들이게.'
|
||||
],
|
||||
choices: [
|
||||
{
|
||||
id: 'accept-with-guard',
|
||||
label: '경계를 세우고 맞는다',
|
||||
response: '간옹은 유비가 의와 경계를 함께 세우겠다고 하자, 그나마 현실적인 대답이라고 웃었다.',
|
||||
rewardExp: 9
|
||||
},
|
||||
{
|
||||
id: 'ask-people-first',
|
||||
label: '백성의 불안을 먼저 살핀다',
|
||||
response: '간옹은 서주 사람들의 두려움을 먼저 살피겠다는 말에 고개를 끄덕였다.',
|
||||
rewardExp: 8
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'guan-zhang-after-xiaopei',
|
||||
title: '성문을 지키는 두 장수',
|
||||
availableAfterBattleIds: [campBattleIds.eighth],
|
||||
unitIds: ['guan-yu', 'zhang-fei'],
|
||||
bondId: 'guan-yu__zhang-fei',
|
||||
rewardExp: 20,
|
||||
lines: [
|
||||
'관우: 여포가 온다면 군율이 먼저 흔들릴 것이다. 성문과 술자리를 모두 조심해야 한다.',
|
||||
'장비: 왜 나를 보며 말하시오? 하지만 알겠소. 이번에는 먼저 소리치기보다 성문부터 붙잡겠소.',
|
||||
'관우: 형님의 뜻을 지키려면 우리부터 흔들리지 않아야 한다.'
|
||||
],
|
||||
choices: [
|
||||
{
|
||||
id: 'guard-gates',
|
||||
label: '성문 경계를 강화한다',
|
||||
response: '관우와 장비는 성문 근무와 순찰을 나누어 맡기로 했다.',
|
||||
rewardExp: 8
|
||||
},
|
||||
{
|
||||
id: 'restrain-temper',
|
||||
label: '장비의 성급함을 다잡는다',
|
||||
response: '장비는 툴툴대면서도 이번만큼은 술보다 군율을 먼저 보겠다고 약속했다.',
|
||||
rewardExp: 9
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
@@ -834,7 +916,8 @@ export class CampScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
private ensureCurrentCampRecruitment() {
|
||||
if (this.currentCampBattleId() !== campBattleIds.seventh || !this.campaign) {
|
||||
const battleId = this.currentCampBattleId();
|
||||
if ((battleId !== campBattleIds.seventh && battleId !== campBattleIds.eighth) || !this.campaign) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -854,6 +937,9 @@ export class CampScene extends Phaser.Scene {
|
||||
|
||||
private currentCampTitle() {
|
||||
const battleId = this.currentCampBattleId();
|
||||
if (battleId === campBattleIds.eighth) {
|
||||
return '소패 방위 군영';
|
||||
}
|
||||
if (battleId === campBattleIds.seventh) {
|
||||
return '서주 인수 준비 군영';
|
||||
}
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
import Phaser from 'phaser';
|
||||
import { soundDirector } from '../audio/SoundDirector';
|
||||
import { firstBattleVictoryPages } from '../data/scenario';
|
||||
import { fifthBattleScenario, fourthBattleScenario, secondBattleScenario, seventhBattleScenario, sixthBattleScenario, thirdBattleScenario } from '../data/battles';
|
||||
import {
|
||||
eighthBattleScenario,
|
||||
fifthBattleScenario,
|
||||
fourthBattleScenario,
|
||||
secondBattleScenario,
|
||||
seventhBattleScenario,
|
||||
sixthBattleScenario,
|
||||
thirdBattleScenario
|
||||
} from '../data/battles';
|
||||
import { hasCampaignSave, loadCampaignState, startNewCampaign } from '../state/campaignState';
|
||||
import { palette } from '../ui/palette';
|
||||
|
||||
@@ -306,7 +314,8 @@ export class TitleScene extends Phaser.Scene {
|
||||
campaign.step === 'fourth-camp' ||
|
||||
campaign.step === 'fifth-camp' ||
|
||||
campaign.step === 'sixth-camp' ||
|
||||
campaign.step === 'seventh-camp'
|
||||
campaign.step === 'seventh-camp' ||
|
||||
campaign.step === 'eighth-camp'
|
||||
) {
|
||||
this.scene.start('CampScene');
|
||||
return;
|
||||
@@ -347,6 +356,11 @@ export class TitleScene extends Phaser.Scene {
|
||||
return;
|
||||
}
|
||||
|
||||
if (campaign.step === 'eighth-battle') {
|
||||
this.scene.start('BattleScene', { battleId: eighthBattleScenario.id });
|
||||
return;
|
||||
}
|
||||
|
||||
if (campaign.step === 'first-victory-story') {
|
||||
this.scene.start('StoryScene', { pages: firstBattleVictoryPages, nextScene: 'TitleScene' });
|
||||
return;
|
||||
|
||||
@@ -53,7 +53,9 @@ export type CampaignStep =
|
||||
| 'sixth-battle'
|
||||
| 'sixth-camp'
|
||||
| 'seventh-battle'
|
||||
| 'seventh-camp';
|
||||
| 'seventh-camp'
|
||||
| 'eighth-battle'
|
||||
| 'eighth-camp';
|
||||
|
||||
export type CampaignUnitProgressSnapshot = {
|
||||
unitId: string;
|
||||
@@ -111,7 +113,8 @@ const campaignBattleSteps: Record<string, { victory: CampaignStep; retry: Campai
|
||||
'fourth-battle-guangzong-camp': { victory: 'fourth-camp', retry: 'fourth-battle' },
|
||||
'fifth-battle-sishui-vanguard': { victory: 'fifth-camp', retry: 'fifth-battle' },
|
||||
'sixth-battle-jieqiao-relief': { victory: 'sixth-camp', retry: 'sixth-battle' },
|
||||
'seventh-battle-xuzhou-rescue': { victory: 'seventh-camp', retry: 'seventh-battle' }
|
||||
'seventh-battle-xuzhou-rescue': { victory: 'seventh-camp', retry: 'seventh-battle' },
|
||||
'eighth-battle-xiaopei-supply-road': { victory: 'eighth-camp', retry: 'eighth-battle' }
|
||||
};
|
||||
|
||||
export type CampaignSaveSlotSummary = {
|
||||
|
||||
Reference in New Issue
Block a user