/* RIVALS ARENA โ INTEGRATED MULTIPLAYER BUILD Original fictional arena game. Includes: - 3D browser arena - Online WebSocket multiplayer - Registration and login - Rooms and team modes - Free-for-all and team matches - Movement, jumping and sprinting - Three fictional energy blasters - Ammo, reload and weapon switching - Health, respawning and scores - Map selection and map voting - Match timer and results - Chat, friends and player reporting - XP, levels, coins and saved statistics - Shop, cosmetics and daily rewards - Missions, achievements and battle pass - Leaderboard and match history - Mobile-friendly controls Install: npm install ws Run: node server.js Open: http://localhost:3000 For internet multiplayer, deploy this server to a host supporting Node.js, persistent processes and WebSockets. */ "use strict"; const http = require("http"); const WebSocket = require("ws"); const crypto = require("crypto"); const fs = require("fs"); const path = require("path"); const PORT = process.env.PORT || 3000; const DATA_FILE = path.join(__dirname, "rivals-arena-data.json"); const MATCH_SECONDS = 180; const MAX_PLAYERS = 8; const TICK_RATE = 20; const WEAPONS = { pulse: { name: "Pulse Blaster", damage: 25, ammo: 20, reload: 1.2, cooldown: 350, range: 35 }, burst: { name: "Burst Blaster", damage: 12, ammo: 36, reload: 1.5, cooldown: 110, range: 28 }, nova: { name: "Nova Blaster", damage: 55, ammo: 6, reload: 1.8, cooldown: 750, range: 22 } }; const MAPS = [ "NeonCity", "SkyArena", "DesertBase", "CrystalFort", "SpaceStation" ]; const MODES = [ "Free For All", "Team Arena", "King Arena", "Training" ]; let database = { users: {} }; try { if (fs.existsSync(DATA_FILE)) { database = JSON.parse(fs.readFileSync(DATA_FILE, "utf8")); if (!database.users) database.users = {}; } } catch { database = { users: {} }; } function saveDatabase() { const temp = DATA_FILE + ".tmp"; fs.writeFileSync(temp, JSON.stringify(database, null, 2)); fs.renameSync(temp, DATA_FILE); } function cleanName(value) { return String(value || "") .replace(/[^a-zA-Z0-9_ -]/g, "") .trim() .slice(0, 18); } function makeId() { return crypto.randomBytes(12).toString("hex"); } function makeCode() { return crypto.randomBytes(3).toString("hex").toUpperCase(); } function passwordHash(password, salt) { return crypto.scryptSync(password, salt, 64).toString("hex"); } function safeUser(user) { return { username: user.username, level: user.level, xp: user.xp, coins: user.coins, wins: user.wins, losses: user.losses, eliminations: user.eliminations, matches: user.matches, equipped: user.equipped, inventory: user.inventory, friends: user.friends, history: user.history.slice(0, 10), achievements: user.achievements, battlePassXP: user.battlePassXP, season: user.season, missions: user.missions }; } function getUser(username) { return database.users[String(username || "").toLowerCase()]; } function createUser(username, password) { const salt = crypto.randomBytes(16).toString("hex"); return { username, salt, passwordHash: passwordHash(password, salt), level: 1, xp: 0, coins: 250, wins: 0, losses: 0, eliminations: 0, matches: 0, equipped: "default", inventory: ["default"], friends: [], history: [], achievements: [], battlePassXP: 0, season: 1, lastDaily: "", missions: { matches: 0, eliminations: 0, wins: 0 }, reports: [] }; } const sessions = new Map(); const sockets = new Map(); const rooms = new Map(); const invitations = new Map(); const rateLimits = new Map(); function rateLimit(key, maximum, period) { const now = Date.now(); let entry = rateLimits.get(key); if (!entry || now - entry.start > period) { entry = { start: now, count: 0 }; } entry.count++; rateLimits.set(key, entry); return entry.count <= maximum; } function send(socket, type, data = {}) { if (socket && socket.readyState === WebSocket.OPEN) { socket.send(JSON.stringify({ type, ...data })); } } function broadcast(room, type, data = {}) { for (const player of room.players.values()) { send(player.socket, type, data); } } function publicPlayer(player) { return { id: player.id, username: player.username, x: player.x, y: player.y, z: player.z, yaw: player.yaw, health: player.health, team: player.team, score: player.score, weapon: player.weapon, alive: player.alive, color: player.color }; } function newPlayer(socket, username) { return { id: makeId(), socket, username, x: Math.random() * 8 - 4, y: 1.6, z: Math.random() * 8 - 4, yaw: 0, pitch: 0, health: 100, team: 0, score: 0, alive: true, weapon: "pulse", ammo: WEAPONS.pulse.ammo, reloading: false, lastShot: 0, lastMove: Date.now(), lastReload: 0, color: ["#5ee7ff", "#ffcc66", "#bd88ff", "#70f0a5"][ Math.floor(Math.random() * 4) ] }; } function createRoom(owner, code) { const room = { code, owner, players: new Map(), status: "lobby", mode: "Free For All", map: "NeonCity", duration: MATCH_SECONDS, timeLeft: MATCH_SECONDS, scoreLimit: 20, startedAt: 0, lastTick: Date.now(), votes: {}, king: null, messages: [], results: [] }; rooms.set(code, room); return room; } function leaveRoom(player) { if (!player || !player.room) return; const room = rooms.get(player.room); if (!room) { player.room = null; return; } room.players.delete(player.id); player.room = null; broadcast(room, "playerLeft", { username: player.username }); if (room.players.size === 0) { rooms.delete(room.code); return; } if (room.owner === player.username) { room.owner = room.players.values().next().value.username; } broadcast(room, "roomUpdate", roomInfo(room)); } function roomInfo(room) { return { code: room.code, owner: room.owner, status: room.status, mode: room.mode, map: room.map, duration: room.duration, scoreLimit: room.scoreLimit, players: [...room.players.values()].map(publicPlayer) }; } function joinRoom(socket, user, code) { code = String(code || "").toUpperCase().replace(/[^A-Z0-9]/g, ""); if (!code) code = makeCode(); let room = rooms.get(code); if (!room) room = createRoom(user.username, code); if (room.status !== "lobby" && room.mode !== "Training") { send(socket, "error", { message: "This match has already started." }); return; } if (room.players.size >= MAX_PLAYERS) { send(socket, "error", { message: "This room is full." }); return; } const player = newPlayer(socket, user.username); player.room = code; if (room.mode === "Team Arena") { const counts = [0, 0]; for (const p of room.players.values()) counts[p.team]++; player.team = counts[0] <= counts[1] ? 0 : 1; } room.players.set(player.id, player); socket.playerId = player.id; send(socket, "joined", { self: player.id, user: safeUser(user), room: roomInfo(room) }); broadcast(room, "playerJoined", { player: publicPlayer(player) }); broadcast(room, "roomUpdate", roomInfo(room)); } function getPlayer(socket) { const id = socket.playerId; const session = sockets.get(socket); if (!session || !id || !session.room) return null; const room = rooms.get(session.room); if (!room) return null; return room.players.get(id) || null; } function startMatch(room) { if (room.status === "playing") return; room.status = "playing"; room.startedAt = Date.now(); room.lastTick = Date.now(); room.timeLeft = room.duration; room.votes = {}; room.king = null; for (const player of room.players.values()) { player.health = 100; player.score = 0; player.alive = true; player.x = Math.random() * 10 - 5; player.z = Math.random() * 10 - 5; player.weapon = "pulse"; player.ammo = WEAPONS.pulse.ammo; player.reloading = false; } broadcast(room, "matchStarted", { map: room.map, mode: room.mode, duration: room.duration, players: [...room.players.values()].map(publicPlayer) }); } function finishMatch(room) { if (room.status !== "playing") return; room.status = "results"; const ranking = [...room.players.values()] .sort((a, b) => b.score - a.score); const topScore = ranking[0] ? ranking[0].score : 0; const winners = ranking.filter(p => p.score === topScore); for (const player of ranking) { const user = getUser(player.username); if (!user) continue; user.matches++; user.missions.matches++; if (winners.some(w => w.id === player.id)) { user.wins++; user.missions.wins++; user.coins += 100; user.xp += 150; } else { user.losses++; user.coins += 35; user.xp += 50; } user.battlePassXP += 50; user.history.unshift({ date: new Date().toISOString(), map: room.map, mode: room.mode, score: player.score, result: winners.some(w => w.id === player.id) ? "Win" : "Played" }); user.history = user.history.slice(0, 20); while (user.xp >= user.level * 250) { user.xp -= user.level * 250; user.level++; user.coins += 50; } } saveDatabase(); const results = ranking.map(p => ({ username: p.username, score: p.score, team: p.team })); room.results = results; broadcast(room, "matchResults", { results, winner: winners.map(w => w.username) }); setTimeout(() => { if (!rooms.has(room.code)) return; room.status = "lobby"; room.timeLeft = room.duration; room.votes = {}; broadcast(room, "roomUpdate", roomInfo(room)); }, 10000); } function reload(player) { const weapon = WEAPONS[player.weapon]; if (!weapon || player.reloading || player.ammo === weapon.ammo) return; player.reloading = true; player.lastReload = Date.now(); send(player.socket, "weaponState", { weapon: player.weapon, ammo: player.ammo, maxAmmo: weapon.ammo, reloading: true }); setTimeout(() => { const currentRoom = rooms.get(player.room); const current = currentRoom && currentRoom.players.get(player.id); if (!current || !current.alive) return; current.ammo = WEAPONS[current.weapon].ammo; current.reloading = false; send(current.socket, "weaponState", { weapon: current.weapon, ammo: current.ammo, maxAmmo: WEAPONS[current.weapon].ammo, reloading: false }); }, weapon.reload * 1000); } function switchWeapon(player, weaponName) { if (!WEAPONS[weaponName]) return; player.weapon = weaponName; player.ammo = WEAPONS[weaponName].ammo; player.reloading = false; send(player.socket, "weaponState", { weapon: weaponName, ammo: player.ammo, maxAmmo: WEAPONS[weaponName].ammo, reloading: false }); } function fire(player, data) { const room = rooms.get(player.room); if (!room || room.status !== "playing" || !player.alive) return; if (player.reloading) return; const weapon = WEAPONS[player.weapon]; if (!weapon || player.ammo <= 0) { send(player.socket, "emptyAmmo"); return; } const now = Date.now(); if (now - player.lastShot < weapon.cooldown) return; player.lastShot = now; player.ammo--; const yaw = Number(data.yaw); const pitch = Number(data.pitch); if (!Number.isFinite(yaw) || !Number.isFinite(pitch)) return; const direction = { x: -Math.sin(yaw) * Math.cos(pitch), y: Math.sin(pitch), z: -Math.cos(yaw) * Math.cos(pitch) }; let target = null; let nearest = weapon.range; for (const other of room.players.values()) { if (other.id === player.id || !other.alive) continue; if ( room.mode === "Team Arena" && other.team === player.team ) continue; const dx = other.x - player.x; const dy = other.y - player.y; const dz = other.z - player.z; const distance = Math.sqrt(dx * dx + dy * dy + dz * dz); if (distance > nearest || distance < 0.01) continue; const dot = (dx / distance) * direction.x + (dy / distance) * direction.y + (dz / distance) * direction.z; if (dot > 0.965) { nearest = distance; target = other; } } if (target) { target.health -= weapon.damage; send(player.socket, "hitMarker", { damage: weapon.damage }); send(target.socket, "hitReceived", { health: Math.max(0, target.health) }); if (target.health <= 0) { target.alive = false; player.score++; const user = getUser(player.username); if (user) { user.eliminations++; user.missions.eliminations++; user.coins += 10; user.xp += 20; } if (room.mode === "King Arena") { room.king = player.username; } broadcast(room, "elimination", { attacker: player.username, target: target.username, scores: [...room.players.values()].map(p => ({ username: p.username, score: p.score })) }); setTimeout(() => { const activeRoom = rooms.get(room.code); const respawning = activeRoom && activeRoom.players.get(target.id); if (!respawning || activeRoom.status !== "playing") return; respawning.health = 100; respawning.alive = true; respawning.x = Math.random() * 10 - 5; respawning.z = Math.random() * 10 - 5; send(respawning.socket, "respawned", { x: respawning.x, y: respawning.y, z: respawning.z }); broadcast(activeRoom, "playerUpdate", { player: publicPlayer(respawning) }); }, 2500); saveDatabase(); if ( room.mode !== "Training" && player.score >= room.scoreLimit ) { finishMatch(room); } } } send(player.socket, "weaponState", { weapon: player.weapon, ammo: player.ammo, maxAmmo: weapon.ammo, reloading: false }); } function handleMessage(socket, message) { const session = sockets.get(socket); if (!session) return; const data = message || {}; const user = session.username ? getUser(session.username) : null; if (!rateLimit(session.username || socket._socket?.remoteAddress || "guest", 100, 1000)) { send(socket, "error", { message: "Please slow down." }); return; } if (data.type === "register") { const username = cleanName(data.username); const password = String(data.password || ""); if (username.length < 3 || password.length < 8) { send(socket, "error", { message: "Use a username of 3+ characters and a password of 8+ characters." }); return; } const key = username.toLowerCase(); if (database.users[key]) { send(socket, "error", { message: "Username already exists." }); return; } const newUser = createUser(username, password); database.users[key] = newUser; saveDatabase(); const token = makeId(); sessions.set(token, username); session.username = username; send(socket, "authenticated", { token, user: safeUser(newUser) }); return; } if (data.type === "login") { const username = cleanName(data.username); const password = String(data.password || ""); const existing = getUser(username); if ( !existing || !crypto.timingSafeEqual( Buffer.from(passwordHash(password, existing.salt), "hex"), Buffer.from(existing.passwordHash, "hex") ) ) { send(socket, "error", { message: "Incorrect username or password." }); return; } const token = makeId(); sessions.set(token, username); session.username = username; send(socket, "authenticated", { token, user: safeUser(existing) }); return; } if (data.type === "resume") { const username = sessions.get(String(data.token || "")); const existing = username && getUser(username); if (!existing) { send(socket, "error", { message: "Please log in again." }); return; } session.username = username; send(socket, "authenticated", { token: data.token, user: safeUser(existing) }); return; } if (!user) { send(socket, "error", { message: "Please log in first." }); return; } if (data.type === "join") { const old = session.room && rooms.get(session.room); if (old) { const oldPlayer = [...old.players.values()] .find(p => p.socket === socket); if (oldPlayer) leaveRoom(oldPlayer); } joinRoom(socket, user, data.code); return; } if (data.type === "createRoom") { const code = makeCode(); joinRoom(socket, user, code); return; } if (data.type === "leave") { const player = getPlayer(socket); if (player) leaveRoom(player); send(socket, "left"); return; } if (data.type === "start") { const player = getPlayer(socket); const room = player && rooms.get(player.room); if (!room || room.owner !== user.username) return; startMatch(room); return; } if (data.type === "settings") { const player = getPlayer(socket); const room = player && rooms.get(player.room); if (!room || room.owner !== user.username || room.status !== "lobby") return; if (MODES.includes(data.mode)) room.mode = data.mode; if (MAPS.includes(data.map)) room.map = data.map; room.duration = Math.max(60, Math.min(600, Number(data.duration) || MATCH_SECONDS)); room.scoreLimit = Math.max(5, Math.min(100, Number(data.scoreLimit) || 20)); for (const p of room.players.values()) { p.team = room.mode === "Team Arena" ? [...room.players.values()].indexOf(p) % 2 : 0; } broadcast(room, "roomUpdate", roomInfo(room)); return; } if (data.type === "voteMap") { const player = getPlayer(socket); const room = player && rooms.get(player.room); if (!room || room.status !== "lobby" || !MAPS.includes(data.map)) return; room.votes[player.username] = data.map; const totals = {}; for (const map of Object.values(room.votes)) { totals[map] = (totals[map] || 0) + 1; } broadcast(room, "mapVotes", { totals }); return; } if (data.type === "move") { const player = getPlayer(socket); const room = player && rooms.get(player.room); if (!player || !room || room.status !== "playing" || !player.alive) return; const now = Date.now(); const delta = Math.min((now - player.lastMove) / 1000, 0.1); player.lastMove = now; const forward = Math.max(-1, Math.min(1, Number(data.forward) || 0)); const sideways = Math.max(-1, Math.min(1, Number(data.sideways) || 0)); const sprint = data.sprint === true; const yaw = Number(data.yaw); if (Number.isFinite(yaw)) player.yaw = yaw; const speed = sprint ? 8 : 5; const dx = (-Math.sin(player.yaw) * forward + Math.cos(player.yaw) * sideways) * speed * delta; const dz = (-Math.cos(player.yaw) * forward - Math.sin(player.yaw) * sideways) * speed * delta; player.x = Math.max(-45, Math.min(45, player.x + dx)); player.z = Math.max(-45, Math.min(45, player.z + dz)); if (data.jump === true && player.y <= 1.61) { player.y = 2.5; } else { player.y = Math.max(1.6, player.y - 0.12); } player.pitch = Math.max(-1.2, Math.min(1.2, Number(data.pitch) || 0)); broadcast(room, "playerUpdate", { player: publicPlayer(player) }); return; } if (data.type === "shoot") { const player = getPlayer(socket); if (player) fire(player, data); return; } if (data.type === "reload") { const player = getPlayer(socket); if (player) reload(player); return; } if (data.type === "switchWeapon") { const player = getPlayer(socket); if (player) switchWeapon(player, data.weapon); return; } if (data.type === "chat") { const player = getPlayer(socket); const room = player && rooms.get(player.room); const message = String(data.message || "").trim().slice(0, 160); if (!room || !message) return; const entry = { username: user.username, message, time: Date.now() }; room.messages.push(entry); room.messages = room.messages.slice(-50); broadcast(room, "chat", entry); return; } if (data.type === "friend") { const friendName = cleanName(data.username); const friend = getUser(friendName); if (!friend || friendName.toLowerCase() === user.username.toLowerCase()) { send(socket, "error", { message: "Player not found." }); return; } if (!user.friends.includes(friend.username)) { user.friends.push(friend.username); friend.friends.push(user.username); saveDatabase(); } send(socket, "profile", { user: safeUser(user) }); return; } if (data.type === "invite") { const targetName = cleanName(data.username); const targetSocket = [...sockets.entries()] .find(([s, info]) => info.username && info.username.toLowerCase() === targetName.toLowerCase() ); if (!targetSocket) { send(socket, "error", { message: "Player is not online." }); return; } const player = getPlayer(socket); if (!player) return; invitations.set(targetName.toLowerCase(), { code: player.room, from: user.username, expires: Date.now() + 60000 }); send(targetSocket[0], "invitation", { from: user.username, code: player.room }); return; } if (data.type === "acceptInvite") { const invite = invitations.get(user.username.toLowerCase()); if (!invite || invite.expires < Date.now()) { send(socket, "error", { message: "Invitation expired." }); return; } invitations.delete(user.username.toLowerCase()); joinRoom(socket, user, invite.code); return; } if (data.type === "dailyReward") { const today = new Date().toISOString().slice(0, 10); if (user.lastDaily === today) { send(socket, "error", { message: "Daily reward already collected." }); return; } user.lastDaily = today; user.coins += 100; saveDatabase(); send(socket, "profile", { user: safeUser(user) }); send(socket, "notice", { message: "Daily reward: 100 coins!" }); return; } if (data.type === "buy") { const items = { neon: 150, galaxy: 300, emerald: 450, sunset: 600 }; const item = String(data.item || ""); const cost = items[item]; if (!cost || user.inventory.includes(item)) return; if (user.coins < cost) { send(socket, "error", { message: "Not enough coins." }); return; } user.coins -= cost; user.inventory.push(item); saveDatabase(); send(socket, "profile", { user: safeUser(user) }); return; } if (data.type === "equip") { const item = String(data.item || ""); if (!user.inventory.includes(item)) return; user.equipped = item; saveDatabase(); send(socket, "profile", { user: safeUser(user) }); return; } if (data.type === "report") { const target = cleanName(data.username); const reason = String(data.reason || "Other").slice(0, 100); if ( !target || target.toLowerCase() === user.username.toLowerCase() ) return; user.reports.push({ target, reason, date: new Date().toISOString() }); user.reports = user.reports.slice(-50); saveDatabase(); send(socket, "notice", { message: "Report submitted for review." }); return; } if (data.type === "leaderboard") { const leaders = Object.values(database.users) .sort((a, b) => b.wins - a.wins) .slice(0, 20) .map(u => ({ username: u.username, wins: u.wins, level: u.level, eliminations: u.eliminations })); send(socket, "leaderboard", { leaders }); return; } if (data.type === "profile") { send(socket, "profile", { user: safeUser(user) }); return; } } function gameTick() { const now = Date.now(); for (const room of rooms.values()) { if (room.status !== "playing") continue; const elapsed = Math.floor((now - room.startedAt) / 1000); room.timeLeft = Math.max(0, room.duration - elapsed); if (room.mode === "King Arena" && !room.king) { const highest = [...room.players.values()] .sort((a, b) => b.score - a.score)[0]; if (highest && highest.score > 0) { room.king = highest.username; } } broadcast(room, "timer", { timeLeft: room.timeLeft, king: room.king }); if (room.timeLeft <= 0) { finishMatch(room); } } } const HTML = `
Enter the arena. Build your skills. Play together.
Join a room, invite friends, choose your equipment and compete.
Matches played
Eliminations
Experience
WASD: move ยท Mouse: look ยท Left click: fire ยท Space: jump ยท Shift: sprint ยท R: reload ยท 1, 2, 3: switch blaster.
On phones, use the movement and action buttons below the game.
Room: Not connected
Players: 0/8
Pulse Blaster
Cosmetic items do not increase damage or give competitive advantages.
Earn battle pass XP by playing matches.
Battle pass XP: 0
This build tracks season progress; premium purchases are not enabled.
Keep chat respectful. Do not share passwords or private information.