const express = require("express"); const router = require("express").Router(); const db = require("../database/database"); /* ── Konstanten ─────────────────────────────────── */ const MAX_BASE_HOURS = 5; // Basis-Stunden pro Session const MAX_LOOPS = 4; // Maximale kaufbare Schleifen const LOOP_COST_GEMS = 10; // Juwelen pro Schleife const LOOP_HOURS = 5; // Zusatzstunden pro Schleife const MINE_RESOURCES = ["gold", "iron", "stone", "wood"]; /* ── Auth-Guard ─────────────────────────────────── */ function requireLogin(req, res, next) { if (!req.session?.user) { return res.status(401).json({ error: "Nicht eingeloggt" }); } next(); } /* ───────────────────────────────────────────────── HELPER: Timer sicherstellen Legt beim allerersten Aufruf einen Eintrag an. Wird durch select() und collect() zurückgesetzt. ───────────────────────────────────────────────── */ async function ensureTimer(userBuildingId) { const [[existing]] = await db.query( "SELECT last_collected FROM building_collect_timer WHERE user_building_id = ?", [userBuildingId] ); if (!existing) { await db.query( `INSERT INTO building_collect_timer (user_building_id, last_collected, selected_resource, loops_purchased) VALUES (?, NOW(), NULL, 0)`, [userBuildingId] ); } } /* ───────────────────────────────────────────────── HELPER: Produktionsdaten laden Filtert auf die vier abbaubaren Ressourcen. ───────────────────────────────────────────────── */ async function loadMineData(userId, buildingId) { const [rows] = await db.query( `SELECT ub.id AS user_building_id, ub.level, bp.resource, bp.amount, bp.cycle_seconds, bct.last_collected, bct.selected_resource, bct.loops_purchased FROM user_buildings ub JOIN building_production bp ON bp.building_id = ub.building_id AND bp.level = ub.level JOIN building_collect_timer bct ON bct.user_building_id = ub.id WHERE ub.user_id = ? AND ub.building_id = ? AND bp.resource IN ('gold','iron','stone','wood')`, [userId, buildingId] ); return rows; } /* ───────────────────────────────────────────────── HELPER: Maximale Zyklen für diese Session Basis 5h + je 5h pro gekaufter Schleife ───────────────────────────────────────────────── */ function calcMaxCycles(loopsPurchased, cycleSeconds) { const maxHours = MAX_BASE_HOURS + loopsPurchased * LOOP_HOURS; return Math.floor((maxHours * 3600) / cycleSeconds); } /* ───────────────────────────────────────────────── GET /api/mine/:buildingId/status ───────────────────────────────────────────────── */ router.get("/:buildingId/status", requireLogin, async (req, res) => { const userId = req.session.user.id; const buildingId = req.params.buildingId; try { const [[userBuilding]] = await db.query( "SELECT id, level FROM user_buildings WHERE user_id = ? AND building_id = ?", [userId, buildingId] ); if (!userBuilding) { return res.status(404).json({ error: "Gebäude nicht gefunden" }); } await ensureTimer(userBuilding.id); const rows = await loadMineData(userId, buildingId); if (!rows.length) { return res.status(404).json({ error: "Keine Produktionsdaten gefunden" }); } const { cycle_seconds, last_collected, selected_resource, loops_purchased, level } = rows[0]; const maxCycles = calcMaxCycles(loops_purchased, cycle_seconds); const maxHours = MAX_BASE_HOURS + loops_purchased * LOOP_HOURS; const elapsed = Math.floor((Date.now() - new Date(last_collected).getTime()) / 1000); const rawCycles = Math.floor(elapsed / cycle_seconds); const cycles = Math.min(rawCycles, maxCycles); const isFull = rawCycles >= maxCycles; const nextIn = isFull ? 0 : cycle_seconds - (elapsed % cycle_seconds); // Menge der gewählten Ressource const selectedRow = selected_resource ? rows.find(r => r.resource === selected_resource) : null; const availableAmount = selectedRow ? selectedRow.amount * cycles : 0; // Produktionsübersicht aller vier Ressourcen const production = rows.map(r => ({ resource: r.resource, amount: r.amount })); // Juwelen des Spielers für Loop-Anzeige const [[currency]] = await db.query( "SELECT gems FROM account_currency WHERE account_id = ?", [userId] ); const gems = currency?.gems ?? 0; res.json({ level, cycles, max_cycles: maxCycles, max_hours: maxHours, is_full: isFull, ready: cycles > 0 && !!selected_resource, selected_resource, loops_purchased, loops_available: MAX_LOOPS - loops_purchased, loop_cost_gems: LOOP_COST_GEMS, can_afford_loop: gems >= LOOP_COST_GEMS, player_gems: gems, available_amount: availableAmount, production, last_collected, next_cycle_in_seconds: nextIn, cycle_seconds, }); } catch (err) { console.error(err); res.status(500).json({ error: "DB Fehler" }); } }); /* ───────────────────────────────────────────────── POST /api/mine/:buildingId/select Ressource wählen – setzt Timer und Schleifen zurück ───────────────────────────────────────────────── */ router.post("/:buildingId/select", requireLogin, async (req, res) => { const userId = req.session.user.id; const buildingId = req.params.buildingId; const { resource } = req.body; if (!MINE_RESOURCES.includes(resource)) { return res.status(400).json({ error: "Ungültige Ressource" }); } try { const [[userBuilding]] = await db.query( "SELECT id FROM user_buildings WHERE user_id = ? AND building_id = ?", [userId, buildingId] ); if (!userBuilding) { return res.status(404).json({ error: "Gebäude nicht gefunden" }); } await ensureTimer(userBuilding.id); // Prüfen ob Ressource in building_production vorhanden const [[prod]] = await db.query( `SELECT bp.resource FROM user_buildings ub JOIN building_production bp ON bp.building_id = ub.building_id AND bp.level = ub.level WHERE ub.id = ? AND bp.resource = ?`, [userBuilding.id, resource] ); if (!prod) { return res.status(400).json({ error: "Ressource für dieses Gebäude nicht verfügbar" }); } // Timer zurücksetzen, Ressource setzen, Schleifen zurücksetzen await db.query( `UPDATE building_collect_timer SET selected_resource = ?, last_collected = NOW(), loops_purchased = 0 WHERE user_building_id = ?`, [resource, userBuilding.id] ); res.json({ success: true, selected_resource: resource }); } catch (err) { console.error(err); res.status(500).json({ error: "DB Fehler" }); } }); /* ───────────────────────────────────────────────── POST /api/mine/:buildingId/collect Ressourcen gutschreiben + Timer vorsetzen ───────────────────────────────────────────────── */ router.post("/:buildingId/collect", requireLogin, async (req, res) => { const userId = req.session.user.id; const buildingId = req.params.buildingId; try { const [[userBuilding]] = await db.query( "SELECT id FROM user_buildings WHERE user_id = ? AND building_id = ?", [userId, buildingId] ); if (!userBuilding) { return res.status(404).json({ error: "Gebäude nicht gefunden" }); } await ensureTimer(userBuilding.id); const rows = await loadMineData(userId, buildingId); if (!rows.length) { return res.status(404).json({ error: "Gebäude nicht gefunden" }); } const { user_building_id, cycle_seconds, last_collected, selected_resource, loops_purchased } = rows[0]; if (!selected_resource) { return res.status(400).json({ error: "Keine Ressource ausgewählt" }); } const maxCycles = calcMaxCycles(loops_purchased, cycle_seconds); const elapsed = Math.floor((Date.now() - new Date(last_collected).getTime()) / 1000); const rawCycles = Math.floor(elapsed / cycle_seconds); const cycles = Math.min(rawCycles, maxCycles); if (cycles < 1) { const waitSeconds = cycle_seconds - elapsed; return res.json({ error: "Noch nichts bereit", ready_in_seconds: waitSeconds, ready_in_display: `${Math.floor(waitSeconds / 60)}m ${waitSeconds % 60}s`, }); } const selectedRow = rows.find(r => r.resource === selected_resource); if (!selectedRow) { return res.status(400).json({ error: "Ressource nicht gefunden" }); } const toAdd = selectedRow.amount * cycles; await db.query( `UPDATE account_currency SET \`${selected_resource}\` = \`${selected_resource}\` + ? WHERE account_id = ?`, [toAdd, userId] ); // Timer exakt vorsetzen – Restsekunden bleiben erhalten const newLastCollected = new Date( new Date(last_collected).getTime() + cycles * cycle_seconds * 1000 ); // Schleifen nach dem Abholen zurücksetzen await db.query( `UPDATE building_collect_timer SET last_collected = ?, loops_purchased = 0 WHERE user_building_id = ?`, [newLastCollected, user_building_id] ); res.json({ success: true, cycles, collected: { resource: selected_resource, amount: toAdd }, }); } catch (err) { console.error(err); res.status(500).json({ error: "DB Fehler" }); } }); /* ───────────────────────────────────────────────── POST /api/mine/:buildingId/loop Schleife für 10 Juwelen kaufen (max. 4 pro Session) ───────────────────────────────────────────────── */ router.post("/:buildingId/loop", requireLogin, async (req, res) => { const userId = req.session.user.id; const buildingId = req.params.buildingId; try { const [[userBuilding]] = await db.query( "SELECT id FROM user_buildings WHERE user_id = ? AND building_id = ?", [userId, buildingId] ); if (!userBuilding) { return res.status(404).json({ error: "Gebäude nicht gefunden" }); } const [[timer]] = await db.query( "SELECT loops_purchased, selected_resource FROM building_collect_timer WHERE user_building_id = ?", [userBuilding.id] ); if (!timer) { return res.status(404).json({ error: "Timer nicht gefunden" }); } if (!timer.selected_resource) { return res.status(400).json({ error: "Erst eine Ressource auswählen" }); } if (timer.loops_purchased >= MAX_LOOPS) { return res.status(400).json({ error: "Maximale Anzahl Schleifen bereits erreicht" }); } // Juwelen prüfen const [[currency]] = await db.query( "SELECT gems FROM account_currency WHERE account_id = ?", [userId] ); if (!currency || currency.gems < LOOP_COST_GEMS) { return res.status(400).json({ error: `Nicht genug Juwelen (benötigt: ${LOOP_COST_GEMS}, vorhanden: ${currency?.gems ?? 0})`, }); } // Juwelen abziehen & Schleife gutschreiben await db.query( "UPDATE account_currency SET gems = gems - ? WHERE account_id = ?", [LOOP_COST_GEMS, userId] ); await db.query( "UPDATE building_collect_timer SET loops_purchased = loops_purchased + 1 WHERE user_building_id = ?", [userBuilding.id] ); const newLoops = timer.loops_purchased + 1; res.json({ success: true, loops_purchased: newLoops, loops_available: MAX_LOOPS - newLoops, gems_remaining: currency.gems - LOOP_COST_GEMS, }); } catch (err) { console.error(err); res.status(500).json({ error: "DB Fehler" }); } }); module.exports = router;