from pathlib import Path import zipfile, textwrap base = Path("/mnt/data/mega_bots_brawl_online") base.mkdir(exist_ok=True) server_js = r'''const WebSocket = require("ws"); const http = require("http"); const PORT = process.env.PORT || 8080; const TICK_RATE = 30; const server = http.createServer((req, res) => { res.writeHead(200, {"Content-Type": "text/plain"}); res.end("Mega Bots Brawl WebSocket server is running."); }); const wss = new WebSocket.Server({ server }); const rooms = new Map(); const WORLD = { width: 1280, height: 720, gravity: 0.65, ground: 0.82 }; function makePlayer(x, facing) { return { x, y: WORLD.height * 0.5, vx: 0, vy: 0, width: 55, height: 70, health: 100, facing, onGround: false, attackCooldown: 0, attackTimer: 0 }; } function cleanCode(value) { return String(value || "") .toUpperCase() .replace(/[^A-Z0-9]/g, "") .slice(0, 8); } function createRoom(code) { const room = { code, players: new Map(), state: { player1: makePlayer(WORLD.width * 0.25, 1), player2: makePlayer(WORLD.width * 0.75, -1) } }; rooms.set(code, room); return room; } function send(ws, data) { if (ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify(data)); } } function broadcast(room, data) { for (const ws of room.players.keys()) { send(ws, data); } } function attack(attacker, opponent) { attacker.attackCooldown = 30; attacker.attackTimer = 10; const distance = Math.abs( (attacker.x + attacker.width / 2) - (opponent.x + opponent.width / 2) ); const verticalDistance = Math.abs(attacker.y - opponent.y); if ( distance < 80 && verticalDistance < 80 && opponent.health > 0 ) { opponent.health = Math.max(0, opponent.health - 10); opponent.vx += attacker.facing * 9; opponent.vy -= 4; } } function updatePlayer(player, input, opponent) { if (player.health <= 0) return; if (input.left) { player.vx = -5; player.facing = -1; } else if (input.right) { player.vx = 5; player.facing = 1; } else { player.vx *= 0.8; } if (input.jump && player.onGround) { player.vy = -13; player.onGround = false; } player.vy += WORLD.gravity; player.x += player.vx; player.y += player.vy; const groundY = WORLD.height * WORLD.ground; if (player.y + player.height >= groundY) { player.y = groundY - player.height; player.vy = 0; player.onGround = true; } player.x = Math.max( 0, Math.min(WORLD.width - player.width, player.x) ); if (input.attackPressed && player.attackCooldown <= 0) { attack(player, opponent); } if (player.attackCooldown > 0) player.attackCooldown--; if (player.attackTimer > 0) player.attackTimer--; } function publicPlayer(player) { return { x: player.x, y: player.y, vx: player.vx, vy: player.vy, health: player.health, facing: player.facing, attackTimer: player.attackTimer }; } function broadcastState(room) { broadcast(room, { type: "state", player1: publicPlayer(room.state.player1), player2: publicPlayer(room.state.player2) }); } function removePlayer(ws) { const room = ws.room; if (!room) return; room.players.delete(ws); if (room.players.size > 0) { broadcast(room, {type: "opponent-left"}); } else { rooms.delete(room.code); } ws.room = null; ws.player = null; } wss.on("connection", ws => { ws.room = null; ws.player = null; ws.input = { left: false, right: false, jump: false, attack: false, attackPressed: false }; ws.on("message", raw => { let msg; try { msg = JSON.parse(raw.toString()); } catch { send(ws, { type: "error", message: "Invalid message." }); return; } if (msg.type === "create") { const code = cleanCode(msg.room); if (!code) { send(ws, { type: "error", message: "Invalid room code." }); return; } if (rooms.has(code)) { send(ws, { type: "error", message: "That room already exists." }); return; } const room = createRoom(code); room.players.set(ws, 1); ws.room = room; ws.player = 1; send(ws, { type: "room-created", room: code }); send(ws, { type: "waiting" }); return; } if (msg.type === "join") { const code = cleanCode(msg.room); const room = rooms.get(code); if (!room) { send(ws, { type: "error", message: "Room not found." }); return; } if (room.players.size >= 2) { send(ws, { type: "error", message: "Room is full." }); return; } room.players.set(ws, 2); ws.room = room; ws.player = 2; for (const other of room.players.keys()) { send(other, { type: "game-start", player: room.players.get(other) }); } broadcastState(room); return; } if (msg.type === "input") { if (!ws.room || !ws.player) return; const input = msg.input || {}; ws.input = { left: !!input.left, right: !!input.right, jump: !!input.jump, attack: !!input.attack, attackPressed: !!input.attackPressed }; } }); ws.on("close", () => removePlayer(ws)); ws.on("error", () => removePlayer(ws)); }); setInterval(() => { for (const room of rooms.values()) { if (room.players.size !== 2) continue; const p1ws = [...room.players.entries()].find(([, n]) => n === 1)?.[0]; const p2ws = [...room.players.entries()].find(([, n]) => n === 2)?.[0]; if (!p1ws || !p2ws) continue; updatePlayer( room.state.player1, p1ws.input, room.state.player2 ); updatePlayer( room.state.player2, p2ws.input, room.state.player1 ); p1ws.input.attackPressed = false; p2ws.input.attackPressed = false; broadcastState(room); } }, 1000 / TICK_RATE); server.listen(PORT, () => { console.log(`Mega Bots Brawl server listening on port ${PORT}`); }); ''' package_json = r'''{ "name": "mega-bots-brawl-server", "version": "1.0.0", "private": true, "main": "server.js", "scripts": { "start": "node server.js" }, "dependencies": { "ws": "^8.18.0" } } ''' readme = r'''# Mega Bots Brawl — Online Multiplayer Neocities can host the game's HTML, but it cannot run the WebSocket server itself. This folder contains the Node.js WebSocket server. ## 1. Install On a computer with Node.js installed: ```bash npm install