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

358 lines
13 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 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;