dok/public/js/buildings/arena.js
2026-03-18 12:05:20 +00:00

437 lines
13 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

export async function loadArena() {
const ui = document.querySelector(".building-ui");
ui.innerHTML = `
<div id="arena-ui">
<div class="arena-title">⚔️ Kampfarena</div>
<p class="arena-subtitle">Wähle deinen Kampfmodus</p>
<div class="arena-modes">
<div class="arena-mode-card" data-mode="1v1">
<div class="arena-mode-icon">🗡️</div>
<div class="arena-mode-label">1v1</div>
<div class="arena-mode-desc">Einzelkampf Beweis deine Stärke im Duell</div>
</div>
<div class="arena-mode-card" data-mode="2v2">
<div class="arena-mode-icon">⚔️</div>
<div class="arena-mode-label">2v2</div>
<div class="arena-mode-desc">Verbünde dich mit einem Kameraden im Kampf</div>
</div>
<div class="arena-mode-card" data-mode="4v4">
<div class="arena-mode-icon">🛡️</div>
<div class="arena-mode-label">4v4</div>
<div class="arena-mode-desc">Schlachtruf Führe deine Truppe zum Sieg</div>
</div>
</div>
<!-- Status-Anzeige (erscheint nur während Suche) -->
<div id="arena-queue-status" style="display:none;"></div>
</div>
`;
injectArenaStyles();
initArenaModes();
}
/* ── Styles ────────────────────────────────────────────────────────────────── */
function injectArenaStyles() {
if (document.getElementById("arena-popup-styles")) return;
const style = document.createElement("style");
style.id = "arena-popup-styles";
style.textContent = `
@keyframes arenaFadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes arenaScaleIn {
from { transform: scale(0.94); opacity: 0; }
to { transform: scale(1); opacity: 1; }
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
/* ── Queue Status Box ── */
#arena-queue-status {
margin-top: 18px;
padding: 12px 20px;
border-radius: 10px;
background: rgba(255, 215, 80, 0.08);
border: 1px solid rgba(255, 215, 80, 0.3);
color: #1a1a1a;
font-family: "Cinzel", serif;
font-size: 13px;
letter-spacing: 1px;
text-align: center;
animation: pulse 2s ease-in-out infinite;
}
#arena-queue-status .qs-cancel {
display: inline-block;
margin-top: 8px;
font-size: 11px;
color: rgba(255,100,100,0.8);
cursor: pointer;
text-decoration: underline;
animation: none;
}
#arena-queue-status .qs-cancel:hover {
color: #e74c3c;
}
/* ── Backdrop ── */
#arena-backdrop {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.82);
backdrop-filter: blur(5px);
z-index: 9998;
animation: arenaFadeIn 0.25s ease;
}
/* ── Popup ── */
#arena-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(255, 215, 80, 0.35),
0 30px 90px rgba(0, 0, 0, 0.85),
0 0 60px rgba(255, 215, 80, 0.08);
animation: arenaScaleIn 0.28s cubic-bezier(0.22, 1, 0.36, 1);
}
/* ── Titelleiste ── */
#arena-popup-titlebar {
display: flex;
align-items: center;
justify-content: space-between;
background: rgba(10, 8, 5, 0.95);
border-bottom: 1px solid rgba(255, 215, 80, 0.3);
padding: 0 16px;
height: 42px;
flex-shrink: 0;
user-select: none;
}
#arena-popup-titlebar .ap-left {
display: flex;
align-items: center;
gap: 10px;
}
#arena-popup-titlebar .ap-dots {
display: flex;
gap: 7px;
}
#arena-popup-titlebar .ap-dot {
width: 13px;
height: 13px;
border-radius: 50%;
cursor: pointer;
transition: filter 0.15s;
}
#arena-popup-titlebar .ap-dot:hover { filter: brightness(1.3); }
#arena-popup-titlebar .ap-dot.close { background: #e74c3c; border: 1px solid rgba(0,0,0,0.25); }
#arena-popup-titlebar .ap-dot.min { background: #f1c40f; border: 1px solid rgba(0,0,0,0.25); }
#arena-popup-titlebar .ap-dot.expand { background: #2ecc71; border: 1px solid rgba(0,0,0,0.25); }
#arena-popup-titlebar .ap-title {
font-family: "Cinzel", serif;
font-size: 13px;
letter-spacing: 4px;
color: rgba(255, 215, 80, 0.85);
text-transform: uppercase;
text-shadow: 0 1px 8px rgba(0,0,0,0.8);
}
#arena-popup-titlebar .ap-url {
font-size: 11px;
color: rgba(255,255,255,0.22);
letter-spacing: 1px;
}
/* ── Match-Found Overlay ── */
#match-found-overlay {
position: fixed;
inset: 0;
z-index: 10000;
background: rgba(0,0,0,0.9);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
animation: arenaFadeIn 0.3s ease;
}
#match-found-overlay .mfo-title {
font-family: "Cinzel", serif;
font-size: 36px;
color: #ffd750;
text-shadow: 0 0 30px rgba(255,215,80,0.6);
letter-spacing: 6px;
margin-bottom: 12px;
}
#match-found-overlay .mfo-vs {
font-family: "Cinzel", serif;
font-size: 18px;
color: rgba(255,255,255,0.75);
letter-spacing: 3px;
}
#match-found-overlay .mfo-bar {
width: 300px;
height: 4px;
background: rgba(255,215,80,0.2);
border-radius: 2px;
margin-top: 24px;
overflow: hidden;
}
#match-found-overlay .mfo-bar-fill {
height: 100%;
background: #ffd750;
width: 0%;
border-radius: 2px;
transition: width 1.5s ease;
}
/* ── iframe ── */
#arena-popup iframe {
flex: 1;
border: none;
width: 100%;
display: block;
}
/* ── Card: gesucht-Zustand ── */
.arena-mode-card.searching {
opacity: 0.6;
pointer-events: none;
border-color: rgba(255,215,80,0.5) !important;
}
`;
document.head.appendChild(style);
}
/* ── Socket-Referenz holen ─────────────────────────────────────────────────── */
function getSocket() {
// Wird von der Haupt-App als window._socket bereitgestellt
return window._socket || null;
}
/* ── Klick-Handler initialisieren ─────────────────────────────────────────── */
function initArenaModes() {
document.querySelectorAll(".arena-mode-card").forEach((card) => {
card.addEventListener("click", () => {
const mode = card.dataset.mode;
if (mode === "1v1") {
handle1v1Click(card);
} else {
console.log("Arena Modus gewählt:", mode);
// Platzhalter für 2v2 / 4v4
}
});
});
}
/* ── 1v1: Hauptlogik ───────────────────────────────────────────────────────── */
async function handle1v1Click(card) {
const socket = getSocket();
if (!socket) {
showArenaError("Keine Verbindung zum Server. Bitte Seite neu laden.");
return;
}
// Bereits in Suche?
if (card.classList.contains("searching")) return;
// Spielerdaten laden
let me;
try {
const res = await fetch("/arena/me");
if (!res.ok) throw new Error("Status " + res.status);
me = await res.json();
} catch (err) {
console.error("[1v1] Spielerdaten konnten nicht geladen werden:", err);
showArenaError("Spielerdaten konnten nicht geladen werden.");
return;
}
// UI: Suche läuft
setCardSearching(card, true);
showQueueStatus(me.level);
// Sicherstellen, dass keine alten Listener hängen
socket.off("match_found");
socket.off("queue_status");
// Queue-Status empfangen
socket.on("queue_status", (data) => {
if (data.status === "waiting") {
showQueueStatus(me.level, data.poolSize);
} else if (data.status === "left") {
setCardSearching(card, false);
hideQueueStatus();
}
});
// Match gefunden
socket.once("match_found", (data) => {
socket.off("queue_status");
setCardSearching(card, false);
hideQueueStatus();
showMatchFoundOverlay(me.name, data.opponent.name, () => {
openArenaPopup(
`/arena/1v1?match=${encodeURIComponent(data.matchId)}&slot=${encodeURIComponent(data.mySlot)}`,
data.opponent.name,
data.matchId,
);
});
});
// Matchmaking starten
socket.emit("join_1v1", {
id: me.id,
name: me.name,
level: me.level,
});
}
/* ── Queue abbrechen ───────────────────────────────────────────────────────── */
function cancelQueue(card) {
const socket = getSocket();
if (socket) {
socket.emit("leave_1v1");
socket.off("match_found");
socket.off("queue_status");
}
setCardSearching(card, false);
hideQueueStatus();
}
/* ── Match-Found Splash ────────────────────────────────────────────────────── */
function showMatchFoundOverlay(myName, opponentName, onDone) {
// Verhindert doppeltes Öffnen
if (document.getElementById("match-found-overlay")) return;
const overlay = document.createElement("div");
overlay.id = "match-found-overlay";
overlay.innerHTML = `
<div class="mfo-title">⚔️ Match gefunden!</div>
<div class="mfo-vs">${myName} &nbsp;vs&nbsp; ${opponentName}</div>
<div class="mfo-bar"><div class="mfo-bar-fill" id="mfBar"></div></div>
`;
document.body.appendChild(overlay);
// Ladebalken animieren
requestAnimationFrame(() => {
const bar = document.getElementById("mfBar");
if (bar) bar.style.width = "100%";
});
// Nach 1.6s zum Spielfeld
setTimeout(() => {
overlay.remove();
onDone();
}, 1600);
}
/* ── Popup öffnen ──────────────────────────────────────────────────────────── */
function openArenaPopup(src, opponentName, matchId) {
// Vorhandenen Popup schließen (falls mehrfach)
document.getElementById("arena-backdrop")?.remove();
document.getElementById("arena-popup")?.remove();
const backdrop = document.createElement("div");
backdrop.id = "arena-backdrop";
const popup = document.createElement("div");
popup.id = "arena-popup";
const title = opponentName
? `⚔️ 1v1 &nbsp;·&nbsp; vs ${opponentName}`
: "⚔️ Arena &nbsp;·&nbsp; 1v1";
popup.innerHTML = `
<div id="arena-popup-titlebar">
<div class="ap-left">
<span class="ap-title">${title}</span>
</div>
<span class="ap-url">${matchId || src}</span>
</div>
<iframe src="${src}" allowfullscreen></iframe>
`;
document.body.appendChild(backdrop);
document.body.appendChild(popup);
// Backdrop-Klick deaktiviert Fenster nur über Aufgeben/Spielende schließbar
}
/* ── UI Hilfsfunktionen ────────────────────────────────────────────────────── */
function setCardSearching(card, searching) {
const label = card.querySelector(".arena-mode-label");
const desc = card.querySelector(".arena-mode-desc");
if (searching) {
card.classList.add("searching");
label.textContent = "⏳ Suche…";
desc.textContent = "Warte auf passenden Gegner…";
} else {
card.classList.remove("searching");
label.textContent = "1v1";
desc.textContent = "Einzelkampf Beweis deine Stärke im Duell";
}
}
function showQueueStatus(myLevel, poolSize) {
const box = document.getElementById("arena-queue-status");
if (!box) return;
const range = 5;
const min = Math.max(1, myLevel - range);
const max = myLevel + range;
const pool = poolSize ? ` · ${poolSize} Spieler im Pool` : "";
box.style.display = "block";
box.innerHTML = `
⏳ Suche Gegner (Level ${min}${max})${pool}
<br>
<span class="qs-cancel" id="qs-cancel-btn">Suche abbrechen</span>
`;
// Cancel-Button Card-Referenz über data-attribute
document.getElementById("qs-cancel-btn")?.addEventListener("click", () => {
const card = document.querySelector(".arena-mode-card[data-mode='1v1']");
if (card) cancelQueue(card);
});
}
function hideQueueStatus() {
const box = document.getElementById("arena-queue-status");
if (box) box.style.display = "none";
}
function showArenaError(msg) {
const box = document.getElementById("arena-queue-status");
if (!box) return;
box.style.display = "block";
box.style.animation = "none";
box.style.borderColor = "rgba(231,76,60,0.5)";
box.style.color = "#e74c3c";
box.textContent = "❌ " + msg;
setTimeout(() => {
box.style.display = "none";
box.style.animation = "";
box.style.borderColor = "";
box.style.color = "";
}, 3000);
}