rtjrt
This commit is contained in:
parent
c92266e23e
commit
c61cddddb6
@ -4,33 +4,56 @@ export async function loadArena() {
|
||||
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>
|
||||
<!-- 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 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 id="arena-queue-status" style="display:none;"></div>
|
||||
</div>
|
||||
|
||||
<!-- Status-Anzeige (erscheint nur während Suche) -->
|
||||
<div id="arena-queue-status" style="display:none;"></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>
|
||||
|
||||
<!-- 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>
|
||||
`;
|
||||
@ -214,6 +237,146 @@ function injectArenaStyles() {
|
||||
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);
|
||||
}
|
||||
@ -229,17 +392,186 @@ 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);
|
||||
// Platzhalter für 2v2 / 4v4
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════
|
||||
2v2 LOBBY
|
||||
══════════════════════════════════════════════════════════ */
|
||||
let my2v2TeamId = null;
|
||||
let myArenaData = null;
|
||||
|
||||
async function open2v2Lobby() {
|
||||
const socket = getSocket();
|
||||
if (!socket) { showArenaError("Keine Verbindung zum Server."); return; }
|
||||
|
||||
// Spielerdaten laden
|
||||
if (!myArenaData) {
|
||||
try {
|
||||
const res = await fetch("/arena/me");
|
||||
if (!res.ok) throw new Error(res.status);
|
||||
myArenaData = await res.json();
|
||||
} catch {
|
||||
showArenaError("Spielerdaten konnten nicht geladen werden.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Screen wechseln
|
||||
document.getElementById("arena-mode-screen").style.display = "none";
|
||||
document.getElementById("arena-2v2-screen").style.display = "flex";
|
||||
|
||||
// Lobbyliste anfordern
|
||||
socket.emit("get_2v2_lobbies");
|
||||
|
||||
// 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 = () => {
|
||||
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) => render2v2LobbyList(list, socket));
|
||||
|
||||
socket.on("2v2_team_joined", (data) => {
|
||||
my2v2TeamId = data.teamId;
|
||||
document.getElementById("arena-team-panel").style.display = "block";
|
||||
document.getElementById("arena-lobby-section").style.display = "none";
|
||||
});
|
||||
|
||||
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>`;
|
||||
// Bereit-Button zurücksetzen
|
||||
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) => {
|
||||
const status = document.getElementById("arena-team-status");
|
||||
if (status) { status.innerHTML = `<span style="color:#e74c3c;">❌ ${data.message}</span>`; }
|
||||
});
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
/* ── 1v1: Hauptlogik ───────────────────────────────────────────────────────── */
|
||||
async function handle1v1Click(card) {
|
||||
const socket = getSocket();
|
||||
|
||||
@ -202,16 +202,30 @@ export async function loadEvents() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Arena Daily UI (nur 2v2) -->
|
||||
<div id="arena2-ui" class="booster-ui" style="display:none;">
|
||||
<button class="booster-back-btn" id="arena2-back-btn">← Zurück</button>
|
||||
<div class="arena-daily-wrap">
|
||||
<div class="arena-mode-card-daily" id="arena-2v2-card">
|
||||
<div class="arena-mode-icon">⚔️</div>
|
||||
<div class="arena-mode-label-daily">2v2</div>
|
||||
<div class="arena-mode-desc-daily">Verbünde dich mit einem Kameraden im Kampf</div>
|
||||
<!-- Arena Daily UI (2v2 Lobby) -->
|
||||
<div id="arena2-ui" class="booster-ui" style="display:none; flex-direction:column; gap:14px; padding:16px; overflow-y:auto;">
|
||||
<div style="display:flex; align-items:center; gap:14px;">
|
||||
<button class="booster-back-btn" id="arena2-back-btn">← Zurück</button>
|
||||
<span style="font-family:'Cinzel',serif; font-size:14px; color:#f0d060;">⚔️ 2v2 Team-Lobby</span>
|
||||
</div>
|
||||
|
||||
<!-- Team-Panel -->
|
||||
<div id="daily-team-panel" style="display:none;">
|
||||
<div class="arena-team-box">
|
||||
<div class="arena-team-title">Dein Team</div>
|
||||
<div id="daily-team-players"></div>
|
||||
<div id="daily-team-actions"></div>
|
||||
</div>
|
||||
<div id="arena2-daily-status" style="display:none;"></div>
|
||||
<div id="daily-team-status" style="margin-top:8px;"></div>
|
||||
</div>
|
||||
|
||||
<!-- Lobby-Liste -->
|
||||
<div id="daily-lobby-section">
|
||||
<div class="arena-lobby-actions">
|
||||
<button class="arena-btn-create" id="daily-create-team-btn">+ Eigenes Team erstellen</button>
|
||||
</div>
|
||||
<div class="arena-lobby-title">Offene Teams</div>
|
||||
<div id="daily-lobby-list"><div class="arena-lobby-empty">Keine offenen Teams vorhanden.</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -258,7 +272,8 @@ export async function loadEvents() {
|
||||
if (card.dataset.type === "arena2") {
|
||||
eventsGrid.style.display = "none";
|
||||
arena2Ui.style.display = "flex";
|
||||
resetArena2Daily();
|
||||
daily2v2Leave();
|
||||
open2v2DailyLobby();
|
||||
return;
|
||||
}
|
||||
if (card.dataset.type === "gold") {
|
||||
@ -511,110 +526,182 @@ export async function loadEvents() {
|
||||
}
|
||||
|
||||
body.querySelector("#arena2-back-btn").addEventListener("click", () => {
|
||||
if (isArena2Searching) return;
|
||||
if (isArena2InMatch) return;
|
||||
eventsGrid.style.display = "";
|
||||
arena2Ui.style.display = "none";
|
||||
cancelArena2Search();
|
||||
daily2v2Leave();
|
||||
});
|
||||
|
||||
/* ── Arena2 Daily Zustand (2v2) ── */
|
||||
let isArena2Searching = false;
|
||||
/* ── Arena2 Daily Zustand (2v2 Lobby) ── */
|
||||
let isArena2InMatch = false;
|
||||
let daily2v2TeamId = null;
|
||||
let daily2v2Me = null;
|
||||
|
||||
function resetArena2Daily() {
|
||||
isArena2Searching = false;
|
||||
const card = body.querySelector("#arena-2v2-card");
|
||||
const status = body.querySelector("#arena2-daily-status");
|
||||
if (card) {
|
||||
card.classList.remove("searching");
|
||||
card.querySelector(".arena-mode-label-daily").textContent = "2v2";
|
||||
card.querySelector(".arena-mode-desc-daily").textContent = "Verbünde dich mit einem Kameraden im Kampf";
|
||||
}
|
||||
if (status) status.style.display = "none";
|
||||
const backBtn = body.querySelector("#arena2-back-btn");
|
||||
if (backBtn) { backBtn.style.opacity = "1"; backBtn.style.cursor = "pointer"; }
|
||||
}
|
||||
|
||||
function cancelArena2Search() {
|
||||
async function open2v2DailyLobby() {
|
||||
const socket = window._socket;
|
||||
if (socket) {
|
||||
socket.emit("leave_2v2");
|
||||
socket.off("match_found_2v2");
|
||||
socket.off("queue_status_2v2");
|
||||
}
|
||||
resetArena2Daily();
|
||||
}
|
||||
if (!socket) { showDaily2v2Error("❌ Keine Verbindung zum Server."); return; }
|
||||
|
||||
body.querySelector("#arena-2v2-card").addEventListener("click", async () => {
|
||||
if (isArena2Searching) return;
|
||||
|
||||
const socket = window._socket;
|
||||
if (!socket) {
|
||||
showArena2Status("❌ Keine Verbindung zum Server.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
let me;
|
||||
try {
|
||||
const res = await fetch("/arena/me");
|
||||
if (!res.ok) throw new Error(res.status);
|
||||
me = await res.json();
|
||||
} catch {
|
||||
showArena2Status("❌ Spielerdaten konnten nicht geladen werden.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
isArena2Searching = true;
|
||||
const card2v2 = body.querySelector("#arena-2v2-card");
|
||||
card2v2.classList.add("searching");
|
||||
card2v2.querySelector(".arena-mode-label-daily").textContent = "⏳ Suche…";
|
||||
card2v2.querySelector(".arena-mode-desc-daily").textContent = "Warte auf Mitspieler & Gegner…";
|
||||
|
||||
const backBtn = body.querySelector("#arena2-back-btn");
|
||||
backBtn.style.opacity = "0.35";
|
||||
backBtn.style.cursor = "not-allowed";
|
||||
|
||||
showArena2Status(`⏳ Suche Gegner (Level ${Math.max(1, me.level - 5)}–${me.level + 5})…
|
||||
<br><span class="arena-cancel-link" id="arena2-cancel-link">Suche abbrechen</span>`);
|
||||
body.querySelector("#arena2-cancel-link")?.addEventListener("click", () => cancelArena2Search());
|
||||
|
||||
socket.off("match_found_2v2");
|
||||
socket.off("queue_status_2v2");
|
||||
|
||||
socket.on("queue_status_2v2", (data) => {
|
||||
if (data.status === "waiting") {
|
||||
const pool = data.poolSize ? ` · ${data.poolSize} im Pool` : "";
|
||||
showArena2Status(`⏳ Suche Gegner (Level ${Math.max(1, me.level - 5)}–${me.level + 5})${pool}
|
||||
<br><span class="arena-cancel-link" id="arena2-cancel-link">Suche abbrechen</span>`);
|
||||
body.querySelector("#arena2-cancel-link")?.addEventListener("click", () => cancelArena2Search());
|
||||
if (!daily2v2Me) {
|
||||
try {
|
||||
const res = await fetch("/arena/me");
|
||||
if (!res.ok) throw new Error(res.status);
|
||||
daily2v2Me = await res.json();
|
||||
} catch {
|
||||
showDaily2v2Error("❌ Spielerdaten konnten nicht geladen werden.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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.emit("get_2v2_lobbies");
|
||||
socket.on("2v2_lobbies", (list) => renderDaily2v2Lobbies(list, socket));
|
||||
|
||||
socket.on("2v2_team_joined", (data) => {
|
||||
daily2v2TeamId = data.teamId;
|
||||
body.querySelector("#daily-team-panel").style.display = "block";
|
||||
body.querySelector("#daily-lobby-section").style.display = "none";
|
||||
});
|
||||
|
||||
socket.on("2v2_team_update", (data) => renderDaily2v2Team(data, socket));
|
||||
|
||||
socket.on("2v2_partner_left", (data) => {
|
||||
const s = body.querySelector("#daily-team-status");
|
||||
if (s) s.innerHTML = `<span style="color:#e74c3c;">⚠️ ${data.name} hat das Team verlassen.</span>`;
|
||||
});
|
||||
|
||||
socket.on("2v2_searching", () => {
|
||||
const s = body.querySelector("#daily-team-status");
|
||||
if (s) s.innerHTML = `<div class="arena-searching-box">⏳ Suche nach Gegnerteam…</div>`;
|
||||
const a = body.querySelector("#daily-team-actions");
|
||||
if (a) a.innerHTML = "";
|
||||
// Zurück sperren während Suche
|
||||
const backBtn = body.querySelector("#arena2-back-btn");
|
||||
if (backBtn) { backBtn.style.opacity = "0.35"; backBtn.style.cursor = "not-allowed"; }
|
||||
isArena2InMatch = true;
|
||||
});
|
||||
|
||||
socket.once("match_found_2v2", (data) => {
|
||||
socket.off("queue_status_2v2");
|
||||
isArena2Searching = false;
|
||||
socket.off("2v2_lobbies");
|
||||
socket.off("2v2_team_update");
|
||||
socket.off("2v2_partner_left");
|
||||
socket.off("2v2_searching");
|
||||
isArena2InMatch = false;
|
||||
markDailyComplete(3);
|
||||
showArenaMatchFound(me.name, data.opponent.name, () => {
|
||||
|
||||
showArenaMatchFound(daily2v2Me.name, `Team ${data.myTeam === 1 ? 2 : 1}`, () => {
|
||||
eventsGrid.style.display = "";
|
||||
arena2Ui.style.display = "none";
|
||||
openArenaMatchPopup(
|
||||
`/arena/2v2?match=${encodeURIComponent(data.matchId)}&slot=${encodeURIComponent(data.mySlot)}`,
|
||||
data.opponent.name,
|
||||
data.opponents?.join(" & ") || "Gegner",
|
||||
data.matchId,
|
||||
"2v2",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
socket.emit("join_2v2", { id: me.id, name: me.name, level: me.level });
|
||||
});
|
||||
socket.on("2v2_error", (data) => showDaily2v2Error(`❌ ${data.message}`));
|
||||
|
||||
function showArena2Status(html, isError = false) {
|
||||
const box = body.querySelector("#arena2-daily-status");
|
||||
if (!box) return;
|
||||
box.style.display = "block";
|
||||
box.style.color = isError ? "#e74c3c" : "#dceb15";
|
||||
box.innerHTML = html;
|
||||
if (isError) setTimeout(() => { box.style.display = "none"; }, 3000);
|
||||
// Buttons
|
||||
body.querySelector("#daily-create-team-btn").onclick = () => {
|
||||
socket.emit("create_2v2_team", daily2v2Me);
|
||||
};
|
||||
}
|
||||
|
||||
function renderDaily2v2Lobbies(list, socket) {
|
||||
const el = body.querySelector("#daily-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(t => `
|
||||
<div class="arena-lobby-row">
|
||||
<div class="arena-lobby-row-info">
|
||||
<span class="arena-lobby-leader">⚔️ ${t.leader}</span>
|
||||
<span class="arena-lobby-level">Lvl ${t.leaderLevel}</span>
|
||||
<span class="arena-lobby-count">${t.count}/2</span>
|
||||
</div>
|
||||
<button class="arena-btn-join" data-teamid="${t.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: daily2v2Me });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function renderDaily2v2Team(data, socket) {
|
||||
const playersEl = body.querySelector("#daily-team-players");
|
||||
const actionsEl = body.querySelector("#daily-team-actions");
|
||||
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>`;
|
||||
} else {
|
||||
const myEntry = data.players.find(p => p.name === daily2v2Me?.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="daily-ready-btn">⚔️ Bereit</button>`;
|
||||
if (!iAmReady) {
|
||||
body.querySelector("#daily-ready-btn")?.addEventListener("click", () => {
|
||||
socket.emit("2v2_player_ready", { teamId: daily2v2TeamId });
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function daily2v2Leave() {
|
||||
const socket = window._socket;
|
||||
if (socket) {
|
||||
socket.emit("leave_2v2_team");
|
||||
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");
|
||||
}
|
||||
daily2v2TeamId = null;
|
||||
isArena2InMatch = false;
|
||||
// Reset UI
|
||||
const panel = body.querySelector("#daily-team-panel");
|
||||
const section = body.querySelector("#daily-lobby-section");
|
||||
const backBtn = body.querySelector("#arena2-back-btn");
|
||||
if (panel) panel.style.display = "none";
|
||||
if (section) section.style.display = "";
|
||||
if (backBtn) { backBtn.style.opacity = "1"; backBtn.style.cursor = "pointer"; }
|
||||
}
|
||||
|
||||
function showDaily2v2Error(msg) {
|
||||
const s = body.querySelector("#daily-team-status");
|
||||
if (!s) return;
|
||||
s.innerHTML = `<span style="color:#e74c3c;">${msg}</span>`;
|
||||
setTimeout(() => { s.innerHTML = ""; }, 3000);
|
||||
}
|
||||
|
||||
// Lobby beim Öffnen des Arena2-UI initialisieren
|
||||
const origArena2Click = body.querySelector('.event-card[data-type="arena2"]');
|
||||
if (origArena2Click) {
|
||||
origArena2Click.addEventListener("click", () => {
|
||||
// open2v2DailyLobby wird nach dem UI-Wechsel in resetArena2Daily aufgerufen
|
||||
});
|
||||
}
|
||||
|
||||
/* ── Booster Zustand ── */
|
||||
|
||||
572
sockets/arena_socket.js
Normal file
572
sockets/arena_socket.js
Normal file
@ -0,0 +1,572 @@
|
||||
/* ============================================================
|
||||
sockets/arena.js
|
||||
Alle Socket-Events rund um 1v1 Matchmaking, Spielfeld & Bereit-System
|
||||
============================================================ */
|
||||
|
||||
const waitingPool = new Map(); // socketId → { socket, player }
|
||||
const LEVEL_RANGE = 5;
|
||||
|
||||
// 2v2 Team-Lobby
|
||||
// teams2v2: teamId → { id, players: [{socketId, player}], ready: Set<socketId> }
|
||||
const teams2v2 = new Map();
|
||||
const readyTeams = new Map(); // teamId → team (fertige Teams warten auf Matchmaking)
|
||||
|
||||
function generateId() {
|
||||
return `${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;
|
||||
}
|
||||
|
||||
/* ── 2v2 Lobby-Liste an alle schicken ── */
|
||||
function broadcastLobbies(io) {
|
||||
const list = [];
|
||||
for (const [teamId, team] of teams2v2) {
|
||||
if (team.players.length < 2) {
|
||||
list.push({
|
||||
teamId,
|
||||
leader: team.players[0]?.player?.name || "?",
|
||||
leaderLevel: team.players[0]?.player?.level || 1,
|
||||
count: team.players.length,
|
||||
});
|
||||
}
|
||||
}
|
||||
io.emit("2v2_lobbies", list);
|
||||
}
|
||||
|
||||
/* ── 2v2 Team-Status an Teammitglieder senden ── */
|
||||
function broadcastTeamStatus(io, teamId) {
|
||||
const team = teams2v2.get(teamId);
|
||||
if (!team) return;
|
||||
const data = {
|
||||
teamId,
|
||||
players: team.players.map(p => ({
|
||||
name: p.player.name,
|
||||
level: p.player.level,
|
||||
ready: team.ready.has(p.socketId),
|
||||
})),
|
||||
count: team.players.length,
|
||||
};
|
||||
for (const p of team.players) {
|
||||
io.to(p.socketId).emit("2v2_team_update", data);
|
||||
}
|
||||
}
|
||||
|
||||
/* ── 2v2 Matchmaking ── */
|
||||
function tryMatchmaking2v2(io) {
|
||||
const readyList = Array.from(readyTeams.values());
|
||||
if (readyList.length < 2) return;
|
||||
|
||||
const team1 = readyList[0];
|
||||
const team2 = readyList[1];
|
||||
|
||||
readyTeams.delete(team1.id);
|
||||
readyTeams.delete(team2.id);
|
||||
teams2v2.delete(team1.id);
|
||||
teams2v2.delete(team2.id);
|
||||
|
||||
const matchId = `2v2_${generateId()}`;
|
||||
|
||||
const allPlayers = [
|
||||
{ team: 1, ...team1.players[0] },
|
||||
{ team: 1, ...team1.players[1] },
|
||||
{ team: 2, ...team2.players[0] },
|
||||
{ team: 2, ...team2.players[1] },
|
||||
];
|
||||
|
||||
for (const p of allPlayers) {
|
||||
const opponents = allPlayers.filter(x => x.team !== p.team).map(x => x.player.name);
|
||||
const teammates = allPlayers.filter(x => x.team === p.team && x.socketId !== p.socketId).map(x => x.player.name);
|
||||
io.to(p.socketId).emit("match_found_2v2", {
|
||||
matchId,
|
||||
myTeam: p.team,
|
||||
teammates,
|
||||
opponents,
|
||||
mySlot: `team${p.team}_player${team1.players.indexOf(p) >= 0 ? team1.players.findIndex(x=>x.socketId===p.socketId)+1 : team2.players.findIndex(x=>x.socketId===p.socketId)+1}`,
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`[2v2] Match: Team1(${team1.players.map(p=>p.player.name)}) vs Team2(${team2.players.map(p=>p.player.name)}) | ${matchId}`);
|
||||
}
|
||||
|
||||
// Werden beim ersten Event lazy initialisiert (auf io gespeichert)
|
||||
// io._arenaRooms → matchId → { sockets, names }
|
||||
// io._arenaReady → matchId → Set of ready slots
|
||||
// io._arenaTimers → matchId → intervalId
|
||||
|
||||
const READY_TIMEOUT = 30; // Sekunden bis Match abgebrochen wird
|
||||
|
||||
function startReadyTimer(io, matchId) {
|
||||
if (!io._arenaTimers) io._arenaTimers = new Map();
|
||||
if (io._arenaTimers.has(matchId)) return;
|
||||
|
||||
let remaining = READY_TIMEOUT;
|
||||
io.to("arena_" + matchId).emit("ready_timer", { remaining });
|
||||
|
||||
const interval = setInterval(() => {
|
||||
remaining--;
|
||||
io.to("arena_" + matchId).emit("ready_timer", { remaining });
|
||||
|
||||
if (remaining <= 0) {
|
||||
clearInterval(interval);
|
||||
io._arenaTimers.delete(matchId);
|
||||
console.log(`[1v1] Match ${matchId} abgebrochen – Zeit abgelaufen.`);
|
||||
io.to("arena_" + matchId).emit("match_cancelled", {
|
||||
reason: "timeout",
|
||||
message: "Zeit abgelaufen – Match wird abgebrochen.",
|
||||
});
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
io._arenaTimers.set(matchId, interval);
|
||||
}
|
||||
|
||||
function stopReadyTimer(io, matchId) {
|
||||
if (!io._arenaTimers) return;
|
||||
const interval = io._arenaTimers.get(matchId);
|
||||
if (interval) {
|
||||
clearInterval(interval);
|
||||
io._arenaTimers.delete(matchId);
|
||||
console.log(`[1v1] Timer für Match ${matchId} gestoppt (beide bereit).`);
|
||||
}
|
||||
}
|
||||
|
||||
function tryMatchmaking(io, newSocketId) {
|
||||
const challenger = waitingPool.get(newSocketId);
|
||||
if (!challenger) return;
|
||||
|
||||
for (const [id, entry] of waitingPool) {
|
||||
if (id === newSocketId) continue;
|
||||
|
||||
const levelDiff = Math.abs(entry.player.level - challenger.player.level);
|
||||
if (levelDiff <= LEVEL_RANGE) {
|
||||
waitingPool.delete(newSocketId);
|
||||
waitingPool.delete(id);
|
||||
|
||||
const matchId = `match_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;
|
||||
|
||||
challenger.socket.emit("match_found", {
|
||||
matchId,
|
||||
opponent: entry.player,
|
||||
mySlot: "player1",
|
||||
});
|
||||
|
||||
entry.socket.emit("match_found", {
|
||||
matchId,
|
||||
opponent: challenger.player,
|
||||
mySlot: "player2",
|
||||
});
|
||||
|
||||
console.log(
|
||||
`[1v1] Match: ${challenger.player.name} (Lvl ${challenger.player.level})` +
|
||||
` vs ${entry.player.name} (Lvl ${entry.player.level}) | ${matchId}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function registerArenaHandlers(io, socket) {
|
||||
|
||||
/* ── 1v1: Queue beitreten ── */
|
||||
socket.on("join_1v1", (playerData) => {
|
||||
if (waitingPool.has(socket.id)) return;
|
||||
|
||||
const player = {
|
||||
id: playerData.id,
|
||||
name: playerData.name,
|
||||
level: Number(playerData.level) || 1,
|
||||
};
|
||||
|
||||
waitingPool.set(socket.id, { socket, player });
|
||||
|
||||
socket.emit("queue_status", {
|
||||
status: "waiting",
|
||||
poolSize: waitingPool.size,
|
||||
message: `Suche Gegner (Level ${player.level - LEVEL_RANGE}–${player.level + LEVEL_RANGE})…`,
|
||||
});
|
||||
|
||||
console.log(`[1v1] ${player.name} (Lvl ${player.level}) im Pool. Größe: ${waitingPool.size}`);
|
||||
tryMatchmaking(io, socket.id);
|
||||
});
|
||||
|
||||
/* ── 1v1: Queue verlassen ── */
|
||||
socket.on("leave_1v1", () => {
|
||||
if (waitingPool.delete(socket.id)) {
|
||||
socket.emit("queue_status", { status: "left" });
|
||||
console.log(`[1v1] ${socket.id} hat Pool verlassen.`);
|
||||
}
|
||||
});
|
||||
|
||||
/* ══════════════════════════════════════════
|
||||
2v2 TEAM LOBBY
|
||||
══════════════════════════════════════════ */
|
||||
|
||||
/* ── Lobby-Liste anfordern ── */
|
||||
socket.on("get_2v2_lobbies", () => {
|
||||
const list = [];
|
||||
for (const [teamId, team] of teams2v2) {
|
||||
if (team.players.length < 2) {
|
||||
list.push({
|
||||
teamId,
|
||||
leader: team.players[0]?.player?.name || "?",
|
||||
leaderLevel: team.players[0]?.player?.level || 1,
|
||||
count: team.players.length,
|
||||
});
|
||||
}
|
||||
}
|
||||
socket.emit("2v2_lobbies", list);
|
||||
});
|
||||
|
||||
/* ── Neues Team erstellen ── */
|
||||
socket.on("create_2v2_team", (playerData) => {
|
||||
// Erst aus alten Teams entfernen
|
||||
leaveAllTeams(socket.id, io);
|
||||
|
||||
const player = { id: playerData.id, name: playerData.name, level: Number(playerData.level) || 1 };
|
||||
const teamId = `team_${generateId()}`;
|
||||
|
||||
teams2v2.set(teamId, {
|
||||
id: teamId,
|
||||
players: [{ socketId: socket.id, player }],
|
||||
ready: new Set(),
|
||||
});
|
||||
|
||||
socket.emit("2v2_team_joined", { teamId, isLeader: true });
|
||||
broadcastTeamStatus(io, teamId);
|
||||
broadcastLobbies(io);
|
||||
console.log(`[2v2] Team ${teamId} erstellt von ${player.name}`);
|
||||
});
|
||||
|
||||
/* ── Team beitreten ── */
|
||||
socket.on("join_2v2_team", (data) => {
|
||||
const { teamId, playerData } = data;
|
||||
const team = teams2v2.get(teamId);
|
||||
|
||||
if (!team) return socket.emit("2v2_error", { message: "Team nicht mehr verfügbar." });
|
||||
if (team.players.length >= 2) return socket.emit("2v2_error", { message: "Team ist bereits voll." });
|
||||
if (team.players.find(p => p.socketId === socket.id)) return;
|
||||
|
||||
leaveAllTeams(socket.id, io);
|
||||
|
||||
const player = { id: playerData.id, name: playerData.name, level: Number(playerData.level) || 1 };
|
||||
team.players.push({ socketId: socket.id, player });
|
||||
|
||||
socket.emit("2v2_team_joined", { teamId, isLeader: false });
|
||||
broadcastTeamStatus(io, teamId);
|
||||
broadcastLobbies(io); // Team aus offener Liste entfernen
|
||||
console.log(`[2v2] ${player.name} hat Team ${teamId} beigetreten`);
|
||||
});
|
||||
|
||||
/* ── Team verlassen ── */
|
||||
socket.on("leave_2v2_team", () => {
|
||||
leaveAllTeams(socket.id, io);
|
||||
});
|
||||
|
||||
/* ── Bereit klicken ── */
|
||||
socket.on("2v2_player_ready", (data) => {
|
||||
const { teamId } = data;
|
||||
const team = teams2v2.get(teamId);
|
||||
if (!team || team.players.length < 2) return;
|
||||
|
||||
team.ready.add(socket.id);
|
||||
broadcastTeamStatus(io, teamId);
|
||||
|
||||
console.log(`[2v2] Bereit in Team ${teamId}: ${team.ready.size}/2`);
|
||||
|
||||
// Beide bereit → Team in Matchmaking-Pool
|
||||
if (team.ready.size >= 2) {
|
||||
readyTeams.set(teamId, team);
|
||||
for (const p of team.players) {
|
||||
io.to(p.socketId).emit("2v2_searching", { message: "Suche nach Gegnerteam…" });
|
||||
}
|
||||
console.log(`[2v2] Team ${teamId} sucht Gegner. Ready-Teams: ${readyTeams.size}`);
|
||||
tryMatchmaking2v2(io);
|
||||
}
|
||||
});
|
||||
|
||||
/* ── Spielfeld: Spieler betritt Arena-Room ── */
|
||||
socket.on("arena_join", (data) => {
|
||||
const { matchId, slot } = data;
|
||||
if (!matchId || !slot) return;
|
||||
|
||||
if (!io._arenaRooms) io._arenaRooms = new Map();
|
||||
if (!io._arenaRooms.has(matchId)) {
|
||||
io._arenaRooms.set(matchId, { sockets: {}, names: {} });
|
||||
}
|
||||
|
||||
const room = io._arenaRooms.get(matchId);
|
||||
room.sockets[slot] = socket.id;
|
||||
room.names[slot] = socket.user || "Spieler";
|
||||
|
||||
socket.join("arena_" + matchId);
|
||||
|
||||
const otherSlot = slot === "player1" ? "player2" : "player1";
|
||||
|
||||
if (room.sockets[otherSlot]) {
|
||||
io.to("arena_" + matchId).emit("arena_ready", {
|
||||
player1: room.names["player1"] || "Spieler 1",
|
||||
player2: room.names["player2"] || "Spieler 2",
|
||||
});
|
||||
console.log(`[Arena] Match ${matchId} bereit: ${room.names["player1"]} vs ${room.names["player2"]}`);
|
||||
startReadyTimer(io, matchId);
|
||||
} else {
|
||||
socket.to("arena_" + matchId).emit("arena_opponent_joined", {
|
||||
name: room.names[slot],
|
||||
slot,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/* ── Bereit-System ── */
|
||||
socket.on("player_ready", (data) => {
|
||||
const { matchId, slot } = data;
|
||||
if (!matchId || !slot) return;
|
||||
|
||||
if (!io._arenaReady) io._arenaReady = new Map();
|
||||
if (!io._arenaReady.has(matchId)) {
|
||||
io._arenaReady.set(matchId, new Set());
|
||||
}
|
||||
|
||||
const readySet = io._arenaReady.get(matchId);
|
||||
readySet.add(slot);
|
||||
|
||||
io.to("arena_" + matchId).emit("ready_status", {
|
||||
readyCount: readySet.size,
|
||||
readySlots: Array.from(readySet),
|
||||
});
|
||||
|
||||
console.log(`[1v1] ${slot} bereit in ${matchId} (${readySet.size}/2)`);
|
||||
|
||||
if (readySet.size >= 2) {
|
||||
stopReadyTimer(io, matchId);
|
||||
io._arenaReady.delete(matchId);
|
||||
}
|
||||
});
|
||||
|
||||
/* ── Aufgeben ── */
|
||||
socket.on("player_surrender", (data) => {
|
||||
const { matchId, slot } = data;
|
||||
console.log(`[1v1] ${slot} hat aufgegeben in Match ${matchId}`);
|
||||
io.to("arena_" + matchId).emit("player_surrendered", { slot });
|
||||
});
|
||||
|
||||
/* ── Disconnect ── */
|
||||
socket.on("disconnect", () => {
|
||||
if (waitingPool.delete(socket.id)) {
|
||||
console.log(`[1v1] ${socket.id} disconnected – aus 1v1-Pool entfernt.`);
|
||||
}
|
||||
leaveAllTeams(socket.id, io);
|
||||
});
|
||||
}
|
||||
|
||||
/* ── Hilfsfunktion: Spieler aus allen 2v2-Teams entfernen ── */
|
||||
function leaveAllTeams(socketId, io) {
|
||||
for (const [teamId, team] of teams2v2) {
|
||||
const idx = team.players.findIndex(p => p.socketId === socketId);
|
||||
if (idx === -1) continue;
|
||||
|
||||
const playerName = team.players[idx].player.name;
|
||||
team.players.splice(idx, 1);
|
||||
team.ready.delete(socketId);
|
||||
readyTeams.delete(teamId);
|
||||
|
||||
if (team.players.length === 0) {
|
||||
teams2v2.delete(teamId);
|
||||
console.log(`[2v2] Team ${teamId} aufgelöst.`);
|
||||
} else {
|
||||
// Verbleibenden Spieler informieren
|
||||
broadcastTeamStatus(io, teamId);
|
||||
for (const p of team.players) {
|
||||
io.to(p.socketId).emit("2v2_partner_left", { name: playerName });
|
||||
}
|
||||
console.log(`[2v2] ${playerName} hat Team ${teamId} verlassen.`);
|
||||
}
|
||||
broadcastLobbies(io);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { registerArenaHandlers };
|
||||
|
||||
function startReadyTimer(io, matchId) {
|
||||
if (!io._arenaTimers) io._arenaTimers = new Map();
|
||||
if (io._arenaTimers.has(matchId)) return; // läuft bereits
|
||||
|
||||
let remaining = READY_TIMEOUT;
|
||||
|
||||
// Sofort ersten Tick senden
|
||||
io.to("arena_" + matchId).emit("ready_timer", { remaining });
|
||||
|
||||
const interval = setInterval(() => {
|
||||
remaining--;
|
||||
io.to("arena_" + matchId).emit("ready_timer", { remaining });
|
||||
|
||||
if (remaining <= 0) {
|
||||
clearInterval(interval);
|
||||
io._arenaTimers.delete(matchId);
|
||||
|
||||
// Match abbrechen – Funktion noch offen
|
||||
console.log(`[1v1] Match ${matchId} abgebrochen – Zeit abgelaufen.`);
|
||||
io.to("arena_" + matchId).emit("match_cancelled", {
|
||||
reason: "timeout",
|
||||
message: "Zeit abgelaufen – Match wird abgebrochen.",
|
||||
});
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
io._arenaTimers.set(matchId, interval);
|
||||
}
|
||||
|
||||
function stopReadyTimer(io, matchId) {
|
||||
if (!io._arenaTimers) return;
|
||||
const interval = io._arenaTimers.get(matchId);
|
||||
if (interval) {
|
||||
clearInterval(interval);
|
||||
io._arenaTimers.delete(matchId);
|
||||
console.log(`[1v1] Timer für Match ${matchId} gestoppt (beide bereit).`);
|
||||
}
|
||||
}
|
||||
|
||||
function tryMatchmaking(io, newSocketId) {
|
||||
const challenger = waitingPool.get(newSocketId);
|
||||
if (!challenger) return;
|
||||
|
||||
for (const [id, entry] of waitingPool) {
|
||||
if (id === newSocketId) continue;
|
||||
|
||||
const levelDiff = Math.abs(entry.player.level - challenger.player.level);
|
||||
if (levelDiff <= LEVEL_RANGE) {
|
||||
waitingPool.delete(newSocketId);
|
||||
waitingPool.delete(id);
|
||||
|
||||
const matchId = `match_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;
|
||||
|
||||
challenger.socket.emit("match_found", {
|
||||
matchId,
|
||||
opponent: entry.player,
|
||||
mySlot: "player1",
|
||||
});
|
||||
|
||||
entry.socket.emit("match_found", {
|
||||
matchId,
|
||||
opponent: challenger.player,
|
||||
mySlot: "player2",
|
||||
});
|
||||
|
||||
console.log(
|
||||
`[1v1] Match: ${challenger.player.name} (Lvl ${challenger.player.level})` +
|
||||
` vs ${entry.player.name} (Lvl ${entry.player.level}) | ${matchId}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function registerArenaHandlers(io, socket) {
|
||||
|
||||
/* ── Queue beitreten ── */
|
||||
socket.on("join_1v1", (playerData) => {
|
||||
if (waitingPool.has(socket.id)) return;
|
||||
|
||||
const player = {
|
||||
id: playerData.id,
|
||||
name: playerData.name,
|
||||
level: Number(playerData.level) || 1,
|
||||
};
|
||||
|
||||
waitingPool.set(socket.id, { socket, player });
|
||||
|
||||
socket.emit("queue_status", {
|
||||
status: "waiting",
|
||||
poolSize: waitingPool.size,
|
||||
message: `Suche Gegner (Level ${player.level - LEVEL_RANGE}–${player.level + LEVEL_RANGE})…`,
|
||||
});
|
||||
|
||||
console.log(`[1v1] ${player.name} (Lvl ${player.level}) im Pool. Größe: ${waitingPool.size}`);
|
||||
tryMatchmaking(io, socket.id);
|
||||
});
|
||||
|
||||
/* ── Queue verlassen ── */
|
||||
socket.on("leave_1v1", () => {
|
||||
if (waitingPool.delete(socket.id)) {
|
||||
socket.emit("queue_status", { status: "left" });
|
||||
console.log(`[1v1] ${socket.id} hat Pool verlassen.`);
|
||||
}
|
||||
});
|
||||
|
||||
/* ── Spielfeld: Spieler betritt Arena-Room ── */
|
||||
socket.on("arena_join", (data) => {
|
||||
const { matchId, slot } = data;
|
||||
if (!matchId || !slot) return;
|
||||
|
||||
if (!io._arenaRooms) io._arenaRooms = new Map();
|
||||
if (!io._arenaRooms.has(matchId)) {
|
||||
io._arenaRooms.set(matchId, { sockets: {}, names: {} });
|
||||
}
|
||||
|
||||
const room = io._arenaRooms.get(matchId);
|
||||
room.sockets[slot] = socket.id;
|
||||
room.names[slot] = socket.user || "Spieler";
|
||||
|
||||
socket.join("arena_" + matchId);
|
||||
|
||||
const otherSlot = slot === "player1" ? "player2" : "player1";
|
||||
|
||||
if (room.sockets[otherSlot]) {
|
||||
io.to("arena_" + matchId).emit("arena_ready", {
|
||||
player1: room.names["player1"] || "Spieler 1",
|
||||
player2: room.names["player2"] || "Spieler 2",
|
||||
});
|
||||
console.log(`[Arena] Match ${matchId} bereit: ${room.names["player1"]} vs ${room.names["player2"]}`);
|
||||
|
||||
// 30-Sekunden Bereit-Timer starten
|
||||
startReadyTimer(io, matchId);
|
||||
} else {
|
||||
socket.to("arena_" + matchId).emit("arena_opponent_joined", {
|
||||
name: room.names[slot],
|
||||
slot,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/* ── Bereit-System ── */
|
||||
socket.on("player_ready", (data) => {
|
||||
const { matchId, slot } = data;
|
||||
if (!matchId || !slot) return;
|
||||
|
||||
if (!io._arenaReady) io._arenaReady = new Map();
|
||||
if (!io._arenaReady.has(matchId)) {
|
||||
io._arenaReady.set(matchId, new Set());
|
||||
}
|
||||
|
||||
const readySet = io._arenaReady.get(matchId);
|
||||
readySet.add(slot);
|
||||
|
||||
io.to("arena_" + matchId).emit("ready_status", {
|
||||
readyCount: readySet.size,
|
||||
readySlots: Array.from(readySet),
|
||||
});
|
||||
|
||||
console.log(`[1v1] ${slot} bereit in ${matchId} (${readySet.size}/2)`);
|
||||
|
||||
if (readySet.size >= 2) {
|
||||
stopReadyTimer(io, matchId);
|
||||
io._arenaReady.delete(matchId);
|
||||
}
|
||||
});
|
||||
|
||||
/* ── Aufgeben ── */
|
||||
socket.on("player_surrender", (data) => {
|
||||
const { matchId, slot } = data;
|
||||
console.log(`[1v1] ${slot} hat aufgegeben in Match ${matchId}`);
|
||||
// Aufgabe-Logik kommt hier rein
|
||||
io.to("arena_" + matchId).emit("player_surrendered", { slot });
|
||||
});
|
||||
|
||||
/* ── Disconnect: aus Pool entfernen ── */
|
||||
socket.on("disconnect", () => {
|
||||
if (waitingPool.delete(socket.id)) {
|
||||
console.log(`[1v1] ${socket.id} disconnected – aus Pool entfernt.`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { registerArenaHandlers };
|
||||
Loading…
Reference in New Issue
Block a user