mlmihjaz's picture
Complete game: Laser Tag Party — name join, blaster w/ unlimited ammo, 5-heart elimination, WebAudio sounds, trees/rocks/houses/tower/lakes, bigger map
dd14326
Raw
History Blame Contribute Delete
4.69 kB
const express = require('express');
const http = require('http');
const WebSocket = require('ws');
const path = require('path');
const url = require('url');
const app = express();
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });
app.use(express.static(path.join(__dirname, 'public')));
const PORT = process.env.PORT || 7860;
// ---- Game settings ----
const MAX_HP = 5; // 5 laser hits and you're out — no time limit, unlimited ammo
// id -> { id, name, color, position:[x,y,z], rotationY, score, hp }
const players = new Map();
const COLORS = [
0xff5555, 0x55ff55, 0x5599ff, 0xffdd55,
0xff55dd, 0x55ffdd, 0xffa500, 0xaa55ff,
];
let colorIndex = 0;
let nextId = 1;
function scoreList() {
return Array.from(players.values()).map((p) => ({ id: p.id, name: p.name, score: p.score }));
}
function broadcast(data, exceptWs) {
const msg = JSON.stringify(data);
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN && client !== exceptWs) {
client.send(msg);
}
});
}
wss.on('connection', (ws, req) => {
const id = 'player-' + (nextId++);
const color = COLORS[colorIndex % COLORS.length];
colorIndex++;
const query = url.parse(req.url, true).query;
const name =
(query.name || '').toString().replace(/[<>]/g, '').trim().slice(0, 16) ||
'Player ' + (nextId - 1);
const initialState = {
id,
name,
color,
position: [0, 1.6, 0],
rotationY: 0,
score: 0,
hp: MAX_HP,
};
players.set(id, initialState);
ws.playerId = id;
ws.lastHitAt = 0;
// Tell the new client who they are + who's already here
ws.send(JSON.stringify({
type: 'init',
id,
name,
color,
hp: MAX_HP,
maxHp: MAX_HP,
players: Array.from(players.values()).filter((p) => p.id !== id),
scores: scoreList(),
}));
// Tell everyone else someone new joined
broadcast({ type: 'join', ...initialState }, ws);
console.log(`[+] ${id} "${name}" connected (${wss.clients.size} online)`);
ws.on('message', (raw) => {
let data;
try {
data = JSON.parse(raw);
} catch (e) {
return;
}
if (data.type === 'update' && Array.isArray(data.position)) {
const p = players.get(id);
if (!p) return;
p.position = data.position;
p.rotationY = data.rotationY || 0;
broadcast({ type: 'update', id, position: p.position, rotationY: p.rotationY }, ws);
} else if (data.type === 'shoot' && Array.isArray(data.from) && Array.isArray(data.to)) {
// Relay the laser shot so everyone else can draw/hear it
broadcast({ type: 'shoot', id, from: data.from, to: data.to }, ws);
} else if (data.type === 'hit' && typeof data.target === 'string') {
const shooter = players.get(id);
const target = players.get(data.target);
if (!shooter || !target || data.target === id) return;
const now = Date.now();
if (now - ws.lastHitAt < 150) return; // simple spam guard
ws.lastHitAt = now;
shooter.score++;
target.hp--;
if (target.hp <= 0) {
// 5 hits — the player is out of the game
broadcast({
type: 'eliminated',
target: data.target,
targetName: target.name,
by: id,
byName: shooter.name,
scores: scoreList(),
});
// Kick the eliminated player's socket shortly after so everyone sees them leave
let targetWs = null;
wss.clients.forEach((c) => { if (c.playerId === data.target) targetWs = c; });
if (targetWs) {
setTimeout(() => { try { targetWs.close(); } catch (e) {} }, 500);
}
} else {
broadcast({
type: 'tagged',
target: data.target,
targetName: target.name,
targetHp: target.hp,
by: id,
byName: shooter.name,
scores: scoreList(),
});
}
} else if (data.type === 'signal' && data.target) {
// Relay a WebRTC signaling message (offer/answer/ICE) to one specific peer.
let targetWs = null;
wss.clients.forEach((c) => {
if (c.playerId === data.target && c.readyState === WebSocket.OPEN) targetWs = c;
});
if (targetWs) {
targetWs.send(JSON.stringify({ type: 'signal', from: id, data: data.data }));
}
}
});
ws.on('close', () => {
players.delete(id);
broadcast({ type: 'leave', id, scores: scoreList() });
console.log(`[-] ${id} disconnected (${wss.clients.size} online)`);
});
ws.on('error', () => {
// swallow - close event will still fire
});
});
server.listen(PORT, () => {
console.log(`Server listening on port ${PORT}`);
});