Add Mianzhu Gate sortie chapter
This commit is contained in:
@@ -38,6 +38,7 @@ const unitTexture: Record<string, string> = {
|
||||
'fa-zheng': 'unit-liu-bei',
|
||||
'wu-yi': 'unit-guan-yu',
|
||||
'yan-yan': 'unit-rebel-archer',
|
||||
'li-yan': 'unit-guan-yu',
|
||||
'rebel-a': 'unit-rebel',
|
||||
'rebel-b': 'unit-rebel',
|
||||
'rebel-c': 'unit-rebel-archer',
|
||||
@@ -427,7 +428,33 @@ const unitTexture: Record<string, string> = {
|
||||
'luo-main-ambusher-c': 'unit-rebel',
|
||||
'luo-main-veteran-yan-yan': 'unit-rebel-archer',
|
||||
'luo-main-officer-a': 'unit-rebel-leader',
|
||||
'luo-main-leader-zhang-ren': 'unit-rebel-leader'
|
||||
'luo-main-leader-zhang-ren': 'unit-rebel-leader',
|
||||
'chengdu-scout-a': 'unit-rebel',
|
||||
'chengdu-scout-b': 'unit-rebel',
|
||||
'chengdu-scout-c': 'unit-rebel',
|
||||
'chengdu-infantry-a': 'unit-rebel',
|
||||
'chengdu-infantry-b': 'unit-rebel',
|
||||
'chengdu-infantry-c': 'unit-rebel',
|
||||
'chengdu-infantry-d': 'unit-rebel',
|
||||
'chengdu-infantry-e': 'unit-rebel',
|
||||
'chengdu-infantry-f': 'unit-rebel',
|
||||
'chengdu-archer-a': 'unit-rebel-archer',
|
||||
'chengdu-archer-b': 'unit-rebel-archer',
|
||||
'chengdu-archer-c': 'unit-rebel-archer',
|
||||
'chengdu-archer-d': 'unit-rebel-archer',
|
||||
'chengdu-cavalry-a': 'unit-rebel-cavalry',
|
||||
'chengdu-cavalry-b': 'unit-rebel-cavalry',
|
||||
'chengdu-cavalry-c': 'unit-rebel-cavalry',
|
||||
'chengdu-guard-a': 'unit-rebel',
|
||||
'chengdu-guard-b': 'unit-rebel',
|
||||
'chengdu-guard-c': 'unit-rebel',
|
||||
'chengdu-strategist-a': 'unit-rebel-archer',
|
||||
'chengdu-strategist-b': 'unit-rebel-archer',
|
||||
'chengdu-ambusher-a': 'unit-rebel',
|
||||
'chengdu-ambusher-b': 'unit-rebel',
|
||||
'chengdu-ambusher-c': 'unit-rebel',
|
||||
'chengdu-officer-fei-guan': 'unit-rebel-leader',
|
||||
'chengdu-leader-li-yan': 'unit-rebel-leader'
|
||||
};
|
||||
|
||||
const unitTextureByClass: Partial<Record<UnitClassKey, string>> = {
|
||||
@@ -1317,7 +1344,33 @@ const enemyAiByUnitId: Record<string, EnemyAiBehavior> = {
|
||||
'luo-main-ambusher-c': 'aggressive',
|
||||
'luo-main-veteran-yan-yan': 'hold',
|
||||
'luo-main-officer-a': 'guard',
|
||||
'luo-main-leader-zhang-ren': 'guard'
|
||||
'luo-main-leader-zhang-ren': 'guard',
|
||||
'chengdu-scout-a': 'aggressive',
|
||||
'chengdu-scout-b': 'aggressive',
|
||||
'chengdu-scout-c': 'aggressive',
|
||||
'chengdu-infantry-a': 'guard',
|
||||
'chengdu-infantry-b': 'guard',
|
||||
'chengdu-infantry-c': 'guard',
|
||||
'chengdu-infantry-d': 'guard',
|
||||
'chengdu-infantry-e': 'guard',
|
||||
'chengdu-infantry-f': 'guard',
|
||||
'chengdu-archer-a': 'hold',
|
||||
'chengdu-archer-b': 'hold',
|
||||
'chengdu-archer-c': 'hold',
|
||||
'chengdu-archer-d': 'hold',
|
||||
'chengdu-cavalry-a': 'aggressive',
|
||||
'chengdu-cavalry-b': 'aggressive',
|
||||
'chengdu-cavalry-c': 'aggressive',
|
||||
'chengdu-guard-a': 'guard',
|
||||
'chengdu-guard-b': 'guard',
|
||||
'chengdu-guard-c': 'guard',
|
||||
'chengdu-strategist-a': 'hold',
|
||||
'chengdu-strategist-b': 'hold',
|
||||
'chengdu-ambusher-a': 'aggressive',
|
||||
'chengdu-ambusher-b': 'aggressive',
|
||||
'chengdu-ambusher-c': 'aggressive',
|
||||
'chengdu-officer-fei-guan': 'guard',
|
||||
'chengdu-leader-li-yan': 'guard'
|
||||
};
|
||||
|
||||
const defaultEnemyAiByClass: Partial<Record<UnitClassKey, EnemyAiBehavior>> = {
|
||||
|
||||
@@ -17,6 +17,7 @@ import seventeenthBattleMapUrl from '../../assets/images/battle/seventeenth-batt
|
||||
import tenthBattleMapUrl from '../../assets/images/battle/tenth-battle-map.svg';
|
||||
import thirteenthBattleMapUrl from '../../assets/images/battle/thirteenth-battle-map.svg';
|
||||
import thirtyFirstBattleMapUrl from '../../assets/images/battle/thirty-first-battle-map.svg';
|
||||
import thirtySecondBattleMapUrl from '../../assets/images/battle/thirty-second-battle-map.svg';
|
||||
import thirtiethBattleMapUrl from '../../assets/images/battle/thirtieth-battle-map.svg';
|
||||
import thirdBattleMapUrl from '../../assets/images/battle/third-battle-map.svg';
|
||||
import twentyEighthBattleMapUrl from '../../assets/images/battle/twenty-eighth-battle-map.svg';
|
||||
@@ -120,6 +121,7 @@ export class BootScene extends Phaser.Scene {
|
||||
this.load.image('battle-map-twenty-ninth', twentyNinthBattleMapUrl);
|
||||
this.load.image('battle-map-thirtieth', thirtiethBattleMapUrl);
|
||||
this.load.image('battle-map-thirty-first', thirtyFirstBattleMapUrl);
|
||||
this.load.image('battle-map-thirty-second', thirtySecondBattleMapUrl);
|
||||
this.load.image('portrait-liu-bei', liuBeiPortraitUrl);
|
||||
this.load.image('portrait-guan-yu', guanYuPortraitUrl);
|
||||
this.load.image('portrait-zhang-fei', zhangFeiPortraitUrl);
|
||||
|
||||
@@ -7,6 +7,8 @@ import { getSortieFlow } from '../data/campaignFlow';
|
||||
import {
|
||||
caoBreakRecruitBonds,
|
||||
caoBreakRecruitUnits,
|
||||
chengduPressureRecruitBonds,
|
||||
chengduPressureRecruitUnits,
|
||||
changshaRecruitBonds,
|
||||
changshaRecruitUnits,
|
||||
firstBattleBonds,
|
||||
@@ -322,7 +324,8 @@ const campaignTimelineChapters: CampaignTimelineChapter[] = [
|
||||
'twenty-eighth-battle-fu-pass-entry',
|
||||
'twenty-ninth-battle-luo-outer-wall',
|
||||
'thirtieth-battle-luofeng-ambush',
|
||||
'thirty-first-battle-luo-main-gate'
|
||||
'thirty-first-battle-luo-main-gate',
|
||||
'thirty-second-battle-mianzhu-gate'
|
||||
],
|
||||
nextHints: ['형주 방위', '익주 진입', '방통·법정 합류']
|
||||
},
|
||||
@@ -367,7 +370,8 @@ const campBattleIds = {
|
||||
twentyEighth: 'twenty-eighth-battle-fu-pass-entry',
|
||||
twentyNinth: 'twenty-ninth-battle-luo-outer-wall',
|
||||
thirtieth: 'thirtieth-battle-luofeng-ambush',
|
||||
thirtyFirst: 'thirty-first-battle-luo-main-gate'
|
||||
thirtyFirst: 'thirty-first-battle-luo-main-gate',
|
||||
thirtySecond: 'thirty-second-battle-mianzhu-gate'
|
||||
} as const;
|
||||
|
||||
const requiredSortieUnitIds = new Set(['liu-bei']);
|
||||
@@ -714,6 +718,18 @@ const sortieRulesByBattleId: Partial<Record<BattleScenarioId, SortieRuleDefiniti
|
||||
{ unitId: 'zhang-fei', reason: '성문 앞 친위대를 짧게 무너뜨리는 돌파력이 필요합니다.' }
|
||||
],
|
||||
note: '낙성 본성 공략은 열여섯 명 중 여섯 명만 출전합니다. 설득을 노리면 책사와 오의를, 빠른 돌파를 노리면 장비와 황충 같은 전투 무장을 우선하십시오.'
|
||||
},
|
||||
'thirty-second-battle-mianzhu-gate': {
|
||||
maxUnits: 6,
|
||||
recommended: [
|
||||
{ unitId: 'liu-bei', reason: '성도 앞 항복 명분과 패배 조건의 중심입니다.' },
|
||||
{ unitId: 'yan-yan', reason: '엄안의 첫 출전입니다. 이엄과 익주 장수들을 설득하는 증거가 됩니다.' },
|
||||
{ unitId: 'fa-zheng', reason: '성도 수비장들의 심리와 면죽관 창고 사정을 읽는 책사입니다.' },
|
||||
{ unitId: 'wu-yi', reason: '익주 안쪽 길과 관문 배치를 아는 새 합류 장수입니다.' },
|
||||
{ unitId: 'pang-tong', reason: '무리한 돌파 대신 항복 권고와 퇴로 확보를 동시에 계산합니다.' },
|
||||
{ unitId: 'huang-zhong', reason: '관문 궁병과 고지 수비를 안정적으로 견제합니다.' }
|
||||
],
|
||||
note: '면죽관 압박전은 열일곱 명 중 여섯 명만 출전합니다. 엄안이 새로 후보에 들어오며, 이엄을 설득하려면 익주 출신 장수와 책사를 함께 고르는 편이 좋습니다.'
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3309,6 +3325,87 @@ const campDialogues: CampDialogue[] = [
|
||||
rewardExp: 11
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'liu-li-after-mianzhu',
|
||||
title: '성도 앞의 군율',
|
||||
availableAfterBattleIds: [campBattleIds.thirtySecond],
|
||||
unitIds: ['liu-bei', 'li-yan'],
|
||||
bondId: 'liu-bei__li-yan',
|
||||
rewardExp: 36,
|
||||
lines: [
|
||||
'이엄: 면죽관을 내주었으나, 성도 앞의 군율까지 무너지면 익주는 오래 앙금을 품을 것입니다.',
|
||||
'유비: 항복한 장수의 말이라도 백성을 살리는 말이면 곧 군령이오. 그대가 익주 병사의 마음을 잡아 주시오.',
|
||||
'이엄: 그렇다면 창고와 관문을 맡아, 항복한 병사도 약탈을 두려워하지 않게 하겠습니다.'
|
||||
],
|
||||
choices: [
|
||||
{
|
||||
id: 'trust-li-yan-discipline',
|
||||
label: '군율 정비를 맡긴다',
|
||||
response: '이엄은 면죽관 창고 명부를 다시 정리하고, 항복 병사에게도 같은 배급 규칙을 적용했습니다.',
|
||||
rewardExp: 14
|
||||
},
|
||||
{
|
||||
id: 'ask-li-yan-chengdu-order',
|
||||
label: '성도 질서를 묻는다',
|
||||
response: '이엄은 성도 안쪽 창고, 문지기, 장수들의 이해관계를 짚어 다음 권고문의 근거를 보탰습니다.',
|
||||
rewardExp: 13
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'zhuge-li-after-mianzhu',
|
||||
title: '내정과 책략',
|
||||
availableAfterBattleIds: [campBattleIds.thirtySecond],
|
||||
unitIds: ['zhuge-liang', 'li-yan'],
|
||||
bondId: 'zhuge-liang__li-yan',
|
||||
rewardExp: 34,
|
||||
lines: [
|
||||
'제갈량: 성을 얻는 일보다 얻은 뒤의 질서가 더 어렵습니다. 이엄 장군은 그 무게를 알고 있군요.',
|
||||
'이엄: 저는 관문을 막는 법은 알지만, 사람 마음을 여는 법은 아직 배워야 합니다.',
|
||||
'제갈량: 그렇다면 관문의 숫자와 사람의 사정을 함께 보지요. 다음 싸움은 이미 싸움 뒤의 정치와 맞닿아 있습니다.'
|
||||
],
|
||||
choices: [
|
||||
{
|
||||
id: 'map-chengdu-storehouses',
|
||||
label: '성도 창고도를 맞춘다',
|
||||
response: '제갈량의 큰 지도 위에 이엄의 창고 명부가 겹치며, 성도 항복 뒤 혼란을 줄일 길이 보였습니다.',
|
||||
rewardExp: 13
|
||||
},
|
||||
{
|
||||
id: 'write-surrender-terms',
|
||||
label: '항복 조건을 다듬는다',
|
||||
response: '두 사람은 유장에게 보낼 문장에 처벌보다 안정을 먼저 적었습니다.',
|
||||
rewardExp: 12
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'yan-li-after-mianzhu',
|
||||
title: '익주 장수의 결단',
|
||||
availableAfterBattleIds: [campBattleIds.thirtySecond],
|
||||
unitIds: ['yan-yan', 'li-yan'],
|
||||
bondId: 'yan-yan__li-yan',
|
||||
rewardExp: 34,
|
||||
lines: [
|
||||
'엄안: 나도 성문 위에서 끝까지 버티려 했소. 그러나 백성이 살아야 장수의 절개도 남는 법이오.',
|
||||
'이엄: 노장께서 먼저 길을 여셨기에 저도 검을 내려놓을 수 있었습니다.',
|
||||
'엄안: 이제는 우리가 익주 장수들에게 말해야 하오. 항복이 부끄러움이 아니라 백성을 살리는 선택이 될 수 있다고.'
|
||||
],
|
||||
choices: [
|
||||
{
|
||||
id: 'share-yizhou-officer-names',
|
||||
label: '익주 장수 명단을 맞춘다',
|
||||
response: '엄안과 이엄은 끝까지 버틸 장수와 설득 가능한 장수를 나누어, 다음 전장의 말길을 열었습니다.',
|
||||
rewardExp: 13
|
||||
},
|
||||
{
|
||||
id: 'honor-yizhou-soldiers',
|
||||
label: '항복 병사를 예우한다',
|
||||
response: '두 장수가 함께 병사들을 달래자, 익주 병사들은 유비군 군영을 조금 덜 두려워하게 되었습니다.',
|
||||
rewardExp: 12
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
@@ -4298,6 +4395,66 @@ const campVisits: CampVisitDefinition[] = [
|
||||
itemRewards: ['익주 창고 장부 1']
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'mianzhu-storehouse-relief',
|
||||
title: '면죽관 창고 구호',
|
||||
location: '면죽관 창고',
|
||||
availableAfterBattleIds: [campBattleIds.thirtySecond],
|
||||
bondId: 'liu-bei__li-yan',
|
||||
description: '유비와 이엄이 면죽관 창고를 열어 항복 병사와 백성에게 공평하게 배급할 방법을 정합니다.',
|
||||
lines: [
|
||||
'이엄: 창고를 빼앗긴 병사들은 패배보다 굶주림을 더 두려워합니다.',
|
||||
'유비: 그렇다면 먼저 명부를 세우고, 우리 병사와 항복 병사를 같은 줄에 세웁시다.',
|
||||
'창고 앞의 긴장된 줄은 천천히 낮아졌고, 면죽관의 문은 전리품이 아니라 구호소처럼 쓰이기 시작했습니다.'
|
||||
],
|
||||
choices: [
|
||||
{
|
||||
id: 'equal-storehouse-ration',
|
||||
label: '배급 줄을 하나로 합친다',
|
||||
response: '유비군과 항복 병사가 같은 줄에서 배급을 받자, 성도까지 보낼 소문이 한층 부드러워졌습니다.',
|
||||
bondExp: 28,
|
||||
itemRewards: ['콩 6', '상처약 3']
|
||||
},
|
||||
{
|
||||
id: 'record-mianzhu-ledger',
|
||||
label: '창고 장부를 남긴다',
|
||||
response: '이엄은 창고 장부에 손댄 곡식과 남긴 곡식을 모두 적어, 다음 성도 권고의 증거를 만들었습니다.',
|
||||
bondExp: 26,
|
||||
gold: 420,
|
||||
itemRewards: ['면죽관 창고 장부 1']
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'chengdu-surrender-council',
|
||||
title: '성도 항복 권고 회의',
|
||||
location: '면죽관 본막',
|
||||
availableAfterBattleIds: [campBattleIds.thirtySecond],
|
||||
bondId: 'zhuge-liang__li-yan',
|
||||
description: '제갈량과 이엄이 성도에 보낼 항복 조건을 정리하고, 유장이 받아들일 만한 명분을 다듬습니다.',
|
||||
lines: [
|
||||
'제갈량: 권고문은 이기는 군대의 칼이 아니라, 성 안의 사람이 붙잡을 난간이어야 합니다.',
|
||||
'이엄: 유장공이 체면을 잃지 않는 문장이라면, 문을 여는 사람도 배신자가 아니라 중재자가 될 수 있습니다.',
|
||||
'두 사람은 항복 뒤 관직과 창고, 병사의 처우를 조목조목 적어 다음 장의 실마리를 만들었습니다.'
|
||||
],
|
||||
choices: [
|
||||
{
|
||||
id: 'soften-surrender-terms',
|
||||
label: '항복 조건을 부드럽게 쓴다',
|
||||
response: '권고문은 위협보다 보전과 질서를 앞세웠고, 성도 안쪽으로 들어갈 여지가 넓어졌습니다.',
|
||||
bondExp: 28,
|
||||
itemRewards: ['성도 항복 권고문 1']
|
||||
},
|
||||
{
|
||||
id: 'prepare-city-officer-list',
|
||||
label: '성도 장수 명단을 정리한다',
|
||||
response: '이엄의 기억과 제갈량의 판단이 겹쳐지며, 다음 설득전에서 먼저 만날 장수들이 정해졌습니다.',
|
||||
bondExp: 26,
|
||||
gold: 440,
|
||||
itemRewards: ['성도 장수 명단 1']
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
@@ -4384,6 +4541,43 @@ export class CampScene extends Phaser.Scene {
|
||||
return;
|
||||
}
|
||||
|
||||
if (battleId === campBattleIds.thirtySecond) {
|
||||
this.campaign = ensureCampaignRosterUnits(
|
||||
[
|
||||
...xuzhouRecruitUnits,
|
||||
...caoBreakRecruitUnits,
|
||||
...liuBiaoRecruitUnits,
|
||||
...zhugeRecruitUnits,
|
||||
...jingzhouRecruitUnits,
|
||||
...guiyangRecruitUnits,
|
||||
...wulingRecruitUnits,
|
||||
...changshaRecruitUnits,
|
||||
...yizhouRecruitUnits,
|
||||
...fuPassRecruitUnits,
|
||||
...luoCastleRecruitUnits,
|
||||
...luoCastleProperRecruitUnits,
|
||||
...chengduPressureRecruitUnits
|
||||
],
|
||||
[
|
||||
...xuzhouRecruitBonds,
|
||||
...caoBreakRecruitBonds,
|
||||
...liuBiaoRecruitBonds,
|
||||
...zhugeRecruitBonds,
|
||||
...jingzhouRecruitBonds,
|
||||
...guiyangRecruitBonds,
|
||||
...wulingRecruitBonds,
|
||||
...changshaRecruitBonds,
|
||||
...yizhouRecruitBonds,
|
||||
...fuPassRecruitBonds,
|
||||
...luoCastleRecruitBonds,
|
||||
...luoCastleProperRecruitBonds,
|
||||
...chengduPressureRecruitBonds
|
||||
]
|
||||
);
|
||||
this.report = this.campaign.firstBattleReport ?? this.report;
|
||||
return;
|
||||
}
|
||||
|
||||
if (battleId === campBattleIds.thirtyFirst) {
|
||||
this.campaign = ensureCampaignRosterUnits(
|
||||
[
|
||||
@@ -4660,6 +4854,9 @@ export class CampScene extends Phaser.Scene {
|
||||
|
||||
private currentCampTitle() {
|
||||
const battleId = this.currentCampBattleId();
|
||||
if (battleId === campBattleIds.thirtySecond) {
|
||||
return '면죽관 압박전 후 군영';
|
||||
}
|
||||
if (battleId === campBattleIds.thirtyFirst) {
|
||||
return '낙성 본성 공략 후 군영';
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
tenthBattleScenario,
|
||||
thirteenthBattleScenario,
|
||||
thirtyFirstBattleScenario,
|
||||
thirtySecondBattleScenario,
|
||||
thirtiethBattleScenario,
|
||||
twentyEighthBattleScenario,
|
||||
twentyFirstBattleScenario,
|
||||
@@ -361,7 +362,8 @@ export class TitleScene extends Phaser.Scene {
|
||||
campaign.step === 'twenty-eighth-camp' ||
|
||||
campaign.step === 'twenty-ninth-camp' ||
|
||||
campaign.step === 'thirtieth-camp' ||
|
||||
campaign.step === 'thirty-first-camp'
|
||||
campaign.step === 'thirty-first-camp' ||
|
||||
campaign.step === 'thirty-second-camp'
|
||||
) {
|
||||
this.scene.start('CampScene');
|
||||
return;
|
||||
@@ -522,6 +524,11 @@ export class TitleScene extends Phaser.Scene {
|
||||
return;
|
||||
}
|
||||
|
||||
if (campaign.step === 'thirty-second-battle') {
|
||||
this.scene.start('BattleScene', { battleId: thirtySecondBattleScenario.id });
|
||||
return;
|
||||
}
|
||||
|
||||
if (campaign.step === 'first-victory-story') {
|
||||
this.scene.start('StoryScene', { pages: firstBattleVictoryPages, nextScene: 'TitleScene' });
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user