fix: refine turn completion and interaction flow
This commit is contained in:
@@ -204,11 +204,16 @@ import {
|
||||
import { clearCampaignBattleSavesForSlot } from '../state/battleSaveKeys';
|
||||
import { releaseTextureSource } from '../render/loaderLifecycle';
|
||||
import { palette } from '../ui/palette';
|
||||
import { startLazyScene } from './lazyScenes';
|
||||
import { startLazyScene, type LazySceneKey } from './lazyScenes';
|
||||
|
||||
const campLegacyCanvasWidth = 1280;
|
||||
const campLegacyCanvasHeight = 720;
|
||||
const campFhdUiScale = 1.5;
|
||||
const campNoticeMinimumHoldMs = 2600;
|
||||
const campNoticeMaximumHoldMs = 9000;
|
||||
const campNoticeBaseHoldMs = 1800;
|
||||
const campNoticeCharacterHoldMs = 55;
|
||||
const campNoticeParagraphPauseMs = 500;
|
||||
|
||||
type CampTab = 'status' | 'dialogue' | 'visit' | 'supplies' | 'equipment' | 'progress';
|
||||
|
||||
@@ -11383,6 +11388,7 @@ export class CampScene extends Phaser.Scene {
|
||||
private openSortieImprovementOnCreate = false;
|
||||
private retrySortieBattleId?: BattleScenarioId;
|
||||
private skipIntroStoryForSortie = false;
|
||||
private navigationPending = false;
|
||||
|
||||
constructor() {
|
||||
super('CampScene');
|
||||
@@ -11398,6 +11404,7 @@ export class CampScene extends Phaser.Scene {
|
||||
|
||||
create() {
|
||||
this.events.once(Phaser.Scenes.Events.SHUTDOWN, () => this.releaseOwnedCampTextures());
|
||||
this.navigationPending = false;
|
||||
this.contentObjects = [];
|
||||
this.campRosterPage = 0;
|
||||
this.campRosterLayout = undefined;
|
||||
@@ -12958,11 +12965,8 @@ export class CampScene extends Phaser.Scene {
|
||||
private saveCampToSlot(slot: number) {
|
||||
soundDirector.playSelect();
|
||||
const returnToSortiePrep = this.sortieObjects.length > 0;
|
||||
this.sortieSwapUndoState = undefined;
|
||||
this.sortiePinnedSwapCandidateUnitId = undefined;
|
||||
this.sortieRecommendationUndoState = undefined;
|
||||
this.closeSortieRecommendationBrowserState();
|
||||
this.sortiePresetUndoState = undefined;
|
||||
this.closeSortiePresetBrowserState();
|
||||
this.hideCampSaveSlotPanel();
|
||||
clearCampaignBattleSavesForSlot(slot);
|
||||
@@ -13536,6 +13540,9 @@ export class CampScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
private handleSortieEnterKey(event: KeyboardEvent) {
|
||||
if (event.repeat || this.navigationPending) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
this.sortieObjects.length === 0 ||
|
||||
this.victoryRewardObjects.length > 0 ||
|
||||
@@ -19757,6 +19764,9 @@ export class CampScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
private startVictoryStory() {
|
||||
if (this.navigationPending) {
|
||||
return;
|
||||
}
|
||||
const flow = this.currentSortieFlow();
|
||||
if (flow.nextBattleId && !this.hasCurrentSortieOrderSelection()) {
|
||||
this.sortiePrepStep = 'formation';
|
||||
@@ -19785,7 +19795,7 @@ export class CampScene extends Phaser.Scene {
|
||||
|
||||
if (this.isFinalEpilogueFlow(flow) && this.campaign?.step === flow.campaignStep) {
|
||||
this.hideSortiePrep();
|
||||
void startLazyScene(this, 'EndingScene');
|
||||
this.startCampNavigation('EndingScene');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -19796,16 +19806,14 @@ export class CampScene extends Phaser.Scene {
|
||||
this.showCampNotice(flow.unavailableNotice ?? '현재 군영 정비를 먼저 마치세요. 대화, 보급, 편성을 점검할 수 있습니다.');
|
||||
return;
|
||||
}
|
||||
markCampaignStep(flow.campaignStep);
|
||||
this.campaign = getCampaignState();
|
||||
void startLazyScene(this, 'StoryScene', {
|
||||
this.startCampNavigationWithCampaignStep(flow.campaignStep, 'StoryScene', {
|
||||
pages: flow.pages,
|
||||
nextScene: flow.campaignStep === 'ending-complete' ? 'EndingScene' : 'CampScene'
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (flow.pages.length > 0) {
|
||||
void startLazyScene(this, 'StoryScene', {
|
||||
this.startCampNavigation('StoryScene', {
|
||||
pages: flow.pages,
|
||||
nextScene: 'CampScene'
|
||||
});
|
||||
@@ -19823,7 +19831,6 @@ export class CampScene extends Phaser.Scene {
|
||||
return;
|
||||
}
|
||||
|
||||
markCampaignStep(flow.campaignStep);
|
||||
const selectedSortieUnitIds = [...this.selectedSortieUnitIds];
|
||||
const sortieFormationAssignments = { ...this.sortieFormationAssignments };
|
||||
const sortieItemAssignments = this.cloneSortieItemAssignments(this.sortieItemAssignments);
|
||||
@@ -19833,7 +19840,7 @@ export class CampScene extends Phaser.Scene {
|
||||
: undefined;
|
||||
const sortieRecommendation = this.currentSortieRecommendationSelection();
|
||||
if (this.skipIntroStoryForSortie) {
|
||||
void startLazyScene(this, 'BattleScene', {
|
||||
this.startCampNavigationWithCampaignStep(flow.campaignStep, 'BattleScene', {
|
||||
battleId: flow.nextBattleId,
|
||||
selectedSortieUnitIds,
|
||||
sortieFormationAssignments,
|
||||
@@ -19845,7 +19852,7 @@ export class CampScene extends Phaser.Scene {
|
||||
return;
|
||||
}
|
||||
|
||||
void startLazyScene(this, 'StoryScene', {
|
||||
this.startCampNavigationWithCampaignStep(flow.campaignStep, 'StoryScene', {
|
||||
pages: flow.pages,
|
||||
nextScene: 'BattleScene',
|
||||
nextSceneData: {
|
||||
@@ -19860,6 +19867,46 @@ export class CampScene extends Phaser.Scene {
|
||||
});
|
||||
}
|
||||
|
||||
private startCampNavigationWithCampaignStep(
|
||||
step: CampaignStep,
|
||||
key: LazySceneKey,
|
||||
data?: Record<string, unknown>
|
||||
) {
|
||||
if (this.navigationPending) {
|
||||
return;
|
||||
}
|
||||
|
||||
const previousStep = getCampaignState().step;
|
||||
markCampaignStep(step);
|
||||
this.campaign = getCampaignState();
|
||||
this.startCampNavigation(key, data, () => {
|
||||
if (getCampaignState().step === step) {
|
||||
markCampaignStep(previousStep);
|
||||
}
|
||||
this.campaign = getCampaignState();
|
||||
});
|
||||
}
|
||||
|
||||
private startCampNavigation(
|
||||
key: LazySceneKey,
|
||||
data?: Record<string, unknown>,
|
||||
recoverState?: () => void
|
||||
) {
|
||||
if (this.navigationPending) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.navigationPending = true;
|
||||
void startLazyScene(this, key, data).catch((error: unknown) => {
|
||||
this.navigationPending = false;
|
||||
recoverState?.();
|
||||
console.error(`[CampScene] ${key} 전환에 실패했습니다.`, error);
|
||||
if (this.sys.isActive()) {
|
||||
this.showCampNotice('다음 장면을 불러오지 못했습니다. 잠시 후 다시 시도하십시오.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private isFinalEpilogueFlow(flow = this.currentSortieFlow()) {
|
||||
return flow.campaignStep === 'ending-complete' && !flow.nextBattleId;
|
||||
}
|
||||
@@ -22881,8 +22928,7 @@ export class CampScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
private showVisitReward(choice: CampVisitChoice) {
|
||||
this.showCampNotice(`${choice.label} · ${this.visitRewardText(choice)}`);
|
||||
this.time.delayedCall(120, () => this.showCampNotice(choice.response));
|
||||
this.showCampNotice(`획득 내역 · ${choice.label} · ${this.visitRewardText(choice)}\n응답 · ${choice.response}`);
|
||||
}
|
||||
|
||||
private visitRewardText(choice: CampVisitChoice) {
|
||||
@@ -22919,8 +22965,7 @@ export class CampScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
private showDialogueReward(dialogue: CampDialogue, choice: CampDialogueChoice, rewardExp: number) {
|
||||
this.showCampNotice(`${choice.label} · 공명 +${rewardExp}`);
|
||||
this.time.delayedCall(120, () => this.showCampNotice(choice.response));
|
||||
this.showCampNotice(`획득 내역 · ${choice.label} · 공명 +${rewardExp}\n응답 · ${choice.response}`);
|
||||
}
|
||||
|
||||
private showCampNotice(message: string) {
|
||||
@@ -22928,10 +22973,8 @@ export class CampScene extends Phaser.Scene {
|
||||
this.dialogueObjects.forEach((object) => object.active && object.destroy());
|
||||
this.dialogueObjects = [];
|
||||
const depth = this.sortieObjects.length > 0 ? 72 : 30;
|
||||
const noticeWidth = Math.min(640, Math.max(360, message.length * 13));
|
||||
const box = this.scaleLegacyCampUi(this.add.rectangle(campLegacyCanvasWidth / 2, 104, noticeWidth, 50, 0x101820, 0.96));
|
||||
box.setStrokeStyle(2, palette.gold, 0.84);
|
||||
box.setDepth(depth);
|
||||
const longestLineLength = Math.max(...message.split('\n').map((line) => line.length));
|
||||
const noticeWidth = Math.min(760, Math.max(420, longestLineLength * 13));
|
||||
const text = this.scaleLegacyCampUi(this.add.text(campLegacyCanvasWidth / 2, 104, message, {
|
||||
...this.textStyle(14, '#f2e3bf', true),
|
||||
align: 'center',
|
||||
@@ -22939,12 +22982,17 @@ export class CampScene extends Phaser.Scene {
|
||||
}));
|
||||
text.setOrigin(0.5);
|
||||
text.setDepth(depth + 1);
|
||||
text.setLineSpacing(5);
|
||||
const noticeHeight = Math.max(56, text.height + 28);
|
||||
const box = this.scaleLegacyCampUi(this.add.rectangle(campLegacyCanvasWidth / 2, 104, noticeWidth, noticeHeight, 0x101820, 0.96));
|
||||
box.setStrokeStyle(2, palette.gold, 0.84);
|
||||
box.setDepth(depth);
|
||||
const noticeObjects: Phaser.GameObjects.GameObject[] = [box, text];
|
||||
this.dialogueObjects = noticeObjects;
|
||||
this.tweens.add({
|
||||
targets: noticeObjects,
|
||||
alpha: 0,
|
||||
delay: 1300,
|
||||
delay: this.campNoticeHoldDuration(message),
|
||||
duration: 320,
|
||||
onComplete: () => {
|
||||
noticeObjects.forEach((object) => object.active && object.destroy());
|
||||
@@ -22955,6 +23003,18 @@ export class CampScene extends Phaser.Scene {
|
||||
});
|
||||
}
|
||||
|
||||
private campNoticeHoldDuration(message: string) {
|
||||
const readableCharacterCount = message.replace(/\s/g, '').length;
|
||||
const paragraphCount = Math.max(1, message.split('\n').filter((line) => line.trim().length > 0).length);
|
||||
return Phaser.Math.Clamp(
|
||||
campNoticeBaseHoldMs +
|
||||
readableCharacterCount * campNoticeCharacterHoldMs +
|
||||
(paragraphCount - 1) * campNoticeParagraphPauseMs,
|
||||
campNoticeMinimumHoldMs,
|
||||
campNoticeMaximumHoldMs
|
||||
);
|
||||
}
|
||||
|
||||
private selectedUnit() {
|
||||
return this.currentUnits().find((unit) => unit.id === this.selectedUnitId);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user