Add battle-specific camp conversations

This commit is contained in:
2026-06-23 00:08:25 +09:00
parent fe93b70a56
commit 8e9bddcacf
3 changed files with 418 additions and 11 deletions

View File

@@ -22,12 +22,13 @@ Build a small complete tactical RPG loop that can grow into a longer Romance of
- Third battle expansion path toward Guangzong with a new battlefield, story bridge, campaign step persistence, and title continue support
- Fourth battle Guangzong main camp confrontation with Zhang Jue, expanded enemy formation, campaign persistence, and anti-Dong Zhuo setup
- Fifth battle Sishui Gate vanguard path starting the anti-Dong Zhuo chapter, with campaign persistence and title continue support
- Battle-specific camp conversations with selectable bond-growth choices from the first camp through the Gongsun Zan camp
- Flow verification script from title through the fifth battle victory and camp save state
## Next Steps
1. Build the Gongsun Zan camp chapter after the Sishui Gate vanguard battle
2. Broaden camp events so bonds, supplies, and equipment choices matter between battles
2. Add the next Gongsun Zan-era battle and the story bridge toward Xu Province
3. Apply treasure equipment effects to damage, defense, recovery, and command rules
4. Add more distinctive victory/defeat branches and optional objectives
5. Keep expanding scenarios through the long campaign instead of treating the current slice as an ending

View File

