diff --git a/app.js b/app.js index be9aa37..8e029eb 100644 --- a/app.js +++ b/app.js @@ -28,6 +28,7 @@ const { registerArenaHandlers } = require("./sockets/arena"); const { registerChatHandlers } = require("./sockets/chat"); const boosterRoutes = require("./routes/booster.route"); const pointsRoutes = require("./routes/points.route"); +const combineRoutes = require("./routes/combine.route"); const compression = require("compression"); @@ -389,6 +390,7 @@ app.use("/arena", arenaRoutes); app.use("/api", boosterRoutes); app.use("/api", require("./routes/daily.route")); app.use("/api/points", pointsRoutes); +app.use("/api", combineRoutes); /* ======================== 404 Handler diff --git a/public/js/gaststaette/glucksspiel.js b/public/js/gaststaette/glucksspiel.js index 2a80fc2..4676fb9 100644 --- a/public/js/gaststaette/glucksspiel.js +++ b/public/js/gaststaette/glucksspiel.js @@ -30,10 +30,24 @@ function rarityImgs(rarity, size = 13) { // State let gs_groupId = null; let gs_page = 1; -let gs_cards = []; // aktuelle Seite -let gs_combineId = null; // card_id der Karte im Kombinier-Feld -let gs_combineCards = []; // [{card_id, name, image, rarity, attack, defends, cooldown}] im Feld +let gs_cards = []; +let gs_combineId = null; +let gs_combineCards = []; let gs_initialized = false; +let gs_tavernLevel = 1; +let gs_required = 10; // Karten für 100% + +function getRequired(tavernLevel) { + if (tavernLevel < 5) return 10; + if (tavernLevel < 10) return 8; + if (tavernLevel < 15) return 6; + if (tavernLevel < 20) return 5; + return 3; +} + +function getChance(amount, required) { + return Math.min(Math.round((amount / required) * 100), 100); +} /* ══════════════════════════════════════════════ INIT @@ -50,6 +64,14 @@ export async function loadGlucksspiel() { attachCombineEvents(); try { + // Gaststätte-Level und benötigte Karten laden + const infoRes = await fetch("/api/cards/combine-info"); + if (infoRes.ok) { + const info = await infoRes.json(); + gs_tavernLevel = info.tavernLevel; + gs_required = info.required; + } + const res = await fetch("/api/card-groups"); if (!res.ok) throw new Error(); const groups = await res.json(); @@ -251,17 +273,6 @@ function renderShell() { gap: 1px; padding: 0 4px; pointer-events: none; z-index: 4; } - .gs-stat-cd { - position: absolute; - bottom: 7px; right: 8px; - width: 20px; height: 20px; - border-radius: 50%; - background: rgba(0,0,0,0.75); - border: 1px solid #f0d060; - display: flex; align-items: center; justify-content: center; - font-family: "Cinzel", serif; font-size: 9px; font-weight: bold; - color: #f0d9a6; z-index: 5; - } /* ── Pagination ──────────────────────────── */ .gs-pagination { @@ -284,13 +295,12 @@ function renderShell() { /* ── Rechte Spalte: Kombinier-Panel ──────── */ .gs-combine-panel { - width: 260px; + width: 320px; flex-shrink: 0; display: flex; flex-direction: column; overflow: hidden; background: rgba(0,0,0,0.15); - margin-right: 40px; } .gs-combine-hint { @@ -479,7 +489,6 @@ function renderGrid(grid, cards) { onerror="this.src='/images/avatar_placeholder.svg'"> ${c.attack != null ? `${c.attack}` : ""} ${c.defends != null ? `${c.defends}` : ""} - ${c.cooldown != null ? `${c.cooldown}` : ""} @@ -542,6 +551,9 @@ function renderCombineField() { hint.style.display = "none"; + const chance = getChance(gs_combineCards.length, gs_required); + const chanceColor = chance >= 100 ? "#44ff44" : chance >= 60 ? "#f0d060" : "#ff6644"; + // Karten als gestapelter Stapel – jede Karte leicht versetzt const offset = Math.min(6, 40 / gs_combineCards.length); // max 6px versatz const stackHeight = offset * (gs_combineCards.length - 1); // extra höhe für stapel @@ -555,8 +567,6 @@ function renderCombineField() { "> ${c.name} - ${c.attack != null ? `${c.attack}` : ""} - ${c.defends != null ? `${c.defends}` : ""} ${c.cooldown != null ? `${c.cooldown}` : ""} `).join(""); @@ -571,7 +581,15 @@ function renderCombineField() { removeFromField(gs_combineCards.length - 1); }); - count.textContent = `${gs_combineCards.length} Karte${gs_combineCards.length !== 1 ? "n" : ""} im Stapel`; + count.innerHTML = ` +
+ ${gs_combineCards.length} Karte${gs_combineCards.length !== 1 ? "n" : ""} im Stapel + ${chance}% Erfolgschance +
+
+
+ ${gs_required} Karten = 100% (Gaststätte Lv.${gs_tavernLevel}) +
`; btn.disabled = gs_combineCards.length < 2; } @@ -608,16 +626,25 @@ function attachCombineEvents() { return; } - showToast("Kombinieren erfolgreich! 🎉"); - gs_combineCards = []; - gs_combineId = null; - gs_initialized = false; // Neu laden erzwingen - renderCombineField(); - await loadCards(); + if (!data.success) { + // Misserfolg – Karten sind weg + showToast(`❌ Misserfolg! (${data.chance}% Chance) – Karten verbraucht.`); + gs_combineCards = []; + gs_combineId = null; + renderCombineField(); + await loadCards(); + } else { + // Erfolg – neue Karte zeigen + showToast(`✅ Erfolg! Du erhältst: ${data.reward.name} (Rarity ${data.reward.rarity})`); + gs_combineCards = []; + gs_combineId = null; + gs_initialized = false; + renderCombineField(); + await loadCards(); + } } catch { showToast("Verbindungsfehler."); btn.disabled = false; - btn.textContent = "Kombinieren"; } finally { btn.textContent = "Kombinieren"; } diff --git a/routes/combine.route.js b/routes/combine.route.js new file mode 100644 index 0000000..84306c4 --- /dev/null +++ b/routes/combine.route.js @@ -0,0 +1,178 @@ +/* ============================================================ + routes/combine.route.js + POST /api/cards/combine + Karten kombinieren in der Gaststätte (Gebäude 6) +============================================================ */ + +const express = require("express"); +const router = express.Router(); +const db = require("../database/database"); + +function requireLogin(req, res, next) { + if (!req.session?.user) return res.status(401).json({ error: "Nicht eingeloggt" }); + next(); +} + +/* ════════════════════════════════════════════ + Gaststätte-Level → benötigte Karten für 100% +════════════════════════════════════════════ */ +function getRequiredCards(tavernLevel) { + if (tavernLevel < 5) return 10; + if (tavernLevel < 10) return 8; + if (tavernLevel < 15) return 6; + if (tavernLevel < 20) return 5; + return 3; +} + +/* ════════════════════════════════════════════ + Max erlaubte Rarity je Spielerlevel + (gleiche Logik wie booster.route.js) +════════════════════════════════════════════ */ +function getMaxRarity(playerLevel) { + if (playerLevel < 10) return 2; + if (playerLevel < 20) return 3; + if (playerLevel < 30) return 4; + if (playerLevel < 40) return 5; + return 6; +} + +/* ════════════════════════════════════════════ + POST /api/cards/combine + Body: { card_id: Number, amount: Number } +════════════════════════════════════════════ */ +router.post("/cards/combine", requireLogin, async (req, res) => { + const userId = req.session.user.id; + const { card_id, amount } = req.body; + + if (!card_id || !amount || amount < 2) { + return res.status(400).json({ error: "Mindestens 2 Karten erforderlich." }); + } + + try { + /* ── 1. Spielerlevel holen ── */ + const [[account]] = await db.query( + "SELECT level FROM accounts WHERE id = ?", + [userId] + ); + const playerLevel = account?.level ?? 1; + + /* ── 2. Gaststätte-Level holen (building_id = 6) ── */ + const [[tavern]] = await db.query( + "SELECT level FROM user_buildings WHERE user_id = ? AND building_id = 6", + [userId] + ); + const tavernLevel = tavern?.level ?? 1; + + /* ── 3. Chance berechnen ── */ + const required = getRequiredCards(tavernLevel); + const chance = Math.min((amount / required) * 100, 100); + + /* ── 4. Besitz prüfen ── */ + const [[owned]] = await db.query( + "SELECT amount FROM user_cards WHERE user_id = ? AND card_id = ?", + [userId, card_id] + ); + if (!owned || owned.amount < amount) { + return res.status(400).json({ error: "Nicht genügend Karten vorhanden." }); + } + + /* ── 5. Eingabe-Karte laden (für Rarity) ── */ + const [[inputCard]] = await db.query( + "SELECT id, name, rarity FROM cards WHERE id = ?", + [card_id] + ); + if (!inputCard) { + return res.status(400).json({ error: "Karte nicht gefunden." }); + } + + /* ── 6. Karten immer abziehen (Glücksspiel!) ── */ + if (owned.amount - amount <= 0) { + await db.query( + "DELETE FROM user_cards WHERE user_id = ? AND card_id = ?", + [userId, card_id] + ); + } else { + await db.query( + "UPDATE user_cards SET amount = amount - ? WHERE user_id = ? AND card_id = ?", + [amount, userId, card_id] + ); + } + + /* ── 7. Zufallsroll gegen Chance ── */ + const roll = Math.random() * 100; + const success = roll <= chance; + + if (!success) { + return res.json({ + success: false, + chance: Math.round(chance), + message: "Kombinieren fehlgeschlagen – die Karten wurden verbraucht.", + }); + } + + /* ── 8. Ausgabe-Karte bestimmen ── */ + const targetRarity = parseInt(inputCard.rarity) + 1; + const maxRarity = getMaxRarity(playerLevel); + const outputRarity = Math.min(targetRarity, maxRarity); + + // Karte mit outputRarity suchen (nicht dieselbe Karte) + const [pool] = await db.query( + "SELECT id, name, image, rarity, attack, defends, cooldown FROM cards WHERE rarity = ? AND id != ?", + [outputRarity, card_id] + ); + + // Fallback: gleiche Rarity falls keine höhere vorhanden + const [fallbackPool] = await db.query( + "SELECT id, name, image, rarity, attack, defends, cooldown FROM cards WHERE rarity = ?", + [parseInt(inputCard.rarity)] + ); + + const finalPool = pool.length ? pool : fallbackPool; + if (!finalPool.length) { + return res.status(500).json({ error: "Keine passende Karte gefunden." }); + } + + const reward = finalPool[Math.floor(Math.random() * finalPool.length)]; + + /* ── 9. Belohnungskarte gutschreiben ── */ + await db.query( + `INSERT INTO user_cards (user_id, card_id, amount) + VALUES (?, ?, 1) + ON DUPLICATE KEY UPDATE amount = amount + 1`, + [userId, reward.id] + ); + + res.json({ + success: true, + chance: Math.round(chance), + reward, + message: `Kombinieren erfolgreich! Du erhältst: ${reward.name}`, + }); + + } catch (err) { + console.error("Combine Fehler:", err); + res.status(500).json({ error: "DB Fehler" }); + } +}); + +/* ════════════════════════════════════════════ + GET /api/cards/combine-info + Gaststätte-Level + benötigte Karten für Frontend +════════════════════════════════════════════ */ +router.get("/cards/combine-info", requireLogin, async (req, res) => { + const userId = req.session.user.id; + try { + const [[tavern]] = await db.query( + "SELECT level FROM user_buildings WHERE user_id = ? AND building_id = 6", + [userId] + ); + const tavernLevel = tavern?.level ?? 1; + const required = getRequiredCards(tavernLevel); + res.json({ tavernLevel, required }); + } catch (err) { + console.error(err); + res.status(500).json({ error: "DB Fehler" }); + } +}); + +module.exports = router;