From d394657b185f73bb8cd91fb2ee48086f188476e7 Mon Sep 17 00:00:00 2001 From: cay Date: Wed, 8 Apr 2026 16:48:02 +0100 Subject: [PATCH] ydfnd --- public/js/buildings/mine.js | 132 ++++++++++++++++++++------------- routes/mine.route.js | 144 +++++++++++++++++++++++------------- views/launcher.ejs | 2 +- 3 files changed, 173 insertions(+), 105 deletions(-) diff --git a/public/js/buildings/mine.js b/public/js/buildings/mine.js index f5d80e8..17ef76b 100644 --- a/public/js/buildings/mine.js +++ b/public/js/buildings/mine.js @@ -28,17 +28,14 @@ async function renderMineStatus(buildingId) { "
" + renderHeader(data) + renderDivider() + - - // Ressource wählen (nur wenn noch keine aktive Session) (!data.selected_resource ? renderResourceSelector(data, buildingId) : renderActiveSession(data, buildingId) ) + - "
"; if (data.selected_resource) { - startCountdown(buildingId, data); + startSessionCountdown(buildingId, data); } } catch (err) { console.error("Mine Fehler:", err); @@ -53,8 +50,8 @@ async function renderMineStatus(buildingId) { function renderHeader(data) { let cycleText = "Keine Ressource gewählt"; if (data.selected_resource) { - if (data.is_full) cycleText = "Voll – bitte abholen!"; - else if (data.cycles > 0) cycleText = data.cycles + "x Zyklus abgeschlossen"; + if (data.is_full) cycleText = "Session beendet – bitte abholen!"; + else if (data.cycles > 0) cycleText = data.cycles + "x Zyklus bereit"; else cycleText = "Läuft..."; } return ( @@ -66,7 +63,7 @@ function renderHeader(data) { } /* ───────────────────────────────────────────────── - Ressourcen-Auswahl (Startscreen ohne aktive Session) + Startscreen (keine aktive Session) ───────────────────────────────────────────────── */ function renderResourceSelector(data, buildingId) { return ( @@ -77,30 +74,32 @@ function renderResourceSelector(data, buildingId) { } /* ───────────────────────────────────────────────── - Aktive Session (mit Timer, Queue, Collect-Button) + Aktive Session ───────────────────────────────────────────────── */ function renderActiveSession(data, buildingId) { return ( - // Aktuelle Produktion renderCurrentProduction(data) + renderDivider() + - - // Warteschlangen-Bereich renderQueueSection(data, buildingId) + renderDivider() + - - // Abholen-Button renderCollectSection(data, buildingId) ); } /* ───────────────────────────────────────────────── Aktuelle Produktion + Zwei Timer: + 1. Session-Countdown → wie lange läuft die 5h noch? + 2. Zyklus-Countdown → wann ist der nächste Zyklus fertig? ───────────────────────────────────────────────── */ function renderCurrentProduction(data) { - const minutesLeft = Math.floor(data.next_cycle_in_seconds / 60); - const secondsLeft = data.next_cycle_in_seconds % 60; - const progressPct = Math.min(100, Math.round((data.cycles / data.max_cycles) * 100)); + const sessionMin = Math.floor(data.session_seconds_left / 60); + const sessionSec = data.session_seconds_left % 60; + const cycleMin = Math.floor(data.next_cycle_in_seconds / 60); + const cycleSec = data.next_cycle_in_seconds % 60; + + /* Fortschritt: wieviele Zyklen von max wurden in der Session erreicht */ + const progressPct = Math.min(100, Math.round((data.cycles / data.max_cycles) * 100)); return ( "

Aktuelle Session – " + data.session_hours + "h

" + @@ -109,27 +108,38 @@ function renderCurrentProduction(data) { "" + resourceLabel(data.selected_resource) + "" + "" + data.available_amount + "" + "" + + + /* Fortschrittsbalken */ "
" + "
" + "
" + + + /* Session-Timer (läuft immer durch) */ "
" + - "" + - (data.is_full ? "Zeit abgelaufen!" : "Nächster Zyklus in") + - "" + + "Session endet in" + (data.is_full - ? "VOLL" - : "" + - minutesLeft + "m " + secondsLeft + "s" + ? "FERTIG" + : "" + + sessionMin + "m " + sessionSec + "s" ) + - "
" + "" + + + /* Zyklus-Timer (nur wenn Session noch läuft und noch Zyklen kommen) */ + (!data.is_full + ? "
" + + "Nächster Zyklus in" + + "" + + cycleMin + "m " + cycleSec + "s" + + "
" + : "" + ) ); } /* ───────────────────────────────────────────────── - Warteschlangen-Bereich - – zeigt belegte Slots als Ressourcen-Badges - – zeigt freie Slots als aufklappbare Auswahl + Warteschlange ───────────────────────────────────────────────── */ function renderQueueSection(data, buildingId) { const { loop_queue, loop_slots_free, loop_slots_used, @@ -139,7 +149,6 @@ function renderQueueSection(data, buildingId) { "

Warteschlange" + " (" + loop_slots_used + "/4)

"; - // Belegte Slots loop_queue.forEach((res, i) => { html += "
" + @@ -150,11 +159,10 @@ function renderQueueSection(data, buildingId) { "
"; }); - // Freie Slots (kaufbar) if (loop_slots_free > 0) { const nextPos = loop_slots_used + 1; html += - "
" + + "
" + "" ); }).join(""); - return "
" + buttons + "
"; } @@ -224,22 +229,20 @@ function renderDivider() { } /* ───────────────────────────────────────────────── - Event: Toggle Schleifenpicker + Event: Loop-Picker togglen ───────────────────────────────────────────────── */ document.addEventListener("click", (e) => { const btn = e.target.closest("#mine-queue-toggle"); if (!btn || btn.disabled) return; - const picker = document.getElementById("mine-loop-picker"); if (!picker) return; - const isOpen = picker.style.display !== "none"; picker.style.display = isOpen ? "none" : "block"; btn.classList.toggle("mine-btn-add-loop-open", !isOpen); }); /* ───────────────────────────────────────────────── - Event: Ressource wählen (select) oder Schleife buchen (loop) + Event: Ressource wählen / Schleife buchen ───────────────────────────────────────────────── */ document.addEventListener("click", async (e) => { const btn = e.target.closest(".mine-res-btn"); @@ -247,7 +250,7 @@ document.addEventListener("click", async (e) => { const resource = btn.dataset.resource; const buildingId = btn.dataset.building; - const mode = btn.dataset.mode; // "select" | "loop" + const mode = btn.dataset.mode; document.querySelectorAll(".mine-res-btn").forEach(b => (b.disabled = true)); @@ -322,11 +325,13 @@ document.addEventListener("click", async (e) => { const c = data.collected; let msg = "Abgeholt!\n" + resourceLabel(c.resource) + ": +" + c.amount; - if (data.next_resource) { - msg += "\n\nNächste Session: " + resourceLabel(data.next_resource) + " (5h)"; - } else { - msg += "\n\nKeine weiteren Schleifen – neue Ressource wählen."; + + if (data.session_ended) { + msg += data.next_resource + ? "\n\nNächste Session: " + resourceLabel(data.next_resource) + " (5h)" + : "\n\nKeine weiteren Schleifen – neue Ressource wählen."; } + /* Session läuft noch → kein Hinweis nötig */ showNotification(msg, "Mine", "⛏️"); await renderMineStatus(buildingId); @@ -339,17 +344,23 @@ document.addEventListener("click", async (e) => { }); /* ───────────────────────────────────────────────── - Countdown-Timer + Zwei parallele Countdowns: + 1. Session-Countdown (mine-session-countdown) + 2. Zyklus-Countdown (mine-cycle-countdown) ───────────────────────────────────────────────── */ -let countdownInterval = null; +let sessionInterval = null; +let cycleInterval = null; + +function startSessionCountdown(buildingId, data) { + if (sessionInterval) clearInterval(sessionInterval); + if (cycleInterval) clearInterval(cycleInterval); -function startCountdown(buildingId, data) { - if (countdownInterval) clearInterval(countdownInterval); if (data.is_full) return; - countdownInterval = setInterval(() => { - const el = document.getElementById("mine-countdown"); - if (!el) { clearInterval(countdownInterval); return; } + /* Session-Timer */ + sessionInterval = setInterval(() => { + const el = document.getElementById("mine-session-countdown"); + if (!el) { clearInterval(sessionInterval); return; } let secs = parseInt(el.dataset.seconds, 10) - 1; if (secs < 0) secs = 0; @@ -357,8 +368,25 @@ function startCountdown(buildingId, data) { el.textContent = Math.floor(secs / 60) + "m " + (secs % 60) + "s"; if (secs === 0) { - clearInterval(countdownInterval); - renderMineStatus(buildingId); + clearInterval(sessionInterval); + clearInterval(cycleInterval); + renderMineStatus(buildingId); // Session abgelaufen → neu rendern + } + }, 1000); + + /* Zyklus-Timer */ + cycleInterval = setInterval(() => { + const el = document.getElementById("mine-cycle-countdown"); + if (!el) { clearInterval(cycleInterval); return; } + + let secs = parseInt(el.dataset.seconds, 10) - 1; + if (secs < 0) secs = 0; + el.dataset.seconds = secs; + el.textContent = Math.floor(secs / 60) + "m " + (secs % 60) + "s"; + + if (secs === 0) { + clearInterval(cycleInterval); + renderMineStatus(buildingId); // Neuer Zyklus → Menge aktualisieren } }, 1000); } diff --git a/routes/mine.route.js b/routes/mine.route.js index 2032811..17a8acb 100644 --- a/routes/mine.route.js +++ b/routes/mine.route.js @@ -3,10 +3,10 @@ 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"]; +const SESSION_HOURS = 5; +const MAX_QUEUE_SLOTS = 4; +const LOOP_COST_GEMS = 10; +const MINE_RESOURCES = ["gold", "iron", "stone", "wood"]; /* ── Auth-Guard ─────────────────────────────────── */ function requireLogin(req, res, next) { @@ -27,8 +27,8 @@ async function ensureTimer(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())`, + (user_building_id, last_collected, session_started, selected_resource, loop_queue) + VALUES (?, NOW(), NOW(), NULL, JSON_ARRAY())`, [userBuildingId] ); } @@ -46,6 +46,7 @@ async function loadMineData(userId, buildingId) { bp.amount, bp.cycle_seconds, bct.last_collected, + bct.session_started, bct.selected_resource, bct.loop_queue FROM user_buildings ub @@ -77,9 +78,7 @@ function parseQueue(raw) { try { const q = typeof raw === "string" ? JSON.parse(raw) : raw; return Array.isArray(q) ? q : []; - } catch { - return []; - } + } catch { return []; } } /* ───────────────────────────────────────────────── @@ -105,18 +104,34 @@ router.get("/:buildingId/status", requireLogin, async (req, res) => { } const { - cycle_seconds, last_collected, + cycle_seconds, last_collected, session_started, selected_resource, loop_queue, level } = rows[0]; const queue = parseQueue(loop_queue); const maxCycles = calcMaxCycles(cycle_seconds); + const now = Date.now(); - 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); + /* ── session_started: wie weit ist die 5h-Session? ── */ + const sessionElapsed = Math.floor((now - new Date(session_started).getTime()) / 1000); + const sessionDuration = SESSION_HOURS * 3600; + const isFull = sessionElapsed >= sessionDuration; + + /* Verbleibende Sessionzeit für den Countdown */ + const sessionSecondsLeft = isFull ? 0 : sessionDuration - sessionElapsed; + + /* ── last_collected: welche Zyklen noch nicht abgeholt? ── */ + const collectElapsed = Math.floor((now - new Date(last_collected).getTime()) / 1000); + const rawCycles = Math.floor(collectElapsed / cycle_seconds); + + /* Nicht mehr als max_cycles und nicht über das Session-Ende hinaus */ + const maxCyclesLeft = Math.floor(sessionElapsed / cycle_seconds); + const cycles = Math.min(rawCycles, maxCycles, maxCyclesLeft); + + /* Nächster Zyklus: basierend auf last_collected, aber nur wenn Session noch läuft */ + const nextCycleIn = isFull + ? 0 + : cycle_seconds - (collectElapsed % cycle_seconds); const selectedRow = selected_resource ? rows.find(r => r.resource === selected_resource) @@ -134,21 +149,23 @@ router.get("/:buildingId/status", requireLogin, async (req, res) => { res.json({ level, cycles, - max_cycles: maxCycles, - session_hours: SESSION_HOURS, - is_full: isFull, - ready: cycles > 0 && !!selected_resource, + max_cycles: maxCycles, + session_hours: SESSION_HOURS, + session_seconds_left: sessionSecondsLeft, // ← komplette Session-Restzeit + next_cycle_in_seconds: nextCycleIn, // ← bis zum nächsten Zyklus + 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, + loop_queue: queue, + 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, + session_started, cycle_seconds, }); } catch (err) { @@ -159,7 +176,7 @@ router.get("/:buildingId/status", requireLogin, async (req, res) => { /* ───────────────────────────────────────────────── POST /api/mine/:buildingId/select - Erste Ressource wählen – startet Session neu + Neue Ressource wählen – setzt BEIDE Zeitstempel ───────────────────────────────────────────────── */ router.post("/:buildingId/select", requireLogin, async (req, res) => { const userId = req.session.user.id; @@ -193,10 +210,13 @@ router.post("/:buildingId/select", requireLogin, async (req, res) => { return res.status(400).json({ error: "Ressource nicht verfügbar" }); } - // Session neu starten, Warteschlange leeren + /* Beide Zeitstempel auf NOW() – frische Session */ await db.query( `UPDATE building_collect_timer - SET selected_resource = ?, last_collected = NOW(), loop_queue = JSON_ARRAY() + SET selected_resource = ?, + last_collected = NOW(), + session_started = NOW(), + loop_queue = JSON_ARRAY() WHERE user_building_id = ?`, [resource, userBuilding.id] ); @@ -210,7 +230,7 @@ router.post("/:buildingId/select", requireLogin, async (req, res) => { /* ───────────────────────────────────────────────── POST /api/mine/:buildingId/loop - Schleife mit eigener Ressourcenwahl zur Queue hinzufügen + Schleife zur Queue hinzufügen ───────────────────────────────────────────────── */ router.post("/:buildingId/loop", requireLogin, async (req, res) => { const userId = req.session.user.id; @@ -234,10 +254,7 @@ router.post("/:buildingId/loop", requireLogin, async (req, res) => { "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) { + if (!timer?.selected_resource) { return res.status(400).json({ error: "Erst eine Ressource für die aktuelle Session wählen" }); } @@ -256,7 +273,6 @@ router.post("/:buildingId/loop", requireLogin, async (req, res) => { }); } - // 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] @@ -284,7 +300,9 @@ router.post("/:buildingId/loop", requireLogin, async (req, res) => { /* ───────────────────────────────────────────────── POST /api/mine/:buildingId/collect - Ressourcen gutschreiben + nächsten Queue-Slot starten + Ressourcen gutschreiben. + session_started wird NICHT verändert – + die 5h laufen unabhängig vom Abholen weiter. ───────────────────────────────────────────────── */ router.post("/:buildingId/collect", requireLogin, async (req, res) => { const userId = req.session.user.id; @@ -307,20 +325,24 @@ router.post("/:buildingId/collect", requireLogin, async (req, res) => { const { user_building_id, cycle_seconds, last_collected, - selected_resource, loop_queue + session_started, 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); + const now = Date.now(); + const maxCycles = calcMaxCycles(cycle_seconds); + const sessionElapsed = Math.floor((now - new Date(session_started).getTime()) / 1000); + const collectElapsed = Math.floor((now - new Date(last_collected).getTime()) / 1000); + + const maxCyclesLeft = Math.floor(sessionElapsed / cycle_seconds); + const rawCycles = Math.floor(collectElapsed / cycle_seconds); + const cycles = Math.min(rawCycles, maxCycles, maxCyclesLeft); if (cycles < 1) { - const waitSeconds = cycle_seconds - elapsed; + const waitSeconds = cycle_seconds - (collectElapsed % cycle_seconds); return res.json({ error: "Noch nichts bereit", ready_in_seconds: waitSeconds, @@ -333,7 +355,7 @@ router.post("/:buildingId/collect", requireLogin, async (req, res) => { return res.status(400).json({ error: "Ressource nicht gefunden" }); } - // Ressource gutschreiben + /* Ressource gutschreiben */ const toAdd = selectedRow.amount * cycles; await db.query( `UPDATE account_currency @@ -342,30 +364,48 @@ router.post("/:buildingId/collect", requireLogin, async (req, res) => { [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) + /* last_collected vorsetzen – session_started bleibt unangetastet */ const newLastCollected = new Date( new Date(last_collected).getTime() + cycles * cycle_seconds * 1000 ); + /* Prüfen ob Session abgelaufen → Queue-Slot starten */ + const isFull = sessionElapsed >= SESSION_HOURS * 3600; + const queue = parseQueue(loop_queue); + let nextResource = selected_resource; // bleibt aktiv falls Session noch läuft + let newQueue = queue; + let newSessionStart = null; // wird nur gesetzt wenn Session wechselt + + if (isFull) { + /* Session ist vorbei → nächsten Queue-Slot holen */ + nextResource = queue.length > 0 ? queue.shift() : null; + newQueue = queue; + newSessionStart = new Date(); // neue Session startet jetzt + } + await db.query( `UPDATE building_collect_timer SET last_collected = ?, + session_started = COALESCE(?, session_started), selected_resource = ?, loop_queue = ? WHERE user_building_id = ?`, - [newLastCollected, nextResource, JSON.stringify(queue), user_building_id] + [ + newLastCollected, + newSessionStart, // NULL wenn Session noch läuft → COALESCE behält alten Wert + nextResource, + JSON.stringify(newQueue), + 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, + session_ended: isFull, + next_resource: isFull ? nextResource : null, + queue_remaining: newQueue.length, }); } catch (err) { console.error(err); diff --git a/views/launcher.ejs b/views/launcher.ejs index 8409552..ae20a61 100644 --- a/views/launcher.ejs +++ b/views/launcher.ejs @@ -8,7 +8,6 @@ href="/images/favicon/dok_favicon_32px.ico" type="image/x-icon" /> -