const canvas = document.getElementById("gameCanvas"); const ctx = canvas.getContext("2d"); canvas.width = 1280; canvas.height = 720; const loginBox = document.getElementById("loginBox"); const registerBox = document.getElementById("registerBox"); const loginUser = document.getElementById("loginUser"); const loginPass = document.getElementById("loginPass"); const loginBtn = document.getElementById("loginBtn"); const regUser = document.getElementById("regUser"); const regPass = document.getElementById("regPass"); const regBtn = document.getElementById("regBtn"); const switchToRegister = document.getElementById("switchToRegister"); const switchToLogin = document.getElementById("switchToLogin"); const shopWindow = document.getElementById("shopWindow"); const shopList = document.getElementById("shopList"); let ws = null; let maps = {}; let tileConfig = {}; let lastServerUpdate = 0; let player = { id: null, x: 100, y: 100, world: "stadt" }; player.health = 100; player.hunger = 100; player.thirst = 100; player.inventory = []; player.money = 1000; let loggedIn = false; let otherPlayers = []; let keys = {}; let shops = []; let atms = []; let cars = []; let drivingCarId = null; let objectConfig = {}; fetch("/objectConfig.json") .then(res => res.json()) .then(data => objectConfig = data); // ------------------------------------------------------------- // Tiles laden // ------------------------------------------------------------- fetch("/tiles.json") .then(res => res.json()) .then(data => { tileConfig = data; console.log("TileConfig geladen:", tileConfig); }); // ------------------------------------------------------------- // REGISTER // ------------------------------------------------------------- regBtn.onclick = async () => { const username = regUser.value; const password = regPass.value; const res = await fetch("/api/register", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username, password }) }); const data = await res.json(); if (!data.ok) { alert(data.error); return; } alert("Registrierung erfolgreich!"); registerBox.style.display = "none"; loginBox.style.display = "block"; }; // ------------------------------------------------------------- // LOGIN // ------------------------------------------------------------- loginBtn.onclick = async () => { const username = loginUser.value; const password = loginPass.value; const res = await fetch("/api/login", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username, password }) }); const data = await res.json(); if (!data.ok) { alert(data.error); return; } loginBox.style.display = "none"; canvas.style.display = "block"; startMultiplayer(data.token); }; // Hilfsfunktion: Spieler holen function getPlayer(playerId) { return playersOnline.get(playerId); } // BANK: PIN prüfen async function handleBankPinCheck(playerId, data, ws) { const p = getPlayer(playerId); if (!p) return; const pin = Number(data.pin) || 0; const [rows] = await db.query("SELECT pin FROM players WHERE id=?", [playerId]); if (!rows.length) return; if (rows[0].pin === pin) { ws.send(JSON.stringify({ type: "bank_pin_ok" })); } else { ws.send(JSON.stringify({ type: "bank_pin_fail" })); } } // BANK: Fenster öffnen (Kontostand + Bargeld) async function handleBankOpen(playerId, ws) { const p = getPlayer(playerId); if (!p) return; ws.send(JSON.stringify({ type: "bank_open", money: p.state.money, bank: p.state.bank })); } // BANK: Einzahlen async function handleBankDeposit(playerId, data, ws) { const p = getPlayer(playerId); if (!p) return; const amount = Number(data.amount) || 0; if (amount <= 0) return; if (p.state.money < amount) return; p.state.money -= amount; p.state.bank += amount; await db.query( "UPDATE players SET money=?, bank=? WHERE id=?", [p.state.money, p.state.bank, playerId] ); ws.send(JSON.stringify({ type: "bank_update", money: p.state.money, bank: p.state.bank })); } // BANK: Auszahlen async function handleBankWithdraw(playerId, data, ws) { const p = getPlayer(playerId); if (!p) return; const amount = Number(data.amount) || 0; if (amount <= 0) return; if (p.state.bank < amount) return; p.state.bank -= amount; p.state.money += amount; await db.query( "UPDATE players SET money=?, bank=? WHERE id=?", [p.state.money, p.state.bank, playerId] ); ws.send(JSON.stringify({ type: "bank_update", money: p.state.money, bank: p.state.bank })); } // BANK: Geld an anderen Spieler senden (Bank → Bank) async function handleBankTransfer(playerId, data, ws) { const p = getPlayer(playerId); if (!p) return; const targetId = Number(data.targetId); const amount = Number(data.amount) || 0; if (amount <= 0) return; const t = getPlayer(targetId); if (!t) return; if (p.state.bank < amount) return; p.state.bank -= amount; t.state.bank += amount; await db.query("UPDATE players SET bank=? WHERE id=?", [p.state.bank, playerId]); await db.query("UPDATE players SET bank=? WHERE id=?", [t.state.bank, targetId]); ws.send(JSON.stringify({ type: "bank_update", money: p.state.money, bank: p.state.bank })); t.ws.send(JSON.stringify({ type: "bank_update", money: t.state.money, bank: t.state.bank })); } // ------------------------------------------------------------- // UI SWITCH // ------------------------------------------------------------- switchToRegister.onclick = () => { loginBox.style.display = "none"; registerBox.style.display = "block"; canvas.style.display = "none"; }; switchToLogin.onclick = () => { registerBox.style.display = "none"; loginBox.style.display = "block"; canvas.style.display = "none"; }; // ------------------------------------------------------------- // MULTIPLAYER STARTEN // ------------------------------------------------------------- function startMultiplayer(token) { ws = new WebSocket("ws://45.81.233.187:5555"); ws.onopen = () => { ws.send(JSON.stringify({ type: "auth", token })); }; ws.onmessage = ev => { const msg = JSON.parse(ev.data); if (msg.type === "auth_ok") { player.id = msg.id; maps = msg.maps; document.getElementById("topMenu").style.display = "flex"; document.getElementById("statusWindow").style.display = "block"; document.getElementById("inventoryWindow").style.display = "block"; player.inventory = msg.inventory || []; document.getElementById("inventoryWindow").style.display = "block"; renderInventoryWindow(); canvas.style.display = "block"; console.log("MAPS GELADEN:", maps); const map = maps[player.world]; if (map && map.spawn) { player.x = map.spawn.x; player.y = map.spawn.y; } //shops=map.shops; loggedIn = true; return; } if (msg.type === "state") { lastServerUpdate = Date.now(); const me = msg.players.find(p => p.id === player.id); if (me) { // nur setzen, wenn wir NICHT gerade bewegen if (!keys["w"] && !keys["a"] && !keys["s"] && !keys["d"]) { player.x = me.x; player.y = me.y; } player.world = me.world; player.money = me.money; player.bank = me.bank; player.health = me.health; player.hunger = me.hunger; player.thirst = me.thirst; player.inventory = me.inventory; //console.log("geld:", player.money); } otherPlayers = msg.players.filter(p => p.id !== player.id); renderInventoryWindow(); updateStatusUI(); } if (msg.type === "npc_dialog") { alert("NPC sagt: " + msg.text); } if (msg.type === "shop_open") { const shopWindow = document.getElementById("shopWindow"); const shopList = document.getElementById("shopList"); shopWindow.style.display = "block"; shopList.innerHTML = ""; msg.items.forEach(item => { const div = document.createElement("div"); div.style.padding = "5px"; div.style.borderBottom = "1px solid #333"; div.innerHTML = ` ${item.name} - ${item.price}$ `; shopList.appendChild(div); }); } if (msg.type === "shop_error") { alert(msg.msg); } if (msg.type === "item_used") { console.log(msg.hunger) console.log(msg.thirst) player.hunger = msg.hunger; player.thirst = msg.thirst; player.health = msg.health; player.inventory = msg.inventory; updateStatusWindow(); renderInventoryWindow(); } if (msg.type === "atm_open") { const pin = prompt("PIN eingeben"); ws.send(JSON.stringify({ type: "bank_pin_check", pin })); } if (msg.type === "bank_pin_ok") { console.log("bank_pin_ok KOMMT AN"); ws.send(JSON.stringify({ type: "bank_open" })); } if (msg.type === "bank_pin_fail") { alert("Falsche PIN!"); } if (msg.type === "bank_open") { console.log("BANK_OPEN KOMMT AN"); openATMWindow(msg.money, msg.bank); } if (msg.type === "bank_update") { document.getElementById("atmMoney").innerText = msg.money; document.getElementById("atmBank").innerText = msg.bank; } // im ws.onmessage ergänzen: if (msg.type === "cars") { cars = msg.cars; const mine = cars.find(c => c.driverId === player.id); drivingCarId = mine ? mine.id : null; } if (msg.type === "object_interact") { if (msg.action === "loot") { alert("Du hast die Kiste geöffnet!"); } } // Map-Daten if (msg.type === "map_data") { tiles = msg.tiles; doors = msg.doors || []; objects = msg.objects || []; shops = msg.shops || []; // <-- WICHTIG atms = msg.atms || []; spawn = msg.spawn; console.log("shops:", shops); console.log("atms:", atms); return; } }; } function openATMWindow(money, bank) { document.getElementById("atmMoney").innerText = money; document.getElementById("atmBank").innerText = bank; document.getElementById("atmBox").style.display = "block"; } function closeATM() { document.getElementById("atmBox").style.display = "none"; } function atmDeposit() { const amount = Number(document.getElementById("atmAmount").value); ws.send(JSON.stringify({ type: "bank_deposit", amount })); } function atmWithdraw() { const amount = Number(document.getElementById("atmAmount").value); ws.send(JSON.stringify({ type: "bank_withdraw", amount })); } function atmTransfer() { const amount = Number(document.getElementById("atmAmount").value); const targetId = Number(document.getElementById("atmTarget").value); ws.send(JSON.stringify({ type: "bank_transfer", amount, targetId })); } function updateStatusUI() { const hungerBar = document.getElementById("hungerValue"); const thirstBar = document.getElementById("thirstValue"); const moneyLabel = document.getElementById("moneyValue"); if (hungerBar) hungerBar.textContent = player.hunger; if (thirstBar) thirstBar.textContent = player.thirst; if (moneyLabel) moneyLabel.textContent = player.money; } function openATM(atm) { ws.send(JSON.stringify({ type: "bank_pin_check", pin: prompt("PIN eingeben") })); } function useItem(itemId) { console.log("useItem wurde ausgeführt mit:", itemId); ws.send(JSON.stringify({ type: "use_item", itemId })); } function buyItem(itemId) { ws.send(JSON.stringify({ type: "shop_buy", itemId })); } // ------------------------------------------------------------- // CAMERA (mit Zentrierung bei kleinen Maps) // ------------------------------------------------------------- function getCamera() { const map = maps[player.world]; if (!map || !map.tiles || map.tiles.length === 0) { return { x: 0, y: 0 }; } const mapWidth = map.tiles[0].length * 32; const mapHeight = map.tiles.length * 32; let camX = player.x - canvas.width / 2; let camY = player.y - canvas.height / 2; if (mapWidth < canvas.width) { camX = -(canvas.width / 2 - mapWidth / 2); } else { if (camX < 0) camX = 0; if (camX > mapWidth - canvas.width) camX = mapWidth - canvas.width; } if (mapHeight < canvas.height) { camY = -(canvas.height / 2 - mapHeight / 2); } else { if (camY < 0) camY = 0; if (camY > mapHeight - canvas.height) camY = mapHeight - canvas.height; } return { x: camX, y: camY }; } // ------------------------------------------------------------- // MOVEMENT + Collision // ------------------------------------------------------------- document.addEventListener("keydown", e => { keys[e.key] = true; if (!ws) return; if (e.key.toLowerCase() === "e") { ws.send(JSON.stringify({ type: "interact", x: player.x, y: player.y, world: player.world })); } }); // Shop schließen (ESC) document.addEventListener("keydown", e => { if (e.key === "Escape") { shopWindow.style.display = "none"; } }); document.addEventListener("keyup", e => { keys[e.key] = false; }); function canMoveTo(nx, ny) { const map = maps[player.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); // Objekt höher als Tile const ow = cfg.width; const oh = cfg.height; // Spieler-Hitbox (30x30) 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; } function updateNeeds() { player.hunger -= 0.002; player.thirst -= 0.004; if (player.hunger < 0) player.hunger = 0; if (player.thirst < 0) player.thirst = 0; if (player.hunger === 0 || player.thirst === 0) { player.health -= 0.01; if (player.health < 0) player.health = 0; } } function updateStatusWindow() { const status = document.getElementById("statusWindow"); status.innerHTML = ` Leben: ${Math.round(player.health)}
Hunger: ${Math.round(player.hunger)}
Durst: ${Math.round(player.thirst)}
Geld: ${player.money}$ `; } function updateMovement() { let moved = false; let nx = player.x; let ny = player.y; /*if (keys["w"]) ny -= 5; if (keys["s"]) ny += 5; if (keys["a"]) nx -= 5; if (keys["d"]) nx += 5;*/ if (keys["w"]) { ny -= 5; moved = true; } if (keys["s"]) { ny += 5; moved = true; } if (keys["a"]) { nx -= 5; moved = true; } if (keys["d"]) { nx += 5; moved = true; } if (canMoveTo(nx, ny)) { player.x = nx; player.y = ny; // FIX: nur senden, wenn WS existiert und verbunden ist if (ws && moved && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: "move", x: nx, y: ny, //health: player.health, //hunger: player.hunger, //thirst: player.thirst })); } } if (ws && player.id && moved) { ws.send(JSON.stringify({ type: "move", x: player.x, y: player.y })); } } function renderInventoryWindow() { // console.log("renderInventoryWindow wurde ausgeführt"); const list = document.getElementById("inventoryList"); if (!list) return; list.innerHTML = ""; (player.inventory || []).forEach(item => { const div = document.createElement("div"); div.style.display = "flex"; div.style.justifyContent = "space-between"; div.style.padding = "5px"; div.style.borderBottom = "1px solid #333"; div.innerHTML = ` ${item.name || item.id} x${item.amount} `; list.appendChild(div); }); const buttons = document.querySelectorAll(".useItemBtn"); //console.log("Buttons gefunden:", buttons.length); //console.log("Buttons gefunden:", document.querySelectorAll(".useItemBtn").length); document.querySelectorAll(".useItemBtn").forEach(btn => { btn.onclick = () => { //console.log("Button wurde geklickt:", btn.dataset.id); //console.log("Buttons gefunden:", document.querySelectorAll(".useItemBtn").length); useItem(btn.dataset.id); }; }); } // ------------------------------------------------------------- // RENDERING // ------------------------------------------------------------- function renderMap() { const map = maps[player.world]; if (!map || !map.tiles) return; const cam = getCamera(); for (let y = 0; y < map.tiles.length; y++) { for (let x = 0; x < map.tiles[y].length; x++) { const tileId = map.tiles[y][x]; const tile = tileConfig[tileId]; if (!tile) continue; ctx.fillStyle = tile.color; ctx.fillRect( x * 32 - cam.x, y * 32 - cam.y, 32, 32 ); } } } function renderDoors() { const map = maps[player.world]; if (!map || !map.doors) return; const cam = getCamera(); map.doors.forEach(d => { ctx.fillStyle = "orange"; ctx.fillRect( d.x - cam.x, d.y - cam.y, 30, 30 ); }); } function renderPlayers() { const cam = getCamera(); ctx.fillStyle = "yellow"; ctx.fillRect( player.x - cam.x, player.y - cam.y, 20, 20 ); otherPlayers.forEach(p => { if (p.world === player.world) { ctx.fillStyle = "cyan"; ctx.fillRect( p.x - cam.x, p.y - cam.y, 20, 20 ); } }); } function renderObjects() { const map = maps[player.world]; if (!map || !map.objects) return; const cam = getCamera(); map.objects.forEach(o => { const cfg = objectConfig[o.type]; if (!cfg) return; ctx.fillStyle = cfg.color; ctx.fillRect( o.x - cam.x, o.y - (cfg.height - 32) - cam.y, cfg.width, cfg.height ); }); } function renderShops() { const cam = getCamera(); shops.forEach(s => { const px = s.x * 32 - cam.x; const py = s.y * 32 - cam.y; ctx.fillStyle = "yellow"; ctx.fillRect(px, py, 32, 32); ctx.strokeStyle = "black"; ctx.strokeRect(px, py, 32, 32); }); } function renderATMs() { const cam = getCamera(); atms.forEach(a => { const px = a.x * 32 - cam.x; // klein! const py = a.y * 32 - cam.y; // klein! ctx.fillStyle = "blue"; ctx.fillRect(px, py, 32, 32); ctx.strokeStyle = "black"; // optional, wie bei Shops ctx.strokeRect(px, py, 32, 32); }); } function render() { ctx.clearRect(0, 0, canvas.width, canvas.height); renderMap(); renderObjects(); renderShops(); renderATMs(); renderDoors(); renderPlayers(); requestAnimationFrame(render); } // ------------------------------------------------------------- // GAME LOOP // ------------------------------------------------------------- function gameLoop() { updateMovement(); setTimeout(gameLoop, 16); updateStatusWindow(); //updateNeeds(); } render(); gameLoop();