fix: isolate camp save modal input
This commit is contained in:
@@ -678,12 +678,21 @@ async function verifyPreparationModalKeyboardIsolation(page) {
|
||||
const campaignModule = await import(
|
||||
'/heros_web/src/game/state/campaignState.ts'
|
||||
);
|
||||
const manualSaveModule = await import(
|
||||
'/heros_web/src/game/state/manualSaveStorage.ts'
|
||||
);
|
||||
const snapshot = campaignModule.getCampaignState();
|
||||
campaignModule.saveCampaignState(snapshot, 2);
|
||||
campaignModule.saveCampaignState(snapshot, 1);
|
||||
const currentCampaign =
|
||||
campaignModule.getCampaignState();
|
||||
manualSaveModule.writeManualSave(2, {
|
||||
campaign: currentCampaign,
|
||||
context: 'camp'
|
||||
});
|
||||
const scene =
|
||||
window.__HEROS_GAME__?.scene.getScene('CampScene');
|
||||
scene.campaign = campaignModule.getCampaignState();
|
||||
scene.campaign = currentCampaign;
|
||||
scene.showSortiePrep();
|
||||
});
|
||||
await page.waitForFunction(
|
||||
@@ -706,7 +715,15 @@ async function verifyPreparationModalKeyboardIsolation(page) {
|
||||
assert.equal(
|
||||
camp.campaign.activeSaveSlot,
|
||||
1,
|
||||
'The 1 key must save to slot 1 instead of selecting the first preparation card.'
|
||||
'A protected manual save must preserve the active automatic slot.'
|
||||
);
|
||||
let manualSave =
|
||||
await readCampManualSave(page, 1);
|
||||
assert.equal(manualSave?.context, 'camp');
|
||||
assert.equal(
|
||||
manualSave?.selectedPriorityId,
|
||||
'companion',
|
||||
'The 1 key must save the current preparation to protected manual record 1.'
|
||||
);
|
||||
|
||||
await selectPreparationCard(page, 'equipment');
|
||||
@@ -718,8 +735,15 @@ async function verifyPreparationModalKeyboardIsolation(page) {
|
||||
assertPreparationSelection(camp, 'equipment');
|
||||
assert.equal(
|
||||
camp.campaign.activeSaveSlot,
|
||||
3,
|
||||
'The 3 key must save to slot 3 instead of selecting the third preparation card.'
|
||||
1,
|
||||
'Manual record 3 must not switch the active automatic slot.'
|
||||
);
|
||||
manualSave = await readCampManualSave(page, 3);
|
||||
assert.equal(manualSave?.context, 'camp');
|
||||
assert.equal(
|
||||
manualSave?.selectedPriorityId,
|
||||
'equipment',
|
||||
'The 3 key must save to protected manual record 3 instead of selecting the third preparation card.'
|
||||
);
|
||||
|
||||
await selectPreparationCard(page, 'information');
|
||||
@@ -758,8 +782,15 @@ async function verifyPreparationModalKeyboardIsolation(page) {
|
||||
assertPreparationSelection(camp, 'information');
|
||||
assert.equal(
|
||||
camp.campaign.activeSaveSlot,
|
||||
2,
|
||||
'Enter must confirm the pending overwrite instead of confirming the focused preparation card.'
|
||||
1,
|
||||
'Confirming a manual overwrite must preserve the active automatic slot.'
|
||||
);
|
||||
manualSave = await readCampManualSave(page, 2);
|
||||
assert.equal(manualSave?.context, 'camp');
|
||||
assert.equal(
|
||||
manualSave?.selectedPriorityId,
|
||||
'information',
|
||||
'Enter must overwrite protected manual record 2 instead of confirming the focused preparation card.'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2199,6 +2230,30 @@ async function readCampSaveModalState(page) {
|
||||
});
|
||||
}
|
||||
|
||||
async function readCampManualSave(page, slot) {
|
||||
return page.evaluate(async (requestedSlot) => {
|
||||
const manualSaveModule = await import(
|
||||
'/heros_web/src/game/state/manualSaveStorage.ts'
|
||||
);
|
||||
const save =
|
||||
manualSaveModule.readManualSave(
|
||||
requestedSlot
|
||||
);
|
||||
return save
|
||||
? {
|
||||
slot: save.slot,
|
||||
context: save.context,
|
||||
activeSaveSlot:
|
||||
save.campaign.activeSaveSlot,
|
||||
selectedPriorityId:
|
||||
save.campaign
|
||||
.thirdCampPreparationSelection
|
||||
?.priorityId ?? null
|
||||
}
|
||||
: null;
|
||||
}, slot);
|
||||
}
|
||||
|
||||
async function openPreparationBrowser(page) {
|
||||
let lastState = await readCamp(page);
|
||||
for (let attempt = 0; attempt < 2; attempt += 1) {
|
||||
|
||||
@@ -11515,6 +11515,7 @@ export class CampScene extends Phaser.Scene {
|
||||
private saveSlotConfirmObjects: Phaser.GameObjects.GameObject[] = [];
|
||||
private navigationBlockerObjects: Phaser.GameObjects.GameObject[] = [];
|
||||
private pendingSaveSlot?: number;
|
||||
private handledCampShortcutEvents = new WeakSet<KeyboardEvent>();
|
||||
private pendingVictoryRewardReport?: CampaignVictoryRewardReport;
|
||||
private victoryRewardLedgerReport?: CampaignVictoryRewardReport;
|
||||
private victorySettlementArrival?: VictorySettlementArrival;
|
||||
@@ -11841,7 +11842,7 @@ export class CampScene extends Phaser.Scene {
|
||||
musicVolume: this.campSoundscape.music.volume,
|
||||
ambienceVolume: this.campSoundscape.ambience.volume
|
||||
});
|
||||
this.input.keyboard?.on('keydown-ESC', () => this.handleEscapeKey());
|
||||
this.input.keyboard?.on('keydown-ESC', (event: KeyboardEvent) => this.handleEscapeKey(event));
|
||||
this.input.keyboard?.on('keydown-ENTER', (event: KeyboardEvent) => this.handleSortieEnterKey(event));
|
||||
this.input.keyboard?.on('keydown', (event: KeyboardEvent) => {
|
||||
if (
|
||||
@@ -13409,6 +13410,25 @@ export class CampScene extends Phaser.Scene {
|
||||
panel.setDepth(depth);
|
||||
panel.setStrokeStyle(2, palette.gold, 0.86);
|
||||
panel.setInteractive();
|
||||
panel.on(
|
||||
'pointerdown',
|
||||
(
|
||||
_pointer: Phaser.Input.Pointer,
|
||||
_localX: number,
|
||||
_localY: number,
|
||||
event?: { stopPropagation: () => void }
|
||||
) => event?.stopPropagation()
|
||||
);
|
||||
panel.on(
|
||||
'wheel',
|
||||
(
|
||||
_pointer: Phaser.Input.Pointer,
|
||||
_deltaX: number,
|
||||
_deltaY: number,
|
||||
_deltaZ: number,
|
||||
event?: { stopPropagation: () => void }
|
||||
) => event?.stopPropagation()
|
||||
);
|
||||
this.trackCampSaveSlot(panel);
|
||||
|
||||
const title = this.add.text(left + 22, top + 16, '수동 저장 기록', this.textStyle(22, '#f2e3bf', true));
|
||||
@@ -13436,7 +13456,13 @@ export class CampScene extends Phaser.Scene {
|
||||
const close = this.add.text(left + width - 58, top + 18, '닫기', this.textStyle(14, '#d8b15f', true));
|
||||
close.setDepth(depth + 2);
|
||||
close.setInteractive({ useHandCursor: true });
|
||||
close.on('pointerdown', () => {
|
||||
close.on('pointerdown', (
|
||||
_pointer: Phaser.Input.Pointer,
|
||||
_localX: number,
|
||||
_localY: number,
|
||||
event?: { stopPropagation: () => void }
|
||||
) => {
|
||||
event?.stopPropagation();
|
||||
soundDirector.playSelect();
|
||||
this.hideCampSaveSlotPanel();
|
||||
});
|
||||
@@ -13505,7 +13531,15 @@ export class CampScene extends Phaser.Scene {
|
||||
saveLabel.setDepth(depth + 1);
|
||||
this.trackCampSaveSlot(saveLabel);
|
||||
|
||||
const run = () => this.requestSaveCampToSlot(summary.slot);
|
||||
const run = (
|
||||
_pointer: Phaser.Input.Pointer,
|
||||
_localX: number,
|
||||
_localY: number,
|
||||
event?: { stopPropagation: () => void }
|
||||
) => {
|
||||
event?.stopPropagation();
|
||||
this.requestSaveCampToSlot(summary.slot);
|
||||
};
|
||||
[row, title, keycap, keyText, detail, meta, saveLabel].forEach((object) => {
|
||||
object.setInteractive({ useHandCursor: true });
|
||||
object.on('pointerdown', run);
|
||||
@@ -13572,6 +13606,29 @@ export class CampScene extends Phaser.Scene {
|
||||
shade.setOrigin(0);
|
||||
shade.setDepth(depth - 1);
|
||||
shade.setInteractive();
|
||||
shade.on(
|
||||
'pointerdown',
|
||||
(
|
||||
_pointer: Phaser.Input.Pointer,
|
||||
_localX: number,
|
||||
_localY: number,
|
||||
event?: { stopPropagation: () => void }
|
||||
) => {
|
||||
event?.stopPropagation();
|
||||
soundDirector.playSelect();
|
||||
this.hideCampSaveConfirm();
|
||||
}
|
||||
);
|
||||
shade.on(
|
||||
'wheel',
|
||||
(
|
||||
_pointer: Phaser.Input.Pointer,
|
||||
_deltaX: number,
|
||||
_deltaY: number,
|
||||
_deltaZ: number,
|
||||
event?: { stopPropagation: () => void }
|
||||
) => event?.stopPropagation()
|
||||
);
|
||||
this.trackCampSaveConfirm(shade);
|
||||
|
||||
const panel = this.add.rectangle(x, y, width, height, 0x111a24, 0.99);
|
||||
@@ -13579,6 +13636,25 @@ export class CampScene extends Phaser.Scene {
|
||||
panel.setDepth(depth);
|
||||
panel.setStrokeStyle(2, palette.gold, 0.9);
|
||||
panel.setInteractive();
|
||||
panel.on(
|
||||
'pointerdown',
|
||||
(
|
||||
_pointer: Phaser.Input.Pointer,
|
||||
_localX: number,
|
||||
_localY: number,
|
||||
event?: { stopPropagation: () => void }
|
||||
) => event?.stopPropagation()
|
||||
);
|
||||
panel.on(
|
||||
'wheel',
|
||||
(
|
||||
_pointer: Phaser.Input.Pointer,
|
||||
_deltaX: number,
|
||||
_deltaY: number,
|
||||
_deltaZ: number,
|
||||
event?: { stopPropagation: () => void }
|
||||
) => event?.stopPropagation()
|
||||
);
|
||||
this.trackCampSaveConfirm(panel);
|
||||
|
||||
const title = this.add.text(x + 22, y + 18, `슬롯 ${summary.slot} 덮어쓰기`, this.textStyle(20, '#f2e3bf', true));
|
||||
@@ -13608,7 +13684,15 @@ export class CampScene extends Phaser.Scene {
|
||||
confirm.setPadding(14, 5, 14, 5);
|
||||
confirm.setBackgroundColor('#6d4420');
|
||||
confirm.setInteractive({ useHandCursor: true });
|
||||
confirm.on('pointerdown', () => this.confirmCampSaveOverwrite());
|
||||
confirm.on('pointerdown', (
|
||||
_pointer: Phaser.Input.Pointer,
|
||||
_localX: number,
|
||||
_localY: number,
|
||||
event?: { stopPropagation: () => void }
|
||||
) => {
|
||||
event?.stopPropagation();
|
||||
this.confirmCampSaveOverwrite();
|
||||
});
|
||||
this.trackCampSaveConfirm(confirm);
|
||||
|
||||
const cancel = this.add.text(x + width - 70, y + 126, '취소', this.textStyle(14, '#d8b15f', true));
|
||||
@@ -13616,7 +13700,13 @@ export class CampScene extends Phaser.Scene {
|
||||
cancel.setPadding(14, 5, 14, 5);
|
||||
cancel.setBackgroundColor('#1a2630');
|
||||
cancel.setInteractive({ useHandCursor: true });
|
||||
cancel.on('pointerdown', () => {
|
||||
cancel.on('pointerdown', (
|
||||
_pointer: Phaser.Input.Pointer,
|
||||
_localX: number,
|
||||
_localY: number,
|
||||
event?: { stopPropagation: () => void }
|
||||
) => {
|
||||
event?.stopPropagation();
|
||||
soundDirector.playSelect();
|
||||
this.hideCampSaveConfirm();
|
||||
});
|
||||
@@ -14267,7 +14357,31 @@ export class CampScene extends Phaser.Scene {
|
||||
return true;
|
||||
}
|
||||
|
||||
private handleEscapeKey() {
|
||||
private acceptCampShortcutEvent(
|
||||
event: KeyboardEvent,
|
||||
shortcut: 'escape' | 'enter'
|
||||
) {
|
||||
const matches =
|
||||
shortcut === 'escape'
|
||||
? event.key === 'Escape' || event.code === 'Escape'
|
||||
: event.key === 'Enter' ||
|
||||
event.code === 'Enter' ||
|
||||
event.code === 'NumpadEnter';
|
||||
if (
|
||||
!matches ||
|
||||
event.repeat ||
|
||||
this.handledCampShortcutEvents.has(event)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
this.handledCampShortcutEvents.add(event);
|
||||
return true;
|
||||
}
|
||||
|
||||
private handleEscapeKey(event: KeyboardEvent) {
|
||||
if (!this.acceptCampShortcutEvent(event, 'escape')) {
|
||||
return;
|
||||
}
|
||||
if (this.navigationPending) {
|
||||
return;
|
||||
}
|
||||
@@ -14335,7 +14449,10 @@ export class CampScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
private handleSortieEnterKey(event: KeyboardEvent) {
|
||||
if (event.repeat || this.navigationPending) {
|
||||
if (
|
||||
!this.acceptCampShortcutEvent(event, 'enter') ||
|
||||
this.navigationPending
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
|
||||
Reference in New Issue
Block a user