418 lines
17 KiB
JavaScript
418 lines
17 KiB
JavaScript
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;
|
||
}
|