Add Cao Cao refuge road chapter
This commit is contained in:
@@ -30,12 +30,13 @@ Build a small complete tactical RPG loop that can grow into a longer Romance of
|
||||
- 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
|
||||
- Ninth battle Xuzhou gate night raid, opening Lu Bu's refuge and the internal instability that leads toward the loss of Xu Province
|
||||
- Tenth battle Xuzhou breakout, covering Zhang Fei's mistake, the loss of Xu Province to Lu Bu, and the retreat toward Cao Cao's shadow
|
||||
- Eleventh battle Xudu refuge road, opening Liu Bei's uneasy service under Cao Cao through a Yuan Shu remnant test battle
|
||||
- Tactical sortie preparation panel with class role, named equipment, core stats, bond partner, and next-map terrain suitability for each deployable officer
|
||||
- Flow verification script from title through the tenth battle victory, recruit sortie selection, and camp save state
|
||||
- Flow verification script from title through the eleventh battle victory, recruit sortie selection, and camp save state
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. Continue into Liu Bei's temporary service under Cao Cao and the tension of surviving in Xu/Hu Du politics
|
||||
1. Continue into Liu Bei's tense service under Cao Cao, then build toward the break from Cao Cao
|
||||
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
|
||||
|
||||
@@ -1102,6 +1102,83 @@ try {
|
||||
throw new Error(`Expected tenth camp to expose Xuzhou loss dialogue set and preserve full roster: ${JSON.stringify(tenthCampState)}`);
|
||||
}
|
||||
|
||||
await page.mouse.click(1120, 38);
|
||||
await page.waitForTimeout(160);
|
||||
const tenthCampSortieState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
|
||||
if (!tenthCampSortieState?.sortieVisible) {
|
||||
throw new Error(`Expected tenth camp sortie prep overlay: ${JSON.stringify(tenthCampSortieState)}`);
|
||||
}
|
||||
assertSortieTacticalRoster(tenthCampSortieState, ['liu-bei', 'guan-yu', 'zhang-fei', 'jian-yong', 'mi-zhu']);
|
||||
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-cao-cao-refuge-story.png', fullPage: true });
|
||||
|
||||
for (let i = 0; i < 16; i += 1) {
|
||||
const enteredEleventhBattle = await page.evaluate(() => {
|
||||
const state = window.__HEROS_DEBUG__?.battle();
|
||||
return state?.scene === 'BattleScene' && state?.battleId === 'eleventh-battle-xudu-refuge-road';
|
||||
});
|
||||
if (enteredEleventhBattle) {
|
||||
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 === 'eleventh-battle-xudu-refuge-road' && state?.battleOutcome === null && state?.phase === 'idle';
|
||||
});
|
||||
await page.screenshot({ path: 'dist/verification-eleventh-battle.png', fullPage: true });
|
||||
|
||||
const eleventhBattleState = await page.evaluate(() => window.__HEROS_DEBUG__?.battle());
|
||||
const eleventhEnemies = eleventhBattleState.units.filter((unit) => unit.faction === 'enemy');
|
||||
const eleventhAllies = eleventhBattleState.units.filter((unit) => unit.faction === 'ally');
|
||||
const eleventhEnemyBehaviors = new Set(eleventhEnemies.map((unit) => unit.ai));
|
||||
if (
|
||||
eleventhBattleState.camera?.mapWidth !== 28 ||
|
||||
eleventhBattleState.camera?.mapHeight !== 22 ||
|
||||
eleventhBattleState.victoryConditionLabel !== '기령 격파' ||
|
||||
eleventhEnemies.length < 12 ||
|
||||
!eleventhEnemyBehaviors.has('aggressive') ||
|
||||
!eleventhEnemyBehaviors.has('guard') ||
|
||||
!eleventhEnemyBehaviors.has('hold') ||
|
||||
!eleventhAllies.some((unit) => unit.id === 'liu-bei') ||
|
||||
!eleventhAllies.some((unit) => unit.id === 'guan-yu') ||
|
||||
!eleventhAllies.some((unit) => unit.id === 'zhang-fei') ||
|
||||
!eleventhAllies.some((unit) => unit.id === 'jian-yong') ||
|
||||
!eleventhAllies.some((unit) => unit.id === 'mi-zhu')
|
||||
) {
|
||||
throw new Error(`Expected eleventh battle to use Xudu road map, mixed AI, and selected Cao Cao refuge sortie: ${JSON.stringify(eleventhBattleState)}`);
|
||||
}
|
||||
|
||||
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 eleventhCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
|
||||
if (
|
||||
eleventhCampState?.campBattleId !== 'eleventh-battle-xudu-refuge-road' ||
|
||||
eleventhCampState.campTitle !== '허도 의탁 군영' ||
|
||||
eleventhCampState.availableDialogueIds?.length !== 3 ||
|
||||
!eleventhCampState.availableDialogueIds.every((id) => id.endsWith('cao-cao-refuge')) ||
|
||||
!eleventhCampState.campaign?.roster?.some((unit) => unit.id === 'guan-yu') ||
|
||||
!eleventhCampState.campaign?.roster?.some((unit) => unit.id === 'jian-yong') ||
|
||||
!eleventhCampState.campaign?.roster?.some((unit) => unit.id === 'mi-zhu')
|
||||
) {
|
||||
throw new Error(`Expected eleventh camp to expose Cao Cao refuge dialogue set and preserve full roster: ${JSON.stringify(eleventhCampState)}`);
|
||||
}
|
||||
|
||||
await page.evaluate(() => window.__HEROS_GAME__?.scene.start('TitleScene'));
|
||||
await page.waitForFunction(() => {
|
||||
const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? [];
|
||||
@@ -1113,7 +1190,7 @@ try {
|
||||
return activeScenes.includes('CampScene');
|
||||
});
|
||||
|
||||
console.log(`Verified title-to-tenth-battle flow, recruit sortie selection, result states, and debug API at ${targetUrl}`);
|
||||
console.log(`Verified title-to-eleventh-battle flow, recruit sortie selection, result states, and debug API at ${targetUrl}`);
|
||||
} finally {
|
||||
await browser?.close();
|
||||
if (serverProcess && !serverProcess.killed) {
|
||||
|
||||
97
src/assets/images/battle/eleventh-battle-map.svg
Normal file
97
src/assets/images/battle/eleventh-battle-map.svg
Normal file
@@ -0,0 +1,97 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2800 2200" width="2800" height="2200">
|
||||
<defs>
|
||||
<filter id="marchGrain">
|
||||
<feTurbulence type="fractalNoise" baseFrequency="0.019" numOctaves="4" seed="1111" />
|
||||
<feColorMatrix type="saturate" values="0.62" />
|
||||
<feBlend mode="multiply" in2="SourceGraphic" />
|
||||
</filter>
|
||||
<pattern id="field" width="100" height="100" patternUnits="userSpaceOnUse">
|
||||
<rect width="100" height="100" fill="#596a48" />
|
||||
<path d="M12 28h24M44 66h34M64 18l18 12M20 84l22-16" stroke="#a8ba74" stroke-width="3" opacity="0.18" />
|
||||
<path d="M12 56l14-22M50 38l20-18M76 80l10-24" stroke="#2a3d23" stroke-width="2" opacity="0.34" />
|
||||
</pattern>
|
||||
<pattern id="road" width="112" height="112" patternUnits="userSpaceOnUse">
|
||||
<rect width="112" height="112" fill="#866b43" />
|
||||
<path d="M0 30c42-18 78 14 112-8M0 78c44-20 76 12 112-5" stroke="#c0a068" stroke-width="8" opacity="0.25" />
|
||||
<path d="M18 88l22-28M60 46l26-18M86 86l12-10" stroke="#3a2d20" stroke-width="3" opacity="0.36" />
|
||||
</pattern>
|
||||
<pattern id="grove" width="132" height="112" patternUnits="userSpaceOnUse">
|
||||
<rect width="132" height="112" fill="#203826" />
|
||||
<circle cx="24" cy="48" r="30" fill="#142818" />
|
||||
<circle cx="64" cy="34" r="38" fill="#41633f" />
|
||||
<circle cx="104" cy="54" r="31" fill="#1f3822" />
|
||||
<circle cx="54" cy="84" r="28" fill="#527046" />
|
||||
<path d="M12 66c36 18 78 16 110-10M36 26c24 20 58 18 76-8" stroke="#0b190e" stroke-width="5" opacity="0.36" />
|
||||
</pattern>
|
||||
<pattern id="ridge" width="132" height="100" patternUnits="userSpaceOnUse">
|
||||
<rect width="132" height="100" fill="#706747" />
|
||||
<path d="M-12 86c48-62 92-82 134-64c38 16 58 46 76 82" fill="#494832" opacity="0.72" />
|
||||
<path d="M18 78c38-42 74-52 110-32" fill="none" stroke="#9a925e" stroke-width="10" opacity="0.28" />
|
||||
</pattern>
|
||||
<linearGradient id="river" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0" stop-color="#54859a" />
|
||||
<stop offset="0.52" stop-color="#275a72" />
|
||||
<stop offset="1" stop-color="#123247" />
|
||||
</linearGradient>
|
||||
<linearGradient id="campWall" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#8b7358" />
|
||||
<stop offset="1" stop-color="#3f3022" />
|
||||
</linearGradient>
|
||||
<radialGradient id="watchFire" cx="0.5" cy="0.42" r="0.6">
|
||||
<stop offset="0" stop-color="#ffe7a0" />
|
||||
<stop offset="0.48" stop-color="#d77a32" />
|
||||
<stop offset="1" stop-color="#4b1e14" stop-opacity="0" />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
<rect width="2800" height="2200" fill="#121b20" />
|
||||
<rect width="2800" height="2200" fill="url(#field)" opacity="0.95" />
|
||||
<g filter="url(#marchGrain)">
|
||||
<path d="M-90 1500C330 1450 750 1480 1120 1496C1510 1514 1970 1490 2910 1450" fill="none" stroke="#94754a" stroke-width="260" stroke-linecap="round" opacity="0.9" />
|
||||
<path d="M-90 1500C330 1450 750 1480 1120 1496C1510 1514 1970 1490 2910 1450" fill="none" stroke="url(#road)" stroke-width="154" stroke-linecap="round" opacity="0.93" />
|
||||
<path d="M790 2180C810 1820 804 1440 800 1100C796 800 792 520 806 170" fill="none" stroke="#94754a" stroke-width="190" stroke-linecap="round" opacity="0.85" />
|
||||
<path d="M790 2180C810 1820 804 1440 800 1100C796 800 792 520 806 170" fill="none" stroke="url(#road)" stroke-width="108" stroke-linecap="round" opacity="0.84" />
|
||||
<path d="M2260 1830C2262 1440 2250 1080 2266 720C2274 540 2290 360 2268 110" fill="none" stroke="#94754a" stroke-width="176" stroke-linecap="round" opacity="0.86" />
|
||||
<path d="M2260 1830C2262 1440 2250 1080 2266 720C2274 540 2290 360 2268 110" fill="none" stroke="url(#road)" stroke-width="98" stroke-linecap="round" opacity="0.84" />
|
||||
<path d="M1620 2220c84-300 78-570-12-808c-80-210-64-424 48-642c88-172 102-362 44-574" fill="none" stroke="#123248" stroke-width="286" stroke-linecap="round" opacity="0.95" />
|
||||
<path d="M1620 2220c84-300 78-570-12-808c-80-210-64-424 48-642c88-172 102-362 44-574" fill="none" stroke="url(#river)" stroke-width="182" stroke-linecap="round" opacity="0.98" />
|
||||
|
||||
<path d="M0 20c278-76 548-32 810 130c54 178-80 324-398 438C178 536 34 414-14 246Z" fill="url(#grove)" opacity="0.84" />
|
||||
<path d="M270 760c316-120 612-70 888 154c-118 238-410 368-872 386C136 1130 128 946 270 760Z" fill="url(#grove)" opacity="0.82" />
|
||||
<path d="M870 1840c228-84 450-52 660 96c-44 164-200 260-464 288c-144-72-210-196-196-384Z" fill="url(#grove)" opacity="0.78" />
|
||||
<path d="M2140 40c208-56 392-22 552 96v300c-190 48-366 10-526-112c-66-92-74-188-26-284Z" fill="url(#ridge)" opacity="0.82" />
|
||||
<path d="M2440 1660c184-54 346-22 484 96v430c-198 58-370 24-516-102c-46-130-36-272 32-424Z" fill="url(#ridge)" opacity="0.82" />
|
||||
|
||||
<g transform="translate(2100 760)">
|
||||
<rect x="-70" y="-58" width="650" height="610" rx="18" fill="#1a130e" opacity="0.54" />
|
||||
<rect x="0" y="0" width="520" height="500" rx="12" fill="url(#campWall)" stroke="#24190f" stroke-width="18" />
|
||||
<path d="M0 104h520M0 214h520M0 324h520M0 430h520" stroke="#2e241b" stroke-width="12" opacity="0.72" />
|
||||
<path d="M110 0v500M260 0v500M410 0v500" stroke="#65513b" stroke-width="10" opacity="0.48" />
|
||||
<rect x="160" y="210" width="200" height="126" fill="#1f1713" stroke="#0e0907" stroke-width="11" />
|
||||
<path d="M160 210l100-66l100 66" fill="#764b24" stroke="#21160e" stroke-width="11" />
|
||||
<path d="M224 336v-76h72v76" fill="#0d0907" />
|
||||
<circle cx="78" cy="140" r="70" fill="url(#watchFire)" opacity="0.68" />
|
||||
<circle cx="450" cy="360" r="72" fill="url(#watchFire)" opacity="0.64" />
|
||||
</g>
|
||||
|
||||
<g transform="translate(820 1000)">
|
||||
<rect x="0" y="0" width="260" height="140" rx="10" fill="#604a35" stroke="#251d16" stroke-width="10" />
|
||||
<path d="M-20 18L130-74l152 92Z" fill="#9d642e" stroke="#2b1f14" stroke-width="10" />
|
||||
<path d="M78 140v-62h60v62" fill="#201713" />
|
||||
</g>
|
||||
|
||||
<g transform="translate(1425 1540)">
|
||||
<rect x="0" y="0" width="246" height="132" rx="10" fill="#604a35" stroke="#251d16" stroke-width="10" />
|
||||
<path d="M-18 14L123-68l142 82Z" fill="#8f5b2b" stroke="#2b1f14" stroke-width="10" />
|
||||
<path d="M76 132v-58h58v58" fill="#201713" />
|
||||
</g>
|
||||
|
||||
<path d="M2000 900c220 44 470 30 754-40" fill="none" stroke="#15100d" stroke-width="18" opacity="0.3" />
|
||||
<path d="M2020 1270c220 48 478 34 776-42" fill="none" stroke="#15100d" stroke-width="18" opacity="0.28" />
|
||||
<path d="M180 1500c300 58 622 58 970 0" fill="none" stroke="#d8b15f" stroke-width="10" opacity="0.15" />
|
||||
</g>
|
||||
|
||||
<rect width="2800" height="2200" fill="#07090c" opacity="0.12" />
|
||||
<path d="M2180 802c-120 170-92 360 84 570" fill="none" stroke="#ffe0a0" stroke-width="8" opacity="0.08" />
|
||||
<path d="M188 1488c260-20 520-14 780 18" fill="none" stroke="#fff0bd" stroke-width="8" opacity="0.08" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.7 KiB |
@@ -4,6 +4,10 @@ import {
|
||||
eighthBattleMap,
|
||||
eighthBattleUnits,
|
||||
eighthBattleVictoryPages,
|
||||
eleventhBattleBonds,
|
||||
eleventhBattleMap,
|
||||
eleventhBattleUnits,
|
||||
eleventhBattleVictoryPages,
|
||||
firstBattleMap,
|
||||
firstBattleUnits,
|
||||
firstBattleVictoryPages,
|
||||
@@ -55,7 +59,8 @@ export type BattleScenarioId =
|
||||
| 'seventh-battle-xuzhou-rescue'
|
||||
| 'eighth-battle-xiaopei-supply-road'
|
||||
| 'ninth-battle-xuzhou-gate-night-raid'
|
||||
| 'tenth-battle-xuzhou-breakout';
|
||||
| 'tenth-battle-xuzhou-breakout'
|
||||
| 'eleventh-battle-xudu-refuge-road';
|
||||
|
||||
export type BattleObjectiveKind = 'defeat-leader' | 'keep-unit-alive' | 'secure-terrain' | 'quick-victory';
|
||||
|
||||
@@ -624,6 +629,59 @@ export const tenthBattleScenario: BattleScenarioDefinition = {
|
||||
nextCampScene: 'CampScene'
|
||||
};
|
||||
|
||||
export const eleventhBattleScenario: BattleScenarioDefinition = {
|
||||
id: 'eleventh-battle-xudu-refuge-road',
|
||||
title: '허도 입성로',
|
||||
victoryConditionLabel: '기령 격파',
|
||||
defeatConditionLabel: '유비 퇴각',
|
||||
openingObjectiveLines: [
|
||||
'조조는 유비군을 받아들이는 대신 허도 입성로를 어지럽히는 원술 잔군을 치라는 명을 내립니다. 기령을 격파하면 길이 열립니다.',
|
||||
'원술군 기병은 길을 따라 돌파하고, 약탈병은 숲과 마을을 끼고 보급을 노립니다. 조조군의 감시 속에서 군율을 증명해야 합니다.',
|
||||
'마을을 확보하고 18턴 안에 승리하면 조조 의탁 이후에도 병력과 민심을 더 많이 보존합니다.'
|
||||
],
|
||||
map: eleventhBattleMap,
|
||||
units: eleventhBattleUnits,
|
||||
bonds: eleventhBattleBonds,
|
||||
mapTextureKey: 'battle-map-eleventh',
|
||||
leaderUnitId: 'xudu-road-leader-ji-ling',
|
||||
quickVictoryTurnLimit: 18,
|
||||
baseVictoryGold: 1320,
|
||||
objectives: [
|
||||
{
|
||||
id: 'leader',
|
||||
kind: 'defeat-leader',
|
||||
label: '기령 격파',
|
||||
rewardGold: 820,
|
||||
unitId: 'xudu-road-leader-ji-ling'
|
||||
},
|
||||
{
|
||||
id: 'liu-bei',
|
||||
kind: 'keep-unit-alive',
|
||||
label: '유비 생존',
|
||||
rewardGold: 320,
|
||||
unitId: 'liu-bei'
|
||||
},
|
||||
{
|
||||
id: 'village',
|
||||
kind: 'secure-terrain',
|
||||
label: '허도 길목 마을 확보',
|
||||
rewardGold: 460,
|
||||
terrain: 'village'
|
||||
},
|
||||
{
|
||||
id: 'quick',
|
||||
kind: 'quick-victory',
|
||||
label: '18턴 이내 승리',
|
||||
rewardGold: 360,
|
||||
maxTurn: 18
|
||||
}
|
||||
],
|
||||
defeatConditions: [{ kind: 'unit-defeated', unitId: 'liu-bei' }],
|
||||
itemRewards: ['콩 4', '상처약 3', '탁주 1', '조조군 군문 통행 +1'],
|
||||
victoryPages: eleventhBattleVictoryPages,
|
||||
nextCampScene: 'CampScene'
|
||||
};
|
||||
|
||||
export const defaultBattleScenarioId: BattleScenarioId = firstBattleScenario.id;
|
||||
|
||||
export const battleScenarios: Record<BattleScenarioId, BattleScenarioDefinition> = {
|
||||
@@ -636,7 +694,8 @@ export const battleScenarios: Record<BattleScenarioId, BattleScenarioDefinition>
|
||||
'seventh-battle-xuzhou-rescue': seventhBattleScenario,
|
||||
'eighth-battle-xiaopei-supply-road': eighthBattleScenario,
|
||||
'ninth-battle-xuzhou-gate-night-raid': ninthBattleScenario,
|
||||
'tenth-battle-xuzhou-breakout': tenthBattleScenario
|
||||
'tenth-battle-xuzhou-breakout': tenthBattleScenario,
|
||||
'eleventh-battle-xudu-refuge-road': eleventhBattleScenario
|
||||
};
|
||||
|
||||
export const defaultBattleScenario = battleScenarios[defaultBattleScenarioId];
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { CampaignStep } from '../state/campaignState';
|
||||
import {
|
||||
defaultBattleScenario,
|
||||
eighthBattleScenario,
|
||||
eleventhBattleScenario,
|
||||
fifthBattleScenario,
|
||||
fourthBattleScenario,
|
||||
ninthBattleScenario,
|
||||
@@ -15,6 +16,8 @@ import {
|
||||
import {
|
||||
eighthBattleIntroPages,
|
||||
eighthBattleVictoryPages,
|
||||
eleventhBattleIntroPages,
|
||||
eleventhBattleVictoryPages,
|
||||
fifthBattleIntroPages,
|
||||
fifthBattleVictoryPages,
|
||||
firstBattleVictoryPages,
|
||||
@@ -140,12 +143,22 @@ const sortieFlows: Record<string, SortieFlow> = {
|
||||
},
|
||||
[tenthBattleScenario.id]: {
|
||||
afterBattleId: tenthBattleScenario.id,
|
||||
eyebrow: '다음 전장',
|
||||
title: eleventhBattleScenario.title,
|
||||
description: '조조는 유비군을 받아들이되 허도 입성로의 원술 잔군을 치게 합니다. 의탁은 시작되지만, 동시에 유비군의 군율과 힘을 재는 시험입니다.',
|
||||
rewardHint: `예상 보상: ${eleventhBattleScenario.title} 개방`,
|
||||
nextBattleId: eleventhBattleScenario.id,
|
||||
campaignStep: 'eleventh-battle',
|
||||
pages: [...tenthBattleVictoryPages, ...eleventhBattleIntroPages]
|
||||
},
|
||||
[eleventhBattleScenario.id]: {
|
||||
afterBattleId: eleventhBattleScenario.id,
|
||||
eyebrow: '다음 장 준비',
|
||||
title: '조조 의탁의 길',
|
||||
description: '서주를 잃은 유비군은 살아남은 병력과 백성을 추슬러 허도로 향할 길을 찾습니다. 다음 흐름은 조조 의탁과 그 안의 긴장으로 이어집니다.',
|
||||
rewardHint: '다음 장: 조조 의탁 준비 중',
|
||||
pages: tenthBattleVictoryPages,
|
||||
unavailableNotice: '조조 의탁 장은 다음 작업에서 이어집니다. 군영에서 성장과 출전 구성을 정비하십시오.'
|
||||
title: '조조의 그늘 아래',
|
||||
description: '허도 입성로를 열어 조조의 장막에 들어섰지만, 유비군은 보호와 감시를 동시에 받게 됩니다. 다음 흐름은 조조 밑에서 기회를 기다리는 장으로 이어집니다.',
|
||||
rewardHint: '다음 장: 조조 휘하의 긴장 준비 중',
|
||||
pages: eleventhBattleVictoryPages,
|
||||
unavailableNotice: '조조 휘하 장은 다음 작업에서 이어집니다. 군영에서 성장과 출전 구성을 정비하십시오.'
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -704,6 +704,61 @@ export const tenthBattleVictoryPages: StoryPage[] = [
|
||||
}
|
||||
];
|
||||
|
||||
export const eleventhBattleIntroPages: StoryPage[] = [
|
||||
{
|
||||
id: 'eleventh-arrive-cao-cao-camp',
|
||||
bgm: 'story-dark',
|
||||
chapter: '조조의 장막',
|
||||
background: 'story-liu-bei',
|
||||
text: '서주를 잃은 유비군은 허도로 향했다. 조조는 웃으며 맞이했지만, 그 웃음 뒤에는 사람을 재는 날카로운 눈빛이 숨어 있었다.'
|
||||
},
|
||||
{
|
||||
id: 'eleventh-cao-cao-test',
|
||||
bgm: 'militia-theme',
|
||||
chapter: '의탁의 대가',
|
||||
background: 'story-militia',
|
||||
speaker: '간옹',
|
||||
text: '현덕, 조조는 우리에게 머물 곳을 주겠지만 공짜로 주지는 않을 걸세. 허도 입성로에 남은 원술 잔군을 치라는 명은 환대이자 시험이지.'
|
||||
},
|
||||
{
|
||||
id: 'eleventh-brothers-under-watch',
|
||||
bgm: 'story-dark',
|
||||
chapter: '감시 속의 출진',
|
||||
background: 'story-three-heroes',
|
||||
speaker: '관우',
|
||||
portrait: 'guanYu',
|
||||
text: '형님, 조조의 깃발 아래 싸우더라도 우리의 칼끝은 백성을 향하지 않습니다. 명을 따르되 뜻은 잃지 않겠습니다.'
|
||||
},
|
||||
{
|
||||
id: 'eleventh-xudu-road-sortie',
|
||||
bgm: 'battle-prep',
|
||||
chapter: '허도 입성로',
|
||||
background: 'story-sortie',
|
||||
speaker: '유비',
|
||||
portrait: 'liuBei',
|
||||
text: '지금은 몸을 낮추어야 할 때다. 그러나 낮춘 몸 안에서도 마음까지 굽힐 수는 없다. 길을 열고, 살아남아 다시 일어서자.'
|
||||
}
|
||||
];
|
||||
|
||||
export const eleventhBattleVictoryPages: StoryPage[] = [
|
||||
{
|
||||
id: 'eleventh-victory-road-cleared',
|
||||
bgm: 'militia-theme',
|
||||
chapter: '열린 허도 길',
|
||||
background: 'story-militia',
|
||||
text: '원술 잔군은 물러났고 허도로 향하는 길이 열렸다. 조조군의 감시병들은 유비군의 전열과 군율을 조용히 기록했다.'
|
||||
},
|
||||
{
|
||||
id: 'eleventh-cao-cao-accepts',
|
||||
bgm: 'story-dark',
|
||||
chapter: '웃음 속의 칼',
|
||||
background: 'story-liu-bei',
|
||||
speaker: '유비',
|
||||
portrait: 'liuBei',
|
||||
text: '조조의 장막은 비바람을 피하게 해 주지만, 그 안의 공기는 오래 머물수록 무겁다. 나는 살아남기 위해 들어가되, 나올 날을 잊지 않겠다.'
|
||||
}
|
||||
];
|
||||
|
||||
export const firstBattleMap: BattleMap = {
|
||||
width: 20,
|
||||
height: 18,
|
||||
@@ -909,6 +964,12 @@ export const tenthBattleMap: BattleMap = {
|
||||
terrain: createTenthBattleTerrain()
|
||||
};
|
||||
|
||||
export const eleventhBattleMap: BattleMap = {
|
||||
width: 28,
|
||||
height: 22,
|
||||
terrain: createEleventhBattleTerrain()
|
||||
};
|
||||
|
||||
export const firstBattleUnits: UnitData[] = [
|
||||
{
|
||||
id: 'liu-bei',
|
||||
@@ -3507,6 +3568,272 @@ export const tenthBattleUnits: UnitData[] = [
|
||||
}
|
||||
];
|
||||
|
||||
const eleventhBattleAllyPositions: Record<string, { x: number; y: number }> = {
|
||||
'liu-bei': { x: 3, y: 15 },
|
||||
'guan-yu': { x: 4, y: 14 },
|
||||
'zhang-fei': { x: 4, y: 16 },
|
||||
'jian-yong': { x: 5, y: 17 },
|
||||
'mi-zhu': { x: 3, y: 18 }
|
||||
};
|
||||
|
||||
export const eleventhBattleUnits: UnitData[] = [
|
||||
...[...firstBattleUnits.filter((unit) => unit.faction === 'ally'), ...xuzhouRecruitUnits].map((unit) =>
|
||||
placeScenarioUnit(unit, eleventhBattleAllyPositions[unit.id] ?? { x: unit.x, y: unit.y })
|
||||
),
|
||||
{
|
||||
id: 'xudu-road-raider-a',
|
||||
name: '원술군 잔병',
|
||||
faction: 'enemy',
|
||||
className: '원술군 보병',
|
||||
classKey: 'yellowTurban',
|
||||
level: 11,
|
||||
exp: 68,
|
||||
hp: 50,
|
||||
maxHp: 50,
|
||||
attack: 16,
|
||||
move: 3,
|
||||
stats: { might: 84, intelligence: 54, leadership: 70, agility: 64, luck: 56 },
|
||||
equipment: {
|
||||
weapon: { itemId: 'yellow-turban-saber', level: 2, exp: 42 },
|
||||
armor: { itemId: 'lamellar-armor', level: 2, exp: 20 },
|
||||
accessory: { itemId: 'yellow-scarf-charm', level: 1, exp: 0 }
|
||||
},
|
||||
x: 11,
|
||||
y: 12
|
||||
},
|
||||
{
|
||||
id: 'xudu-road-raider-b',
|
||||
name: '원술군 잔병',
|
||||
faction: 'enemy',
|
||||
className: '원술군 보병',
|
||||
classKey: 'yellowTurban',
|
||||
level: 11,
|
||||
exp: 70,
|
||||
hp: 51,
|
||||
maxHp: 51,
|
||||
attack: 16,
|
||||
move: 3,
|
||||
stats: { might: 85, intelligence: 55, leadership: 71, agility: 65, luck: 56 },
|
||||
equipment: {
|
||||
weapon: { itemId: 'yellow-turban-saber', level: 2, exp: 44 },
|
||||
armor: { itemId: 'lamellar-armor', level: 2, exp: 20 },
|
||||
accessory: { itemId: 'yellow-scarf-charm', level: 1, exp: 0 }
|
||||
},
|
||||
x: 13,
|
||||
y: 16
|
||||
},
|
||||
{
|
||||
id: 'xudu-road-raider-c',
|
||||
name: '원술군 잔병',
|
||||
faction: 'enemy',
|
||||
className: '원술군 보병',
|
||||
classKey: 'yellowTurban',
|
||||
level: 11,
|
||||
exp: 70,
|
||||
hp: 51,
|
||||
maxHp: 51,
|
||||
attack: 16,
|
||||
move: 3,
|
||||
stats: { might: 85, intelligence: 55, leadership: 71, agility: 65, luck: 56 },
|
||||
equipment: {
|
||||
weapon: { itemId: 'yellow-turban-saber', level: 2, exp: 44 },
|
||||
armor: { itemId: 'lamellar-armor', level: 2, exp: 20 },
|
||||
accessory: { itemId: 'yellow-scarf-charm', level: 1, exp: 0 }
|
||||
},
|
||||
x: 17,
|
||||
y: 11
|
||||
},
|
||||
{
|
||||
id: 'xudu-road-bandit-a',
|
||||
name: '군량 약탈병',
|
||||
faction: 'enemy',
|
||||
className: '원술군 약탈병',
|
||||
classKey: 'bandit',
|
||||
level: 11,
|
||||
exp: 68,
|
||||
hp: 45,
|
||||
maxHp: 45,
|
||||
attack: 17,
|
||||
move: 4,
|
||||
stats: { might: 86, intelligence: 53, leadership: 62, agility: 78, luck: 56 },
|
||||
equipment: {
|
||||
weapon: { itemId: 'yellow-turban-saber', level: 2, exp: 42 },
|
||||
armor: { itemId: 'rebel-vest', level: 2, exp: 20 },
|
||||
accessory: { itemId: 'grain-pouch', level: 1, exp: 0 }
|
||||
},
|
||||
x: 8,
|
||||
y: 8
|
||||
},
|
||||
{
|
||||
id: 'xudu-road-bandit-b',
|
||||
name: '군량 약탈병',
|
||||
faction: 'enemy',
|
||||
className: '원술군 약탈병',
|
||||
classKey: 'bandit',
|
||||
level: 11,
|
||||
exp: 70,
|
||||
hp: 46,
|
||||
maxHp: 46,
|
||||
attack: 17,
|
||||
move: 4,
|
||||
stats: { might: 87, intelligence: 53, leadership: 63, agility: 79, luck: 56 },
|
||||
equipment: {
|
||||
weapon: { itemId: 'yellow-turban-saber', level: 2, exp: 44 },
|
||||
armor: { itemId: 'rebel-vest', level: 2, exp: 20 },
|
||||
accessory: { itemId: 'grain-pouch', level: 1, exp: 0 }
|
||||
},
|
||||
x: 9,
|
||||
y: 18
|
||||
},
|
||||
{
|
||||
id: 'xudu-road-archer-a',
|
||||
name: '원술군 궁병',
|
||||
faction: 'enemy',
|
||||
className: '원술군 궁병',
|
||||
classKey: 'archer',
|
||||
level: 11,
|
||||
exp: 68,
|
||||
hp: 40,
|
||||
maxHp: 40,
|
||||
attack: 17,
|
||||
move: 3,
|
||||
stats: { might: 76, intelligence: 66, leadership: 64, agility: 76, luck: 57 },
|
||||
equipment: {
|
||||
weapon: { itemId: 'short-bow', level: 2, exp: 42 },
|
||||
armor: { itemId: 'cloth-armor', level: 2, exp: 18 },
|
||||
accessory: { itemId: 'wind-quiver', level: 1, exp: 0 }
|
||||
},
|
||||
x: 16,
|
||||
y: 6
|
||||
},
|
||||
{
|
||||
id: 'xudu-road-archer-b',
|
||||
name: '원술군 궁병',
|
||||
faction: 'enemy',
|
||||
className: '원술군 궁병',
|
||||
classKey: 'archer',
|
||||
level: 11,
|
||||
exp: 70,
|
||||
hp: 41,
|
||||
maxHp: 41,
|
||||
attack: 17,
|
||||
move: 3,
|
||||
stats: { might: 77, intelligence: 67, leadership: 65, agility: 77, luck: 57 },
|
||||
equipment: {
|
||||
weapon: { itemId: 'short-bow', level: 2, exp: 44 },
|
||||
armor: { itemId: 'cloth-armor', level: 2, exp: 18 },
|
||||
accessory: { itemId: 'wind-quiver', level: 1, exp: 0 }
|
||||
},
|
||||
x: 20,
|
||||
y: 15
|
||||
},
|
||||
{
|
||||
id: 'xudu-road-cavalry-a',
|
||||
name: '원술군 기병',
|
||||
faction: 'enemy',
|
||||
className: '원술군 기병',
|
||||
classKey: 'cavalry',
|
||||
level: 12,
|
||||
exp: 72,
|
||||
hp: 55,
|
||||
maxHp: 55,
|
||||
attack: 19,
|
||||
move: 5,
|
||||
stats: { might: 93, intelligence: 54, leadership: 72, agility: 94, luck: 59 },
|
||||
equipment: {
|
||||
weapon: { itemId: 'yellow-turban-saber', level: 2, exp: 48 },
|
||||
armor: { itemId: 'rebel-vest', level: 2, exp: 22 },
|
||||
accessory: { itemId: 'grain-pouch', level: 1, exp: 0 }
|
||||
},
|
||||
x: 21,
|
||||
y: 8
|
||||
},
|
||||
{
|
||||
id: 'xudu-road-cavalry-b',
|
||||
name: '원술군 기병',
|
||||
faction: 'enemy',
|
||||
className: '원술군 기병',
|
||||
classKey: 'cavalry',
|
||||
level: 12,
|
||||
exp: 74,
|
||||
hp: 56,
|
||||
maxHp: 56,
|
||||
attack: 19,
|
||||
move: 5,
|
||||
stats: { might: 94, intelligence: 54, leadership: 72, agility: 95, luck: 59 },
|
||||
equipment: {
|
||||
weapon: { itemId: 'yellow-turban-saber', level: 2, exp: 50 },
|
||||
armor: { itemId: 'rebel-vest', level: 2, exp: 22 },
|
||||
accessory: { itemId: 'grain-pouch', level: 1, exp: 0 }
|
||||
},
|
||||
x: 22,
|
||||
y: 17
|
||||
},
|
||||
{
|
||||
id: 'xudu-road-guard-a',
|
||||
name: '원술군 친위병',
|
||||
faction: 'enemy',
|
||||
className: '원술군 창병',
|
||||
classKey: 'spearman',
|
||||
level: 12,
|
||||
exp: 72,
|
||||
hp: 54,
|
||||
maxHp: 54,
|
||||
attack: 18,
|
||||
move: 3,
|
||||
stats: { might: 90, intelligence: 56, leadership: 76, agility: 68, luck: 58 },
|
||||
equipment: {
|
||||
weapon: { itemId: 'yellow-turban-saber', level: 2, exp: 48 },
|
||||
armor: { itemId: 'lamellar-armor', level: 2, exp: 22 },
|
||||
accessory: { itemId: 'yellow-scarf-charm', level: 1, exp: 0 }
|
||||
},
|
||||
x: 23,
|
||||
y: 11
|
||||
},
|
||||
{
|
||||
id: 'xudu-road-guard-b',
|
||||
name: '원술군 친위병',
|
||||
faction: 'enemy',
|
||||
className: '원술군 창병',
|
||||
classKey: 'spearman',
|
||||
level: 12,
|
||||
exp: 74,
|
||||
hp: 55,
|
||||
maxHp: 55,
|
||||
attack: 18,
|
||||
move: 3,
|
||||
stats: { might: 91, intelligence: 56, leadership: 76, agility: 69, luck: 58 },
|
||||
equipment: {
|
||||
weapon: { itemId: 'yellow-turban-saber', level: 2, exp: 50 },
|
||||
armor: { itemId: 'lamellar-armor', level: 2, exp: 22 },
|
||||
accessory: { itemId: 'yellow-scarf-charm', level: 1, exp: 0 }
|
||||
},
|
||||
x: 24,
|
||||
y: 13
|
||||
},
|
||||
{
|
||||
id: 'xudu-road-leader-ji-ling',
|
||||
name: '기령',
|
||||
faction: 'enemy',
|
||||
className: '원술군 대장',
|
||||
classKey: 'rebelLeader',
|
||||
level: 13,
|
||||
exp: 80,
|
||||
hp: 74,
|
||||
maxHp: 74,
|
||||
attack: 20,
|
||||
move: 4,
|
||||
stats: { might: 94, intelligence: 64, leadership: 88, agility: 74, luck: 60 },
|
||||
equipment: {
|
||||
weapon: { itemId: 'leader-axe', level: 2, exp: 52 },
|
||||
armor: { itemId: 'reinforced-lamellar', level: 2, exp: 24 },
|
||||
accessory: { itemId: 'war-manual', level: 1, exp: 0 }
|
||||
},
|
||||
x: 24,
|
||||
y: 10
|
||||
}
|
||||
];
|
||||
|
||||
export const firstBattleBonds: BattleBond[] = [
|
||||
{
|
||||
id: 'liu-bei__guan-yu',
|
||||
@@ -3562,6 +3889,7 @@ export const seventhBattleBonds: BattleBond[] = firstBattleBonds.map(cloneBattle
|
||||
export const eighthBattleBonds: BattleBond[] = [...firstBattleBonds, ...xuzhouRecruitBonds].map(cloneBattleBondForScenario);
|
||||
export const ninthBattleBonds: BattleBond[] = eighthBattleBonds.map(cloneBattleBondForScenario);
|
||||
export const tenthBattleBonds: BattleBond[] = ninthBattleBonds.map(cloneBattleBondForScenario);
|
||||
export const eleventhBattleBonds: BattleBond[] = tenthBattleBonds.map(cloneBattleBondForScenario);
|
||||
|
||||
function createEighthBattleTerrain(): TerrainType[][] {
|
||||
return Array.from({ length: 22 }, (_, y) =>
|
||||
@@ -3671,6 +3999,53 @@ function createTenthBattleTerrain(): TerrainType[][] {
|
||||
);
|
||||
}
|
||||
|
||||
function createEleventhBattleTerrain(): TerrainType[][] {
|
||||
return Array.from({ length: 22 }, (_, y) =>
|
||||
Array.from({ length: 28 }, (_, x): TerrainType => {
|
||||
if ((x <= 4 && y >= 14 && y <= 20) || (x >= 5 && x <= 7 && y >= 16 && y <= 20)) {
|
||||
return 'camp';
|
||||
}
|
||||
if ((x >= 22 && x <= 26 && y >= 8 && y <= 13) || (x >= 19 && x <= 21 && y >= 5 && y <= 8)) {
|
||||
return 'fort';
|
||||
}
|
||||
if ((x >= 8 && x <= 10 && y >= 10 && y <= 12) || (x >= 14 && x <= 16 && y >= 15 && y <= 17)) {
|
||||
return 'village';
|
||||
}
|
||||
if ((x === 17 || x === 18) && y >= 0 && y <= 19) {
|
||||
return 'river';
|
||||
}
|
||||
if (
|
||||
(y === 15 && x >= 2 && x <= 25) ||
|
||||
(x === 8 && y >= 6 && y <= 20) ||
|
||||
(x === 22 && y >= 4 && y <= 17) ||
|
||||
(x + y === 28 && x >= 9 && x <= 21)
|
||||
) {
|
||||
return 'road';
|
||||
}
|
||||
if (
|
||||
(x <= 5 && y <= 5) ||
|
||||
(x >= 6 && x <= 13 && y >= 2 && y <= 7) ||
|
||||
(x >= 3 && x <= 8 && y >= 8 && y <= 13) ||
|
||||
(x >= 9 && x <= 14 && y >= 18 && y <= 21)
|
||||
) {
|
||||
return 'forest';
|
||||
}
|
||||
if (
|
||||
(x >= 21 && y <= 3) ||
|
||||
(x >= 24 && y >= 16) ||
|
||||
(x >= 11 && x <= 14 && y >= 11 && y <= 14) ||
|
||||
(x <= 4 && y >= 6 && y <= 10)
|
||||
) {
|
||||
return 'hill';
|
||||
}
|
||||
if ((x >= 25 && y >= 4 && y <= 7) || (x >= 19 && x <= 21 && y <= 2)) {
|
||||
return 'cliff';
|
||||
}
|
||||
return 'plain';
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function placeScenarioUnit(unit: UnitData, position: { x: number; y: number }): UnitData {
|
||||
return {
|
||||
...cloneUnitForScenario(unit),
|
||||
|
||||
@@ -556,7 +556,19 @@ const enemyAiByUnitId: Record<string, EnemyAiBehavior> = {
|
||||
'xuzhou-escape-archer-b': 'hold',
|
||||
'xuzhou-escape-raider-a': 'aggressive',
|
||||
'xuzhou-escape-raider-b': 'aggressive',
|
||||
'xuzhou-escape-leader-song-xian': 'guard'
|
||||
'xuzhou-escape-leader-song-xian': 'guard',
|
||||
'xudu-road-raider-a': 'guard',
|
||||
'xudu-road-raider-b': 'guard',
|
||||
'xudu-road-raider-c': 'guard',
|
||||
'xudu-road-bandit-a': 'aggressive',
|
||||
'xudu-road-bandit-b': 'aggressive',
|
||||
'xudu-road-archer-a': 'hold',
|
||||
'xudu-road-archer-b': 'hold',
|
||||
'xudu-road-cavalry-a': 'aggressive',
|
||||
'xudu-road-cavalry-b': 'aggressive',
|
||||
'xudu-road-guard-a': 'guard',
|
||||
'xudu-road-guard-b': 'guard',
|
||||
'xudu-road-leader-ji-ling': 'guard'
|
||||
};
|
||||
|
||||
const defaultEnemyAiByClass: Partial<Record<UnitClassKey, EnemyAiBehavior>> = {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Phaser from 'phaser';
|
||||
import eighthBattleMapUrl from '../../assets/images/battle/eighth-battle-map.svg';
|
||||
import eleventhBattleMapUrl from '../../assets/images/battle/eleventh-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';
|
||||
@@ -78,6 +79,7 @@ export class BootScene extends Phaser.Scene {
|
||||
this.load.image('battle-map-eighth', eighthBattleMapUrl);
|
||||
this.load.image('battle-map-ninth', ninthBattleMapUrl);
|
||||
this.load.image('battle-map-tenth', tenthBattleMapUrl);
|
||||
this.load.image('battle-map-eleventh', eleventhBattleMapUrl);
|
||||
this.load.image('portrait-liu-bei', liuBeiPortraitUrl);
|
||||
this.load.image('portrait-guan-yu', guanYuPortraitUrl);
|
||||
this.load.image('portrait-zhang-fei', zhangFeiPortraitUrl);
|
||||
|
||||
@@ -140,7 +140,8 @@ const campBattleIds = {
|
||||
seventh: 'seventh-battle-xuzhou-rescue',
|
||||
eighth: 'eighth-battle-xiaopei-supply-road',
|
||||
ninth: 'ninth-battle-xuzhou-gate-night-raid',
|
||||
tenth: 'tenth-battle-xuzhou-breakout'
|
||||
tenth: 'tenth-battle-xuzhou-breakout',
|
||||
eleventh: 'eleventh-battle-xudu-refuge-road'
|
||||
} as const;
|
||||
|
||||
const requiredSortieUnitIds = new Set(['liu-bei']);
|
||||
@@ -1010,6 +1011,87 @@ const campDialogues: CampDialogue[] = [
|
||||
rewardExp: 8
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'liu-guan-after-cao-cao-refuge',
|
||||
title: '남의 깃발 아래',
|
||||
availableAfterBattleIds: [campBattleIds.eleventh],
|
||||
unitIds: ['liu-bei', 'guan-yu'],
|
||||
bondId: 'liu-bei__guan-yu',
|
||||
rewardExp: 22,
|
||||
lines: [
|
||||
'관우: 조조의 깃발 아래 서 있으니 병사들의 눈빛이 조심스러워졌습니다.',
|
||||
'유비: 운장, 깃발은 잠시 빌릴 수 있어도 뜻은 빌릴 수 없다. 우리가 지켜야 할 선을 잊지 맙시다.',
|
||||
'관우: 형님의 뜻이 흔들리지 않는다면, 남의 진영에서도 우리의 칼은 길을 잃지 않을 것입니다.'
|
||||
],
|
||||
choices: [
|
||||
{
|
||||
id: 'keep-oath-visible',
|
||||
label: '도원의 뜻을 분명히 한다',
|
||||
response: '관우는 병사들에게 조조의 명을 따르되 유비군의 군율을 잃지 말라고 전했다.',
|
||||
rewardExp: 10
|
||||
},
|
||||
{
|
||||
id: 'watch-cao-cao-camp',
|
||||
label: '조조 진영의 움직임을 살핀다',
|
||||
response: '관우는 지나친 경계가 오히려 의심을 부른다며 조용한 감시를 맡았다.',
|
||||
rewardExp: 9
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'liu-jian-yong-after-cao-cao-refuge',
|
||||
title: '웃음 뒤의 문장',
|
||||
availableAfterBattleIds: [campBattleIds.eleventh],
|
||||
unitIds: ['liu-bei', 'jian-yong'],
|
||||
bondId: 'liu-bei__jian-yong',
|
||||
rewardExp: 20,
|
||||
lines: [
|
||||
'간옹: 조조는 웃으며 맞았지만, 그 웃음은 사람을 묶는 줄이기도 하네.',
|
||||
'유비: 그 줄을 끊으면 목이 조이고, 그대로 두면 발이 묶일 것이오.',
|
||||
'간옹: 그래서 말과 문서를 조심해야지. 지금은 충심을 보이되, 떠날 길의 이름을 잊지 않는 때라네.'
|
||||
],
|
||||
choices: [
|
||||
{
|
||||
id: 'write-carefully',
|
||||
label: '문서를 조심스럽게 정리한다',
|
||||
response: '간옹은 조조에게 보일 문서와 유비군 내부 문서를 따로 정리했다.',
|
||||
rewardExp: 9
|
||||
},
|
||||
{
|
||||
id: 'hear-rumors',
|
||||
label: '허도 소문을 모은다',
|
||||
response: '간옹은 말 많은 술자리와 시장길에서 조조 진영의 분위기를 살피기로 했다.',
|
||||
rewardExp: 8
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'liu-mi-after-cao-cao-refuge',
|
||||
title: '빌린 창고',
|
||||
availableAfterBattleIds: [campBattleIds.eleventh],
|
||||
unitIds: ['liu-bei', 'mi-zhu'],
|
||||
bondId: 'liu-bei__mi-zhu',
|
||||
rewardExp: 18,
|
||||
lines: [
|
||||
'미축: 조조군의 창고를 빌리더라도 우리 병사의 식량 장부는 따로 남겨 두겠습니다.',
|
||||
'유비: 군량 하나에도 마음이 묶이는 법이오. 빌린 것은 빌린 것이라 분명히 해야 하오.',
|
||||
'미축: 그렇다면 보급은 받되, 유비군이 스스로 버틸 양도 함께 모아 두겠습니다.'
|
||||
],
|
||||
choices: [
|
||||
{
|
||||
id: 'separate-ledgers',
|
||||
label: '장부를 분리한다',
|
||||
response: '미축은 조조군 보급과 유비군 예비 군량을 따로 기록해 훗날의 발목을 줄였다.',
|
||||
rewardExp: 8
|
||||
},
|
||||
{
|
||||
id: 'save-refugee-grain',
|
||||
label: '피난민 군량을 남긴다',
|
||||
response: '미축은 병사 몫을 줄이지 않으면서도 피난민에게 갈 곡식을 별도로 숨겨 두었다.',
|
||||
rewardExp: 8
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
@@ -1092,7 +1174,8 @@ export class CampScene extends Phaser.Scene {
|
||||
(battleId !== campBattleIds.seventh &&
|
||||
battleId !== campBattleIds.eighth &&
|
||||
battleId !== campBattleIds.ninth &&
|
||||
battleId !== campBattleIds.tenth) ||
|
||||
battleId !== campBattleIds.tenth &&
|
||||
battleId !== campBattleIds.eleventh) ||
|
||||
!this.campaign
|
||||
) {
|
||||
return;
|
||||
@@ -1114,6 +1197,9 @@ export class CampScene extends Phaser.Scene {
|
||||
|
||||
private currentCampTitle() {
|
||||
const battleId = this.currentCampBattleId();
|
||||
if (battleId === campBattleIds.eleventh) {
|
||||
return '허도 의탁 군영';
|
||||
}
|
||||
if (battleId === campBattleIds.tenth) {
|
||||
return '서주 상실 후 군영';
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { soundDirector } from '../audio/SoundDirector';
|
||||
import { firstBattleVictoryPages } from '../data/scenario';
|
||||
import {
|
||||
eighthBattleScenario,
|
||||
eleventhBattleScenario,
|
||||
fifthBattleScenario,
|
||||
fourthBattleScenario,
|
||||
ninthBattleScenario,
|
||||
@@ -319,7 +320,8 @@ export class TitleScene extends Phaser.Scene {
|
||||
campaign.step === 'seventh-camp' ||
|
||||
campaign.step === 'eighth-camp' ||
|
||||
campaign.step === 'ninth-camp' ||
|
||||
campaign.step === 'tenth-camp'
|
||||
campaign.step === 'tenth-camp' ||
|
||||
campaign.step === 'eleventh-camp'
|
||||
) {
|
||||
this.scene.start('CampScene');
|
||||
return;
|
||||
@@ -375,6 +377,11 @@ export class TitleScene extends Phaser.Scene {
|
||||
return;
|
||||
}
|
||||
|
||||
if (campaign.step === 'eleventh-battle') {
|
||||
this.scene.start('BattleScene', { battleId: eleventhBattleScenario.id });
|
||||
return;
|
||||
}
|
||||
|
||||
if (campaign.step === 'first-victory-story') {
|
||||
this.scene.start('StoryScene', { pages: firstBattleVictoryPages, nextScene: 'TitleScene' });
|
||||
return;
|
||||
|
||||
@@ -59,7 +59,9 @@ export type CampaignStep =
|
||||
| 'ninth-battle'
|
||||
| 'ninth-camp'
|
||||
| 'tenth-battle'
|
||||
| 'tenth-camp';
|
||||
| 'tenth-camp'
|
||||
| 'eleventh-battle'
|
||||
| 'eleventh-camp';
|
||||
|
||||
export type CampaignUnitProgressSnapshot = {
|
||||
unitId: string;
|
||||
@@ -120,7 +122,8 @@ const campaignBattleSteps: Record<string, { victory: CampaignStep; retry: Campai
|
||||
'seventh-battle-xuzhou-rescue': { victory: 'seventh-camp', retry: 'seventh-battle' },
|
||||
'eighth-battle-xiaopei-supply-road': { victory: 'eighth-camp', retry: 'eighth-battle' },
|
||||
'ninth-battle-xuzhou-gate-night-raid': { victory: 'ninth-camp', retry: 'ninth-battle' },
|
||||
'tenth-battle-xuzhou-breakout': { victory: 'tenth-camp', retry: 'tenth-battle' }
|
||||
'tenth-battle-xuzhou-breakout': { victory: 'tenth-camp', retry: 'tenth-battle' },
|
||||
'eleventh-battle-xudu-refuge-road': { victory: 'eleventh-camp', retry: 'eleventh-battle' }
|
||||
};
|
||||
|
||||
export type CampaignSaveSlotSummary = {
|
||||
|
||||
Reference in New Issue
Block a user