This commit is contained in:
cay 2026-04-14 09:56:18 +01:00
parent 1af0496722
commit 0fdfda8b0a
2 changed files with 275 additions and 2 deletions

79
app.js
View File

@ -443,15 +443,90 @@ app.get("/api/energy", requireLogin, async (req, res) => {
const ENERGY_MAX = 40; const ENERGY_MAX = 40;
try { try {
const [[row]] = await db.query( const [[row]] = await db.query(
"SELECT energy FROM account_currency WHERE account_id = ?", "SELECT energy, energy_bought FROM account_currency WHERE account_id = ?",
[userId], [userId],
); );
res.json({ energy: row?.energy ?? ENERGY_MAX, energy_max: ENERGY_MAX }); const today = new Date().toISOString().slice(0, 10);
const boughtDate = row?.energy_bought
? new Date(row.energy_bought).toISOString().slice(0, 10)
: null;
res.json({
energy: row?.energy ?? ENERGY_MAX,
energy_max: ENERGY_MAX,
bought_today: boughtDate === today,
});
} catch (err) { } catch (err) {
res.status(500).json({ error: "DB Fehler" }); res.status(500).json({ error: "DB Fehler" });
} }
}); });
/* ========================
Energie kaufen
======================== */
app.post("/api/energy/buy", requireLogin, async (req, res) => {
const userId = req.session.user.id;
const ENERGY_MAX = 40;
const ENERGY_BUY = 10; // Energie die dazugekauft wird
const COST_GEMS = 10;
const COST_GOLD = 200;
const { currency: payWith } = req.body; // "gems" oder "gold"
if (!["gems", "gold"].includes(payWith)) {
return res.status(400).json({ error: "Ungültige Zahlungsmethode." });
}
try {
const today = new Date().toISOString().slice(0, 10);
const [[row]] = await db.query(
"SELECT energy, gems, gold, energy_bought FROM account_currency WHERE account_id = ?",
[userId],
);
if (!row) return res.status(404).json({ error: "Account nicht gefunden." });
/* Bereits heute gekauft? */
const boughtDate = row.energy_bought
? new Date(row.energy_bought).toISOString().slice(0, 10)
: null;
if (boughtDate === today) {
return res.status(400).json({ error: "Du hast heute bereits Energie aufgefüllt." });
}
/* Genug Währung? */
if (payWith === "gems" && row.gems < COST_GEMS) {
return res.status(400).json({ error: `Nicht genug Gems. Benötigt: ${COST_GEMS}.` });
}
if (payWith === "gold" && row.gold < COST_GOLD) {
return res.status(400).json({ error: `Nicht genug Gold. Benötigt: ${COST_GOLD}.` });
}
/* Energie darf max auf ENERGY_MAX steigen */
const newEnergy = Math.min((row.energy ?? ENERGY_MAX) + ENERGY_BUY, ENERGY_MAX);
if (payWith === "gems") {
await db.query(
`UPDATE account_currency
SET energy = ?, gems = gems - ?, energy_bought = ?
WHERE account_id = ?`,
[newEnergy, COST_GEMS, today, userId],
);
} else {
await db.query(
`UPDATE account_currency
SET energy = ?, gold = gold - ?, energy_bought = ?
WHERE account_id = ?`,
[newEnergy, COST_GOLD, today, userId],
);
}
res.json({ success: true, energy: newEnergy, energy_max: ENERGY_MAX });
} catch (err) {
console.error("[Energy Buy]", err);
res.status(500).json({ error: "DB Fehler" });
}
});
/* ======================== /* ========================
404 Handler 404 Handler
======================== */ ======================== */

View File

