Add Xuzhou gate night raid battle

This commit is contained in:
2026-06-23 01:42:44 +09:00
parent 1c501057e3
commit 056fb34226
11 changed files with 682 additions and 16 deletions

View File

@@ -535,7 +535,17 @@ const enemyAiByUnitId: Record<string, EnemyAiBehavior> = {
'xiaopei-guard-b': 'aggressive',
'xiaopei-cavalry-a': 'aggressive',
'xiaopei-cavalry-b': 'aggressive',
'xiaopei-leader-gao-shun': 'guard'
'xiaopei-leader-gao-shun': 'guard',
'xuzhou-gate-traitor-a': 'aggressive',
'xuzhou-gate-traitor-b': 'aggressive',
'xuzhou-gate-infantry-a': 'guard',
'xuzhou-gate-infantry-b': 'guard',
'xuzhou-gate-archer-a': 'hold',
'xuzhou-gate-archer-b': 'hold',
'lubu-cavalry-a': 'aggressive',
'lubu-cavalry-b': 'aggressive',
'lubu-cavalry-c': 'aggressive',
'xuzhou-leader-cao-bao': 'guard'
};
const defaultEnemyAiByClass: Partial<Record<UnitClassKey, EnemyAiBehavior>> = {

View File

@@ -3,6 +3,7 @@ import eighthBattleMapUrl from '../../assets/images/battle/eighth-battle-map.svg
import fifthBattleMapUrl from '../../assets/images/battle/fifth-battle-map.svg';
import firstBattleMapUrl from '../../assets/images/battle/first-battle-map.png';
import fourthBattleMapUrl from '../../assets/images/battle/fourth-battle-map.svg';
import ninthBattleMapUrl from '../../assets/images/battle/ninth-battle-map.svg';
import secondBattleMapUrl from '../../assets/images/battle/second-battle-map.svg';
import seventhBattleMapUrl from '../../assets/images/battle/seventh-battle-map.svg';
import sixthBattleMapUrl from '../../assets/images/battle/sixth-battle-map.svg';
@@ -74,6 +75,7 @@ export class BootScene extends Phaser.Scene {
this.load.image('battle-map-sixth', sixthBattleMapUrl);
this.load.image('battle-map-seventh', seventhBattleMapUrl);
this.load.image('battle-map-eighth', eighthBattleMapUrl);
this.load.image('battle-map-ninth', ninthBattleMapUrl);
this.load.image('portrait-liu-bei', liuBeiPortraitUrl);
this.load.image('portrait-guan-yu', guanYuPortraitUrl);
this.load.image('portrait-zhang-fei', zhangFeiPortraitUrl);

View File

@@ -131,7 +131,8 @@ const campBattleIds = {
fifth: 'fifth-battle-sishui-vanguard',
sixth: 'sixth-battle-jieqiao-relief',
seventh: 'seventh-battle-xuzhou-rescue',
eighth: 'eighth-battle-xiaopei-supply-road'
eighth: 'eighth-battle-xiaopei-supply-road',
ninth: 'ninth-battle-xuzhou-gate-night-raid'
} as const;
const requiredSortieUnitIds = new Set(['liu-bei']);
@@ -839,6 +840,87 @@ const campDialogues: CampDialogue[] = [
rewardExp: 9
}
]
},
{
id: 'liu-zhang-after-xuzhou-gate',
title: '성문 뒤의 후회',
availableAfterBattleIds: [campBattleIds.ninth],
unitIds: ['liu-bei', 'zhang-fei'],
bondId: 'liu-bei__zhang-fei',
rewardExp: 22,
lines: [
'장비: 형님, 성문은 지켰지만 조표 같은 자들이 움직인 것 자체가 제 불찰입니다.',
'유비: 익덕, 성급함은 네 강점이자 약점이다. 하지만 후회로 멈추면 다음 성문도 지키지 못한다.',
'장비: 다음에는 술도, 분노도, 먼저 앞세우지 않겠습니다.'
],
choices: [
{
id: 'trust-reform',
label: '다시 맡기되 군율을 세운다',
response: '장비는 꾸짖음보다 믿음을 받은 것에 더 무겁게 고개를 숙였다.',
rewardExp: 10
},
{
id: 'share-command',
label: '관우와 함께 성문을 맡긴다',
response: '장비는 운장 형님과 함께라면 이번 실수를 씻어 보이겠다고 답했다.',
rewardExp: 8
}
]
},
{
id: 'liu-jian-yong-after-xuzhou-gate',
title: '의와 현실 사이',
availableAfterBattleIds: [campBattleIds.ninth],
unitIds: ['liu-bei', 'jian-yong'],
bondId: 'liu-bei__jian-yong',
rewardExp: 22,
lines: [
'간옹: 현덕, 여포를 들인 일은 이제 말 한마디로 되돌릴 수 없네. 사람들은 이미 네 의와 그의 칼을 함께 보고 있어.',
'유비: 의로 시작한 일이 백성을 위태롭게 한다면, 나는 무엇을 지켜야 하오?',
'간옹: 그래서 더더욱 눈을 뜨고 있어야지. 의도 잠들면 남의 칼집이 된다네.'
],
choices: [
{
id: 'watch-lu-bu',
label: '여포의 움직임을 감시한다',
response: '간옹은 마침내 현실적인 지시가 나왔다며 사람을 붙이겠다고 했다.',
rewardExp: 10
},
{
id: 'protect-civilians-first',
label: '백성 피난로를 먼저 정한다',
response: '간옹은 서주를 잃더라도 사람을 잃지 않겠다는 말에 한숨 섞인 미소를 지었다.',
rewardExp: 8
}
]
},
{
id: 'liu-mi-after-xuzhou-gate',
title: '성문과 창고',
availableAfterBattleIds: [campBattleIds.ninth],
unitIds: ['liu-bei', 'mi-zhu'],
bondId: 'liu-bei__mi-zhu',
rewardExp: 18,
lines: [
'미축: 성문이 흔들리면 창고도 오래 버티지 못합니다. 군량을 지키는 길과 성문을 지키는 길은 다르지 않습니다.',
'유비: 그렇다면 창고의 문서와 수레를 성문 수비와 함께 움직입시다. 길이 끊겨도 백성에게 갈 몫은 남겨 두어야 하오.',
'미축: 그 뜻이면 충분합니다. 다음 혼란에 대비하겠습니다.'
],
choices: [
{
id: 'move-grain-carts',
label: '군량 수레를 재배치한다',
response: '미축은 군량 수레를 성문 안쪽 길목으로 옮겨 비상시에 대비했다.',
rewardExp: 8
},
{
id: 'guard-ledgers',
label: '창고 장부를 보호한다',
response: '관우는 장부를 지키는 일도 전열을 지키는 일이라며 병사를 배치했다.',
rewardExp: 7
}
]
}
];
@@ -917,7 +999,7 @@ export class CampScene extends Phaser.Scene {
private ensureCurrentCampRecruitment() {
const battleId = this.currentCampBattleId();
if ((battleId !== campBattleIds.seventh && battleId !== campBattleIds.eighth) || !this.campaign) {
if ((battleId !== campBattleIds.seventh && battleId !== campBattleIds.eighth && battleId !== campBattleIds.ninth) || !this.campaign) {
return;
}
@@ -937,6 +1019,9 @@ export class CampScene extends Phaser.Scene {
private currentCampTitle() {
const battleId = this.currentCampBattleId();
if (battleId === campBattleIds.ninth) {
return '서주 성문 군영';
}
if (battleId === campBattleIds.eighth) {
return '소패 방위 군영';
}

View File

@@ -5,6 +5,7 @@ import {
eighthBattleScenario,
fifthBattleScenario,
fourthBattleScenario,
ninthBattleScenario,
secondBattleScenario,
seventhBattleScenario,
sixthBattleScenario,
@@ -315,7 +316,8 @@ export class TitleScene extends Phaser.Scene {
campaign.step === 'fifth-camp' ||
campaign.step === 'sixth-camp' ||
campaign.step === 'seventh-camp' ||
campaign.step === 'eighth-camp'
campaign.step === 'eighth-camp' ||
campaign.step === 'ninth-camp'
) {
this.scene.start('CampScene');
return;
@@ -361,6 +363,11 @@ export class TitleScene extends Phaser.Scene {
return;
}
if (campaign.step === 'ninth-battle') {
this.scene.start('BattleScene', { battleId: ninthBattleScenario.id });
return;
}
if (campaign.step === 'first-victory-story') {
this.scene.start('StoryScene', { pages: firstBattleVictoryPages, nextScene: 'TitleScene' });
return;