Add Weishui north bank chapter

This commit is contained in:
2026-06-24 21:10:11 +09:00
parent ddc862d217
commit 018132b59c
11 changed files with 869 additions and 84 deletions

View File

@@ -4066,6 +4066,59 @@ export const sixtyFourthBattleVictoryPages: StoryPage[] = [
}
];
export const sixtyFifthBattleIntroPages: StoryPage[] = [
{
id: 'sixty-fifth-northbank-council',
bgm: 'story-dark',
chapter: '위수 북안',
background: 'story-weishui-northbank',
speaker: '제갈량',
text: '위수 남안의 진영은 확보했지만, 사마의는 북안의 진문과 봉화대를 더 촘촘히 묶었습니다. 이번에는 강을 건너 끝장을 보려는 싸움이 아니라, 북안 지연선을 눌러 다음 출정의 숨을 확보하는 싸움입니다.'
},
{
id: 'sixty-fifth-forward-ledger',
bgm: 'battle-prep',
chapter: '전진 장부',
background: 'story-weishui-northbank',
speaker: '황권',
text: '강을 건너면 이긴 곳보다 버릴 수 없는 곳이 더 빨리 늘어납니다. 전진 진영과 회수선, 마을 군량을 한 장부에 묶지 않으면 승리한 길이 곧 굶주린 길이 됩니다.'
},
{
id: 'sixty-fifth-rearguard-threat',
bgm: 'story-dark',
chapter: '재추격의 그림자',
background: 'story-weishui-northbank',
speaker: '강유',
text: '장합은 물러나는 척하며 다시 뒤를 물 것이고, 곽회는 봉화와 우회 기병으로 사마의의 시간을 벌 것입니다. 북안으로 나아가되, 돌아올 표식과 남길 진영을 동시에 지켜야 합니다.'
}
];
export const sixtyFifthBattleVictoryPages: StoryPage[] = [
{
id: 'sixty-fifth-victory-delay-line-pressed',
bgm: 'militia-theme',
chapter: '눌린 지연선',
background: 'story-weishui-northbank',
text: '촉한군은 위수 북안의 전진 진영을 압박하고 장합의 재추격을 늦췄습니다. 사마의의 본영은 아직 물러서지 않았지만, 그의 시간을 버는 선이 처음으로 흔들렸습니다.'
},
{
id: 'sixty-fifth-victory-northbank-ledger',
bgm: 'story-dark',
chapter: '강북 장부',
background: 'story-weishui-northbank',
speaker: '황권',
text: '전진 진영과 마을 군량, 회수 깃발이 한 장부에 묶였습니다. 제갈량은 그 장부 위에 더 깊은 북방 길을 그리되, 아직 끝이라는 말을 적지 않았습니다.'
},
{
id: 'sixty-fifth-next-front-choice',
bgm: 'story-dark',
chapter: '다음 선택',
background: 'story-weishui-northbank',
speaker: '제갈량',
text: '위수 북안이 열렸다고 북벌이 끝난 것은 아닙니다. 오래 붙들 진영, 버릴 진영, 다시 밀고 들어갈 길을 나누어야만 다음 북방 공세가 병사의 숨을 해치지 않습니다.'
}
];
export const firstBattleMap: BattleMap = {
width: 20,
height: 18,
@@ -4595,6 +4648,12 @@ export const sixtyFourthBattleMap: BattleMap = {
terrain: createSixtyFourthBattleTerrain()
};
export const sixtyFifthBattleMap: BattleMap = {
width: 130,
height: 112,
terrain: createSixtyFifthBattleTerrain()
};
export const firstBattleUnits: UnitData[] = [
{
id: 'liu-bei',
@@ -18188,6 +18247,103 @@ export const sixtyFourthBattleUnits: UnitData[] = [
...sixtyFourthEnemySpecs.map((spec) => createThirtyFirstEnemyUnit(...spec))
];
const sixtyFifthBattleAllyPositions: Record<string, { x: number; y: number }> = {
'guan-yu': { x: 18, y: 100 },
'zhang-fei': { x: 26, y: 103 },
'jian-yong': { x: 9, y: 108 },
'mi-zhu': { x: 15, y: 109 },
'sun-qian': { x: 21, y: 109 },
'zhao-yun': { x: 33, y: 98 },
'zhuge-liang': { x: 20, y: 95 },
'ma-liang': { x: 27, y: 92 },
'yi-ji': { x: 9, y: 110 },
'gong-zhi': { x: 31, y: 107 },
'huang-zhong': { x: 41, y: 96 },
'wei-yan': { x: 48, y: 96 },
'pang-tong': { x: 17, y: 91 },
'fa-zheng': { x: 31, y: 90 },
'wu-yi': { x: 50, y: 105 },
'yan-yan': { x: 54, y: 98 },
'li-yan': { x: 36, y: 89 },
'huang-quan': { x: 14, y: 94 },
'ma-chao': { x: 57, y: 94 },
'ma-dai': { x: 63, y: 101 },
'wang-ping': { x: 43, y: 89 },
'jiang-wei': { x: 37, y: 92 }
};
const sixtyFifthEnemySpecs: ReadonlyArray<
readonly [string, string, string, UnitClassKey, number, number, number, number, number]
> = [
['northern-eleventh-scout-a', '북안 서쪽 척후병', '서쪽 강북 정찰', 'bandit', 241, 1160, 314, 52, 92],
['northern-eleventh-scout-b', '회수선 척후병', '회수선 정찰', 'bandit', 241, 1160, 314, 66, 88],
['northern-eleventh-scout-c', '북안 물목 척후병', '중앙 물목 정찰', 'bandit', 242, 1172, 316, 80, 82],
['northern-eleventh-scout-d', '봉화 척후병', '동쪽 봉화 정찰', 'bandit', 242, 1172, 316, 94, 74],
['northern-eleventh-scout-e', '곽회 전초 척후병', '차단로 정찰', 'bandit', 243, 1184, 318, 110, 62],
['northern-eleventh-scout-f', '북안 뒤 척후병', '북쪽 진문 정찰', 'bandit', 243, 1184, 318, 128, 40],
['northern-eleventh-infantry-a', '위군 북안 보병', '서쪽 강북 보병', 'yellowTurban', 242, 1780, 420, 59, 87],
['northern-eleventh-infantry-b', '위군 회수선 보병', '회수선 보병', 'yellowTurban', 242, 1780, 420, 72, 84],
['northern-eleventh-infantry-c', '위군 물목 보병', '중앙 물목 보병', 'yellowTurban', 243, 1800, 424, 86, 77],
['northern-eleventh-infantry-d', '위군 봉화 보병', '봉화대 보병', 'yellowTurban', 243, 1800, 424, 101, 70],
['northern-eleventh-infantry-e', '곽회 차단 보병', '차단로 보병', 'yellowTurban', 244, 1820, 428, 114, 57],
['northern-eleventh-infantry-f', '사마의 북안 보병', '북안 본영 보병', 'yellowTurban', 244, 1820, 428, 125, 47],
['northern-eleventh-infantry-g', '수레 차단병', '강북 수레 차단 보병', 'yellowTurban', 245, 1840, 432, 97, 93],
['northern-eleventh-infantry-h', '북쪽 진문 보병', '진문 보병', 'yellowTurban', 245, 1840, 432, 128, 29],
['northern-eleventh-archer-a', '북안 서쪽 궁병', '서쪽 능선 궁병', 'archer', 242, 1290, 346, 68, 79],
['northern-eleventh-archer-b', '회수선 궁병', '회수선 궁병', 'archer', 242, 1290, 346, 82, 73],
['northern-eleventh-archer-c', '물목 궁병', '중앙 물목 궁병', 'archer', 243, 1302, 348, 96, 65],
['northern-eleventh-archer-d', '봉화대 궁병', '봉화 궁병', 'archer', 244, 1314, 350, 110, 54],
['northern-eleventh-archer-e', '북안 본영 궁병', '본영 궁병', 'archer', 244, 1314, 350, 122, 37],
['northern-eleventh-archer-f', '북쪽 절벽 궁병', '절벽 궁병', 'archer', 245, 1326, 352, 128, 21],
['northern-eleventh-cavalry-a', '장합 재추격 기병', '서쪽 재추격 기병', 'cavalry', 243, 1860, 444, 54, 80],
['northern-eleventh-cavalry-b', '장합 재추격 기병', '회수선 재추격 기병', 'cavalry', 243, 1860, 444, 68, 77],
['northern-eleventh-cavalry-c', '장합 재추격 기병', '중앙 재추격 기병', 'cavalry', 244, 1882, 448, 84, 69],
['northern-eleventh-cavalry-d', '곽회 차단 기병', '동쪽 차단 기병', 'cavalry', 245, 1904, 452, 101, 61],
['northern-eleventh-cavalry-e', '사마의 예비 기병', '본영 예비 기병', 'cavalry', 245, 1904, 452, 117, 47],
['northern-eleventh-cavalry-f', '북쪽 절벽 기병', '절벽 차단 기병', 'cavalry', 246, 1926, 456, 128, 32],
['northern-eleventh-camp-guard-a', '북안 서쪽 수비대', '서쪽 전진 진영 수비', 'yellowTurban', 243, 1800, 426, 46, 66],
['northern-eleventh-camp-guard-b', '회수 표식 수비대', '표식 수비', 'yellowTurban', 243, 1800, 426, 64, 62],
['northern-eleventh-camp-guard-c', '물목 수비대', '중앙 물목 수비', 'yellowTurban', 244, 1820, 430, 82, 55],
['northern-eleventh-camp-guard-d', '봉화 길 수비대', '봉화 길 수비', 'yellowTurban', 244, 1820, 430, 100, 46],
['northern-eleventh-camp-guard-e', '사마의 북안 수비대', '본영 수비', 'yellowTurban', 245, 1840, 434, 116, 38],
['northern-eleventh-camp-guard-f', '북쪽 절벽 수비대', '절벽 수비', 'yellowTurban', 245, 1840, 434, 128, 25],
['northern-eleventh-strategist-a', '위군 북안 군리', '강북 장부 군리', 'strategist', 243, 1380, 366, 84, 62],
['northern-eleventh-strategist-b', '위군 보급 군리', '수레 차단 군리', 'strategist', 244, 1392, 368, 100, 50],
['northern-eleventh-strategist-c', '사마의 지연 군리', '지연선 장부 군리', 'strategist', 244, 1392, 368, 116, 40],
['northern-eleventh-strategist-d', '곽회 봉화 군리', '봉화 장부 군리', 'strategist', 245, 1404, 370, 124, 70],
['northern-eleventh-officer-northbank', '북안 전진장', '북안 전진 진영 장수', 'rebelLeader', 247, 1920, 456, 90, 84],
['northern-eleventh-officer-supply', '강북 수레 차단장', '수레 차단 장수', 'rebelLeader', 247, 1920, 456, 111, 76],
['northern-eleventh-officer-guo-huai', '곽회', '북안 봉화 장수', 'rebelLeader', 246, 1900, 452, 121, 74],
['northern-eleventh-officer-relay', '봉화 연락장', '북안 뒤 봉화 장수', 'rebelLeader', 246, 1900, 452, 125, 56],
['northern-eleventh-officer-zhang-he', '장합', '재추격 대장', 'rebelLeader', 248, 1960, 462, 126, 35],
['northern-eleventh-leader-sima-yi', '사마의', '북안 지연 총지휘', 'rebelLeader', 249, 2000, 468, 122, 47]
];
export const sixtyFifthBattleUnits: UnitData[] = [
...[
...firstBattleUnits.filter((unit) => unit.faction === 'ally' && unit.id !== 'liu-bei'),
...xuzhouRecruitUnits,
...caoBreakRecruitUnits,
...liuBiaoRecruitUnits,
...zhugeRecruitUnits,
...jingzhouRecruitUnits,
...guiyangRecruitUnits,
...wulingRecruitUnits,
...changshaRecruitUnits,
...yizhouRecruitUnits,
...fuPassRecruitUnits,
...luoCastleRecruitUnits,
...luoCastleProperRecruitUnits,
...chengduPressureRecruitUnits,
...chengduSurrenderRecruitUnits,
...hanzhongOpeningRecruitUnits,
...hanzhongScoutRecruitUnits,
...hanzhongMainRecruitUnits,
...jiangWeiRecruitUnits
].map((unit) => placeScenarioUnit(unit, sixtyFifthBattleAllyPositions[unit.id] ?? { x: unit.x, y: unit.y })),
...sixtyFifthEnemySpecs.map((spec) => createThirtyFirstEnemyUnit(...spec))
];
export const firstBattleBonds: BattleBond[] = [
{
id: 'liu-bei__guan-yu',
@@ -19665,6 +19821,46 @@ export const sixtyFourthBattleBonds: BattleBond[] = [
...northernTenthBattleBonds
].map(cloneBattleBondForScenario);
export const northernEleventhBattleBonds: BattleBond[] = [
{
id: 'jiang-wei__ma-dai_northern-eleventh',
unitIds: ['jiang-wei', 'ma-dai'],
title: '낮은 길과 측면',
level: 60,
exp: 0,
description: '강유가 북안 낮은 길을 읽고 마대가 측면 기병을 묶으면, 장합의 재추격을 먼저 꺾을 수 있습니다.'
},
{
id: 'zhuge-liang__li-yan_northern-eleventh',
unitIds: ['zhuge-liang', 'li-yan'],
title: '군령과 수레',
level: 58,
exp: 0,
description: '제갈량의 전진 명령과 이엄의 수레 배정이 맞물리면, 강북 진영을 얻고도 후방 숨을 잃지 않습니다.'
},
{
id: 'wang-ping__huang-quan_northern-eleventh',
unitIds: ['wang-ping', 'huang-quan'],
title: '정지선과 장부',
level: 56,
exp: 0,
description: '왕평의 멈출 선과 황권의 소모 장부가 함께 움직이면, 북안 압박 뒤에도 지켜야 할 진영을 구분할 수 있습니다.'
},
{
id: 'wei-yan__zhao-yun_northern-eleventh',
unitIds: ['wei-yan', 'zhao-yun'],
title: '돌파와 회수',
level: 54,
exp: 0,
description: '위연이 북안 진영 사이를 흔들고 조운이 예비대를 붙들면, 돌파가 고립으로 바뀌기 전에 군을 되돌릴 수 있습니다.'
}
];
export const sixtyFifthBattleBonds: BattleBond[] = [
...sixtyFourthBattleBonds,
...northernEleventhBattleBonds
].map(cloneBattleBondForScenario);
function createEighthBattleTerrain(): TerrainType[][] {
return Array.from({ length: 22 }, (_, y) =>
Array.from({ length: 26 }, (_, x): TerrainType => {
@@ -23304,6 +23500,88 @@ function createSixtyFourthBattleTerrain(): TerrainType[][] {
);
}
function createSixtyFifthBattleTerrain(): TerrainType[][] {
return Array.from({ length: 112 }, (_, y) =>
Array.from({ length: 130 }, (_, x): TerrainType => {
if (
(x >= 2 && x <= 128 && y >= 27 && y <= 38) ||
(x >= 3 && x <= 128 && y >= 42 && y <= 52)
) {
return 'river';
}
if (
(x >= 7 && x <= 92 && y >= 97 && y <= 104) ||
(x >= 25 && x <= 111 && y >= 85 && y <= 94) ||
(x >= 50 && x <= 119 && y >= 73 && y <= 83) ||
(x >= 80 && x <= 126 && y >= 60 && y <= 72) ||
(x >= 101 && x <= 129 && y >= 47 && y <= 59) ||
(x >= 34 && x <= 45 && y >= 77 && y <= 107) ||
(x >= 60 && x <= 71 && y >= 63 && y <= 98) ||
(x >= 90 && x <= 101 && y >= 49 && y <= 88) ||
(x >= 104 && x <= 115 && y >= 32 && y <= 74) ||
(x >= 44 && x <= 90 && y >= 89 && y <= 95)
) {
return 'road';
}
if (
(x >= 0 && x <= 42 && y >= 68 && y <= 101) ||
(x >= 45 && x <= 88 && y >= 82 && y <= 103) ||
(x >= 94 && x <= 129 && y >= 76 && y <= 102) ||
(x >= 16 && x <= 47 && y >= 52 && y <= 72) ||
(x >= 104 && x <= 129 && y >= 60 && y <= 80) ||
(x >= 120 && x <= 129 && y >= 34 && y <= 56)
) {
return 'forest';
}
if (
(x >= 0 && x <= 56 && y <= 25) ||
(x >= 48 && x <= 100 && y <= 26) ||
(x >= 92 && x <= 129 && y <= 32) ||
(x >= 102 && x <= 129 && y >= 53 && y <= 80)
) {
return 'hill';
}
if (
(x <= 2 && y <= 103) ||
(x >= 127 && y <= 96) ||
(x >= 42 && x <= 49 && y >= 0 && y <= 73) ||
(x >= 82 && x <= 89 && y >= 0 && y <= 56) ||
(x >= 66 && x <= 75 && y >= 92 && y <= 111)
) {
return 'cliff';
}
if (
(x >= 14 && x <= 35 && y >= 93 && y <= 104) ||
(x >= 46 && x <= 70 && y >= 80 && y <= 92) ||
(x >= 86 && x <= 108 && y >= 66 && y <= 78) ||
(x >= 108 && x <= 127 && y >= 50 && y <= 64) ||
(x >= 115 && x <= 129 && y >= 34 && y <= 44)
) {
return 'village';
}
if (
(x >= 23 && x <= 54 && y >= 87 && y <= 101) ||
(x >= 58 && x <= 88 && y >= 74 && y <= 88) ||
(x >= 94 && x <= 121 && y >= 60 && y <= 74) ||
(x >= 103 && x <= 124 && y >= 32 && y <= 48)
) {
return 'camp';
}
if (
(x >= 52 && x <= 76 && y >= 54 && y <= 70) ||
(x >= 77 && x <= 106 && y >= 44 && y <= 60) ||
(x >= 99 && x <= 126 && y >= 53 && y <= 69) ||
(x >= 108 && x <= 129 && y >= 18 && y <= 43) ||
(x >= 114 && x <= 129 && y >= 72 && y <= 90) ||
(x >= 118 && x <= 129 && y >= 5 && y <= 18)
) {
return 'fort';
}
return 'plain';
})
);
}
function placeScenarioUnit(unit: UnitData, position: { x: number; y: number }): UnitData {
return {
...cloneUnitForScenario(unit),