import uWS from 'uWebSockets.js'; import { Packr } from 'msgpackr'; import { createClient } from '@libsql/client'; import dotenv from 'dotenv'; import fs from 'fs'; dotenv.config(); // إعداد ضغط البيانات (MessagePack) const packr = new Packr(); // إعداد قاعدة بيانات Turso (احتياطياً للمستقبل) const db = createClient({ url: process.env.TURSO_URL || 'file:local.db', authToken: process.env.TURSO_AUTH_TOKEN || '' }); const MAX_PLAYERS_PER_WORLD = 30; const DISCONNECT_GRACE_PERIOD = 60 * 1000; // خوارزمية توليد قارات بكسلية (Cellular Automata) function generatePixelMap(width, height) { const grid = new Uint8Array(width * height); // عشوائية أولية for (let i = 0; i < grid.length; i++) { grid[i] = Math.random() > 0.55 ? 1 : 0; // 1 يابسة، 0 ماء } // تنعيم لإنشاء قارات for (let iter = 0; iter < 4; iter++) { const newGrid = new Uint8Array(width * height); for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { let neighbors = 0; for (let dy = -1; dy <= 1; dy++) { for (let dx = -1; dx <= 1; dx++) { if (dx === 0 && dy === 0) continue; let nx = x + dx, ny = y + dy; if (nx >= 0 && nx < width && ny >= 0 && ny < height) { neighbors += grid[ny * width + nx]; } } } // قواعد الحياة للقارات newGrid[y * width + x] = neighbors > 4 || (neighbors === 4 && grid[y * width + x]) ? 1 : 0; } } for (let i = 0; i < grid.length; i++) grid[i] = newGrid[i]; } return grid; } class World { constructor(id) { this.id = id; this.players = new Map(); // إعداد الخريطة البكسلية (مثلاً 250x250 بكسل/مربع) this.mapWidth = 250; this.mapHeight = 250; this.mapGrid = generatePixelMap(this.mapWidth, this.mapHeight); } get playerCount() { let count = 0; this.players.forEach(p => { if (p.online) count++; }); return count; } addPlayer(ws, playerData) { this.players.set(ws.id, playerData); } removePlayer(wsId) { this.players.delete(wsId); } } // إنشاء 5 عوالم const worlds = [1, 2, 3, 4, 5].map(id => new World(id)); const app = uWS.App().ws('/*', { compression: uWS.SHARED_COMPRESSOR, maxPayloadLength: 16 * 1024 * 1024, idleTimeout: 60, upgrade: (res, req, context) => { const ip = res.getRemoteAddressAsText(); res.upgrade( { id: Math.random().toString(36).substr(2, 9), ip }, req.getHeader('sec-websocket-key'), req.getHeader('sec-websocket-protocol'), req.getHeader('sec-websocket-extensions'), context ); }, open: (ws) => { ws.isAlive = true; const worldStats = worlds.map(w => ({ id: w.id, players: w.playerCount })); ws.send(packr.pack({ type: 'SERVER_LIST', data: worldStats }), true); }, message: (ws, message, isBinary) => { try { if (!isBinary) return; const data = packr.unpack(message); switch (data.type) { case 'JOIN_WORLD': handleJoinWorld(ws, data.worldId, data.playerName); break; case 'PING': ws.send(packr.pack({ type: 'PONG' }), true); break; case 'REFRESH_SERVERS': const stats = worlds.map(w => ({ id: w.id, players: w.playerCount })); ws.send(packr.pack({ type: 'SERVER_LIST', data: stats }), true); break; } } catch (e) { // تجاهل البيانات الخبيثة } }, close: (ws, code, message) => { if (ws.worldId) { const world = worlds.find(w => w.id === ws.worldId); if (world && world.players.has(ws.id)) { const player = world.players.get(ws.id); player.online = false; setTimeout(() => { const checkPlayer = world.players.get(ws.id); if (checkPlayer && !checkPlayer.online) { world.removePlayer(ws.id); broadcast(world, { type: 'PLAYER_REMOVED', id: ws.id }); } }, DISCONNECT_GRACE_PERIOD); } } } }); function handleJoinWorld(ws, worldId, playerName) { const world = worlds.find(w => w.id === worldId); if (!world) return; if (world.playerCount >= MAX_PLAYERS_PER_WORLD) { ws.send(packr.pack({ type: 'ERROR', msg: 'الخادم ممتلئ' }), true); return; } const safeName = String(playerName).substring(0, 20).replace(/[^a-zA-Z0-9أ-ي]/g, ''); ws.worldId = worldId; ws.subscribe(`world-${worldId}`); const color = `#${Math.floor(Math.random()*16777215).toString(16).padStart(6, '0')}`; world.addPlayer(ws, { id: ws.id, name: safeName, color: color, online: true }); ws.send(packr.pack({ type: 'JOIN_SUCCESS', worldId, color, name: safeName }), true); // إرسال الخريطة البكسلية للعميل (عرض، طول، والمصفوفة) ws.send(packr.pack({ type: 'MAP_DATA', width: world.mapWidth, height: world.mapHeight, grid: world.mapGrid }), true); broadcast(world, { type: 'PLAYER_JOINED', id: ws.id, name: safeName }); } function broadcast(world, messageObj) { const buffer = packr.pack(messageObj); app.publish(`world-${world.id}`, buffer, true); } // تقديم ملف الواجهة (index.html) app.get('/*', (res, req) => { try { const html = fs.readFileSync('index.html', 'utf8'); res.writeHeader('Content-Type', 'text/html; charset=utf-8').end(html); } catch (e) { res.writeStatus('500 Internal Server Error').end('Index file not found'); } }); const PORT = process.env.PORT || 7860; app.listen(PORT, (token) => { if (token) { console.log(`Server running on port ${PORT} (Pixel Grid Engine Ready)`); } else { console.log(`Failed to listen to port ${PORT}`); } });