299 lines
11 KiB
JavaScript
299 lines
11 KiB
JavaScript
/* ================================
|
||
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 = `<img src="/images/items/${file}" alt="Stufe ${rarity}" style="width:${size}px;height:${size}px;object-fit:contain;filter:drop-shadow(0 1px 2px rgba(0,0,0,0.8));">`;
|
||
return img.repeat(count);
|
||
}
|
||
|
||
function cardHTML(card, isFront = true) {
|
||
if (!isFront) return `<img class="booster-slot-img" src="/images/items/rueckseite.png" alt="?" draggable="false">`;
|
||
|
||
// image zuerst, dann icon als Fallback
|
||
const imgFile = card?.image || card?.icon || null;
|
||
const img = imgFile ? `/images/cards/${imgFile}` : "/images/items/rueckseite.png";
|
||
return `
|
||
<img class="booster-slot-img" src="${img}" alt="${card?.name || ''}" draggable="false"
|
||
onerror="this.src='/images/items/rueckseite.png'">
|
||
${card?.attack != null ? `<span class="bs-stat-atk">${card.attack}</span>` : ""}
|
||
${card?.defends != null ? `<span class="bs-stat-def">${card.defends}</span>` : ""}
|
||
${card?.cooldown!= null ? `<span class="bs-stat-cd">${card.cooldown}</span>` : ""}
|
||
${card?.rarity ? `<div class="bs-rarity">${rarityImgs(card.rarity, 11)}</div>` : ""}
|
||
<div class="bs-card-name">${card?.name || ''}</div>
|
||
`;
|
||
}
|
||
|
||
/* ================================
|
||
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/runenhaufen.png", label: "Textzeile 4" },
|
||
{ id: 5, img: "/images/items/runenhaufen.png", label: "Textzeile 5" },
|
||
];
|
||
|
||
body.innerHTML = `
|
||
<div class="events-grid" id="events-grid">
|
||
${events.map(ev => `
|
||
<div class="event-card" data-event-id="${ev.id}" data-type="${ev.type || ''}">
|
||
<div class="event-card-img-wrap">
|
||
<img src="${ev.img}" alt="${ev.label}" draggable="false">
|
||
</div>
|
||
<span class="event-card-label">${ev.label}</span>
|
||
</div>`).join("")}
|
||
</div>
|
||
|
||
<!-- Booster UI -->
|
||
<div id="booster-ui" class="booster-ui" style="display:none;">
|
||
<button class="booster-back-btn" id="booster-back-btn">← Zurück</button>
|
||
<div class="booster-stage">
|
||
<div class="booster-left">
|
||
<img id="booster-stapel" src="/images/items/boosterstapel.png" alt="Booster" draggable="false" class="booster-stapel-img">
|
||
<span class="booster-stapel-hint" id="booster-hint">Klicken zum Öffnen</span>
|
||
</div>
|
||
<div class="booster-slots" id="booster-slots">
|
||
${Array.from({length: 5}, (_, i) => `
|
||
<div class="booster-slot" id="booster-slot-${i}">
|
||
<div class="booster-slot-inner">
|
||
<img class="booster-slot-img" src="/images/items/rueckseite.png" alt="?" draggable="false">
|
||
</div>
|
||
</div>`).join("")}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Standard Detail-Popup -->
|
||
<div id="event-detail-overlay">
|
||
<div id="event-detail-popup">
|
||
<button class="edp-close" id="edp-close-btn">✕</button>
|
||
<img class="edp-img" id="edp-img" src="" alt="">
|
||
<div class="edp-title" id="edp-title"></div>
|
||
<div class="edp-body" id="edp-body">Inhalt folgt...</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
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 eventsGrid = body.querySelector("#events-grid");
|
||
|
||
/* ── Event-Karten ── */
|
||
body.querySelectorAll(".event-card").forEach(card => {
|
||
card.addEventListener("click", () => {
|
||
if (card.dataset.type === "booster") {
|
||
eventsGrid.style.display = "none";
|
||
boosterUi.style.display = "flex";
|
||
resetBooster();
|
||
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");
|
||
});
|
||
});
|
||
|
||
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 || !allRevealed) return; // ← gesperrt bis alle Karten enthüllt
|
||
eventsGrid.style.display = "";
|
||
boosterUi.style.display = "none";
|
||
clearAllIntervals();
|
||
isSpinning = false;
|
||
});
|
||
|
||
document.addEventListener("keydown", e => {
|
||
if (e.key === "Escape") {
|
||
overlay.classList.remove("active");
|
||
if (!isSpinning && allRevealed) { // ← ESC im Booster nur wenn fertig
|
||
eventsGrid.style.display = "";
|
||
boosterUi.style.display = "none";
|
||
clearAllIntervals();
|
||
}
|
||
}
|
||
});
|
||
|
||
/* ── 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 = `<img class="booster-slot-img" src="/images/items/rueckseite.png" alt="?" draggable="false">`;
|
||
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);
|
||
}
|
||
|
||
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();
|
||
}
|