Add Nanzhong stabilization battle

This commit is contained in:
2026-06-23 20:32:53 +09:00
parent 199a1cee68
commit eb471d9648
11 changed files with 850 additions and 60 deletions

View File

@@ -41,6 +41,7 @@ import {
luoCastleRecruitBonds,
luoCastleRecruitUnits,
maichengIsolationBonds,
nanzhongRecoveryBonds,
wulingRecruitBonds,
wulingRecruitUnits,
xuzhouRecruitBonds,
@@ -405,9 +406,10 @@ const campaignTimelineChapters: CampaignTimelineChapter[] = [
'forty-third-battle-gongan-collapse',
'forty-fourth-battle-maicheng-isolation',
'forty-fifth-battle-yiling-vanguard',
'forty-sixth-battle-yiling-fire'
'forty-sixth-battle-yiling-fire',
'forty-seventh-battle-nanzhong-stabilization'
],
nextHints: ['왕업 선언', '번성 포위', '형주 위기', '이릉 화공']
nextHints: ['왕업 선언', '번성 포위', '형주 위기', '이릉 화공', '남중 안정']
}
];
@@ -457,7 +459,8 @@ const campBattleIds = {
fortyThird: 'forty-third-battle-gongan-collapse',
fortyFourth: 'forty-fourth-battle-maicheng-isolation',
fortyFifth: 'forty-fifth-battle-yiling-vanguard',
fortySixth: 'forty-sixth-battle-yiling-fire'
fortySixth: 'forty-sixth-battle-yiling-fire',
fortySeventh: 'forty-seventh-battle-nanzhong-stabilization'
} as const;
const defaultRequiredSortieUnitIds = ['liu-bei'];
@@ -1004,6 +1007,20 @@ const sortieRulesByBattleId: Partial<Record<BattleScenarioId, SortieRuleDefiniti
{ unitId: 'ma-chao', reason: '오군 추격 기병과 불길 바깥 차단선을 강하게 밀어낼 수 있습니다.' }
],
note: '이릉 화공전은 스물두 명 중 일곱 명만 출전합니다. 유비는 필수이며, 불길 수습, 퇴로 확보, 추격 차단 중 무엇을 두껍게 할지 선택하는 전투입니다.'
},
'forty-seventh-battle-nanzhong-stabilization': {
maxUnits: 7,
requiredUnitIds: ['zhuge-liang'],
recommended: [
{ unitId: 'zhuge-liang', reason: '백제성 이후 촉한을 이끄는 섭정이며 패배 조건의 중심입니다.' },
{ unitId: 'zhao-yun', reason: '산길 선봉과 후위 회수에 안정적인 기동 축입니다.' },
{ unitId: 'wang-ping', reason: '남중 산길과 강변 지형을 읽는 길잡이입니다.' },
{ unitId: 'ma-liang', reason: '마을 회유와 남중 민심 판단에 필요합니다.' },
{ unitId: 'huang-quan', reason: '창고와 보급 장부를 안정시키는 내정 참모입니다.' },
{ unitId: 'huang-zhong', reason: '밀림 궁병과 고지 방어선을 견제하는 원거리 축입니다.' },
{ unitId: 'ma-chao', reason: '남중 기병과 빠른 차단선에 대응하는 강한 측면 카드입니다.' }
],
note: '남중 안정로는 백제성 이후 첫 전투입니다. 유비가 아니라 제갈량이 필수이며, 산길, 마을, 창고를 동시에 보아야 합니다.'
}
};
@@ -4898,6 +4915,87 @@ const campDialogues: CampDialogue[] = [
rewardExp: 17
}
]
},
{
id: 'nanzhong-zhuge-zhaoyun-vanguard',
title: '섭정의 첫 선봉',
availableAfterBattleIds: [campBattleIds.fortySeventh],
unitIds: ['zhuge-liang', 'zhao-yun'],
bondId: 'zhuge-liang__zhao-yun_nanzhong',
rewardExp: 56,
lines: [
'제갈량: 남중의 길은 이기기만 해서는 다시 닫힙니다. 먼저 병사들이 두려워하지 않을 길을 열어야 합니다.',
'조운: 선봉은 제가 맡겠습니다. 칼끝보다 깃발이 먼저 보이도록, 지나친 추격은 삼가겠습니다.',
'제갈량: 그래서 자룡이 필요합니다. 흔들린 나라의 첫 발은 빠르되 가볍지 않아야 합니다.'
],
choices: [
{
id: 'steady-vanguard-route',
label: '안정된 선봉로를 맡긴다',
response: '조운은 산길 표식을 나누어 세우고, 제갈량은 선봉과 본대의 간격을 다시 계산했습니다.',
rewardExp: 20
},
{
id: 'protect-rear-wounded',
label: '후위 회수를 부탁한다',
response: '조운은 부상병과 보급대가 뒤처지지 않도록 후위 기동선을 먼저 확인했습니다.',
rewardExp: 19
}
]
},
{
id: 'nanzhong-zhuge-huangquan-ledger',
title: '나라 안의 장부',
availableAfterBattleIds: [campBattleIds.fortySeventh],
unitIds: ['zhuge-liang', 'huang-quan'],
bondId: 'zhuge-liang__huang-quan_nanzhong',
rewardExp: 54,
lines: [
'황권: 남중의 창고를 빼앗기만 하면 반란은 잠시 잦아들 뿐입니다. 장부가 다시 돌아와야 길이 남습니다.',
'제갈량: 토벌과 회유의 순서를 틀리면 백성은 다시 산으로 숨을 것입니다.',
'황권: 군량을 셀 때도 사람의 마음을 함께 세겠습니다.'
],
choices: [
{
id: 'audit-southern-storehouse',
label: '창고 장부를 먼저 본다',
response: '황권은 남중 창고의 빼앗긴 양과 돌려줄 양을 나누어, 전후 처리의 기준을 세웠습니다.',
rewardExp: 18
},
{
id: 'prepare-amnesty-list',
label: '회유 명단을 준비한다',
response: '제갈량과 황권은 끝까지 싸울 수장과 먼저 달랠 촌장을 분리해 적었습니다.',
rewardExp: 19
}
]
},
{
id: 'nanzhong-maliang-wangping-villages',
title: '마을과 산길',
availableAfterBattleIds: [campBattleIds.fortySeventh],
unitIds: ['ma-liang', 'wang-ping'],
bondId: 'ma-liang__wang-ping_nanzhong',
rewardExp: 52,
lines: [
'왕평: 이 길은 지도대로만 가면 늪으로 빠집니다. 마을 사람들이 쓰는 낮은 둑길이 따로 있습니다.',
'마량: 그렇다면 마을을 적으로 돌리지 않는 것이 곧 지형을 얻는 일이군요.',
'왕평: 예. 산길은 칼로 여는 길이 있고, 말로 열리는 길이 있습니다.'
],
choices: [
{
id: 'ask-village-guides',
label: '마을 길잡이를 구한다',
response: '마량은 촌장들에게 군령보다 먼저 보호 약속을 전했고, 왕평은 낮은 둑길을 지도에 표시했습니다.',
rewardExp: 18
},
{
id: 'mark-marsh-detours',
label: '늪지 우회로를 표시한다',
response: '왕평은 진흙길의 깊이를 짚었고, 마량은 병사들이 마을 밭을 밟지 않는 우회 명령을 정리했습니다.',
rewardExp: 17
}
]
}
];
@@ -6849,6 +6947,66 @@ const campVisits: CampVisitDefinition[] = [
itemRewards: ['탁주 5']
}
]
},
{
id: 'nanzhong-village-reassurance',
title: '남중 마을 회유',
location: '남중 산마을',
availableAfterBattleIds: [campBattleIds.fortySeventh],
bondId: 'ma-liang__wang-ping_nanzhong',
description: '마량과 왕평이 산마을을 돌며 반란군과 마을 사람을 구분하고, 다음 진군로의 길잡이를 부탁합니다.',
lines: [
'마량: 군이 지나간 뒤에도 밭이 남아야 마을이 다시 우리 편이 됩니다.',
'왕평: 길잡이를 억지로 데려가면 다음 길은 모두 거짓이 됩니다. 먼저 지켜 준다는 표시가 필요합니다.',
'마을 앞 작은 사당에 촉한군의 보호 표식과 길 안내 깃발이 함께 세워졌습니다.'
],
choices: [
{
id: 'promise-village-protection',
label: '보호 표식을 세운다',
response: '마을 사람들은 군량 징발 대신 보호 표식이 먼저 세워지는 것을 보고 조금씩 길을 알려 주었습니다.',
bondExp: 42,
itemRewards: ['남중 길잡이 표식 1']
},
{
id: 'return-stored-grain',
label: '빼앗긴 곡식을 돌려준다',
response: '왕평은 창고에서 찾은 곡식을 되돌렸고, 마량은 반란군과 마을 사람의 이름을 구분해 적었습니다.',
bondExp: 40,
gold: 1180,
itemRewards: ['콩 9']
}
]
},
{
id: 'nanzhong-supply-road-ledger',
title: '남중 보급로 장부',
location: '남중 산길 창고',
availableAfterBattleIds: [campBattleIds.fortySeventh],
bondId: 'zhuge-liang__huang-quan_nanzhong',
description: '제갈량과 황권이 산길 창고에서 군량, 포로, 회유 대상의 장부를 나누어 남중 안정의 다음 순서를 정합니다.',
lines: [
'황권: 이 창고에는 반란군의 군량과 마을의 생계가 함께 섞여 있습니다.',
'제갈량: 가져갈 것과 돌려줄 것을 나누는 일이 다음 싸움의 절반입니다.',
'창고 안의 죽간에는 토벌할 수장, 달랠 촌장, 보호할 마을의 이름이 따로 적혔습니다.'
],
choices: [
{
id: 'separate-grain-ledgers',
label: '군량 장부를 나눈다',
response: '황권은 촉한군이 쓸 군량과 마을에 돌려줄 곡식을 나누어, 병사들의 약탈을 막을 기준을 세웠습니다.',
bondExp: 42,
gold: 1360,
itemRewards: ['남중 산길 지도 1']
},
{
id: 'mark-amnesty-targets',
label: '회유 대상을 표시한다',
response: '제갈량은 끝까지 반란을 부추긴 수장과 달랠 수 있는 촌장을 분리해, 다음 전투의 목표를 더 선명하게 만들었습니다.',
bondExp: 40,
itemRewards: ['마을 회유 문서 2']
}
]
}
];
@@ -6943,7 +7101,8 @@ export class CampScene extends Phaser.Scene {
battleId === campBattleIds.fortyThird ||
battleId === campBattleIds.fortyFourth ||
battleId === campBattleIds.fortyFifth ||
battleId === campBattleIds.fortySixth
battleId === campBattleIds.fortySixth ||
battleId === campBattleIds.fortySeventh
) {
this.campaign = ensureCampaignRosterUnits(
[
@@ -6992,7 +7151,8 @@ export class CampScene extends Phaser.Scene {
...jingCollapseBonds,
...maichengIsolationBonds,
...yilingVanguardBonds,
...yilingFireBonds
...yilingFireBonds,
...nanzhongRecoveryBonds
]
);
this.report = this.campaign.firstBattleReport ?? this.report;
@@ -7630,6 +7790,9 @@ export class CampScene extends Phaser.Scene {
if (this.campaign?.step === 'baidi-entrustment-camp') {
return '백제성 유탁 후 군영';
}
if (battleId === campBattleIds.fortySeventh) {
return '남중 안정로 후 군영';
}
if (battleId === campBattleIds.fortySixth) {
return '이릉 화공전 후 군영';
}
@@ -9735,7 +9898,13 @@ export class CampScene extends Phaser.Scene {
}
private sortieAllies() {
return this.currentUnits().filter((unit) => unit.faction === 'ally' && unit.hp > 0);
const scenario = this.nextSortieScenario();
const scenarioAllyIds = scenario
? new Set(scenario.units.filter((unit) => unit.faction === 'ally').map((unit) => unit.id))
: undefined;
return this.currentUnits().filter(
(unit) => unit.faction === 'ally' && unit.hp > 0 && (!scenarioAllyIds || scenarioAllyIds.has(unit.id))
);
}
private selectedSortieUnits() {