Add Xiaopei supply road battle

This commit is contained in:
2026-06-23 01:26:45 +09:00
parent 6b863882aa
commit 1c501057e3
11 changed files with 693 additions and 25 deletions

View File

@@ -524,7 +524,18 @@ const enemyAiByUnitId: Record<string, EnemyAiBehavior> = {
'xuzhou-cavalry-c': 'aggressive',
'xuzhou-guard-b': 'guard',
'xuzhou-archer-c': 'hold',
'xuzhou-leader-xiahou-dun': 'guard'
'xuzhou-leader-xiahou-dun': 'guard',
'xiaopei-raider-a': 'aggressive',
'xiaopei-raider-b': 'aggressive',
'xiaopei-infantry-a': 'guard',
'xiaopei-infantry-b': 'guard',
'xiaopei-archer-a': 'hold',
'xiaopei-archer-b': 'hold',
'xiaopei-guard-a': 'aggressive',
'xiaopei-guard-b': 'aggressive',
'xiaopei-cavalry-a': 'aggressive',
'xiaopei-cavalry-b': 'aggressive',
'xiaopei-leader-gao-shun': 'guard'
};
const defaultEnemyAiByClass: Partial<Record<UnitClassKey, EnemyAiBehavior>> = {

View File

@@ -1,4 +1,5 @@
import Phaser from 'phaser';
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';
@@ -72,6 +73,7 @@ export class BootScene extends Phaser.Scene {
this.load.image('battle-map-fifth', fifthBattleMapUrl);
this.load.image('battle-map-sixth', sixthBattleMapUrl);
this.load.image('battle-map-seventh', seventhBattleMapUrl);
this.load.image('battle-map-eighth', eighthBattleMapUrl);
this.load.image('portrait-liu-bei', liuBeiPortraitUrl);
this.load.image('portrait-guan-yu', guanYuPortraitUrl);
this.load.image('portrait-zhang-fei', zhangFeiPortraitUrl);

View File

@@ -130,7 +130,8 @@ const campBattleIds = {
fourth: 'fourth-battle-guangzong-camp',
fifth: 'fifth-battle-sishui-vanguard',
sixth: 'sixth-battle-jieqiao-relief',
seventh: 'seventh-battle-xuzhou-rescue'
seventh: 'seventh-battle-xuzhou-rescue',
eighth: 'eighth-battle-xiaopei-supply-road'
} as const;
const requiredSortieUnitIds = new Set(['liu-bei']);
@@ -757,6 +758,87 @@ const campDialogues: CampDialogue[] = [
rewardExp: 7
}
]
},
{
id: 'liu-mi-zhu-after-xiaopei',
title: '지켜낸 보급로',
availableAfterBattleIds: [campBattleIds.eighth],
unitIds: ['liu-bei', 'mi-zhu'],
bondId: 'liu-bei__mi-zhu',
rewardExp: 20,
lines: [
'미축: 소패 보급로는 지켰지만, 서주는 아직 안정되었다고 보기 어렵습니다. 창고를 지켜도 사람의 마음이 흩어지면 다시 흔들립니다.',
'유비: 군량은 칼보다 조용하지만, 백성을 살리는 힘이 있소. 그 일을 계속 맡아 주시오.',
'미축: 맡겨 주신다면 상단과 호족을 설득해 서주의 숨통을 조금 더 넓히겠습니다.'
],
choices: [
{
id: 'trust-supply-network',
label: '보급망을 믿고 맡긴다',
response: '미축은 유비가 실무를 믿고 맡기자 서주의 창고와 상단을 더 단단히 묶겠다고 답했다.',
rewardExp: 8
},
{
id: 'share-grain-openly',
label: '군량 배분을 투명하게 한다',
response: '유비가 백성 앞에서 군량 배분을 숨기지 않겠다고 하자 미축의 표정이 한결 밝아졌다.',
rewardExp: 9
}
]
},
{
id: 'liu-jian-yong-after-xiaopei',
title: '여포를 맞을 것인가',
availableAfterBattleIds: [campBattleIds.eighth],
unitIds: ['liu-bei', 'jian-yong'],
bondId: 'liu-bei__jian-yong',
rewardExp: 20,
lines: [
'간옹: 여포를 받아들이면 의롭다는 말은 들을 걸세. 하지만 사람들은 그 뒤에 칼집이 몇 개인지도 세어 볼 거야.',
'유비: 곤궁한 이를 외면할 수는 없소. 그러나 서주 백성의 근심도 가볍게 여길 수 없소.',
'간옹: 그래서 선택에는 늘 값을 치르는 법이지. 적어도 그 값을 알고 받아들이게.'
],
choices: [
{
id: 'accept-with-guard',
label: '경계를 세우고 맞는다',
response: '간옹은 유비가 의와 경계를 함께 세우겠다고 하자, 그나마 현실적인 대답이라고 웃었다.',
rewardExp: 9
},
{
id: 'ask-people-first',
label: '백성의 불안을 먼저 살핀다',
response: '간옹은 서주 사람들의 두려움을 먼저 살피겠다는 말에 고개를 끄덕였다.',
rewardExp: 8
}
]
},
{
id: 'guan-zhang-after-xiaopei',
title: '성문을 지키는 두 장수',
availableAfterBattleIds: [campBattleIds.eighth],
unitIds: ['guan-yu', 'zhang-fei'],
bondId: 'guan-yu__zhang-fei',
rewardExp: 20,
lines: [
'관우: 여포가 온다면 군율이 먼저 흔들릴 것이다. 성문과 술자리를 모두 조심해야 한다.',
'장비: 왜 나를 보며 말하시오? 하지만 알겠소. 이번에는 먼저 소리치기보다 성문부터 붙잡겠소.',
'관우: 형님의 뜻을 지키려면 우리부터 흔들리지 않아야 한다.'
],
choices: [
{
id: 'guard-gates',
label: '성문 경계를 강화한다',
response: '관우와 장비는 성문 근무와 순찰을 나누어 맡기로 했다.',
rewardExp: 8
},
{
id: 'restrain-temper',
label: '장비의 성급함을 다잡는다',
response: '장비는 툴툴대면서도 이번만큼은 술보다 군율을 먼저 보겠다고 약속했다.',
rewardExp: 9
}
]
}
];
@@ -834,7 +916,8 @@ export class CampScene extends Phaser.Scene {
}
private ensureCurrentCampRecruitment() {
if (this.currentCampBattleId() !== campBattleIds.seventh || !this.campaign) {
const battleId = this.currentCampBattleId();
if ((battleId !== campBattleIds.seventh && battleId !== campBattleIds.eighth) || !this.campaign) {
return;
}
@@ -854,6 +937,9 @@ export class CampScene extends Phaser.Scene {
private currentCampTitle() {
const battleId = this.currentCampBattleId();
if (battleId === campBattleIds.eighth) {
return '소패 방위 군영';
}
if (battleId === campBattleIds.seventh) {
return '서주 인수 준비 군영';
}

View File

@@ -1,7 +1,15 @@
import Phaser from 'phaser';
import { soundDirector } from '../audio/SoundDirector';
import { firstBattleVictoryPages } from '../data/scenario';
import { fifthBattleScenario, fourthBattleScenario, secondBattleScenario, seventhBattleScenario, sixthBattleScenario, thirdBattleScenario } from '../data/battles';
import {
eighthBattleScenario,
fifthBattleScenario,
fourthBattleScenario,
secondBattleScenario,
seventhBattleScenario,
sixthBattleScenario,
thirdBattleScenario
} from '../data/battles';
import { hasCampaignSave, loadCampaignState, startNewCampaign } from '../state/campaignState';
import { palette } from '../ui/palette';
@@ -306,7 +314,8 @@ export class TitleScene extends Phaser.Scene {
campaign.step === 'fourth-camp' ||
campaign.step === 'fifth-camp' ||
campaign.step === 'sixth-camp' ||
campaign.step === 'seventh-camp'
campaign.step === 'seventh-camp' ||
campaign.step === 'eighth-camp'
) {
this.scene.start('CampScene');
return;
@@ -347,6 +356,11 @@ export class TitleScene extends Phaser.Scene {
return;
}
if (campaign.step === 'eighth-battle') {
this.scene.start('BattleScene', { battleId: eighthBattleScenario.id });
return;
}
if (campaign.step === 'first-victory-story') {
this.scene.start('StoryScene', { pages: firstBattleVictoryPages, nextScene: 'TitleScene' });
return;