Sg / server.js
Poorpoor6976's picture
Update server.js
faf3a50 verified
Raw
History Blame Contribute Delete
6.58 kB
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}`);
}
});