diff --git a/src/game-v4.js b/src/game-v4.js index a90ca90..549bc60 100644 --- a/src/game-v4.js +++ b/src/game-v4.js @@ -159,6 +159,7 @@ class GameScene extends Phaser.Scene { this.multiplayerHasAuthoritativeState = false; this.multiplayerDead = false; this.multiplayerLastHull = null; + this.multiplayerServerClockOffsetMs = 0; this.multiplayerSendAccumulatorMs = 0; airshipLog('scene:init', { @@ -226,7 +227,12 @@ class GameScene extends Phaser.Scene { this.createPlayerSmoke(); this.handleMultiplayerSnapshot = (event) => { - this.syncMultiplayerSnapshot(event.detail || {}); + const detail = event.detail || {}; + const serverTime = Number(detail.serverTime) || 0; + if (serverTime > 0) { + this.multiplayerServerClockOffsetMs = serverTime - Date.now(); + } + this.syncMultiplayerSnapshot(detail); }; this.handleMultiplayerState = (event) => { if (event.detail?.state === 'online') return; @@ -242,6 +248,7 @@ class GameScene extends Phaser.Scene { }; window.addEventListener('airship:multiplayer-snapshot', this.handleMultiplayerSnapshot); window.addEventListener('airship:multiplayer-state', this.handleMultiplayerState); + window.airshipMultiplayer?.setWeatherRoutes?.(this.world.tradeRoutes); this.pirates = this.physics.add.group(); this.merchants = this.physics.add.group(); @@ -362,6 +369,25 @@ class GameScene extends Phaser.Scene { + weatherTimeMs() { + if (window.airshipMultiplayer?.isConnected?.()) { + return Date.now() + (this.multiplayerServerClockOffsetMs || 0); + } + return this.time?.now ?? 0; + } + + weatherWindAt(x, y, timeMs = this.weatherTimeMs()) { + return environmentWindAt({ + x, + y, + timeMs, + islands: this.world?.islands, + routes: this.world?.tradeRoutes, + worldWidth: this.world?.width || WORLD_SIZE, + worldHeight: this.world?.height || WORLD_SIZE, + }); + } + createRollingClouds() { if (!this.textures.exists('rolling-cloud')) { const cloudTexture = this.make.graphics({ x: 0, y: 0, add: false }); @@ -434,15 +460,10 @@ class GameScene extends Phaser.Scene { if (!this.rollingClouds?.length) return; const dt = Math.min(deltaMs / 1000, 0.05); - const now = this.time?.now ?? 0; + const now = this.weatherTimeMs(); for (const cloud of this.rollingClouds) { - const wind = environmentWindAt({ - x: cloud.x, - y: cloud.y, - timeMs: now, - islands: this.world?.islands, - }); + const wind = this.weatherWindAt(cloud.x, cloud.y, now); const driftScale = cloud.getData('driftScale') || 1; cloud.x += wind.x * driftScale * dt; cloud.y += wind.y * driftScale * dt; @@ -459,8 +480,17 @@ class GameScene extends Phaser.Scene { const weatherPhase = cloud.getData('weatherPhase') || 0; const cyclePosition = (now % weatherCycleMs) / weatherCycleMs; const weatherWave = 0.5 + 0.5 * Math.sin(cyclePosition * Math.PI * 2 + weatherPhase); - const windPresence = clamp((wind.strength - 5) / 18, 0, 1); - const targetPresence = clamp(weatherWave * 0.72 + windPresence * 0.38 - 0.12, 0, 1); + const windPresence = clamp((wind.strength - 5) / 24, 0, 1); + const routeCloudiness = 0.32 + + (1 - (wind.routeInfluence || 0)) * 0.68 + + (wind.routeEdge || 0) * 0.3 + + (wind.stormIntensity || 0) * 0.85; + const targetPresence = clamp( + (weatherWave * 0.62 + windPresence * 0.32 - 0.1) * routeCloudiness + + (wind.stormIntensity || 0) * 0.28, + 0, + 1, + ); const previousPresence = cloud.getData('presence') ?? targetPresence; presence = Phaser.Math.Linear(previousPresence, targetPresence, Math.min(1, dt * 0.7)); cloud.setData('presence', presence); @@ -975,12 +1005,7 @@ class GameScene extends Phaser.Scene { const spread = Phaser.Math.FloatBetween(-0.16, 0.16); const trailHeading = heading + Math.PI + spread; const trailSpeed = Phaser.Math.Linear(16, 46, effectiveForwardThrust) * Phaser.Math.FloatBetween(0.82, 1.16); - const localWind = environmentWindAt({ - x: sternX, - y: sternY, - timeMs: this.time.now, - islands: this.world?.islands, - }); + const localWind = this.weatherWindAt(sternX, sternY); const velocityX = Math.cos(trailHeading) * trailSpeed + localWind.x * 1.4; const velocityY = Math.sin(trailHeading) * trailSpeed + localWind.y * 1.4; @@ -1190,12 +1215,7 @@ class GameScene extends Phaser.Scene { merchant.angle += clamp(delta, -34 * dt, 34 * dt); const h = headingRad(merchant); - const wind = environmentWindAt({ - x: merchant.x, - y: merchant.y, - timeMs: this.time.now, - islands: this.world?.islands, - }); + const wind = this.weatherWindAt(merchant.x, merchant.y); const velocity = velocityWithEnvironment( Math.cos(h) * (merchant.getData('speed') || 76), Math.sin(h) * (merchant.getData('speed') || 76), @@ -1780,14 +1800,15 @@ class GameScene extends Phaser.Scene { this.player.anims.timeScale = 1; } - this.environmentWind = environmentWindAt({ - x: this.player.x, - y: this.player.y, - timeMs: this.time.now, - islands: this.world?.islands, - }); + this.environmentWind = this.weatherWindAt(this.player.x, this.player.y); const h = headingRad(this.player); - this.player.setVelocity(Math.cos(h) * this.playerSpeed, Math.sin(h) * this.playerSpeed); + const predictedVelocity = velocityWithEnvironment( + Math.cos(h) * this.playerSpeed, + Math.sin(h) * this.playerSpeed, + this.environmentWind, + 1, + ); + this.player.setVelocity(predictedVelocity.x, predictedVelocity.y); if (Phaser.Input.Keyboard.JustDown(this.keys.firePort)) this.playerBroadside(-1); if (Phaser.Input.Keyboard.JustDown(this.keys.fireStarboard)) this.playerBroadside(1); @@ -1836,12 +1857,7 @@ class GameScene extends Phaser.Scene { } const h = headingRad(this.player); - this.environmentWind = environmentWindAt({ - x: this.player.x, - y: this.player.y, - timeMs: this.time.now, - islands: this.world?.islands, - }); + this.environmentWind = this.weatherWindAt(this.player.x, this.player.y); const playerVelocity = velocityWithEnvironment( Math.cos(h) * this.playerSpeed, Math.sin(h) * this.playerSpeed, @@ -1902,12 +1918,7 @@ class GameScene extends Phaser.Scene { baseSpeed, }); const h = headingRad(pirate); - const pirateWind = environmentWindAt({ - x: pirate.x, - y: pirate.y, - timeMs: this.time.now, - islands: this.world?.islands, - }); + const pirateWind = this.weatherWindAt(pirate.x, pirate.y); const pirateVelocity = velocityWithEnvironment( Math.cos(h) * speed, Math.sin(h) * speed, @@ -1984,12 +1995,7 @@ class GameScene extends Phaser.Scene { shot.setData('projectileOwner', actor); shot.setData('sourceShip', ship); if (actor === 'pirate') shot.setData('playerHitQueued', false); - const shotWind = environmentWindAt({ - x, - y, - timeMs: this.time.now, - islands: this.world?.islands, - }); + const shotWind = this.weatherWindAt(x, y); const shotVelocity = velocityWithEnvironment( Math.cos(angle) * SHOT_SPEED * velocityScale + ship.body.velocity.x * 0.25, Math.sin(angle) * SHOT_SPEED * velocityScale + ship.body.velocity.y * 0.25, @@ -2334,6 +2340,10 @@ class GameScene extends Phaser.Scene { windSpeed: this.environmentWind?.strength || 0, windHeading: this.environmentWind?.heading || 0, concealment: this.player?.getData?.('cloudConcealment') || 0, + weatherZone: this.environmentWind?.zone || 'open-sky', + routeInfluence: this.environmentWind?.routeInfluence || 0, + routeEdge: this.environmentWind?.routeEdge || 0, + stormIntensity: this.environmentWind?.stormIntensity || 0, }, trade: { activeMerchants: this.merchants?.countActive?.(true) || 0,