Add Chengdu surrender chapter

This commit is contained in:
2026-06-23 12:38:53 +09:00
parent 9ac621da76
commit aca7d6568a
11 changed files with 879 additions and 38 deletions

View File

@@ -2076,6 +2076,68 @@ export const thirtySecondBattleVictoryPages: StoryPage[] = [
}
];
export const thirtyThirdBattleIntroPages: StoryPage[] = [
{
id: 'thirty-third-chengdu-encircled',
bgm: 'story-dark',
chapter: '성도 앞의 마지막 권고',
background: 'story-three-heroes',
speaker: '제갈량',
text: '면죽관이 열렸고 이엄까지 합류했습니다. 성도는 아직 문을 닫았으나, 이제 유장은 싸움과 항복 사이에서 더 이상 오래 머물 수 없습니다.'
},
{
id: 'thirty-third-li-yan-counsel',
bgm: 'battle-prep',
chapter: '이엄의 증언',
background: 'story-liu-bei',
speaker: '이엄',
text: '황권은 성도의 체면과 백성의 안위를 함께 보는 장수입니다. 그가 물러서면 유장공도 백성을 살리는 길을 택할 수 있습니다.'
},
{
id: 'thirty-third-final-plan',
bgm: 'battle-prep',
chapter: '성도 항복 권고전',
background: 'story-militia',
speaker: '법정',
text: '성도 성문을 부수는 것이 능사가 아닙니다. 황권의 방어선을 눌러 항복 조건을 받아들이게 하고, 마을과 창고를 지켜 유비군의 약속을 증명해야 합니다.'
},
{
id: 'thirty-third-sortie',
bgm: 'battle-prep',
chapter: '익주를 얻는 길',
background: 'story-sortie',
speaker: '유비',
portrait: 'liuBei',
text: '오늘의 승리는 성도의 문을 여는 것보다, 문 안의 백성이 두려움을 내려놓게 하는 데 있소. 칼을 들되 마음을 먼저 잃지 마시오.'
}
];
export const thirtyThirdBattleVictoryPages: StoryPage[] = [
{
id: 'thirty-third-victory-chengdu-yields',
bgm: 'militia-theme',
chapter: '성도 항복',
background: 'story-sortie',
text: '성도 외곽의 방어선은 무너졌지만 성문 안쪽은 불타지 않았습니다. 유장에게 보내진 마지막 권고문은 백성을 보전하고 장수의 체면을 남기는 조건을 담았습니다.'
},
{
id: 'thirty-third-huang-quan-joins',
bgm: 'battle-prep',
chapter: '황권의 귀순',
background: 'story-liu-bei',
speaker: '황권',
text: '익주를 지키려던 마음은 변하지 않았습니다. 다만 이제는 백성을 살리는 길이 유비공의 군율 아래 있다는 것을 보았습니다. 황권이 그 길에 서겠습니다.'
},
{
id: 'thirty-third-yizhou-secured',
bgm: 'story-dark',
chapter: '익주 확보',
background: 'story-three-heroes',
speaker: '제갈량',
text: '형주와 익주의 기반이 마련되었습니다. 유비군은 이제 유랑군이 아니라 나라의 뿌리를 갖춘 세력으로 바뀌며, 촉한 건국을 향한 다음 장을 준비합니다.'
}
];
export const firstBattleMap: BattleMap = {
width: 20,
height: 18,
@@ -2413,6 +2475,12 @@ export const thirtySecondBattleMap: BattleMap = {
terrain: createThirtySecondBattleTerrain()
};
export const thirtyThirdBattleMap: BattleMap = {
width: 66,
height: 48,
terrain: createThirtyThirdBattleTerrain()
};
export const firstBattleUnits: UnitData[] = [
{
id: 'liu-bei',
@@ -4606,6 +4674,30 @@ export const chengduPressureRecruitUnits: UnitData[] = [
}
];
export const chengduSurrenderRecruitUnits: UnitData[] = [
{
id: 'huang-quan',
name: '황권',
faction: 'ally',
className: '익주 참모',
classKey: 'strategist',
level: 21,
exp: 0,
hp: 38,
maxHp: 38,
attack: 13,
move: 4,
stats: { might: 67, intelligence: 86, leadership: 82, agility: 64, luck: 74 },
equipment: {
weapon: { itemId: 'white-feather-fan', level: 3, exp: 18 },
armor: { itemId: 'oath-robe', level: 3, exp: 8 },
accessory: { itemId: 'war-manual', level: 2, exp: 12 }
},
x: 12,
y: 30
}
];
const eighthBattleAllyPositions: Record<string, { x: number; y: number }> = {
'liu-bei': { x: 2, y: 17 },
'guan-yu': { x: 3, y: 17 },
@@ -13193,6 +13285,72 @@ export const thirtySecondBattleUnits: UnitData[] = [
createThirtyFirstEnemyUnit('chengdu-leader-li-yan', '이엄', '면죽관 수비장', 'rebelLeader', 61, 306, 86, 60, 18)
];
const thirtyThirdBattleAllyPositions: Record<string, { x: number; y: number }> = {
'liu-bei': { x: 4, y: 42 },
'guan-yu': { x: 5, y: 40 },
'zhang-fei': { x: 4, y: 44 },
'jian-yong': { x: 3, y: 45 },
'mi-zhu': { x: 5, y: 46 },
'sun-qian': { x: 7, y: 45 },
'zhao-yun': { x: 8, y: 41 },
'zhuge-liang': { x: 6, y: 40 },
'ma-liang': { x: 8, y: 46 },
'yi-ji': { x: 9, y: 43 },
'gong-zhi': { x: 10, y: 46 },
'huang-zhong': { x: 7, y: 42 },
'wei-yan': { x: 10, y: 40 },
'pang-tong': { x: 9, y: 41 },
'fa-zheng': { x: 11, y: 43 },
'wu-yi': { x: 12, y: 41 },
'yan-yan': { x: 11, y: 39 },
'li-yan': { x: 13, y: 40 }
};
export const thirtyThirdBattleUnits: UnitData[] = [
...[
...firstBattleUnits.filter((unit) => unit.faction === 'ally'),
...xuzhouRecruitUnits,
...caoBreakRecruitUnits,
...liuBiaoRecruitUnits,
...zhugeRecruitUnits,
...jingzhouRecruitUnits,
...guiyangRecruitUnits,
...wulingRecruitUnits,
...changshaRecruitUnits,
...yizhouRecruitUnits,
...fuPassRecruitUnits,
...luoCastleRecruitUnits,
...luoCastleProperRecruitUnits,
...chengduPressureRecruitUnits
].map((unit) => placeScenarioUnit(unit, thirtyThirdBattleAllyPositions[unit.id] ?? { x: unit.x, y: unit.y })),
createThirtyFirstEnemyUnit('chengdu-final-scout-a', '성도 척후', '남문 척후', 'bandit', 60, 182, 66, 19, 41),
createThirtyFirstEnemyUnit('chengdu-final-scout-b', '성도 척후', '서문 척후', 'bandit', 60, 182, 66, 25, 39),
createThirtyFirstEnemyUnit('chengdu-final-scout-c', '성도 척후', '수로 척후', 'bandit', 61, 190, 68, 32, 37),
createThirtyFirstEnemyUnit('chengdu-final-infantry-a', '성도 보병', '외곽 보병', 'yellowTurban', 61, 266, 82, 31, 34),
createThirtyFirstEnemyUnit('chengdu-final-infantry-b', '성도 보병', '외곽 보병', 'yellowTurban', 61, 266, 82, 36, 33),
createThirtyFirstEnemyUnit('chengdu-final-infantry-c', '성도 보병', '남문 보병', 'yellowTurban', 62, 276, 84, 43, 28),
createThirtyFirstEnemyUnit('chengdu-final-infantry-d', '성도 보병', '남문 보병', 'yellowTurban', 62, 276, 84, 47, 27),
createThirtyFirstEnemyUnit('chengdu-final-infantry-e', '유장 친위대', '성문 친위대', 'yellowTurban', 63, 288, 86, 55, 23),
createThirtyFirstEnemyUnit('chengdu-final-infantry-f', '유장 친위대', '창고 친위대', 'yellowTurban', 63, 288, 86, 60, 25),
createThirtyFirstEnemyUnit('chengdu-final-archer-a', '성도 궁병', '성벽 궁병', 'archer', 61, 182, 66, 34, 31),
createThirtyFirstEnemyUnit('chengdu-final-archer-b', '성도 궁병', '남문 궁병', 'archer', 62, 190, 68, 46, 23),
createThirtyFirstEnemyUnit('chengdu-final-archer-c', '성도 궁병', '고지 궁병', 'archer', 62, 190, 68, 52, 18),
createThirtyFirstEnemyUnit('chengdu-final-archer-d', '성도 궁병', '창고 궁병', 'archer', 63, 198, 70, 61, 20),
createThirtyFirstEnemyUnit('chengdu-final-cavalry-a', '성도 기병', '우익 기병', 'cavalry', 62, 276, 86, 28, 43),
createThirtyFirstEnemyUnit('chengdu-final-cavalry-b', '성도 기병', '좌익 기병', 'cavalry', 62, 276, 86, 43, 38),
createThirtyFirstEnemyUnit('chengdu-final-cavalry-c', '성도 기병', '내성 기병', 'cavalry', 63, 288, 88, 58, 31),
createThirtyFirstEnemyUnit('chengdu-final-guard-a', '황권 친위대', '권고 반대파', 'yellowTurban', 63, 300, 88, 50, 22),
createThirtyFirstEnemyUnit('chengdu-final-guard-b', '황권 친위대', '권고 반대파', 'yellowTurban', 63, 300, 88, 54, 21),
createThirtyFirstEnemyUnit('chengdu-final-guard-c', '황권 친위대', '성도 친위대', 'yellowTurban', 64, 312, 90, 60, 18),
createThirtyFirstEnemyUnit('chengdu-final-strategist-a', '성도 책사', '항복 반대파', 'strategist', 62, 190, 68, 48, 19),
createThirtyFirstEnemyUnit('chengdu-final-strategist-b', '성도 책사', '유장 사자', 'strategist', 63, 198, 70, 57, 16),
createThirtyFirstEnemyUnit('chengdu-final-ambusher-a', '성도 복병', '남벽 복병', 'bandit', 61, 190, 70, 37, 41),
createThirtyFirstEnemyUnit('chengdu-final-ambusher-b', '성도 복병', '동문 복병', 'bandit', 61, 190, 70, 51, 35),
createThirtyFirstEnemyUnit('chengdu-final-ambusher-c', '성도 복병', '북벽 복병', 'bandit', 62, 198, 72, 62, 12),
createThirtyFirstEnemyUnit('chengdu-final-officer-a', '성도 부장', '남문 부장', 'rebelLeader', 64, 318, 92, 58, 22),
createThirtyFirstEnemyUnit('chengdu-final-leader-huang-quan', '황권', '성도 수비 참모', 'rebelLeader', 65, 336, 94, 62, 18)
];
export const firstBattleBonds: BattleBond[] = [
{
id: 'liu-bei__guan-yu',
@@ -13571,6 +13729,33 @@ export const chengduPressureRecruitBonds: BattleBond[] = [
}
];
export const chengduSurrenderRecruitBonds: BattleBond[] = [
{
id: 'liu-bei__huang-quan',
unitIds: ['liu-bei', 'huang-quan'],
title: '항복과 보전',
level: 31,
exp: 0,
description: '유비의 보전 약속과 황권의 현실적인 판단이 맞닿아, 성도 항복 뒤 백성과 장수의 불안을 낮춥니다.'
},
{
id: 'zhuge-liang__huang-quan',
unitIds: ['zhuge-liang', 'huang-quan'],
title: '성도 수습책',
level: 29,
exp: 0,
description: '제갈량의 큰 내정 구상과 황권의 현지 행정 감각이 만나, 익주 통치의 첫 질서를 세웁니다.'
},
{
id: 'fa-zheng__huang-quan',
unitIds: ['fa-zheng', 'huang-quan'],
title: '익주 책사들의 균형',
level: 28,
exp: 0,
description: '법정의 결단력과 황권의 신중함이 서로를 보완하며, 익주 장수들을 다루는 말길을 넓힙니다.'
}
];
export const secondBattleBonds: BattleBond[] = firstBattleBonds.map(cloneBattleBondForScenario);
export const thirdBattleBonds: BattleBond[] = firstBattleBonds.map(cloneBattleBondForScenario);
export const fourthBattleBonds: BattleBond[] = firstBattleBonds.map(cloneBattleBondForScenario);
@@ -13610,6 +13795,9 @@ export const thirtyFirstBattleBonds: BattleBond[] = [...thirtiethBattleBonds].ma
export const thirtySecondBattleBonds: BattleBond[] = [...thirtyFirstBattleBonds, ...luoCastleProperRecruitBonds].map(
cloneBattleBondForScenario
);
export const thirtyThirdBattleBonds: BattleBond[] = [...thirtySecondBattleBonds, ...chengduPressureRecruitBonds].map(
cloneBattleBondForScenario
);
function createEighthBattleTerrain(): TerrainType[][] {
return Array.from({ length: 22 }, (_, y) =>
@@ -14937,6 +15125,72 @@ function createThirtySecondBattleTerrain(): TerrainType[][] {
);
}
function createThirtyThirdBattleTerrain(): TerrainType[][] {
return Array.from({ length: 48 }, (_, y) =>
Array.from({ length: 66 }, (_, x): TerrainType => {
if (x <= 4 && y >= 41) {
return 'camp';
}
if (
(x >= 50 && x <= 64 && y >= 10 && y <= 27) ||
(x >= 46 && x <= 56 && y >= 7 && y <= 18) ||
(x >= 56 && x <= 65 && y >= 18 && y <= 32) ||
(x >= 53 && x <= 61 && y >= 29 && y <= 35)
) {
return 'fort';
}
if (
(x >= 18 && x <= 22 && y >= 39 && y <= 41) ||
(x >= 36 && x <= 39 && y >= 33 && y <= 35) ||
(x >= 46 && x <= 48 && y >= 28 && y <= 30) ||
(x >= 57 && x <= 60 && y >= 31 && y <= 34)
) {
return 'village';
}
if (
(y >= 43 && x >= 4 && x <= 20) ||
(x >= 19 && x <= 32 && y >= 39 && y <= 43) ||
(x >= 31 && x <= 44 && y >= 34 && y <= 39) ||
(x >= 43 && x <= 55 && y >= 28 && y <= 34) ||
(x >= 54 && x <= 63 && y >= 22 && y <= 28) ||
(x >= 59 && x <= 65 && y >= 19 && y <= 23)
) {
return 'road';
}
if ((x === 15 || x === 16) && y >= 0 && y <= 37) {
return 'river';
}
if ((x === 36 || x === 37) && y >= 4 && y <= 32) {
return 'river';
}
if ((x === 48 || x === 49) && y >= 34 && y <= 47) {
return 'river';
}
if (
(x >= 4 && x <= 22 && y >= 8 && y <= 32) ||
(x >= 23 && x <= 35 && y >= 14 && y <= 33) ||
(x >= 38 && x <= 52 && y >= 35 && y <= 47) ||
(x >= 51 && x <= 65 && y >= 31 && y <= 46)
) {
return 'forest';
}
if (
(x >= 0 && x <= 13 && y <= 17) ||
(x >= 23 && x <= 35 && y <= 13) ||
(x >= 37 && x <= 54 && y >= 2 && y <= 14) ||
(x >= 57 && x <= 65 && y <= 12) ||
(x >= 27 && x <= 40 && y >= 40 && y <= 47)
) {
return 'hill';
}
if ((x <= 2 && y <= 40) || (x >= 64 && y >= 28) || (x >= 50 && x <= 53 && y <= 8)) {
return 'cliff';
}
return 'plain';
})
);
}
function placeScenarioUnit(unit: UnitData, position: { x: number; y: number }): UnitData {
return {
...cloneUnitForScenario(unit),