gj,gd
This commit is contained in:
parent
c2a8515a60
commit
f535f1f83c
2
app.js
2
app.js
@ -30,6 +30,7 @@ const boosterRoutes = require("./routes/booster.route");
|
|||||||
const pointsRoutes = require("./routes/points.route");
|
const pointsRoutes = require("./routes/points.route");
|
||||||
const combineRoutes = require("./routes/combine.route");
|
const combineRoutes = require("./routes/combine.route");
|
||||||
const bazaarRoutes = require("./routes/bazaar.route");
|
const bazaarRoutes = require("./routes/bazaar.route");
|
||||||
|
const himmelstorRoutes = require("./routes/himmelstor.route");
|
||||||
|
|
||||||
const compression = require("compression");
|
const compression = require("compression");
|
||||||
|
|
||||||
@ -406,6 +407,7 @@ app.use("/api", require("./routes/daily.route"));
|
|||||||
app.use("/api/points", pointsRoutes);
|
app.use("/api/points", pointsRoutes);
|
||||||
app.use("/api", combineRoutes);
|
app.use("/api", combineRoutes);
|
||||||
app.use("/api", bazaarRoutes);
|
app.use("/api", bazaarRoutes);
|
||||||
|
app.use("/himmelstor", himmelstorRoutes);
|
||||||
|
|
||||||
/* ========================
|
/* ========================
|
||||||
404 Handler
|
404 Handler
|
||||||
|
|||||||
120
public/css/himmelstor.css
Normal file
120
public/css/himmelstor.css
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
/* ================================
|
||||||
|
HIMMELSTOR UI
|
||||||
|
public/css/himmelstor.css
|
||||||
|
================================ */
|
||||||
|
|
||||||
|
#himmelstor-ui {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 32px 20px;
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ht-title {
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #c8a0ff;
|
||||||
|
text-shadow:
|
||||||
|
0 0 8px rgba(155, 114, 207, 0.8),
|
||||||
|
2px 2px 4px rgba(0, 0, 0, 0.9);
|
||||||
|
letter-spacing: 3px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
font-family: "Palatino Linotype", "Book Antiqua", Palatino, serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ht-subtitle {
|
||||||
|
color: #7a6090;
|
||||||
|
font-size: 13px;
|
||||||
|
margin: 0 0 36px 0;
|
||||||
|
font-style: italic;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================================
|
||||||
|
MODE CARDS
|
||||||
|
================================ */
|
||||||
|
|
||||||
|
.ht-modes {
|
||||||
|
display: flex;
|
||||||
|
gap: 24px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ht-mode-card {
|
||||||
|
width: 160px;
|
||||||
|
background: linear-gradient(
|
||||||
|
160deg,
|
||||||
|
rgba(25, 15, 40, 0.95) 0%,
|
||||||
|
rgba(10, 8, 20, 0.98) 100%
|
||||||
|
);
|
||||||
|
border: 2px solid #4a3a6b;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 28px 16px 22px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow:
|
||||||
|
inset 0 1px 0 rgba(155, 114, 207, 0.15),
|
||||||
|
0 4px 16px rgba(0, 0, 0, 0.6);
|
||||||
|
transition:
|
||||||
|
transform 0.15s ease,
|
||||||
|
border-color 0.15s ease,
|
||||||
|
box-shadow 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ornamental top line */
|
||||||
|
.ht-mode-card::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 10%;
|
||||||
|
right: 10%;
|
||||||
|
height: 2px;
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
transparent,
|
||||||
|
#9b72cf,
|
||||||
|
transparent
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ht-mode-card:hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
border-color: #9b72cf;
|
||||||
|
box-shadow:
|
||||||
|
inset 0 1px 0 rgba(155, 114, 207, 0.25),
|
||||||
|
0 8px 24px rgba(0, 0, 0, 0.7),
|
||||||
|
0 0 12px rgba(155, 114, 207, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ht-mode-card:active {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ht-mode-icon {
|
||||||
|
font-size: 36px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
line-height: 1;
|
||||||
|
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.8));
|
||||||
|
}
|
||||||
|
|
||||||
|
.ht-mode-label {
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #c8a0ff;
|
||||||
|
text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.9);
|
||||||
|
letter-spacing: 2px;
|
||||||
|
font-family: "Palatino Linotype", "Book Antiqua", Palatino, serif;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ht-mode-desc {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #6a5080;
|
||||||
|
line-height: 1.5;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
382
public/js/buildings/himmelstor.js
Normal file
382
public/js/buildings/himmelstor.js
Normal file
@ -0,0 +1,382 @@
|
|||||||
|
/* ============================================================
|
||||||
|
public/js/buildings/himmelstor.js
|
||||||
|
Himmelstor – Tages- und Wochenherausforderung
|
||||||
|
Gleiche Struktur wie arena.js, aber Daily & Weekly
|
||||||
|
============================================================ */
|
||||||
|
|
||||||
|
export async function loadHimmelstor() {
|
||||||
|
const ui = document.querySelector(".building-ui");
|
||||||
|
if (!ui) return;
|
||||||
|
|
||||||
|
/* ── Decks laden ─────────────────────────────────────────── */
|
||||||
|
let decks = [];
|
||||||
|
try {
|
||||||
|
const res = await fetch("/api/decks");
|
||||||
|
if (res.ok) decks = await res.json();
|
||||||
|
} catch (e) { console.error("[Himmelstor] Decks:", e); }
|
||||||
|
|
||||||
|
const deckOptions = decks.length === 0
|
||||||
|
? `<option value="">– Erst Deck erstellen –</option>`
|
||||||
|
: `<option value="">– Deck wählen –</option>` +
|
||||||
|
decks.filter(d => d.card_count > 0)
|
||||||
|
.map(d => `<option value="${d.id}">${d.name} (${d.card_count} Karten)</option>`)
|
||||||
|
.join("") +
|
||||||
|
(decks.some(d => d.card_count === 0)
|
||||||
|
? decks.filter(d => d.card_count === 0)
|
||||||
|
.map(d => `<option value="" disabled>⚠ ${d.name} (leer)</option>`)
|
||||||
|
.join("")
|
||||||
|
: "");
|
||||||
|
|
||||||
|
ui.innerHTML = `
|
||||||
|
<div id="himmelstor-ui">
|
||||||
|
<div id="himmelstor-mode-screen">
|
||||||
|
<div class="ht-title">🌤️ Himmelstor</div>
|
||||||
|
<p class="ht-subtitle">Wähle Deck und Herausforderung</p>
|
||||||
|
|
||||||
|
<div class="ht-modes">
|
||||||
|
|
||||||
|
<div class="ht-mode-wrap">
|
||||||
|
<div class="ht-mode-card ht-mode-locked" data-mode="daily">
|
||||||
|
<div class="ht-mode-icon">☀️</div>
|
||||||
|
<div class="ht-mode-label">Daily</div>
|
||||||
|
<div class="ht-mode-desc">Tagesherausforderung</div>
|
||||||
|
</div>
|
||||||
|
<select class="ht-deck-select" data-mode="daily">
|
||||||
|
${deckOptions}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ht-mode-wrap">
|
||||||
|
<div class="ht-mode-card ht-mode-locked" data-mode="weekly">
|
||||||
|
<div class="ht-mode-icon">🌙</div>
|
||||||
|
<div class="ht-mode-label">Weekly</div>
|
||||||
|
<div class="ht-mode-desc">Wochenherausforderung</div>
|
||||||
|
</div>
|
||||||
|
<select class="ht-deck-select" data-mode="weekly">
|
||||||
|
${deckOptions}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${decks.length === 0
|
||||||
|
? `<div class="ht-no-deck-hint">⚠️ Kein Deck vorhanden – gehe zur <strong>Burg → Kartendeck</strong></div>`
|
||||||
|
: ""}
|
||||||
|
|
||||||
|
<div id="ht-queue-status" style="display:none;"></div>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
injectHimmelstorStyles();
|
||||||
|
initHimmelstorModes();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Styles ──────────────────────────────────────────────── */
|
||||||
|
function injectHimmelstorStyles() {
|
||||||
|
if (document.getElementById("ht-popup-styles")) return;
|
||||||
|
const style = document.createElement("style");
|
||||||
|
style.id = "ht-popup-styles";
|
||||||
|
style.textContent = `
|
||||||
|
@keyframes htFadeIn { from{opacity:0} to{opacity:1} }
|
||||||
|
@keyframes htScaleIn { from{transform:scale(0.94);opacity:0} to{transform:scale(1);opacity:1} }
|
||||||
|
@keyframes htPulse { 0%,100%{opacity:1} 50%{opacity:0.5} }
|
||||||
|
|
||||||
|
#himmelstor-ui { display:flex; flex-direction:column; height:100%; }
|
||||||
|
|
||||||
|
#himmelstor-mode-screen {
|
||||||
|
display:flex; flex-direction:column; align-items:center; padding:16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ht-title {
|
||||||
|
font-family:"Cinzel",serif; font-size:18px; color:#f0d060;
|
||||||
|
letter-spacing:3px; text-align:center; margin-bottom:4px;
|
||||||
|
}
|
||||||
|
.ht-subtitle {
|
||||||
|
font-family:"Cinzel",serif; font-size:11px; color:#a08060;
|
||||||
|
margin:0 0 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ht-modes {
|
||||||
|
display:flex; gap:10px; justify-content:center;
|
||||||
|
flex-wrap:wrap; width:100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ht-mode-wrap {
|
||||||
|
flex:1; min-width:120px; max-width:195px;
|
||||||
|
display:flex; flex-direction:column; gap:6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ht-mode-card {
|
||||||
|
flex:1; min-width:120px; max-width:195px;
|
||||||
|
background:linear-gradient(180deg,#1a1a2e,#0f0f1a);
|
||||||
|
border:2px solid #4a3a6b; border-radius:12px;
|
||||||
|
padding:21px 12px; cursor:pointer; text-align:center;
|
||||||
|
transition:border-color .2s, transform .1s;
|
||||||
|
box-shadow: inset 0 1px 0 rgba(160,120,255,0.1), 0 4px 16px rgba(0,0,0,0.6);
|
||||||
|
}
|
||||||
|
.ht-mode-card:hover { border-color:#9b72cf; transform:translateY(-2px); }
|
||||||
|
.ht-mode-card.searching {
|
||||||
|
opacity:.6; pointer-events:none;
|
||||||
|
border-color:rgba(155,114,207,.5) !important;
|
||||||
|
}
|
||||||
|
.ht-mode-locked {
|
||||||
|
opacity:.45; cursor:not-allowed !important;
|
||||||
|
border-color:#2a1e40 !important;
|
||||||
|
}
|
||||||
|
.ht-mode-locked:hover { transform:none !important; border-color:#2a1e40 !important; }
|
||||||
|
|
||||||
|
.ht-mode-icon { font-size:36px; margin-bottom:9px; }
|
||||||
|
.ht-mode-label {
|
||||||
|
font-family:"Cinzel",serif; font-size:19px;
|
||||||
|
color:#c8a0ff; font-weight:bold;
|
||||||
|
}
|
||||||
|
.ht-mode-desc { font-size:15px; color:#6a5080; margin-top:6px; line-height:1.4; }
|
||||||
|
|
||||||
|
.ht-deck-select {
|
||||||
|
width:100%; background:#0f0f1a; border:1px solid #4a3a6b;
|
||||||
|
border-radius:6px; color:#e0d0ff; font-family:"Cinzel",serif;
|
||||||
|
font-size:15px; padding:8px 9px; cursor:pointer;
|
||||||
|
appearance:none; -webkit-appearance:none;
|
||||||
|
background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23806090'/%3E%3C/svg%3E");
|
||||||
|
background-repeat:no-repeat; background-position:right 6px center;
|
||||||
|
padding-right:28px;
|
||||||
|
}
|
||||||
|
.ht-deck-select:focus { outline:none; border-color:#9b72cf; }
|
||||||
|
.ht-deck-select option { background:#0f0f1a; color:#e0d0ff; }
|
||||||
|
|
||||||
|
.ht-no-deck-hint {
|
||||||
|
margin-top:10px; padding:8px 12px; border-radius:7px;
|
||||||
|
background:rgba(231,76,60,.1); border:1px solid rgba(231,76,60,.3);
|
||||||
|
color:#e07060; font-family:"Cinzel",serif; font-size:11px; text-align:center;
|
||||||
|
}
|
||||||
|
.ht-no-deck-hint strong { color:#c8a0ff; }
|
||||||
|
|
||||||
|
#ht-queue-status {
|
||||||
|
margin-top:12px; padding:10px 18px; border-radius:10px;
|
||||||
|
width:100%; box-sizing:border-box;
|
||||||
|
background:rgba(155,114,207,.08); border:1px solid rgba(155,114,207,.3);
|
||||||
|
color:#c8a0ff; font-family:"Cinzel",serif; font-size:12px;
|
||||||
|
letter-spacing:1px; text-align:center; animation:htPulse 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
.ht-cancel {
|
||||||
|
display:inline-block; margin-top:6px; font-size:11px;
|
||||||
|
color:rgba(255,100,100,.8); cursor:pointer; text-decoration:underline;
|
||||||
|
animation:none;
|
||||||
|
}
|
||||||
|
.ht-cancel:hover { color:#e74c3c; }
|
||||||
|
|
||||||
|
/* Match-Found Overlay */
|
||||||
|
#ht-match-found-overlay {
|
||||||
|
position:fixed; inset:0; z-index:10000;
|
||||||
|
background:rgba(0,0,0,.9); display:flex; flex-direction:column;
|
||||||
|
align-items:center; justify-content:center; animation:htFadeIn .3s ease;
|
||||||
|
}
|
||||||
|
.ht-mfo-title {
|
||||||
|
font-family:"Cinzel",serif; font-size:36px; color:#c8a0ff;
|
||||||
|
text-shadow:0 0 30px rgba(155,114,207,.6);
|
||||||
|
letter-spacing:6px; margin-bottom:12px;
|
||||||
|
}
|
||||||
|
.ht-mfo-vs {
|
||||||
|
font-family:"Cinzel",serif; font-size:18px;
|
||||||
|
color:rgba(255,255,255,.75); letter-spacing:3px;
|
||||||
|
}
|
||||||
|
.ht-mfo-bar {
|
||||||
|
width:300px; height:4px; background:rgba(155,114,207,.2);
|
||||||
|
border-radius:2px; margin-top:24px; overflow:hidden;
|
||||||
|
}
|
||||||
|
.ht-mfo-bar-fill {
|
||||||
|
height:100%; background:#9b72cf; width:0%;
|
||||||
|
border-radius:2px; transition:width 1.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Popup iframe */
|
||||||
|
#ht-backdrop {
|
||||||
|
position:fixed; inset:0; background:rgba(0,0,0,.82);
|
||||||
|
backdrop-filter:blur(5px); z-index:9998; animation:htFadeIn .25s ease;
|
||||||
|
}
|
||||||
|
#ht-popup {
|
||||||
|
position:fixed; inset:50px; z-index:9999; display:flex;
|
||||||
|
flex-direction:column; border-radius:14px; overflow:hidden;
|
||||||
|
box-shadow:0 0 0 1px rgba(155,114,207,.35),0 30px 90px rgba(0,0,0,.85);
|
||||||
|
animation:htScaleIn .28s cubic-bezier(.22,1,.36,1);
|
||||||
|
}
|
||||||
|
#ht-popup-titlebar {
|
||||||
|
display:flex; align-items:center; justify-content:space-between;
|
||||||
|
background:rgba(10,8,20,.95); border-bottom:1px solid rgba(155,114,207,.3);
|
||||||
|
padding:0 16px; height:42px; flex-shrink:0;
|
||||||
|
}
|
||||||
|
#ht-popup-titlebar .ht-ap-title {
|
||||||
|
font-family:"Cinzel",serif; font-size:13px; letter-spacing:4px;
|
||||||
|
color:rgba(200,160,255,.85); text-transform:uppercase;
|
||||||
|
}
|
||||||
|
#ht-popup-titlebar .ht-ap-url { font-size:11px; color:rgba(255,255,255,.22); }
|
||||||
|
#ht-popup iframe { flex:1; border:none; width:100%; display:block; }
|
||||||
|
`;
|
||||||
|
document.head.appendChild(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── State ───────────────────────────────────────────────── */
|
||||||
|
let htSelectedDeckId = null;
|
||||||
|
|
||||||
|
/* ── Modus-Initialisierung ───────────────────────────────── */
|
||||||
|
function initHimmelstorModes() {
|
||||||
|
/* Deck-Dropdown: Karte freischalten wenn Deck gewählt */
|
||||||
|
document.querySelectorAll(".ht-deck-select").forEach(select => {
|
||||||
|
select.addEventListener("change", () => {
|
||||||
|
const mode = select.dataset.mode;
|
||||||
|
const card = document.querySelector(`.ht-mode-card[data-mode="${mode}"]`);
|
||||||
|
if (!card) return;
|
||||||
|
if (select.value) {
|
||||||
|
card.classList.remove("ht-mode-locked");
|
||||||
|
htSelectedDeckId = Number(select.value);
|
||||||
|
} else {
|
||||||
|
card.classList.add("ht-mode-locked");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Modus-Karte klicken */
|
||||||
|
document.querySelectorAll(".ht-mode-card").forEach(card => {
|
||||||
|
card.addEventListener("click", () => {
|
||||||
|
if (card.classList.contains("ht-mode-locked")) return;
|
||||||
|
const mode = card.dataset.mode;
|
||||||
|
const select = document.querySelector(`.ht-deck-select[data-mode="${mode}"]`);
|
||||||
|
htSelectedDeckId = select ? Number(select.value) : null;
|
||||||
|
if (!htSelectedDeckId) return;
|
||||||
|
sessionStorage.setItem("selectedDeckId", htSelectedDeckId);
|
||||||
|
handleHtModeClick(card, mode);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Modus klicken ───────────────────────────────────────── */
|
||||||
|
async function handleHtModeClick(card, mode) {
|
||||||
|
if (card.classList.contains("searching")) return;
|
||||||
|
|
||||||
|
let me;
|
||||||
|
try {
|
||||||
|
const res = await fetch("/arena/me");
|
||||||
|
if (!res.ok) throw new Error("Status " + res.status);
|
||||||
|
me = await res.json();
|
||||||
|
} catch {
|
||||||
|
showHtError("Spielerdaten konnten nicht geladen werden.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setHtCardSearching(card, mode, true);
|
||||||
|
showHtQueueStatus(mode);
|
||||||
|
|
||||||
|
const socket = window._socket;
|
||||||
|
if (!socket) { showHtError("Keine Verbindung zum Server."); return; }
|
||||||
|
|
||||||
|
socket.off("ht_match_found");
|
||||||
|
socket.off("ht_queue_status");
|
||||||
|
|
||||||
|
socket.on("ht_queue_status", data => {
|
||||||
|
if (data.status === "waiting") showHtQueueStatus(mode, data.poolSize);
|
||||||
|
else if (data.status === "left") { setHtCardSearching(card, mode, false); hideHtQueueStatus(); }
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.once("ht_match_found", data => {
|
||||||
|
socket.off("ht_queue_status");
|
||||||
|
setHtCardSearching(card, mode, false);
|
||||||
|
hideHtQueueStatus();
|
||||||
|
showHtMatchFoundOverlay(me.name, data.opponent?.name || "Gegner", () => {
|
||||||
|
openHtPopup(
|
||||||
|
`/himmelstor/${mode}?match=${encodeURIComponent(data.matchId)}&slot=${encodeURIComponent(data.mySlot)}&deck=${encodeURIComponent(htSelectedDeckId || '')}&opponent=${encodeURIComponent(data.opponent?.name || '')}`,
|
||||||
|
data.opponent?.name || "Gegner", data.matchId
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.emit("ht_join", {
|
||||||
|
id: me.id, name: me.name, level: me.level, mode,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── UI Hilfsfunktionen ──────────────────────────────────── */
|
||||||
|
const HT_LABELS = { daily: "Daily", weekly: "Weekly" };
|
||||||
|
const HT_DESCS = { daily: "Tagesherausforderung", weekly: "Wochenherausforderung" };
|
||||||
|
|
||||||
|
function setHtCardSearching(card, mode, searching) {
|
||||||
|
const label = card.querySelector(".ht-mode-label");
|
||||||
|
const desc = card.querySelector(".ht-mode-desc");
|
||||||
|
if (searching) {
|
||||||
|
card.classList.add("searching");
|
||||||
|
label.textContent = "⏳ Suche…";
|
||||||
|
desc.textContent = "Warte auf Gegner…";
|
||||||
|
} else {
|
||||||
|
card.classList.remove("searching");
|
||||||
|
label.textContent = HT_LABELS[mode] || mode;
|
||||||
|
desc.textContent = HT_DESCS[mode] || "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showHtQueueStatus(mode, poolSize) {
|
||||||
|
const box = document.getElementById("ht-queue-status");
|
||||||
|
if (!box) return;
|
||||||
|
const pool = poolSize ? ` · ${poolSize} im Pool` : "";
|
||||||
|
const label = mode === "daily" ? "Tagesherausforderung" : "Wochenherausforderung";
|
||||||
|
box.style.display = "block";
|
||||||
|
box.innerHTML = `⏳ Suche ${label}${pool}
|
||||||
|
<br><span class="ht-cancel" id="ht-cancel-btn">Suche abbrechen</span>`;
|
||||||
|
document.getElementById("ht-cancel-btn")?.addEventListener("click", () => {
|
||||||
|
cancelHtQueue(document.querySelector(`.ht-mode-card[data-mode="${mode}"]`), mode);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideHtQueueStatus() {
|
||||||
|
const box = document.getElementById("ht-queue-status");
|
||||||
|
if (box) box.style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
function showHtError(msg) {
|
||||||
|
const box = document.getElementById("ht-queue-status");
|
||||||
|
if (!box) return;
|
||||||
|
box.style.cssText += ";display:block;animation:none;border-color:rgba(231,76,60,.5);color:#e74c3c;";
|
||||||
|
box.textContent = "❌ " + msg;
|
||||||
|
setTimeout(() => {
|
||||||
|
box.style.display = "none";
|
||||||
|
box.style.animation = "";
|
||||||
|
box.style.borderColor = "";
|
||||||
|
box.style.color = "";
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelHtQueue(card, mode) {
|
||||||
|
const socket = window._socket;
|
||||||
|
if (socket) { socket.emit("ht_leave", { mode }); socket.off("ht_match_found"); socket.off("ht_queue_status"); }
|
||||||
|
if (card) setHtCardSearching(card, mode, false);
|
||||||
|
hideHtQueueStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
function showHtMatchFoundOverlay(myName, opponentName, onDone) {
|
||||||
|
if (document.getElementById("ht-match-found-overlay")) return;
|
||||||
|
const overlay = document.createElement("div");
|
||||||
|
overlay.id = "ht-match-found-overlay";
|
||||||
|
overlay.innerHTML = `
|
||||||
|
<div class="ht-mfo-title">🌤️ Herausforderung!</div>
|
||||||
|
<div class="ht-mfo-vs">${myName} vs ${opponentName}</div>
|
||||||
|
<div class="ht-mfo-bar"><div class="ht-mfo-bar-fill" id="htMfBar"></div></div>`;
|
||||||
|
document.body.appendChild(overlay);
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
const b = document.getElementById("htMfBar");
|
||||||
|
if (b) b.style.width = "100%";
|
||||||
|
});
|
||||||
|
setTimeout(() => { overlay.remove(); onDone(); }, 1600);
|
||||||
|
}
|
||||||
|
|
||||||
|
function openHtPopup(src, opponentName, matchId) {
|
||||||
|
document.getElementById("ht-backdrop")?.remove();
|
||||||
|
document.getElementById("ht-popup")?.remove();
|
||||||
|
const backdrop = document.createElement("div"); backdrop.id = "ht-backdrop";
|
||||||
|
const popup = document.createElement("div"); popup.id = "ht-popup";
|
||||||
|
popup.innerHTML = `
|
||||||
|
<div id="ht-popup-titlebar">
|
||||||
|
<span class="ht-ap-title">🌤️ Himmelstor · vs ${opponentName}</span>
|
||||||
|
<span class="ht-ap-url">${matchId || src}</span>
|
||||||
|
</div>
|
||||||
|
<iframe src="${src}" allowfullscreen></iframe>`;
|
||||||
|
document.body.appendChild(backdrop);
|
||||||
|
document.body.appendChild(popup);
|
||||||
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import { loadArena } from "./buildings/arena.js?v=4";
|
import { loadArena } from "./buildings/arena.js?v=4";
|
||||||
|
import { loadHimmelstor } from "./buildings/himmelstor.js";
|
||||||
import { loadCharacterHouse } from "./buildings/character-house.js?v=2";
|
import { loadCharacterHouse } from "./buildings/character-house.js?v=2";
|
||||||
import { loadBlackmarket } from "./buildings/blackmarket.js?v=2";
|
import { loadBlackmarket } from "./buildings/blackmarket.js?v=2";
|
||||||
import { loadMine } from "./buildings/mine.js?v=2";
|
import { loadMine } from "./buildings/mine.js?v=2";
|
||||||
@ -48,7 +49,8 @@ document.getElementById("qm-overlay")?.addEventListener("mouseenter", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const buildingModules = {
|
const buildingModules = {
|
||||||
1: loadArena, // Arena – eigenes UI, Tabs ausblenden
|
1: loadArena, // Arena – eigenes UI, Tabs ausblenden
|
||||||
|
3: loadHimmelstor, // Himmelstor – eigenes UI, Tabs ausblenden
|
||||||
10: loadMine, // Mine – Tabs bleiben sichtbar
|
10: loadMine, // Mine – Tabs bleiben sichtbar
|
||||||
11: loadCharacterHouse, // Charakterhaus – eigenes UI, Tabs ausblenden
|
11: loadCharacterHouse, // Charakterhaus – eigenes UI, Tabs ausblenden
|
||||||
12: loadBlackmarket, // Schwarzmarkt – eigenes UI, Tabs ausblenden
|
12: loadBlackmarket, // Schwarzmarkt – eigenes UI, Tabs ausblenden
|
||||||
|
|||||||
112
routes/himmelstor.route.js
Normal file
112
routes/himmelstor.route.js
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
/* ============================================================
|
||||||
|
routes/himmelstor.route.js
|
||||||
|
GET /himmelstor/daily – Tagesherausforderung Spielfeld
|
||||||
|
GET /himmelstor/weekly – Wochenherausforderung Spielfeld
|
||||||
|
============================================================ */
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* HP-Formel: 20 + (level-1)*2 */
|
||||||
|
function calcAvatarHp(level) {
|
||||||
|
return 20 + (Math.max(1, Math.min(50, level || 1)) - 1) * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getPlayerHp(userId) {
|
||||||
|
try {
|
||||||
|
const [[acc]] = await db.query("SELECT level FROM accounts WHERE id = ?", [userId]);
|
||||||
|
return calcAvatarHp(acc?.level ?? 1);
|
||||||
|
} catch {
|
||||||
|
return 20;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── GET /himmelstor/daily ─────────────────────────────── */
|
||||||
|
router.get("/daily", requireLogin, async (req, res) => {
|
||||||
|
const userId = req.session.user.id;
|
||||||
|
const { match: matchId, slot } = req.query;
|
||||||
|
|
||||||
|
if (!matchId) {
|
||||||
|
return res.render("1v1-battlefield", {
|
||||||
|
title: "☀️ Daily Herausforderung",
|
||||||
|
matchId: null,
|
||||||
|
mySlot: "player1",
|
||||||
|
player1: req.session?.user?.ingame_name || "Spieler 1",
|
||||||
|
player2: "Gegner",
|
||||||
|
player1hp: 20,
|
||||||
|
player1mana: 3,
|
||||||
|
player2hp: 20,
|
||||||
|
player2mana: 3,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [[me]] = await db.query("SELECT ingame_name FROM accounts WHERE id = ?", [userId]);
|
||||||
|
const hp = await getPlayerHp(userId);
|
||||||
|
const isP1 = slot === "player1";
|
||||||
|
|
||||||
|
res.render("1v1-battlefield", {
|
||||||
|
title: "☀️ Daily Herausforderung",
|
||||||
|
matchId,
|
||||||
|
mySlot: slot || "player1",
|
||||||
|
player1: isP1 ? (me?.ingame_name || "Du") : "Gegner",
|
||||||
|
player2: isP1 ? "Gegner" : (me?.ingame_name || "Du"),
|
||||||
|
player1hp: isP1 ? hp : 20,
|
||||||
|
player1mana: 3,
|
||||||
|
player2hp: isP1 ? 20 : hp,
|
||||||
|
player2mana: 3,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error("[Himmelstor /daily]", err);
|
||||||
|
res.status(500).send("Fehler beim Laden des Spielfelds.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/* ── GET /himmelstor/weekly ────────────────────────────── */
|
||||||
|
router.get("/weekly", requireLogin, async (req, res) => {
|
||||||
|
const userId = req.session.user.id;
|
||||||
|
const { match: matchId, slot } = req.query;
|
||||||
|
|
||||||
|
if (!matchId) {
|
||||||
|
return res.render("1v1-battlefield", {
|
||||||
|
title: "🌙 Weekly Herausforderung",
|
||||||
|
matchId: null,
|
||||||
|
mySlot: "player1",
|
||||||
|
player1: req.session?.user?.ingame_name || "Spieler 1",
|
||||||
|
player2: "Gegner",
|
||||||
|
player1hp: 20,
|
||||||
|
player1mana: 3,
|
||||||
|
player2hp: 20,
|
||||||
|
player2mana: 3,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [[me]] = await db.query("SELECT ingame_name FROM accounts WHERE id = ?", [userId]);
|
||||||
|
const hp = await getPlayerHp(userId);
|
||||||
|
const isP1 = slot === "player1";
|
||||||
|
|
||||||
|
res.render("1v1-battlefield", {
|
||||||
|
title: "🌙 Weekly Herausforderung",
|
||||||
|
matchId,
|
||||||
|
mySlot: slot || "player1",
|
||||||
|
player1: isP1 ? (me?.ingame_name || "Du") : "Gegner",
|
||||||
|
player2: isP1 ? "Gegner" : (me?.ingame_name || "Du"),
|
||||||
|
player1hp: isP1 ? hp : 20,
|
||||||
|
player1mana: 3,
|
||||||
|
player2hp: isP1 ? 20 : hp,
|
||||||
|
player2mana: 3,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error("[Himmelstor /weekly]", err);
|
||||||
|
res.status(500).send("Fehler beim Laden des Spielfelds.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
@ -93,9 +93,13 @@
|
|||||||
<link rel="stylesheet" href="/css/popup.css" />
|
<link rel="stylesheet" href="/css/popup.css" />
|
||||||
<link rel="stylesheet" href="/css/quickmenu.css" />
|
<link rel="stylesheet" href="/css/quickmenu.css" />
|
||||||
<link rel="stylesheet" href="/css/gaststaette.css" />
|
<link rel="stylesheet" href="/css/gaststaette.css" />
|
||||||
|
<link rel="stylesheet" href="/css/himmelstor.css" />
|
||||||
<link rel="stylesheet" href="/css/events.css" />
|
<link rel="stylesheet" href="/css/events.css" />
|
||||||
<link rel="stylesheet" href="/css/hud.css" />
|
<link rel="stylesheet" href="/css/hud.css" />
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" />
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css"
|
||||||
|
/>
|
||||||
<link rel="stylesheet" href="/css/mine.css" />
|
<link rel="stylesheet" href="/css/mine.css" />
|
||||||
<script src="/js/heartbeat.js"></script>
|
<script src="/js/heartbeat.js"></script>
|
||||||
<script src="/js/map-scale.js"></script>
|
<script src="/js/map-scale.js"></script>
|
||||||
@ -656,12 +660,16 @@
|
|||||||
<div id="hud-currency">
|
<div id="hud-currency">
|
||||||
<div class="hud-res-row">
|
<div class="hud-res-row">
|
||||||
<div class="hud-res">
|
<div class="hud-res">
|
||||||
<span class="hud-res-icon"><i class="fa-solid fa-gem" style="color:#4fc3f7;"></i></span>
|
<span class="hud-res-icon"
|
||||||
|
><i class="fa-solid fa-gem" style="color: #4fc3f7"></i
|
||||||
|
></span>
|
||||||
<span class="hud-res-value" id="hud-gems">0</span>
|
<span class="hud-res-value" id="hud-gems">0</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="hud-sep"></div>
|
<div class="hud-sep"></div>
|
||||||
<div class="hud-res">
|
<div class="hud-res">
|
||||||
<span class="hud-res-icon"><i class="fa-solid fa-coins" style="color:#f0c040;"></i></span>
|
<span class="hud-res-icon"
|
||||||
|
><i class="fa-solid fa-coins" style="color: #f0c040"></i
|
||||||
|
></span>
|
||||||
<span class="hud-res-value" id="hud-gold">0</span>
|
<span class="hud-res-value" id="hud-gold">0</span>
|
||||||
</div>
|
</div>
|
||||||
<button id="hud-gold-btn">Shop</button>
|
<button id="hud-gold-btn">Shop</button>
|
||||||
@ -669,12 +677,16 @@
|
|||||||
|
|
||||||
<div class="hud-res-row">
|
<div class="hud-res-row">
|
||||||
<div class="hud-res">
|
<div class="hud-res">
|
||||||
<span class="hud-res-icon"><i class="fa-solid fa-tree" style="color:#66bb6a;"></i></span>
|
<span class="hud-res-icon"
|
||||||
|
><i class="fa-solid fa-tree" style="color: #66bb6a"></i
|
||||||
|
></span>
|
||||||
<span class="hud-res-value" id="hud-wood">0</span>
|
<span class="hud-res-value" id="hud-wood">0</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="hud-sep"></div>
|
<div class="hud-sep"></div>
|
||||||
<div class="hud-res">
|
<div class="hud-res">
|
||||||
<span class="hud-res-icon"><i class="fa-solid fa-mountain" style="color:#a0a0a0;"></i></span>
|
<span class="hud-res-icon"
|
||||||
|
><i class="fa-solid fa-mountain" style="color: #a0a0a0"></i
|
||||||
|
></span>
|
||||||
<span class="hud-res-value" id="hud-stone">0</span>
|
<span class="hud-res-value" id="hud-stone">0</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -796,15 +808,25 @@
|
|||||||
<div class="gaststaette-body">
|
<div class="gaststaette-body">
|
||||||
<!-- Linke Tab-Sidebar -->
|
<!-- Linke Tab-Sidebar -->
|
||||||
<div class="gaststaette-tabs">
|
<div class="gaststaette-tabs">
|
||||||
<button class="gaststaette-tab active" data-tab="gs-tab1">Glücksspiel</button>
|
<button class="gaststaette-tab active" data-tab="gs-tab1">
|
||||||
|
Glücksspiel
|
||||||
|
</button>
|
||||||
<button class="gaststaette-tab" data-tab="gs-tab2">Dummy 2</button>
|
<button class="gaststaette-tab" data-tab="gs-tab2">Dummy 2</button>
|
||||||
<button class="gaststaette-tab" data-tab="gs-tab3">Dummy 3</button>
|
<button class="gaststaette-tab" data-tab="gs-tab3">Dummy 3</button>
|
||||||
</div>
|
</div>
|
||||||
<!-- Hauptinhalt -->
|
<!-- Hauptinhalt -->
|
||||||
<div class="gaststaette-content">
|
<div class="gaststaette-content">
|
||||||
<div class="gaststaette-tab-content active" id="gs-tab1"></div>
|
<div class="gaststaette-tab-content active" id="gs-tab1"></div>
|
||||||
<div class="gaststaette-tab-content" id="gs-tab2"><p style="color:#5c3b20;font-family:'Cinzel',serif;">Inhalt folgt...</p></div>
|
<div class="gaststaette-tab-content" id="gs-tab2">
|
||||||
<div class="gaststaette-tab-content" id="gs-tab3"><p style="color:#5c3b20;font-family:'Cinzel',serif;">Inhalt folgt...</p></div>
|
<p style="color: #5c3b20; font-family: "Cinzel", serif">
|
||||||
|
Inhalt folgt...
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="gaststaette-tab-content" id="gs-tab3">
|
||||||
|
<p style="color: #5c3b20; font-family: "Cinzel", serif">
|
||||||
|
Inhalt folgt...
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -898,7 +920,9 @@
|
|||||||
<span id="shop-title">💠 Gems kaufen</span>
|
<span id="shop-title">💠 Gems kaufen</span>
|
||||||
<span id="shop-close">✕</span>
|
<span id="shop-close">✕</span>
|
||||||
</div>
|
</div>
|
||||||
<div id="shop-subtitle">Unterstütze das Spiel und erhalte wertvolle Gems!</div>
|
<div id="shop-subtitle">
|
||||||
|
Unterstütze das Spiel und erhalte wertvolle Gems!
|
||||||
|
</div>
|
||||||
<div id="shop-grid"></div>
|
<div id="shop-grid"></div>
|
||||||
<div id="shop-footer">
|
<div id="shop-footer">
|
||||||
Sichere Zahlung via Stripe · Kreditkarte & PayPal akzeptiert
|
Sichere Zahlung via Stripe · Kreditkarte & PayPal akzeptiert
|
||||||
@ -1039,13 +1063,18 @@
|
|||||||
loadHud();
|
loadHud();
|
||||||
|
|
||||||
// Shop-Button
|
// Shop-Button
|
||||||
document.getElementById("hud-gold-btn")
|
document
|
||||||
|
.getElementById("hud-gold-btn")
|
||||||
?.addEventListener("click", openShop);
|
?.addEventListener("click", openShop);
|
||||||
|
|
||||||
// Stripe Rückkehr-Benachrichtigung
|
// Stripe Rückkehr-Benachrichtigung
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
if (params.get("payment") === "success") {
|
if (params.get("payment") === "success") {
|
||||||
window.showNotification("Zahlung erfolgreich! Deine Gems wurden gutgeschrieben. 💠", "Shop", "💠");
|
window.showNotification(
|
||||||
|
"Zahlung erfolgreich! Deine Gems wurden gutgeschrieben. 💠",
|
||||||
|
"Shop",
|
||||||
|
"💠",
|
||||||
|
);
|
||||||
history.replaceState({}, "", "/launcher");
|
history.replaceState({}, "", "/launcher");
|
||||||
loadHud(); // HUD sofort aktualisieren
|
loadHud(); // HUD sofort aktualisieren
|
||||||
} else if (params.get("payment") === "cancel") {
|
} else if (params.get("payment") === "cancel") {
|
||||||
@ -1079,16 +1108,16 @@
|
|||||||
<script>
|
<script>
|
||||||
(function () {
|
(function () {
|
||||||
const chat = document.getElementById("game-chat");
|
const chat = document.getElementById("game-chat");
|
||||||
const btn = document.getElementById("chat-toggle");
|
const btn = document.getElementById("chat-toggle");
|
||||||
let hidden = false;
|
let hidden = false;
|
||||||
|
|
||||||
btn.addEventListener("click", () => {
|
btn.addEventListener("click", () => {
|
||||||
hidden = !hidden;
|
hidden = !hidden;
|
||||||
chat.classList.toggle("chat-collapsed", hidden);
|
chat.classList.toggle("chat-collapsed", hidden);
|
||||||
btn.textContent = hidden ? "▶" : "◀";
|
btn.textContent = hidden ? "▶" : "◀";
|
||||||
btn.title = hidden ? "Chat einblenden" : "Chat ausblenden";
|
btn.title = hidden ? "Chat einblenden" : "Chat ausblenden";
|
||||||
// Button wandert mit: rechts vom Chat (273px) oder am Rand (20px)
|
// Button wandert mit: rechts vom Chat (273px) oder am Rand (20px)
|
||||||
btn.style.left = hidden ? "20px" : "273px";
|
btn.style.left = hidden ? "20px" : "273px";
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user