@ -67,3 +67,201 @@ function formatNumber(n) {
if (n === undefined || n === null) return "0"; if (n === undefined || n === null) return "0";
return Number(n).toLocaleString("de-DE"); return Number(n).toLocaleString("de-DE");
} }
/*
Energie kaufen Popup beim + Klick
*/
document.getElementById("hud-energy-plus")?.addEventListener("click", openEnergyBuyPopup);
async function openEnergyBuyPopup() {
/* Aktuellen Stand laden */
let hud, energyStatus;
try {
[hud, energyStatus] = await Promise.all([
fetch("/api/hud").then(r => r.json()),
fetch("/api/energy").then(r => r.json()),
]);
} catch {
return;
}
/* Altes Popup entfernen */
document.getElementById("energy-buy-overlay")?.remove();
const alreadyBought = energyStatus.bought_today;
const energyFull = (energyStatus.energy ?? 0) >= (energyStatus.energy_max ?? 40);
const canGems = hud.gems >= 10;
const canGold = hud.gold >= 200;
const overlay = document.createElement("div");
overlay.id = "energy-buy-overlay";
overlay.style.cssText = `
position:fixed; inset:0; z-index:99999;
background:rgba(0,0,0,.75);
display:flex; align-items:center; justify-content:center;`;
/* ── Inhalt je nach Status ── */
let body = "";
if (alreadyBought) {
body = `
<div style="font-size:36px;margin-bottom:12px;"></div>
<div style="font-family:'Cinzel',serif;font-size:16px;color:#f0d060;
letter-spacing:2px;margin-bottom:10px;">BEREITS AUFGEFÜLLT</div>
<p style="font-family:'Cinzel',serif;font-size:12px;color:#a08060;
line-height:1.8;margin:0 0 20px;">
Du hast heute bereits Energie dazugekauft.<br>
<strong style="color:#f0d060;">Morgen wieder verfügbar.</strong>
</p>`;
} else if (energyFull) {
body = `
<div style="font-size:36px;margin-bottom:12px;"></div>
<div style="font-family:'Cinzel',serif;font-size:16px;color:#7de87d;
letter-spacing:2px;margin-bottom:10px;">ENERGIE VOLL</div>
<p style="font-family:'Cinzel',serif;font-size:12px;color:#a08060;
line-height:1.8;margin:0 0 20px;">
Deine Energie ist bereits auf dem Maximum.
</p>`;
} else {
const gemBtn = canGems
? `<button class="ebtn" data-pay="gems"
style="background:linear-gradient(#1a2a4a,#0a1a3a);border:2px solid #4a7acc;
border-radius:9px;color:#a0c8ff;font-family:'Cinzel',serif;
font-size:12px;padding:12px 20px;cursor:pointer;flex:1;transition:.15s;">
<div style="font-size:22px;margin-bottom:5px;">💠</div>
<div style="font-weight:700;font-size:14px;margin-bottom:3px;">10 Gems</div>
<div style="font-size:10px;color:#6090c0;">Verfügbar: ${formatNum(hud.gems)}</div>
</button>`
: `<button disabled style="background:rgba(20,20,30,.6);border:2px solid rgba(80,100,140,.3);
border-radius:9px;color:rgba(100,130,180,.4);font-family:'Cinzel',serif;
font-size:12px;padding:12px 20px;flex:1;cursor:not-allowed;">
<div style="font-size:22px;margin-bottom:5px;">💠</div>
<div style="font-weight:700;font-size:14px;margin-bottom:3px;">10 Gems</div>
<div style="font-size:10px;">Zu wenig Gems</div>
</button>`;
const goldBtn = canGold
? `<button class="ebtn" data-pay="gold"
style="background:linear-gradient(#2a1a04,#1a0f02);border:2px solid #c8960c;
border-radius:9px;color:#f0d060;font-family:'Cinzel',serif;
font-size:12px;padding:12px 20px;cursor:pointer;flex:1;transition:.15s;">
<div style="font-size:22px;margin-bottom:5px;">🪙</div>
<div style="font-weight:700;font-size:14px;margin-bottom:3px;">200 Gold</div>
<div style="font-size:10px;color:#a08040;">Verfügbar: ${formatNum(hud.gold)}</div>
</button>`
: `<button disabled style="background:rgba(20,15,5,.6);border:2px solid rgba(140,100,20,.3);
border-radius:9px;color:rgba(180,140,40,.4);font-family:'Cinzel',serif;
font-size:12px;padding:12px 20px;flex:1;cursor:not-allowed;">
<div style="font-size:22px;margin-bottom:5px;">🪙</div>
<div style="font-weight:700;font-size:14px;margin-bottom:3px;">200 Gold</div>
<div style="font-size:10px;">Zu wenig Gold</div>
</button>`;
body = `
<div style="font-size:36px;margin-bottom:10px;"></div>
<div style="font-family:'Cinzel',serif;font-size:16px;color:#f0d060;
letter-spacing:2px;margin-bottom:6px;">ENERGIE AUFFÜLLEN</div>
<p style="font-family:'Cinzel',serif;font-size:11px;color:#a08060;
margin:0 0 18px;line-height:1.7;">
+10 Energie · max. 1× täglich
</p>
<div style="display:flex;gap:12px;width:100%;">${gemBtn}${goldBtn}</div>
${!canGems && !canGold
? `<p style="font-family:'Cinzel',serif;font-size:11px;color:#e74c3c;
margin:12px 0 0;">Weder genug Gems noch Gold vorhanden.</p>`
: ""}`;
}
overlay.innerHTML = `
<div style="
background:linear-gradient(135deg,#1a0f04,#0a0803);
border:2px solid #c8960c; border-radius:14px;
padding:28px 32px; max-width:360px; width:90%;
text-align:center;
box-shadow:0 20px 60px rgba(0,0,0,.9);
font-family:'Cinzel',serif;">
${body}
<button id="ebtn-close"
style="margin-top:16px;background:none;border:1px solid rgba(200,150,60,.35);
border-radius:7px;color:#806040;font-family:'Cinzel',serif;
font-size:11px;padding:7px 22px;cursor:pointer;transition:.15s;">
Schließen
</button>
</div>`;
document.body.appendChild(overlay);
/* Schließen */
document.getElementById("ebtn-close").addEventListener("click", () => overlay.remove());
overlay.addEventListener("click", e => { if (e.target === overlay) overlay.remove(); });
/* Kauf-Buttons */
overlay.querySelectorAll(".ebtn").forEach(btn => {
btn.addEventListener("mouseenter", () => { btn.style.transform = "translateY(-2px)"; btn.style.opacity = ".9"; });
btn.addEventListener("mouseleave", () => { btn.style.transform = ""; btn.style.opacity = ""; });
btn.addEventListener("click", () => buyEnergy(btn.dataset.pay, overlay));
});
}
async function buyEnergy(payWith, overlay) {
/* Buttons deaktivieren während Kauf */
overlay.querySelectorAll(".ebtn").forEach(b => { b.disabled = true; b.style.opacity = ".5"; });
try {
const res = await fetch("/api/energy/buy", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ currency: payWith }),
});
const data = await res.json();
if (!res.ok) {
overlay.querySelector("div > div:last-of-type").insertAdjacentHTML(
"beforebegin",
`<p style="font-family:'Cinzel',serif;font-size:11px;color:#e74c3c;
margin:8px 0 0;">${data.error || "Fehler beim Kauf."}</p>`
);
overlay.querySelectorAll(".ebtn").forEach(b => { b.disabled = false; b.style.opacity = ""; });
return;
}
/* Erfolg: HUD aktualisieren + Popup mit Bestätigung */
await refreshHud();
overlay.remove();
const confirm = document.createElement("div");
confirm.style.cssText = `
position:fixed; inset:0; z-index:99999;
background:rgba(0,0,0,.75);
display:flex; align-items:center; justify-content:center;`;
confirm.innerHTML = `
<div style="background:linear-gradient(135deg,#0a1a08,#041004);
border:2px solid #4a8a3c; border-radius:14px;
padding:28px 36px; text-align:center; max-width:300px;
box-shadow:0 20px 60px rgba(0,0,0,.9); font-family:'Cinzel',serif;">
<div style="font-size:44px;margin-bottom:10px;"></div>
<div style="font-size:16px;color:#7de87d;letter-spacing:2px;margin-bottom:8px;">
+10 ENERGIE
</div>
<p style="font-size:12px;color:#a0c890;margin:0 0 18px;">
Energie aufgefüllt auf ${data.energy} / ${data.energy_max}
</p>
<button onclick="this.closest('div[style]').remove()"
style="background:linear-gradient(#1a4a18,#0f2a0e);border:2px solid #4a8a3c;
border-radius:7px;color:#a0e090;font-family:'Cinzel',serif;
font-size:12px;padding:8px 24px;cursor:pointer;">
OK
</button>
</div>`;
document.body.appendChild(confirm);
setTimeout(() => confirm.remove(), 3000);
} catch {
overlay.querySelectorAll(".ebtn").forEach(b => { b.disabled = false; b.style.opacity = ""; });
}
}
function formatNum(n) {
return Number(n || 0).toLocaleString("de-DE");
}