Add Red Cliffs fire attack chapter

This commit is contained in:
2026-06-23 07:15:43 +09:00
parent 7f80600d0c
commit 302f0e0a76
11 changed files with 1059 additions and 24 deletions

View File

@@ -0,0 +1,80 @@
<svg xmlns="http://www.w3.org/2000/svg" width="4400" height="3000" viewBox="0 0 4400 3000">
<defs>
<filter id="grain">
<feTurbulence type="fractalNoise" baseFrequency="0.011" numOctaves="5" seed="22"/>
<feColorMatrix type="saturate" values="0.55"/>
<feBlend mode="multiply" in2="SourceGraphic"/>
</filter>
<linearGradient id="river" x1="0" x2="1" y1="0" y2="1">
<stop offset="0" stop-color="#08182c"/>
<stop offset="0.45" stop-color="#1f6688"/>
<stop offset="1" stop-color="#081525"/>
</linearGradient>
<linearGradient id="plain" x1="0" x2="1" y1="0" y2="1">
<stop offset="0" stop-color="#9f9862"/>
<stop offset="0.55" stop-color="#6e7750"/>
<stop offset="1" stop-color="#4e654d"/>
</linearGradient>
<linearGradient id="deck" x1="0" x2="1">
<stop offset="0" stop-color="#d3b36c"/>
<stop offset="0.45" stop-color="#8b6236"/>
<stop offset="1" stop-color="#46311f"/>
</linearGradient>
<radialGradient id="fire" cx="45%" cy="50%" r="55%">
<stop offset="0" stop-color="#fff3a7"/>
<stop offset="0.35" stop-color="#ff9b2c"/>
<stop offset="0.72" stop-color="#ba2e18"/>
<stop offset="1" stop-color="#371010"/>
</radialGradient>
<pattern id="waves" width="160" height="96" patternUnits="userSpaceOnUse">
<path d="M0 48 C38 18 82 78 122 48 C140 36 150 34 160 38" fill="none" stroke="#b6e7ef" stroke-opacity="0.22" stroke-width="9"/>
<path d="M0 84 C42 56 82 112 126 82 C142 70 154 68 160 72" fill="none" stroke="#0d304b" stroke-opacity="0.34" stroke-width="10"/>
</pattern>
<pattern id="grass" width="120" height="120" patternUnits="userSpaceOnUse">
<rect width="120" height="120" fill="url(#plain)"/>
<path d="M18 80 L40 62 M58 93 L83 71 M12 31 L35 20 M92 42 L114 24" stroke="#253d29" stroke-opacity="0.34" stroke-width="8"/>
<circle cx="24" cy="98" r="8" fill="#b0a75e" opacity="0.42"/>
<circle cx="78" cy="28" r="7" fill="#334726" opacity="0.38"/>
</pattern>
</defs>
<rect width="4400" height="3000" fill="#121515"/>
<rect x="0" y="0" width="4400" height="3000" fill="url(#grass)" filter="url(#grain)"/>
<path d="M1780 -80 C2050 520 1990 1080 1850 1520 C1730 1900 1775 2380 1540 3100 L3180 3100 C3020 2380 3230 1980 3140 1540 C3035 1030 3140 520 2940 -80 Z" fill="url(#river)"/>
<path d="M1780 -80 C2050 520 1990 1080 1850 1520 C1730 1900 1775 2380 1540 3100 L3180 3100 C3020 2380 3230 1980 3140 1540 C3035 1030 3140 520 2940 -80 Z" fill="url(#waves)" opacity="0.82"/>
<path d="M0 2180 C520 2100 880 2140 1260 1980 C1550 1860 1700 1880 1960 2020" fill="none" stroke="#a88045" stroke-width="190" stroke-linecap="round"/>
<path d="M1220 2050 C1620 1940 1960 1810 2320 1680 C2780 1510 3210 1430 4000 1420" fill="none" stroke="#c29c56" stroke-width="135" stroke-linecap="round"/>
<path d="M3050 1440 L4100 1430" stroke="#6a3f25" stroke-width="210" stroke-linecap="round"/>
<path d="M3050 1440 L4100 1430" stroke="url(#deck)" stroke-width="160" stroke-linecap="round"/>
<path d="M3500 1020 C3820 930 4210 965 4400 1060 L4400 1890 C4130 1960 3820 1900 3500 1760 Z" fill="#4c3a2b" opacity="0.72"/>
<path d="M3440 1120 C3740 1050 4060 1080 4310 1160 L4310 1730 C4000 1810 3720 1740 3440 1640 Z" fill="#283337" opacity="0.64"/>
<g opacity="0.96">
<ellipse cx="2600" cy="2130" rx="260" ry="86" fill="#69421f"/>
<ellipse cx="2920" cy="1970" rx="300" ry="92" fill="#6f4722"/>
<ellipse cx="3160" cy="1690" rx="320" ry="98" fill="#755024"/>
<path d="M2380 2105 L2810 1955 L3210 1650" fill="none" stroke="#d1bd74" stroke-width="26" stroke-dasharray="60 42"/>
<path d="M2510 2045 C2730 1900 3000 1780 3310 1660" fill="none" stroke="#ffcf6a" stroke-width="18" stroke-opacity="0.7"/>
</g>
<g opacity="0.9">
<ellipse cx="3035" cy="1940" rx="210" ry="150" fill="url(#fire)"/>
<ellipse cx="3265" cy="1685" rx="250" ry="180" fill="url(#fire)"/>
<ellipse cx="3620" cy="1430" rx="310" ry="220" fill="url(#fire)"/>
<path d="M2930 1870 C3020 1690 3130 1700 3190 1510 C3240 1690 3440 1720 3370 1960 C3210 1880 3110 2010 2930 1870 Z" fill="#fff0a8" opacity="0.55"/>
</g>
<g opacity="0.88">
<path d="M480 940 C780 650 1200 480 1720 520 C1540 710 1440 880 1390 1120 C1050 1060 750 1120 480 940 Z" fill="#283b26"/>
<path d="M3600 150 C3920 30 4220 80 4400 180 L4400 840 C4160 690 3860 650 3570 760 C3660 540 3640 350 3600 150 Z" fill="#354326"/>
<path d="M3280 2260 C3600 2220 3980 2320 4400 2550 L4400 3000 L3320 3000 C3440 2740 3370 2490 3280 2260 Z" fill="#2f3b25"/>
</g>
<g opacity="0.9">
<rect x="90" y="2175" width="520" height="520" rx="22" fill="#2c3b31" stroke="#d2b46c" stroke-width="18"/>
<rect x="160" y="2248" width="360" height="120" fill="#b07a35"/>
<rect x="185" y="2390" width="300" height="230" fill="#587145"/>
<path d="M185 2390 L335 2290 L485 2390" fill="#d7a541"/>
<path d="M3680 1040 L4240 1040 L4240 1790 L3680 1790 Z" fill="none" stroke="#c79f52" stroke-width="30"/>
<path d="M3770 1160 L4150 1160 M3770 1320 L4150 1320 M3770 1480 L4150 1480 M3770 1640 L4150 1640" stroke="#71512d" stroke-width="36"/>
</g>
<g opacity="0.16">
<path d="M0 0 H4400 M0 100 H4400 M0 200 H4400 M0 300 H4400 M0 400 H4400 M0 500 H4400 M0 600 H4400 M0 700 H4400 M0 800 H4400 M0 900 H4400 M0 1000 H4400 M0 1100 H4400 M0 1200 H4400 M0 1300 H4400 M0 1400 H4400 M0 1500 H4400 M0 1600 H4400 M0 1700 H4400 M0 1800 H4400 M0 1900 H4400 M0 2000 H4400 M0 2100 H4400 M0 2200 H4400 M0 2300 H4400 M0 2400 H4400 M0 2500 H4400 M0 2600 H4400 M0 2700 H4400 M0 2800 H4400 M0 2900 H4400" stroke="#0c0f10" stroke-width="3"/>
<path d="M0 0 V3000 M100 0 V3000 M200 0 V3000 M300 0 V3000 M400 0 V3000 M500 0 V3000 M600 0 V3000 M700 0 V3000 M800 0 V3000 M900 0 V3000 M1000 0 V3000 M1100 0 V3000 M1200 0 V3000 M1300 0 V3000 M1400 0 V3000 M1500 0 V3000 M1600 0 V3000 M1700 0 V3000 M1800 0 V3000 M1900 0 V3000 M2000 0 V3000 M2100 0 V3000 M2200 0 V3000 M2300 0 V3000 M2400 0 V3000 M2500 0 V3000 M2600 0 V3000 M2700 0 V3000 M2800 0 V3000 M2900 0 V3000 M3000 0 V3000 M3100 0 V3000 M3200 0 V3000 M3300 0 V3000 M3400 0 V3000 M3500 0 V3000 M3600 0 V3000 M3700 0 V3000 M3800 0 V3000 M3900 0 V3000 M4000 0 V3000 M4100 0 V3000 M4200 0 V3000 M4300 0 V3000" stroke="#0c0f10" stroke-width="3"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@@ -43,6 +43,10 @@ import {
twentyFirstBattleMap,
twentyFirstBattleUnits,
twentyFirstBattleVictoryPages,
twentySecondBattleBonds,
twentySecondBattleMap,
twentySecondBattleUnits,
twentySecondBattleVictoryPages,
twentiethBattleBonds,
twentiethBattleMap,
twentiethBattleUnits,
@@ -110,7 +114,8 @@ export type BattleScenarioId =
| 'eighteenth-battle-bowang-ambush'
| 'nineteenth-battle-changban-refuge'
| 'twentieth-battle-jiangdong-envoy'
| 'twenty-first-battle-red-cliffs-vanguard';
| 'twenty-first-battle-red-cliffs-vanguard'
| 'twenty-second-battle-red-cliffs-fire';
export type BattleObjectiveKind = 'defeat-leader' | 'keep-unit-alive' | 'secure-terrain' | 'quick-victory';
@@ -1293,6 +1298,69 @@ export const twentyFirstBattleScenario: BattleScenarioDefinition = {
nextCampScene: 'CampScene'
};
export const twentySecondBattleScenario: BattleScenarioDefinition = {
id: 'twenty-second-battle-red-cliffs-fire',
title: '적벽 화공전',
victoryConditionLabel: '조조 본선 퇴각',
defeatConditionLabel: '유비 또는 제갈량 퇴각',
openingObjectiveLines: [
'황개의 거짓 항복선이 조조 함대로 향합니다. 조조군 감시선이 화선을 알아차리면 적벽의 불길은 시작되기도 전에 꺼집니다.',
'강 위에는 연환선과 배다리가 얽혀 있습니다. 전열 장수로 갑판 보병을 묶고, 기동 장수로 강안 기병을 끊어 화선 접안로를 열어야 합니다.',
'제갈량을 지키고 조조 본선을 퇴각시키십시오. 화선 준비 지점을 확보하면 적벽대전의 결정적 승리를 얻을 수 있습니다.'
],
map: twentySecondBattleMap,
units: twentySecondBattleUnits,
bonds: twentySecondBattleBonds,
mapTextureKey: 'battle-map-twenty-second',
leaderUnitId: 'redcliff-fire-leader-cao-cao',
quickVictoryTurnLimit: 31,
baseVictoryGold: 2840,
objectives: [
{
id: 'leader',
kind: 'defeat-leader',
label: '조조 본선 퇴각',
rewardGold: 1900,
unitId: 'redcliff-fire-leader-cao-cao'
},
{
id: 'liu-bei',
kind: 'keep-unit-alive',
label: '유비 생존',
rewardGold: 560,
unitId: 'liu-bei'
},
{
id: 'zhuge-liang',
kind: 'keep-unit-alive',
label: '제갈량 생존',
rewardGold: 860,
unitId: 'zhuge-liang'
},
{
id: 'fire-ship-landing',
kind: 'secure-terrain',
label: '화선 접안로 확보',
rewardGold: 980,
terrain: 'village'
},
{
id: 'quick',
kind: 'quick-victory',
label: '31턴 이내 승리',
rewardGold: 720,
maxTurn: 31
}
],
defeatConditions: [
{ kind: 'unit-defeated', unitId: 'liu-bei' },
{ kind: 'unit-defeated', unitId: 'zhuge-liang' }
],
itemRewards: ['콩 9', '상처약 6', '탁주 4', '화공 전과 +1', '동남풍 기록 +1'],
victoryPages: twentySecondBattleVictoryPages,
nextCampScene: 'CampScene'
};
export const defaultBattleScenarioId: BattleScenarioId = firstBattleScenario.id;
export const battleScenarios: Record<BattleScenarioId, BattleScenarioDefinition> = {
@@ -1316,7 +1384,8 @@ export const battleScenarios: Record<BattleScenarioId, BattleScenarioDefinition>
'eighteenth-battle-bowang-ambush': eighteenthBattleScenario,
'nineteenth-battle-changban-refuge': nineteenthBattleScenario,
'twentieth-battle-jiangdong-envoy': twentiethBattleScenario,
'twenty-first-battle-red-cliffs-vanguard': twentyFirstBattleScenario
'twenty-first-battle-red-cliffs-vanguard': twentyFirstBattleScenario,
'twenty-second-battle-red-cliffs-fire': twentySecondBattleScenario
};
export const defaultBattleScenario = battleScenarios[defaultBattleScenarioId];

View File

@@ -18,6 +18,7 @@ import {
tenthBattleScenario,
thirteenthBattleScenario,
twentyFirstBattleScenario,
twentySecondBattleScenario,
twentiethBattleScenario,
twelfthBattleScenario,
thirdBattleScenario,
@@ -59,6 +60,8 @@ import {
thirteenthBattleVictoryPages,
twentyFirstBattleIntroPages,
twentyFirstBattleVictoryPages,
twentySecondBattleIntroPages,
twentySecondBattleVictoryPages,
twentiethBattleIntroPages,
twentiethBattleVictoryPages,
twelfthBattleIntroPages,
@@ -291,13 +294,24 @@ const sortieFlows: Record<string, SortieFlow> = {
},
[twentyFirstBattleScenario.id]: {
afterBattleId: twentyFirstBattleScenario.id,
eyebrow: '다음 장 준비',
title: '적벽 화공 준비',
eyebrow: '다음 장',
title: twentySecondBattleScenario.title,
description:
'조조군 수군 선봉을 밀어내며 손유 동맹의 첫 전선이 세워졌습니다. 이제 주유와 황개, 제갈량의 계책이 맞물려 본격적인 화공을 준비해야 합니다.',
rewardHint: '다음 장: 황개 고육계와 적벽 화공 준비 중',
pages: twentyFirstBattleVictoryPages,
unavailableNotice: '황개 고육계와 적벽 화공 장은 다음 작업에서 이어집니다. 군영에서 화공 논의, 공명, 보급을 정비하십시오.'
rewardHint: `예상 보상: ${twentySecondBattleScenario.title} 개방 / 적벽대전 결전`,
nextBattleId: twentySecondBattleScenario.id,
campaignStep: 'twenty-second-battle',
pages: [...twentyFirstBattleVictoryPages, ...twentySecondBattleIntroPages]
},
[twentySecondBattleScenario.id]: {
afterBattleId: twentySecondBattleScenario.id,
eyebrow: '다음 장 준비',
title: '형주 확보 준비',
description:
'적벽의 불길로 조조의 남하가 꺾였습니다. 이제 유비군은 머물 땅을 마련하기 위해 형주 남부와 익주로 이어질 근거지를 준비해야 합니다.',
rewardHint: '다음 장: 형주 남부 확보 준비 중',
pages: twentySecondBattleVictoryPages,
unavailableNotice: '형주 남부 확보 장은 다음 작업에서 이어집니다. 군영에서 전후 수습, 공명, 보급을 정비하십시오.'
}
};

View File

@@ -1385,6 +1385,69 @@ export const twentyFirstBattleVictoryPages: StoryPage[] = [
}
];
export const twentySecondBattleIntroPages: StoryPage[] = [
{
id: 'twenty-second-huang-gai-bitter-plan',
bgm: 'story-dark',
chapter: '황개의 결단',
background: 'story-liu-bei',
speaker: '주유',
text: '황개 장군은 거짓 항복의 배를 준비했습니다. 조조가 그 배를 의심하지 않게 하려면, 유비군은 강안의 감시선을 흔들어 화선이 다가갈 길을 열어야 합니다.'
},
{
id: 'twenty-second-zhuge-wind-watch',
bgm: 'battle-prep',
chapter: '동남풍을 기다리다',
background: 'story-three-heroes',
speaker: '제갈량',
text: '바람은 아직 완전히 돌지 않았습니다. 그러나 때가 오면 불길은 강 위에서 산처럼 번질 것입니다. 그 전까지 조조의 척후와 갑판 궁병을 묶어 두어야 합니다.'
},
{
id: 'twenty-second-liu-bei-fire-line',
bgm: 'militia-theme',
chapter: '불길 앞의 뜻',
background: 'story-militia',
speaker: '유비',
portrait: 'liuBei',
text: '오늘의 불은 백성을 태우기 위한 불이 아니라 난세의 물결을 멈추기 위한 불이오. 화선이 닿을 때까지 강안 전선을 흔들림 없이 지킵시다.'
},
{
id: 'twenty-second-fire-attack-sortie',
bgm: 'battle-prep',
chapter: '적벽 화공전',
background: 'story-sortie',
speaker: '제갈량',
text: '조조의 함대가 사슬로 묶인 채 강북에 모였습니다. 화선의 길을 막는 초병을 베고, 조조 본선이 불길에 휩싸일 때까지 전선을 지키십시오.'
}
];
export const twentySecondBattleVictoryPages: StoryPage[] = [
{
id: 'twenty-second-victory-fire-rises',
bgm: 'battle-prep',
chapter: '불길이 일다',
background: 'story-sortie',
text: '화선이 조조군 함대에 닿자 강 위가 붉게 일렁였습니다. 사슬로 묶인 배들은 서로를 구하지 못했고, 장강의 바람은 불길을 북쪽으로 몰아붙였습니다.'
},
{
id: 'twenty-second-cao-cao-retreat',
bgm: 'story-dark',
chapter: '조조의 퇴각',
background: 'story-three-heroes',
speaker: '조조',
text: '오늘은 하늘과 강이 나를 막는구나. 그러나 난세는 아직 끝나지 않았다. 살아 돌아가 다시 군을 모으면, 이 치욕도 갚을 날이 있을 것이다.'
},
{
id: 'twenty-second-liu-bei-next-land',
bgm: 'militia-theme',
chapter: '형주를 바라보다',
background: 'story-militia',
speaker: '유비',
portrait: 'liuBei',
text: '조조의 대군은 물러났지만 우리의 근본은 아직 없습니다. 이제 백성이 머물 땅을 얻고, 형주와 익주로 향할 길을 차근히 세워야 합니다.'
}
];
export const firstBattleMap: BattleMap = {
width: 20,
height: 18,
@@ -1656,6 +1719,12 @@ export const twentyFirstBattleMap: BattleMap = {
terrain: createTwentyFirstBattleTerrain()
};
export const twentySecondBattleMap: BattleMap = {
width: 44,
height: 30,
terrain: createTwentySecondBattleTerrain()
};
export const firstBattleUnits: UnitData[] = [
{
id: 'liu-bei',
@@ -8132,6 +8201,405 @@ export const twentyFirstBattleUnits: UnitData[] = [
}
];
const twentySecondBattleAllyPositions: Record<string, { x: number; y: number }> = {
'liu-bei': { x: 4, y: 23 },
'guan-yu': { x: 6, y: 21 },
'zhang-fei': { x: 6, y: 25 },
'jian-yong': { x: 3, y: 26 },
'mi-zhu': { x: 5, y: 27 },
'sun-qian': { x: 8, y: 23 },
'zhao-yun': { x: 8, y: 20 },
'zhuge-liang': { x: 6, y: 22 }
};
export const twentySecondBattleUnits: UnitData[] = [
...[
...firstBattleUnits.filter((unit) => unit.faction === 'ally'),
...xuzhouRecruitUnits,
...caoBreakRecruitUnits,
...liuBiaoRecruitUnits,
...zhugeRecruitUnits
].map((unit) => placeScenarioUnit(unit, twentySecondBattleAllyPositions[unit.id] ?? { x: unit.x, y: unit.y })),
{
id: 'redcliff-fire-scout-a',
name: '조조 화선 감시병',
faction: 'enemy',
className: '강안 척후',
classKey: 'bandit',
level: 28,
exp: 42,
hp: 78,
maxHp: 78,
attack: 31,
move: 4,
stats: { might: 110, intelligence: 82, leadership: 88, agility: 108, luck: 74 },
equipment: {
weapon: { itemId: 'yellow-turban-saber', level: 4, exp: 4 },
armor: { itemId: 'rebel-vest', level: 3, exp: 42 },
accessory: { itemId: 'grain-pouch', level: 1, exp: 0 }
},
x: 14,
y: 21
},
{
id: 'redcliff-fire-scout-b',
name: '조조 화선 감시병',
faction: 'enemy',
className: '강변 척후',
classKey: 'bandit',
level: 28,
exp: 42,
hp: 78,
maxHp: 78,
attack: 31,
move: 4,
stats: { might: 110, intelligence: 82, leadership: 88, agility: 108, luck: 74 },
equipment: {
weapon: { itemId: 'yellow-turban-saber', level: 4, exp: 4 },
armor: { itemId: 'rebel-vest', level: 3, exp: 42 },
accessory: { itemId: 'grain-pouch', level: 1, exp: 0 }
},
x: 16,
y: 24
},
{
id: 'redcliff-fire-marine-a',
name: '조조 갑판 보병',
faction: 'enemy',
className: '갑판 보병',
classKey: 'yellowTurban',
level: 28,
exp: 44,
hp: 102,
maxHp: 102,
attack: 33,
move: 3,
stats: { might: 120, intelligence: 82, leadership: 102, agility: 90, luck: 74 },
equipment: {
weapon: { itemId: 'iron-spear', level: 4, exp: 12 },
armor: { itemId: 'lamellar-armor', level: 4, exp: 2 },
accessory: { itemId: 'war-manual', level: 1, exp: 0 }
},
x: 21,
y: 19
},
{
id: 'redcliff-fire-marine-b',
name: '조조 갑판 보병',
faction: 'enemy',
className: '갑판 보병',
classKey: 'yellowTurban',
level: 28,
exp: 44,
hp: 102,
maxHp: 102,
attack: 33,
move: 3,
stats: { might: 120, intelligence: 82, leadership: 102, agility: 90, luck: 74 },
equipment: {
weapon: { itemId: 'iron-spear', level: 4, exp: 12 },
armor: { itemId: 'lamellar-armor', level: 4, exp: 2 },
accessory: { itemId: 'war-manual', level: 1, exp: 0 }
},
x: 23,
y: 21
},
{
id: 'redcliff-fire-marine-c',
name: '조조 갑판 보병',
faction: 'enemy',
className: '연환선 보병',
classKey: 'yellowTurban',
level: 29,
exp: 46,
hp: 106,
maxHp: 106,
attack: 34,
move: 3,
stats: { might: 122, intelligence: 84, leadership: 104, agility: 92, luck: 75 },
equipment: {
weapon: { itemId: 'iron-spear', level: 4, exp: 18 },
armor: { itemId: 'reinforced-lamellar', level: 4, exp: 8 },
accessory: { itemId: 'war-manual', level: 1, exp: 0 }
},
x: 30,
y: 14
},
{
id: 'redcliff-fire-marine-d',
name: '조조 갑판 보병',
faction: 'enemy',
className: '연환선 보병',
classKey: 'yellowTurban',
level: 29,
exp: 46,
hp: 106,
maxHp: 106,
attack: 34,
move: 3,
stats: { might: 122, intelligence: 84, leadership: 104, agility: 92, luck: 75 },
equipment: {
weapon: { itemId: 'iron-spear', level: 4, exp: 18 },
armor: { itemId: 'reinforced-lamellar', level: 4, exp: 8 },
accessory: { itemId: 'war-manual', level: 1, exp: 0 }
},
x: 32,
y: 17
},
{
id: 'redcliff-fire-archer-a',
name: '조조 선상 궁병',
faction: 'enemy',
className: '선상 궁병',
classKey: 'archer',
level: 28,
exp: 42,
hp: 76,
maxHp: 76,
attack: 33,
move: 3,
stats: { might: 106, intelligence: 96, leadership: 92, agility: 104, luck: 76 },
equipment: {
weapon: { itemId: 'short-bow', level: 4, exp: 12 },
armor: { itemId: 'cloth-armor', level: 4, exp: 4 },
accessory: { itemId: 'wind-quiver', level: 1, exp: 0 }
},
x: 22,
y: 15
},
{
id: 'redcliff-fire-archer-b',
name: '조조 선상 궁병',
faction: 'enemy',
className: '선상 궁병',
classKey: 'archer',
level: 28,
exp: 42,
hp: 76,
maxHp: 76,
attack: 33,
move: 3,
stats: { might: 106, intelligence: 96, leadership: 92, agility: 104, luck: 76 },
equipment: {
weapon: { itemId: 'short-bow', level: 4, exp: 12 },
armor: { itemId: 'cloth-armor', level: 4, exp: 4 },
accessory: { itemId: 'wind-quiver', level: 1, exp: 0 }
},
x: 26,
y: 18
},
{
id: 'redcliff-fire-archer-c',
name: '조조 연환 궁병',
faction: 'enemy',
className: '연환선 궁병',
classKey: 'archer',
level: 29,
exp: 46,
hp: 78,
maxHp: 78,
attack: 34,
move: 3,
stats: { might: 108, intelligence: 98, leadership: 94, agility: 106, luck: 76 },
equipment: {
weapon: { itemId: 'short-bow', level: 4, exp: 20 },
armor: { itemId: 'cloth-armor', level: 4, exp: 8 },
accessory: { itemId: 'wind-quiver', level: 1, exp: 0 }
},
x: 35,
y: 12
},
{
id: 'redcliff-fire-archer-d',
name: '조조 연환 궁병',
faction: 'enemy',
className: '연환선 궁병',
classKey: 'archer',
level: 29,
exp: 46,
hp: 78,
maxHp: 78,
attack: 34,
move: 3,
stats: { might: 108, intelligence: 98, leadership: 94, agility: 106, luck: 76 },
equipment: {
weapon: { itemId: 'short-bow', level: 4, exp: 20 },
armor: { itemId: 'cloth-armor', level: 4, exp: 8 },
accessory: { itemId: 'wind-quiver', level: 1, exp: 0 }
},
x: 37,
y: 17
},
{
id: 'redcliff-fire-cavalry-a',
name: '조조 강안 기병',
faction: 'enemy',
className: '강안 기병',
classKey: 'cavalry',
level: 29,
exp: 46,
hp: 110,
maxHp: 110,
attack: 36,
move: 5,
stats: { might: 126, intelligence: 82, leadership: 100, agility: 118, luck: 76 },
equipment: {
weapon: { itemId: 'iron-spear', level: 4, exp: 22 },
armor: { itemId: 'lamellar-armor', level: 4, exp: 12 },
accessory: { itemId: 'bravery-token', level: 1, exp: 0 }
},
x: 18,
y: 25
},
{
id: 'redcliff-fire-cavalry-b',
name: '조조 강안 기병',
faction: 'enemy',
className: '강안 기병',
classKey: 'cavalry',
level: 29,
exp: 46,
hp: 110,
maxHp: 110,
attack: 36,
move: 5,
stats: { might: 126, intelligence: 82, leadership: 100, agility: 118, luck: 76 },
equipment: {
weapon: { itemId: 'iron-spear', level: 4, exp: 22 },
armor: { itemId: 'lamellar-armor', level: 4, exp: 12 },
accessory: { itemId: 'bravery-token', level: 1, exp: 0 }
},
x: 27,
y: 24
},
{
id: 'redcliff-fire-cavalry-c',
name: '조조 강안 기병',
faction: 'enemy',
className: '강북 기병',
classKey: 'cavalry',
level: 30,
exp: 48,
hp: 114,
maxHp: 114,
attack: 37,
move: 5,
stats: { might: 128, intelligence: 84, leadership: 102, agility: 120, luck: 77 },
equipment: {
weapon: { itemId: 'iron-spear', level: 4, exp: 28 },
armor: { itemId: 'reinforced-lamellar', level: 4, exp: 16 },
accessory: { itemId: 'bravery-token', level: 1, exp: 0 }
},
x: 34,
y: 22
},
{
id: 'redcliff-fire-guard-a',
name: '조조 본선 친위',
faction: 'enemy',
className: '본선 친위',
classKey: 'yellowTurban',
level: 30,
exp: 50,
hp: 116,
maxHp: 116,
attack: 36,
move: 3,
stats: { might: 128, intelligence: 86, leadership: 110, agility: 94, luck: 78 },
equipment: {
weapon: { itemId: 'iron-spear', level: 4, exp: 34 },
armor: { itemId: 'reinforced-lamellar', level: 4, exp: 22 },
accessory: { itemId: 'war-manual', level: 1, exp: 0 }
},
x: 38,
y: 13
},
{
id: 'redcliff-fire-guard-b',
name: '조조 본선 친위',
faction: 'enemy',
className: '본선 친위',
classKey: 'yellowTurban',
level: 30,
exp: 50,
hp: 116,
maxHp: 116,
attack: 36,
move: 3,
stats: { might: 128, intelligence: 86, leadership: 110, agility: 94, luck: 78 },
equipment: {
weapon: { itemId: 'iron-spear', level: 4, exp: 34 },
armor: { itemId: 'reinforced-lamellar', level: 4, exp: 22 },
accessory: { itemId: 'war-manual', level: 1, exp: 0 }
},
x: 39,
y: 16
},
{
id: 'redcliff-fire-strategist-a',
name: '조조 수군 군리',
faction: 'enemy',
className: '연환선 군리',
classKey: 'archer',
level: 29,
exp: 48,
hp: 82,
maxHp: 82,
attack: 33,
move: 3,
stats: { might: 84, intelligence: 104, leadership: 98, agility: 90, luck: 78 },
equipment: {
weapon: { itemId: 'short-bow', level: 4, exp: 28 },
armor: { itemId: 'cloth-armor', level: 4, exp: 14 },
accessory: { itemId: 'war-manual', level: 1, exp: 0 }
},
x: 36,
y: 11
},
{
id: 'redcliff-fire-strategist-b',
name: '조조 수군 군리',
faction: 'enemy',
className: '연환선 군리',
classKey: 'archer',
level: 29,
exp: 48,
hp: 82,
maxHp: 82,
attack: 33,
move: 3,
stats: { might: 84, intelligence: 104, leadership: 98, agility: 90, luck: 78 },
equipment: {
weapon: { itemId: 'short-bow', level: 4, exp: 28 },
armor: { itemId: 'cloth-armor', level: 4, exp: 14 },
accessory: { itemId: 'war-manual', level: 1, exp: 0 }
},
x: 40,
y: 12
},
{
id: 'redcliff-fire-leader-cao-cao',
name: '조조',
faction: 'enemy',
className: '위왕군 총대장',
classKey: 'rebelLeader',
level: 32,
exp: 54,
hp: 156,
maxHp: 156,
attack: 39,
move: 4,
stats: { might: 130, intelligence: 118, leadership: 132, agility: 96, luck: 82 },
equipment: {
weapon: { itemId: 'leader-axe', level: 4, exp: 42 },
armor: { itemId: 'reinforced-lamellar', level: 4, exp: 34 },
accessory: { itemId: 'war-manual', level: 1, exp: 0 }
},
x: 41,
y: 14
}
];
export const firstBattleBonds: BattleBond[] = [
{
id: 'liu-bei__guan-yu',
@@ -8285,6 +8753,7 @@ export const eighteenthBattleBonds: BattleBond[] = [...sixteenthBattleBonds, ...
export const nineteenthBattleBonds: BattleBond[] = [...eighteenthBattleBonds].map(cloneBattleBondForScenario);
export const twentiethBattleBonds: BattleBond[] = [...nineteenthBattleBonds].map(cloneBattleBondForScenario);
export const twentyFirstBattleBonds: BattleBond[] = [...twentiethBattleBonds].map(cloneBattleBondForScenario);
export const twentySecondBattleBonds: BattleBond[] = [...twentyFirstBattleBonds].map(cloneBattleBondForScenario);
function createEighthBattleTerrain(): TerrainType[][] {
return Array.from({ length: 22 }, (_, y) =>
@@ -8964,6 +9433,61 @@ function createTwentyFirstBattleTerrain(): TerrainType[][] {
);
}
function createTwentySecondBattleTerrain(): TerrainType[][] {
return Array.from({ length: 30 }, (_, y) =>
Array.from({ length: 44 }, (_, x): TerrainType => {
if ((x <= 4 && y >= 21 && y <= 28) || (x >= 5 && x <= 9 && y >= 24 && y <= 29)) {
return 'camp';
}
if ((x >= 36 && x <= 43 && y >= 10 && y <= 18) || (x >= 33 && x <= 37 && y >= 12 && y <= 16)) {
return 'fort';
}
if ((x >= 12 && x <= 15 && y >= 20 && y <= 22) || (x >= 27 && x <= 30 && y >= 11 && y <= 13)) {
return 'village';
}
if (
(y === 23 && x >= 3 && x <= 17) ||
(y === 20 && x >= 15 && x <= 28) ||
(y === 15 && x >= 28 && x <= 40) ||
(x === 36 && y >= 10 && y <= 22)
) {
return 'road';
}
if (
(x >= 7 && x <= 18 && y === 26 - Math.floor(x / 2)) ||
(x >= 19 && x <= 35 && y === 25 - Math.floor(x / 3)) ||
(x >= 30 && x <= 39 && y === 14)
) {
return 'road';
}
if ((x >= 18 && x <= 25 && y >= 0 && y <= 29) || (x >= 29 && x <= 31 && y >= 0 && y <= 22)) {
return 'river';
}
if ((x >= 14 && x <= 16 && y >= 24 && y <= 29) || (x >= 26 && x <= 28 && y >= 20 && y <= 29)) {
return 'river';
}
if (
(x >= 4 && x <= 13 && y >= 8 && y <= 17) ||
(x <= 8 && y <= 7) ||
(x >= 31 && x <= 42 && y <= 8)
) {
return 'forest';
}
if (
(x >= 9 && x <= 18 && y >= 2 && y <= 6) ||
(x >= 32 && x <= 43 && y >= 21 && y <= 29) ||
(x >= 38 && x <= 43 && y >= 3 && y <= 7)
) {
return 'hill';
}
if ((x <= 1 && y <= 13) || (x >= 42 && y <= 9) || (x >= 42 && y >= 20)) {
return 'cliff';
}
return 'plain';
})
);
}
function placeScenarioUnit(unit: UnitData, position: { x: number; y: number }): UnitData {
return {
...cloneUnitForScenario(unit),

View File

@@ -222,7 +222,25 @@ const unitTexture: Record<string, string> = {
'redcliff-vanguard-guard-b': 'unit-rebel',
'redcliff-vanguard-strategist-a': 'unit-rebel-archer',
'redcliff-vanguard-strategist-b': 'unit-rebel-archer',
'redcliff-vanguard-leader-cai-mao': 'unit-rebel-leader'
'redcliff-vanguard-leader-cai-mao': 'unit-rebel-leader',
'redcliff-fire-scout-a': 'unit-rebel',
'redcliff-fire-scout-b': 'unit-rebel',
'redcliff-fire-marine-a': 'unit-rebel',
'redcliff-fire-marine-b': 'unit-rebel',
'redcliff-fire-marine-c': 'unit-rebel',
'redcliff-fire-marine-d': 'unit-rebel',
'redcliff-fire-archer-a': 'unit-rebel-archer',
'redcliff-fire-archer-b': 'unit-rebel-archer',
'redcliff-fire-archer-c': 'unit-rebel-archer',
'redcliff-fire-archer-d': 'unit-rebel-archer',
'redcliff-fire-cavalry-a': 'unit-rebel-cavalry',
'redcliff-fire-cavalry-b': 'unit-rebel-cavalry',
'redcliff-fire-cavalry-c': 'unit-rebel-cavalry',
'redcliff-fire-guard-a': 'unit-rebel',
'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'
};
const unitTextureByClass: Partial<Record<UnitClassKey, string>> = {
@@ -900,7 +918,25 @@ const enemyAiByUnitId: Record<string, EnemyAiBehavior> = {
'redcliff-vanguard-guard-b': 'guard',
'redcliff-vanguard-strategist-a': 'hold',
'redcliff-vanguard-strategist-b': 'hold',
'redcliff-vanguard-leader-cai-mao': 'guard'
'redcliff-vanguard-leader-cai-mao': 'guard',
'redcliff-fire-scout-a': 'aggressive',
'redcliff-fire-scout-b': 'aggressive',
'redcliff-fire-marine-a': 'guard',
'redcliff-fire-marine-b': 'guard',
'redcliff-fire-marine-c': 'guard',
'redcliff-fire-marine-d': 'guard',
'redcliff-fire-archer-a': 'hold',
'redcliff-fire-archer-b': 'hold',
'redcliff-fire-archer-c': 'hold',
'redcliff-fire-archer-d': 'hold',
'redcliff-fire-cavalry-a': 'aggressive',
'redcliff-fire-cavalry-b': 'aggressive',
'redcliff-fire-cavalry-c': 'aggressive',
'redcliff-fire-guard-a': 'guard',
'redcliff-fire-guard-b': 'guard',
'redcliff-fire-strategist-a': 'hold',
'redcliff-fire-strategist-b': 'hold',
'redcliff-fire-leader-cao-cao': 'guard'
};
const defaultEnemyAiByClass: Partial<Record<UnitClassKey, EnemyAiBehavior>> = {

View File

@@ -18,6 +18,7 @@ import tenthBattleMapUrl from '../../assets/images/battle/tenth-battle-map.svg';
import thirteenthBattleMapUrl from '../../assets/images/battle/thirteenth-battle-map.svg';
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 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';
@@ -100,6 +101,7 @@ export class BootScene extends Phaser.Scene {
this.load.image('battle-map-nineteenth', nineteenthBattleMapUrl);
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('portrait-liu-bei', liuBeiPortraitUrl);
this.load.image('portrait-guan-yu', guanYuPortraitUrl);
this.load.image('portrait-zhang-fei', zhangFeiPortraitUrl);

View File

@@ -285,7 +285,8 @@ const campaignTimelineChapters: CampaignTimelineChapter[] = [
'eighteenth-battle-bowang-ambush',
'nineteenth-battle-changban-refuge',
'twentieth-battle-jiangdong-envoy',
'twenty-first-battle-red-cliffs-vanguard'
'twenty-first-battle-red-cliffs-vanguard',
'twenty-second-battle-red-cliffs-fire'
],
nextHints: ['강동 사절', '화공 준비', '조조 남하']
},
@@ -328,7 +329,8 @@ const campBattleIds = {
eighteenth: 'eighteenth-battle-bowang-ambush',
nineteenth: 'nineteenth-battle-changban-refuge',
twentieth: 'twentieth-battle-jiangdong-envoy',
twentyFirst: 'twenty-first-battle-red-cliffs-vanguard'
twentyFirst: 'twenty-first-battle-red-cliffs-vanguard',
twentySecond: 'twenty-second-battle-red-cliffs-fire'
} as const;
const requiredSortieUnitIds = new Set(['liu-bei']);
@@ -555,6 +557,18 @@ const sortieRulesByBattleId: Partial<Record<BattleScenarioId, SortieRuleDefiniti
{ unitId: 'sun-qian', reason: '강동 회담 기록과 동맹 명분을 전투 뒤 보고로 이어 줍니다.' }
],
note: '적벽 전초전은 첫 강상 전투입니다. 유비와 제갈량을 중심으로 전열, 기동, 외교 기록 중 어느 축을 두껍게 할지 고르십시오.'
},
'twenty-second-battle-red-cliffs-fire': {
maxUnits: 6,
recommended: [
{ unitId: 'liu-bei', reason: '적벽 결전의 명분과 패배 조건의 중심입니다.' },
{ unitId: 'zhuge-liang', reason: '동남풍과 화공 시점을 판단하는 핵심 책략 담당입니다.' },
{ unitId: 'guan-yu', reason: '갑판 보병과 본선 친위를 정면에서 받아낼 전열입니다.' },
{ unitId: 'zhao-yun', reason: '화선을 노리는 강안 기병과 척후를 빠르게 끊습니다.' },
{ unitId: 'zhang-fei', reason: '배다리와 좁은 갑판 길을 밀어붙이는 돌파 역할입니다.' },
{ unitId: 'mi-zhu', reason: '화공전의 장기 피해를 버티기 위한 회복과 보급을 맡습니다.' }
],
note: '적벽 화공전은 강 위의 결전입니다. 제갈량의 책략과 전열 유지, 화선 접안로 확보를 중심으로 여섯 명을 고르십시오.'
}
};
@@ -2313,6 +2327,87 @@ const campDialogues: CampDialogue[] = [
rewardExp: 10
}
]
},
{
id: 'liu-zhuge-after-red-cliffs-fire',
title: '불길 뒤의 대계',
availableAfterBattleIds: [campBattleIds.twentySecond],
unitIds: ['liu-bei', 'zhuge-liang'],
bondId: 'liu-bei__zhuge-liang',
rewardExp: 34,
lines: [
'유비: 적벽의 불길은 조조를 물렸지만, 우리에게 머물 땅이 생긴 것은 아니오.',
'제갈량: 그래서 이제 싸움의 목적이 바뀝니다. 물러나는 조조를 보며 형주 남부의 길을 열어야 합니다.',
'유비: 백성이 다시 떠돌지 않게 하려면, 다음 걸음은 전공보다 근본을 세우는 일이겠구려.'
],
choices: [
{
id: 'plan-jingzhou-foundation',
label: '형주 근거지 계획을 세운다',
response: '제갈량은 형주 남부의 군현과 민심을 나누어 적었고, 유비는 처음으로 머물 땅의 조건을 분명히 말했다.',
rewardExp: 13
},
{
id: 'temper-victory-pride',
label: '승리의 들뜸을 경계한다',
response: '유비는 큰 승리 뒤의 방심을 경계했고, 제갈량은 그 말을 다음 작전의 첫 원칙으로 삼았다.',
rewardExp: 11
}
]
},
{
id: 'guan-zhuge-after-red-cliffs-fire',
title: '불길을 지킨 전열',
availableAfterBattleIds: [campBattleIds.twentySecond],
unitIds: ['guan-yu', 'zhuge-liang'],
bondId: 'guan-yu__zhuge-liang',
rewardExp: 26,
lines: [
'관우: 불길이 번질 때 병사들이 흔들렸으나, 전열을 무너뜨리지는 않았습니다.',
'제갈량: 운장 장군의 침착함이 있었기에 화선의 길이 끊기지 않았습니다. 계책도 그것을 지킬 칼이 있어야 완성됩니다.',
'관우: 다음 땅을 얻는 싸움에서도, 계책이 가리키는 곳을 제 칼로 지키겠습니다.'
],
choices: [
{
id: 'guard-next-crossing',
label: '다음 도하 지점을 맡긴다',
response: '제갈량은 관우에게 다음 강가 전열을 맡겼고, 관우는 묵묵히 청룡언월도를 세워 답했다.',
rewardExp: 10
},
{
id: 'honor-steady-line',
label: '흔들리지 않은 전열을 칭찬한다',
response: '관우는 큰 공을 말하지 않았지만, 병사들이 그의 등 뒤에서 안정을 찾았다는 말에는 조용히 고개를 숙였다.',
rewardExp: 9
}
]
},
{
id: 'liu-mi-after-red-cliffs-fire',
title: '불길 속 보급로',
availableAfterBattleIds: [campBattleIds.twentySecond],
unitIds: ['liu-bei', 'mi-zhu'],
bondId: 'liu-bei__mi-zhu',
rewardExp: 24,
lines: [
'미축: 불길이 번지는 전장은 칼보다 물과 약이 먼저 필요했습니다. 젖은 약품을 버리고 마른 꾸러미부터 살렸습니다.',
'유비: 전공이 커도 병사가 쓰러지면 다음 걸음은 없소. 그대가 붙잡은 보급로가 곧 우리의 근본이오.',
'미축: 다음부터는 빠른 말과 마른 약품을 한 묶음으로 준비하겠습니다. 전장도 이제 더 넓어질 테니까요.'
],
choices: [
{
id: 'bind-fast-supply-line',
label: '기동 보급로를 정한다',
response: '미축은 기동 부대가 바로 가져갈 수 있는 보급 꾸러미를 따로 묶었고, 유비는 다음 전장의 장기전을 대비했다.',
rewardExp: 10
},
{
id: 'reserve-dry-medicine',
label: '젖지 않는 약품을 챙긴다',
response: '미축은 젖은 강가에서도 쓸 수 있는 약품을 따로 묶었고, 유비는 병사들이 승리 뒤에도 무너지지 않게 했다.',
rewardExp: 9
}
]
}
];
@@ -2702,6 +2797,66 @@ const campVisits: CampVisitDefinition[] = [
itemRewards: ['탁주 1']
}
]
},
{
id: 'red-cliffs-aftermath-relief',
title: '적벽 전후 수습',
location: '강안 임시 진영',
availableAfterBattleIds: [campBattleIds.twentySecond],
bondId: 'liu-bei__mi-zhu',
description: '불길 뒤의 강안에서 부상자와 흩어진 병사를 수습하며 다음 행군의 보급을 정리합니다.',
lines: [
'미축: 승리는 컸지만 젖은 군량과 다친 병사가 많습니다. 지금 정리하지 않으면 형주로 향할 때 다시 흔들릴 것입니다.',
'유비: 불길 뒤에 남은 사람을 먼저 살펴야 하오. 승리의 이름보다 살아 움직일 군이 먼저요.',
'강안 임시 진영에는 젖은 깃발과 그을린 갑옷이 널려 있고, 병사들은 비로소 숨을 고릅니다.'
],
choices: [
{
id: 'treat-burned-soldiers',
label: '화상 입은 병사를 돌본다',
response: '미축은 약품을 아끼지 않았고, 유비는 전공보다 병사를 먼저 살핀다는 군영의 기준을 다시 세웠다.',
bondExp: 22,
itemRewards: ['상처약 2']
},
{
id: 'restore-river-supplies',
label: '강안 보급을 다시 묶는다',
response: '젖은 군량을 버리고 쓸 수 있는 물자를 다시 묶자, 다음 형주 진입을 버틸 보급이 마련되었다.',
bondExp: 18,
gold: 180,
itemRewards: ['콩 3']
}
]
},
{
id: 'jingzhou-entry-council',
title: '형주 진입 논의',
location: '전후 군막',
availableAfterBattleIds: [campBattleIds.twentySecond],
bondId: 'liu-bei__zhuge-liang',
description: '제갈량과 함께 적벽 이후의 형세를 정리하고 형주 남부로 향할 첫 명분을 준비합니다.',
lines: [
'제갈량: 조조가 물러난 지금, 빈틈은 오래 머물지 않습니다. 형주 남부의 군현은 두려움과 기대 사이에 있습니다.',
'유비: 힘으로 빼앗는다면 조조와 무엇이 다르겠소. 백성이 기대어 설 명분을 먼저 세워야 하오.',
'군막의 지도 위에는 적벽의 붉은 표시가 지워지고, 형주 남부의 길목이 새로 동그라미 쳐집니다.'
],
choices: [
{
id: 'draft-jingzhou-cause',
label: '형주 진입 명분을 쓴다',
response: '제갈량은 조조를 막은 공과 백성 보호의 뜻을 함께 적어, 형주 남부로 향할 첫 문장을 만들었다.',
bondExp: 24,
itemRewards: ['형주 진입 문서 1']
},
{
id: 'survey-southern-commandery',
label: '남부 군현 정보를 모은다',
response: '손건과 간옹이 들은 풍문까지 지도에 더해지자, 다음 행군에서 설득해야 할 성과 피해야 할 길이 분명해졌다.',
bondExp: 20,
gold: 200,
itemRewards: ['민심 기록 1']
}
]
}
];
@@ -2792,7 +2947,8 @@ export class CampScene extends Phaser.Scene {
battleId === campBattleIds.eighteenth ||
battleId === campBattleIds.nineteenth ||
battleId === campBattleIds.twentieth ||
battleId === campBattleIds.twentyFirst
battleId === campBattleIds.twentyFirst ||
battleId === campBattleIds.twentySecond
) {
this.campaign = ensureCampaignRosterUnits(
[...xuzhouRecruitUnits, ...caoBreakRecruitUnits, ...liuBiaoRecruitUnits, ...zhugeRecruitUnits],
@@ -2850,6 +3006,9 @@ export class CampScene extends Phaser.Scene {
private currentCampTitle() {
const battleId = this.currentCampBattleId();
if (battleId === campBattleIds.twentySecond) {
return '적벽 화공전 후 군영';
}
if (battleId === campBattleIds.twentyFirst) {
return '적벽 전초전 후 군영';
}

View File

@@ -19,6 +19,7 @@ import {
tenthBattleScenario,
thirteenthBattleScenario,
twentyFirstBattleScenario,
twentySecondBattleScenario,
twentiethBattleScenario,
twelfthBattleScenario,
thirdBattleScenario
@@ -341,7 +342,8 @@ export class TitleScene extends Phaser.Scene {
campaign.step === 'eighteenth-camp' ||
campaign.step === 'nineteenth-camp' ||
campaign.step === 'twentieth-camp' ||
campaign.step === 'twenty-first-camp'
campaign.step === 'twenty-first-camp' ||
campaign.step === 'twenty-second-camp'
) {
this.scene.start('CampScene');
return;
@@ -452,6 +454,11 @@ export class TitleScene extends Phaser.Scene {
return;
}
if (campaign.step === 'twenty-second-battle') {
this.scene.start('BattleScene', { battleId: twentySecondBattleScenario.id });
return;
}
if (campaign.step === 'first-victory-story') {
this.scene.start('StoryScene', { pages: firstBattleVictoryPages, nextScene: 'TitleScene' });
return;

View File

@@ -83,7 +83,9 @@ export type CampaignStep =
| 'twentieth-battle'
| 'twentieth-camp'
| 'twenty-first-battle'
| 'twenty-first-camp';
| 'twenty-first-camp'
| 'twenty-second-battle'
| 'twenty-second-camp';
export type CampaignUnitProgressSnapshot = {
unitId: string;
@@ -166,7 +168,8 @@ const campaignBattleSteps: Record<string, { victory: CampaignStep; retry: Campai
'eighteenth-battle-bowang-ambush': { victory: 'eighteenth-camp', retry: 'eighteenth-battle' },
'nineteenth-battle-changban-refuge': { victory: 'nineteenth-camp', retry: 'nineteenth-battle' },
'twentieth-battle-jiangdong-envoy': { victory: 'twentieth-camp', retry: 'twentieth-battle' },
'twenty-first-battle-red-cliffs-vanguard': { victory: 'twenty-first-camp', retry: 'twenty-first-battle' }
'twenty-first-battle-red-cliffs-vanguard': { victory: 'twenty-first-camp', retry: 'twenty-first-battle' },
'twenty-second-battle-red-cliffs-fire': { victory: 'twenty-second-camp', retry: 'twenty-second-battle' }
};
export type CampaignSaveSlotSummary = {