UNPKG

lab13-sdk

Version:

JS13K Online communication protocol SDK for multiplayer games.

176 lines (175 loc) 6.34 kB
//#region src/index.ts const generateId = () => { const t = Date.now().toString(36); const r = Math.random().toString(36).slice(2, 8); return (t + r).slice(-16); }; const Lab13Client = (socket, options) => { const { bot = false } = options || {}; let playerId = null; const clientIds = /* @__PURE__ */ new Set(); const playerIds = /* @__PURE__ */ new Set(); const botIds = /* @__PURE__ */ new Set(); const clientType = bot ? "bot" : "player"; let state = {}; const tombstoneEntities = /* @__PURE__ */ new Set(); function deepMerge(target, source, isEntityCollection = false) { const result = { ...target }; for (const key in source) { if (tombstoneEntities.has(key) && isEntityCollection) continue; if (source[key] === null) { delete result[key]; if (isEntityCollection) tombstoneEntities.add(key); } else if (source[key] && typeof source[key] === "object" && !Array.isArray(source[key])) result[key] = deepMerge(result[key] || {}, source[key], key.startsWith("_")); else result[key] = source[key]; } return result; } socket.addEventListener("message", (event) => { const msg = event.data.toString(); console.log("got message", msg.slice(0, 1)); switch (msg[0]) { case "@": playerId = msg.slice(1); if (bot) api.sendToAll(`b${playerId}`); socket.dispatchEvent(new CustomEvent("player-id-updated", { detail: playerId })); break; case "b": const botId = msg.slice(1); clientIds.add(botId); botIds.add(botId); playerIds.delete(botId); if (state._players && state._players[botId]) delete state._players[botId]; console.log("got bot"); socket.dispatchEvent(new CustomEvent("client-ids-updated", { detail: Array.from(clientIds) })); socket.dispatchEvent(new CustomEvent("player-ids-updated", { detail: Array.from(playerIds) })); socket.dispatchEvent(new CustomEvent("bot-ids-updated", { detail: Array.from(botIds) })); break; case "+": const newClientId = msg.slice(1); clientIds.add(newClientId); botIds.delete(newClientId); playerIds.add(newClientId); if (!state._players) state._players = {}; state._players[newClientId] = { id: newClientId }; socket.dispatchEvent(new CustomEvent("client-connected", { detail: newClientId })); socket.dispatchEvent(new CustomEvent("client-ids-updated", { detail: Array.from(clientIds) })); socket.dispatchEvent(new CustomEvent("player-ids-updated", { detail: Array.from(playerIds) })); socket.dispatchEvent(new CustomEvent("bot-ids-updated", { detail: Array.from(botIds) })); break; case "-": const disconnectedClientId = msg.slice(1); clientIds.delete(disconnectedClientId); playerIds.delete(disconnectedClientId); botIds.delete(disconnectedClientId); if (state._players && state._players[disconnectedClientId]) { delete state._players[disconnectedClientId]; tombstoneEntities.add(disconnectedClientId); } socket.dispatchEvent(new CustomEvent("client-disconnected", { detail: disconnectedClientId })); socket.dispatchEvent(new CustomEvent("client-ids-updated", { detail: Array.from(clientIds) })); socket.dispatchEvent(new CustomEvent("player-ids-updated", { detail: Array.from(playerIds) })); socket.dispatchEvent(new CustomEvent("bot-ids-updated", { detail: Array.from(botIds) })); break; case "?": { const subCommand = msg[1]; switch (subCommand) { case "i": const replyPlayerId = msg.slice(2); if (playerId) api.sendToPlayer(replyPlayerId, `.i${playerId}${bot ? "|b" : ""}`); break; case "s": const replyId = msg.slice(2); api.sendToPlayer(replyId, `.s${JSON.stringify(state)}`); break; default: break; } } break; case ".": { const subCommand = msg[1]; switch (subCommand) { case "i": const [newClientId$1, isBot] = msg.slice(2).split("|"); clientIds.add(newClientId$1); if (isBot) { botIds.add(newClientId$1); playerIds.delete(newClientId$1); if (state._players && state._players[newClientId$1]) delete state._players[newClientId$1]; } else { playerIds.add(newClientId$1); botIds.delete(newClientId$1); if (!state._players) state._players = {}; if (!state._players[newClientId$1]) state._players[newClientId$1] = { id: newClientId$1 }; } socket.dispatchEvent(new CustomEvent("client-ids-updated", { detail: Array.from(clientIds) })); socket.dispatchEvent(new CustomEvent("player-ids-updated", { detail: Array.from(playerIds) })); socket.dispatchEvent(new CustomEvent("bot-ids-updated", { detail: Array.from(botIds) })); break; case "s": try { state = JSON.parse(msg.slice(2)); socket.dispatchEvent(new CustomEvent("state-updated", { detail: { state } })); } catch (e) { console.error("Failed to parse state data:", e); } break; default: break; } } break; case "d": try { const delta = JSON.parse(msg.slice(1)); state = deepMerge(state, delta); socket.dispatchEvent(new CustomEvent("state-updated", { detail: { state, delta } })); } catch (e) { console.error("Failed to parse delta:", e); } break; default: break; } }); const api = { queryPlayerIds: () => { if (playerId) socket.send(`?i${playerId}`); }, on(event, callback) { socket.addEventListener(event, callback); }, off(event, callback) { socket.removeEventListener(event, callback); }, playerId: () => playerId, clientIds: () => Array.from(clientIds), playerIds: () => Array.from(playerIds), botIds: () => Array.from(botIds), clientType: () => clientType, sendToPlayer: (playerId$1, message) => { socket.send(`@${playerId$1}|${message}`); }, sendToAll: (message) => { socket.send(`${message}`); }, state: () => state, mutateState: (delta) => { state = deepMerge(state, delta); socket.send(`d${JSON.stringify(delta)}`); socket.dispatchEvent(new CustomEvent("state-updated", { detail: { state, delta } })); }, generateId }; return api; }; if (typeof window !== "undefined") window.Lab13Client = Lab13Client; //#endregion export { Lab13Client, generateId }; //# sourceMappingURL=index.js.map