Add Meng Huo pacification battle

This commit is contained in:
2026-06-23 20:59:52 +09:00
parent eb471d9648
commit 688b51f3cc
11 changed files with 824 additions and 63 deletions

View File

@@ -3138,6 +3138,59 @@ export const fortySeventhBattleVictoryPages: StoryPage[] = [
}
];
export const fortyEighthBattleIntroPages: StoryPage[] = [
{
id: 'forty-eighth-meng-huo-gathers',
bgm: 'story-dark',
chapter: '맹획의 깃발',
background: 'story-militia',
speaker: '왕평',
text: '옹개의 지휘선은 흩어졌지만 남중의 마을들은 아직 촉한군을 멀리서 바라봅니다. 깊은 숲 안쪽에서는 맹획이 여러 호족을 모아, 첫 패배를 갚겠다고 병력을 다시 세우고 있습니다.'
},
{
id: 'forty-eighth-zhuge-amnesty',
bgm: 'battle-prep',
chapter: '힘과 회유',
background: 'story-three-heroes',
speaker: '제갈량',
text: '맹획을 쓰러뜨리는 것만으로는 남중을 얻을 수 없습니다. 반드시 생포하여 돌려보낼 길을 남기십시오. 마을과 포로 수용소를 지키면, 다음 싸움에서 칼보다 말이 먼저 닿을 것입니다.'
},
{
id: 'forty-eighth-sortie',
bgm: 'battle-prep',
chapter: '맹획 본대전',
background: 'story-sortie',
speaker: '마량',
text: '맹획의 본대는 숲길, 늪지, 산채를 함께 쓰고 있습니다. 조운과 마초가 돌격을 막고, 왕평이 지형을 읽으며, 황권이 포로와 군량의 장부를 흐트러뜨리지 않아야 합니다.'
}
];
export const fortyEighthBattleVictoryPages: StoryPage[] = [
{
id: 'forty-eighth-victory-captured',
bgm: 'militia-theme',
chapter: '첫 생포',
background: 'story-sortie',
text: '맹획의 본대는 흩어졌고, 촉한군은 산채의 불길을 키우지 않은 채 포로와 마을 사람을 갈라 세웠습니다. 병사들은 적장을 베지 않고 생포하는 싸움이 왜 어려운지 처음으로 깨달았습니다.'
},
{
id: 'forty-eighth-victory-release',
bgm: 'story-dark',
chapter: '돌려보내는 책략',
background: 'story-three-heroes',
speaker: '제갈량',
text: '맹획은 아직 진심으로 굴복하지 않았습니다. 그러나 억지 항복은 오래가지 않습니다. 오늘은 그를 돌려보내 남중의 사람들에게 촉한군이 약탈하러 온 군대가 아님을 보이겠습니다.'
},
{
id: 'forty-eighth-next-capture',
bgm: 'story-dark',
chapter: '칠종칠금의 첫 장',
background: 'story-militia',
speaker: '마량',
text: '맹획은 풀려났으나 산속의 호족들은 이제 촉한군의 태도를 보았습니다. 다음 싸움부터는 힘뿐 아니라 누구를 먼저 달래고 누구를 끝까지 추격할지 선택해야 합니다.'
}
];
export const firstBattleMap: BattleMap = {
width: 20,
height: 18,
@@ -3565,6 +3618,12 @@ export const fortySeventhBattleMap: BattleMap = {
terrain: createFortySeventhBattleTerrain()
};
export const fortyEighthBattleMap: BattleMap = {
width: 96,
height: 78,
terrain: createFortyEighthBattleTerrain()
};
export const firstBattleUnits: UnitData[] = [
{
id: 'liu-bei',
@@ -15623,6 +15682,86 @@ export const fortySeventhBattleUnits: UnitData[] = [
createThirtyFirstEnemyUnit('nanzhong-leader-yong-kai', '옹개', '남중 반란 수장', 'rebelLeader', 130, 856, 212, 88, 42)
];
const fortyEighthBattleAllyPositions: Record<string, { x: number; y: number }> = {
'guan-yu': { x: 12, y: 56 },
'zhang-fei': { x: 16, y: 57 },
'jian-yong': { x: 8, y: 61 },
'mi-zhu': { x: 10, y: 59 },
'sun-qian': { x: 13, y: 63 },
'zhao-yun': { x: 20, y: 53 },
'zhuge-liang': { x: 10, y: 54 },
'ma-liang': { x: 14, y: 51 },
'yi-ji': { x: 9, y: 66 },
'gong-zhi': { x: 17, y: 64 },
'huang-zhong': { x: 23, y: 55 },
'wei-yan': { x: 27, y: 58 },
'pang-tong': { x: 13, y: 48 },
'fa-zheng': { x: 18, y: 49 },
'wu-yi': { x: 29, y: 62 },
'yan-yan': { x: 31, y: 56 },
'li-yan': { x: 23, y: 46 },
'huang-quan': { x: 8, y: 52 },
'ma-chao': { x: 34, y: 53 },
'ma-dai': { x: 36, y: 57 },
'wang-ping': { x: 25, y: 50 }
};
export const fortyEighthBattleUnits: 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
].map((unit) => placeScenarioUnit(unit, fortyEighthBattleAllyPositions[unit.id] ?? { x: unit.x, y: unit.y })),
createThirtyFirstEnemyUnit('menghuo-scout-a', '남만 척후', '산채 척후', 'bandit', 130, 552, 170, 42, 32),
createThirtyFirstEnemyUnit('menghuo-scout-b', '남만 척후', '숲길 척후', 'bandit', 130, 552, 170, 55, 28),
createThirtyFirstEnemyUnit('menghuo-scout-c', '남만 척후', '늪지 척후', 'bandit', 131, 560, 172, 70, 48),
createThirtyFirstEnemyUnit('menghuo-scout-d', '남만 척후', '동쪽 척후', 'bandit', 131, 560, 172, 86, 59),
createThirtyFirstEnemyUnit('menghuo-vine-infantry-a', '등갑병', '산채 등갑병', 'yellowTurban', 131, 812, 206, 48, 38),
createThirtyFirstEnemyUnit('menghuo-vine-infantry-b', '등갑병', '늪길 등갑병', 'yellowTurban', 132, 824, 208, 61, 42),
createThirtyFirstEnemyUnit('menghuo-vine-infantry-c', '등갑병', '마을 등갑병', 'yellowTurban', 132, 824, 208, 72, 52),
createThirtyFirstEnemyUnit('menghuo-vine-infantry-d', '등갑병', '본대 등갑병', 'yellowTurban', 133, 836, 210, 84, 45),
createThirtyFirstEnemyUnit('menghuo-vine-infantry-e', '등갑병', '수장 등갑병', 'yellowTurban', 133, 836, 210, 89, 36),
createThirtyFirstEnemyUnit('menghuo-archer-a', '남만 궁병', '나무 궁병', 'archer', 130, 604, 180, 45, 24),
createThirtyFirstEnemyUnit('menghuo-archer-b', '남만 궁병', '고지 궁병', 'archer', 131, 612, 182, 58, 21),
createThirtyFirstEnemyUnit('menghuo-archer-c', '남만 궁병', '마을 궁병', 'archer', 131, 612, 182, 68, 39),
createThirtyFirstEnemyUnit('menghuo-archer-d', '남만 궁병', '늪지 궁병', 'archer', 132, 620, 184, 80, 57),
createThirtyFirstEnemyUnit('menghuo-archer-e', '남만 궁병', '본대 궁병', 'archer', 132, 620, 184, 91, 50),
createThirtyFirstEnemyUnit('menghuo-beast-rider-a', '맹수 기병', '코끼리 선봉', 'cavalry', 132, 828, 210, 51, 31),
createThirtyFirstEnemyUnit('menghuo-beast-rider-b', '맹수 기병', '산짐승 기병', 'cavalry', 132, 828, 210, 65, 34),
createThirtyFirstEnemyUnit('menghuo-beast-rider-c', '맹수 기병', '늪지 기병', 'cavalry', 133, 840, 212, 76, 46),
createThirtyFirstEnemyUnit('menghuo-beast-rider-d', '맹수 기병', '추격 기병', 'cavalry', 133, 840, 212, 88, 54),
createThirtyFirstEnemyUnit('menghuo-ambusher-a', '남만 복병', '덩굴 복병', 'bandit', 131, 570, 174, 36, 45),
createThirtyFirstEnemyUnit('menghuo-ambusher-b', '남만 복병', '숲속 복병', 'bandit', 132, 578, 176, 52, 49),
createThirtyFirstEnemyUnit('menghuo-ambusher-c', '남만 복병', '강가 복병', 'bandit', 132, 578, 176, 64, 61),
createThirtyFirstEnemyUnit('menghuo-ambusher-d', '남만 복병', '후방 차단대', 'bandit', 133, 586, 178, 82, 68),
createThirtyFirstEnemyUnit('menghuo-ambusher-e', '남만 복병', '서쪽 차단대', 'bandit', 133, 586, 178, 31, 59),
createThirtyFirstEnemyUnit('menghuo-guard-a', '맹획 친위대', '포로장 경비대', 'yellowTurban', 134, 862, 216, 70, 36),
createThirtyFirstEnemyUnit('menghuo-guard-b', '맹획 친위대', '산채 경비대', 'yellowTurban', 134, 862, 216, 78, 44),
createThirtyFirstEnemyUnit('menghuo-guard-c', '맹획 친위대', '본영 경비대', 'yellowTurban', 135, 874, 218, 88, 40),
createThirtyFirstEnemyUnit('menghuo-shaman-a', '남만 주술사', '독안개 주술사', 'strategist', 133, 644, 190, 60, 30),
createThirtyFirstEnemyUnit('menghuo-shaman-b', '남만 주술사', '늪지 주술사', 'strategist', 134, 652, 192, 73, 43),
createThirtyFirstEnemyUnit('menghuo-shaman-c', '남만 주술사', '산채 주술사', 'strategist', 134, 652, 192, 83, 32),
createThirtyFirstEnemyUnit('menghuo-officer-ahuinan', '아회남', '남만 호족장', 'rebelLeader', 135, 890, 222, 72, 31),
createThirtyFirstEnemyUnit('menghuo-officer-dongtuna', '동도나', '남만 산채장', 'rebelLeader', 135, 890, 222, 78, 60),
createThirtyFirstEnemyUnit('menghuo-officer-mangyachang', '망아장', '맹수대장', 'rebelLeader', 136, 902, 224, 86, 48),
createThirtyFirstEnemyUnit('menghuo-leader', '맹획', '남만왕', 'rebelLeader', 138, 940, 232, 90, 38)
];
export const firstBattleBonds: BattleBond[] = [
{
id: 'liu-bei__guan-yu',
@@ -16471,6 +16610,35 @@ export const nanzhongRecoveryBonds: BattleBond[] = [
export const fortySeventhBattleBonds: BattleBond[] = [...fortySixthBattleBonds, ...nanzhongRecoveryBonds].map(
cloneBattleBondForScenario
);
export const mengHuoPacificationBonds: BattleBond[] = [
{
id: 'zhuge-liang__ma-liang_menghuo',
unitIds: ['zhuge-liang', 'ma-liang'],
title: '회유의 문장',
level: 43,
exp: 0,
description: '제갈량의 큰 방침과 마량의 부드러운 문장은 맹획을 베기보다 돌려보낼 명분을 세운다.'
},
{
id: 'zhao-yun__ma-chao_menghuo',
unitIds: ['zhao-yun', 'ma-chao'],
title: '남중 돌격 차단',
level: 38,
exp: 0,
description: '조운의 안정된 기동과 마초의 강한 돌파는 맹수 기병의 첫 충돌을 받아 내는 힘이 된다.'
},
{
id: 'huang-quan__wang-ping_menghuo',
unitIds: ['huang-quan', 'wang-ping'],
title: '포로와 산길',
level: 37,
exp: 0,
description: '황권의 장부와 왕평의 지형 판단은 포로 수용소와 산마을을 불필요한 원한 없이 정리한다.'
}
];
export const fortyEighthBattleBonds: BattleBond[] = [...fortySeventhBattleBonds, ...mengHuoPacificationBonds].map(
cloneBattleBondForScenario
);
function createEighthBattleTerrain(): TerrainType[][] {
return Array.from({ length: 22 }, (_, y) =>
@@ -18857,6 +19025,69 @@ function createFortySeventhBattleTerrain(): TerrainType[][] {
);
}
function createFortyEighthBattleTerrain(): TerrainType[][] {
return Array.from({ length: 78 }, (_, y) =>
Array.from({ length: 96 }, (_, x): TerrainType => {
if (
(x >= 0 && x <= 95 && y >= 69) ||
(x >= 43 && x <= 55 && y >= 55 && y <= 77) ||
(x >= 3 && x <= 19 && y >= 60 && y <= 77) ||
(x >= 72 && x <= 82 && y >= 50 && y <= 68)
) {
return 'river';
}
if (
(x >= 3 && x <= 89 && y >= 53 && y <= 58) ||
(x >= 21 && x <= 92 && y >= 62 && y <= 66) ||
(x >= 27 && x <= 40 && y >= 37 && y <= 62) ||
(x >= 60 && x <= 72 && y >= 25 && y <= 63) ||
(x >= 73 && x <= 90 && y >= 35 && y <= 41)
) {
return 'road';
}
if (
(x >= 24 && x <= 68 && y >= 20 && y <= 67) ||
(x >= 50 && x <= 88 && y >= 27 && y <= 70) ||
(x >= 2 && x <= 34 && y >= 46 && y <= 68) ||
(x >= 74 && x <= 95 && y >= 38 && y <= 68)
) {
return 'forest';
}
if (
(x >= 0 && x <= 30 && y <= 35) ||
(x >= 35 && x <= 63 && y <= 25) ||
(x >= 66 && x <= 95 && y <= 31) ||
(x >= 84 && x <= 95 && y >= 29 && y <= 48)
) {
return 'hill';
}
if (
(x <= 2 && y <= 68) ||
(x >= 93 && y <= 62) ||
(x >= 31 && x <= 37 && y >= 0 && y <= 35) ||
(x >= 64 && x <= 70 && y >= 0 && y <= 28) ||
(x >= 38 && x <= 44 && y >= 58 && y <= 77)
) {
return 'cliff';
}
if (
(x >= 10 && x <= 24 && y >= 48 && y <= 60) ||
(x >= 46 && x <= 58 && y >= 41 && y <= 55) ||
(x >= 72 && x <= 87 && y >= 43 && y <= 55)
) {
return 'village';
}
if ((x >= 17 && x <= 28 && y >= 38 && y <= 47) || (x >= 77 && x <= 91 && y >= 31 && y <= 43)) {
return 'fort';
}
if ((x >= 54 && x <= 66 && y >= 54 && y <= 64) || (x >= 31 && x <= 43 && y >= 44 && y <= 53)) {
return 'camp';
}
return 'plain';
})
);
}
function placeScenarioUnit(unit: UnitData, position: { x: number; y: number }): UnitData {
return {
...cloneUnitForScenario(unit),