diff --git a/routes/booster_route.js b/routes/booster_route.js new file mode 100644 index 0000000..4e3bf06 --- /dev/null +++ b/routes/booster_route.js @@ -0,0 +1,248 @@ +const express = require("express"); +const router = express.Router(); +const db = require("../database/database"); + +/* ================================ + Gewichtete Zufallsauswahl +================================ */ +function weightedRandom(weights) { + const total = weights.reduce((s, w) => s + w.weight, 0); + let r = Math.random() * total; + for (const entry of weights) { + r -= entry.weight; + if (r <= 0) return entry.rarity; + } + return weights[weights.length - 1].rarity; +} + +/* ================================ + Gewichte je Spielerlevel +================================ */ +function getWeights(playerLevel) { + if (playerLevel < 10) + return [ + { rarity: 1, weight: 95 }, // 95% + { rarity: 2, weight: 5 }, // 5% + ]; + if (playerLevel < 20) + return [ + { rarity: 1, weight: 87 }, // 87% + { rarity: 2, weight: 11 }, // 11% + { rarity: 3, weight: 2 }, // 2% + ]; + if (playerLevel < 30) + return [ + { rarity: 1, weight: 80 }, // 80% + { rarity: 2, weight: 14 }, // 14% + { rarity: 3, weight: 4 }, // 4% + { rarity: 4, weight: 2 }, // 2% + ]; + if (playerLevel < 40) + return [ + { rarity: 1, weight: 75 }, // 75% + { rarity: 2, weight: 16 }, // 16% + { rarity: 3, weight: 5 }, // 5% + { rarity: 4, weight: 3 }, // 3% + { rarity: 5, weight: 1 }, // 1% + ]; + return [ + { rarity: 1, weight: 60 }, // 60.0% + { rarity: 2, weight: 25 }, // 25.0% + { rarity: 3, weight: 9 }, // 9.0% + { rarity: 4, weight: 4 }, // 4.0% + { rarity: 5, weight: 1.5 }, // 1.5% + { rarity: 6, weight: 0.5 }, // 0.5% + ]; +} + +/* ================================ + GET /api/booster/cards + Alle Karten inkl. Stats für Slot-Animation +================================ */ +router.get("/booster/cards", async (req, res) => { + if (!req.session?.user) + return res.status(401).json({ error: "Nicht eingeloggt" }); + try { + const [cards] = await db.query( + "SELECT id, name, image, icon, max_level, rarity, attack, defends, cooldown, `range`, `race` FROM cards ORDER BY id", + ); + res.json(cards); + } catch (err) { + console.error(err); + res.status(500).json({ error: "DB Fehler" }); + } +}); + +/* ================================ + POST /api/booster/open + 5 gewichtete Zufallskarten inkl. Stats +================================ */ +router.post("/booster/open", async (req, res) => { + if (!req.session?.user) + return res.status(401).json({ error: "Nicht eingeloggt" }); + + const userId = req.session.user.id; + try { + const [[account]] = await db.query( + "SELECT level FROM accounts WHERE id = ?", + [userId], + ); + const playerLevel = account?.level ?? 1; + const weights = getWeights(playerLevel); + const maxAllowed = Math.max(...weights.map((w) => w.rarity)); + + const [allCards] = await db.query( + "SELECT id, name, image, icon, max_level, rarity, attack, defends, cooldown, `range`, `race` FROM cards WHERE rarity <= ?", + [maxAllowed], + ); + + if (!allCards.length) + return res.status(400).json({ error: "Keine Karten verfügbar" }); + + const result = []; + for (let i = 0; i < 5; i++) { + const targetRarity = weightedRandom(weights); + let pool = allCards.filter((c) => parseInt(c.rarity) === targetRarity); + if (!pool.length) { + for (let fb = targetRarity - 1; fb >= 1; fb--) { + pool = allCards.filter((c) => parseInt(c.rarity) === fb); + if (pool.length) break; + } + } + if (!pool.length) pool = allCards; + result.push(pool[Math.floor(Math.random() * pool.length)]); + } + + res.json({ cards: result, playerLevel }); + } catch (err) { + console.error(err); + res.status(500).json({ error: "DB Fehler" }); + } +}); + +/* ================================ + POST /api/booster/save + Gezogene Karten in user_cards speichern + Body: { cardIds: [1, 2, 3, 4, 5] } +================================ */ +router.post("/booster/save", async (req, res) => { + if (!req.session?.user) + return res.status(401).json({ error: "Nicht eingeloggt" }); + + const userId = req.session.user.id; + const cardIds = req.body?.cardIds; + + if (!Array.isArray(cardIds) || cardIds.length === 0) { + return res.status(400).json({ error: "Keine Karten übergeben" }); + } + + try { + for (const cardId of cardIds) { + await db.query( + `INSERT INTO user_cards (user_id, card_id, amount) + VALUES (?, ?, 1) + ON DUPLICATE KEY UPDATE amount = amount + 1`, + [userId, cardId], + ); + } + res.json({ success: true }); + } catch (err) { + console.error(err); + res.status(500).json({ error: "DB Fehler beim Speichern" }); + } +}); + +/* ================================ + POST /api/booster/wood-donate + 100 Holz bezahlen → 1 zufällige Rarity-3 Karte +================================ */ +router.post("/booster/wood-donate", async (req, res) => { + if (!req.session?.user) + return res.status(401).json({ error: "Nicht eingeloggt" }); + + const userId = req.session.user.id; + try { + // Holz prüfen + const [[currency]] = await db.query( + "SELECT wood FROM account_currency WHERE account_id = ?", + [userId], + ); + if (!currency || currency.wood < 100) { + return res.status(400).json({ error: "Nicht genug Holz (100 benötigt)" }); + } + + // Rarity-3 Karten laden + const [pool] = await db.query( + "SELECT id, name, image, icon, max_level, rarity, attack, defends, cooldown, `range`, `race` FROM cards WHERE rarity = 3", + ); + if (!pool.length) + return res.status(400).json({ error: "Keine Rarity-3 Karten verfügbar" }); + + // 100 Holz abziehen + await db.query( + "UPDATE account_currency SET wood = wood - 100 WHERE account_id = ?", + [userId], + ); + + // 1 zufällige Rarity-3 Karte ziehen + const card = pool[Math.floor(Math.random() * pool.length)]; + + // Direkt in user_cards speichern + await db.query( + `INSERT INTO user_cards (user_id, card_id, amount) VALUES (?, ?, 1) + ON DUPLICATE KEY UPDATE amount = amount + 1`, + [userId, card.id], + ); + + res.json({ card }); + } catch (err) { + console.error(err); + res.status(500).json({ error: "DB Fehler" }); + } +}); + +/* ================================ + POST /api/booster/gold-donate + 100 Gold bezahlen → 1 zufällige Rarity-4 Karte +================================ */ +router.post("/booster/gold-donate", async (req, res) => { + if (!req.session?.user) + return res.status(401).json({ error: "Nicht eingeloggt" }); + + const userId = req.session.user.id; + try { + const [[currency]] = await db.query( + "SELECT gold FROM account_currency WHERE account_id = ?", + [userId], + ); + if (!currency || currency.gold < 100) { + return res.status(400).json({ error: "Nicht genug Gold (100 benötigt)" }); + } + + const [pool] = await db.query( + "SELECT id, name, image, icon, max_level, rarity, attack, defends, cooldown, `range`, `race` FROM cards WHERE rarity = 4", + ); + if (!pool.length) + return res.status(400).json({ error: "Keine Rarity-4 Karten verfügbar" }); + + await db.query( + "UPDATE account_currency SET gold = gold - 100 WHERE account_id = ?", + [userId], + ); + + const card = pool[Math.floor(Math.random() * pool.length)]; + + await db.query( + `INSERT INTO user_cards (user_id, card_id, amount) VALUES (?, ?, 1) + ON DUPLICATE KEY UPDATE amount = amount + 1`, + [userId, card.id], + ); + + res.json({ card }); + } catch (err) { + console.error(err); + res.status(500).json({ error: "DB Fehler" }); + } +}); + +module.exports = router; diff --git a/routes/carddeck_route.js b/routes/carddeck_route.js new file mode 100644 index 0000000..25e8d62 --- /dev/null +++ b/routes/carddeck_route.js @@ -0,0 +1,332 @@ +const express = require("express"); +const router = express.Router(); +const db = require("../database/database"); + +/* ======================== + Auth Middleware +======================== */ + +function requireLogin(req, res, next) { + if (!req.session.user) return res.status(401).json({ error: "Nicht eingeloggt" }); + next(); +} + +router.use(requireLogin); + +/* ════════════════════════════════════════════ + GET /api/card-groups + Alle Kartengruppen +════════════════════════════════════════════ */ + +router.get("/card-groups", async (req, res) => { + try { + const [groups] = await db.query("SELECT * FROM card_groups ORDER BY id"); + res.json(groups); + } catch (err) { + console.error(err); + res.status(500).json({ error: "DB Fehler" }); + } +}); + +/* ════════════════════════════════════════════ + GET /api/cards?group_id=X&page=Y&limit=Z + Alle Karten (Admin-/Gruppen-Ansicht, kein Besitz) +════════════════════════════════════════════ */ + +router.get("/cards", async (req, res) => { + const { group_id, page = 1, limit = 12 } = req.query; + const offset = (page - 1) * limit; + + try { + const [cards] = await db.query( + `SELECT c.*, cg.name AS group_name, cg.color AS group_color, + c.attack, c.defends, c.cooldown, c.\`range\`, c.\`race\` + FROM cards c + LEFT JOIN card_groups cg ON cg.id = c.group_id + WHERE c.group_id = ? + LIMIT ? OFFSET ?`, + [group_id, parseInt(limit), parseInt(offset)] + ); + const [[{ total }]] = await db.query( + "SELECT COUNT(*) AS total FROM cards WHERE group_id = ?", + [group_id] + ); + res.json({ cards, total, page: parseInt(page), totalPages: Math.ceil(total / limit) }); + } catch (err) { + console.error(err); + res.status(500).json({ error: "DB Fehler" }); + } +}); + +/* ════════════════════════════════════════════ + GET /api/user-cards?group_id=X&page=Y&limit=Z + Karten die der Spieler besitzt (mit amount) +════════════════════════════════════════════ */ + +router.get("/user-cards", async (req, res) => { + const userId = req.session.user.id; + const { group_id, page = 1, limit = 18 } = req.query; + const offset = (page - 1) * limit; + + try { + const [cards] = await db.query( + `SELECT + uc.card_id, + uc.amount, + c.name, + c.image, + c.rarity, + cg.name AS group_name, + cg.color AS group_color, + c.attack, + c.defends, + c.cooldown, + c.`range`, + c.`race` + FROM user_cards uc + JOIN cards c ON c.id = uc.card_id + JOIN card_groups cg ON cg.id = c.group_id + WHERE uc.user_id = ? AND c.group_id = ? + ORDER BY c.id + LIMIT ? OFFSET ?`, + [userId, group_id, parseInt(limit), parseInt(offset)] + ); + + const [[{ total }]] = await db.query( + `SELECT COUNT(*) AS total + FROM user_cards uc + JOIN cards c ON c.id = uc.card_id + WHERE uc.user_id = ? AND c.group_id = ?`, + [userId, group_id] + ); + + res.json({ cards, total, page: parseInt(page), totalPages: Math.ceil(total / limit) }); + } catch (err) { + console.error(err); + res.status(500).json({ error: "DB Fehler" }); + } +}); + +/* ════════════════════════════════════════════ + GET /api/decks + Alle Decks des Spielers +════════════════════════════════════════════ */ + +router.get("/decks", async (req, res) => { + const userId = req.session.user.id; + try { + const [decks] = await db.query( + `SELECT d.id, d.name, + COALESCE(SUM(dc.amount), 0) AS card_count + FROM decks d + LEFT JOIN deck_cards dc ON dc.deck_id = d.id + WHERE d.user_id = ? + GROUP BY d.id + ORDER BY d.created_at`, + [userId] + ); + res.json(decks); + } catch (err) { + console.error(err); + res.status(500).json({ error: "DB Fehler" }); + } +}); + +/* ════════════════════════════════════════════ + POST /api/decks { name } + Neues Deck erstellen (max. 10 pro Spieler) +════════════════════════════════════════════ */ + +router.post("/decks", async (req, res) => { + const userId = req.session.user.id; + const { name } = req.body; + + if (!name || !name.trim()) { + return res.status(400).json({ error: "Name darf nicht leer sein." }); + } + + try { + const [[{ count }]] = await db.query( + "SELECT COUNT(*) AS count FROM decks WHERE user_id = ?", + [userId] + ); + if (count >= 10) { + return res.status(400).json({ error: "Maximale Anzahl von 10 Decks erreicht." }); + } + + const [result] = await db.query( + "INSERT INTO decks (user_id, name) VALUES (?, ?)", + [userId, name.trim()] + ); + res.status(201).json({ id: result.insertId, name: name.trim(), card_count: 0 }); + } catch (err) { + console.error(err); + res.status(500).json({ error: "DB Fehler" }); + } +}); + +/* ════════════════════════════════════════════ + GET /api/decks/:id/cards + Karten eines bestimmten Decks +════════════════════════════════════════════ */ + +router.get("/decks/:id/cards", async (req, res) => { + const userId = req.session.user.id; + const deckId = req.params.id; + + try { + // Deck gehört dem Spieler? + const [[deck]] = await db.query( + "SELECT id FROM decks WHERE id = ? AND user_id = ?", + [deckId, userId] + ); + if (!deck) return res.status(404).json({ error: "Deck nicht gefunden." }); + + const [cards] = await db.query( + `SELECT + dc.card_id, + dc.amount, + c.name, + c.image, + c.rarity, + c.attack, + c.defends, + c.cooldown, + c.`range`, + c.`race` + FROM deck_cards dc + JOIN cards c ON c.id = dc.card_id + WHERE dc.deck_id = ? + ORDER BY c.name`, + [deckId] + ); + res.json(cards); + } catch (err) { + console.error(err); + res.status(500).json({ error: "DB Fehler" }); + } +}); + +/* ════════════════════════════════════════════ + POST /api/decks/:id/cards { card_id, level } + Karte zum Deck hinzufügen +════════════════════════════════════════════ */ + +router.post("/decks/:id/cards", async (req, res) => { + const userId = req.session.user.id; + const deckId = req.params.id; + const { card_id } = req.body; + + if (!card_id) return res.status(400).json({ error: "card_id fehlt." }); + + try { + // Deck gehört dem Spieler? + const [[deck]] = await db.query( + "SELECT id FROM decks WHERE id = ? AND user_id = ?", + [deckId, userId] + ); + if (!deck) return res.status(404).json({ error: "Deck nicht gefunden." }); + + // Besitzt der Spieler diese Karte? + const [[owned]] = await db.query( + "SELECT amount FROM user_cards WHERE user_id = ? AND card_id = ?", + [userId, card_id] + ); + if (!owned) return res.status(400).json({ error: "Du besitzt diese Karte nicht." }); + + // Deck-Gesamtzahl prüfen (max 30) + const [[{ total }]] = await db.query( + "SELECT COALESCE(SUM(amount), 0) AS total FROM deck_cards WHERE deck_id = ?", + [deckId] + ); + if (total >= 30) { + return res.status(400).json({ error: "Deck ist voll (max. 30 Karten)." }); + } + + // Rarity > 5: max. 1× im Deck + const [[cardInfo]] = await db.query( + "SELECT rarity FROM cards WHERE id = ?", + [card_id] + ); + const [[existing]] = await db.query( + "SELECT amount FROM deck_cards WHERE deck_id = ? AND card_id = ?", + [deckId, card_id] + ); + + if (parseInt(cardInfo?.rarity) > 5 && existing) { + return res.status(400).json({ error: "Karten ab Rarity 6 dürfen nur einmal im Deck sein." }); + } + + // Nicht mehr einfügen als besessen + const currentInDeck = existing ? existing.amount : 0; + if (currentInDeck >= owned.amount) { + return res.status(400).json({ error: "Du hast keine weiteren Exemplare dieser Karte." }); + } + + // Einfügen oder erhöhen + if (existing) { + await db.query( + "UPDATE deck_cards SET amount = amount + 1 WHERE deck_id = ? AND card_id = ?", + [deckId, card_id] + ); + } else { + await db.query( + "INSERT INTO deck_cards (deck_id, card_id, amount) VALUES (?, ?, 1)", + [deckId, card_id] + ); + } + + res.json({ success: true }); + } catch (err) { + // DB-Trigger Fehler (45000) sauber weitergeben + if (err.sqlState === "45000") { + return res.status(400).json({ error: err.message }); + } + console.error(err); + res.status(500).json({ error: "DB Fehler" }); + } +}); + +/* ════════════════════════════════════════════ + DELETE /api/decks/:id/cards { card_id, level } + Karte aus Deck entfernen (amount - 1, bei 0 löschen) +════════════════════════════════════════════ */ + +router.delete("/decks/:id/cards", async (req, res) => { + const userId = req.session.user.id; + const deckId = req.params.id; + const { card_id } = req.body; + + if (!card_id) return res.status(400).json({ error: "card_id fehlt." }); + + try { + // Deck gehört dem Spieler? + const [[deck]] = await db.query( + "SELECT id FROM decks WHERE id = ? AND user_id = ?", + [deckId, userId] + ); + if (!deck) return res.status(404).json({ error: "Deck nicht gefunden." }); + + const [[entry]] = await db.query( + "SELECT id, amount FROM deck_cards WHERE deck_id = ? AND card_id = ?", + [deckId, card_id] + ); + if (!entry) return res.status(404).json({ error: "Karte nicht im Deck." }); + + if (entry.amount > 1) { + await db.query( + "UPDATE deck_cards SET amount = amount - 1 WHERE id = ?", + [entry.id] + ); + } else { + await db.query("DELETE FROM deck_cards WHERE id = ?", [entry.id]); + } + + res.json({ success: true }); + } catch (err) { + console.error(err); + res.status(500).json({ error: "DB Fehler" }); + } +}); + +module.exports = router;