/* ================================ Kristall-Mapping (aus carddeck.js) ================================ */ const RARITY_CRYSTALS = { 1: "roter-cristal.png", 2: "blauer-cristal.png", 3: "gelber-cristal.png", 4: "gruener-cristal.png", 5: "oranger-cristal.png", 6: "violet-cristal.png", 7: "pinker-cristal.png", }; function rarityImgs(rarity, size = 13) { const file = RARITY_CRYSTALS[String(rarity)]; if (!file) return ""; const count = parseInt(rarity) || 0; const img = `Stufe ${rarity}`; return img.repeat(count); } function cardHTML(card, isFront = true) { if (!isFront) return `?`; // image zuerst, dann icon als Fallback const imgFile = card?.image || card?.icon || null; const img = imgFile ? `/images/cards/${imgFile}` : "/images/items/rueckseite.png"; return ` ${card?.name || ''} ${card?.attack != null ? `${card.attack}` : ""} ${card?.defends != null ? `${card.defends}` : ""} ${card?.cooldown!= null ? `${card.cooldown}` : ""} ${card?.rarity ? `
${rarityImgs(card.rarity, 11)}
` : ""}
${card?.name || ''}
`; } /* ================================ Haupt-Export ================================ */ export async function loadEvents() { const body = document.getElementById("qm-body-events"); if (!body) return; if (!document.querySelector('link[href="/css/events.css"]')) { const link = document.createElement("link"); link.rel = "stylesheet"; link.href = "/css/events.css"; document.head.appendChild(link); } const events = [ { id: 1, img: "/images/items/runenhaufen.png", label: "Booster Öffnen", type: "booster" }, { id: 2, img: "/images/items/runenhaufen.png", label: "Textzeile 2" }, { id: 3, img: "/images/items/runenhaufen.png", label: "Textzeile 3" }, { id: 4, img: "/images/items/holz.png", label: "Holz Spenden", type: "wood", woodCost: 100 }, { id: 5, img: "/images/items/runenhaufen.png", label: "Textzeile 5" }, ]; // Täglichen Status + Holz-Bestand parallel laden let completedToday = []; let playerWood = 0; try { const [statusRes, hudRes] = await Promise.all([ fetch("/api/daily/status"), fetch("/api/hud"), ]); if (statusRes.ok) completedToday = (await statusRes.json()).completed || []; if (hudRes.ok) playerWood = (await hudRes.json()).wood || 0; } catch (e) { console.error("Laden fehlgeschlagen", e); } body.innerHTML = `
${events.map(ev => { const done = completedToday.includes(ev.id); const locked = !done && ev.woodCost && playerWood < ev.woodCost; const cls = done ? ' event-done' : locked ? ' event-locked' : ''; return `
${ev.label} ${done ? `
` : ''} ${locked ? `
🪵 ${ev.woodCost} Holz
benötigt
` : ''}
${ev.label}
`; }).join("")}
Inhalt folgt...
`; const overlay = body.querySelector("#event-detail-overlay"); const edpImg = body.querySelector("#edp-img"); const edpTitle = body.querySelector("#edp-title"); const edpBody = body.querySelector("#edp-body"); const boosterUi = body.querySelector("#booster-ui"); const woodUi = body.querySelector("#wood-ui"); const eventsGrid = body.querySelector("#events-grid"); /* ── Event-Karten ── */ body.querySelectorAll(".event-card").forEach(card => { card.addEventListener("click", () => { if (card.dataset.done === "true") return; if (card.dataset.locked === "true") return; if (card.dataset.type === "booster") { eventsGrid.style.display = "none"; boosterUi.style.display = "flex"; resetBooster(); return; } if (card.dataset.type === "wood") { eventsGrid.style.display = "none"; woodUi.style.display = "flex"; resetWood(); return; } const id = Number(card.dataset.eventId); const ev = events.find(e => e.id === id); if (!ev) return; edpImg.src = ev.img; edpImg.alt = ev.label; edpTitle.textContent = ev.label; edpBody.textContent = "Inhalt folgt..."; overlay.classList.add("active"); }); }); /* ── Daily als erledigt markieren (für nicht-Booster Events) ── */ async function markDailyComplete(eventId) { try { await fetch("/api/daily/complete", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ eventId }), }); // Karte visuell als erledigt markieren const card = body.querySelector(`.event-card[data-event-id="${eventId}"]`); if (card) { card.dataset.done = "true"; card.classList.add("event-done"); const wrap = card.querySelector(".event-card-img-wrap"); if (wrap && !wrap.querySelector(".event-done-overlay")) { wrap.insertAdjacentHTML("beforeend", `
`); } if (!card.querySelector(".event-done-label")) { card.insertAdjacentHTML("beforeend", `Bereits erledigt`); } } } catch (e) { console.error("Daily markieren fehlgeschlagen", e); } } body.querySelector("#edp-close-btn").addEventListener("click", () => overlay.classList.remove("active")); overlay.addEventListener("click", e => { if (e.target === overlay) overlay.classList.remove("active"); }); body.querySelector("#booster-back-btn").addEventListener("click", () => { if (isSpinning) return; const boosterCard = body.querySelector('.event-card[data-type="booster"]'); const boosterDone = boosterCard?.dataset.done === "true"; if (!allRevealed && !boosterDone) return; eventsGrid.style.display = ""; boosterUi.style.display = "none"; clearAllIntervals(); isSpinning = false; }); body.querySelector("#wood-back-btn").addEventListener("click", () => { if (isWoodSpinning) return; const woodCard = body.querySelector('.event-card[data-type="wood"]'); const woodDone = woodCard?.dataset.done === "true"; if (!woodRevealed && !woodDone) return; eventsGrid.style.display = ""; woodUi.style.display = "none"; clearWoodIntervals(); isWoodSpinning = false; }); // KEIN document.addEventListener("keydown") hier – // ESC wird zentral in quickmenu.js behandelt (verhindert Listener-Stapelung) /* ── Booster Zustand ── */ let allCards = []; let isSpinning = false; let allRevealed = false; // true sobald alle 5 Karten aufgedeckt + gespeichert let spinIntervals = {}; // { index: intervalId } let slotLocked = {}; // { index: true } → verhindert Überschreiben nach reveal function clearAllIntervals() { Object.values(spinIntervals).forEach(id => clearInterval(id)); spinIntervals = {}; slotLocked = {}; } async function preloadCards() { if (allCards.length) return; try { const res = await fetch("/api/booster/cards"); if (!res.ok) throw new Error(res.status); allCards = await res.json(); } catch (e) { console.error("Karten laden fehlgeschlagen", e); } } function resetBooster() { clearAllIntervals(); isSpinning = false; allRevealed = false; for (let i = 0; i < 5; i++) { const inner = body.querySelector(`#booster-slot-${i} .booster-slot-inner`); inner.innerHTML = `?`; body.querySelector(`#booster-slot-${i}`).classList.remove("revealed", "spinning"); } const stapel = body.querySelector("#booster-stapel"); stapel.classList.remove("used"); stapel.style.opacity = "1"; stapel.style.cursor = "pointer"; body.querySelector("#booster-hint").textContent = "Klicken zum Öffnen"; preloadCards(); } /* ── Slot drehen – 350ms, damit man die Karten erkennt ── */ function startSpinSlot(index) { slotLocked[index] = false; const slot = body.querySelector(`#booster-slot-${index}`); const inner = slot.querySelector(".booster-slot-inner"); slot.classList.add("spinning"); const iv = setInterval(() => { if (!allCards.length || slotLocked[index]) return; const rnd = allCards[Math.floor(Math.random() * allCards.length)]; inner.innerHTML = cardHTML(rnd, true); }, 150); spinIntervals[index] = iv; } /* ── Slot enthüllen – Sperre setzen BEVOR clearInterval ── */ function revealSlot(index, card) { slotLocked[index] = true; // zuerst sperren clearInterval(spinIntervals[index]); // dann stoppen delete spinIntervals[index]; console.log(`[Booster] Slot ${index} enthüllt:`, card); const slot = body.querySelector(`#booster-slot-${index}`); const inner = slot.querySelector(".booster-slot-inner"); slot.classList.remove("spinning"); slot.classList.add("revealed"); // innerHTML setzen und danach nochmal sicherstellen (doppelte Absicherung) inner.innerHTML = cardHTML(card, true); setTimeout(() => { if (slot.classList.contains("revealed")) { inner.innerHTML = cardHTML(card, true); } }, 100); } /* ── Stapel klicken ── */ body.querySelector("#booster-stapel").addEventListener("click", async () => { if (isSpinning) return; if (!allCards.length) await preloadCards(); if (!allCards.length) return; isSpinning = true; const stapel = body.querySelector("#booster-stapel"); stapel.classList.add("used"); stapel.style.opacity = "0.35"; stapel.style.cursor = "default"; body.querySelector("#booster-hint").textContent = "Wird gezogen..."; // Zurück-Button während der Animation sperren const backBtn = body.querySelector("#booster-back-btn"); backBtn.style.opacity = "0.35"; backBtn.style.cursor = "not-allowed"; let drawnCards = []; try { const res = await fetch("/api/booster/open", { method: "POST" }); const text = await res.text(); console.log("[Booster] API Antwort raw:", text); const data = JSON.parse(text); console.log("[Booster] API Antwort parsed:", data); drawnCards = data.cards || []; console.log("[Booster] drawnCards:", drawnCards); } catch (e) { console.error("Booster öffnen fehlgeschlagen", e); resetBooster(); return; } for (let i = 0; i < 5; i++) startSpinSlot(i); for (let i = 0; i < 5; i++) { setTimeout(() => revealSlot(i, drawnCards[i]), (i + 1) * 5000); } setTimeout(async () => { body.querySelector("#booster-hint").textContent = "Karten werden gespeichert..."; isSpinning = false; // Karten in user_cards speichern try { const cardIds = drawnCards.map(c => c.id); const saveRes = await fetch("/api/booster/save", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ cardIds }), }); if (!saveRes.ok) throw new Error(saveRes.status); } catch (e) { console.error("Karten speichern fehlgeschlagen", e); } // Booster-Daily als erledigt markieren (event_id = 1) await markDailyComplete(1); allRevealed = true; body.querySelector("#booster-hint").textContent = "Alle Karten erhalten! ✓"; const backBtn = body.querySelector("#booster-back-btn"); backBtn.style.opacity = "1"; backBtn.style.cursor = "pointer"; }, 5 * 5000 + 500); }); preloadCards(); /* ================================ Holz-Spenden Zustand ================================ */ let isWoodSpinning = false; let woodRevealed = false; let woodIntervals = {}; let woodLocked = {}; let rarity3Cards = []; async function preloadRarity3() { if (rarity3Cards.length) return; try { const res = await fetch("/api/booster/cards"); if (!res.ok) throw new Error(res.status); const all = await res.json(); rarity3Cards = all.filter(c => parseInt(c.rarity) === 3); // Fallback: wenn keine rarity-3 vorhanden alle nehmen if (!rarity3Cards.length) rarity3Cards = all; } catch (e) { console.error("Rarity-3 Karten laden fehlgeschlagen", e); } } function clearWoodIntervals() { Object.values(woodIntervals).forEach(id => clearInterval(id)); woodIntervals = {}; woodLocked = {}; } function resetWood() { clearWoodIntervals(); isWoodSpinning = false; woodRevealed = false; const inner = body.querySelector(`#wood-slot-0 .booster-slot-inner`); inner.innerHTML = `?`; body.querySelector(`#wood-slot-0`).classList.remove("revealed", "spinning"); const btn = body.querySelector("#wood-btn"); const stamp = body.querySelector("#wood-stamp"); btn.classList.remove("used"); btn.style.opacity = "1"; btn.style.cursor = "pointer"; if (stamp) stamp.style.display = ""; body.querySelector("#wood-hint").textContent = "100 Holz spenden"; preloadRarity3(); } function startWoodSlot(index) { woodLocked[index] = false; const slot = body.querySelector(`#wood-slot-${index}`); const inner = slot.querySelector(".booster-slot-inner"); slot.classList.add("spinning"); const iv = setInterval(() => { if (!rarity3Cards.length || woodLocked[index]) return; const rnd = rarity3Cards[Math.floor(Math.random() * rarity3Cards.length)]; inner.innerHTML = cardHTML(rnd, true); }, 150); woodIntervals[index] = iv; } function revealWoodSlot(index, card) { woodLocked[index] = true; clearInterval(woodIntervals[index]); delete woodIntervals[index]; const slot = body.querySelector(`#wood-slot-${index}`); const inner = slot.querySelector(".booster-slot-inner"); slot.classList.remove("spinning"); slot.classList.add("revealed"); inner.innerHTML = cardHTML(card, true); setTimeout(() => { if (slot.classList.contains("revealed")) inner.innerHTML = cardHTML(card, true); }, 100); } body.querySelector("#wood-btn").addEventListener("click", async () => { if (isWoodSpinning) return; if (!rarity3Cards.length) await preloadRarity3(); if (!rarity3Cards.length) return; isWoodSpinning = true; const btn = body.querySelector("#wood-btn"); const stamp = body.querySelector("#wood-stamp"); btn.classList.add("used"); btn.style.opacity = "0.35"; btn.style.cursor = "default"; if (stamp) stamp.style.display = "none"; body.querySelector("#wood-hint").textContent = "Wird verarbeitet..."; const backBtn = body.querySelector("#wood-back-btn"); backBtn.style.opacity = "0.35"; backBtn.style.cursor = "not-allowed"; // API aufrufen – 100 Holz abziehen + Karte ziehen let drawnCard = null; try { const res = await fetch("/api/booster/wood-donate", { method: "POST" }); const data = await res.json(); if (!res.ok) { body.querySelector("#wood-hint").textContent = data.error || "Fehler"; isWoodSpinning = false; btn.classList.remove("used"); btn.style.opacity = "1"; btn.style.cursor = "pointer"; backBtn.style.opacity = "1"; backBtn.style.cursor = "pointer"; return; } drawnCard = data.card; } catch (e) { console.error("Holz-Spenden fehlgeschlagen", e); resetWood(); return; } // Nur Slot 0 starten startWoodSlot(0); // Nach 5 Sekunden enthüllen setTimeout(() => { revealWoodSlot(0, drawnCard); body.querySelector("#wood-hint").textContent = "Karte erhalten! ✓"; isWoodSpinning = false; woodRevealed = true; markDailyComplete(4); backBtn.style.opacity = "1"; backBtn.style.cursor = "pointer"; }, 5000); }); preloadRarity3(); }