Spaces:
Sleeping
Sleeping
File size: 4,692 Bytes
c4cf09a dd14326 c4cf09a dd14326 c4cf09a dd14326 c4cf09a dd14326 c4cf09a dd14326 c4cf09a dd14326 c4cf09a dd14326 c4cf09a dd14326 c4cf09a dd14326 c4cf09a dd14326 c4cf09a dd14326 c4cf09a dd14326 c4cf09a dd14326 95f6015 c4cf09a dd14326 c4cf09a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 | 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}`);
});
|