This commit is contained in:
cay 2026-04-10 12:29:23 +01:00
parent f7ba41d31c
commit a3d4dc9ce8
3 changed files with 234 additions and 27 deletions

2
app.js
View File

@ -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

View File

@ -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 ? `<span class="gs-stat-atk">${c.attack}</span>` : ""}
${c.defends != null ? `<span class="gs-stat-def">${c.defends}</span>` : ""}
${c.cooldown != null ? `<span class="gs-stat-cd">${c.cooldown}</span>` : ""}
<div class="gs-card-footer">
<span class="gs-count-owned">× ${available < 0 ? 0 : available}</span>
</div>
@ -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() {
">
<img src="/images/cards/${c.image}" alt="${c.name}"
onerror="this.src='/images/avatar_placeholder.svg'">
${c.attack != null ? `<span class="gs-stat-atk">${c.attack}</span>` : ""}
${c.defends != null ? `<span class="gs-stat-def">${c.defends}</span>` : ""}
${c.cooldown != null ? `<span class="gs-stat-cd">${c.cooldown}</span>` : ""}
</div>
`).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 = `
<div style="display:flex;flex-direction:column;align-items:center;gap:4px;">
<span>${gs_combineCards.length} Karte${gs_combineCards.length !== 1 ? "n" : ""} im Stapel</span>
<span style="font-size:15px;font-weight:bold;color:${chanceColor};">${chance}% Erfolgschance</span>
<div style="width:90%;height:6px;background:#2a1a08;border-radius:3px;border:1px solid #6b4b2a;">
<div style="width:${chance}%;height:100%;background:${chanceColor};border-radius:3px;transition:width 0.3s;"></div>
</div>
<span style="font-size:10px;color:#8b6a3c;">${gs_required} Karten = 100% (Gaststätte Lv.${gs_tavernLevel})</span>
</div>`;
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";
}

178
routes/combine.route.js Normal file
View File

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