dok/public/js/buildings/arena.js
2026-04-08 19:13:50 +01:00

780 lines
25 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">
<!-- Modus-Auswahl -->
<div id="arena-mode-screen">
<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>
<div id="arena-queue-status" style="display:none;"></div>
</div>
<!-- 2v2 Lobby-Screen -->
<div id="arena-2v2-screen" style="display:none;">
<div class="arena-lobby-header">
<button class="arena-back-btn" id="arena-2v2-back">← Zurück</button>
<div class="arena-title" style="margin:0;">⚔️ 2v2 Team-Lobby</div>
</div>
<!-- Fehlermeldung auf dem 2v2-Screen -->
<div id="arena-2v2-error" style="display:none;"></div>
<!-- Team-Panel (erscheint nach Beitritt) -->
<div id="arena-team-panel" style="display:none;">
<div class="arena-team-box">
<div class="arena-team-title">Dein Team</div>
<div id="arena-team-players"></div>
<div id="arena-team-actions"></div>
</div>
<div id="arena-team-status"></div>
</div>
<!-- Lobby-Liste -->
<div id="arena-lobby-section">
<div class="arena-lobby-actions">
<button class="arena-btn-create" id="arena-create-team-btn"> Eigenes Team erstellen</button>
</div>
<div class="arena-lobby-title">Offene Teams</div>
<div id="arena-lobby-list"><div class="arena-lobby-empty">Keine offenen Teams vorhanden.</div></div>
</div>
</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: #dceb15;
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; }
/* ── 2v2 Fehlermeldung ── */
#arena-2v2-error {
padding: 10px 14px;
border-radius: 8px;
background: rgba(231,76,60,0.12);
border: 1px solid rgba(231,76,60,0.4);
color: #e74c3c;
font-family: "Cinzel", serif;
font-size: 12px;
text-align: center;
margin-bottom: 8px;
}
/* ── 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-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;
}
/* ── 2v2 Lobby Screen ── */
#arena-2v2-screen {
flex-direction: column;
gap: 14px;
padding: 16px;
height: 100%;
overflow-y: auto;
}
.arena-lobby-header {
display: flex;
align-items: center;
gap: 16px;
}
.arena-back-btn {
background: none;
border: 1px solid rgba(255,200,80,0.3);
color: #c8960c;
font-family: "Cinzel", serif;
font-size: 12px;
padding: 4px 12px;
border-radius: 4px;
cursor: pointer;
}
.arena-back-btn:hover { background: rgba(200,150,12,0.15); }
.arena-lobby-actions { display: flex; gap: 10px; margin-bottom: 4px; }
.arena-btn-create {
background: linear-gradient(#4a3018, #2a1a08);
border: 2px solid #8b6a3c;
border-radius: 7px;
color: #f0d9a6;
font-family: "Cinzel", serif;
font-size: 12px;
padding: 8px 16px;
cursor: pointer;
transition: 0.2s;
}
.arena-btn-create:hover { border-color: #f0d060; }
.arena-lobby-title {
font-family: "Cinzel", serif;
font-size: 13px;
color: #a08060;
letter-spacing: 1px;
margin-bottom: 6px;
}
.arena-lobby-empty {
font-family: "Cinzel", serif;
font-size: 12px;
color: #606060;
padding: 12px 0;
}
.arena-lobby-row {
display: flex;
align-items: center;
justify-content: space-between;
background: linear-gradient(#2a1a08, #1a0f04);
border: 1px solid #6b4b2a;
border-radius: 8px;
padding: 10px 14px;
margin-bottom: 6px;
}
.arena-lobby-row-info { display: flex; align-items: center; gap: 12px; }
.arena-lobby-leader { font-family: "Cinzel", serif; font-size: 13px; color: #f0d9a6; }
.arena-lobby-level { font-size: 11px; color: #a08060; }
.arena-lobby-count { font-size: 11px; color: #6a9a4a; }
.arena-btn-join {
background: linear-gradient(#1a4a18, #0f2a0e);
border: 2px solid #4a8a3c;
border-radius: 6px;
color: #a0e090;
font-family: "Cinzel", serif;
font-size: 11px;
padding: 5px 12px;
cursor: pointer;
transition: 0.2s;
}
.arena-btn-join:hover { border-color: #8ae060; color: #c0f0a0; }
/* ── Team-Panel ── */
.arena-team-box {
background: linear-gradient(#2a1a08, #1a0f04);
border: 2px solid #6b4b2a;
border-radius: 10px;
padding: 14px;
}
.arena-team-title {
font-family: "Cinzel", serif;
font-size: 13px;
color: #f0d060;
letter-spacing: 2px;
margin-bottom: 10px;
}
.arena-team-player {
display: flex;
align-items: center;
gap: 10px;
padding: 7px 10px;
border: 1px solid #3a2810;
border-radius: 6px;
margin-bottom: 5px;
background: rgba(255,255,255,0.03);
}
.arena-team-player.ready { border-color: #4a8a3c; background: rgba(74,138,60,0.1); }
.arena-team-player-name { font-family: "Cinzel", serif; font-size: 12px; color: #f0d9a6; flex: 1; }
.arena-team-player-level { font-size: 11px; color: #a08060; }
.arena-team-player-status { font-size: 11px; }
.arena-waiting-partner {
font-family: "Cinzel", serif;
font-size: 11px;
color: #a08060;
text-align: center;
padding: 8px;
animation: pulse 2s ease-in-out infinite;
}
.arena-btn-ready {
width: 100%;
margin-top: 10px;
background: linear-gradient(#1a4a18, #0f2a0e);
border: 2px solid #4a8a3c;
border-radius: 8px;
color: #a0e090;
font-family: "Cinzel", serif;
font-size: 14px;
padding: 10px;
cursor: pointer;
transition: 0.2s;
}
.arena-btn-ready:hover:not([disabled]) { border-color: #8ae060; background: linear-gradient(#2a6a28, #1a3a18); }
.arena-btn-ready.active { border-color: #8ae060; color: #c0f0a0; cursor: default; }
.arena-searching-box {
font-family: "Cinzel", serif;
font-size: 12px;
color: #dceb15;
text-align: center;
padding: 10px;
border: 1px solid rgba(255,215,80,0.3);
border-radius: 8px;
margin-top: 8px;
animation: pulse 2s ease-in-out infinite;
}
`;
document.head.appendChild(style);
}
/* ── Socket-Referenz holen ──────────────────────────────────────────────────── */
function getSocket() {
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 if (mode === "2v2") {
open2v2Lobby();
} else {
console.log("Arena Modus gewählt:", mode);
}
});
});
}
/* ══════════════════════════════════════════════════════════
2v2 LOBBY
══════════════════════════════════════════════════════════ */
let my2v2TeamId = null;
let myArenaData = null;
async function open2v2Lobby() {
const socket = getSocket();
// Screen ZUERST wechseln so sind Fehlermeldungen sichtbar
document.getElementById("arena-mode-screen").style.display = "none";
document.getElementById("arena-2v2-screen").style.display = "flex";
if (!socket) {
console.error("[2v2] Kein Socket window._socket:", window._socket);
show2v2Error("Keine Verbindung zum Server. Bitte Seite neu laden.");
return;
}
// Spielerdaten laden
if (!myArenaData) {
try {
console.log("[2v2] Lade Spielerdaten…");
const res = await fetch("/arena/me");
if (!res.ok) throw new Error("HTTP " + res.status);
myArenaData = await res.json();
console.log("[2v2] Spielerdaten:", myArenaData);
} catch (err) {
console.error("[2v2] Spielerdaten Fehler:", err);
show2v2Error("Spielerdaten konnten nicht geladen werden. (Eingeloggt?)");
return;
}
}
// Lobbyliste anfordern
socket.emit("get_2v2_lobbies");
console.log("[2v2] get_2v2_lobbies gesendet");
// Zurück-Button
document.getElementById("arena-2v2-back").onclick = () => {
leave2v2Team(socket);
document.getElementById("arena-2v2-screen").style.display = "none";
document.getElementById("arena-mode-screen").style.display = "";
my2v2TeamId = null;
};
// Team erstellen
document.getElementById("arena-create-team-btn").onclick = () => {
console.log("[2v2] Team erstellen geklickt sende create_2v2_team mit:", myArenaData);
socket.emit("create_2v2_team", myArenaData);
};
// Socket-Listener registrieren (einmalig)
socket.off("2v2_lobbies");
socket.off("2v2_team_joined");
socket.off("2v2_team_update");
socket.off("2v2_partner_left");
socket.off("2v2_searching");
socket.off("match_found_2v2");
socket.off("2v2_error");
socket.on("2v2_lobbies", (list) => {
console.log("[2v2] Lobbies empfangen:", list);
render2v2LobbyList(list, socket);
});
socket.on("2v2_team_joined", (data) => {
console.log("[2v2] Team joined:", data);
my2v2TeamId = data.teamId;
document.getElementById("arena-team-panel").style.display = "block";
document.getElementById("arena-lobby-section").style.display = "none";
hide2v2Error();
});
socket.on("2v2_team_update", (data) => render2v2TeamPanel(data, socket));
socket.on("2v2_partner_left", (data) => {
const status = document.getElementById("arena-team-status");
if (status) status.innerHTML = `<span style="color:#e74c3c;">⚠️ ${data.name} hat das Team verlassen.</span>`;
render2v2TeamPanel({ teamId: my2v2TeamId, players: [{ name: myArenaData.name, level: myArenaData.level, ready: false }], count: 1 }, socket);
});
socket.on("2v2_searching", () => {
const status = document.getElementById("arena-team-status");
if (status) status.innerHTML = `<div class="arena-searching-box">⏳ Suche nach Gegnerteam…</div>`;
const actions = document.getElementById("arena-team-actions");
if (actions) actions.innerHTML = "";
});
socket.on("match_found_2v2", (data) => {
socket.off("2v2_lobbies");
socket.off("2v2_team_update");
socket.off("2v2_partner_left");
socket.off("2v2_searching");
showMatchFoundOverlay(myArenaData.name, `Team ${data.myTeam === 1 ? 2 : 1}`, () => {
document.getElementById("arena-2v2-screen").style.display = "none";
document.getElementById("arena-mode-screen").style.display = "";
openArenaPopup(
`/arena/2v2?match=${encodeURIComponent(data.matchId)}&slot=${encodeURIComponent(data.mySlot)}`,
data.opponents?.join(" & ") || "Gegner",
data.matchId,
);
});
});
socket.on("2v2_error", (data) => {
console.warn("[2v2] Fehler vom Server:", data.message);
show2v2Error(data.message);
});
}
function render2v2LobbyList(list, socket) {
const el = document.getElementById("arena-lobby-list");
if (!el) return;
if (!list.length) {
el.innerHTML = `<div class="arena-lobby-empty">Keine offenen Teams vorhanden.</div>`;
return;
}
el.innerHTML = list.map(team => `
<div class="arena-lobby-row">
<div class="arena-lobby-row-info">
<span class="arena-lobby-leader">⚔️ ${team.leader}</span>
<span class="arena-lobby-level">Lvl ${team.leaderLevel}</span>
<span class="arena-lobby-count">${team.count}/2 Spieler</span>
</div>
<button class="arena-btn-join" data-teamid="${team.teamId}">Beitreten</button>
</div>
`).join("");
el.querySelectorAll(".arena-btn-join").forEach(btn => {
btn.addEventListener("click", () => {
socket.emit("join_2v2_team", { teamId: btn.dataset.teamid, playerData: myArenaData });
});
});
}
function render2v2TeamPanel(data, socket) {
const playersEl = document.getElementById("arena-team-players");
const actionsEl = document.getElementById("arena-team-actions");
const statusEl = document.getElementById("arena-team-status");
if (!playersEl || !actionsEl) return;
playersEl.innerHTML = data.players.map(p => `
<div class="arena-team-player ${p.ready ? 'ready' : ''}">
<span class="arena-team-player-name">${p.name}</span>
<span class="arena-team-player-level">Lvl ${p.level}</span>
<span class="arena-team-player-status">${p.ready ? '✅ Bereit' : '⌛ Wartet'}</span>
</div>
`).join("");
if (data.count < 2) {
actionsEl.innerHTML = `<div class="arena-waiting-partner">Warte auf Partner…</div>`;
if (statusEl) statusEl.innerHTML = "";
} else {
const myEntry = data.players.find(p => p.name === myArenaData?.name);
const iAmReady = myEntry?.ready;
actionsEl.innerHTML = iAmReady
? `<button class="arena-btn-ready active" disabled>✅ Du bist bereit</button>`
: `<button class="arena-btn-ready" id="arena-ready-btn">⚔️ Bereit</button>`;
if (!iAmReady) {
document.getElementById("arena-ready-btn")?.addEventListener("click", () => {
socket.emit("2v2_player_ready", { teamId: my2v2TeamId });
});
}
}
}
function leave2v2Team(socket) {
if (my2v2TeamId) {
socket.emit("leave_2v2_team");
my2v2TeamId = null;
}
socket.off("2v2_lobbies");
socket.off("2v2_team_joined");
socket.off("2v2_team_update");
socket.off("2v2_partner_left");
socket.off("2v2_searching");
socket.off("match_found_2v2");
socket.off("2v2_error");
}
/* ── 2v2 Fehlermeldung ──────────────────────────────────────────────────────── */
function show2v2Error(msg) {
const el = document.getElementById("arena-2v2-error");
if (!el) return;
el.textContent = "❌ " + msg;
el.style.display = "block";
setTimeout(() => { el.style.display = "none"; }, 4000);
}
function hide2v2Error() {
const el = document.getElementById("arena-2v2-error");
if (el) el.style.display = "none";
}
/* ── 1v1: Hauptlogik ────────────────────────────────────────────────────────── */
async function handle1v1Click(card) {
const socket = getSocket();
if (!socket) {
showArenaError("Keine Verbindung zum Server. Bitte Seite neu laden.");
return;
}
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 (err) {
console.error("[1v1] Spielerdaten Fehler:", err);
showArenaError("Spielerdaten konnten nicht geladen werden. (Eingeloggt?)");
return;
}
setCardSearching(card, true);
showQueueStatus(me.level);
socket.off("match_found");
socket.off("queue_status");
socket.on("queue_status", (data) => {
if (data.status === "waiting") {
showQueueStatus(me.level, data.poolSize);
} else if (data.status === "left") {
setCardSearching(card, false);
hideQueueStatus();
}
});
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,
);
});
});
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) {
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);
requestAnimationFrame(() => {
const bar = document.getElementById("mfBar");
if (bar) bar.style.width = "100%";
});
setTimeout(() => {
overlay.remove();
onDone();
}, 1600);
}
/* ── Popup öffnen ───────────────────────────────────────────────────────────── */
function openArenaPopup(src, opponentName, matchId) {
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
? `⚔️ Arena &nbsp;·&nbsp; vs ${opponentName}`
: "⚔️ Arena";
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);
}
/* ── 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 min = Math.max(1, myLevel - 5);
const max = myLevel + 5;
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>
`;
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);
}