feat: add first-victory camp exploration
This commit is contained in:
@@ -38,6 +38,7 @@ import {
|
||||
firstPursuitScoutVisitId,
|
||||
resolveFirstPursuitScoutMemory
|
||||
} from '../data/firstPursuitScoutMemory';
|
||||
import { firstPursuitScoutVisitDefinition } from '../data/firstPursuitScoutVisit';
|
||||
import {
|
||||
findCityStayAfterBattle,
|
||||
type CityEquipmentOffer,
|
||||
@@ -8401,33 +8402,13 @@ const campDialogues: CampDialogue[] = [
|
||||
|
||||
const campVisits: CampVisitDefinition[] = [
|
||||
{
|
||||
id: 'first-pursuit-scout-tent',
|
||||
title: '북문 추격로 정찰',
|
||||
location: '탁현 북문 정찰막',
|
||||
availableAfterBattleIds: [campBattleIds.first],
|
||||
bondId: 'liu-bei__jian-yong',
|
||||
description: '간옹과 황건 잔당의 두 갈래 길을 살피고 정찰 기록을 출진 준비에 올립니다.',
|
||||
lines: [
|
||||
'간옹: 발자국이 강가와 마을길로 갈렸습니다.',
|
||||
'유비: 매복도 찾고 백성의 길도 지켜야 하오.',
|
||||
'정찰 지도에 두 길을 나란히 표시했습니다.'
|
||||
],
|
||||
choices: [
|
||||
{
|
||||
id: 'trace-river-ambush',
|
||||
label: '강가 매복 흔적을 쫓는다',
|
||||
response: '간옹: 강변의 꺾인 갈대와 버려진 횃불을 표시했습니다. 상처약도 챙겨 이 기록을 출진 준비에 올리겠습니다.',
|
||||
bondExp: 10,
|
||||
itemRewards: ['상처약 1']
|
||||
},
|
||||
{
|
||||
id: 'mark-village-relief',
|
||||
label: '마을 구호 길을 표시한다',
|
||||
response: '간옹: 피난민이 돌아올 샛길과 군량을 나눌 곳을 적었습니다. 콩 한 자루와 이 기록을 다음 전투 준비에 보태겠습니다.',
|
||||
bondExp: 10,
|
||||
itemRewards: ['콩 1']
|
||||
}
|
||||
]
|
||||
...firstPursuitScoutVisitDefinition,
|
||||
availableAfterBattleIds: [...firstPursuitScoutVisitDefinition.availableAfterBattleIds],
|
||||
lines: [...firstPursuitScoutVisitDefinition.lines],
|
||||
choices: firstPursuitScoutVisitDefinition.choices.map((choice) => ({
|
||||
...choice,
|
||||
itemRewards: [...choice.itemRewards]
|
||||
}))
|
||||
},
|
||||
{
|
||||
id: 'jingzhou-scholar-rumors',
|
||||
@@ -11407,6 +11388,7 @@ export class CampScene extends Phaser.Scene {
|
||||
private campDialogueChoiceButtons: Record<string, Phaser.GameObjects.Rectangle> = {};
|
||||
private campDialogueBodyText?: Phaser.GameObjects.Text;
|
||||
private selectedVisitId = campVisits[0].id;
|
||||
private firstPursuitExplorationButton?: Phaser.GameObjects.Rectangle;
|
||||
private equipmentInventoryPage = 0;
|
||||
private equipmentPanelBackground?: Phaser.GameObjects.Rectangle;
|
||||
private equipmentInventoryPreviousButton?: Phaser.GameObjects.Rectangle;
|
||||
@@ -11531,6 +11513,7 @@ export class CampScene extends Phaser.Scene {
|
||||
this.campDialogueRowButtons = {};
|
||||
this.campDialogueChoiceButtons = {};
|
||||
this.campDialogueBodyText = undefined;
|
||||
this.firstPursuitExplorationButton = undefined;
|
||||
this.dialogueObjects = [];
|
||||
this.sortieObjects = [];
|
||||
this.sortieComparisonPanelView = undefined;
|
||||
@@ -11642,6 +11625,14 @@ export class CampScene extends Phaser.Scene {
|
||||
} else if (this.activeTab === 'city') {
|
||||
this.activeTab = 'status';
|
||||
}
|
||||
const pendingFirstPursuitScoutVisit = this.availableCampVisits().some(
|
||||
(visit) =>
|
||||
visit.id === firstPursuitScoutVisitId &&
|
||||
!this.completedCampVisits().includes(visit.id)
|
||||
);
|
||||
if (!cityStay && pendingFirstPursuitScoutVisit && this.activeTab === 'status') {
|
||||
this.activeTab = 'visit';
|
||||
}
|
||||
soundDirector.playSoundscape({
|
||||
musicKey: this.campSoundscape.music.trackKey,
|
||||
ambienceKey: this.campSoundscape.ambience.trackKey,
|
||||
@@ -13883,7 +13874,7 @@ export class CampScene extends Phaser.Scene {
|
||||
: width < 70
|
||||
? 'N'
|
||||
: 'NEW';
|
||||
const newBadge = tab === 'city' || tab === 'dialogue' || tab === 'supplies' || tab === 'equipment'
|
||||
const newBadge = tab === 'city' || tab === 'dialogue' || tab === 'visit' || tab === 'supplies' || tab === 'equipment'
|
||||
? this.scaleLegacyCampUi(this.add.text(x + width / 2 - 10, y - 17, badgeLabel, {
|
||||
...this.textStyle(9, '#fff2b8', true),
|
||||
backgroundColor: '#8a3f25',
|
||||
@@ -13911,6 +13902,9 @@ export class CampScene extends Phaser.Scene {
|
||||
const active = this.activeTab === tab;
|
||||
const pendingFirstBattleFollowup = tab === 'dialogue' && this.hasPendingFirstBattleCampFollowup();
|
||||
const pendingCamaraderieMemory = tab === 'dialogue' && this.hasPendingFirstBattleCamaraderieMemory();
|
||||
const pendingCampVisit = tab === 'visit' && this.availableCampVisits().some(
|
||||
(visit) => !this.completedCampVisits().includes(visit.id)
|
||||
);
|
||||
bg.setFillStyle(active ? 0x2b4052 : hovered ? 0x243747 : 0x182431, active || hovered ? 0.98 : 0.94);
|
||||
bg.setStrokeStyle(active || hovered ? 2 : 1, active || hovered ? accentColor : palette.blue, active ? 0.96 : hovered ? 0.82 : 0.62);
|
||||
text.setColor(active || hovered ? '#fff2b8' : '#f2e3bf');
|
||||
@@ -13923,6 +13917,7 @@ export class CampScene extends Phaser.Scene {
|
||||
newBadge?.setVisible(Boolean(
|
||||
(tab === 'city' && this.hasPendingCityStayActions()) ||
|
||||
(tab === 'dialogue' && (pendingFirstBattleFollowup || pendingCamaraderieMemory)) ||
|
||||
pendingCampVisit ||
|
||||
(tab === 'supplies' && pendingCategories.has('supplies')) ||
|
||||
(tab === 'equipment' && pendingCategories.has('equipment'))
|
||||
));
|
||||
@@ -23366,6 +23361,48 @@ export class CampScene extends Phaser.Scene {
|
||||
return;
|
||||
}
|
||||
|
||||
if (visit.id === firstPursuitScoutVisitId) {
|
||||
const guide = this.track(this.add.rectangle(x + 18, y + height - 102, width - 36, 84, 0x151f2a, 0.96));
|
||||
guide.setOrigin(0);
|
||||
guide.setStrokeStyle(1, palette.blue, 0.62);
|
||||
this.track(this.add.text(
|
||||
x + 32,
|
||||
y + height - 90,
|
||||
'유비를 움직여 간옹에게 직접 말을 거세요.',
|
||||
this.textStyle(13, '#d4dce6', true)
|
||||
));
|
||||
this.track(this.add.text(
|
||||
x + 32,
|
||||
y + height - 68,
|
||||
'WASD·방향키 이동 · E/Space 대화',
|
||||
this.textStyle(11, '#9fb0bf')
|
||||
));
|
||||
|
||||
const buttonY = y + height - 36;
|
||||
const button = this.track(this.add.rectangle(x + width / 2, buttonY, width - 64, 34, 0x4a371d, 0.98));
|
||||
button.setStrokeStyle(2, palette.gold, 0.94);
|
||||
button.setInteractive({ useHandCursor: true });
|
||||
const openExploration = () => {
|
||||
soundDirector.playSelect();
|
||||
this.startCampNavigation('CampVisitExplorationScene', { visitId: visit.id });
|
||||
};
|
||||
button.on('pointerover', () => button.setFillStyle(0x654c25, 1));
|
||||
button.on('pointerout', () => button.setFillStyle(0x4a371d, 0.98));
|
||||
button.on('pointerdown', openExploration);
|
||||
this.firstPursuitExplorationButton = button;
|
||||
|
||||
const label = this.track(this.add.text(
|
||||
x + width / 2,
|
||||
buttonY,
|
||||
'정찰막으로 이동',
|
||||
this.textStyle(14, '#fff2b8', true)
|
||||
));
|
||||
label.setOrigin(0.5);
|
||||
label.setInteractive({ useHandCursor: true });
|
||||
label.on('pointerdown', openExploration);
|
||||
return;
|
||||
}
|
||||
|
||||
visit.choices.forEach((choice, index) => {
|
||||
const choiceY = y + height - 80 + index * 42;
|
||||
const button = this.track(this.add.rectangle(x + width / 2, choiceY, width - 36, 34, 0x1a2630, 0.96));
|
||||
@@ -24904,6 +24941,7 @@ export class CampScene extends Phaser.Scene {
|
||||
this.campDialogueRowButtons = {};
|
||||
this.campDialogueChoiceButtons = {};
|
||||
this.campDialogueBodyText = undefined;
|
||||
this.firstPursuitExplorationButton = undefined;
|
||||
this.campRosterLayout = undefined;
|
||||
this.cityPanelBackground = undefined;
|
||||
this.cityExploreButton = undefined;
|
||||
@@ -25227,6 +25265,15 @@ export class CampScene extends Phaser.Scene {
|
||||
visitId: firstPursuitScoutVisitId,
|
||||
available: Boolean(firstPursuitScoutVisit),
|
||||
completed: this.completedCampVisits().includes(firstPursuitScoutVisitId),
|
||||
mode: this.completedCampVisits().includes(firstPursuitScoutVisitId)
|
||||
? 'completed'
|
||||
: firstPursuitScoutVisit
|
||||
? 'walk-to-npc'
|
||||
: 'unavailable',
|
||||
explorationButtonBounds: this.sortieInteractiveObjectBoundsDebug(
|
||||
this.firstPursuitExplorationButton
|
||||
),
|
||||
explorationInteractive: Boolean(this.firstPursuitExplorationButton?.input?.enabled),
|
||||
choiceId: this.campaign?.campVisitChoiceIds[firstPursuitScoutVisitId] ?? null,
|
||||
choices: firstPursuitScoutVisit?.choices.map((choice) => ({
|
||||
id: choice.id,
|
||||
|
||||
Reference in New Issue
Block a user