@@ -272,6 +272,13 @@ try {
if (!campState || campState.scene !== 'CampScene' || campState.report?.rewardGold <= 0) {
throw new Error(`Expected CampScene report after victory: ${JSON.stringify(campState)}`);
}
if (
campState.campBattleId !== 'first-battle-zhuo-commandery' ||
campState.availableDialogueIds?.length !== 3 ||
!campState.availableDialogueIds.every((id) => id.endsWith('first-battle'))
) {
throw new Error(`Expected first camp to expose first-battle dialogue set: ${JSON.stringify(campState)}`);
}
await page.evaluate(() => {
const campScene = window.__HEROS_GAME__?.scene.getScene('CampScene');
@@ -320,7 +327,7 @@ try {
await page.waitForTimeout(260);
const campStateAfterDialogue = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
if (!campStateAfterDialogue.report?.completedCampDialogues?.length) {
if (!campStateAfterDialogue.report?.completedCampDialogues?.length || campStateAfterDialogue.completedAvailableDialogues?.length !== 1) {
throw new Error(`Expected camp dialogue to award bond exp: ${JSON.stringify(campStateAfterDialogue)}`);
}
@@ -646,6 +653,16 @@ try {
throw new Error(`Expected campaign save to persist fifth battle victory: ${JSON.stringify(campaignSaveAfterFifthBattle)}`);
}
const fifthCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
if (
fifthCampState?.campBattleId !== 'fifth-battle-sishui-vanguard' ||
fifthCampState.campTitle !== '공손찬 진영 군영' ||
fifthCampState.availableDialogueIds?.length !== 3 ||
!fifthCampState.availableDialogueIds.every((id) => id.endsWith('sishui'))
) {
throw new Error(`Expected fifth camp to expose Gongsun Zan/Sishui dialogue set: ${JSON.stringify(fifthCampState)}`);
}
await page.evaluate(() => window.__HEROS_GAME__?.scene.start('TitleScene'));
await page.waitForFunction(() => {
const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? [];

View File

@@ -21,6 +21,7 @@ type CampTab = 'status' | 'dialogue' | 'supplies';
type CampDialogue = {
id: string;
title: string;
availableAfterBattleIds: string[];
unitIds: [string, string];
bondId: string;
rewardExp: number;
@@ -121,10 +122,19 @@ const merchantItems: MerchantItemDefinition[] = [
}
];
const campBattleIds = {
first: defaultBattleScenario.id,
second: 'second-battle-yellow-turban-pursuit',
third: 'third-battle-guangzong-road',
fourth: 'fourth-battle-guangzong-camp',
fifth: 'fifth-battle-sishui-vanguard'
} as const;
const campDialogues: CampDialogue[] = [
{
id: 'liu-guan-after-first-battle',
title: '의리를 다지는 밤',
availableAfterBattleIds: [campBattleIds.first],
unitIds: ['liu-bei', 'guan-yu'],
bondId: 'liu-bei__guan-yu',
rewardExp: 14,
@@ -151,6 +161,7 @@ const campDialogues: CampDialogue[] = [
{
id: 'liu-zhang-after-first-battle',
title: '거친 용기를 붙잡다',
availableAfterBattleIds: [campBattleIds.first],
unitIds: ['liu-bei', 'zhang-fei'],
bondId: 'liu-bei__zhang-fei',
rewardExp: 14,
@@ -177,6 +188,7 @@ const campDialogues: CampDialogue[] = [
{
id: 'guan-zhang-after-first-battle',
title: '칼과 창의 약속',
availableAfterBattleIds: [campBattleIds.first],
unitIds: ['guan-yu', 'zhang-fei'],
bondId: 'guan-yu__zhang-fei',
rewardExp: 12,
@@ -199,6 +211,330 @@ const campDialogues: CampDialogue[] = [
rewardExp: 4
}
]
},
{
id: 'liu-guan-after-pursuit',
title: '추격 뒤의 절제',
availableAfterBattleIds: [campBattleIds.second],
unitIds: ['liu-bei', 'guan-yu'],
bondId: 'liu-bei__guan-yu',
rewardExp: 15,
lines: [
'유비: 잔당을 쫓는 싸움일수록 칼끝이 흐려지기 쉽소. 오늘 운장의 판단이 군을 지켰소.',
'관우: 형님이 백성의 길을 먼저 살피라 하셨기에 무리한 추격을 멈출 수 있었습니다.',
'유비: 이기되 어지럽히지 않는 것, 그것이 우리가 지켜야 할 싸움이오.'
],
choices: [
{
id: 'steady-line',
label: '전열의 절제를 칭찬한다',
response: '관우는 전공보다 전열의 안정이 먼저라는 말을 마음에 새겼다.',
rewardExp: 6
},
{
id: 'entrust-village',
label: '마을 수비를 맡긴다',
response: '관우는 다음 싸움에서도 백성이 있는 곳을 먼저 지키겠다고 답했다.',
rewardExp: 5
}
]
},
{
id: 'liu-zhang-after-pursuit',
title: '성급함과 선봉',
availableAfterBattleIds: [campBattleIds.second],
unitIds: ['liu-bei', 'zhang-fei'],
bondId: 'liu-bei__zhang-fei',
rewardExp: 15,
lines: [
'장비: 형님, 추격전에서는 한 발만 더 빨랐다면 놈들을 몽땅 묶어 둘 수 있었습니다.',
'유비: 익덕의 빠른 창은 큰 힘이오. 다만 혼자 먼저 닿으면 뒤따르는 병사들이 흔들리오.',
'장비: 좋소. 다음엔 앞서 달리되, 형님 깃발이 보이는 거리 안에서 치겠습니다.'
],
choices: [
{
id: 'controlled-charge',
label: '통제된 돌격을 약속받는다',
response: '장비는 선봉의 힘과 군율이 함께 가야 한다는 말을 받아들였다.',
rewardExp: 6
},
{
id: 'praise-speed',
label: '빠른 결단을 칭찬한다',
response: '믿음을 받은 장비는 다음 전장에서 더 날카로운 선봉이 되겠다고 웃었다.',
rewardExp: 5
}
]
},
{
id: 'guan-zhang-after-pursuit',
title: '길목을 나누다',
availableAfterBattleIds: [campBattleIds.second],
unitIds: ['guan-yu', 'zhang-fei'],
bondId: 'guan-yu__zhang-fei',
rewardExp: 13,
lines: [
'관우: 익덕, 네가 길목을 막아 준 덕에 궁병들이 숨을 돌렸다.',
'장비: 운장 형님이 옆을 눌러 주니 제가 마음 놓고 앞을 칠 수 있었지요.',
'관우: 다음에도 누가 먼저 나서든, 남은 한 사람이 뒤를 끊어 주면 된다.'
],
choices: [
{
id: 'split-roads',
label: '길목 분담을 정한다',
response: '두 사람은 좁은 길에서 서로의 창끝과 칼끝이 부딪히지 않을 간격을 익혔다.',
rewardExp: 6
},
{
id: 'bold-competition',
label: '전공을 겨루게 둔다',
response: '가벼운 경쟁심이 두 맹장의 호흡을 더 뜨겁게 만들었다.',
rewardExp: 4
}
]
},
{
id: 'liu-guan-after-guangzong-road',
title: '광종 길목의 침묵',
availableAfterBattleIds: [campBattleIds.third],
unitIds: ['liu-bei', 'guan-yu'],
bondId: 'liu-bei__guan-yu',
rewardExp: 16,
lines: [
'유비: 광종으로 가는 길은 좁고, 적은 지형을 믿고 버티고 있소.',
'관우: 험한 길에서는 말보다 마음이 먼저 흔들립니다. 제가 앞에서 병사들의 숨을 고르겠습니다.',
'유비: 운장이 앞을 잡아 준다면, 나도 뒤에서 군의 뜻을 잃지 않게 하겠소.'
],
choices: [
{
id: 'lead-narrow-road',
label: '좁은 길의 선봉을 맡긴다',
response: '관우는 험지를 뚫는 임무를 조용히 받아들였다.',
rewardExp: 7
},
{
id: 'keep-discipline',
label: '군율 유지를 당부한다',
response: '관우는 승리보다 흐트러지지 않는 행군이 더 중요하다고 답했다.',
rewardExp: 5
}
]
},
{
id: 'liu-zhang-after-guangzong-road',
title: '강가의 큰소리',
availableAfterBattleIds: [campBattleIds.third],
unitIds: ['liu-bei', 'zhang-fei'],
bondId: 'liu-bei__zhang-fei',
rewardExp: 16,
lines: [
'장비: 강가 길목이 답답해서 창을 휘두를 맛이 덜했습니다.',
'유비: 답답한 싸움에서도 익덕이 버텨 주니 병사들이 물러서지 않았소.',
'장비: 다음엔 답답한 길도 제가 먼저 웃으며 열어 보이겠습니다.'
],
choices: [
{
id: 'laugh-through',
label: '호방함으로 사기를 올린다',
response: '장비의 큰 웃음이 군영의 무거운 공기를 풀었다.',
rewardExp: 6
},
{
id: 'guard-bridgehead',
label: '교두보 수비를 맡긴다',
response: '장비는 한 발도 물러서지 않는 수비 역시 선봉의 일이라며 고개를 끄덕였다.',
rewardExp: 5
}
]
},
{
id: 'guan-zhang-after-guangzong-road',
title: '험지의 호흡',
availableAfterBattleIds: [campBattleIds.third],
unitIds: ['guan-yu', 'zhang-fei'],
bondId: 'guan-yu__zhang-fei',
rewardExp: 14,
lines: [
'관우: 숲과 언덕에서는 한 걸음 늦어도 전열 전체가 늦어진다.',
'장비: 그럴 땐 형님이 길을 재고, 제가 길을 넓히면 되지 않겠습니까?',
'관우: 좋은 말이다. 칼이 재고 창이 연다면 험지도 길이 된다.'
],
choices: [
{
id: 'measure-and-break',
label: '관우가 재고 장비가 뚫는다',
response: '두 사람은 험지 돌파의 역할을 명확히 나누었다.',
rewardExp: 7
},
{
id: 'watch-each-other',
label: '서로의 후방을 살핀다',
response: '성급한 돌파보다 빈틈을 지우는 호흡이 깊어졌다.',
rewardExp: 5
}
]
},
{
id: 'liu-guan-after-zhang-jue',
title: '황건의 깃발이 꺾인 밤',
availableAfterBattleIds: [campBattleIds.fourth],
unitIds: ['liu-bei', 'guan-yu'],
bondId: 'liu-bei__guan-yu',
rewardExp: 18,
lines: [
'유비: 장각이 쓰러졌으나 천하가 바로 맑아진 것은 아니오.',
'관우: 큰 난이 끝난 뒤에도 작은 탐욕은 남습니다. 형님의 뜻이 흔들리지 않게 제가 곁에 있겠습니다.',
'유비: 운장과 익덕이 있으니, 더 큰 혼란에도 나아갈 용기가 생기오.'
],
choices: [
{
id: 'prepare-larger-chaos',
label: '더 큰 혼란을 준비한다',
response: '관우는 반동탁의 소문을 들으며 다시 칼을 정비했다.',
rewardExp: 7
},
{
id: 'honor-fallen-people',
label: '희생된 백성을 기린다',
response: '두 사람은 전공보다 백성의 상처를 먼저 기억하기로 했다.',
rewardExp: 6
}
]
},
{
id: 'liu-zhang-after-zhang-jue',
title: '끝난 싸움, 남은 분노',
availableAfterBattleIds: [campBattleIds.fourth],
unitIds: ['liu-bei', 'zhang-fei'],
bondId: 'liu-bei__zhang-fei',
rewardExp: 18,
lines: [
'장비: 장각을 꺾었는데도 속이 시원하지만은 않습니다. 아직 백성을 괴롭히는 놈들이 많습니다.',
'유비: 그 분노가 백성을 향하지 않고 악한 자에게만 향하도록 붙잡아야 하오.',
'장비: 형님이 길을 가리키면, 제 창은 그 길 밖으로 나가지 않겠습니다.'
],
choices: [
{
id: 'temper-anger',
label: '분노를 다스리게 한다',
response: '장비는 분노를 창끝에만 싣고 마음에는 남기지 않겠다고 했다.',
rewardExp: 7
},
{
id: 'promise-next-vanguard',
label: '다음 선봉을 약속한다',
response: '장비는 반동탁의 길에서도 가장 먼저 적진을 흔들겠다고 웃었다.',
rewardExp: 6
}
]
},
{
id: 'guan-zhang-after-zhang-jue',
title: '맹장들의 전공',
availableAfterBattleIds: [campBattleIds.fourth],
unitIds: ['guan-yu', 'zhang-fei'],
bondId: 'guan-yu__zhang-fei',
rewardExp: 16,
lines: [
'장비: 운장 형님, 오늘은 제가 더 크게 적진을 흔든 것 같습니다.',
'관우: 전공을 다투는 마음이 백성을 구하는 데 쓰인다면 나쁠 것이 없다.',
'장비: 그럼 다음 전장에서도 누가 더 크게 이름을 떨치는지 봅시다!'
],
choices: [
{
id: 'clean-rivalry',
label: '깨끗한 경쟁을 인정한다',
response: '두 맹장은 웃으며 전공 경쟁을 다음 전장의 활력으로 삼았다.',
rewardExp: 6
},
{
id: 'brothers-before-glory',
label: '명성보다 형제를 앞세운다',
response: '관우와 장비는 전공보다 서로의 생존을 먼저 지키기로 했다.',
rewardExp: 7
}
]
},
{
id: 'liu-guan-after-sishui',
title: '공손찬의 깃발 아래',
availableAfterBattleIds: [campBattleIds.fifth],
unitIds: ['liu-bei', 'guan-yu'],
bondId: 'liu-bei__guan-yu',
rewardExp: 18,
lines: [
'유비: 공손찬의 진영은 크고, 제후들의 생각은 저마다 다르오. 우리 뜻을 잃지 않는 것이 더 어려워졌소.',
'관우: 큰 깃발 아래 설수록 작은 의리를 더 단단히 붙잡아야 합니다.',
'유비: 운장의 말이 옳소. 남의 군영에 있어도 우리가 지킬 길은 변하지 않소.'
],
choices: [
{
id: 'hold-oath-in-coalition',
label: '연합 속에서도 맹세를 지킨다',
response: '관우는 도원에서 맺은 뜻을 낯선 군영에서도 흐리지 않겠다고 답했다.',
rewardExp: 7
},
{
id: 'observe-warlords',
label: '제후들의 속내를 살핀다',
response: '관우는 싸움만이 아니라 사람의 뜻을 살피는 눈도 필요하다고 받아들였다.',
rewardExp: 6
}
]
},
{
id: 'liu-zhang-after-sishui',
title: '낯선 군영의 술잔',
availableAfterBattleIds: [campBattleIds.fifth],
unitIds: ['liu-bei', 'zhang-fei'],
bondId: 'liu-bei__zhang-fei',
rewardExp: 18,
lines: [
'장비: 형님, 공손찬 군영엔 말도 많고 술도 많고, 괜히 시비 걸 놈들도 많습니다.',
'유비: 익덕의 기개는 좋으나, 지금은 싸워야 할 적과 참아야 할 말을 구분해야 하오.',
'장비: 하하, 형님이 그렇게 말하면 술잔은 들되 주먹은 참아 보겠습니다.'
],
choices: [
{
id: 'restrain-in-camp',
label: '군영 안의 절제를 당부한다',
response: '장비는 시비보다 형님의 체면을 먼저 지키겠다고 약속했다.',
rewardExp: 7
},
{
id: 'trust-his-honesty',
label: '솔직함을 믿어 준다',
response: '믿음을 받은 장비는 낯선 제후들 사이에서도 형님의 편에 서겠다고 웃었다.',
rewardExp: 6
}
]
},
{
id: 'guan-zhang-after-sishui',
title: '연합군의 두 맹장',
availableAfterBattleIds: [campBattleIds.fifth],
unitIds: ['guan-yu', 'zhang-fei'],
bondId: 'guan-yu__zhang-fei',
rewardExp: 16,
lines: [
'관우: 이곳에는 이름난 장수도 많고, 허세뿐인 자도 많다.',
'장비: 그럼 우리가 전장에서 보여 주면 되지요. 말보다 칼과 창이 빠릅니다.',
'관우: 다만 형님의 이름이 먼저 서야 한다. 우리의 무공은 그 뜻을 받치는 것이니.'
],
choices: [
{
id: 'raise-liu-bei-name',
label: '유비의 이름을 세운다',
response: '두 사람은 자신의 전공보다 유비의 명성을 먼저 드러내기로 뜻을 모았다.',
rewardExp: 7
},
{
id: 'test-famous-generals',
label: '이름난 장수들을 의식한다',
response: '장비의 승부욕과 관우의 침착함이 묘한 균형을 이루었다.',
rewardExp: 5
}
]
}
];
@@ -227,6 +563,7 @@ export class CampScene extends Phaser.Scene {
this.campaign = getCampaignState();
this.report = this.campaign.firstBattleReport ?? getFirstBattleReport() ?? this.createFallbackReport();
this.selectedUnitId = this.currentUnits().find((unit) => unit.faction === 'ally')?.id ?? 'liu-bei';
this.selectedDialogueId = this.availableCampDialogues()[0]?.id ?? campDialogues[0].id;
soundDirector.playMusic('militia-theme');
const { width, height } = this.scale;
@@ -234,7 +571,7 @@ export class CampScene extends Phaser.Scene {
this.add.rectangle(0, 0, width, height, 0x06090d, 0.54).setOrigin(0);
this.add.rectangle(0, 0, width, 84, 0x06090d, 0.52).setOrigin(0);
this.add.text(42, 28, '탁현 의용군 군영', {
this.add.text(42, 28, this.currentCampTitle(), {
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
fontSize: '30px',
color: '#f2e3bf',
@@ -278,7 +615,39 @@ export class CampScene extends Phaser.Scene {
const achieved = this.report.objectives.filter((objective) => objective.achieved).length;
const gold = this.campaign?.gold ?? this.report.rewardGold;
return `첫 전투 ${this.report.turnNumber}턴 승리 · 군자금 ${gold} · 목표 ${achieved}/${this.report.objectives.length} · 격파 ${this.report.defeatedEnemies}/${this.report.totalEnemies}`;
return `${this.report.battleTitle} ${this.report.turnNumber}턴 승리 · 군자금 ${gold} · 목표 ${achieved}/${this.report.objectives.length} · 격파 ${this.report.defeatedEnemies}/${this.report.totalEnemies}`;
}
private currentCampTitle() {
const battleId = this.currentCampBattleId();
if (battleId === campBattleIds.fifth) {
return '공손찬 진영 군영';
}
if (battleId === campBattleIds.fourth) {
return '광종 토벌 후 군영';
}
if (battleId === campBattleIds.third) {
return '광종 구원로 군영';
}
if (battleId === campBattleIds.second) {
return '황건 잔당 추격 군영';
}
return '탁현 의용군 군영';
}
private currentCampBattleId() {
return this.campaign?.latestBattleId ?? this.report?.battleId ?? defaultBattleScenario.id;
}
private availableCampDialogues() {
const battleId = this.currentCampBattleId();
const dialogues = campDialogues.filter((dialogue) => dialogue.availableAfterBattleIds.includes(battleId));
return dialogues.length > 0 ? dialogues : campDialogues.filter((dialogue) => dialogue.availableAfterBattleIds.includes(defaultBattleScenario.id));
}
private completedAvailableDialogues() {
const completed = this.completedCampDialogues();
return this.availableCampDialogues().filter((dialogue) => completed.includes(dialogue.id));
}
private renderStaticButtons() {
@@ -482,11 +851,12 @@ export class CampScene extends Phaser.Scene {
bg.setOrigin(0);
bg.setDepth(depth);
bg.setStrokeStyle(1, palette.gold, 0.4);
const completedDialogues = this.completedCampDialogues().length;
const availableDialogues = this.availableCampDialogues();
const completedDialogues = this.completedAvailableDialogues().length;
const inventory = this.inventoryLabels().join(', ');
const reward = getSortieFlow(this.campaign?.latestBattleId).rewardHint;
this.trackSortie(this.add.text(x + 16, y + 10, reward, this.textStyle(13, '#f2e3bf', true))).setDepth(depth + 1);
this.trackSortie(this.add.text(x + 16, y + 30, `현재 준비: 대화 ${completedDialogues}/${campDialogues.length} · 보유 ${inventory || '없음'}`, this.textStyle(12, '#d4dce6'))).setDepth(depth + 1);
this.trackSortie(this.add.text(x + 16, y + 30, `현재 준비: 대화 ${completedDialogues}/${availableDialogues.length} · 보유 ${inventory || '없음'}`, this.textStyle(12, '#d4dce6'))).setDepth(depth + 1);
}
private nextSortieBriefing() {
@@ -503,7 +873,8 @@ export class CampScene extends Phaser.Scene {
const liuBei = units.find((unit) => unit.id === 'liu-bei');
const injured = units.filter((unit) => unit.hp < unit.maxHp);
const supplyCount = campSupplies.reduce((total, supply) => total + this.inventoryAmount(supply.label), 0);
const completedDialogues = this.completedCampDialogues().length;
const availableDialogues = this.availableCampDialogues();
const completedDialogues = this.completedAvailableDialogues().length;
return [
{
label: '유비 생존',
@@ -522,8 +893,8 @@ export class CampScene extends Phaser.Scene {
},
{
label: '공명 대화',
complete: completedDialogues >= campDialogues.length,
detail: `${completedDialogues}/${campDialogues.length} 완료`
complete: availableDialogues.length === 0 || completedDialogues >= availableDialogues.length,
detail: `${completedDialogues}/${availableDialogues.length} 완료`
},
{
label: '장비 상태',
@@ -700,7 +1071,12 @@ export class CampScene extends Phaser.Scene {
this.track(this.add.text(x + 24, y + 22, '장수 대화', this.textStyle(24, '#f2e3bf', true)));
this.track(this.add.text(x + 24, y + 56, '출진 전 대화에서 선택지를 고르면 해당 장수들의 공명 경험치가 오릅니다.', this.textStyle(14, '#d4dce6')));
campDialogues.forEach((dialogue, index) => {
const dialogues = this.availableCampDialogues();
if (!dialogues.some((dialogue) => dialogue.id === this.selectedDialogueId)) {
this.selectedDialogueId = dialogues[0]?.id ?? campDialogues[0].id;
}
dialogues.forEach((dialogue, index) => {
const completed = this.completedCampDialogues().includes(dialogue.id);
const rowY = y + 96 + index * 64;
const selected = this.selectedDialogueId === dialogue.id;
@@ -723,7 +1099,16 @@ export class CampScene extends Phaser.Scene {
}
private renderSelectedDialogue(x: number, y: number, width: number, height: number) {
const dialogue = campDialogues.find((candidate) => candidate.id === this.selectedDialogueId) ?? campDialogues[0];
const dialogues = this.availableCampDialogues();
const dialogue = dialogues.find((candidate) => candidate.id === this.selectedDialogueId) ?? dialogues[0];
if (!dialogue) {
const empty = this.track(this.add.rectangle(x, y, width, height, 0x0d141c, 0.92));
empty.setOrigin(0);
empty.setStrokeStyle(1, palette.blue, 0.42);
this.track(this.add.text(x + 18, y + 16, '대화 없음', this.textStyle(20, '#f2e3bf', true)));
this.track(this.add.text(x + 18, y + 54, '현재 군영에서 가능한 장수 대화가 없습니다.', this.textStyle(14, '#9fb0bf')));
return;
}
const completed = this.completedCampDialogues().includes(dialogue.id);
const bg = this.track(this.add.rectangle(x, y, width, height, 0x0d141c, 0.92));
bg.setOrigin(0);
@@ -1207,6 +1592,10 @@ export class CampScene extends Phaser.Scene {
sortieVisible: this.sortieObjects.length > 0,
selectedUnitId: this.selectedUnitId,
selectedDialogueId: this.selectedDialogueId,
campBattleId: this.currentCampBattleId(),
campTitle: this.currentCampTitle(),
availableDialogueIds: this.availableCampDialogues().map((dialogue) => dialogue.id),
completedAvailableDialogues: this.completedAvailableDialogues().map((dialogue) => dialogue.id),
campaign: this.campaign
? {
step: this.campaign.step,