dok/routes/mine.route.js
2026-04-08 14:15:17 +01:00

377 lines
14 KiB
JavaScript
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.

const express = require("express");
const router = require("express").Router();
const db = require("../database/database");
/* ── Konstanten ─────────────────────────────────── */
const SESSION_HOURS = 5; // Stunden pro Session / Slot
const MAX_QUEUE_SLOTS = 4; // Maximale Schleifen in der Warteschlange
const LOOP_COST_GEMS = 10; // Juwelen 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
───────────────────────────────────────────────── */
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, loop_queue)
VALUES (?, NOW(), NULL, JSON_ARRAY())`,
[userBuildingId]
);
}
}
/* ─────────────────────────────────────────────────
HELPER: Produktionsdaten + Timer laden
───────────────────────────────────────────────── */
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.loop_queue
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 einer 5h-Session
───────────────────────────────────────────────── */
function calcMaxCycles(cycleSeconds) {
return Math.floor((SESSION_HOURS * 3600) / cycleSeconds);
}
/* ─────────────────────────────────────────────────
HELPER: loop_queue sicher parsen
───────────────────────────────────────────────── */
function parseQueue(raw) {
if (!raw) return [];
try {
const q = typeof raw === "string" ? JSON.parse(raw) : raw;
return Array.isArray(q) ? q : [];
} catch {
return [];
}
}
/* ─────────────────────────────────────────────────
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, loop_queue, level
} = rows[0];
const queue = parseQueue(loop_queue);
const maxCycles = calcMaxCycles(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);
const isFull = rawCycles >= maxCycles;
const nextIn = isFull ? 0 : cycle_seconds - (elapsed % cycle_seconds);
const selectedRow = selected_resource
? rows.find(r => r.resource === selected_resource)
: null;
const availableAmount = selectedRow ? selectedRow.amount * cycles : 0;
const production = rows.map(r => ({ resource: r.resource, amount: r.amount }));
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,
session_hours: SESSION_HOURS,
is_full: isFull,
ready: cycles > 0 && !!selected_resource,
selected_resource,
loop_queue: queue, // z.B. ["wood", "iron"]
loop_slots_used: queue.length,
loop_slots_free: MAX_QUEUE_SLOTS - queue.length,
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
Erste Ressource wählen startet Session neu
───────────────────────────────────────────────── */
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);
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 nicht verfügbar" });
}
// Session neu starten, Warteschlange leeren
await db.query(
`UPDATE building_collect_timer
SET selected_resource = ?, last_collected = NOW(), loop_queue = JSON_ARRAY()
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/loop
Schleife mit eigener Ressourcenwahl zur Queue hinzufügen
───────────────────────────────────────────────── */
router.post("/:buildingId/loop", 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" });
}
const [[timer]] = await db.query(
"SELECT loop_queue, 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 für die aktuelle Session wählen" });
}
const queue = parseQueue(timer.loop_queue);
if (queue.length >= MAX_QUEUE_SLOTS) {
return res.status(400).json({ error: "Warteschlange ist voll (max. 4 Schleifen)" });
}
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 & Ressource ans Ende der Queue hängen
await db.query(
"UPDATE account_currency SET gems = gems - ? WHERE account_id = ?",
[LOOP_COST_GEMS, userId]
);
queue.push(resource);
await db.query(
"UPDATE building_collect_timer SET loop_queue = ? WHERE user_building_id = ?",
[JSON.stringify(queue), userBuilding.id]
);
res.json({
success: true,
loop_queue: queue,
loop_slots_used: queue.length,
loop_slots_free: MAX_QUEUE_SLOTS - queue.length,
gems_remaining: currency.gems - LOOP_COST_GEMS,
});
} catch (err) {
console.error(err);
res.status(500).json({ error: "DB Fehler" });
}
});
/* ─────────────────────────────────────────────────
POST /api/mine/:buildingId/collect
Ressourcen gutschreiben + nächsten Queue-Slot starten
───────────────────────────────────────────────── */
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, loop_queue
} = rows[0];
if (!selected_resource) {
return res.status(400).json({ error: "Keine Ressource ausgewählt" });
}
const maxCycles = calcMaxCycles(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" });
}
// Ressource gutschreiben
const toAdd = selectedRow.amount * cycles;
await db.query(
`UPDATE account_currency
SET \`${selected_resource}\` = \`${selected_resource}\` + ?
WHERE account_id = ?`,
[toAdd, userId]
);
// Queue: erstes Element wird zur neuen aktiven Session
const queue = parseQueue(loop_queue);
const nextResource = queue.length > 0 ? queue.shift() : null;
// Timer exakt vorsetzen (Restsekunden bleiben erhalten)
const newLastCollected = new Date(
new Date(last_collected).getTime() + cycles * cycle_seconds * 1000
);
await db.query(
`UPDATE building_collect_timer
SET last_collected = ?,
selected_resource = ?,
loop_queue = ?
WHERE user_building_id = ?`,
[newLastCollected, nextResource, JSON.stringify(queue), user_building_id]
);
res.json({
success: true,
cycles,
collected: { resource: selected_resource, amount: toAdd },
next_resource: nextResource, // null wenn Queue leer war
queue_remaining: queue.length,
});
} catch (err) {
console.error(err);
res.status(500).json({ error: "DB Fehler" });
}
});
module.exports = router;