Send authoritative multiplayer inputs from browser
This commit is contained in:
parent
7cf6103544
commit
909abb1487
@ -8,12 +8,15 @@ const KEYCLOAK_CONFIG = {
|
|||||||
clientId: 'airship-web',
|
clientId: 'airship-web',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
|
||||||
|
|
||||||
const keycloak = new Keycloak(KEYCLOAK_CONFIG);
|
const keycloak = new Keycloak(KEYCLOAK_CONFIG);
|
||||||
let socket = null;
|
let socket = null;
|
||||||
let selfId = null;
|
let selfId = null;
|
||||||
let reconnectTimer = null;
|
let reconnectTimer = null;
|
||||||
let authenticated = false;
|
let authenticated = false;
|
||||||
let playerCount = 0;
|
let playerCount = 0;
|
||||||
|
let inputSequence = 0;
|
||||||
|
|
||||||
const statusButton = document.createElement('button');
|
const statusButton = document.createElement('button');
|
||||||
statusButton.id = 'multiplayer-status';
|
statusButton.id = 'multiplayer-status';
|
||||||
@ -30,7 +33,7 @@ style.textContent = `
|
|||||||
left: max(10px, env(safe-area-inset-left));
|
left: max(10px, env(safe-area-inset-left));
|
||||||
z-index: 410;
|
z-index: 410;
|
||||||
min-height: 38px;
|
min-height: 38px;
|
||||||
max-width: 180px;
|
max-width: 190px;
|
||||||
padding: 7px 11px;
|
padding: 7px 11px;
|
||||||
border: 1px solid rgba(220, 168, 79, .72);
|
border: 1px solid rgba(220, 168, 79, .72);
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
@ -95,6 +98,7 @@ const connect = async () => {
|
|||||||
socket = ws;
|
socket = ws;
|
||||||
|
|
||||||
ws.addEventListener('open', () => {
|
ws.addEventListener('open', () => {
|
||||||
|
inputSequence = 0;
|
||||||
setStatus('online', 'MULTIPLAYER • ONLINE');
|
setStatus('online', 'MULTIPLAYER • ONLINE');
|
||||||
emitConnection('online');
|
emitConnection('online');
|
||||||
});
|
});
|
||||||
@ -110,13 +114,24 @@ const connect = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (message.type === 'snapshot') {
|
if (message.type === 'snapshot') {
|
||||||
const players = Array.isArray(message.players)
|
const allPlayers = Array.isArray(message.players)
|
||||||
? message.players.filter((player) => player?.id && player.id !== selfId)
|
? message.players.filter((player) => player?.id)
|
||||||
: [];
|
: [];
|
||||||
playerCount = players.length + 1;
|
const self = allPlayers.find((player) => player.id === selfId) || null;
|
||||||
setStatus('online', `MULTIPLAYER • ${playerCount}`);
|
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', {
|
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) => {
|
const sendInput = (input = {}) => {
|
||||||
if (socket?.readyState !== WebSocket.OPEN || !state) return false;
|
if (socket?.readyState !== WebSocket.OPEN) return 0;
|
||||||
|
inputSequence += 1;
|
||||||
socket.send(JSON.stringify({
|
socket.send(JSON.stringify({
|
||||||
type: 'state',
|
type: 'input',
|
||||||
x: Number(state.x) || 0,
|
seq: inputSequence,
|
||||||
y: Number(state.y) || 0,
|
throttle: clamp(Number(input.throttle) || 0, -1, 1),
|
||||||
angle: Number(state.angle) || 0,
|
steer: clamp(Number(input.steer) || 0, -1, 1),
|
||||||
hull: Math.max(0, Math.round(Number(state.hull) || 0)),
|
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;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -158,10 +182,12 @@ statusButton.addEventListener('click', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
window.airshipMultiplayer = {
|
window.airshipMultiplayer = {
|
||||||
sendState,
|
sendInput,
|
||||||
|
fire,
|
||||||
login,
|
login,
|
||||||
logout,
|
logout,
|
||||||
connect,
|
connect,
|
||||||
|
getSelfId: () => selfId,
|
||||||
isAuthenticated: () => authenticated,
|
isAuthenticated: () => authenticated,
|
||||||
isConnected: () => socket?.readyState === WebSocket.OPEN,
|
isConnected: () => socket?.readyState === WebSocket.OPEN,
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user