Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions src/render/MapRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -982,7 +982,7 @@ export class MapRenderer {
this._setScrollKey(e.key, true);

if (this._handleDialogKey(e)) return;
this._handleGameKey(e);
if (this._handleGameKey(e)) e.preventDefault();
}

_handleDialogKey(e) {
Expand Down Expand Up @@ -4117,6 +4117,10 @@ export class MapRenderer {
this._stopEventVideo();
// Suppress any opening video overlay still in the DOM (prevent it from re-enabling title screen)
this._openingVideoDone = true;
if (this._openingVideoSkipHandler) {
document.removeEventListener('keydown', this._openingVideoSkipHandler, true);
this._openingVideoSkipHandler = null;
}
const openVid = document.querySelector('video[src*="OPENING"]');
if (openVid?.parentNode) {
openVid.pause();
Expand Down Expand Up @@ -4154,12 +4158,17 @@ export class MapRenderer {
gs._updateVisibility();
}

const firstUnit = gs.activeUnit ?? gs.units[0];
// Never centre a human load on an unseen AI unit. If the player has no
// active piece (common immediately after founding the capital), use their
// first remaining unit or city instead.
const firstUnit = gs.activeUnit?.civId === 0
? gs.activeUnit
: gs.units.find(unit => unit.civId === 0);
if (firstUnit) {
this.centerOn(firstUnit.col, firstUnit.row, this._canvasW, this._canvasH);
} else if (gs.cities.length > 0) {
const firstCity = gs.cities[0];
this.centerOn(firstCity.col, firstCity.row, this._canvasW, this._canvasH);
} else {
const firstCity = gs.cities.find(city => city.civId === 0);
if (firstCity) this.centerOn(firstCity.col, firstCity.row, this._canvasW, this._canvasH);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/render/mixins/DialogsMixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ export function applyDialogsMixin(MapRenderer) {
if (others.length === 0) {
ctx.font = FONT.BODY;
ctx.textAlign = 'center';
this._panelText(ctx, 'No other civilizations known.', px + PW / 2, py + 80);
this._panelText(ctx, 'No other civilizations known.', px + PW / 2, py + 54);
ctx.textAlign = 'left';
}

Expand Down
22 changes: 18 additions & 4 deletions src/render/mixins/WizardMixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -366,12 +366,14 @@ export function applyWizardMixin(MapRenderer) {
video.playsInline = true;
video.muted = false;
let soundButton = null;
let skipOnKey = null;

const finish = () => {
if (this._openingVideoDone) return;
this._openingVideoDone = true;
clearTimeout(stallTimer);
document.removeEventListener('keydown', skipOnKey);
if (skipOnKey) document.removeEventListener('keydown', skipOnKey, true);
if (this._openingVideoSkipHandler === skipOnKey) this._openingVideoSkipHandler = null;
soundButton?.remove();
if (video.parentNode) video.parentNode.removeChild(video);
onDone();
Expand All @@ -383,9 +385,21 @@ export function applyWizardMixin(MapRenderer) {
const stallTimer = setTimeout(finish, 60_000);

video.addEventListener('ended', finish);
video.addEventListener('click', finish);
const skipOnKey = () => finish();
document.addEventListener('keydown', skipOnKey);
video.addEventListener('click', event => {
event.preventDefault();
event.stopPropagation();
finish();
});
skipOnKey = event => {
if (this._openingVideoDone) return;
// The same Enter/Space press must not fall through to the freshly shown
// title dialog and activate its default selection.
event.preventDefault();
event.stopImmediatePropagation();
finish();
};
this._openingVideoSkipHandler = skipOnKey;
document.addEventListener('keydown', skipOnKey, true);

video.addEventListener('canplay', async () => {
try {
Expand Down
70 changes: 69 additions & 1 deletion tests/game.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,14 @@ test.describe('Title Screen', () => {
expect(newGame).not.toBeNull();
expect(titleScreen).toBe(false);
});

test('Enter skips the opening movie without also starting a new game', async ({ page }) => {
await gotoGame(page);
await page.keyboard.press('Enter');
await page.waitForFunction(() => window.__civ2?.mapScreen?._titleScreen === true);
expect(await ms(page, '_wizard')).toBeNull();
expect(await ms(page, '_titleSelection')).toBe(0);
});
});

test.describe('Original MGE graphics data', () => {
Expand Down Expand Up @@ -1379,6 +1387,31 @@ test.describe('Save / Load', () => {
expect(restoredTurn).toBe(turnBeforeSave);
});

test('loading without a human unit centres on the first human city, not an unseen AI unit', async ({ page }) => {
await gotoGame(page);
await startTestGame(page);
const result = await page.evaluate(() => {
const screen = window.__civ2.mapScreen;
const gs = screen.gameState;
let city = gs.cities.find(c => c.civId === 0);
if (!city) {
const settler = gs.units.find(u => u.civId === 0 && u.typeId === 0);
city = gs.foundCity(settler);
}
gs.units = gs.units.filter(u => u.civId !== 0);
screen._saveGame();
let centred = null;
const originalCenterOn = screen.centerOn.bind(screen);
screen.centerOn = (col, row, ...rest) => {
centred = { col, row };
return originalCenterOn(col, row, ...rest);
};
screen._loadGame();
return { centred, city: { col: city.col, row: city.row } };
});
expect(result.centred).toEqual(result.city);
});

test('title screen enables Load Saved Game after a save exists', async ({ page }) => {
await gotoGame(page);
await page.evaluate(() => localStorage.removeItem('civ2_save'));
Expand Down Expand Up @@ -6969,17 +7002,25 @@ test.describe('Human playthrough regressions', () => {
const gs = ms.gameState;
const canvas = document.createElement('canvas');
canvas.width = 1280; canvas.height = 800;
let emptyTextY = null;
const originalPanelText = ms._panelText.bind(ms);
ms._panelText = (ctx, text, x, y, ...rest) => {
if (text === 'No other civilizations known.') emptyTextY = y;
return originalPanelText(ctx, text, x, y, ...rest);
};
ms._drawDiplomacyScreen(canvas.getContext('2d'), canvas.width, canvas.height);
const before = ms._diplomacyScreenRects.filter(r => r.action === 'contact').map(r => r.civId);
const close = ms._diplomacyScreenRects.find(r => r.action === 'close');
gs.establishContact(0, 1);
ms._drawDiplomacyScreen(canvas.getContext('2d'), canvas.width, canvas.height);
const after = ms._diplomacyScreenRects.filter(r => r.action === 'contact').map(r => r.civId);
const restored = gs.constructor.fromSaveData(gs.toSaveData());
return { before, after, savedContact: restored.hasContact(0, 1) };
return { before, after, savedContact: restored.hasContact(0, 1), emptyTextY, closeY: close.y };
});
expect(result.before).toEqual([]);
expect(result.after).toEqual([1]);
expect(result.savedContact).toBe(true);
expect(result.emptyTextY).toBeLessThan(result.closeY - 8);
});

test('a garrisoned city opens before unit or tile actions in both view modes', async ({ page }) => {
Expand Down Expand Up @@ -7151,6 +7192,33 @@ test.describe('Human playthrough regressions', () => {
expect(result.after).toBe(result.before + 1);
expect(result.domestic).toBe(true);
});

test('handled cheat function keys suppress browser focus shortcuts', async ({ page }) => {
await gotoGame(page);
await startTestGame(page);
const result = await page.evaluate(() => {
const ms = window.__civ2.mapScreen;
const gs = ms.gameState;
const civ = gs.civs[0];
civ.currentResearch = gs.availableAdvances(0)[0].id;
ms._executeMenuAction('cheat_toggle');
const before = { x: ms.viewX, y: ms.viewY };
let prevented = false;
ms._onKeyDown({
key: 'F6', shiftKey: true, ctrlKey: false, altKey: false,
preventDefault() { prevented = true; },
});
return {
prevented,
before,
after: { x: ms.viewX, y: ms.viewY },
completed: civ.beakers === gs.advanceCost(civ),
};
});
expect(result.prevented).toBe(true);
expect(result.after).toEqual(result.before);
expect(result.completed).toBe(true);
});
});

// ─── Dialog Padding Constants ────────────────────────────────────────────────
Expand Down
Loading