hmf
This commit is contained in:
parent
f7ba41d31c
commit
a3d4dc9ce8
2
app.js
2
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
|
||||
|
||||
@ -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
178
routes/combine.route.js
Normal 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;
|
||||
Loading…
Reference in New Issue
Block a user