437 lines
13 KiB
JavaScript
437 lines
13 KiB
JavaScript
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} vs ${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 · vs ${opponentName}`
|
||
: "⚔️ Arena · 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);
|
||
}
|