Send authoritative multiplayer inputs from browser

This commit is contained in:
Admin 2026-09-21 01:52:50 +00:00
parent 7cf6103544
commit 909abb1487

View File

@ -8,12 +8,15 @@ const KEYCLOAK_CONFIG = {
clientId: 'airship-web',
};
const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
const keycloak = new Keycloak(KEYCLOAK_CONFIG);
let socket = null;
let selfId = null;
let reconnectTimer = null;
let authenticated = false;
let playerCount = 0;
let inputSequence = 0;
const statusButton = document.createElement('button');
statusButton.id = 'multiplayer-status';
@ -30,7 +33,7 @@ style.textContent = `
left: max(10px, env(safe-area-inset-left));
z-index: 410;
min-height: 38px;
max-width: 180px;
max-width: 190px;
padding: 7px 11px;
border: 1px solid rgba(220, 168, 79, .72);
border-radius: 999px;
@ -95,6 +98,7 @@ const connect = async () => {
socket = ws;
ws.addEventListener('open', () => {
inputSequence = 0;
setStatus('online', 'MULTIPLAYER • ONLINE');
emitConnection('online');
});
@ -110,13 +114,24 @@ const connect = async () => {
}
if (message.type === 'snapshot') {
const players = Array.isArray(message.players)
? message.players.filter((player) => player?.id && player.id !== selfId)
const allPlayers = Array.isArray(message.players)
? message.players.filter((player) => player?.id)
: [];
playerCount = players.length + 1;
setStatus('online', `MULTIPLAYER • ${playerCount}`);
const self = allPlayers.find((player) => player.id === selfId) || null;
const players = allPlayers.filter((player) => player.id !== selfId);
const projectiles = Array.isArray(message.projectiles)
? message.projectiles.filter((shot) => shot?.id)
: [];
playerCount = allPlayers.length;
const score = self ? ` • ${self.kills || 0}/${self.deaths || 0}` : '';
setStatus('online', `MULTIPLAYER • ${playerCount}${score}`);
window.dispatchEvent(new CustomEvent('airship:multiplayer-snapshot', {
detail: { players, serverTime: message.serverTime || 0 },
detail: {
self,
players,
projectiles,
serverTime: message.serverTime || 0,
},
}));
}
});
@ -134,15 +149,24 @@ const connect = async () => {
});
};
const sendState = (state) => {
if (socket?.readyState !== WebSocket.OPEN || !state) return false;
const sendInput = (input = {}) => {
if (socket?.readyState !== WebSocket.OPEN) return 0;
inputSequence += 1;
socket.send(JSON.stringify({
type: 'state',
x: Number(state.x) || 0,
y: Number(state.y) || 0,
angle: Number(state.angle) || 0,
hull: Math.max(0, Math.round(Number(state.hull) || 0)),
type: 'input',
seq: inputSequence,
throttle: clamp(Number(input.throttle) || 0, -1, 1),
steer: clamp(Number(input.steer) || 0, -1, 1),
stop: Boolean(input.stop),
docked: Boolean(input.docked),
}));
return inputSequence;
};
const fire = (side) => {
if (socket?.readyState !== WebSocket.OPEN) return false;
const normalized = side === 'port' || side === -1 ? 'port' : 'starboard';
socket.send(JSON.stringify({ type: 'fire', side: normalized }));
return true;
};
@ -158,10 +182,12 @@ statusButton.addEventListener('click', () => {
});
window.airshipMultiplayer = {
sendState,
sendInput,
fire,
login,
logout,
connect,
getSelfId: () => selfId,
isAuthenticated: () => authenticated,
isConnected: () => socket?.readyState === WebSocket.OPEN,
};