Add Red Cliffs vanguard chapter

This commit is contained in:
2026-06-23 06:59:33 +09:00
parent 0af7c9cfe8
commit 7f80600d0c
11 changed files with 1058 additions and 24 deletions

View File

@@ -281,7 +281,12 @@ const campaignTimelineChapters: CampaignTimelineChapter[] = [
title: '적벽대전',
period: '강동의 바람',
description: '제갈량의 계책과 강동의 힘이 맞물려 조조의 대군을 막아서는 큰 전환 장입니다.',
battleIds: ['eighteenth-battle-bowang-ambush', 'nineteenth-battle-changban-refuge', 'twentieth-battle-jiangdong-envoy'],
battleIds: [
'eighteenth-battle-bowang-ambush',
'nineteenth-battle-changban-refuge',
'twentieth-battle-jiangdong-envoy',
'twenty-first-battle-red-cliffs-vanguard'
],
nextHints: ['강동 사절', '화공 준비', '조조 남하']
},
{
@@ -322,7 +327,8 @@ const campBattleIds = {
seventeenth: 'seventeenth-battle-wolong-visit-road',
eighteenth: 'eighteenth-battle-bowang-ambush',
nineteenth: 'nineteenth-battle-changban-refuge',
twentieth: 'twentieth-battle-jiangdong-envoy'
twentieth: 'twentieth-battle-jiangdong-envoy',
twentyFirst: 'twenty-first-battle-red-cliffs-vanguard'
} as const;
const requiredSortieUnitIds = new Set(['liu-bei']);
@@ -537,6 +543,18 @@ const sortieRulesByBattleId: Partial<Record<BattleScenarioId, SortieRuleDefiniti
{ unitId: 'mi-zhu', reason: '사절 보급과 장기전 회복 운용에 유리합니다.' }
],
note: '강동 사절로는 전투력만으로 고르기 어렵습니다. 제갈량과 손건을 보호하면서 전열, 기동, 보급 중 무엇을 남길지 선택하십시오.'
},
'twenty-first-battle-red-cliffs-vanguard': {
maxUnits: 6,
recommended: [
{ unitId: 'liu-bei', reason: '손유 동맹의 명분이자 패배 조건의 중심입니다.' },
{ unitId: 'zhuge-liang', reason: '바람과 화공을 잇는 계책 담당이며 반드시 생존해야 합니다.' },
{ unitId: 'zhao-yun', reason: '강안의 기병과 척후를 빠르게 끊어 포구를 안정시킵니다.' },
{ unitId: 'guan-yu', reason: '배다리와 포구 전열에서 적 수군을 받아낼 핵심 전열입니다.' },
{ unitId: 'zhang-fei', reason: '좁은 강변 길목을 밀어붙이며 적 보병을 흔드는 돌파 역할입니다.' },
{ unitId: 'sun-qian', reason: '강동 회담 기록과 동맹 명분을 전투 뒤 보고로 이어 줍니다.' }
],
note: '적벽 전초전은 첫 강상 전투입니다. 유비와 제갈량을 중심으로 전열, 기동, 외교 기록 중 어느 축을 두껍게 할지 고르십시오.'
}
};
@@ -2214,6 +2232,87 @@ const campDialogues: CampDialogue[] = [
rewardExp: 8
}
]
},
{
id: 'liu-zhuge-after-red-cliffs-vanguard',
title: '첫 강안 전선',
availableAfterBattleIds: [campBattleIds.twentyFirst],
unitIds: ['liu-bei', 'zhuge-liang'],
bondId: 'liu-bei__zhuge-liang',
rewardExp: 32,
lines: [
'유비: 강가에 첫 전선을 세우고 보니, 조조의 대군이 얼마나 큰 물결인지 더 분명히 보이는구려.',
'제갈량: 그래서 더더욱 작은 승리가 필요했습니다. 오늘의 포구는 화공을 논할 자리이자 병사들이 믿을 첫 발판입니다.',
'유비: 불길을 일으키더라도 그 끝에는 백성이 숨 쉴 길이 있어야 하오. 그 기준만은 잊지 맙시다.'
],
choices: [
{
id: 'hold-river-line',
label: '강안 전선을 굳힌다',
response: '제갈량은 유비의 뜻을 전선 배치도 첫머리에 적었고, 적벽의 싸움이 단순한 불길이 아님을 분명히 했다.',
rewardExp: 12
},
{
id: 'protect-civilians-before-fire',
label: '화공 전 피난로를 살핀다',
response: '유비는 포구 주변 피난로를 먼저 열게 했고, 제갈량은 그 길을 화공 계획의 제약이 아니라 기준으로 삼았다.',
rewardExp: 11
}
]
},
{
id: 'zhao-zhuge-after-red-cliffs-vanguard',
title: '강안의 흰 창',
availableAfterBattleIds: [campBattleIds.twentyFirst],
unitIds: ['zhao-yun', 'zhuge-liang'],
bondId: 'zhao-yun__zhuge-liang',
rewardExp: 24,
lines: [
'조운: 강가 길목은 말이 마음껏 달릴 수 없었습니다. 그래도 적 척후가 전선을 흔들기 전에는 끊을 수 있었습니다.',
'제갈량: 자룡 장군이 먼저 강안의 빈틈을 막아 주었기에 포구 수비대가 흐트러지지 않았습니다.',
'조운: 다음 싸움에서도 군사의 깃발이 가리키는 빈틈부터 메우겠습니다.'
],
choices: [
{
id: 'mark-river-flanks',
label: '강안 측면로를 표시한다',
response: '조운은 말을 돌릴 수 있는 좁은 길을 지도에 남겼고, 제갈량은 그 길을 다음 화공 엄호선으로 삼았다.',
rewardExp: 10
},
{
id: 'guard-strategist-route',
label: '군사의 이동로를 호위한다',
response: '제갈량의 이동로가 조운의 창끝 아래 정리되자, 전선 뒤쪽의 책략 운용이 한결 안정되었다.',
rewardExp: 9
}
]
},
{
id: 'sun-zhuge-after-red-cliffs-vanguard',
title: '동맹군의 기록',
availableAfterBattleIds: [campBattleIds.twentyFirst],
unitIds: ['sun-qian', 'zhuge-liang'],
bondId: 'sun-qian__zhuge-liang',
rewardExp: 28,
lines: [
'손건: 오늘의 승리는 작지만, 강동에 보여 줄 기록으로는 충분히 무겁습니다.',
'제갈량: 맞습니다. 유비군이 전선을 세웠고, 강동은 불을 준비할 시간을 얻었습니다. 기록은 그 균형을 보여야 합니다.',
'손건: 그렇다면 전공보다 동맹의 호흡을 먼저 적겠습니다. 함께 이긴 기록이어야 다음 명령도 흔들리지 않겠습니다.'
],
choices: [
{
id: 'write-allied-record',
label: '동맹 전과를 함께 적는다',
response: '손건은 유비군과 강동군의 역할을 나누어 적었고, 제갈량은 그 기록을 주유에게 보낼 논거로 다듬었다.',
rewardExp: 11
},
{
id: 'summarize-fire-prep',
label: '화공 준비 상황을 요약한다',
response: '전초전의 포구, 배, 바람 기록이 한 장의 보고로 묶였고, 다음 적벽 계책의 설득력이 더해졌다.',
rewardExp: 10
}
]
}
];
@@ -2543,6 +2642,66 @@ const campVisits: CampVisitDefinition[] = [
itemRewards: ['상처약 2']
}
]
},
{
id: 'red-cliffs-firecraft-prep',
title: '적벽 화공 예행',
location: '강동 선착장',
availableAfterBattleIds: [campBattleIds.twentyFirst],
bondId: 'liu-bei__zhuge-liang',
description: '첫 강안 전선을 확보한 뒤, 배와 바람을 살피며 화공을 실제 전장 계획으로 다듬습니다.',
lines: [
'제갈량: 전초전으로 포구를 얻었으니 이제 불길이 어디서 시작되어 어디로 번질지 따져 볼 수 있습니다.',
'유비: 불은 무서운 계책이오. 다만 조조의 대군을 멈추지 못하면 더 많은 백성이 떠밀릴 것이오.',
'선착장에는 젖은 짚단과 빈 배가 놓였고, 병사들은 처음으로 불을 전장의 언어로 배우기 시작합니다.'
],
choices: [
{
id: 'test-fire-boat-spacing',
label: '화선 간격을 시험한다',
response: '제갈량은 배 사이 간격과 바람의 흐름을 기록했고, 적벽 화공의 첫 실행안이 더 또렷해졌다.',
bondExp: 24,
itemRewards: ['화공 논의 1']
},
{
id: 'prepare-evacuation-line',
label: '퇴로와 피난로를 먼저 정한다',
response: '유비는 불길을 쓰기 전 아군과 백성이 빠질 길부터 표시하게 했고, 계책은 더 조심스러운 형태를 갖추었다.',
bondExp: 20,
gold: 160,
itemRewards: ['상처약 2']
}
]
},
{
id: 'alliance-war-council-records',
title: '동맹 군의 회의록',
location: '강동 군막',
availableAfterBattleIds: [campBattleIds.twentyFirst],
bondId: 'sun-qian__zhuge-liang',
description: '손건과 제갈량이 주유에게 보낼 전초전 기록과 다음 화공 논의를 정리합니다.',
lines: [
'손건: 동맹은 말로 맺었지만, 계속 이어 가려면 기록과 전공이 서로 맞아야 합니다.',
'제갈량: 오늘 유비군이 세운 전선과 강동군이 준비할 화공을 한 장의 흐름으로 묶겠습니다.',
'군막 안에는 조조 수군의 배치도와 포구 전초전 기록이 펼쳐지고, 다음 싸움의 책임이 문장 위에 놓입니다.'
],
choices: [
{
id: 'write-joint-command-note',
label: '합동 지휘 요지를 적는다',
response: '손건은 유비군의 전선 유지와 강동군의 화공 준비를 함께 적어 동맹의 균형을 분명히 했다.',
bondExp: 22,
itemRewards: ['동맹 군의 기록 1']
},
{
id: 'clarify-zhou-yu-requests',
label: '주유에게 요청할 내용을 정한다',
response: '제갈량은 주유가 요구할 병력, 배, 시점을 예상했고 손건은 그 답을 짧은 회의록으로 정리했다.',
bondExp: 18,
gold: 180,
itemRewards: ['탁주 1']
}
]
}
];
@@ -2632,7 +2791,8 @@ export class CampScene extends Phaser.Scene {
battleId === campBattleIds.seventeenth ||
battleId === campBattleIds.eighteenth ||
battleId === campBattleIds.nineteenth ||
battleId === campBattleIds.twentieth
battleId === campBattleIds.twentieth ||
battleId === campBattleIds.twentyFirst
) {
this.campaign = ensureCampaignRosterUnits(
[...xuzhouRecruitUnits, ...caoBreakRecruitUnits, ...liuBiaoRecruitUnits, ...zhugeRecruitUnits],
@@ -2690,6 +2850,9 @@ export class CampScene extends Phaser.Scene {
private currentCampTitle() {
const battleId = this.currentCampBattleId();
if (battleId === campBattleIds.twentyFirst) {
return '적벽 전초전 후 군영';
}
if (battleId === campBattleIds.twentieth) {
return '강동 사절 준비 군영';
}