diff --git a/src/game-v4.js b/src/game-v4.js index 81f3816..1ff5fae 100644 --- a/src/game-v4.js +++ b/src/game-v4.js @@ -4,6 +4,13 @@ import airshipSheetUrl from './assets/airships.svg?url'; import playerAirshipUrl from './assets/player-airship-4frame.png?url'; import explosionSheetUrl from './assets/explosions.svg?url'; import cannonballUrl from './assets/cannonball.png?url'; +import smallIslandUrl from './assets/islands/small_island.png?url'; +import mediumIslandUrl from './assets/islands/medium_island.png?url'; +import rockyIslandUrl from './assets/islands/rocky_island.png?url'; +import pirateIslandUrl from './assets/islands/pirate_island.png?url'; +import dockIslandUrl from './assets/islands/dock_island.png?url'; +import tradingOutpostIslandUrl from './assets/islands/trading_outpost_island.png?url'; +import volcanicIslandUrl from './assets/islands/volcanic_island.png?url'; import { airshipLog, registerDebugSnapshot } from './diagnostics.js'; import { DEFAULT_PIRATE_SHOT_DAMAGE, @@ -190,6 +197,13 @@ class GameScene extends Phaser.Scene { frameHeight: 64, }); this.load.image('cannonball', cannonballUrl); + this.load.image('island-small', smallIslandUrl); + this.load.image('island-medium', mediumIslandUrl); + this.load.image('island-rocky', rockyIslandUrl); + this.load.image('island-pirate', pirateIslandUrl); + this.load.image('island-dock', dockIslandUrl); + this.load.image('island-trading-outpost', tradingOutpostIslandUrl); + this.load.image('island-volcanic', volcanicIslandUrl); } create() { @@ -1359,6 +1373,22 @@ class GameScene extends Phaser.Scene { return pirate; } + islandTextureFor(island) { + const port = this.world.ports.find(({ islandId }) => islandId === island.id); + const pirateHaven = this.world.pirateTerritories + .some(({ havenIslandId }) => havenIslandId === island.id); + + if (pirateHaven) return 'island-pirate'; + if (port?.type === 'capital') return 'island-trading-outpost'; + if (port?.type === 'industrial' || port?.type === 'fuel') return 'island-trading-outpost'; + if (port?.type === 'military' || port?.type === 'trade') return 'island-dock'; + if (island.resource === 'fuel') return 'island-volcanic'; + if (island.resource === 'ore') return 'island-rocky'; + + const islandNumber = Number(String(island.id).split('-').pop()) || 0; + return islandNumber % 2 === 0 ? 'island-medium' : 'island-small'; + } + drawWorld() { const bg = this.add.graphics(); bg.fillStyle(0x132335, 1); @@ -1383,42 +1413,41 @@ class GameScene extends Phaser.Scene { routeGraphics.lineBetween(route.from.x, route.from.y, route.to.x, route.to.y); } - const islandGraphics = this.add.graphics().setDepth(2); for (const island of this.world.islands) { - islandGraphics.fillStyle(0x5d694e, 1); - islandGraphics.fillEllipse( - island.x, - island.y + 8, - island.radius * 2.15, - island.radius * 1.45, - ); - islandGraphics.fillStyle(0xa18a62, 1); - islandGraphics.fillEllipse( - island.x, - island.y, - island.radius * 1.78, - island.radius * 1.08, - ); - islandGraphics.fillStyle(0x283827, 0.95); - islandGraphics.fillCircle( - island.x - island.radius * 0.2, - island.y - island.radius * 0.18, - Math.max(8, island.radius * 0.16), - ); + const texture = this.islandTextureFor(island); + const art = this.add.image(island.x, island.y, texture).setDepth(2); + const targetDiameter = island.radius * 2.45; + const sourceSize = Math.max(1, art.width, art.height); + art.setScale(targetDiameter / sourceSize); + art.setData('islandId', island.id); + art.setData('collisionRadius', island.radius); } for (const port of this.world.ports) { - const portMarker = this.add.circle(port.x, port.y, 9, 0xf4d27a, 0.95) + const island = this.world.islands.find(({ id }) => id === port.islandId); + const markerOffset = Math.max(20, (island?.radius || 90) * 0.62); + const portMarker = this.add.circle( + port.x + markerOffset * 0.38, + port.y + markerOffset * 0.32, + 7, + 0xf4d27a, + 0.95, + ) .setStrokeStyle(2, 0x4a3016, 0.95) .setDepth(3); portMarker.setData('portId', port.id); - this.add.text(port.x + 13, port.y - 8, port.name, { - fontFamily: 'monospace', - fontSize: '11px', - color: '#f5df9a', - backgroundColor: '#071018aa', - padding: { x: 3, y: 2 }, - }).setDepth(3); + this.add.text( + port.x - (island?.radius || 90) * 0.68, + port.y + (island?.radius || 90) * 0.72, + port.name, + { + fontFamily: 'monospace', + fontSize: '11px', + color: '#f5df9a', + backgroundColor: '#071018cc', + padding: { x: 4, y: 3 }, + }, + ).setDepth(3); } } diff --git a/tests/wiring.test.mjs b/tests/wiring.test.mjs index 346a19a..623e792 100644 --- a/tests/wiring.test.mjs +++ b/tests/wiring.test.mjs @@ -4,6 +4,26 @@ import { readFile } from 'node:fs/promises'; const read = (path) => readFile(new URL(`../${path}`, import.meta.url), 'utf8'); +test('procedural world renders uploaded steampunk island variants while keeping soft terrain physics', async () => { + const source = await read('src/game-v4.js'); + + assert.match(source, /small_island\.png\?url/); + assert.match(source, /medium_island\.png\?url/); + assert.match(source, /rocky_island\.png\?url/); + assert.match(source, /pirate_island\.png\?url/); + assert.match(source, /dock_island\.png\?url/); + assert.match(source, /trading_outpost_island\.png\?url/); + assert.match(source, /volcanic_island\.png\?url/); + assert.match(source, /islandTextureFor\(island\)/); + assert.match(source, /pirateHaven/); + assert.match(source, /port\?\.type === 'capital'/); + assert.match(source, /island\.resource === 'fuel'/); + assert.match(source, /this\.add\.image\(island\.x, island\.y, texture\)/); + assert.doesNotMatch(source, /islandGraphics\.fillEllipse/); + assert.match(source, /this\.physics\.add\.overlap\(this\.player, obstacle/); + assert.doesNotMatch(source, /this\.physics\.add\.collider\(this\.player, obstacle/); +}); + test('player settings owns account, multiplayer and local flight preferences', async () => { const [entry, settings, multiplayer, game] = await Promise.all([ read('src/game-entry.js'),