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