fix: prevent dialogue input rebound
This commit is contained in:
@@ -748,6 +748,10 @@ async function openResonanceChoice(page) {
|
||||
}
|
||||
|
||||
async function openShop(page) {
|
||||
await waitForCityInteractionIdle(
|
||||
page,
|
||||
fixture.marketActorId
|
||||
);
|
||||
let interacted = false;
|
||||
for (let attempt = 0; attempt < 8; attempt += 1) {
|
||||
interacted = await page.evaluate((actorId) =>
|
||||
@@ -761,10 +765,18 @@ async function openShop(page) {
|
||||
}
|
||||
await page.waitForTimeout(120);
|
||||
}
|
||||
const diagnostic = interacted
|
||||
? null
|
||||
: await readCityInteractionDiagnostic(
|
||||
page,
|
||||
fixture.marketActorId
|
||||
);
|
||||
assert.equal(
|
||||
interacted,
|
||||
true,
|
||||
`${renderer}: could not open the city shop.`
|
||||
`${renderer}: could not open the city shop: ${JSON.stringify(
|
||||
diagnostic
|
||||
)}`
|
||||
);
|
||||
await waitForShopOpen(page);
|
||||
const city = await readCity(page);
|
||||
@@ -864,17 +876,115 @@ function assertDialogue(
|
||||
async function finishCityDialogue(page) {
|
||||
for (let attempt = 0; attempt < 12; attempt += 1) {
|
||||
const city = await readCity(page);
|
||||
if (!city?.dialogue?.active) {
|
||||
if (
|
||||
city?.ready === true &&
|
||||
city.dialogue?.active === false
|
||||
) {
|
||||
await waitForCityInteractionIdle(page);
|
||||
return;
|
||||
}
|
||||
await page.waitForTimeout(150);
|
||||
await page.keyboard.press('e');
|
||||
if (city?.dialogue?.active === true) {
|
||||
await page.waitForTimeout(150);
|
||||
await page.keyboard.press('e');
|
||||
} else {
|
||||
await page.waitForTimeout(150);
|
||||
}
|
||||
}
|
||||
throw new Error(
|
||||
`${renderer}: city dialogue did not finish.`
|
||||
);
|
||||
}
|
||||
|
||||
async function waitForCityInteractionIdle(
|
||||
page,
|
||||
targetActorId
|
||||
) {
|
||||
const expected = { targetActorId };
|
||||
const waitForIdle = () =>
|
||||
page.waitForFunction(
|
||||
({ targetActorId: actorId }) => {
|
||||
const debug = window.__HEROS_DEBUG__;
|
||||
const city = debug?.cityStay?.();
|
||||
return (
|
||||
debug
|
||||
?.activeScenes?.()
|
||||
.includes('CityStayScene') === true &&
|
||||
city?.scene === 'CityStayScene' &&
|
||||
city.ready === true &&
|
||||
city.navigationPending === false &&
|
||||
city.dialogue?.active === false &&
|
||||
city.shop?.open === false &&
|
||||
city.choice?.open === false &&
|
||||
city.campaignObjectiveJournal?.view?.visible ===
|
||||
false &&
|
||||
(!actorId ||
|
||||
city.actors?.some(
|
||||
({ id }) => id === actorId
|
||||
))
|
||||
);
|
||||
},
|
||||
expected,
|
||||
{ timeout: 30000 }
|
||||
);
|
||||
await waitForIdle();
|
||||
await afterTwoFrames(page);
|
||||
const stable = await readCityInteractionDiagnostic(
|
||||
page,
|
||||
targetActorId
|
||||
);
|
||||
assert.equal(
|
||||
stable.idle,
|
||||
true,
|
||||
`${renderer}: city interaction became blocked after reaching idle; the closing key may have reopened a modal: ${JSON.stringify(
|
||||
stable
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
async function readCityInteractionDiagnostic(
|
||||
page,
|
||||
targetActorId
|
||||
) {
|
||||
return page.evaluate((actorId) => {
|
||||
const debug = window.__HEROS_DEBUG__;
|
||||
const scene = debug?.scene('CityStayScene');
|
||||
const city = debug?.cityStay?.() ?? null;
|
||||
const activeScenes = debug?.activeScenes?.() ?? [];
|
||||
const actor =
|
||||
city?.actors?.find(({ id }) => id === actorId) ??
|
||||
null;
|
||||
const objectiveJournalOpen =
|
||||
city?.campaignObjectiveJournal?.view?.visible ??
|
||||
null;
|
||||
const idle =
|
||||
activeScenes.includes('CityStayScene') &&
|
||||
city?.scene === 'CityStayScene' &&
|
||||
city.ready === true &&
|
||||
city.navigationPending === false &&
|
||||
city.dialogue?.active === false &&
|
||||
city.shop?.open === false &&
|
||||
city.choice?.open === false &&
|
||||
objectiveJournalOpen === false &&
|
||||
(!actorId || Boolean(actor));
|
||||
return {
|
||||
idle,
|
||||
activeScenes,
|
||||
sceneActive:
|
||||
scene?.scene?.isActive?.() ?? false,
|
||||
scenePaused:
|
||||
scene?.scene?.isPaused?.() ?? false,
|
||||
ready: city?.ready ?? null,
|
||||
navigationPending:
|
||||
city?.navigationPending ?? null,
|
||||
dialogue: city?.dialogue ?? null,
|
||||
shopOpen: city?.shop?.open ?? null,
|
||||
choiceOpen: city?.choice?.open ?? null,
|
||||
objectiveJournalOpen,
|
||||
actor
|
||||
};
|
||||
}, targetActorId);
|
||||
}
|
||||
|
||||
async function finishCityDialogueToCamp(page) {
|
||||
for (let attempt = 0; attempt < 12; attempt += 1) {
|
||||
const activeScenes = await page.evaluate(
|
||||
@@ -1229,7 +1339,7 @@ async function ensureLocalServer(url) {
|
||||
}
|
||||
await delay(250);
|
||||
}
|
||||
child.kill();
|
||||
await stopServerProcess(child);
|
||||
throw new Error(
|
||||
`Vite server did not start at ${url}\n${stderr.join('')}`
|
||||
);
|
||||
@@ -1247,11 +1357,25 @@ async function canReach(url) {
|
||||
}
|
||||
|
||||
async function stopServerProcess(child) {
|
||||
if (!child || child.killed) {
|
||||
if (
|
||||
!child ||
|
||||
child.exitCode !== null ||
|
||||
child.signalCode !== null
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const exited = new Promise((resolve) =>
|
||||
child.once('exit', resolve)
|
||||
);
|
||||
child.kill();
|
||||
await delay(80);
|
||||
await Promise.race([exited, delay(5000)]);
|
||||
if (
|
||||
child.exitCode === null &&
|
||||
child.signalCode === null
|
||||
) {
|
||||
child.kill('SIGKILL');
|
||||
await Promise.race([exited, delay(2000)]);
|
||||
}
|
||||
}
|
||||
|
||||
function delay(ms) {
|
||||
|
||||
@@ -214,6 +214,7 @@ export class CityStayScene extends Phaser.Scene {
|
||||
private navigationPending = false;
|
||||
private inputReadyAt = Number.POSITIVE_INFINITY;
|
||||
private autoDialogueInputReadyAt = 0;
|
||||
private interactionInputReadyAt = 0;
|
||||
private stepIndex = 0;
|
||||
private lastStepAt = 0;
|
||||
private lastNotice = '';
|
||||
@@ -367,6 +368,9 @@ export class CityStayScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
if (this.consumeInteractionRequest()) {
|
||||
if (time < this.interactionInputReadyAt) {
|
||||
return;
|
||||
}
|
||||
this.tryInteract();
|
||||
return;
|
||||
}
|
||||
@@ -677,6 +681,7 @@ export class CityStayScene extends Phaser.Scene {
|
||||
this.navigationPending = false;
|
||||
this.inputReadyAt = Number.POSITIVE_INFINITY;
|
||||
this.autoDialogueInputReadyAt = 0;
|
||||
this.interactionInputReadyAt = 0;
|
||||
this.explorationInput = undefined;
|
||||
this.stepIndex = 0;
|
||||
this.lastStepAt = 0;
|
||||
@@ -3220,6 +3225,9 @@ export class CityStayScene extends Phaser.Scene {
|
||||
this.dialogueState = undefined;
|
||||
this.dialoguePanel?.setVisible(false);
|
||||
this.stopPlayerMovement();
|
||||
this.interactionInputReadyAt =
|
||||
this.time.now + inputCarryoverGuardMs;
|
||||
this.explorationInput?.discardInteractionRequest();
|
||||
this.restoreActorDirection(sourceTargetId);
|
||||
const checkpointCommitted = onComplete?.() === true;
|
||||
if (!checkpointCommitted) {
|
||||
@@ -3233,6 +3241,9 @@ export class CityStayScene extends Phaser.Scene {
|
||||
this.dialogueState = undefined;
|
||||
this.dialoguePanel?.setVisible(false);
|
||||
this.stopPlayerMovement();
|
||||
this.interactionInputReadyAt =
|
||||
this.time.now + inputCarryoverGuardMs;
|
||||
this.explorationInput?.discardInteractionRequest();
|
||||
this.restoreActorDirection(sourceTargetId);
|
||||
this.persistExplorationCheckpoint(true);
|
||||
this.refreshInteractionPrompt();
|
||||
|
||||
Reference in New Issue
Block a user