dok/public/js/buildings/mine.js
2026-04-08 16:48:02 +01:00

418 lines
17 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.

import { showNotification } from "../notification.js";
import { refreshHud } from "../hud.js";
/* ── Einstiegspunkt ─────────────────────────────── */
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);
}
/* ─────────────────────────────────────────────────
Haupt-Render-Funktion
───────────────────────────────────────────────── */
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;
}
actionsTab.innerHTML =
"<div class='mine-panel'>" +
renderHeader(data) +
renderDivider() +
(!data.selected_resource
? renderResourceSelector(data, buildingId)
: renderActiveSession(data, buildingId)
) +
"</div>";
if (data.selected_resource) {
startSessionCountdown(buildingId, data);
}
} catch (err) {
console.error("Mine Fehler:", err);
actionsTab.innerHTML =
"<p class='mine-error'>Fehler beim Laden der Mineninfo.</p>";
}
}
/* ─────────────────────────────────────────────────
Header
───────────────────────────────────────────────── */
function renderHeader(data) {
let cycleText = "Keine Ressource gewählt";
if (data.selected_resource) {
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 (
"<div class='mine-header-row'>" +
"<span class='mine-level-badge'>Level " + data.level + "</span>" +
"<span class='mine-cycles'>" + cycleText + "</span>" +
"</div>"
);
}
/* ─────────────────────────────────────────────────
Startscreen (keine aktive Session)
───────────────────────────────────────────────── */
function renderResourceSelector(data, buildingId) {
return (
"<p class='mine-section-title'>Ressource wählen</p>" +
"<p class='mine-hint'>Wähle eine Ressource für die nächsten " + data.session_hours + "h.</p>" +
renderResourceGrid(data.production, null, buildingId, "select")
);
}
/* ─────────────────────────────────────────────────
Aktive Session
───────────────────────────────────────────────── */
function renderActiveSession(data, buildingId) {
return (
renderCurrentProduction(data) +
renderDivider() +
renderQueueSection(data, buildingId) +
renderDivider() +
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 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 (
"<p class='mine-section-title'>Aktuelle Session " + data.session_hours + "h</p>" +
"<div class='mine-resource-row" + (data.ready ? " mine-resource-ready" : "") + "'>" +
"<span class='mine-resource-icon'>" + resourceIcon(data.selected_resource) + "</span>" +
"<span class='mine-resource-label'>" + resourceLabel(data.selected_resource) + "</span>" +
"<span class='mine-resource-amount'>" + data.available_amount + "</span>" +
"</div>" +
/* Fortschrittsbalken */
"<div class='mine-progress-wrap'>" +
"<div class='mine-progress-bar' style='width:" + progressPct + "%'></div>" +
"</div>" +
/* Session-Timer (läuft immer durch) */
"<div class='mine-timer-row'>" +
"<span class='mine-timer-label'>Session endet in</span>" +
(data.is_full
? "<span class='mine-timer mine-timer-full'>FERTIG</span>"
: "<span class='mine-timer' id='mine-session-countdown'" +
" data-seconds='" + data.session_seconds_left + "'>" +
sessionMin + "m " + sessionSec + "s</span>"
) +
"</div>" +
/* Zyklus-Timer (nur wenn Session noch läuft und noch Zyklen kommen) */
(!data.is_full
? "<div class='mine-timer-row mine-timer-row-sub'>" +
"<span class='mine-timer-label'>Nächster Zyklus in</span>" +
"<span class='mine-timer mine-timer-sub' id='mine-cycle-countdown'" +
" data-seconds='" + data.next_cycle_in_seconds + "'>" +
cycleMin + "m " + cycleSec + "s</span>" +
"</div>"
: ""
)
);
}
/* ─────────────────────────────────────────────────
Warteschlange
───────────────────────────────────────────────── */
function renderQueueSection(data, buildingId) {
const { loop_queue, loop_slots_free, loop_slots_used,
loop_cost_gems, can_afford_loop, player_gems } = data;
let html =
"<p class='mine-section-title'>Warteschlange" +
" <span class='mine-queue-count'>(" + loop_slots_used + "/4)</span></p>";
loop_queue.forEach((res, i) => {
html +=
"<div class='mine-queue-slot mine-queue-slot-filled'>" +
"<span class='mine-queue-pos'>" + (i + 1) + ".</span>" +
"<span class='mine-queue-icon'>" + resourceIcon(res) + "</span>" +
"<span class='mine-queue-label'>" + resourceLabel(res) + "</span>" +
"<span class='mine-queue-dur'>" + data.session_hours + "h</span>" +
"</div>";
});
if (loop_slots_free > 0) {
const nextPos = loop_slots_used + 1;
html +=
"<div class='mine-queue-add'>" +
"<button class='mine-btn-add-loop" + (can_afford_loop ? "" : " mine-btn-disabled") + "'" +
" id='mine-queue-toggle'" +
" data-building='" + buildingId + "'" +
(can_afford_loop ? "" : " disabled") + ">" +
"<span class='mine-loop-gem-icon'>💎</span>" +
(can_afford_loop
? "Schleife " + nextPos + " hinzufügen (" + loop_cost_gems + " Juwelen)"
: "Schleife " + nextPos + " (" + loop_cost_gems + " 💎 nur " + player_gems + " verfügbar)"
) +
"</button>" +
"<div class='mine-loop-picker' id='mine-loop-picker' style='display:none'>" +
"<p class='mine-hint'>Welche Ressource soll in Slot " + nextPos + " abgebaut werden?</p>" +
renderResourceGrid(data.production, null, buildingId, "loop") +
"</div>" +
"</div>";
}
if (loop_slots_free === 0) {
html += "<p class='mine-hint mine-hint-center'>Warteschlange voll 4 Schleifen aktiv.</p>";
}
return html;
}
/* ─────────────────────────────────────────────────
Ressourcen-Raster
───────────────────────────────────────────────── */
function renderResourceGrid(production, activeResource, buildingId, mode) {
const buttons = production.map(r => {
const isActive = r.resource === activeResource;
return (
"<button class='mine-res-btn" + (isActive ? " mine-res-btn-active" : "") + "'" +
" data-resource='" + r.resource + "'" +
" data-building='" + buildingId + "'" +
" data-mode='" + mode + "'" +
(isActive ? " disabled" : "") + ">" +
"<span class='mine-res-btn-icon'>" + resourceIcon(r.resource) + "</span>" +
"<span class='mine-res-btn-name'>" + resourceLabel(r.resource) + "</span>" +
"<span class='mine-res-btn-amount'>+" + r.amount + "/Zyklus</span>" +
"</button>"
);
}).join("");
return "<div class='mine-res-selector'>" + buttons + "</div>";
}
/* ─────────────────────────────────────────────────
Collect-Button
───────────────────────────────────────────────── */
function renderCollectSection(data, buildingId) {
return (
"<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>"
);
}
function renderDivider() {
return "<div class='mine-divider'></div>";
}
/* ─────────────────────────────────────────────────
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 / Schleife buchen
───────────────────────────────────────────────── */
document.addEventListener("click", async (e) => {
const btn = e.target.closest(".mine-res-btn");
if (!btn || btn.disabled || btn.classList.contains("mine-res-btn-active")) return;
const resource = btn.dataset.resource;
const buildingId = btn.dataset.building;
const mode = btn.dataset.mode;
document.querySelectorAll(".mine-res-btn").forEach(b => (b.disabled = true));
const endpoint = mode === "loop"
? "/api/mine/" + buildingId + "/loop"
: "/api/mine/" + buildingId + "/select";
try {
const res = await fetch(endpoint, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ resource }),
});
if (!res.ok) throw new Error("API Fehler");
const data = await res.json();
if (data.error) {
showNotification(data.error, "Mine", "⛏️");
await renderMineStatus(buildingId);
return;
}
if (mode === "loop") {
showNotification(
"Schleife hinzugefügt!\n" + resourceLabel(resource) + " 5h\n" +
"Noch " + data.loop_slots_free + " Slot(s) frei.",
"Mine", "💎"
);
} else {
showNotification(
resourceLabel(resource) + " ausgewählt.\n5h Session startet jetzt.",
"Mine", "⛏️"
);
}
await renderMineStatus(buildingId);
if (mode === "loop") await refreshHud();
} catch (err) {
console.error("Mine Fehler:", err);
await renderMineStatus(buildingId);
}
});
/* ─────────────────────────────────────────────────
Event: Abholen
───────────────────────────────────────────────── */
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
? "Noch nicht bereit.\nBereit in: " + data.ready_in_display
: data.error,
"Mine", "⛏️"
);
await renderMineStatus(buildingId);
return;
}
const c = data.collected;
let msg = "Abgeholt!\n" + resourceLabel(c.resource) + ": +" + c.amount;
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);
await refreshHud();
} catch (err) {
console.error("Abholen Fehler:", err);
showNotification("Fehler beim Abholen. Bitte erneut versuchen.", "Fehler", "⚠️");
await renderMineStatus(buildingId);
}
});
/* ─────────────────────────────────────────────────
Zwei parallele Countdowns:
1. Session-Countdown (mine-session-countdown)
2. Zyklus-Countdown (mine-cycle-countdown)
───────────────────────────────────────────────── */
let sessionInterval = null;
let cycleInterval = null;
function startSessionCountdown(buildingId, data) {
if (sessionInterval) clearInterval(sessionInterval);
if (cycleInterval) clearInterval(cycleInterval);
if (data.is_full) 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;
el.dataset.seconds = secs;
el.textContent = Math.floor(secs / 60) + "m " + (secs % 60) + "s";
if (secs === 0) {
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);
}
/* ─────────────────────────────────────────────────
Icons & Labels
───────────────────────────────────────────────── */
function resourceIcon(resource) {
const imgMap = {
iron: "/images/items/eisen.png",
gold: "/images/items/goldmuenze.png",
wood: "/images/items/holz.png",
stone: "/images/items/stein.png",
};
if (imgMap[resource]) {
return (
"<span class='mine-resource-icon-" + resource + "'>" +
"<img src='" + imgMap[resource] + "' alt=''>" +
"</span>"
);
}
return "📦";
}
function resourceLabel(resource) {
const map = { gold: "Gold", iron: "Eisen", stone: "Stein", wood: "Holz" };
return map[resource] || resource;
}