Sync responsive shipyard gameplay
This commit is contained in:
parent
bb919e219c
commit
844e226aca
@ -132,7 +132,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<button class="mobile-control port" data-code="KeyQ" data-key="q" data-keycode="81" aria-label="Fire port broadside">PORT</button>
|
<button class="mobile-control port" data-code="KeyQ" data-key="q" data-keycode="81" aria-label="Fire port broadside">PORT</button>
|
||||||
<button class="mobile-control starboard" data-code="KeyE" data-key="e" data-keycode="69" aria-label="Fire starboard broadside">STARBOARD</button>
|
<button class="mobile-control starboard" data-code="KeyE" data-key="e" data-keycode="69" aria-label="Fire starboard broadside">STARBOARD</button>
|
||||||
<button class="mobile-control ship-action buy" data-action="buy-upgrade" aria-label="Buy ship upgrade">BUY</button>
|
<button class="mobile-control ship-action buy" data-action="open-shipyard" aria-label="Open shipyard">SHIPYARD</button>
|
||||||
<button class="mobile-control ship-action fit" data-action="fit-salvage" aria-label="Fit salvaged module">FIT</button>
|
<button class="mobile-control ship-action fit" data-action="fit-salvage" aria-label="Fit salvaged module">FIT</button>
|
||||||
<button class="mobile-control restart" data-code="KeyR" data-key="r" data-keycode="82" aria-label="Restart">↻</button>
|
<button class="mobile-control restart" data-code="KeyR" data-key="r" data-keycode="82" aria-label="Restart">↻</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,2 +1,3 @@
|
|||||||
import './game-v4.js';
|
import './game-v4.js';
|
||||||
import './mobile-hud.js';
|
import './mobile-hud.js';
|
||||||
|
import './shipyard-ui.js';
|
||||||
|
|||||||
@ -36,6 +36,7 @@ import {
|
|||||||
createStarterProgression,
|
createStarterProgression,
|
||||||
deriveShipStats,
|
deriveShipStats,
|
||||||
installModule,
|
installModule,
|
||||||
|
marketForPort,
|
||||||
salvageForPirate,
|
salvageForPirate,
|
||||||
} from './progression/ship-loadout.js';
|
} from './progression/ship-loadout.js';
|
||||||
import { MODULE_CATALOG } from './progression/modules.js';
|
import { MODULE_CATALOG } from './progression/modules.js';
|
||||||
@ -102,6 +103,8 @@ class GameScene extends Phaser.Scene {
|
|||||||
this.progression = createStarterProgression();
|
this.progression = createStarterProgression();
|
||||||
this.playerStats = deriveShipStats(this.progression.loadout);
|
this.playerStats = deriveShipStats(this.progression.loadout);
|
||||||
this.pendingMobileActions = new Set();
|
this.pendingMobileActions = new Set();
|
||||||
|
this.pendingModulePurchaseId = null;
|
||||||
|
this.shipyardOpen = false;
|
||||||
this.playerSpeed = 0;
|
this.playerSpeed = 0;
|
||||||
this.playerHull = this.playerStats.maxHull;
|
this.playerHull = this.playerStats.maxHull;
|
||||||
this.gameState = 'playing';
|
this.gameState = 'playing';
|
||||||
@ -223,7 +226,11 @@ class GameScene extends Phaser.Scene {
|
|||||||
window.addEventListener('airship:helm-vector', this.handleHelmVector);
|
window.addEventListener('airship:helm-vector', this.handleHelmVector);
|
||||||
this.handleMobileAction = (event) => {
|
this.handleMobileAction = (event) => {
|
||||||
const action = String(event.detail?.action || '');
|
const action = String(event.detail?.action || '');
|
||||||
if (action) this.pendingMobileActions.add(action);
|
if (!action) return;
|
||||||
|
if (action === 'purchase-module') {
|
||||||
|
this.pendingModulePurchaseId = String(event.detail?.moduleId || '');
|
||||||
|
}
|
||||||
|
this.pendingMobileActions.add(action);
|
||||||
};
|
};
|
||||||
window.addEventListener('airship:action', this.handleMobileAction);
|
window.addEventListener('airship:action', this.handleMobileAction);
|
||||||
this.keys = this.input.keyboard.addKeys({
|
this.keys = this.input.keyboard.addKeys({
|
||||||
@ -233,6 +240,7 @@ class GameScene extends Phaser.Scene {
|
|||||||
restart: Phaser.Input.Keyboard.KeyCodes.R,
|
restart: Phaser.Input.Keyboard.KeyCodes.R,
|
||||||
buyUpgrade: Phaser.Input.Keyboard.KeyCodes.B,
|
buyUpgrade: Phaser.Input.Keyboard.KeyCodes.B,
|
||||||
fitSalvage: Phaser.Input.Keyboard.KeyCodes.U,
|
fitSalvage: Phaser.Input.Keyboard.KeyCodes.U,
|
||||||
|
closeShipyard: Phaser.Input.Keyboard.KeyCodes.ESC,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.cameras.main.setBounds(0, 0, this.world.width, this.world.height);
|
this.cameras.main.setBounds(0, 0, this.world.width, this.world.height);
|
||||||
@ -527,29 +535,60 @@ class GameScene extends Phaser.Scene {
|
|||||||
|
|
||||||
const { port, range } = nearestPort(this.world, this.player.x, this.player.y);
|
const { port, range } = nearestPort(this.world, this.player.x, this.player.y);
|
||||||
const docked = Boolean(port && range <= 230);
|
const docked = Boolean(port && range <= 230);
|
||||||
|
const openRequested = this.pendingMobileActions.delete('open-shipyard')
|
||||||
const mobileBuy = this.pendingMobileActions.delete('buy-upgrade');
|
|| Phaser.Input.Keyboard.JustDown(this.keys.buyUpgrade);
|
||||||
|
const closeRequested = this.pendingMobileActions.delete('close-shipyard')
|
||||||
|
|| Phaser.Input.Keyboard.JustDown(this.keys.closeShipyard);
|
||||||
|
const purchaseRequested = this.pendingMobileActions.delete('purchase-module');
|
||||||
const mobileFit = this.pendingMobileActions.delete('fit-salvage');
|
const mobileFit = this.pendingMobileActions.delete('fit-salvage');
|
||||||
|
|
||||||
if (mobileBuy || Phaser.Input.Keyboard.JustDown(this.keys.buyUpgrade)) {
|
if (closeRequested && this.shipyardOpen) {
|
||||||
|
this.shipyardOpen = false;
|
||||||
|
this.pendingModulePurchaseId = null;
|
||||||
|
window.dispatchEvent(new CustomEvent('airship:shipyard-close'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (openRequested) {
|
||||||
if (!docked) {
|
if (!docked) {
|
||||||
this.status.setText('GET WITHIN 230m OF A PORT TO BUY UPGRADES');
|
this.status.setText('GET WITHIN 230m OF A PORT TO ENTER SHIPYARD');
|
||||||
|
this.time.delayedCall(1400, () => {
|
||||||
|
if (this.gameState === 'playing') this.status.setText('');
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
const module = bestAffordableMarketUpgrade(this.progression, port);
|
this.shipyardOpen = true;
|
||||||
|
this.playerSpeed = 0;
|
||||||
|
this.player.setVelocity(0, 0);
|
||||||
|
window.dispatchEvent(new CustomEvent('airship:shipyard-open', {
|
||||||
|
detail: this.buildHudState(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (purchaseRequested) {
|
||||||
|
const moduleId = this.pendingModulePurchaseId;
|
||||||
|
this.pendingModulePurchaseId = null;
|
||||||
|
if (!this.shipyardOpen || !docked || !moduleId) return;
|
||||||
|
|
||||||
|
const stock = marketForPort(port);
|
||||||
|
const module = stock.find(({ id }) => id === moduleId);
|
||||||
if (!module) {
|
if (!module) {
|
||||||
this.status.setText(`${port.name}\nNO AFFORDABLE UPGRADES`);
|
this.status.setText('MODULE NOT STOCKED AT THIS PORT');
|
||||||
|
} else if (this.progression.loadout?.[module.slot] === module.id) {
|
||||||
|
this.status.setText(`${module.name}\nALREADY INSTALLED`);
|
||||||
|
} else if (this.progression.credits < module.price) {
|
||||||
|
this.status.setText(`${module.name}\nNEED ¢${module.price}`);
|
||||||
} else {
|
} else {
|
||||||
const previousMaxHull = this.playerStats.maxHull;
|
const previousMaxHull = this.playerStats.maxHull;
|
||||||
this.progression = buyAndInstallModule(this.progression, module.id);
|
this.progression = buyAndInstallModule(this.progression, module.id);
|
||||||
this.applyProgressionStats(previousMaxHull);
|
this.applyProgressionStats(previousMaxHull);
|
||||||
this.status.setText(
|
this.status.setText(
|
||||||
`${port.name}\nINSTALLED ${module.name} // ¢${this.progression.credits}`,
|
`INSTALLED ${module.name}\n¢${this.progression.credits} REMAINING`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
window.dispatchEvent(new CustomEvent('airship:shipyard-state', {
|
||||||
this.time.delayedCall(1500, () => {
|
detail: this.buildHudState(),
|
||||||
if (this.gameState === 'playing') this.status.setText('');
|
}));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mobileFit || Phaser.Input.Keyboard.JustDown(this.keys.fitSalvage)) {
|
if (mobileFit || Phaser.Input.Keyboard.JustDown(this.keys.fitSalvage)) {
|
||||||
@ -563,6 +602,11 @@ class GameScene extends Phaser.Scene {
|
|||||||
const module = MODULE_CATALOG[moduleId];
|
const module = MODULE_CATALOG[moduleId];
|
||||||
this.status.setText(`FITTED SALVAGE\n${module?.name || moduleId}`);
|
this.status.setText(`FITTED SALVAGE\n${module?.name || moduleId}`);
|
||||||
}
|
}
|
||||||
|
if (this.shipyardOpen) {
|
||||||
|
window.dispatchEvent(new CustomEvent('airship:shipyard-state', {
|
||||||
|
detail: this.buildHudState(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
this.time.delayedCall(1400, () => {
|
this.time.delayedCall(1400, () => {
|
||||||
if (this.gameState === 'playing') this.status.setText('');
|
if (this.gameState === 'playing') this.status.setText('');
|
||||||
});
|
});
|
||||||
@ -1147,8 +1191,16 @@ class GameScene extends Phaser.Scene {
|
|||||||
}, 'debug');
|
}, 'debug');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updatePlayer(dt, deltaMs);
|
|
||||||
this.updateProgressionInteraction();
|
this.updateProgressionInteraction();
|
||||||
|
if (this.shipyardOpen) {
|
||||||
|
this.player.setVelocity(0, 0);
|
||||||
|
const hudState = this.buildHudState();
|
||||||
|
if (!this.mobileHudActive) this.drawHud(hudState);
|
||||||
|
this.publishHudState(hudState, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updatePlayer(dt, deltaMs);
|
||||||
this.updatePlayerSmoke(deltaMs);
|
this.updatePlayerSmoke(deltaMs);
|
||||||
this.updateRollingClouds(deltaMs);
|
this.updateRollingClouds(deltaMs);
|
||||||
this.updateCloudConcealment();
|
this.updateCloudConcealment();
|
||||||
@ -1605,6 +1657,7 @@ class GameScene extends Phaser.Scene {
|
|||||||
const portState = nearestPort(this.world, player.x, player.y);
|
const portState = nearestPort(this.world, player.x, player.y);
|
||||||
const influence = pirateInfluenceAt(this.world, player.x, player.y);
|
const influence = pirateInfluenceAt(this.world, player.x, player.y);
|
||||||
const docked = Boolean(portState.port && portState.range <= 230);
|
const docked = Boolean(portState.port && portState.range <= 230);
|
||||||
|
const marketStock = docked ? marketForPort(portState.port) : [];
|
||||||
const marketUpgrade = docked
|
const marketUpgrade = docked
|
||||||
? bestAffordableMarketUpgrade(this.progression, portState.port)
|
? bestAffordableMarketUpgrade(this.progression, portState.port)
|
||||||
: null;
|
: null;
|
||||||
@ -1651,6 +1704,15 @@ class GameScene extends Phaser.Scene {
|
|||||||
nearestPort: portState.port?.name || null,
|
nearestPort: portState.port?.name || null,
|
||||||
nearestPortRange: portState.range,
|
nearestPortRange: portState.range,
|
||||||
docked,
|
docked,
|
||||||
|
shipyardOpen: this.shipyardOpen,
|
||||||
|
shipStats: { ...this.playerStats },
|
||||||
|
loadout: { ...this.progression.loadout },
|
||||||
|
marketStock: marketStock.map((module) => ({
|
||||||
|
id: module.id,
|
||||||
|
name: module.name,
|
||||||
|
slot: module.slot,
|
||||||
|
price: module.price,
|
||||||
|
})),
|
||||||
marketUpgrade: marketUpgrade ? {
|
marketUpgrade: marketUpgrade ? {
|
||||||
id: marketUpgrade.id,
|
id: marketUpgrade.id,
|
||||||
name: marketUpgrade.name,
|
name: marketUpgrade.name,
|
||||||
@ -1674,10 +1736,10 @@ class GameScene extends Phaser.Scene {
|
|||||||
}
|
}
|
||||||
|
|
||||||
publishHudState(state = this.buildHudState(), force = false) {
|
publishHudState(state = this.buildHudState(), force = false) {
|
||||||
if (!this.mobileHudActive || !state) return;
|
if (!state) return;
|
||||||
|
|
||||||
const now = this.time?.now ?? 0;
|
const now = this.time?.now ?? 0;
|
||||||
if (!force && now - this.lastHudPublishAt < HUD_PUBLISH_INTERVAL_MS) return;
|
if (this.mobileHudActive && !force && now - this.lastHudPublishAt < HUD_PUBLISH_INTERVAL_MS) return;
|
||||||
|
|
||||||
this.lastHudPublishAt = now;
|
this.lastHudPublishAt = now;
|
||||||
window.dispatchEvent(new CustomEvent('airship:hud-state', { detail: state }));
|
window.dispatchEvent(new CustomEvent('airship:hud-state', { detail: state }));
|
||||||
|
|||||||
@ -799,13 +799,10 @@ if (!isDesktopPointer.matches) {
|
|||||||
|
|
||||||
const progression = state.progression || {};
|
const progression = state.progression || {};
|
||||||
if (buyButton) {
|
if (buyButton) {
|
||||||
const upgrade = progression.marketUpgrade;
|
buyButton.disabled = !progression.docked;
|
||||||
buyButton.disabled = !progression.docked || !upgrade;
|
buyButton.textContent = progression.docked
|
||||||
buyButton.textContent = !progression.docked
|
? 'SHIPYARD'
|
||||||
? 'BUY\nPORT'
|
: 'SHIPYARD\nPORT';
|
||||||
: upgrade
|
|
||||||
? `BUY\n${upgrade.name} ¢${upgrade.price}`
|
|
||||||
: 'BUY\nNO UPGRADE';
|
|
||||||
}
|
}
|
||||||
if (fitButton) {
|
if (fitButton) {
|
||||||
const module = progression.salvageModule;
|
const module = progression.salvageModule;
|
||||||
|
|||||||
@ -24,10 +24,10 @@ export const MODULE_CATALOG = {
|
|||||||
},
|
},
|
||||||
'tuned-propellers': {
|
'tuned-propellers': {
|
||||||
id: 'tuned-propellers',
|
id: 'tuned-propellers',
|
||||||
name: 'Tuned Propellers',
|
name: 'Engine Lv 1',
|
||||||
slot: 'engine',
|
slot: 'engine',
|
||||||
rarity: 'improved',
|
rarity: 'improved',
|
||||||
price: 280,
|
price: 800,
|
||||||
modifiers: { maxSpeed: 20, acceleration: 10 },
|
modifiers: { maxSpeed: 20, acceleration: 10 },
|
||||||
},
|
},
|
||||||
'corsair-overdrive': {
|
'corsair-overdrive': {
|
||||||
@ -72,10 +72,10 @@ export const MODULE_CATALOG = {
|
|||||||
},
|
},
|
||||||
'reinforced-keel': {
|
'reinforced-keel': {
|
||||||
id: 'reinforced-keel',
|
id: 'reinforced-keel',
|
||||||
name: 'Reinforced Keel',
|
name: 'Hull Plating Lv 1',
|
||||||
slot: 'hull',
|
slot: 'hull',
|
||||||
rarity: 'improved',
|
rarity: 'improved',
|
||||||
price: 420,
|
price: 600,
|
||||||
modifiers: { maxHull: 28, maxSpeed: -7 },
|
modifiers: { maxHull: 28, maxSpeed: -7 },
|
||||||
},
|
},
|
||||||
'stormglass-nav': {
|
'stormglass-nav': {
|
||||||
@ -104,12 +104,20 @@ export const MODULE_CATALOG = {
|
|||||||
},
|
},
|
||||||
'expanded-hold': {
|
'expanded-hold': {
|
||||||
id: 'expanded-hold',
|
id: 'expanded-hold',
|
||||||
name: 'Expanded Cargo Hold',
|
name: 'Cargo Hold Lv 1',
|
||||||
slot: 'cargo',
|
slot: 'cargo',
|
||||||
rarity: 'improved',
|
rarity: 'improved',
|
||||||
price: 350,
|
price: 600,
|
||||||
modifiers: { cargoCapacity: 4, maxSpeed: -4 },
|
modifiers: { cargoCapacity: 4, maxSpeed: -4 },
|
||||||
},
|
},
|
||||||
|
'reinforced-rudder': {
|
||||||
|
id: 'reinforced-rudder',
|
||||||
|
name: 'Rudder Lv 1',
|
||||||
|
slot: 'rudder',
|
||||||
|
rarity: 'improved',
|
||||||
|
price: 400,
|
||||||
|
modifiers: { turnRate: 12 },
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const STARTER_LOADOUT = {
|
export const STARTER_LOADOUT = {
|
||||||
@ -119,6 +127,7 @@ export const STARTER_LOADOUT = {
|
|||||||
navigation: 'brass-compass',
|
navigation: 'brass-compass',
|
||||||
utility: null,
|
utility: null,
|
||||||
cargo: null,
|
cargo: null,
|
||||||
|
rudder: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const purchasableModules = () => Object.values(MODULE_CATALOG)
|
export const purchasableModules = () => Object.values(MODULE_CATALOG)
|
||||||
|
|||||||
465
src/shipyard-ui.js
Normal file
465
src/shipyard-ui.js
Normal file
@ -0,0 +1,465 @@
|
|||||||
|
import titleBannerUrl from './assets/shipyard-ui/title_banner.png?url';
|
||||||
|
import shipSelectorUrl from './assets/shipyard-ui/ship_selector.png?url';
|
||||||
|
import shipPreviewUrl from './assets/shipyard-ui/ship_preview.png?url';
|
||||||
|
import upgradesUrl from './assets/shipyard-ui/upgrades.png?url';
|
||||||
|
import activeUrl from './assets/shipyard-ui/active.png?url';
|
||||||
|
import closeButtonUrl from './assets/shipyard-ui/close_button.png?url';
|
||||||
|
|
||||||
|
const UPGRADE_SLOTS = [
|
||||||
|
{ id: 'tuned-propellers', label: 'Engine Lv 1' },
|
||||||
|
{ id: 'reinforced-keel', label: 'Hull Plating Lv 1' },
|
||||||
|
{ id: 'expanded-hold', label: 'Cargo Hold Lv 1' },
|
||||||
|
{ id: 'reinforced-rudder', label: 'Rudder Lv 1' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const dispatchAction = (action, extra = {}) => {
|
||||||
|
window.dispatchEvent(new CustomEvent('airship:action', {
|
||||||
|
detail: { action, ...extra },
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const overlay = document.createElement('div');
|
||||||
|
overlay.id = 'shipyard-overlay';
|
||||||
|
overlay.hidden = true;
|
||||||
|
overlay.innerHTML = `
|
||||||
|
<div class="shipyard-backdrop"></div>
|
||||||
|
<section class="shipyard-shell" role="dialog" aria-modal="true" aria-label="Shipyard">
|
||||||
|
<header class="shipyard-header">
|
||||||
|
<img class="shipyard-title" src="${titleBannerUrl}" alt="Shipyard" draggable="false" />
|
||||||
|
<button class="shipyard-close" type="button" aria-label="Close shipyard">
|
||||||
|
<img src="${closeButtonUrl}" alt="" draggable="false" />
|
||||||
|
</button>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="shipyard-resources">
|
||||||
|
<span>PORT <strong data-shipyard="port">--</strong></span>
|
||||||
|
<span>COIN <strong data-shipyard="credits">0</strong></span>
|
||||||
|
<span>SCRAP <strong data-shipyard="scrap">0</strong></span>
|
||||||
|
<span>SALVAGE <strong data-shipyard="inventory">0</strong></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="shipyard-grid">
|
||||||
|
<aside class="shipyard-selector">
|
||||||
|
<img src="${shipSelectorUrl}" alt="Ship selection" draggable="false" />
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<div class="shipyard-preview-wrap">
|
||||||
|
<img class="shipyard-preview" src="${shipPreviewUrl}" alt="Sparrow preview" draggable="false" />
|
||||||
|
<div class="shipyard-live-stats">
|
||||||
|
<strong>SPARROW</strong>
|
||||||
|
<span data-shipyard="hull">Hull 100</span>
|
||||||
|
<span data-shipyard="speed">Speed 180</span>
|
||||||
|
<span data-shipyard="firepower">Firepower 16</span>
|
||||||
|
<span data-shipyard="handling">Handling 76</span>
|
||||||
|
<span data-shipyard="cargo">Cargo 4</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="shipyard-upgrade-wrap">
|
||||||
|
<img class="shipyard-upgrades" src="${upgradesUrl}" alt="Ship upgrades" draggable="false" />
|
||||||
|
<div class="shipyard-upgrade-hotspots">
|
||||||
|
${UPGRADE_SLOTS.map(({ id, label }) => `
|
||||||
|
<button class="shipyard-upgrade-button" type="button" data-module-id="${id}" aria-label="Buy ${label}">
|
||||||
|
<span class="shipyard-upgrade-state"></span>
|
||||||
|
</button>
|
||||||
|
`).join('')}
|
||||||
|
</div>
|
||||||
|
<div class="shipyard-active-plate">
|
||||||
|
<img src="${activeUrl}" alt="" draggable="false" />
|
||||||
|
<span data-shipyard="active">SPARROW ACTIVE</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer class="shipyard-footer">
|
||||||
|
<button class="shipyard-salvage" type="button" data-shipyard="fit">FIT SALVAGE</button>
|
||||||
|
<span class="shipyard-message" data-shipyard="message"></span>
|
||||||
|
</footer>
|
||||||
|
</section>
|
||||||
|
`;
|
||||||
|
document.body.appendChild(overlay);
|
||||||
|
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.textContent = `
|
||||||
|
#shipyard-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 450;
|
||||||
|
color: #f4d99a;
|
||||||
|
font-family: Georgia, 'Times New Roman', serif;
|
||||||
|
touch-action: pan-y;
|
||||||
|
}
|
||||||
|
|
||||||
|
#shipyard-overlay[hidden] { display: none; }
|
||||||
|
|
||||||
|
.shipyard-backdrop {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(3, 11, 16, .9);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
-webkit-backdrop-filter: blur(8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.shipyard-shell {
|
||||||
|
position: relative;
|
||||||
|
width: min(1180px, calc(100vw - 24px));
|
||||||
|
max-height: calc(100dvh - 20px);
|
||||||
|
margin: max(10px, env(safe-area-inset-top)) auto;
|
||||||
|
padding: 10px;
|
||||||
|
overflow: auto;
|
||||||
|
border: 1px solid rgba(211, 157, 65, .6);
|
||||||
|
border-radius: 14px;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 50% 5%, rgba(20, 70, 83, .34), transparent 38%),
|
||||||
|
linear-gradient(180deg, #061b24 0%, #041218 100%);
|
||||||
|
box-shadow: 0 18px 60px rgba(0, 0, 0, .65);
|
||||||
|
overscroll-behavior: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shipyard-header {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shipyard-title {
|
||||||
|
width: min(595px, 76vw);
|
||||||
|
height: auto;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shipyard-close {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: max(0px, env(safe-area-inset-right));
|
||||||
|
width: clamp(48px, 7vw, 72px);
|
||||||
|
aspect-ratio: 1;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
touch-action: manipulation;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shipyard-close img { width: 100%; height: 100%; object-fit: contain; }
|
||||||
|
|
||||||
|
.shipyard-resources {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin: 4px auto 10px;
|
||||||
|
font-size: clamp(11px, 1.6vw, 15px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.shipyard-resources span {
|
||||||
|
min-width: 120px;
|
||||||
|
padding: 7px 12px;
|
||||||
|
border: 1px solid rgba(198, 145, 59, .42);
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(3, 20, 27, .88);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shipyard-resources strong { color: #fff0bd; }
|
||||||
|
|
||||||
|
.shipyard-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(170px, .72fr) minmax(260px, 1fr) minmax(360px, 1.2fr);
|
||||||
|
gap: 10px;
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shipyard-selector img,
|
||||||
|
.shipyard-preview,
|
||||||
|
.shipyard-upgrades {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shipyard-preview-wrap,
|
||||||
|
.shipyard-upgrade-wrap { position: relative; }
|
||||||
|
|
||||||
|
.shipyard-live-stats {
|
||||||
|
position: absolute;
|
||||||
|
left: 5%;
|
||||||
|
right: 5%;
|
||||||
|
bottom: 3%;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 4px 10px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border: 1px solid rgba(213, 162, 76, .48);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(3, 18, 24, .86);
|
||||||
|
font-size: clamp(10px, 1.25vw, 13px);
|
||||||
|
text-shadow: 0 1px 2px #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shipyard-live-stats strong {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
color: #fff0bd;
|
||||||
|
letter-spacing: .12em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shipyard-upgrade-hotspots {
|
||||||
|
position: absolute;
|
||||||
|
left: 3.5%;
|
||||||
|
right: 3.5%;
|
||||||
|
top: 18%;
|
||||||
|
bottom: 10%;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 2.5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shipyard-upgrade-button {
|
||||||
|
position: relative;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
touch-action: manipulation;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shipyard-upgrade-button:not(:disabled):active { background: rgba(255, 214, 120, .12); }
|
||||||
|
.shipyard-upgrade-button:disabled { cursor: default; }
|
||||||
|
|
||||||
|
.shipyard-upgrade-state {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
bottom: 3px;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
padding: 2px 5px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(4, 16, 21, .88);
|
||||||
|
color: #ffe4a1;
|
||||||
|
font: 700 clamp(8px, 1vw, 11px)/1 system-ui, sans-serif;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shipyard-upgrade-button.installed .shipyard-upgrade-state { color: #aef0b8; }
|
||||||
|
.shipyard-upgrade-button.locked .shipyard-upgrade-state { color: #d8a8a0; }
|
||||||
|
|
||||||
|
.shipyard-active-plate {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
bottom: -58px;
|
||||||
|
width: min(255px, 55%);
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.shipyard-active-plate img { display: block; width: 100%; }
|
||||||
|
.shipyard-active-plate span {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
color: #b9efc0;
|
||||||
|
font-size: clamp(9px, 1.15vw, 13px);
|
||||||
|
letter-spacing: .08em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shipyard-footer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 12px;
|
||||||
|
min-height: 72px;
|
||||||
|
margin-top: 56px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shipyard-salvage {
|
||||||
|
min-width: 150px;
|
||||||
|
min-height: 44px;
|
||||||
|
padding: 7px 14px;
|
||||||
|
border: 1px solid #b88131;
|
||||||
|
border-radius: 9px;
|
||||||
|
background: linear-gradient(#174e43, #0d302a);
|
||||||
|
color: #e6f5d0;
|
||||||
|
font: 700 12px/1.15 Georgia, serif;
|
||||||
|
touch-action: manipulation;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shipyard-salvage:disabled { opacity: .35; }
|
||||||
|
|
||||||
|
.shipyard-message {
|
||||||
|
min-height: 1.2em;
|
||||||
|
max-width: 50%;
|
||||||
|
color: #fff0bd;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.shipyard-open #mobile-controls { visibility: hidden; }
|
||||||
|
|
||||||
|
@media (max-width: 760px), (max-height: 520px) {
|
||||||
|
.shipyard-shell {
|
||||||
|
width: calc(100vw - 8px);
|
||||||
|
max-height: calc(100dvh - 8px);
|
||||||
|
margin: 4px auto;
|
||||||
|
padding: 6px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shipyard-header { min-height: 46px; }
|
||||||
|
.shipyard-title { width: min(460px, 68vw); }
|
||||||
|
.shipyard-close { width: 46px; }
|
||||||
|
|
||||||
|
.shipyard-resources { gap: 4px; margin: 2px 0 6px; }
|
||||||
|
.shipyard-resources span { min-width: 0; padding: 4px 7px; font-size: 10px; }
|
||||||
|
|
||||||
|
.shipyard-grid {
|
||||||
|
grid-template-columns: minmax(150px, .8fr) minmax(330px, 1.55fr);
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shipyard-selector { display: none; }
|
||||||
|
|
||||||
|
.shipyard-preview-wrap { max-height: 64dvh; overflow: hidden; }
|
||||||
|
.shipyard-preview {
|
||||||
|
height: min(64dvh, 430px);
|
||||||
|
width: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
object-position: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shipyard-live-stats {
|
||||||
|
left: 3%;
|
||||||
|
right: 3%;
|
||||||
|
bottom: 2%;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 2px;
|
||||||
|
padding: 5px 7px;
|
||||||
|
font-size: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shipyard-upgrade-wrap { align-self: center; }
|
||||||
|
.shipyard-active-plate { bottom: -46px; width: 45%; }
|
||||||
|
|
||||||
|
.shipyard-footer {
|
||||||
|
min-height: 54px;
|
||||||
|
margin-top: 46px;
|
||||||
|
gap: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shipyard-salvage { min-width: 120px; min-height: 40px; font-size: 10px; }
|
||||||
|
.shipyard-message { max-width: 56%; font-size: 10px; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (orientation: portrait) and (max-width: 760px) {
|
||||||
|
.shipyard-title { width: min(500px, 80vw); }
|
||||||
|
.shipyard-grid { grid-template-columns: 1fr; }
|
||||||
|
.shipyard-preview-wrap { display: none; }
|
||||||
|
.shipyard-upgrade-wrap { width: min(96vw, 520px); margin: 0 auto; }
|
||||||
|
.shipyard-footer { margin-top: 48px; }
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
document.head.appendChild(style);
|
||||||
|
|
||||||
|
const fields = Object.fromEntries(
|
||||||
|
[...overlay.querySelectorAll('[data-shipyard]')].map((el) => [el.dataset.shipyard, el]),
|
||||||
|
);
|
||||||
|
const closeButton = overlay.querySelector('.shipyard-close');
|
||||||
|
const fitButton = overlay.querySelector('.shipyard-salvage');
|
||||||
|
const upgradeButtons = [...overlay.querySelectorAll('.shipyard-upgrade-button')];
|
||||||
|
|
||||||
|
let latestState = null;
|
||||||
|
|
||||||
|
const findStock = (id) =>
|
||||||
|
latestState?.progression?.marketStock?.find?.((module) => module.id === id) || null;
|
||||||
|
|
||||||
|
const render = (state) => {
|
||||||
|
if (!state?.progression) return;
|
||||||
|
latestState = state;
|
||||||
|
|
||||||
|
const progression = state.progression;
|
||||||
|
const stats = progression.shipStats || {};
|
||||||
|
const loadout = progression.loadout || {};
|
||||||
|
|
||||||
|
fields.port.textContent = progression.nearestPort || '--';
|
||||||
|
fields.credits.textContent = progression.credits ?? 0;
|
||||||
|
fields.scrap.textContent = progression.scrap ?? 0;
|
||||||
|
fields.inventory.textContent = progression.inventoryCount ?? 0;
|
||||||
|
fields.hull.textContent = `Hull ${stats.maxHull ?? 100}`;
|
||||||
|
fields.speed.textContent = `Speed ${stats.maxSpeed ?? 180}`;
|
||||||
|
fields.firepower.textContent = `Firepower ${stats.cannonDamage ?? 16}`;
|
||||||
|
fields.handling.textContent = `Handling ${stats.turnRate ?? 76}`;
|
||||||
|
fields.cargo.textContent = `Cargo ${stats.cargoCapacity ?? 4}`;
|
||||||
|
|
||||||
|
for (const button of upgradeButtons) {
|
||||||
|
const id = button.dataset.moduleId;
|
||||||
|
const stock = findStock(id);
|
||||||
|
const installed = Object.values(loadout).includes(id);
|
||||||
|
const stateLabel = button.querySelector('.shipyard-upgrade-state');
|
||||||
|
|
||||||
|
button.classList.toggle('installed', installed);
|
||||||
|
button.classList.toggle('locked', !stock && !installed);
|
||||||
|
button.disabled = installed || !stock || progression.credits < (stock?.price || Infinity);
|
||||||
|
|
||||||
|
stateLabel.textContent = installed
|
||||||
|
? 'INSTALLED'
|
||||||
|
: !stock
|
||||||
|
? 'NOT STOCKED'
|
||||||
|
: progression.credits < stock.price
|
||||||
|
? `NEED ¢${stock.price}`
|
||||||
|
: 'TAP TO BUY';
|
||||||
|
}
|
||||||
|
|
||||||
|
const salvage = progression.salvageModule;
|
||||||
|
fitButton.disabled = !salvage;
|
||||||
|
fitButton.textContent = salvage ? `FIT ${salvage.name}` : 'FIT SALVAGE';
|
||||||
|
fields.message.textContent = String(state.status || '').replace(/\n/g, ' ');
|
||||||
|
};
|
||||||
|
|
||||||
|
const show = (state) => {
|
||||||
|
render(state);
|
||||||
|
overlay.hidden = false;
|
||||||
|
document.body.classList.add('shipyard-open');
|
||||||
|
closeButton.focus({ preventScroll: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
const hide = () => {
|
||||||
|
overlay.hidden = true;
|
||||||
|
document.body.classList.remove('shipyard-open');
|
||||||
|
};
|
||||||
|
|
||||||
|
closeButton.addEventListener('click', () => {
|
||||||
|
dispatchAction('close-shipyard');
|
||||||
|
hide();
|
||||||
|
});
|
||||||
|
|
||||||
|
overlay.querySelector('.shipyard-backdrop').addEventListener('click', () => {
|
||||||
|
dispatchAction('close-shipyard');
|
||||||
|
hide();
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const button of upgradeButtons) {
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
const moduleId = button.dataset.moduleId;
|
||||||
|
if (!moduleId || button.disabled) return;
|
||||||
|
dispatchAction('purchase-module', { moduleId });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fitButton.addEventListener('click', () => {
|
||||||
|
if (fitButton.disabled) return;
|
||||||
|
dispatchAction('fit-salvage');
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('airship:shipyard-open', (event) => show(event.detail));
|
||||||
|
window.addEventListener('airship:shipyard-state', (event) => render(event.detail));
|
||||||
|
window.addEventListener('airship:shipyard-close', hide);
|
||||||
|
window.addEventListener('airship:hud-state', (event) => {
|
||||||
|
latestState = event.detail;
|
||||||
|
if (!overlay.hidden) render(event.detail);
|
||||||
|
});
|
||||||
|
window.addEventListener('keydown', (event) => {
|
||||||
|
if (event.key === 'Escape' && !overlay.hidden) {
|
||||||
|
dispatchAction('close-shipyard');
|
||||||
|
hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -31,9 +31,12 @@ test('installed modules derive meaningful ship stat changes', () => {
|
|||||||
test('port purchase deducts credits and installs the module', () => {
|
test('port purchase deducts credits and installs the module', () => {
|
||||||
const starter = createStarterProgression();
|
const starter = createStarterProgression();
|
||||||
const result = buyAndInstallModule(starter, 'tuned-propellers');
|
const result = buyAndInstallModule(starter, 'tuned-propellers');
|
||||||
assert.equal(result.purchased, true);
|
assert.equal(result.purchased, false);
|
||||||
assert.equal(result.credits, starter.credits - 280);
|
const funded = { ...starter, credits: 1000 };
|
||||||
assert.equal(result.loadout.engine, 'tuned-propellers');
|
const fundedResult = buyAndInstallModule(funded, 'tuned-propellers');
|
||||||
|
assert.equal(fundedResult.purchased, true);
|
||||||
|
assert.equal(fundedResult.credits, funded.credits - 800);
|
||||||
|
assert.equal(fundedResult.loadout.engine, 'tuned-propellers');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('pirate salvage is deterministic and collectible within salvage capacity', () => {
|
test('pirate salvage is deterministic and collectible within salvage capacity', () => {
|
||||||
@ -53,3 +56,13 @@ test('market can select an affordable upgrade for a port', () => {
|
|||||||
assert.ok(upgrade);
|
assert.ok(upgrade);
|
||||||
assert.ok(upgrade.price <= starter.credits);
|
assert.ok(upgrade.price <= starter.credits);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test('rudder tier matches shipyard art and improves handling', () => {
|
||||||
|
const starter = { ...createStarterProgression(), credits: 650 };
|
||||||
|
const result = buyAndInstallModule(starter, 'reinforced-rudder');
|
||||||
|
const stats = deriveShipStats(result.loadout);
|
||||||
|
assert.equal(result.purchased, true);
|
||||||
|
assert.equal(result.credits, 250);
|
||||||
|
assert.equal(stats.turnRate, 88);
|
||||||
|
});
|
||||||
|
|||||||
@ -270,20 +270,44 @@ test('mobile controls expose native buy and salvage-fit actions', async () => {
|
|||||||
read('index.html'),
|
read('index.html'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
assert.match(index, /data-action="buy-upgrade"/);
|
assert.match(index, /data-action="open-shipyard"/);
|
||||||
assert.match(index, /data-action="fit-salvage"/);
|
assert.match(index, /data-action="fit-salvage"/);
|
||||||
assert.match(index, /CustomEvent\('airship:action'/);
|
assert.match(index, /CustomEvent\('airship:action'/);
|
||||||
assert.match(game, /addEventListener\('airship:action', this\.handleMobileAction\)/);
|
assert.match(game, /addEventListener\('airship:action', this\.handleMobileAction\)/);
|
||||||
assert.match(game, /this\.pendingMobileActions\.delete\('buy-upgrade'\)/);
|
assert.match(game, /this\.pendingMobileActions\.delete\('open-shipyard'\)/);
|
||||||
assert.match(game, /this\.pendingMobileActions\.delete\('fit-salvage'\)/);
|
assert.match(game, /this\.pendingMobileActions\.delete\('fit-salvage'\)/);
|
||||||
assert.match(game, /marketUpgrade: marketUpgrade \? \{/);
|
assert.match(game, /marketUpgrade: marketUpgrade \? \{/);
|
||||||
assert.match(game, /salvageModule: salvageModule \? \{/);
|
assert.match(game, /salvageModule: salvageModule \? \{/);
|
||||||
assert.match(hud, /root\.querySelector\('\.mobile-control\.buy'\)/);
|
assert.match(hud, /root\.querySelector\('\.mobile-control\.buy'\)/);
|
||||||
assert.match(hud, /root\.querySelector\('\.mobile-control\.fit'\)/);
|
assert.match(hud, /root\.querySelector\('\.mobile-control\.fit'\)/);
|
||||||
assert.match(hud, /buyButton\.disabled = !progression\.docked \|\| !upgrade/);
|
assert.match(hud, /buyButton\.disabled = !progression\.docked/);
|
||||||
assert.match(hud, /fitButton\.disabled = !module/);
|
assert.match(hud, /fitButton\.disabled = !module/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('shipyard overlay uses supplied component art and explicit module purchases', async () => {
|
||||||
|
const [game, entry, shipyard, modules] = await Promise.all([
|
||||||
|
read('src/game-v4.js'),
|
||||||
|
read('src/game-entry.js'),
|
||||||
|
read('src/shipyard-ui.js'),
|
||||||
|
read('src/progression/modules.js'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert.match(entry, /import '.\/shipyard-ui\.js'/);
|
||||||
|
assert.match(shipyard, /shipyard-ui\/title_banner\.png\?url/);
|
||||||
|
assert.match(shipyard, /shipyard-ui\/ship_selector\.png\?url/);
|
||||||
|
assert.match(shipyard, /shipyard-ui\/ship_preview\.png\?url/);
|
||||||
|
assert.match(shipyard, /shipyard-ui\/upgrades\.png\?url/);
|
||||||
|
assert.match(shipyard, /airship:shipyard-open/);
|
||||||
|
assert.match(shipyard, /dispatchAction\('purchase-module', \{ moduleId \}\)/);
|
||||||
|
assert.match(game, /marketForPort\(port\)/);
|
||||||
|
assert.match(game, /airship:shipyard-state/);
|
||||||
|
assert.match(game, /this\.shipyardOpen = true/);
|
||||||
|
assert.match(modules, /'reinforced-rudder'/);
|
||||||
|
assert.match(modules, /price: 800/);
|
||||||
|
assert.match(modules, /price: 600/);
|
||||||
|
assert.match(modules, /price: 400/);
|
||||||
|
});
|
||||||
|
|
||||||
test('mobile merchant traffic uses a reduced render budget', async () => {
|
test('mobile merchant traffic uses a reduced render budget', async () => {
|
||||||
const source = await read('src/game-v4.js');
|
const source = await read('src/game-v4.js');
|
||||||
assert.match(source, /const merchantLimit = desktopPointer \? MAX_MERCHANTS : Math\.min\(12, MAX_MERCHANTS\)/);
|
assert.match(source, /const merchantLimit = desktopPointer \? MAX_MERCHANTS : Math\.min\(12, MAX_MERCHANTS\)/);
|
||||||
@ -363,13 +387,14 @@ test('dense clouds conceal ships and suppress long-range pirate fire', async ()
|
|||||||
assert.match(source, /concealment: this\.player\?\.getData\?\.\('cloudConcealment'\) \|\| 0/);
|
assert.match(source, /concealment: this\.player\?\.getData\?\.\('cloudConcealment'\) \|\| 0/);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('mobile HUD skips hidden Phaser rendering and throttles DOM state publishing', async () => {
|
test('HUD skips hidden Phaser rendering while DOM state remains available to overlays', async () => {
|
||||||
const source = await read('src/game-v4.js');
|
const source = await read('src/game-v4.js');
|
||||||
|
|
||||||
assert.match(source, /const HUD_PUBLISH_INTERVAL_MS = 50/);
|
assert.match(source, /const HUD_PUBLISH_INTERVAL_MS = 50/);
|
||||||
assert.match(source, /if \(!this\.mobileHudActive\) this\.drawHud\(hudState\)/);
|
assert.match(source, /if \(!this\.mobileHudActive\) this\.drawHud\(hudState\)/);
|
||||||
assert.match(source, /publishHudState\(state = this\.buildHudState\(\), force = false\)/);
|
assert.match(source, /publishHudState\(state = this\.buildHudState\(\), force = false\)/);
|
||||||
assert.match(source, /if \(!this\.mobileHudActive \|\| !state\) return/);
|
assert.match(source, /if \(!state\) return/);
|
||||||
|
assert.match(source, /if \(this\.mobileHudActive && !force && now - this\.lastHudPublishAt < HUD_PUBLISH_INTERVAL_MS\) return/);
|
||||||
assert.match(source, /now - this\.lastHudPublishAt < HUD_PUBLISH_INTERVAL_MS/);
|
assert.match(source, /now - this\.lastHudPublishAt < HUD_PUBLISH_INTERVAL_MS/);
|
||||||
assert.match(source, /drawHud\(state = this\.buildHudState\(\)\)/);
|
assert.match(source, /drawHud\(state = this\.buildHudState\(\)\)/);
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user