Files
bordervillegame/backup/backup-server.js.txt
T
2026-08-22 08:40:29 +02:00

1102 lines
33 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import express from "express";
import http from "http";
import { WebSocketServer } from "ws";
import mysql from "mysql2/promise";
import bcrypt from "bcrypt";
import jwt from "jsonwebtoken";
import path from "path";
import fs from "fs";
import { fileURLToPath } from "url";
const JWT_SECRET = "SUPER_SECRET_KEY";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// -------------------------------------------------------------
// TILE CONFIG LADEN
// -------------------------------------------------------------
const tileConfig = JSON.parse(
fs.readFileSync(path.join(__dirname, "tiles.json"), "utf8")
);
// -------------------------------------------------------------
// OBJECT CONFIG LADEN
// -------------------------------------------------------------
const objectConfig = JSON.parse(
fs.readFileSync(path.join(__dirname, "objectConfig.json"), "utf8")
);
const carConfig = JSON.parse(
fs.readFileSync(path.join(__dirname, "carConfig.json"), "utf8")
);
const cars = new Map(); // carId -> { id, ownerId, model, world, x, y, angle, speed, driverId, throttle, steer }
// -------------------------------------------------------------
// MAPS LADEN
// -------------------------------------------------------------
const maps = {};
function loadMaps() {
const dir = path.join(__dirname, "maps");
const files = fs.readdirSync(dir).filter(f => f.endsWith(".json"));
for (const file of files) {
const data = JSON.parse(fs.readFileSync(path.join(dir, file), "utf8"));
maps[data.name] = data;
}
console.log("Maps geladen:", Object.keys(maps));
}
loadMaps();
// -------------------------------------------------------------
// MYSQL VERBINDUNG
// -------------------------------------------------------------
export const db = await mysql.createPool({
host: "156.67.28.205",
user: "game",
password: "tito13101",
database: "gamegta",
port: "3406",
connectionLimit: 10
});
// -------------------------------------------------------------
// EXPRESS SERVER
// -------------------------------------------------------------
const app = express();
app.use(express.json());
app.use(express.static(__dirname));
const server = http.createServer(app);
const wss = new WebSocketServer({ server });
const playersOnline = new Map();
// -------------------------------------------------------------
// REGISTER
// -------------------------------------------------------------
app.post("/api/register", async (req, res) => {
const { username, password } = req.body;
const hash = await bcrypt.hash(password, 10);
try {
await db.query(
"INSERT INTO players (username, password_hash, world, x, y) VALUES (?, ?, 'stadt', 112, 144)",
[username, hash]
);
res.json({ ok: true });
} catch {
res.json({ ok: false, error: "Username vergeben" });
}
});
// -------------------------------------------------------------
// LOGIN
// -------------------------------------------------------------
app.post("/api/login", async (req, res) => {
const { username, password } = req.body;
const [rows] = await db.query(
"SELECT * FROM players WHERE username = ?",
[username]
);
if (rows.length === 0)
return res.json({ ok: false, error: "User nicht gefunden" });
const player = rows[0];
if (!await bcrypt.compare(password, player.password_hash))
return res.json({ ok: false, error: "Passwort falsch" });
const token = jwt.sign(
{ id: player.id, username: player.username },
JWT_SECRET
);
res.json({ ok: true, token });
});
// -------------------------------------------------------------
// MAP SPEICHERN (Editor)
// -------------------------------------------------------------
app.post("/api/save_map", (req, res) => {
const { name, data } = req.body;
if (!name || !data) {
return res.json({ ok: false, error: "Name oder Daten fehlen" });
}
const filePath = path.join(__dirname, "maps", name + ".json");
try {
fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
loadMaps();
res.json({ ok: true });
} catch {
res.json({ ok: false, error: "Speichern fehlgeschlagen" });
}
});
app.get("/api/get_maps", (req, res) => {
res.json({ ok: true, maps });
});
// -------------------------------------------------------------
// COLLISION (Tiles + Objekte)
// -------------------------------------------------------------
async function loadCars() {
const [rows] = await db.query("SELECT * FROM cars");
for (const row of rows) {
cars.set(row.id, {
id: row.id,
ownerId: row.owner_id,
model: row.model,
world: row.world,
x: row.x,
y: row.y,
angle: row.angle,
speed: 0,
driverId: null,
throttle: 0,
steer: 0
});
}
console.log("Autos geladen:", cars.size);
}
await loadCars();
function sendCarsToAll() {
const list = [];
for (const [id, c] of cars) {
list.push({
id: c.id, model: c.model, world: c.world,
x: c.x, y: c.y, angle: c.angle,
driverId: c.driverId
});
}
const msg = JSON.stringify({ type: "cars", cars: list });
for (const [, p] of playersOnline) p.ws.send(msg);
}
function canMove(player, nx, ny) {
const map = maps[player.state.world];
if (!map || !map.tiles) return true;
const tileX = Math.floor(nx / 32);
const tileY = Math.floor(ny / 32);
// TILE COLLISION
const tileId = map.tiles[tileY]?.[tileX];
const tile = tileConfig[tileId];
if (tile && tile.collision) return false;
// OBJECT COLLISION
if (map.objects) {
for (const obj of map.objects) {
const cfg = objectConfig[obj.type];
if (!cfg || !cfg.collision) continue;
const ox = obj.x;
const oy = obj.y - (cfg.height - 32);
const ow = cfg.width;
const oh = cfg.height;
const px = nx;
const py = ny;
const pw = 30;
const ph = 30;
const hit =
px < ox + ow &&
px + pw > ox &&
py < oy + oh &&
py + ph > oy;
if (hit) return false;
}
}
return true;
}
const shops = new Map(); // shopId -> { id, name, world, x, y, items: [{id, name, price, type, model, restore}] }
async function loadShops() {
shops.clear();
const [shopRows] = await db.query("SELECT * FROM shops");
for (const s of shopRows) {
shops.set(s.id, { id: s.id, name: s.name, world: s.world, x: s.x, y: s.y, items: [] });
}
const [itemRows] = await db.query(`
SELECT si.shop_id, si.price, i.id AS item_id, i.name, i.type, i.model, i.restore
FROM shop_items si
JOIN items i ON i.id = si.item_id
`);
for (const row of itemRows) {
const shop = shops.get(row.shop_id);
if (!shop) continue;
shop.items.push({
id: row.item_id,
name: row.name,
price: row.price,
type: row.type,
model: row.model,
restore: row.restore
});
}
console.log("Shops geladen:", shops.size);
}
await loadShops();
// Hilfsfunktion: Spieler holen
function getPlayer(playerId) {
return playersOnline.get(playerId);
}
// -------------------------------------------------------------
// STATE AN ALLE SENDEN
// -------------------------------------------------------------
function sendStateToAll() {
const allPlayers = [];
for (const [id, p] of playersOnline) {
allPlayers.push({
id: id,
x: p.state.x,
y: p.state.y,
world: p.state.world,
money: p.state.money,
bank: p.state.bank,
health: p.state.health,
hunger: p.state.hunger,
thirst: p.state.thirst,
inventory: p.state.inventory
});
}
const msg = JSON.stringify({
type: "state",
players: allPlayers
});
for (const [id, p] of playersOnline) {
p.ws.send(msg);
}
}
async function getItemData(itemId) {
const [rows] = await db.query("SELECT * FROM items WHERE id=?", [itemId]);
return rows[0] || null;
}
function round2(value) {
return Math.round(value * 100) / 100;
}
async function useItem(p, itemId) {
const invItem = p.state.inventory.find(i => i.id === itemId);
if (!invItem) {
return { msg: "Item nicht im Inventar gefunden." };
}
const [rows] = await db.query("SELECT * FROM items WHERE id=?", [itemId]);
if (rows.length === 0) {
return { msg: "Item existiert nicht in der Datenbank." };
}
const itemData = rows[0];
// Werte normalisieren
p.state.hunger = Number(p.state.hunger) || 0;
p.state.thirst = Number(p.state.thirst) || 0;
p.state.health = Number(p.state.health) || 100;
// ADDIEREN statt voll auffüllen
const restore = Number(itemData.restore) || 0;
if (itemData.type === "food") {
console.log(itemData.restore)
console.log(p.state.hunger)
p.state.hunger = round2(Math.min(100, p.state.hunger + restore));
console.log(p.state.hunger)
}
if (itemData.type === "drink") {
p.state.thirst = round2(Math.min(100, p.state.thirst + restore));
}
if (itemData.type === "heal") {
p.state.health = round2(Math.min(100, p.state.health + restore));
}
// Inventar reduzieren
invItem.amount--;
if (invItem.amount <= 0) {
p.state.inventory = p.state.inventory.filter(i => i.id !== itemId);
}
return {
msg: `${itemData.name} benutzt.`,
hunger: p.state.hunger,
thirst: p.state.thirst,
health: p.state.health,
inventory: p.state.inventory
};
}
/*
function handleInteraction(player, data) {
const map = maps[player.state.world];
if (!map) return;
const px = player.state.x;
const py = player.state.y;
// 1. Türen prüfen
if (map.doors) {
const door = map.doors.find(d =>
Math.abs(d.x - px) < 16 &&
Math.abs(d.y - py) < 16
);
if (door) {
if (!maps[door.targetMap]) return;
player.state.world = door.targetMap;
player.state.x = door.targetX * 32;
player.state.y = door.targetY * 32;
db.query(
"UPDATE players SET world=?, x=?, y=? WHERE id=?",
[player.state.world, player.state.x, player.state.y, player.id]
);
sendStateToAll();
player.ws.send(JSON.stringify({
type: "map_data",
tiles: maps[player.state.world].tiles,
doors: maps[player.state.world].doors,
objects: maps[player.state.world].objects,
shops: maps[player.state.world].shops, // <-- WICHTIG
spawn: maps[player.state.world].spawn
}));
return;
}
}
// 2. NPCs prüfen
if (map.npcs) {
const npc = map.npcs.find(n =>
Math.abs(n.x - px) < 32 &&
Math.abs(n.y - py) < 32
);
if (npc) {
player.ws.send(JSON.stringify({
type: "npc_dialog",
npcId: npc.id,
text: npc.dialog
}));
return;
}
}
// 3. Shops prüfen
if (map.shops && Array.isArray(map.shops)) {
const shop = map.shops.find(s =>
Math.abs(s.x * 32 - player.state.x) < 32 &&
Math.abs(s.y * 32 - player.state.y) < 32
);
if (shop) {
player.ws.send(JSON.stringify({
type: "shop_open",
shopId: shop.id,
items: shop.items
}));
return;
}
}
// 4. Interaktive Objekte (z.B. Kisten)
if (map.objects) {
const obj = map.objects.find(o =>
o.interactive &&
Math.abs(o.x - px) < 32 &&
Math.abs(o.y - py) < 32
);
if (obj) {
player.ws.send(JSON.stringify({
type: "object_interact",
objectId: obj.id,
action: obj.action
}));
return;
}
}
}*/
function handleInteraction(player, data) {
const map = maps[player.state.world];
if (!map) return;
const px = player.state.x;
const py = player.state.y;
// 1. Türen prüfen (unverändert)
if (map.doors) {
const door = map.doors.find(d =>
Math.abs(d.x - px) < 16 &&
Math.abs(d.y - py) < 16
);
if (door) {
if (!maps[door.targetMap]) return;
player.state.world = door.targetMap;
player.state.x = door.targetX * 32;
player.state.y = door.targetY * 32;
db.query(
"UPDATE players SET world=?, x=?, y=? WHERE id=?",
[player.state.world, player.state.x, player.state.y, player.id]
);
sendStateToAll();
player.ws.send(JSON.stringify({
type: "map_data",
tiles: maps[player.state.world].tiles,
doors: maps[player.state.world].doors,
objects: maps[player.state.world].objects,
shops: maps[player.state.world].shops,
atms: maps[player.state.world].atms || [], // <-- NEU
spawn: maps[player.state.world].spawn
}));
return;
}
}
// 2. NPCs prüfen (unverändert)
if (map.npcs) {
const npc = map.npcs.find(n =>
Math.abs(n.x - px) < 32 &&
Math.abs(n.y - py) < 32
);
if (npc) {
player.ws.send(JSON.stringify({
type: "npc_dialog",
npcId: npc.id,
text: npc.dialog
}));
return;
}
}
// 3. Shops prüfen (unverändert)
if (map.shops && Array.isArray(map.shops)) {
const shop = map.shops.find(s =>
Math.abs(s.x * 32 - player.state.x) < 32 &&
Math.abs(s.y * 32 - player.state.y) < 32
);
if (shop) {
player.ws.send(JSON.stringify({
type: "shop_open",
shopId: shop.id,
items: shop.items
}));
return;
}
}
// 3b. ATMs prüfen (GENAU wie Shop)
if (map.atms && Array.isArray(map.atms)) {
const atm = map.atms.find(a =>
Math.abs(a.x * 32 - player.state.x) < 32 &&
Math.abs(a.y * 32 - player.state.y) < 32
);
if (atm) {
// sende atm_open an den Client (Server entscheidet, ob PIN nötig)
player.ws.send(JSON.stringify({
type: "atm_open",
atmId: atm.id,
money: player.state.money,
bank: player.state.bank
}));
return;
}
}
// 4. Interaktive Objekte (z.B. Kisten) (unverändert)
if (map.objects) {
const obj = map.objects.find(o =>
o.interactive &&
Math.abs(o.x - px) < 32 &&
Math.abs(o.y - py) < 32
);
if (obj) {
player.ws.send(JSON.stringify({
type: "object_interact",
objectId: obj.id,
action: obj.action
}));
return;
}
}
}
// -------------------------------------------------------------
// WEBSOCKET MULTIPLAYER
// -------------------------------------------------------------
wss.on("connection", ws => {
let playerId = null;
ws.on("message", async msg => {
let data;
try {
data = JSON.parse(msg);
} catch {
return;
}
// AUTH
if (data.type === "auth") {
try {
const payload = jwt.verify(data.token, JWT_SECRET);
playerId = payload.id;
ws.playerId = playerId;
const [rows] = await db.query(
"SELECT world, x, y, health, hunger, thirst, money,inventory FROM players WHERE id = ?",
[playerId]
);
const row = rows[0];
playersOnline.set(playerId, {
ws,
state: {
world: row.world,
x: row.x,
y: row.y,
health: row.health,
hunger: row.hunger,
thirst: row.thirst,
inventory: JSON.parse(row.inventory || "[]"),
money: row.money ?? 0,
bank: row.bank ?? 0
}
});
ws.send(JSON.stringify({
type: "auth_ok",
id: playerId,
maps
}));
// Map-Daten direkt nach Login senden
const currentMap = maps[row.world];
ws.send(JSON.stringify({
type: "map_data",
tiles: currentMap.tiles,
doors: currentMap.doors,
objects: currentMap.objects,
shops: currentMap.shops,
atms: currentMap.atms, // <-- NEU
spawn: currentMap.spawn
}));
sendStateToAll();
} catch {
ws.close();
}
}
if (data.type === "bank_pin_check") {
// Spieler korrekt holen
const player = playersOnline.get(ws.playerId);
console.log(playerId)
if (!player) {
console.log("Kein Spieler gefunden für ws.playerId:", playerId);
return;
}
console.log("Player ID:", playerId);
const [rows] = await db.query("SELECT pin FROM players WHERE id=?", [playerId]);
console.log("DB Row:", rows[0]);
console.log("Client PIN:", data.pin);
if (rows.length && Number(rows[0].pin) === Number(data.pin)) {
player.ws.send(JSON.stringify({ type: "bank_pin_ok" }));
} else {
player.ws.send(JSON.stringify({ type: "bank_pin_fail" }));
}
}
if (data.type === "bank_open") {
const player = playersOnline.get(ws.playerId);
if (!player) return;
player.ws.send(JSON.stringify({
type: "bank_open",
money: player.state.money,
bank: player.state.bank
}));
}
if (data.type === "bank_deposit") {
const player = playersOnline.get(ws.playerId); // <-- NEU hinzugefügt
if (!player) return;
const amount = Math.max(0, Number(data.amount));
if (player.state.money >= amount) {
player.state.money -= amount;
player.state.bank += amount;
await db.query("UPDATE players SET money=?, bank=? WHERE id=?",
[player.state.money, player.state.bank, playerId]);
player.ws.send(JSON.stringify({
type: "bank_update",
money: player.state.money,
bank: player.state.bank
}));
}
}
if (data.type === "bank_withdraw") {
const player = playersOnline.get(ws.playerId); // <-- NEU hinzugefügt
if (!player) return;
const amount = Math.max(0, Number(data.amount));
if (player.state.bank >= amount) {
player.state.bank -= amount;
player.state.money += amount;
await db.query("UPDATE players SET money=?, bank=? WHERE id=?",
[player.state.money, player.state.bank, playerId]);
player.ws.send(JSON.stringify({
type: "bank_update",
money: player.state.money,
bank: player.state.bank
}));
}
}
if (data.type === "bank_transfer") {
const player = playersOnline.get(ws.playerId); // <-- NEU hinzugefügt
if (!player) return;
const targetId = Number(data.targetId);
const amount = Math.max(0, Number(data.amount));
const target = playersOnline.get(targetId);
if (target && player.state.bank >= amount) {
player.state.bank -= amount;
target.state.bank += amount;
await db.query("UPDATE players SET bank=? WHERE id=?", [player.state.bank, playerId]);
await db.query("UPDATE players SET bank=? WHERE id=?", [target.state.bank, targetId]);
player.ws.send(JSON.stringify({
type: "bank_update",
money: player.state.money,
bank: player.state.bank
}));
target.ws.send(JSON.stringify({
type: "bank_update",
money: target.state.money,
bank: target.state.bank
}));
}
}
if (data.type === "car_enter") {
const p = playersOnline.get(playerId);
if (!p) return;
let nearestCar = null;
for (const [, c] of cars) {
if (c.world !== p.state.world || c.driverId) continue;
if (Math.abs(c.x - p.state.x) < 40 && Math.abs(c.y - p.state.y) < 40) {
nearestCar = c;
break;
}
}
if (!nearestCar) return;
nearestCar.driverId = playerId;
p.drivingCarId = nearestCar.id;
sendCarsToAll();
}
if (data.type === "car_exit") {
const p = playersOnline.get(playerId);
if (!p || !p.drivingCarId) return;
const c = cars.get(p.drivingCarId);
if (c) {
c.driverId = null;
c.throttle = 0;
c.steer = 0;
p.state.x = c.x;
p.state.y = c.y + 30;
db.query("UPDATE cars SET x=?, y=?, angle=?, world=? WHERE id=?",
[c.x, c.y, c.angle, c.world, c.id]);
}
p.drivingCarId = null;
sendCarsToAll();
sendStateToAll();
}
if (data.type === "car_control") {
const p = playersOnline.get(playerId);
if (!p || !p.drivingCarId) return;
const c = cars.get(p.drivingCarId);
if (!c) return;
c.throttle = Math.max(-1, Math.min(1, Number(data.throttle) || 0));
c.steer = Math.max(-1, Math.min(1, Number(data.steer) || 0));
}
if (msg.type === "shop_error") {
alert(msg.msg);
}
if (data.type === "shop_buy") {
const p = playersOnline.get(playerId);
if (!p) return;
const map = maps[p.state.world];
if (!map || !map.shops) return;
const shop = map.shops.find(s =>
Math.abs(s.x * 32 - p.state.x) < 32 &&
Math.abs(s.y * 32 - p.state.y) < 32
);
if (!shop) return;
const item = shop.items.find(i => i.id === data.itemId);
if (!item) return;
if (p.state.money < item.price) {
p.ws.send(JSON.stringify({ type: "shop_error", msg: "Zu wenig Geld!" }));
return;
}
p.state.money -= item.price;
if (item.type === "car") {
const [result] = await db.query(
"INSERT INTO cars (owner_id, model, world, x, y, angle) VALUES (?, ?, ?, ?, ?, 0)",
[playerId, item.model || "sedan", p.state.world, p.state.x + 40, p.state.y]
);
cars.set(result.insertId, {
id: result.insertId,
ownerId: playerId,
model: item.model || "sedan",
world: p.state.world,
x: p.state.x + 40,
y: p.state.y,
angle: 0,
speed: 0,
driverId: null,
throttle: 0,
steer: 0
});
await db.query("UPDATE players SET money=? WHERE id=?", [p.state.money, playerId]);
sendCarsToAll();
p.ws.send(JSON.stringify({ type: "shop_info", msg: "Auto gekauft! Steht neben dir." }));
} else {
const invItem = p.state.inventory.find(i => i.id === item.id);
if (invItem) invItem.amount++;
else p.state.inventory.push({ id: item.id, amount: 1 });
await db.query(
"UPDATE players SET money=?, inventory=? WHERE id=?",
[p.state.money, JSON.stringify(p.state.inventory), playerId]
);
}
sendStateToAll();
}
if (data.type === "state") {
const player = playersOnline.get(ws.playerId);
if (!player) return;
const me = data.players.find(p => p.id === ws.playerId);
if (!me) return;
// SERVERSEITIG IMMER player.state benutzen!
player.state.x = me.x;
player.state.y = me.y;
player.state.world = me.world;
player.state.money = me.money;
player.state.bank = me.bank;
player.state.health = me.health;
player.state.hunger = me.hunger;
player.state.thirst = me.thirst;
player.state.inventory = me.inventory || [];
// Andere Spieler aktualisieren
player.otherPlayers = data.players.filter(p => p.id !== ws.playerId);
// UI aktualisieren (Client)
updateStatusWindow();
renderInventoryWindow();
}
if (!playerId) return;
const p = playersOnline.get(playerId);
if (!p) return;
// TÜR BENUTZEN
if (data.type === "use_door") {
const currentMap = maps[p.state.world];
if (!currentMap || !currentMap.doors) return;
const door = currentMap.doors.find(d =>
Math.abs(d.x - data.x) < 10 &&
Math.abs(d.y - data.y) < 10
);
if (!door) return;
if (!maps[door.targetMap]) return;
p.state.world = door.targetMap;
p.state.x = door.targetX * 32;
p.state.y = door.targetY * 32;
await db.query(
"UPDATE players SET world = ?, x = ?, y = ? WHERE id = ?",
[p.state.world, p.state.x, p.state.y, playerId]
);
sendStateToAll();
}
if (data.type === "interact") {
const p = playersOnline.get(playerId);
if (!p) return;
handleInteraction(p, data);
}
if (data.type === "use_item") {
const p = playersOnline.get(playerId);
if (!p) return;
const result = await useItem(p, data.itemId);
const hunger = Math.max(0, Number(p.state.hunger) || 0);
const thirst = Math.max(0, Number(p.state.thirst) || 0);
const health = Math.max(0, Number(p.state.health) || 100);
await db.query(
"UPDATE players SET inventory=?, hunger=?, thirst=?, health=? WHERE id=?",
[
JSON.stringify(p.state.inventory),
round2(p.state.hunger),
round2(p.state.thirst),
round2(p.state.health),
playerId
]
);
p.ws.send(JSON.stringify({
type: "item_used",
msg: result.msg,
hunger,
thirst,
health,
inventory: p.state.inventory
}));
sendStateToAll();
}
if (msg.type === "item_used") {
// player.hunger = msg.hunger;
//player.thirst = msg.thirst;
//player.inventory = msg.inventory;
renderInventoryWindow();
updateStatusUI();
}
// MOVEMENT MIT COLLISION
if (data.type === "move") {
const nx = data.x;
const ny = data.y;
const oldX = p.state.x;
const oldY = p.state.y;
if (canMove(p, nx, ny)) {
p.state.x = nx;
p.state.y = ny;
// Nur Position speichern KEINE Werte vom Client übernehmen!
await db.query(
"UPDATE players SET x=?, y=? WHERE id=?",
[p.state.x, p.state.y, playerId]
);
if (oldX !== p.state.x || oldY !== p.state.y) {
sendStateToAll();
}
}
}
// MAP WECHSEL
if (data.type === "change_map") {
const mapName = data.map;
if (!maps[mapName]) return;
p.state.world = mapName;
p.state.x = maps[mapName].spawn.x;
p.state.y = maps[mapName].spawn.y;
await db.query(
"UPDATE players SET world = ?, x = ?, y = ? WHERE id = ?",
[mapName, p.state.x, p.state.y, playerId]
);
sendStateToAll();
player.ws.send(JSON.stringify({
type: "map_data",
tiles: maps[p.state.world].tiles,
doors: maps[p.state.world].doors,
objects: maps[p.state.world].objects,
shops: maps[p.state.world].shops, // <-- WICHTIG
atms: maps[player.state.world].atms, // <-- ATM wird jetzt gesendet
spawn: maps[p.state.world].spawn
}));
}
});
ws.on("close", () => {
if (playerId) {
playersOnline.delete(playerId);
sendStateToAll();
}
});
});
function updatePlayerNeeds(p) {
// Werte IMMER in Zahlen umwandeln
p.state.hunger = Number(p.state.hunger) || 0;
p.state.thirst = Number(p.state.thirst) || 0;
p.state.health = Number(p.state.health) || 100;
// Hunger / Durst sinken langsam
p.state.hunger = round2(p.state.hunger - 0.002);
p.state.thirst = round2(p.state.thirst - 0.004);
if (p.state.hunger < 0) p.state.hunger = 0;
if (p.state.thirst < 0) p.state.thirst = 0;
if (p.state.hunger === 0 || p.state.thirst === 0) {
p.state.health = round2(p.state.health - 0.01);
if (p.state.health < 0) p.state.health = 0;
}
// Werte erneut absichern (gegen NaN)
p.state.hunger = Math.max(0, Number(p.state.hunger) || 0);
p.state.thirst = Math.max(0, Number(p.state.thirst) || 0);
p.state.health = Math.max(0, Number(p.state.health) || 100);
}
setInterval(async () => {
for (const [playerId, p] of playersOnline) {
updatePlayerNeeds(p);
await db.query(
"UPDATE players SET health=?, hunger=?, thirst=?, inventory=? WHERE id=?",
[
p.state.health,
p.state.hunger,
p.state.thirst,
JSON.stringify(p.state.inventory),
playerId
]
);
sendStateToAll();
}
}, 1000); // alle 1 Sekunde
setInterval(() => {
let changed = false;
for (const [, c] of cars) {
if (!c.driverId) continue;
const cfg = carConfig[c.model] || carConfig.sedan;
if (c.throttle > 0) c.speed += cfg.accel;
else if (c.throttle < 0) c.speed -= cfg.brake;
else c.speed *= (1 - cfg.friction);
c.speed = Math.max(-cfg.maxSpeed / 2, Math.min(cfg.maxSpeed, c.speed));
if (Math.abs(c.speed) > 0.05) {
c.angle += c.steer * cfg.turnSpeed * (c.speed > 0 ? 1 : -1);
}
const nx = c.x + Math.cos(c.angle) * c.speed;
const ny = c.y + Math.sin(c.angle) * c.speed;
if (canMove({ state: { world: c.world } }, nx, ny)) {
c.x = nx;
c.y = ny;
} else {
c.speed = 0;
}
const driver = playersOnline.get(c.driverId);
if (driver) {
driver.state.x = c.x;
driver.state.y = c.y;
}
changed = true;
}
if (changed) { sendCarsToAll(); sendStateToAll(); }
}, 50); // 20x pro Sekunde
// -------------------------------------------------------------
// SERVER STARTEN
// -------------------------------------------------------------
server.listen(5555, () => {
console.log("Server läuft auf Port 5555");
});