earga
This commit is contained in:
parent
c61cddddb6
commit
efe45c3591
@ -53,7 +53,71 @@
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
/* ── Ressourcen-Liste ──────────────────────────── */
|
||||
/* ── Ressourcen-Auswahl ────────────────────────── */
|
||||
.mine-res-selector {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 6px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.mine-res-btn {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
background: linear-gradient(180deg, #2a1e0a 0%, #1a1206 100%);
|
||||
border: 1px solid rgba(122, 90, 26, 0.5);
|
||||
border-radius: 8px;
|
||||
padding: 10px 8px;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s, background 0.2s, transform 0.1s;
|
||||
color: #c8a860;
|
||||
}
|
||||
|
||||
.mine-res-btn:hover:not(:disabled):not(.mine-res-btn-active) {
|
||||
border-color: #c8952a;
|
||||
background: linear-gradient(180deg, #3a2a0e 0%, #2a1e08 100%);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.mine-res-btn-active {
|
||||
background: linear-gradient(180deg, #4a3010 0%, #3a2008 100%);
|
||||
border-color: #f0c84a;
|
||||
box-shadow: 0 0 8px rgba(240, 200, 74, 0.25);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.mine-res-btn-icon {
|
||||
font-size: 20px;
|
||||
line-height: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.mine-res-btn-icon img {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
object-fit: contain;
|
||||
filter: drop-shadow(0 1px 3px rgba(0,0,0,0.7));
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mine-res-btn-name {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
color: #f0c84a;
|
||||
}
|
||||
|
||||
.mine-res-btn-amount {
|
||||
font-size: 10px;
|
||||
color: #a07830;
|
||||
}
|
||||
|
||||
/* ── Aktuelle Produktion ───────────────────────── */
|
||||
.mine-resources {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -68,9 +132,7 @@
|
||||
border: 1px solid rgba(122, 90, 26, 0.3);
|
||||
border-radius: 6px;
|
||||
padding: 7px 12px;
|
||||
transition:
|
||||
border-color 0.2s,
|
||||
background 0.2s;
|
||||
transition: border-color 0.2s, background 0.2s;
|
||||
}
|
||||
|
||||
.mine-resource-row.mine-resource-ready {
|
||||
@ -87,31 +149,27 @@
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* Ressource Icons (gem, silver, wood, stone, gold, iron) – einheitliche Größe */
|
||||
.mine-resource-icon-gem,
|
||||
.mine-resource-icon-silver,
|
||||
.mine-resource-icon-wood,
|
||||
.mine-resource-icon-stone,
|
||||
/* Ressource Icons – einheitliche Größe */
|
||||
.mine-resource-icon-gold,
|
||||
.mine-resource-icon-iron {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
.mine-resource-icon-iron,
|
||||
.mine-resource-icon-wood,
|
||||
.mine-resource-icon-stone {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.mine-resource-icon-gem img,
|
||||
.mine-resource-icon-silver img,
|
||||
.mine-resource-icon-wood img,
|
||||
.mine-resource-icon-stone img,
|
||||
.mine-resource-icon-gold img,
|
||||
.mine-resource-icon-iron img {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
.mine-resource-icon-iron img,
|
||||
.mine-resource-icon-wood img,
|
||||
.mine-resource-icon-stone img {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
object-fit: contain;
|
||||
filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.7));
|
||||
filter: drop-shadow(0 1px 3px rgba(0,0,0,0.7));
|
||||
display: block;
|
||||
}
|
||||
|
||||
@ -120,7 +178,6 @@
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
color: #ffffff;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.mine-resource-amount {
|
||||
@ -129,7 +186,23 @@
|
||||
color: #f0c84a;
|
||||
min-width: 32px;
|
||||
text-align: right;
|
||||
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);
|
||||
text-shadow: 0 1px 3px rgba(0,0,0,0.6);
|
||||
}
|
||||
|
||||
/* ── Fortschrittsbalken ────────────────────────── */
|
||||
.mine-progress-wrap {
|
||||
height: 5px;
|
||||
background: rgba(255,255,255,0.07);
|
||||
border-radius: 3px;
|
||||
margin: 8px 0 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mine-progress-bar {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #c8952a, #f0c84a);
|
||||
border-radius: 3px;
|
||||
transition: width 0.6s ease;
|
||||
}
|
||||
|
||||
/* ── Timer-Zeile ───────────────────────────────── */
|
||||
@ -149,21 +222,117 @@
|
||||
font-size: 13px;
|
||||
font-family: monospace;
|
||||
color: #f0c84a;
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
background: rgba(0,0,0,0.35);
|
||||
border: 1px solid #7a5a1a;
|
||||
border-radius: 5px;
|
||||
padding: 3px 10px;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.mine-timer-full {
|
||||
color: #ff9040;
|
||||
border-color: #ff6020;
|
||||
animation: minePulse 1s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* ── Schleifen-Bereich ─────────────────────────── */
|
||||
.mine-loop-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.mine-loop-dots {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.mine-loop-dot {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 50%;
|
||||
background: rgba(255,255,255,0.08);
|
||||
border: 1px solid rgba(122, 90, 26, 0.5);
|
||||
transition: background 0.3s, border-color 0.3s;
|
||||
}
|
||||
|
||||
.mine-loop-dot-active {
|
||||
background: linear-gradient(135deg, #a030f0, #6010c0);
|
||||
border-color: #c060ff;
|
||||
box-shadow: 0 0 6px rgba(160, 48, 240, 0.6);
|
||||
}
|
||||
|
||||
.mine-loop-info {
|
||||
font-size: 12px;
|
||||
color: #a07830;
|
||||
}
|
||||
|
||||
.mine-loop-maxed {
|
||||
color: #f0c84a;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.mine-btn-loop {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
background: linear-gradient(180deg, #3a1060 0%, #200840 100%);
|
||||
color: #d090ff;
|
||||
border: 1px solid #8040c0;
|
||||
border-radius: 6px;
|
||||
padding: 8px 16px;
|
||||
font-size: 12px;
|
||||
font-family: sans-serif;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
letter-spacing: 0.03em;
|
||||
transition: filter 0.15s, transform 0.1s;
|
||||
box-shadow: 0 2px 6px rgba(0,0,0,0.4);
|
||||
}
|
||||
|
||||
.mine-btn-loop:hover:not(:disabled) {
|
||||
filter: brightness(1.25);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.mine-btn-loop:active:not(:disabled) {
|
||||
transform: translateY(0);
|
||||
filter: brightness(0.9);
|
||||
}
|
||||
|
||||
.mine-btn-loop.mine-btn-disabled,
|
||||
.mine-btn-loop:disabled {
|
||||
background: linear-gradient(180deg, #1a1020 0%, #100810 100%);
|
||||
border-color: #3a2050;
|
||||
color: #604080;
|
||||
cursor: not-allowed;
|
||||
box-shadow: none;
|
||||
transform: none;
|
||||
filter: none;
|
||||
}
|
||||
|
||||
.mine-loop-gem-icon {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.mine-loop-no-gems {
|
||||
font-size: 10px;
|
||||
color: #804060;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
/* ── Aktions-Bereich ───────────────────────────── */
|
||||
.mine-actions {
|
||||
margin-top: 14px;
|
||||
margin-top: 4px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* ── Abholen-Button (wie notification-btn) ─────── */
|
||||
/* ── Abholen-Button ────────────────────────────── */
|
||||
.mine-btn-collect {
|
||||
background: linear-gradient(180deg, #c8952a 0%, #7a5310 100%);
|
||||
color: #fff8e0;
|
||||
@ -175,12 +344,10 @@
|
||||
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);
|
||||
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);
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
.mine-btn-collect:hover:not(:disabled) {
|
||||
@ -215,13 +382,8 @@
|
||||
}
|
||||
|
||||
@keyframes minePulse {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.45;
|
||||
}
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.45; }
|
||||
}
|
||||
|
||||
.mine-error {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { showNotification } from "../notification.js";
|
||||
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;
|
||||
@ -8,6 +9,9 @@ export async function loadMine(buildingId) {
|
||||
await renderMineStatus(buildingId);
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────
|
||||
Haupt-Render-Funktion
|
||||
───────────────────────────────────────────────── */
|
||||
async function renderMineStatus(buildingId) {
|
||||
const actionsTab = document.getElementById("tab-actions");
|
||||
try {
|
||||
@ -16,78 +20,25 @@ async function renderMineStatus(buildingId) {
|
||||
const data = await res.json();
|
||||
|
||||
if (data.error) {
|
||||
actionsTab.innerHTML = "<p class='mine-error'> " + data.error + "</p>";
|
||||
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;
|
||||
|
||||
const resourceRows = data.available
|
||||
.map((r) => {
|
||||
const icon = resourceIcon(r.resource);
|
||||
const label = resourceLabel(r.resource);
|
||||
return (
|
||||
"<div class='mine-resource-row " +
|
||||
(data.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 + "x Zyklus abgeschlossen" : "Laeuft...") +
|
||||
"</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'>Naechster 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>" +
|
||||
renderHeader(data) +
|
||||
renderDivider() +
|
||||
renderResourceSelector(data, buildingId) +
|
||||
(data.selected_resource ? renderProductionSection(data) : "") +
|
||||
(data.selected_resource ? renderDivider() : "") +
|
||||
(data.selected_resource ? renderLoopSection(data, buildingId) : "") +
|
||||
(data.selected_resource ? renderDivider() : "") +
|
||||
(data.selected_resource ? renderCollectSection(data, buildingId) : "") +
|
||||
"</div>";
|
||||
|
||||
startCountdown(buildingId);
|
||||
if (data.selected_resource) {
|
||||
startCountdown(buildingId, data);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Mine Fehler:", err);
|
||||
actionsTab.innerHTML =
|
||||
@ -95,13 +46,240 @@ async function renderMineStatus(buildingId) {
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────
|
||||
Abschnitt: Header (Level + Zyklusinfo)
|
||||
───────────────────────────────────────────────── */
|
||||
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";
|
||||
} 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>"
|
||||
);
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────
|
||||
Abschnitt: Ressourcen-Auswahl
|
||||
───────────────────────────────────────────────── */
|
||||
function renderResourceSelector(data, buildingId) {
|
||||
const resources = data.production; // [{resource, amount}]
|
||||
|
||||
const buttons = resources.map(r => {
|
||||
const isSelected = data.selected_resource === r.resource;
|
||||
return (
|
||||
"<button class='mine-res-btn" +
|
||||
(isSelected ? " mine-res-btn-active" : "") +
|
||||
"' data-resource='" + r.resource +
|
||||
"' data-building='" + buildingId + "'>" +
|
||||
"<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 (
|
||||
"<p class='mine-section-title'>Ressource wählen</p>" +
|
||||
"<div class='mine-res-selector'>" +
|
||||
buttons +
|
||||
"</div>"
|
||||
);
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────
|
||||
Abschnitt: Aktuelle Produktion
|
||||
───────────────────────────────────────────────── */
|
||||
function renderProductionSection(data) {
|
||||
const minutesLeft = Math.floor(data.next_cycle_in_seconds / 60);
|
||||
const secondsLeft = data.next_cycle_in_seconds % 60;
|
||||
|
||||
const maxHoursDisplay = data.max_hours + "h";
|
||||
const progressPercent = Math.min(
|
||||
100,
|
||||
Math.round((data.cycles / data.max_cycles) * 100)
|
||||
);
|
||||
|
||||
return (
|
||||
renderDivider() +
|
||||
"<p class='mine-section-title'>Aktuelle Produktion</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>" +
|
||||
"<div class='mine-progress-wrap'>" +
|
||||
"<div class='mine-progress-bar' style='width:" + progressPercent + "%'></div>" +
|
||||
"</div>" +
|
||||
"<div class='mine-timer-row'>" +
|
||||
"<span class='mine-timer-label'>" +
|
||||
(data.is_full
|
||||
? "Maximale Zeit erreicht (" + maxHoursDisplay + ")"
|
||||
: "Nächster Zyklus in") +
|
||||
"</span>" +
|
||||
(data.is_full
|
||||
? "<span class='mine-timer mine-timer-full'>VOLL</span>"
|
||||
: "<span class='mine-timer' id='mine-countdown' data-seconds='" +
|
||||
data.next_cycle_in_seconds + "'>" +
|
||||
minutesLeft + "m " + secondsLeft + "s</span>") +
|
||||
"</div>"
|
||||
);
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────
|
||||
Abschnitt: Schleifen (Loops)
|
||||
───────────────────────────────────────────────── */
|
||||
function renderLoopSection(data, buildingId) {
|
||||
const loopDots = [];
|
||||
for (let i = 0; i < 4; i++) {
|
||||
loopDots.push(
|
||||
"<span class='mine-loop-dot" +
|
||||
(i < data.loops_purchased ? " mine-loop-dot-active" : "") +
|
||||
"'></span>"
|
||||
);
|
||||
}
|
||||
|
||||
const canBuy = data.loops_available > 0 && data.can_afford_loop;
|
||||
const maxReached = data.loops_available === 0;
|
||||
|
||||
return (
|
||||
"<p class='mine-section-title'>Schleifen</p>" +
|
||||
"<div class='mine-loop-row'>" +
|
||||
"<div class='mine-loop-dots'>" + loopDots.join("") + "</div>" +
|
||||
"<span class='mine-loop-info'>" +
|
||||
data.max_hours + "h max" +
|
||||
(maxReached ? " <span class='mine-loop-maxed'>(Max)</span>" : "") +
|
||||
"</span>" +
|
||||
"</div>" +
|
||||
(!maxReached
|
||||
? "<button class='mine-btn-loop" + (canBuy ? "" : " mine-btn-disabled") + "'" +
|
||||
" id='mine-loop-btn'" +
|
||||
" data-building='" + buildingId + "'" +
|
||||
(canBuy ? "" : " disabled") + ">" +
|
||||
"<span class='mine-loop-gem-icon'>💎</span>" +
|
||||
"10 Juwelen → +5h" +
|
||||
(data.can_afford_loop ? "" : " <span class='mine-loop-no-gems'>(" + data.player_gems + " verfügbar)</span>") +
|
||||
"</button>"
|
||||
: ""
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────
|
||||
Abschnitt: Abholen-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-Delegation: Ressource wählen
|
||||
───────────────────────────────────────────────── */
|
||||
document.addEventListener("click", async (e) => {
|
||||
const btn = e.target.closest(".mine-res-btn");
|
||||
if (!btn || btn.classList.contains("mine-res-btn-active")) return;
|
||||
|
||||
const resource = btn.dataset.resource;
|
||||
const buildingId = btn.dataset.building;
|
||||
|
||||
// Alle Buttons kurz deaktivieren
|
||||
document.querySelectorAll(".mine-res-btn").forEach(b => b.disabled = true);
|
||||
|
||||
try {
|
||||
const res = await fetch("/api/mine/" + buildingId + "/select", {
|
||||
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;
|
||||
}
|
||||
|
||||
showNotification(
|
||||
resourceLabel(resource) + " ausgewählt.\nTimer wurde zurückgesetzt.",
|
||||
"Mine", "⛏️"
|
||||
);
|
||||
await renderMineStatus(buildingId);
|
||||
} catch (err) {
|
||||
console.error("Ressource wählen Fehler:", err);
|
||||
await renderMineStatus(buildingId);
|
||||
}
|
||||
});
|
||||
|
||||
/* ─────────────────────────────────────────────────
|
||||
Event-Delegation: Schleife kaufen
|
||||
───────────────────────────────────────────────── */
|
||||
document.addEventListener("click", async (e) => {
|
||||
const btn = e.target.closest("#mine-loop-btn");
|
||||
if (!btn || btn.disabled) return;
|
||||
|
||||
const buildingId = btn.dataset.building;
|
||||
btn.disabled = true;
|
||||
btn.textContent = "Kaufe...";
|
||||
|
||||
try {
|
||||
const res = await fetch("/api/mine/" + buildingId + "/loop", {
|
||||
method: "POST",
|
||||
});
|
||||
if (!res.ok) throw new Error("API Fehler");
|
||||
const data = await res.json();
|
||||
|
||||
if (data.error) {
|
||||
showNotification(data.error, "Mine", "⛏️");
|
||||
await renderMineStatus(buildingId);
|
||||
return;
|
||||
}
|
||||
|
||||
showNotification(
|
||||
"Schleife aktiviert! +" + 5 + "h Abbauzeit.\n" +
|
||||
"Noch " + data.loops_available + " Schleife(n) verfügbar.",
|
||||
"Mine", "💎"
|
||||
);
|
||||
await renderMineStatus(buildingId);
|
||||
await refreshHud();
|
||||
} catch (err) {
|
||||
console.error("Schleife kaufen Fehler:", err);
|
||||
await renderMineStatus(buildingId);
|
||||
}
|
||||
});
|
||||
|
||||
/* ─────────────────────────────────────────────────
|
||||
Event-Delegation: Ressource 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...";
|
||||
btn.disabled = true;
|
||||
btn.textContent = "Wird abgeholt...";
|
||||
|
||||
try {
|
||||
const res = await fetch("/api/mine/" + buildingId + "/collect", {
|
||||
@ -113,50 +291,40 @@ document.addEventListener("click", async (e) => {
|
||||
if (data.error) {
|
||||
showNotification(
|
||||
data.ready_in_display
|
||||
? "Die Mine ist noch nicht bereit.\nBereit in: " +
|
||||
data.ready_in_display
|
||||
? "Noch nicht bereit.\nBereit in: " + data.ready_in_display
|
||||
: data.error,
|
||||
"Mine",
|
||||
"⛏️",
|
||||
"Mine", "⛏️"
|
||||
);
|
||||
await renderMineStatus(buildingId);
|
||||
return;
|
||||
}
|
||||
|
||||
const lines = data.collected
|
||||
.map(
|
||||
(c) =>
|
||||
resourceLabel(c.resource) +
|
||||
": +" +
|
||||
c.amount,
|
||||
)
|
||||
.join("\n");
|
||||
|
||||
showNotification("Erfolgreich abgeholt!\n\n" + lines, "Mine", "⛏️");
|
||||
const c = data.collected;
|
||||
showNotification(
|
||||
"Abgeholt!\n" + resourceLabel(c.resource) + ": +" + c.amount,
|
||||
"Mine", "⛏️"
|
||||
);
|
||||
await renderMineStatus(buildingId);
|
||||
await refreshHud();
|
||||
} catch (err) {
|
||||
console.error("Abholen Fehler:", err);
|
||||
showNotification(
|
||||
"Fehler beim Abholen. Bitte erneut versuchen.",
|
||||
"Fehler",
|
||||
"⚠️",
|
||||
);
|
||||
showNotification("Fehler beim Abholen. Bitte erneut versuchen.", "Fehler", "⚠️");
|
||||
await renderMineStatus(buildingId);
|
||||
}
|
||||
});
|
||||
|
||||
/* ─────────────────────────────────────────────────
|
||||
Countdown-Timer
|
||||
───────────────────────────────────────────────── */
|
||||
let countdownInterval = null;
|
||||
|
||||
function startCountdown(buildingId) {
|
||||
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;
|
||||
}
|
||||
if (!el) { clearInterval(countdownInterval); return; }
|
||||
|
||||
let secs = parseInt(el.dataset.seconds, 10) - 1;
|
||||
if (secs < 0) secs = 0;
|
||||
@ -170,50 +338,32 @@ function startCountdown(buildingId) {
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────
|
||||
Icons & Labels
|
||||
───────────────────────────────────────────────── */
|
||||
function resourceIcon(resource) {
|
||||
if (resource === "iron") {
|
||||
return (
|
||||
"<span class='mine-resource-icon-iron'>" +
|
||||
"<img src='/images/items/eisen.png' alt=''>" +
|
||||
"</span>"
|
||||
);
|
||||
}
|
||||
if (resource === "gold") {
|
||||
return (
|
||||
"<span class='mine-resource-icon-gold'>" +
|
||||
"<img src='/images/items/goldmuenze.png' alt=''>" +
|
||||
"</span>"
|
||||
);
|
||||
}
|
||||
if (resource === "wood") {
|
||||
return (
|
||||
"<span class='mine-resource-icon-wood'>" +
|
||||
"<img src='/images/items/holz.png' alt=''>" +
|
||||
"</span>"
|
||||
);
|
||||
}
|
||||
if (resource === "stone") {
|
||||
return (
|
||||
"<span class='mine-resource-icon-stone'>" +
|
||||
"<img src='/images/items/stein.png' alt=''>" +
|
||||
"</span>"
|
||||
);
|
||||
}
|
||||
const map = {
|
||||
copper: "🟤",
|
||||
silver: "⚪",
|
||||
const imgMap = {
|
||||
iron: "/images/items/eisen.png",
|
||||
gold: "/images/items/goldmuenze.png",
|
||||
wood: "/images/items/holz.png",
|
||||
stone: "/images/items/stein.png",
|
||||
};
|
||||
return map[resource] || "📦";
|
||||
if (imgMap[resource]) {
|
||||
return (
|
||||
"<span class='mine-resource-icon-" + resource + "'>" +
|
||||
"<img src='" + imgMap[resource] + "' alt=''>" +
|
||||
"</span>"
|
||||
);
|
||||
}
|
||||
return "📦";
|
||||
}
|
||||
|
||||
function resourceLabel(resource) {
|
||||
const map = {
|
||||
gold: "Gold",
|
||||
copper: "Kupfer",
|
||||
silver: "Silber",
|
||||
iron: "Eisen",
|
||||
gold: "Gold",
|
||||
iron: "Eisen",
|
||||
stone: "Stein",
|
||||
wood: "Holz",
|
||||
wood: "Holz",
|
||||
};
|
||||
return map[resource] || resource;
|
||||
}
|
||||
|
||||
@ -2,6 +2,14 @@ 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" });
|
||||
@ -9,104 +17,131 @@ function requireLogin(req, res, next) {
|
||||
next();
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────
|
||||
/* ─────────────────────────────────────────────────
|
||||
HELPER: Timer sicherstellen
|
||||
Legt beim allerersten Aufruf einen
|
||||
Eintrag an – laeuft dann fuer immer durch.
|
||||
Wird NUR durch collect() zurueckgesetzt.
|
||||
───────────────────────────────────────── */
|
||||
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],
|
||||
[userBuildingId]
|
||||
);
|
||||
if (!existing) {
|
||||
await db.query(
|
||||
"INSERT INTO building_collect_timer (user_building_id, last_collected) VALUES (?, NOW())",
|
||||
[userBuildingId],
|
||||
`INSERT INTO building_collect_timer
|
||||
(user_building_id, last_collected, selected_resource, loops_purchased)
|
||||
VALUES (?, NOW(), NULL, 0)`,
|
||||
[userBuildingId]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────
|
||||
/* ─────────────────────────────────────────────────
|
||||
HELPER: Produktionsdaten laden
|
||||
Gibt immer last_collected aus der DB
|
||||
───────────────────────────────────────── */
|
||||
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
|
||||
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 = ?
|
||||
`,
|
||||
[userId, buildingId],
|
||||
`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 userId = req.session.user.id;
|
||||
const buildingId = req.params.buildingId;
|
||||
|
||||
try {
|
||||
// user_building holen
|
||||
const [[userBuilding]] = await db.query(
|
||||
"SELECT id, level FROM user_buildings WHERE user_id = ? AND building_id = ?",
|
||||
[userId, buildingId],
|
||||
[userId, buildingId]
|
||||
);
|
||||
|
||||
if (!userBuilding) {
|
||||
return res.status(404).json({ error: "Gebaeude nicht gefunden" });
|
||||
return res.status(404).json({ error: "Gebäude nicht gefunden" });
|
||||
}
|
||||
|
||||
// Timer einmalig starten falls noch nie geoeffnet
|
||||
await ensureTimer(userBuilding.id);
|
||||
|
||||
const rows = await loadMineData(userId, buildingId);
|
||||
if (!rows.length) {
|
||||
return res.status(404).json({
|
||||
error: `Keine Produktionsdaten fuer Level ${userBuilding.level} gefunden. Bitte building_production Tabelle pruefen.`,
|
||||
});
|
||||
return res.status(404).json({ error: "Keine Produktionsdaten gefunden" });
|
||||
}
|
||||
|
||||
const { cycle_seconds, last_collected, level } = rows[0];
|
||||
const {
|
||||
cycle_seconds, last_collected,
|
||||
selected_resource, loops_purchased, level
|
||||
} = rows[0];
|
||||
|
||||
const elapsed = Math.floor(
|
||||
(Date.now() - new Date(last_collected).getTime()) / 1000,
|
||||
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 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,
|
||||
}));
|
||||
|
||||
const production = rows.map((r) => ({
|
||||
resource: r.resource,
|
||||
amount: r.amount,
|
||||
}));
|
||||
const gems = currency?.gems ?? 0;
|
||||
|
||||
res.json({
|
||||
level,
|
||||
cycles,
|
||||
ready: cycles > 0,
|
||||
available,
|
||||
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,
|
||||
@ -118,90 +153,201 @@ router.get("/:buildingId/status", requireLogin, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
/* ─────────────────────────────────────────
|
||||
/* ─────────────────────────────────────────────────
|
||||
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 reset
|
||||
───────────────────────────────────────── */
|
||||
Ressourcen gutschreiben + Timer vorsetzen
|
||||
───────────────────────────────────────────────── */
|
||||
router.post("/:buildingId/collect", requireLogin, async (req, res) => {
|
||||
const userId = req.session.user.id;
|
||||
const userId = req.session.user.id;
|
||||
const buildingId = req.params.buildingId;
|
||||
|
||||
try {
|
||||
// user_building holen
|
||||
const [[userBuilding]] = await db.query(
|
||||
"SELECT id FROM user_buildings WHERE user_id = ? AND building_id = ?",
|
||||
[userId, buildingId],
|
||||
[userId, buildingId]
|
||||
);
|
||||
|
||||
if (!userBuilding) {
|
||||
return res.status(404).json({ error: "Gebaeude nicht gefunden" });
|
||||
return res.status(404).json({ error: "Gebäude nicht gefunden" });
|
||||
}
|
||||
|
||||
// Timer sicherstellen (Fallback falls Status nie aufgerufen wurde)
|
||||
await ensureTimer(userBuilding.id);
|
||||
|
||||
const rows = await loadMineData(userId, buildingId);
|
||||
if (!rows.length) {
|
||||
return res.status(404).json({ error: "Gebaeude nicht gefunden" });
|
||||
return res.status(404).json({ error: "Gebäude nicht gefunden" });
|
||||
}
|
||||
|
||||
const { user_building_id, cycle_seconds, last_collected } = rows[0];
|
||||
const {
|
||||
user_building_id, cycle_seconds, last_collected,
|
||||
selected_resource, loops_purchased
|
||||
} = rows[0];
|
||||
|
||||
const elapsed = Math.floor(
|
||||
(Date.now() - new Date(last_collected).getTime()) / 1000,
|
||||
);
|
||||
const cycles = Math.floor(elapsed / cycle_seconds);
|
||||
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;
|
||||
const minutes = Math.floor(waitSeconds / 60);
|
||||
const seconds = waitSeconds % 60;
|
||||
return res.json({
|
||||
error: "Noch nichts bereit",
|
||||
error: "Noch nichts bereit",
|
||||
ready_in_seconds: waitSeconds,
|
||||
ready_in_display: `${minutes}m ${seconds}s`,
|
||||
ready_in_display: `${Math.floor(waitSeconds / 60)}m ${waitSeconds % 60}s`,
|
||||
});
|
||||
}
|
||||
|
||||
// Jede Ressource einzeln gutschreiben
|
||||
const allowedResources = [
|
||||
"gold",
|
||||
"silver",
|
||||
"copper",
|
||||
"iron",
|
||||
"wood",
|
||||
"stone",
|
||||
"gems",
|
||||
];
|
||||
|
||||
for (const row of rows) {
|
||||
if (!allowedResources.includes(row.resource)) continue;
|
||||
|
||||
const toAdd = row.amount * cycles;
|
||||
|
||||
await db.query(
|
||||
`UPDATE account_currency SET \`${row.resource}\` = \`${row.resource}\` + ? WHERE account_id = ?`,
|
||||
[toAdd, userId],
|
||||
);
|
||||
const selectedRow = rows.find(r => r.resource === selected_resource);
|
||||
if (!selectedRow) {
|
||||
return res.status(400).json({ error: "Ressource nicht gefunden" });
|
||||
}
|
||||
|
||||
// Timer zuruecksetzen: last_collected um genau die abgeschlossenen
|
||||
// Zyklen vorruecken – Restsekunden bleiben erhalten, kein Verlust
|
||||
const newLastCollected = new Date(
|
||||
new Date(last_collected).getTime() + cycles * cycle_seconds * 1000,
|
||||
);
|
||||
const toAdd = selectedRow.amount * cycles;
|
||||
|
||||
await db.query(
|
||||
"UPDATE building_collect_timer SET last_collected = ? WHERE user_building_id = ?",
|
||||
[newLastCollected, user_building_id],
|
||||
`UPDATE account_currency
|
||||
SET \`${selected_resource}\` = \`${selected_resource}\` + ?
|
||||
WHERE account_id = ?`,
|
||||
[toAdd, userId]
|
||||
);
|
||||
|
||||
const collected = rows.map((r) => ({
|
||||
resource: r.resource,
|
||||
amount: r.amount * cycles,
|
||||
}));
|
||||
// Timer exakt vorsetzen – Restsekunden bleiben erhalten
|
||||
const newLastCollected = new Date(
|
||||
new Date(last_collected).getTime() + cycles * cycle_seconds * 1000
|
||||
);
|
||||
|
||||
res.json({ success: true, cycles, collected });
|
||||
// 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" });
|
||||
|
||||
Loading…
Reference in New Issue
Block a user