mine hinzugefügt
This commit is contained in:
parent
a5753bf835
commit
ae46dcb20f
2
app.js
2
app.js
@ -21,6 +21,7 @@ const avatar = require("./routes/avatar");
|
|||||||
const equip = require("./routes/equip");
|
const equip = require("./routes/equip");
|
||||||
const equipment = require("./routes/equipment");
|
const equipment = require("./routes/equipment");
|
||||||
const blackmarket = require("./routes/blackmarket");
|
const blackmarket = require("./routes/blackmarket");
|
||||||
|
const mineRoute = require("./routes/mine_route");
|
||||||
|
|
||||||
const compression = require("compression");
|
const compression = require("compression");
|
||||||
|
|
||||||
@ -208,6 +209,7 @@ app.use("/api/avatar", avatar);
|
|||||||
app.use("/api/equip", equip);
|
app.use("/api/equip", equip);
|
||||||
app.use("/api/equipment", equipment);
|
app.use("/api/equipment", equipment);
|
||||||
app.use("/api/blackmarket", blackmarket);
|
app.use("/api/blackmarket", blackmarket);
|
||||||
|
app.use("/api/mine", mineRoute);
|
||||||
|
|
||||||
/* ========================
|
/* ========================
|
||||||
404 Handler
|
404 Handler
|
||||||
|
|||||||
190
public/css/mine.css
Normal file
190
public/css/mine.css
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
/* ================================================
|
||||||
|
mine.css – Mine Gebäude UI
|
||||||
|
Passt zum bestehenden Design aus building.css
|
||||||
|
und dem qm-popup / game-notification System
|
||||||
|
================================================ */
|
||||||
|
|
||||||
|
/* ── Panel-Wrapper ─────────────────────────────── */
|
||||||
|
.mine-panel {
|
||||||
|
padding: 14px 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Header-Zeile: Level + Zyklusinfo ──────────── */
|
||||||
|
.mine-header-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mine-level-badge {
|
||||||
|
background: linear-gradient(135deg, #3a2200, #6a3c00);
|
||||||
|
border: 1px solid #c8952a;
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 4px 12px;
|
||||||
|
color: #f0c84a;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mine-cycles {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #a07830;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Trennlinie ────────────────────────────────── */
|
||||||
|
.mine-divider {
|
||||||
|
height: 1px;
|
||||||
|
background: linear-gradient(90deg, transparent, #7a5a1a, transparent);
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Abschnitt-Titel ───────────────────────────── */
|
||||||
|
.mine-section-title {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #a07830;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
margin: 0 0 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Ressourcen-Liste ──────────────────────────── */
|
||||||
|
.mine-resources {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mine-resource-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
background: rgba(255, 255, 255, 0.03);
|
||||||
|
border: 1px solid rgba(122, 90, 26, 0.3);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 7px 12px;
|
||||||
|
transition: border-color 0.2s, background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mine-resource-row.mine-resource-ready {
|
||||||
|
border-color: rgba(200, 149, 42, 0.55);
|
||||||
|
background: rgba(200, 149, 42, 0.07);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mine-resource-icon {
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 1;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mine-resource-label {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #d4b870;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mine-resource-amount {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #f0c84a;
|
||||||
|
min-width: 32px;
|
||||||
|
text-align: right;
|
||||||
|
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Timer-Zeile ───────────────────────────────── */
|
||||||
|
.mine-timer-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 2px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mine-timer-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #a07830;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mine-timer {
|
||||||
|
font-size: 13px;
|
||||||
|
font-family: monospace;
|
||||||
|
color: #f0c84a;
|
||||||
|
background: rgba(0, 0, 0, 0.35);
|
||||||
|
border: 1px solid #7a5a1a;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 3px 10px;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Aktions-Bereich ───────────────────────────── */
|
||||||
|
.mine-actions {
|
||||||
|
margin-top: 14px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Abholen-Button (wie notification-btn) ─────── */
|
||||||
|
.mine-btn-collect {
|
||||||
|
background: linear-gradient(180deg, #c8952a 0%, #7a5310 100%);
|
||||||
|
color: #fff8e0;
|
||||||
|
border: 1px solid #f0c84a;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 9px 28px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
transition: filter 0.15s, transform 0.1s;
|
||||||
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
|
||||||
|
width: 100%;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mine-btn-collect:hover:not(:disabled) {
|
||||||
|
filter: brightness(1.2);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mine-btn-collect:active:not(:disabled) {
|
||||||
|
transform: translateY(0);
|
||||||
|
filter: brightness(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mine-btn-collect.mine-btn-disabled,
|
||||||
|
.mine-btn-collect:disabled {
|
||||||
|
background: linear-gradient(180deg, #3a3020 0%, #2a2010 100%);
|
||||||
|
border-color: #5a4a1a;
|
||||||
|
color: #7a6a3a;
|
||||||
|
cursor: not-allowed;
|
||||||
|
box-shadow: none;
|
||||||
|
transform: none;
|
||||||
|
filter: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Lade- & Fehlerzustände ────────────────────── */
|
||||||
|
.mine-loading {
|
||||||
|
padding: 24px;
|
||||||
|
text-align: center;
|
||||||
|
color: #a07830;
|
||||||
|
font-size: 13px;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
animation: minePulse 1.2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes minePulse {
|
||||||
|
0%, 100% { opacity: 1; }
|
||||||
|
50% { opacity: 0.45; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.mine-error {
|
||||||
|
padding: 16px;
|
||||||
|
text-align: center;
|
||||||
|
color: #c84040;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
207
public/js/buildings/mine.js
Normal file
207
public/js/buildings/mine.js
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
/* ================================
|
||||||
|
Mine – Frontend
|
||||||
|
Lädt den Aktionen-Tab mit
|
||||||
|
Produktionsanzeige + Abholen-Button
|
||||||
|
================================ */
|
||||||
|
|
||||||
|
/* showNotification() kommt aus dem globalen Scope (launcher.ejs) */
|
||||||
|
|
||||||
|
export async function loadMine(buildingId) {
|
||||||
|
const actionsTab = document.getElementById("tab-actions");
|
||||||
|
if (!actionsTab) return;
|
||||||
|
|
||||||
|
actionsTab.innerHTML = `<div class="mine-loading">⛏️ Lade Mineninfo...</div>`;
|
||||||
|
|
||||||
|
await renderMineStatus(buildingId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─────────────────────────────────────────
|
||||||
|
Status rendern
|
||||||
|
───────────────────────────────────────── */
|
||||||
|
async function renderMineStatus(buildingId) {
|
||||||
|
const actionsTab = document.getElementById("tab-actions");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/mine/${buildingId}/status`);
|
||||||
|
if (!res.ok) throw new Error("API Fehler");
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
if (data.error) {
|
||||||
|
actionsTab.innerHTML = `<p class="mine-error">⚠️ ${data.error}</p>`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const minutesLeft = Math.floor(data.next_cycle_in_seconds / 60);
|
||||||
|
const secondsLeft = data.next_cycle_in_seconds % 60;
|
||||||
|
|
||||||
|
/* Ressourcen-Zeilen */
|
||||||
|
const resourceRows = data.available
|
||||||
|
.map((r) => {
|
||||||
|
const icon = resourceIcon(r.resource);
|
||||||
|
const label = resourceLabel(r.resource);
|
||||||
|
const ready = data.ready;
|
||||||
|
return `
|
||||||
|
<div class="mine-resource-row ${ready ? "mine-resource-ready" : ""}">
|
||||||
|
<span class="mine-resource-icon">${icon}</span>
|
||||||
|
<span class="mine-resource-label">${label}</span>
|
||||||
|
<span class="mine-resource-amount">${r.amount}</span>
|
||||||
|
</div>`;
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
actionsTab.innerHTML = `
|
||||||
|
<div class="mine-panel">
|
||||||
|
|
||||||
|
<div class="mine-header-row">
|
||||||
|
<span class="mine-level-badge">⛏️ Level ${data.level}</span>
|
||||||
|
<span class="mine-cycles">${data.cycles > 0 ? `${data.cycles}× Zyklus abgeschlossen` : "Läuft..."}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mine-divider"></div>
|
||||||
|
|
||||||
|
<p class="mine-section-title">Abgebaut</p>
|
||||||
|
<div class="mine-resources">
|
||||||
|
${resourceRows}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mine-divider"></div>
|
||||||
|
|
||||||
|
<div class="mine-timer-row">
|
||||||
|
<span class="mine-timer-label">Nächster Zyklus in</span>
|
||||||
|
<span class="mine-timer" id="mine-countdown"
|
||||||
|
data-seconds="${data.next_cycle_in_seconds}">
|
||||||
|
${minutesLeft}m ${secondsLeft}s
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mine-actions">
|
||||||
|
<button
|
||||||
|
class="mine-btn-collect ${data.ready ? "" : "mine-btn-disabled"}"
|
||||||
|
id="mine-collect-btn"
|
||||||
|
data-building="${buildingId}"
|
||||||
|
${data.ready ? "" : "disabled"}
|
||||||
|
>
|
||||||
|
${data.ready ? "⛏️ Abholen" : "⏳ Noch nicht bereit"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
startCountdown(buildingId);
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Mine Fehler:", err);
|
||||||
|
actionsTab.innerHTML = `<p class="mine-error">⚠️ Fehler beim Laden der Mineninfo.</p>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─────────────────────────────────────────
|
||||||
|
Abholen-Klick
|
||||||
|
───────────────────────────────────────── */
|
||||||
|
document.addEventListener("click", async (e) => {
|
||||||
|
const btn = e.target.closest("#mine-collect-btn");
|
||||||
|
if (!btn || btn.disabled) return;
|
||||||
|
|
||||||
|
const buildingId = btn.dataset.building;
|
||||||
|
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.textContent = "⏳ Wird abgeholt...";
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/mine/${buildingId}/collect`, { method: "POST" });
|
||||||
|
if (!res.ok) throw new Error("API Fehler");
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
if (data.error) {
|
||||||
|
showNotification(
|
||||||
|
data.ready_in_display
|
||||||
|
? `Die Mine ist noch nicht bereit.\nBereit in: ${data.ready_in_display}`
|
||||||
|
: data.error,
|
||||||
|
"Mine",
|
||||||
|
"⛏️"
|
||||||
|
);
|
||||||
|
await renderMineStatus(buildingId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Erfolg – was wurde abgeholt? */
|
||||||
|
const lines = data.collected
|
||||||
|
.map((c) => `${resourceIcon(c.resource)} ${resourceLabel(c.resource)}: +${c.amount}`)
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
|
showNotification(
|
||||||
|
`Erfolgreich abgeholt!\n\n${lines}`,
|
||||||
|
"Mine",
|
||||||
|
"⛏️"
|
||||||
|
);
|
||||||
|
|
||||||
|
await renderMineStatus(buildingId);
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Abholen Fehler:", err);
|
||||||
|
showNotification(
|
||||||
|
"Fehler beim Abholen. Bitte erneut versuchen.",
|
||||||
|
"Fehler",
|
||||||
|
"⚠️"
|
||||||
|
);
|
||||||
|
await renderMineStatus(buildingId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/* ─────────────────────────────────────────
|
||||||
|
Countdown-Timer (live)
|
||||||
|
───────────────────────────────────────── */
|
||||||
|
let countdownInterval = null;
|
||||||
|
|
||||||
|
function startCountdown(buildingId) {
|
||||||
|
if (countdownInterval) clearInterval(countdownInterval);
|
||||||
|
|
||||||
|
countdownInterval = setInterval(() => {
|
||||||
|
const el = document.getElementById("mine-countdown");
|
||||||
|
if (!el) {
|
||||||
|
clearInterval(countdownInterval);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let secs = parseInt(el.dataset.seconds, 10) - 1;
|
||||||
|
if (secs < 0) secs = 0;
|
||||||
|
el.dataset.seconds = secs;
|
||||||
|
|
||||||
|
const m = Math.floor(secs / 60);
|
||||||
|
const s = secs % 60;
|
||||||
|
el.textContent = `${m}m ${s}s`;
|
||||||
|
|
||||||
|
/* Zyklus abgeschlossen → UI neu laden */
|
||||||
|
if (secs === 0) {
|
||||||
|
clearInterval(countdownInterval);
|
||||||
|
renderMineStatus(buildingId);
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─────────────────────────────────────────
|
||||||
|
Hilfsfunktionen
|
||||||
|
───────────────────────────────────────── */
|
||||||
|
function resourceIcon(resource) {
|
||||||
|
const map = {
|
||||||
|
gold: "🪙",
|
||||||
|
copper: "🟤",
|
||||||
|
silver: "⚪",
|
||||||
|
iron: "⚙️",
|
||||||
|
stone: "🪨",
|
||||||
|
wood: "🪵",
|
||||||
|
};
|
||||||
|
return map[resource] ?? "📦";
|
||||||
|
}
|
||||||
|
|
||||||
|
function resourceLabel(resource) {
|
||||||
|
const map = {
|
||||||
|
gold: "Gold",
|
||||||
|
copper: "Kupfer",
|
||||||
|
silver: "Silber",
|
||||||
|
iron: "Eisen",
|
||||||
|
stone: "Stein",
|
||||||
|
wood: "Holz",
|
||||||
|
};
|
||||||
|
return map[resource] ?? resource;
|
||||||
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
import { loadWohnhaus } from "./buildings/wohnhaus.js";
|
import { loadWohnhaus } from "./buildings/wohnhaus.js";
|
||||||
import { loadSchwarzmarkt } from "./buildings/schwarzmarkt.js";
|
import { loadSchwarzmarkt } from "./buildings/schwarzmarkt.js";
|
||||||
|
import { loadMine } from "./buildings/mine.js";
|
||||||
const popup = document.getElementById("building-popup");
|
const popup = document.getElementById("building-popup");
|
||||||
const title = document.getElementById("popup-title");
|
const title = document.getElementById("popup-title");
|
||||||
const tooltip = document.getElementById("map-tooltip");
|
const tooltip = document.getElementById("map-tooltip");
|
||||||
@ -9,6 +10,7 @@ const tooltipCache = {};
|
|||||||
const buildingModules = {
|
const buildingModules = {
|
||||||
11: loadWohnhaus,
|
11: loadWohnhaus,
|
||||||
12: loadSchwarzmarkt,
|
12: loadSchwarzmarkt,
|
||||||
|
10: loadMine,
|
||||||
};
|
};
|
||||||
|
|
||||||
/* ================================
|
/* ================================
|
||||||
|
|||||||
163
routes/mine_route.js
Normal file
163
routes/mine_route.js
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
const express = require("express");
|
||||||
|
const router = express.Router();
|
||||||
|
const db = require("../database/database");
|
||||||
|
const auth = require("../middleware/auth");
|
||||||
|
|
||||||
|
/* ─────────────────────────────────────────
|
||||||
|
GET /api/mine/:buildingId/status
|
||||||
|
Liefert Level, verfügbare Ressourcen
|
||||||
|
und Countdown bis zum nächsten Zyklus
|
||||||
|
───────────────────────────────────────── */
|
||||||
|
router.get("/:buildingId/status", auth, async (req, res) => {
|
||||||
|
const userId = req.session.user.id;
|
||||||
|
const buildingId = req.params.buildingId;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [rows] = await db.query(
|
||||||
|
`
|
||||||
|
SELECT
|
||||||
|
ub.id AS user_building_id,
|
||||||
|
ub.level,
|
||||||
|
bp.resource,
|
||||||
|
bp.amount,
|
||||||
|
bp.cycle_seconds,
|
||||||
|
COALESCE(bct.last_collected, NOW()) AS last_collected
|
||||||
|
FROM user_buildings ub
|
||||||
|
JOIN building_production bp
|
||||||
|
ON bp.building_id = ub.building_id
|
||||||
|
AND bp.level = ub.level
|
||||||
|
LEFT JOIN building_collect_timer bct
|
||||||
|
ON bct.user_building_id = ub.id
|
||||||
|
WHERE ub.user_id = ?
|
||||||
|
AND ub.building_id = ?
|
||||||
|
`,
|
||||||
|
[userId, buildingId]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!rows.length) {
|
||||||
|
return res.status(404).json({ error: "Gebäude nicht gefunden" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { cycle_seconds, last_collected, level } = rows[0];
|
||||||
|
|
||||||
|
const elapsed = Math.floor(
|
||||||
|
(Date.now() - new Date(last_collected).getTime()) / 1000
|
||||||
|
);
|
||||||
|
const cycles = Math.floor(elapsed / cycle_seconds);
|
||||||
|
const nextIn = cycle_seconds - (elapsed % cycle_seconds);
|
||||||
|
|
||||||
|
const available = rows.map((r) => ({
|
||||||
|
resource: r.resource,
|
||||||
|
amount: r.amount * cycles,
|
||||||
|
}));
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
level,
|
||||||
|
cycles,
|
||||||
|
ready: cycles > 0,
|
||||||
|
available,
|
||||||
|
last_collected,
|
||||||
|
next_cycle_in_seconds: nextIn,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
res.status(500).json({ error: "DB Fehler" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/* ─────────────────────────────────────────
|
||||||
|
POST /api/mine/:buildingId/collect
|
||||||
|
Schreibt Ressourcen gut, setzt Timer
|
||||||
|
───────────────────────────────────────── */
|
||||||
|
router.post("/:buildingId/collect", auth, async (req, res) => {
|
||||||
|
const userId = req.session.user.id;
|
||||||
|
const buildingId = req.params.buildingId;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [rows] = await db.query(
|
||||||
|
`
|
||||||
|
SELECT
|
||||||
|
ub.id AS user_building_id,
|
||||||
|
ub.level,
|
||||||
|
bp.resource,
|
||||||
|
bp.amount,
|
||||||
|
bp.cycle_seconds,
|
||||||
|
COALESCE(bct.last_collected, NOW()) AS last_collected
|
||||||
|
FROM user_buildings ub
|
||||||
|
JOIN building_production bp
|
||||||
|
ON bp.building_id = ub.building_id
|
||||||
|
AND bp.level = ub.level
|
||||||
|
LEFT JOIN building_collect_timer bct
|
||||||
|
ON bct.user_building_id = ub.id
|
||||||
|
WHERE ub.user_id = ?
|
||||||
|
AND ub.building_id = ?
|
||||||
|
`,
|
||||||
|
[userId, buildingId]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!rows.length) {
|
||||||
|
return res.status(404).json({ error: "Gebäude nicht gefunden" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { user_building_id, cycle_seconds, last_collected } = rows[0];
|
||||||
|
|
||||||
|
const elapsed = Math.floor(
|
||||||
|
(Date.now() - new Date(last_collected).getTime()) / 1000
|
||||||
|
);
|
||||||
|
const cycles = Math.floor(elapsed / cycle_seconds);
|
||||||
|
|
||||||
|
if (cycles < 1) {
|
||||||
|
const waitSeconds = cycle_seconds - elapsed;
|
||||||
|
const minutes = Math.floor(waitSeconds / 60);
|
||||||
|
const seconds = waitSeconds % 60;
|
||||||
|
return res.json({
|
||||||
|
error: "Noch nichts bereit",
|
||||||
|
ready_in_seconds: waitSeconds,
|
||||||
|
ready_in_display: `${minutes}m ${seconds}s`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ressourcen gutschreiben
|
||||||
|
for (const row of rows) {
|
||||||
|
const toAdd = row.amount * cycles;
|
||||||
|
await db.query(
|
||||||
|
`
|
||||||
|
INSERT INTO account_currency (account_id, \`${row.resource}\`)
|
||||||
|
VALUES (?, ?)
|
||||||
|
ON DUPLICATE KEY UPDATE \`${row.resource}\` = \`${row.resource}\` + ?
|
||||||
|
`,
|
||||||
|
[userId, toAdd, toAdd]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timer vorsetzen — Rest-Sekunden bleiben erhalten, kein Verlust
|
||||||
|
const newLastCollected = new Date(
|
||||||
|
new Date(last_collected).getTime() + cycles * cycle_seconds * 1000
|
||||||
|
);
|
||||||
|
|
||||||
|
await db.query(
|
||||||
|
`
|
||||||
|
INSERT INTO building_collect_timer (user_building_id, last_collected)
|
||||||
|
VALUES (?, ?)
|
||||||
|
ON DUPLICATE KEY UPDATE last_collected = ?
|
||||||
|
`,
|
||||||
|
[user_building_id, newLastCollected, newLastCollected]
|
||||||
|
);
|
||||||
|
|
||||||
|
const collected = rows.map((r) => ({
|
||||||
|
resource: r.resource,
|
||||||
|
amount: r.amount * cycles,
|
||||||
|
}));
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
cycles,
|
||||||
|
collected,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
res.status(500).json({ error: "DB Fehler" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
@ -12,6 +12,7 @@
|
|||||||
<link rel="stylesheet" href="/css/global.css" />
|
<link rel="stylesheet" href="/css/global.css" />
|
||||||
<link rel="stylesheet" href="/css/building.css" />
|
<link rel="stylesheet" href="/css/building.css" />
|
||||||
<link rel="stylesheet" href="/css/quickmenu.css" />
|
<link rel="stylesheet" href="/css/quickmenu.css" />
|
||||||
|
<link rel="stylesheet" href="/css/mine.css" />
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user