Render authenticated multiplayer presence

This commit is contained in:
Admin 2026-09-21 01:38:02 +00:00
parent 7b90701434
commit 74ecc68b17

View File

@ -144,6 +144,8 @@ class GameScene extends Phaser.Scene {
this.tradeDeliveries = 0; this.tradeDeliveries = 0;
this.tradeLosses = 0; this.tradeLosses = 0;
this.playerTradeLosses = 0; this.playerTradeLosses = 0;
this.remotePlayers = new Map();
this.multiplayerSendAccumulatorMs = 0;
airshipLog('scene:init', { airshipLog('scene:init', {
hull: this.playerHull, hull: this.playerHull,
@ -182,6 +184,11 @@ class GameScene extends Phaser.Scene {
this.player.setFrame(0); this.player.setFrame(0);
this.createPlayerSmoke(); this.createPlayerSmoke();
this.handleMultiplayerSnapshot = (event) => {
this.syncRemotePlayers(event.detail?.players || []);
};
window.addEventListener('airship:multiplayer-snapshot', this.handleMultiplayerSnapshot);
this.pirates = this.physics.add.group(); this.pirates = this.physics.add.group();
this.merchants = this.physics.add.group(); this.merchants = this.physics.add.group();
this.playerShots = this.physics.add.group(); this.playerShots = this.physics.add.group();
@ -276,6 +283,12 @@ class GameScene extends Phaser.Scene {
registerDebugSnapshot(null); registerDebugSnapshot(null);
window.removeEventListener('airship:helm-vector', this.handleHelmVector); window.removeEventListener('airship:helm-vector', this.handleHelmVector);
window.removeEventListener('airship:action', this.handleMobileAction); window.removeEventListener('airship:action', this.handleMobileAction);
window.removeEventListener('airship:multiplayer-snapshot', this.handleMultiplayerSnapshot);
for (const remote of this.remotePlayers.values()) {
remote.sprite?.destroy();
remote.label?.destroy();
}
this.remotePlayers.clear();
airshipLog('scene:shutdown', { airshipLog('scene:shutdown', {
gameState: this.gameState, gameState: this.gameState,
hull: this.playerHull, hull: this.playerHull,
@ -1401,6 +1414,7 @@ class GameScene extends Phaser.Scene {
} }
this.updateProgressionInteraction(); this.updateProgressionInteraction();
this.updateMultiplayer(deltaMs);
if (this.dockOpen) { if (this.dockOpen) {
this.player.setVelocity(0, 0); this.player.setVelocity(0, 0);
const hudState = this.buildHudState(); const hudState = this.buildHudState();
@ -1437,6 +1451,85 @@ class GameScene extends Phaser.Scene {
} }
} }
syncRemotePlayers(players = []) {
const seen = new Set();
for (const state of players) {
if (!state?.id) continue;
seen.add(state.id);
let remote = this.remotePlayers.get(state.id);
if (!remote) {
const sprite = this.add.sprite(
Number(state.x) || this.world.spawn.x,
Number(state.y) || this.world.spawn.y,
'player-airship',
0,
);
sprite
.setDepth(5.5)
.setDisplaySize(60, 114)
.setTint(0x8cd3ff)
.setAlpha(0.86);
sprite.play('player-forward');
sprite.anims.timeScale = 0.72;
const label = this.add.text(sprite.x, sprite.y - 70, state.name || 'CAPTAIN', {
fontFamily: 'monospace',
fontSize: '10px',
color: '#bfe6ff',
backgroundColor: '#071018aa',
padding: { x: 4, y: 2 },
}).setOrigin(0.5).setDepth(12);
remote = {
sprite,
label,
targetX: sprite.x,
targetY: sprite.y,
targetAngle: Number(state.angle) || 0,
hull: Number(state.hull) || 100,
};
this.remotePlayers.set(state.id, remote);
}
remote.targetX = clamp(Number(state.x) || 0, 0, this.world.width);
remote.targetY = clamp(Number(state.y) || 0, 0, this.world.height);
remote.targetAngle = normalizeDegrees(Number(state.angle) || 0);
remote.hull = Math.max(0, Number(state.hull) || 0);
remote.label.setText(`${state.name || 'CAPTAIN'} ${Math.round(remote.hull)}`);
}
for (const [id, remote] of this.remotePlayers) {
if (seen.has(id)) continue;
remote.sprite?.destroy();
remote.label?.destroy();
this.remotePlayers.delete(id);
}
}
updateMultiplayer(deltaMs) {
this.multiplayerSendAccumulatorMs += deltaMs;
if (this.multiplayerSendAccumulatorMs >= 100) {
this.multiplayerSendAccumulatorMs %= 100;
window.airshipMultiplayer?.sendState?.({
x: this.player?.x || 0,
y: this.player?.y || 0,
angle: this.player?.angle || 0,
hull: this.playerHull,
});
}
for (const remote of this.remotePlayers.values()) {
const sprite = remote.sprite;
if (!sprite?.active) continue;
sprite.x = Phaser.Math.Linear(sprite.x, remote.targetX, 0.32);
sprite.y = Phaser.Math.Linear(sprite.y, remote.targetY, 0.32);
sprite.angle += signedAngleDelta(remote.targetAngle, sprite.angle) * 0.32;
remote.label.setPosition(sprite.x, sprite.y - 70);
}
}
updatePlayer(dt, deltaMs) { updatePlayer(dt, deltaMs) {
if (!this.player?.body) return; if (!this.player?.body) return;
const stats = this.playerStats || deriveShipStats(this.progression?.loadout, this.progression?.installedConditions); const stats = this.playerStats || deriveShipStats(this.progression?.loadout, this.progression?.installedConditions);