dukt
This commit is contained in:
parent
1af0496722
commit
0fdfda8b0a
79
app.js
79
app.js
@ -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
|
||||||
======================== */
|
======================== */
|
||||||
|
|||||||
198
public/js/hud.js
198
public/js/hud.js
@ -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");
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user