Add Wudu Yinping campaign chapter

This commit is contained in:
2026-06-24 11:28:46 +09:00
parent 779b639bdf
commit f3d23a13be
10 changed files with 833 additions and 76 deletions

View File

@@ -3801,6 +3801,59 @@ export const fiftyNinthBattleVictoryPages: StoryPage[] = [
}
];
export const sixtiethBattleIntroPages: StoryPage[] = [
{
id: 'sixtieth-wudu-yinping-order',
bgm: 'story-dark',
chapter: '무도와 음평',
background: 'story-militia',
speaker: '제갈량',
text: '진창의 성벽은 오래 물고 늘어질 곳이 아니었습니다. 이번에는 성을 치기보다 무도와 음평의 산길을 나누어 잡아, 북방의 두 고을을 촉한의 숨통으로 만들겠습니다.'
},
{
id: 'sixtieth-guo-huai-relief',
bgm: 'battle-prep',
chapter: '곽회의 구원로',
background: 'story-sortie',
speaker: '강유',
text: '곽회는 성을 지키는 장수가 아니라 길을 끊는 장수입니다. 제가 아는 천수 길로 앞을 보겠습니다. 다만 위연 장군의 돌파, 조운과 마대의 기병, 왕평의 산길 판단을 모두 한꺼번에 쓸 수는 없습니다.'
},
{
id: 'sixtieth-two-commandery-supply',
bgm: 'story-dark',
chapter: '둘로 갈라진 보급',
background: 'story-three-heroes',
speaker: '황권',
text: '무도와 음평을 동시에 안정시키려면 장부가 둘로 갈라집니다. 마량이 마을을 달래고, 왕평이 산길 표식을 세우며, 누가 빠르게 치고 누가 멈출지 출전 전에 정해야 합니다.'
}
];
export const sixtiethBattleVictoryPages: StoryPage[] = [
{
id: 'sixtieth-victory-commanderies-secured',
bgm: 'militia-theme',
chapter: '두 고을 확보',
background: 'story-sortie',
text: '촉한군은 무도와 음평의 길목을 차례로 눌러 곽회의 구원로를 늦췄습니다. 큰 성 하나를 무너뜨린 승리가 아니라, 북벌이 다시 숨 쉴 수 있는 산길을 확보한 승리였습니다.'
},
{
id: 'sixtieth-victory-jiangwei-weiyan-choice',
bgm: 'story-dark',
chapter: '돌파 뒤의 지도',
background: 'story-militia',
speaker: '제갈량',
text: '강유는 길을 읽었고 위연은 길이 열릴 때 망설이지 않았습니다. 하지만 둘이 함께 앞서갈수록 조운과 마대의 회수선, 왕평과 황권의 제동 장치가 더 중요해졌습니다.'
},
{
id: 'sixtieth-next-northern-plan',
bgm: 'story-dark',
chapter: '다음 북방 계산',
background: 'story-three-heroes',
speaker: '마량',
text: '무도와 음평의 백성에게는 오늘의 승리가 끝이 아니라 약속의 시작입니다. 다음 북벌에서는 얻은 땅을 지키는 장부와 다시 나아갈 장수의 마음을 함께 보아야 합니다.'
}
];
export const firstBattleMap: BattleMap = {
width: 20,
height: 18,
@@ -4300,6 +4353,12 @@ export const fiftyNinthBattleMap: BattleMap = {
terrain: createFiftyNinthBattleTerrain()
};
export const sixtiethBattleMap: BattleMap = {
width: 120,
height: 102,
terrain: createSixtiethBattleTerrain()
};
export const firstBattleUnits: UnitData[] = [
{
id: 'liu-bei',
@@ -17426,6 +17485,97 @@ export const fiftyNinthBattleUnits: UnitData[] = [
createThirtyFirstEnemyUnit('northern-fifth-leader-hao-zhao', '학소', '진창 수비장', 'rebelLeader', 197, 1540, 362, 109, 35)
];
const sixtiethBattleAllyPositions: Record<string, { x: number; y: number }> = {
'guan-yu': { x: 18, y: 90 },
'zhang-fei': { x: 24, y: 92 },
'jian-yong': { x: 10, y: 97 },
'mi-zhu': { x: 14, y: 98 },
'sun-qian': { x: 19, y: 98 },
'zhao-yun': { x: 31, y: 88 },
'zhuge-liang': { x: 17, y: 87 },
'ma-liang': { x: 22, y: 84 },
'yi-ji': { x: 9, y: 100 },
'gong-zhi': { x: 27, y: 98 },
'huang-zhong': { x: 37, y: 86 },
'wei-yan': { x: 43, y: 88 },
'pang-tong': { x: 15, y: 83 },
'fa-zheng': { x: 27, y: 82 },
'wu-yi': { x: 45, y: 96 },
'yan-yan': { x: 49, y: 89 },
'li-yan': { x: 35, y: 81 },
'huang-quan': { x: 12, y: 86 },
'ma-chao': { x: 52, y: 85 },
'ma-dai': { x: 56, y: 92 },
'wang-ping': { x: 40, y: 81 },
'jiang-wei': { x: 33, y: 84 }
};
export const sixtiethBattleUnits: 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, sixtiethBattleAllyPositions[unit.id] ?? { x: unit.x, y: unit.y })),
createThirtyFirstEnemyUnit('northern-sixth-scout-a', '무도 산길 척후병', '서쪽 고개 정찰', 'bandit', 198, 1000, 278, 50, 80),
createThirtyFirstEnemyUnit('northern-sixth-scout-b', '음평 하곡 척후병', '하곡 정찰', 'bandit', 198, 1000, 278, 61, 76),
createThirtyFirstEnemyUnit('northern-sixth-scout-c', '무도 산길 척후병', '중앙 갈림길 정찰', 'bandit', 199, 1012, 280, 74, 69),
createThirtyFirstEnemyUnit('northern-sixth-scout-d', '음평 하곡 척후병', '동쪽 하천 정찰', 'bandit', 199, 1012, 280, 87, 61),
createThirtyFirstEnemyUnit('northern-sixth-scout-e', '곽회 전초 척후병', '곽회 본영 정찰', 'bandit', 200, 1024, 282, 103, 47),
createThirtyFirstEnemyUnit('northern-sixth-scout-f', '북쪽 봉화 척후병', '북쪽 봉화 정찰', 'bandit', 200, 1024, 282, 116, 30),
createThirtyFirstEnemyUnit('northern-sixth-infantry-a', '위군 무도 보병', '서쪽 고개 보병', 'yellowTurban', 199, 1400, 336, 55, 75),
createThirtyFirstEnemyUnit('northern-sixth-infantry-b', '위군 음평 보병', '하곡 보병', 'yellowTurban', 199, 1400, 336, 65, 70),
createThirtyFirstEnemyUnit('northern-sixth-infantry-c', '위군 무도 보병', '중앙 보병', 'yellowTurban', 200, 1420, 340, 77, 64),
createThirtyFirstEnemyUnit('northern-sixth-infantry-d', '위군 음평 보병', '동쪽 물목 보병', 'yellowTurban', 200, 1420, 340, 90, 57),
createThirtyFirstEnemyUnit('northern-sixth-infantry-e', '위군 무도 보병', '방어선 보병', 'yellowTurban', 201, 1440, 344, 102, 50),
createThirtyFirstEnemyUnit('northern-sixth-infantry-f', '위군 음평 보병', '곽회 본영 보병', 'yellowTurban', 201, 1440, 344, 112, 38),
createThirtyFirstEnemyUnit('northern-sixth-infantry-g', '위군 보급 차단병', '군량로 차단 보병', 'yellowTurban', 202, 1460, 348, 95, 78),
createThirtyFirstEnemyUnit('northern-sixth-infantry-h', '북쪽 능선 보병', '북쪽 능선 보병', 'yellowTurban', 202, 1460, 348, 117, 20),
createThirtyFirstEnemyUnit('northern-sixth-archer-a', '무도 능선 궁병', '서쪽 능선 궁병', 'archer', 199, 1050, 290, 60, 65),
createThirtyFirstEnemyUnit('northern-sixth-archer-b', '음평 물목 궁병', '하곡 궁병', 'archer', 199, 1050, 290, 72, 60),
createThirtyFirstEnemyUnit('northern-sixth-archer-c', '무도 능선 궁병', '중앙 능선 궁병', 'archer', 200, 1062, 292, 86, 53),
createThirtyFirstEnemyUnit('northern-sixth-archer-d', '음평 물목 궁병', '동쪽 물목 궁병', 'archer', 201, 1074, 294, 98, 45),
createThirtyFirstEnemyUnit('northern-sixth-archer-e', '곽회 본영 궁병', '곽회 본영 궁병', 'archer', 201, 1074, 294, 110, 33),
createThirtyFirstEnemyUnit('northern-sixth-archer-f', '북쪽 봉화 궁병', '봉화대 궁병', 'archer', 202, 1086, 296, 118, 17),
createThirtyFirstEnemyUnit('northern-sixth-cavalry-a', '곽회 구원 기병', '서쪽 구원 기병', 'cavalry', 200, 1458, 354, 51, 68),
createThirtyFirstEnemyUnit('northern-sixth-cavalry-b', '곽회 구원 기병', '하곡 구원 기병', 'cavalry', 200, 1458, 354, 64, 65),
createThirtyFirstEnemyUnit('northern-sixth-cavalry-c', '곽회 구원 기병', '중앙 우회 기병', 'cavalry', 201, 1480, 358, 78, 58),
createThirtyFirstEnemyUnit('northern-sixth-cavalry-d', '곽회 구원 기병', '동쪽 우회 기병', 'cavalry', 202, 1502, 362, 92, 51),
createThirtyFirstEnemyUnit('northern-sixth-cavalry-e', '곽회 구원 기병', '본영 지원 기병', 'cavalry', 202, 1502, 362, 105, 43),
createThirtyFirstEnemyUnit('northern-sixth-cavalry-f', '곽회 구원 기병', '북쪽 차단 기병', 'cavalry', 203, 1524, 366, 116, 27),
createThirtyFirstEnemyUnit('northern-sixth-ridge-guard-a', '무도 고개 수비대', '서쪽 고개 수비', 'yellowTurban', 200, 1420, 342, 46, 55),
createThirtyFirstEnemyUnit('northern-sixth-ridge-guard-b', '음평 물길 수비대', '하곡 물길 수비', 'yellowTurban', 200, 1420, 342, 60, 49),
createThirtyFirstEnemyUnit('northern-sixth-ridge-guard-c', '무도 고개 수비대', '중앙 고개 수비', 'yellowTurban', 201, 1440, 346, 76, 41),
createThirtyFirstEnemyUnit('northern-sixth-ridge-guard-d', '음평 물길 수비대', '동쪽 물목 수비', 'yellowTurban', 201, 1440, 346, 92, 34),
createThirtyFirstEnemyUnit('northern-sixth-ridge-guard-e', '곽회 본영 수비대', '곽회 본영 수비', 'yellowTurban', 202, 1460, 350, 106, 26),
createThirtyFirstEnemyUnit('northern-sixth-ridge-guard-f', '북쪽 절벽 수비대', '북쪽 절벽 수비', 'yellowTurban', 202, 1460, 350, 118, 14),
createThirtyFirstEnemyUnit('northern-sixth-strategist-a', '위군 무도 군리', '무도 장부 군리', 'strategist', 200, 1120, 304, 74, 56),
createThirtyFirstEnemyUnit('northern-sixth-strategist-b', '위군 음평 군리', '음평 장부 군리', 'strategist', 201, 1132, 306, 88, 47),
createThirtyFirstEnemyUnit('northern-sixth-strategist-c', '곽회 본영 군리', '곽회 본영 군리', 'strategist', 201, 1132, 306, 102, 38),
createThirtyFirstEnemyUnit('northern-sixth-strategist-d', '위군 보급 군리', '군량 차단 군리', 'strategist', 202, 1144, 308, 108, 66),
createThirtyFirstEnemyUnit('northern-sixth-officer-wudu', '무도 수비장', '무도 고개 장수', 'rebelLeader', 203, 1500, 360, 86, 72),
createThirtyFirstEnemyUnit('northern-sixth-officer-yinping', '음평 수비장', '음평 물목 장수', 'rebelLeader', 203, 1500, 360, 101, 60),
createThirtyFirstEnemyUnit('northern-sixth-officer-fei-yao', '비요', '곽회 지원 장수', 'rebelLeader', 202, 1480, 356, 109, 70),
createThirtyFirstEnemyUnit('northern-sixth-officer-supply', '위군 군량 차단장', '두 고을 보급 차단 장수', 'rebelLeader', 201, 1460, 352, 80, 80),
createThirtyFirstEnemyUnit('northern-sixth-officer-relay', '위군 봉화 장수', '북쪽 봉화 장수', 'rebelLeader', 202, 1480, 356, 115, 25),
createThirtyFirstEnemyUnit('northern-sixth-leader-guo-huai', '곽회', '위군 북방 구원 총대장', 'rebelLeader', 204, 1580, 372, 112, 36)
];
export const firstBattleBonds: BattleBond[] = [
{
id: 'liu-bei__guan-yu',
@@ -18703,6 +18853,46 @@ export const fiftyNinthBattleBonds: BattleBond[] = [
...northernFifthBattleBonds
].map(cloneBattleBondForScenario);
export const northernSixthBattleBonds: BattleBond[] = [
{
id: 'jiang-wei__wei-yan_northern-sixth',
unitIds: ['jiang-wei', 'wei-yan'],
title: '길을 여는 돌파',
level: 48,
exp: 0,
description: '강유의 산길 판단과 위연의 돌파력이 맞물리면 무도와 음평의 좁은 갈림길에서도 빠르게 압박할 수 있습니다.'
},
{
id: 'zhao-yun__ma-dai_northern-sixth',
unitIds: ['zhao-yun', 'ma-dai'],
title: '두 기병의 회수선',
level: 56,
exp: 0,
description: '조운의 안정된 회수 기동과 마대의 서량 우회 감각이 함께하면 곽회의 구원 기병을 늦추면서도 본대와 끊어지지 않습니다.'
},
{
id: 'wang-ping__huang-quan_northern-sixth',
unitIds: ['wang-ping', 'huang-quan'],
title: '산길과 장부',
level: 50,
exp: 0,
description: '왕평의 산길 표식과 황권의 보급 장부가 맞물리면 두 고을을 얻은 뒤에도 군량선이 무너지지 않습니다.'
},
{
id: 'ma-liang__jiang-wei_northern-sixth',
unitIds: ['ma-liang', 'jiang-wei'],
title: '새 고을의 약속',
level: 42,
exp: 0,
description: '마량의 민심 문서와 강유의 천수 사람 이해가 함께하면 무도와 음평의 백성이 새 전선을 두려움만으로 보지 않게 됩니다.'
}
];
export const sixtiethBattleBonds: BattleBond[] = [
...fiftyNinthBattleBonds,
...northernSixthBattleBonds
].map(cloneBattleBondForScenario);
function createEighthBattleTerrain(): TerrainType[][] {
return Array.from({ length: 22 }, (_, y) =>
Array.from({ length: 26 }, (_, x): TerrainType => {
@@ -21946,6 +22136,81 @@ function createFiftyNinthBattleTerrain(): TerrainType[][] {
);
}
function createSixtiethBattleTerrain(): TerrainType[][] {
return Array.from({ length: 102 }, (_, y) =>
Array.from({ length: 120 }, (_, x): TerrainType => {
if (
(x >= 5 && x <= 11 && y >= 0 && y <= 101) ||
(x >= 62 && x <= 68 && y >= 16 && y <= 101) ||
(x >= 0 && x <= 119 && y >= 94)
) {
return 'river';
}
if (
(x >= 9 && x <= 80 && y >= 86 && y <= 92) ||
(x >= 23 && x <= 98 && y >= 75 && y <= 83) ||
(x >= 48 && x <= 108 && y >= 63 && y <= 73) ||
(x >= 76 && x <= 116 && y >= 49 && y <= 61) ||
(x >= 92 && x <= 119 && y >= 35 && y <= 46) ||
(x >= 30 && x <= 38 && y >= 68 && y <= 96) ||
(x >= 54 && x <= 62 && y >= 55 && y <= 88) ||
(x >= 84 && x <= 93 && y >= 41 && y <= 78)
) {
return 'road';
}
if (
(x >= 0 && x <= 35 && y >= 58 && y <= 90) ||
(x >= 42 && x <= 78 && y >= 72 && y <= 93) ||
(x >= 88 && x <= 119 && y >= 66 && y <= 91) ||
(x >= 14 && x <= 38 && y >= 42 && y <= 60)
) {
return 'forest';
}
if (
(x >= 0 && x <= 49 && y <= 38) ||
(x >= 46 && x <= 90 && y <= 32) ||
(x >= 86 && x <= 119 && y <= 50) ||
(x >= 98 && x <= 119 && y >= 44 && y <= 70)
) {
return 'hill';
}
if (
(x <= 2 && y <= 92) ||
(x >= 117 && y <= 86) ||
(x >= 38 && x <= 44 && y >= 0 && y <= 62) ||
(x >= 75 && x <= 81 && y >= 0 && y <= 44) ||
(x >= 59 && x <= 66 && y >= 84 && y <= 101)
) {
return 'cliff';
}
if (
(x >= 12 && x <= 28 && y >= 82 && y <= 92) ||
(x >= 43 && x <= 62 && y >= 70 && y <= 82) ||
(x >= 78 && x <= 96 && y >= 56 && y <= 68) ||
(x >= 100 && x <= 116 && y >= 41 && y <= 54)
) {
return 'village';
}
if (
(x >= 20 && x <= 45 && y >= 77 && y <= 90) ||
(x >= 54 && x <= 79 && y >= 64 && y <= 76) ||
(x >= 87 && x <= 110 && y >= 50 && y <= 63)
) {
return 'camp';
}
if (
(x >= 48 && x <= 67 && y >= 45 && y <= 60) ||
(x >= 72 && x <= 96 && y >= 35 && y <= 50) ||
(x >= 94 && x <= 116 && y >= 44 && y <= 60) ||
(x >= 102 && x <= 119 && y >= 14 && y <= 33)
) {
return 'fort';
}
return 'plain';
})
);
}
function placeScenarioUnit(unit: UnitData, position: { x: number; y: number }): UnitData {
return {
...cloneUnitForScenario(unit),