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.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;