From a17a6c1414dc41f64dde931deebd86fcd0b1acae Mon Sep 17 00:00:00 2001 From: cay Date: Tue, 14 Apr 2026 10:14:51 +0100 Subject: [PATCH] sjz --- public/js/buildings/1v1.js | 881 ++++++++++++++++------------------- sockets/1vKI_daily.socket.js | 23 +- sockets/arena.socket.js | 1 + 3 files changed, 410 insertions(+), 495 deletions(-) diff --git a/public/js/buildings/1v1.js b/public/js/buildings/1v1.js index f4b0237..ab53d05 100644 --- a/public/js/buildings/1v1.js +++ b/public/js/buildings/1v1.js @@ -5,48 +5,41 @@ ═══════════════════════════════════════════════════════════════ */ /* ── Konfiguration aus EJS-Bridge ────────────────────────── */ -const matchId = - new URLSearchParams(window.location.search).get("match") || - window.GAME_CONFIG.matchId; -const mySlot = - new URLSearchParams(window.location.search).get("slot") || - window.GAME_CONFIG.mySlot; -const amIPlayer1 = mySlot === "player1"; +const matchId = new URLSearchParams(window.location.search).get('match') || window.GAME_CONFIG.matchId; +const mySlot = new URLSearchParams(window.location.search).get('slot') || window.GAME_CONFIG.mySlot; +const amIPlayer1 = mySlot === 'player1'; /* ── Gegner-Name aus URL setzen ──────────────────────────── */ const _urlParams = new URLSearchParams(window.location.search); -const opponentNameFromUrl = _urlParams.get("opponent"); +const opponentNameFromUrl = _urlParams.get('opponent'); if (opponentNameFromUrl) { - const oppEl = document.getElementById(amIPlayer1 ? "nameRight" : "nameLeft"); + const oppEl = document.getElementById(amIPlayer1 ? 'nameRight' : 'nameLeft'); if (oppEl) oppEl.textContent = decodeURIComponent(opponentNameFromUrl); } /* ═══════════════════════════════════════════════════════════ SPIELFELD AUFBAUEN ═══════════════════════════════════════════════════════════ */ -["row1", "row2"].forEach((rowId) => { +['row1', 'row2'].forEach(rowId => { const row = document.getElementById(rowId); for (let i = 1; i <= 11; i++) { - const s = document.createElement("div"); - s.className = "card-slot"; - s.dataset.row = rowId; + const s = document.createElement('div'); + s.className = 'card-slot'; + s.dataset.row = rowId; s.dataset.slotIndex = i; - s.id = `${rowId}-slot-${i}`; - s.innerHTML = - '\u2736' + - i + - ""; + s.id = `${rowId}-slot-${i}`; + s.innerHTML = '\u2736' + i + ''; row.appendChild(s); } }); -const hand = document.getElementById("handArea"); +const hand = document.getElementById('handArea'); /* ── Deck-Stapel ─────────────────────────────────────────── */ -const deckSlot = document.createElement("div"); -deckSlot.className = "hand-slot hand-slot-deck"; -deckSlot.id = "deck-stack"; -deckSlot.title = "Dein Deck"; +const deckSlot = document.createElement('div'); +deckSlot.className = 'hand-slot hand-slot-deck'; +deckSlot.id = 'deck-stack'; +deckSlot.title = 'Dein Deck'; deckSlot.innerHTML = `
@@ -58,19 +51,11 @@ deckSlot.innerHTML = ` hand.appendChild(deckSlot); /* ── Hand-Karten-Slots ───────────────────────────────────── */ -const handCardIds = [ - "hand-card-1", - "hand-card-2", - "hand-card-3", - "hand-card-4", - "hand-card-5", - "hand-card-6", - "hand-card-7", -]; -handCardIds.forEach((id) => { - const s = document.createElement("div"); - s.className = "hand-slot hand-slot-card"; - s.id = id; +const handCardIds = ['hand-card-1','hand-card-2','hand-card-3','hand-card-4','hand-card-5','hand-card-6','hand-card-7']; +handCardIds.forEach(id => { + const s = document.createElement('div'); + s.className = 'hand-slot hand-slot-card'; + s.id = id; s.innerHTML = '\uD83C\uDCCF'; hand.appendChild(s); }); @@ -80,26 +65,24 @@ handCardIds.forEach((id) => { ═══════════════════════════════════════════════════════════ */ let deckQueue = []; const handSlotState = {}; -handCardIds.forEach((id) => { - handSlotState[id] = null; -}); +handCardIds.forEach(id => { handSlotState[id] = null; }); /* ── SVG-Icons ───────────────────────────────────────────── */ const SVG_RANGE = ``; -const SVG_RACE = ``; +const SVG_RACE = ``; /* ── Hand-Slot rendern ───────────────────────────────────── */ function renderHandSlot(id) { - const slot = document.getElementById(id); + const slot = document.getElementById(id); const state = handSlotState[id]; if (!slot) return; - slot.innerHTML = ""; - slot.style.backgroundImage = "none"; - slot.style.opacity = "1"; - slot.style.filter = "none"; - slot.style.backgroundColor = "#0a0805"; - slot.classList.remove("hand-slot-ready", "hand-slot--filled"); + slot.innerHTML = ''; + slot.style.backgroundImage = 'none'; + slot.style.opacity = '1'; + slot.style.filter = 'none'; + slot.style.backgroundColor = '#0a0805'; + slot.classList.remove('hand-slot-ready', 'hand-slot--filled'); if (!state) { slot.innerHTML = '\uD83C\uDCCF'; @@ -111,23 +94,20 @@ function renderHandSlot(id) { const { card, currentCd } = state; const isReady = currentCd <= 0; - const atkVal = card.attack ?? null; + const atkVal = card.attack ?? null; const defVal = card.defends ?? null; - const rngVal = card.range ?? null; - const rceVal = card.race ?? null; + const rngVal = card.range ?? null; + const rceVal = card.race ?? null; const statsHtml = `
- ${atkVal != null ? `${atkVal}` : ""} - ${defVal != null ? `${defVal}` : ""} - ${card.cooldown != null ? `${isReady ? "\u2713" : currentCd}` : ""} - ${rngVal != null ? `${SVG_RANGE} ${rngVal}` : ""} - ${rceVal != null ? `${SVG_RACE} ${rceVal}` : ""} + ${atkVal != null ? `${atkVal}` : ''} + ${defVal != null ? `${defVal}` : ''} + ${card.cooldown != null ? `${isReady ? '\u2713' : currentCd}` : ''} + ${rngVal != null ? `${SVG_RANGE} ${rngVal}` : ''} + ${rceVal != null ? `${SVG_RACE} ${rceVal}` : ''}
`; - const readyBadge = - isReady && isMyTurn - ? '
SPIELEN
' - : ""; + const readyBadge = (isReady && isMyTurn) ? '
SPIELEN
' : ''; slot.innerHTML = card.image ? `${statsHtml}${readyBadge}` @@ -136,8 +116,8 @@ function renderHandSlot(id) { ${card.name}
${statsHtml}${readyBadge}`; - slot.classList.toggle("hand-slot-ready", isReady); - slot.classList.add("hand-slot--filled"); + slot.classList.toggle('hand-slot-ready', isReady); + slot.classList.add('hand-slot--filled'); if (isReady && isMyTurn) { slot.draggable = true; @@ -149,31 +129,27 @@ function renderHandSlot(id) { } function setHandSlot(id, card) { - handSlotState[id] = { - card, - currentCd: card.cooldown ?? 0, - wasReduced: false, - }; + handSlotState[id] = { card, currentCd: card.cooldown ?? 0, wasReduced: false }; renderHandSlot(id); } function drawNextCard() { if (deckQueue.length === 0) return; - const freeSlot = handCardIds.find((id) => handSlotState[id] === null); + const freeSlot = handCardIds.find(id => handSlotState[id] === null); if (!freeSlot) return; const card = deckQueue.shift(); setHandSlot(freeSlot, card); - const countEl = document.getElementById("deck-count"); + const countEl = document.getElementById('deck-count'); if (countEl) countEl.textContent = deckQueue.length; } function tickHandCooldowns() { - handCardIds.forEach((id) => { + handCardIds.forEach(id => { const state = handSlotState[id]; if (!state) return; const baseCd = Number(state.card.cooldown || 0); if (state.currentCd <= 0 && !state.wasReduced) { - state.currentCd = baseCd > 0 ? Math.max(1, Math.floor(baseCd / 2)) : 0; + state.currentCd = baseCd > 0 ? Math.max(1, Math.floor(baseCd / 2)) : 0; state.wasReduced = true; } else if (state.currentCd > 0) { state.currentCd--; @@ -185,48 +161,43 @@ function tickHandCooldowns() { /* ── Deck laden ──────────────────────────────────────────── */ (async () => { try { - const urlP = new URLSearchParams(window.location.search); - const deckId = urlP.get("deck") || sessionStorage.getItem("selectedDeckId"); + const urlP = new URLSearchParams(window.location.search); + const deckId = urlP.get('deck') || sessionStorage.getItem('selectedDeckId'); if (!deckId) return; - const res = await fetch("/api/decks/" + deckId + "/cards"); + const res = await fetch('/api/decks/' + deckId + '/cards'); if (!res.ok) return; const cards = await res.json(); const expanded = []; - cards.forEach((c) => { - for (let i = 0; i < (c.amount ?? 1); i++) expanded.push(c); - }); + cards.forEach(c => { for (let i = 0; i < (c.amount ?? 1); i++) expanded.push(c); }); for (let i = expanded.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [expanded[i], expanded[j]] = [expanded[j], expanded[i]]; } - for (let i = 0; i < 3; i++) { - if (expanded[i]) setHandSlot(handCardIds[i], expanded[i]); - } + for (let i = 0; i < 3; i++) { if (expanded[i]) setHandSlot(handCardIds[i], expanded[i]); } deckQueue = expanded.slice(3); - const countEl = document.getElementById("deck-count"); + const countEl = document.getElementById('deck-count'); if (countEl) countEl.textContent = deckQueue.length; - } catch (e) { - console.error("[Battlefield] Deck laden:", e); - } + } catch (e) { console.error('[Battlefield] Deck laden:', e); } })(); + /* ═══════════════════════════════════════════════════════════ ZUG-TIMER ═══════════════════════════════════════════════════════════ */ const TURN_SECONDS = 30; -const TT_CIRCUM = 2 * Math.PI * 18; -let turnTimerInt = null; -let turnSecsLeft = TURN_SECONDS; -let amILeftPlayer = null; -let isMyTurn = false; +const TT_CIRCUM = 2 * Math.PI * 18; +let turnTimerInt = null; +let turnSecsLeft = TURN_SECONDS; +let amILeftPlayer = null; +let isMyTurn = false; function startTurnTimer(activeName) { clearInterval(turnTimerInt); turnSecsLeft = TURN_SECONDS; updateTimerUI(turnSecsLeft, activeName); - const wrap = document.getElementById("turn-timer-wrap"); - if (wrap) wrap.style.display = "flex"; + const wrap = document.getElementById('turn-timer-wrap'); + if (wrap) wrap.style.display = 'flex'; turnTimerInt = setInterval(() => { turnSecsLeft--; updateTimerUI(turnSecsLeft, activeName); @@ -239,27 +210,21 @@ function startTurnTimer(activeName) { function stopTurnTimer() { clearInterval(turnTimerInt); - const wrap = document.getElementById("turn-timer-wrap"); - if (wrap) wrap.style.display = "none"; + const wrap = document.getElementById('turn-timer-wrap'); + if (wrap) wrap.style.display = 'none'; } function updateTimerUI(secs, activeName) { - const num = document.getElementById("turn-timer-num"); - const circle = document.getElementById("tt-circle"); - const wrap = document.getElementById("turn-timer-wrap"); - if (num) num.textContent = secs; - if (wrap && activeName) wrap.title = activeName + " ist am Zug"; + const num = document.getElementById('turn-timer-num'); + const circle = document.getElementById('tt-circle'); + const wrap = document.getElementById('turn-timer-wrap'); + if (num) num.textContent = secs; + if (wrap && activeName) wrap.title = activeName + ' ist am Zug'; if (circle) { circle.style.strokeDashoffset = TT_CIRCUM * (1 - secs / TURN_SECONDS); - circle.style.stroke = - secs > 15 ? "#27ae60" : secs > 8 ? "#f39c12" : "#e74c3c"; - if (secs <= 8) { - num.style.color = "#e74c3c"; - num.style.animation = "tt-pulse 0.5s ease-in-out infinite"; - } else { - num.style.color = "#fff"; - num.style.animation = "none"; - } + circle.style.stroke = secs > 15 ? '#27ae60' : secs > 8 ? '#f39c12' : '#e74c3c'; + if (secs <= 8) { num.style.color = '#e74c3c'; num.style.animation = 'tt-pulse 0.5s ease-in-out infinite'; } + else { num.style.color = '#fff'; num.style.animation = 'none'; } } } @@ -268,27 +233,27 @@ function updateTimerUI(secs, activeName) { ═══════════════════════════════════════════════════════════ */ function setTurnState(myTurn, activeName) { isMyTurn = myTurn; - const btn = document.getElementById("end-turn-btn"); + const btn = document.getElementById('end-turn-btn'); if (!btn) return; - handCardIds.forEach((id) => renderHandSlot(id)); + handCardIds.forEach(id => renderHandSlot(id)); if (myTurn) { - btn.disabled = false; - btn.textContent = "Zug beenden"; - btn.style.opacity = "1"; - document.getElementById("turn-indicator")?.remove(); - startTurnTimer(activeName || "Du"); + btn.disabled = false; + btn.textContent = 'Zug beenden'; + btn.style.opacity = '1'; + document.getElementById('turn-indicator')?.remove(); + startTurnTimer(activeName || 'Du'); } else { - btn.disabled = true; - btn.style.opacity = "0.4"; + btn.disabled = true; + btn.style.opacity = '0.4'; showTurnIndicator(activeName); - startTurnTimer(activeName || "Gegner"); + startTurnTimer(activeName || 'Gegner'); } } function showTurnIndicator(activeName) { - document.getElementById("turn-indicator")?.remove(); - const ind = document.createElement("div"); - ind.id = "turn-indicator"; + document.getElementById('turn-indicator')?.remove(); + const ind = document.createElement('div'); + ind.id = 'turn-indicator'; ind.style.cssText = ` position:fixed;bottom:calc(var(--s)*200);left:50%;transform:translateX(-50%); background:linear-gradient(135deg,rgba(20,20,40,0.95),rgba(10,10,25,0.95)); @@ -297,9 +262,7 @@ function showTurnIndicator(activeName) { font-size:calc(var(--s)*11);letter-spacing:calc(var(--s)*3); padding:calc(var(--s)*8) calc(var(--s)*20);z-index:100;pointer-events:none; text-transform:uppercase;box-shadow:0 4px 20px rgba(0,0,0,0.6);`; - ind.textContent = activeName - ? `\u23F3 ${activeName} ist am Zug` - : "\u23F3 Gegner ist am Zug"; + ind.textContent = activeName ? `\u23F3 ${activeName} ist am Zug` : '\u23F3 Gegner ist am Zug'; document.body.appendChild(ind); } @@ -310,10 +273,10 @@ function endMyTurn() { setTurnState(false); tickHandCooldowns(); drawNextCard(); - socket.emit("end_turn", { matchId, slot: mySlot }); + socket.emit('end_turn', { matchId, slot: mySlot }); } -document.getElementById("end-turn-btn")?.addEventListener("click", endMyTurn); +document.getElementById('end-turn-btn')?.addEventListener('click', endMyTurn); /* ═══════════════════════════════════════════════════════════ KARTE AUF BOARD RENDERN @@ -327,8 +290,8 @@ document.getElementById("end-turn-btn")?.addEventListener("click", endMyTurn); const boardState = {}; /* ── Kampf + HP Animationen ────────────────────────────── */ -(function () { - const s = document.createElement("style"); +(function() { + const s = document.createElement('style'); s.textContent = ` @keyframes dmg-float { 0% { opacity:1; transform:translateX(-50%) translateY(0); } @@ -345,36 +308,34 @@ const boardState = {}; /* ── Spieler-Farbe auf Slot oder Avatar anwenden ────────────────── */ function applyOwnerStyle(el, owner) { if (!el || !owner) return; - const isLeft = owner === (window._leftSlot || "player1"); + const isLeft = owner === (window._leftSlot || 'player1'); if (isLeft) { - el.style.borderColor = "rgba(60, 140, 255, 0.95)"; - el.style.boxShadow = - "0 0 14px rgba(60, 140, 255, 0.5), inset 0 0 10px rgba(60,140,255,0.08)"; + el.style.borderColor = 'rgba(60, 140, 255, 0.95)'; + el.style.boxShadow = '0 0 14px rgba(60, 140, 255, 0.5), inset 0 0 10px rgba(60,140,255,0.08)'; } else { - el.style.borderColor = "rgba(220, 60, 60, 0.95)"; - el.style.boxShadow = - "0 0 14px rgba(220, 60, 60, 0.5), inset 0 0 10px rgba(220,60,60,0.08)"; + el.style.borderColor = 'rgba(220, 60, 60, 0.95)'; + el.style.boxShadow = '0 0 14px rgba(220, 60, 60, 0.5), inset 0 0 10px rgba(220,60,60,0.08)'; } } function buildStatsHtml(card) { - const atk = card.attack ?? null; + const atk = card.attack ?? null; const def = card.defends ?? null; - const cd = card.cooldown ?? null; - const rng = card.range ?? null; - const rce = card.race ?? null; + const cd = card.cooldown ?? null; + const rng = card.range ?? null; + const rce = card.race ?? null; return `
- ${atk != null ? `${atk}` : ""} - ${def != null ? `${def}` : ""} - ${cd != null ? `${cd}` : ""} - ${rng != null ? `${SVG_RANGE} ${rng}` : ""} - ${rce != null ? `${SVG_RACE} ${rce}` : ""} + ${atk != null ? `${atk}` : ''} + ${def != null ? `${def}` : ''} + ${cd != null ? `${cd}` : ''} + ${rng != null ? `${SVG_RANGE} ${rng}` : ''} + ${rce != null ? `${SVG_RACE} ${rce}` : ''}
`; } function renderCardOnBoard(slotEl, card, owner) { const statsHtml = buildStatsHtml(card); - slotEl.classList.add("slot-occupied"); + slotEl.classList.add('slot-occupied'); applyOwnerStyle(slotEl, owner); slotEl.innerHTML = card.image ? `${statsHtml}` @@ -383,7 +344,7 @@ function renderCardOnBoard(slotEl, card, owner) { function renderCardInSlot(slot, card) { if (!slot || !card) return; - slot.classList.add("slot-occupied"); + slot.classList.add('slot-occupied'); slot.innerHTML = card.image ? `${buildStatsHtml(card)}` : `
\u2694\uFE0F${card.name}
${buildStatsHtml(card)}`; @@ -394,15 +355,13 @@ function clearBoardSlot(slotId) { delete boardState[slotId]; const el = document.getElementById(slotId); if (!el) return; - el.classList.remove("slot-occupied"); - el.style.borderColor = ""; - el.style.boxShadow = ""; - el.innerHTML = - '\u2736' + - slotId.split("-slot-")[1] + - ""; + el.classList.remove('slot-occupied'); + el.style.borderColor = ''; + el.style.boxShadow = ''; + el.innerHTML = '\u2736' + slotId.split('-slot-')[1] + ''; } + /* ═══════════════════════════════════════════════════════════ SOCKET & ARENA-JOIN ═══════════════════════════════════════════════════════════ */ @@ -410,60 +369,37 @@ const socket = io(); let myIngameName = null; function emitArenaJoin(me) { - myIngameName = - (me && (me.ingame_name || me.username || me.name || String(me.id))) || - "Spieler"; - const myNameEl = document.getElementById( - amIPlayer1 ? "nameLeft" : "nameRight", - ); + myIngameName = (me && (me.ingame_name || me.username || me.name || String(me.id))) || 'Spieler'; + const myNameEl = document.getElementById(amIPlayer1 ? 'nameLeft' : 'nameRight'); if (myNameEl) myNameEl.textContent = myIngameName; - console.log("[1v1] emitArenaJoin:", { - matchId, - slot: mySlot, - name: myIngameName, - }); - socket.emit("arena_join", { - matchId, - slot: mySlot, - accountId: me?.id ?? null, - playerName: myIngameName, - }); + console.log('[1v1] emitArenaJoin:', { matchId, slot: mySlot, name: myIngameName }); + socket.emit('arena_join', { matchId, slot: mySlot, accountId: me?.id ?? null, playerName: myIngameName }); } function fetchAndJoin() { - fetch("/arena/me") - .then((r) => r.json()) - .then((me) => emitArenaJoin(me)) - .catch(() => { - console.warn("[1v1] /arena/me fehlgeschlagen"); - emitArenaJoin(null); - }); + fetch('/arena/me') + .then(r => r.json()) + .then(me => emitArenaJoin(me)) + .catch(() => { console.warn('[1v1] /arena/me fehlgeschlagen'); emitArenaJoin(null); }); } fetchAndJoin(); -socket.on("connect", () => { - console.log("[1v1] Socket connected:", socket.id); - if (myIngameName && myIngameName !== "Spieler") { - socket.emit("arena_join", { - matchId, - slot: mySlot, - accountId: null, - playerName: myIngameName, - }); +socket.on('connect', () => { + console.log('[1v1] Socket connected:', socket.id); + if (myIngameName && myIngameName !== 'Spieler') { + socket.emit('arena_join', { matchId, slot: mySlot, accountId: null, playerName: myIngameName }); } else { fetchAndJoin(); } }); -socket.on("connect_error", (err) => - console.error("[1v1] connect_error:", err.message), -); +socket.on('connect_error', err => console.error('[1v1] connect_error:', err.message)); const readyFallbackTimer = setTimeout(() => { - console.warn("[1v1] Kein arena-Event nach 10s"); - document.getElementById("connecting-overlay")?.remove(); - const lo = document.getElementById("board-lock-overlay"); - if (lo) lo.style.display = "flex"; + console.warn('[1v1] Kein arena-Event nach 10s'); + document.getElementById('connecting-overlay')?.remove(); + const lo = document.getElementById('board-lock-overlay'); + if (lo) lo.style.display = 'flex'; }, 10000); /* ── Board-Sync ──────────────────────────────────────────── */ @@ -473,81 +409,66 @@ const readyFallbackTimer = setTimeout(() => { */ function applyBoardSync(cards) { if (!cards || !cards.length) return; - cards.forEach((cd) => { + cards.forEach(cd => { const slotEl = document.getElementById(cd.boardSlot); if (!slotEl || boardState[cd.boardSlot]) return; - const owner = cd.owner ?? cd.slot ?? "unknown"; + const owner = cd.owner ?? (cd.slot ?? 'unknown'); boardState[cd.boardSlot] = { card: cd.card, owner }; renderCardOnBoard(slotEl, cd.card, cd.owner); }); - console.log("[1v1] Board sync:", cards.length, "Karten"); + console.log('[1v1] Board sync:', cards.length, 'Karten'); } /* ── Socket Events ───────────────────────────────────────── */ -socket.on("arena_opponent_joined", (data) => { - console.log("[Arena] arena_opponent_joined:", data); +socket.on('arena_opponent_joined', data => { + console.log('[Arena] arena_opponent_joined:', data); clearTimeout(readyFallbackTimer); - document.getElementById(amIPlayer1 ? "nameRight" : "nameLeft").textContent = - data.name || "Gegner"; - document.getElementById("connecting-overlay")?.remove(); - const lo = document.getElementById("board-lock-overlay"); - if (lo) lo.style.display = "flex"; + document.getElementById(amIPlayer1 ? 'nameRight' : 'nameLeft').textContent = data.name || 'Gegner'; + document.getElementById('connecting-overlay')?.remove(); + const lo = document.getElementById('board-lock-overlay'); + if (lo) lo.style.display = 'flex'; }); -socket.on("arena_ready", (data) => { - console.log("[Arena] arena_ready:", data); +socket.on('arena_ready', data => { + console.log('[Arena] arena_ready:', data); clearTimeout(readyFallbackTimer); - document.getElementById("connecting-overlay")?.remove(); + document.getElementById('connecting-overlay')?.remove(); if (data.boardSync) applyBoardSync(data.boardSync); if (data.hp && data.maxHp) applyHpFromEvent(data); const oppName = amIPlayer1 ? data.player2 : data.player1; - const oppEl = document.getElementById(amIPlayer1 ? "nameRight" : "nameLeft"); - const urlOppName = _urlParams.get("opponent"); - if ( - oppEl && - oppName && - !["Spieler", "Spieler 1", "Spieler 2", "Gegner"].includes(oppName) - ) { + const oppEl = document.getElementById(amIPlayer1 ? 'nameRight' : 'nameLeft'); + const urlOppName = _urlParams.get('opponent'); + if (oppEl && oppName && !['Spieler','Spieler 1','Spieler 2','Gegner'].includes(oppName)) { oppEl.textContent = oppName; - } else if ( - oppEl && - urlOppName && - (!oppEl.textContent || oppEl.textContent === "Gegner") - ) { + } else if (oppEl && urlOppName && (!oppEl.textContent || oppEl.textContent === 'Gegner')) { oppEl.textContent = decodeURIComponent(urlOppName); } - const myEl = document.getElementById(amIPlayer1 ? "nameLeft" : "nameRight"); + const myEl = document.getElementById(amIPlayer1 ? 'nameLeft' : 'nameRight'); if (myEl && myIngameName) myEl.textContent = myIngameName; - const lo = document.getElementById("board-lock-overlay"); - if (lo) lo.style.display = "flex"; + const lo = document.getElementById('board-lock-overlay'); + if (lo) lo.style.display = 'flex'; }); -socket.on("board_sync", (data) => { - if (data.cards) applyBoardSync(data.cards); -}); +socket.on('board_sync', data => { if (data.cards) applyBoardSync(data.cards); }); -socket.on("turn_change", (data) => { - document.getElementById("board-lock-overlay")?.remove(); - document.getElementById("connecting-overlay")?.remove(); +socket.on('turn_change', data => { + document.getElementById('board-lock-overlay')?.remove(); + document.getElementById('connecting-overlay')?.remove(); if (data.boardSync) applyBoardSync(data.boardSync); - const activeSlot = data.activeSlot; - const nowMyTurn = activeSlot === mySlot; - const activeNameEl = document.getElementById( - activeSlot === (window._leftSlot || "player1") ? "nameLeft" : "nameRight", - ); - const activeName = activeNameEl?.textContent || (nowMyTurn ? "Du" : "Gegner"); + const activeSlot = data.activeSlot; + const nowMyTurn = activeSlot === mySlot; + const activeNameEl = document.getElementById(activeSlot === (window._leftSlot || 'player1') ? 'nameLeft' : 'nameRight'); + const activeName = activeNameEl?.textContent || (nowMyTurn ? 'Du' : 'Gegner'); console.log(`[1v1] turn_change: ${activeSlot} | meinZug: ${nowMyTurn}`); setTurnState(nowMyTurn, activeName); if (data.hp && data.maxHp) applyHpFromEvent(data); }); -socket.on("turn_started", (data) => { - const myT = data.slot === mySlot; - const nameEl = document.getElementById( - data.slot === (window._leftSlot || "player1") ? "nameLeft" : "nameRight", - ); - setTurnState(myT, nameEl?.textContent || (myT ? "Du" : "Gegner")); +socket.on('turn_started', data => { + const myT = data.slot === mySlot; + const nameEl = document.getElementById(data.slot === (window._leftSlot || 'player1') ? 'nameLeft' : 'nameRight'); + setTurnState(myT, nameEl?.textContent || (myT ? 'Du' : 'Gegner')); }); /* ═══════════════════════════════════════════════════════════ @@ -558,95 +479,80 @@ let myReady = false; function handleBereit() { if (myReady) return; myReady = true; - const btn = document.getElementById("bereit-btn"); - btn.textContent = "\u2714 BEREIT"; - btn.classList.add("bereit-clicked"); + const btn = document.getElementById('bereit-btn'); + btn.textContent = '\u2714 BEREIT'; + btn.classList.add('bereit-clicked'); btn.disabled = true; - socket.emit("player_ready", { matchId, slot: mySlot }); + socket.emit('player_ready', { matchId, slot: mySlot }); } const CIRCUMFERENCE = 2 * Math.PI * 34; -const timerCircle = document.getElementById("timer-circle"); +const timerCircle = document.getElementById('timer-circle'); if (timerCircle) timerCircle.style.strokeDasharray = CIRCUMFERENCE; -socket.on("ready_timer", (data) => { - const num = document.getElementById("ready-timer-number"); +socket.on('ready_timer', data => { + const num = document.getElementById('ready-timer-number'); if (num) num.textContent = data.remaining; if (timerCircle) { - timerCircle.style.strokeDashoffset = - CIRCUMFERENCE * (1 - data.remaining / 30); - timerCircle.style.stroke = - data.remaining > 15 - ? "#27ae60" - : data.remaining > 7 - ? "#f39c12" - : "#e74c3c"; + timerCircle.style.strokeDashoffset = CIRCUMFERENCE * (1 - data.remaining / 30); + timerCircle.style.stroke = data.remaining > 15 ? '#27ae60' : data.remaining > 7 ? '#f39c12' : '#e74c3c'; } }); -socket.on("ready_status", (data) => { - console.log("[1v1] ready_status:", data); - const pip1 = document.getElementById("pip-player1"); - const pip2 = document.getElementById("pip-player2"); +socket.on('ready_status', data => { + console.log('[1v1] ready_status:', data); + const pip1 = document.getElementById('pip-player1'); + const pip2 = document.getElementById('pip-player2'); if (data.readySlots) { - if (data.readySlots.includes("player1") && pip1) - pip1.textContent = - "\u2705 " + - (document.getElementById("nameLeft")?.textContent || "Spieler 1"); - if (data.readySlots.includes("player2") && pip2) - pip2.textContent = - "\u2705 " + - (document.getElementById("nameRight")?.textContent || "Spieler 2"); + if (data.readySlots.includes('player1') && pip1) + pip1.textContent = '\u2705 ' + (document.getElementById('nameLeft')?.textContent || 'Spieler 1'); + if (data.readySlots.includes('player2') && pip2) + pip2.textContent = '\u2705 ' + (document.getElementById('nameRight')?.textContent || 'Spieler 2'); } if (data.readyCount === 2) { - const seed = matchId.split("").reduce((a, c) => a + c.charCodeAt(0), 0); - const flip = seed % 2 === 1; - const myName = myIngameName || "Spieler"; - const oppEl = document.getElementById( - amIPlayer1 ? "nameRight" : "nameLeft", - ); - const oppName = oppEl?.textContent || "Gegner"; - const p1Name = amIPlayer1 ? myName : oppName; - const p2Name = amIPlayer1 ? oppName : myName; - const leftName = flip ? p2Name : p1Name; + const seed = matchId.split('').reduce((a, c) => a + c.charCodeAt(0), 0); + const flip = seed % 2 === 1; + const myName = myIngameName || 'Spieler'; + const oppEl = document.getElementById(amIPlayer1 ? 'nameRight' : 'nameLeft'); + const oppName = oppEl?.textContent || 'Gegner'; + const p1Name = amIPlayer1 ? myName : oppName; + const p2Name = amIPlayer1 ? oppName : myName; + const leftName = flip ? p2Name : p1Name; const rightName = flip ? p1Name : p2Name; - document.getElementById("nameLeft").textContent = leftName; - document.getElementById("nameRight").textContent = rightName; + document.getElementById('nameLeft').textContent = leftName; + document.getElementById('nameRight').textContent = rightName; - ["avLeft", "avRight"].forEach((avId) => { - const av = document.getElementById(avId); - const ph = av?.querySelector(".av-placeholder"); - const name = avId === "avLeft" ? leftName : rightName; - if (ph) - ph.innerHTML = `
${name}
`; + ['avLeft', 'avRight'].forEach(avId => { + const av = document.getElementById(avId); + const ph = av?.querySelector('.av-placeholder'); + const name = avId === 'avLeft' ? leftName : rightName; + if (ph) ph.innerHTML = `
${name}
`; }); - document.getElementById("board-lock-overlay")?.remove(); - amILeftPlayer = flip ? mySlot === "player2" : mySlot === "player1"; + document.getElementById('board-lock-overlay')?.remove(); + amILeftPlayer = flip ? mySlot === 'player2' : mySlot === 'player1'; // Avatare farbig umranden: links=blau, rechts=rot - const _avL = document.getElementById("avLeft"); - const _avR = document.getElementById("avRight"); + const _avL = document.getElementById('avLeft'); + const _avR = document.getElementById('avRight'); if (_avL) { - _avL.style.borderColor = "rgba(60, 140, 255, 0.95)"; - _avL.style.boxShadow = - "0 0 28px rgba(60, 140, 255, 0.55), inset 0 0 20px rgba(0,0,0,0.5)"; + _avL.style.borderColor = 'rgba(60, 140, 255, 0.95)'; + _avL.style.boxShadow = '0 0 28px rgba(60, 140, 255, 0.55), inset 0 0 20px rgba(0,0,0,0.5)'; } if (_avR) { - _avR.style.borderColor = "rgba(220, 60, 60, 0.95)"; - _avR.style.boxShadow = - "0 0 28px rgba(220, 60, 60, 0.55), inset 0 0 20px rgba(0,0,0,0.5)"; + _avR.style.borderColor = 'rgba(220, 60, 60, 0.95)'; + _avR.style.boxShadow = '0 0 28px rgba(220, 60, 60, 0.55), inset 0 0 20px rgba(0,0,0,0.5)'; } - const board = document.querySelector(".board"); + const board = document.querySelector('.board'); if (board) { - board.classList.remove("my-side-left", "my-side-right"); - board.classList.add(amILeftPlayer ? "my-side-left" : "my-side-right"); + board.classList.remove('my-side-left', 'my-side-right'); + board.classList.add(amILeftPlayer ? 'my-side-left' : 'my-side-right'); } - const leftSlot = flip ? "player2" : "player1"; + const leftSlot = flip ? 'player2' : 'player1'; window._leftSlot = leftSlot; console.log(`[1v1] linker Slot: ${leftSlot} | ich: ${mySlot}`); @@ -656,80 +562,76 @@ socket.on("ready_status", (data) => { const el = document.getElementById(slotId); if (el && entry?.owner) applyOwnerStyle(el, entry.owner); }); - if (mySlot === "player1") { - socket.emit("start_turn_request", { matchId, starterSlot: leftSlot }); + if (mySlot === 'player1') { + socket.emit('start_turn_request', { matchId, starterSlot: leftSlot }); } } }); + /* ═══════════════════════════════════════════════════════════ DRAG & DROP ═══════════════════════════════════════════════════════════ */ function isMyZone(slotIndex) { - const idx = Number(slotIndex); + const idx = Number(slotIndex); const iAmLeft = amILeftPlayer !== null ? amILeftPlayer : amIPlayer1; return iAmLeft ? idx <= 3 : idx >= 9; } function setDropZones(active) { - document.querySelectorAll(".card-slot").forEach((slot) => { + document.querySelectorAll('.card-slot').forEach(slot => { const idx = Number(slot.dataset.slotIndex); if (!isMyZone(idx)) return; - if (active && !boardState[slot.id]) slot.classList.add("drop-zone-active"); - else slot.classList.remove("drop-zone-active", "drop-zone-hover"); + if (active && !boardState[slot.id]) slot.classList.add('drop-zone-active'); + else slot.classList.remove('drop-zone-active', 'drop-zone-hover'); }); } let draggedCardSlotId = null; -document.getElementById("handArea").addEventListener("dragstart", (e) => { - const slot = e.target.closest("[data-card-slot-id]"); - if (!slot || !isMyTurn) { - e.preventDefault(); - return; - } +document.getElementById('handArea').addEventListener('dragstart', e => { + const slot = e.target.closest('[data-card-slot-id]'); + if (!slot || !isMyTurn) { e.preventDefault(); return; } draggedCardSlotId = slot.dataset.cardSlotId; - slot.classList.add("dragging"); - e.dataTransfer.effectAllowed = "move"; - e.dataTransfer.setData("text/plain", draggedCardSlotId); + slot.classList.add('dragging'); + e.dataTransfer.effectAllowed = 'move'; + e.dataTransfer.setData('text/plain', draggedCardSlotId); setTimeout(() => setDropZones(true), 0); }); -document.getElementById("handArea").addEventListener("dragend", (e) => { - const slot = e.target.closest("[data-card-slot-id]"); - if (slot) slot.classList.remove("dragging"); +document.getElementById('handArea').addEventListener('dragend', e => { + const slot = e.target.closest('[data-card-slot-id]'); + if (slot) slot.classList.remove('dragging'); setDropZones(false); draggedCardSlotId = null; }); -["row1", "row2"].forEach((rowId) => { +['row1', 'row2'].forEach(rowId => { const row = document.getElementById(rowId); - row.addEventListener("dragover", (e) => { - const slot = e.target.closest(".card-slot"); + row.addEventListener('dragover', e => { + const slot = e.target.closest('.card-slot'); if (!slot) return; const idx = Number(slot.dataset.slotIndex); if (!isMyZone(idx) || boardState[slot.id]) return; e.preventDefault(); - e.dataTransfer.dropEffect = "move"; - row - .querySelectorAll(".drop-zone-hover") - .forEach((s) => s.classList.remove("drop-zone-hover")); - slot.classList.add("drop-zone-hover"); + e.dataTransfer.dropEffect = 'move'; + row.querySelectorAll('.drop-zone-hover').forEach(s => s.classList.remove('drop-zone-hover')); + slot.classList.add('drop-zone-hover'); }); - row.addEventListener("dragleave", (e) => { - const slot = e.target.closest(".card-slot"); - if (slot) slot.classList.remove("drop-zone-hover"); + row.addEventListener('dragleave', e => { + const slot = e.target.closest('.card-slot'); + if (slot) slot.classList.remove('drop-zone-hover'); }); - row.addEventListener("drop", (e) => { + row.addEventListener('drop', e => { e.preventDefault(); - const slot = e.target.closest(".card-slot"); + const slot = e.target.closest('.card-slot'); if (!slot) return; const idx = Number(slot.dataset.slotIndex); if (!isMyZone(idx) || boardState[slot.id]) return; - const sourceId = draggedCardSlotId || e.dataTransfer.getData("text/plain"); + const sourceId = draggedCardSlotId || e.dataTransfer.getData('text/plain'); if (!sourceId) return; const cardState = handSlotState[sourceId]; if (!cardState || cardState.currentCd > 0) return; @@ -740,35 +642,29 @@ document.getElementById("handArea").addEventListener("dragend", (e) => { /* ── boardState mit owner speichern ─────────────────── */ boardState[slot.id] = { card: cardState.card, owner: mySlot }; renderCardOnBoard(slot, cardState.card, mySlot); - slot.classList.remove("drop-zone-active", "drop-zone-hover"); + slot.classList.remove('drop-zone-active', 'drop-zone-hover'); - socket.emit("card_played", { - matchId, - slot: mySlot, - boardSlot: slot.id, - row: slot.dataset.row, - slotIndex: idx, - card: cardState.card, + socket.emit('card_played', { + matchId, slot: mySlot, boardSlot: slot.id, + row: slot.dataset.row, slotIndex: idx, card: cardState.card, }); console.log(`[1v1] Karte gespielt: ${cardState.card.name} -> ${slot.id}`); }); }); -socket.on("card_played", (data) => { +socket.on('card_played', data => { if (data.slot === mySlot) return; if (boardState[data.boardSlot]) return; const slotEl = document.getElementById(data.boardSlot); - if (!slotEl) { - console.warn("[1v1] card_played: Slot fehlt:", data.boardSlot); - return; - } + if (!slotEl) { console.warn('[1v1] card_played: Slot fehlt:', data.boardSlot); return; } /* ── boardState mit owner des Gegners speichern ─────── */ boardState[data.boardSlot] = { card: data.card, owner: data.slot }; renderCardOnBoard(slotEl, data.card, data.slot); - console.log("[1v1] Gegner Karte:", data.card?.name, "->", data.boardSlot); + console.log('[1v1] Gegner Karte:', data.card?.name, '->', data.boardSlot); }); + /* ═══════════════════════════════════════════════════════════ KAMPFPHASE – CLIENT-SEITIGE VERARBEITUNG Der Server sendet nach end_turn ein 'combat_phase'-Event mit: @@ -776,16 +672,16 @@ socket.on("card_played", (data) => { ═══════════════════════════════════════════════════════════ */ /* Timing (ms) zwischen einzelnen Kampf-Events */ -const COMBAT_DELAY_MOVE = 350; +const COMBAT_DELAY_MOVE = 350; const COMBAT_DELAY_ATTACK = 450; -const COMBAT_DELAY_DIE = 300; +const COMBAT_DELAY_DIE = 300; /* Kampf-Log-Banner (erscheint kurz über dem Board) */ -function showCombatBanner(text, color = "#f0d060") { - const old = document.getElementById("combat-banner"); +function showCombatBanner(text, color = '#f0d060') { + const old = document.getElementById('combat-banner'); if (old) old.remove(); - const el = document.createElement("div"); - el.id = "combat-banner"; + const el = document.createElement('div'); + el.id = 'combat-banner'; el.style.cssText = ` position:fixed;top:18%;left:50%;transform:translateX(-50%); background:rgba(10,8,5,0.92);border:1px solid ${color}; @@ -803,10 +699,8 @@ function flashSlot(slotId, color, durationMs = 300) { const el = document.getElementById(slotId); if (!el) return; el.style.transition = `box-shadow ${durationMs / 2}ms ease`; - el.style.boxShadow = `0 0 18px 6px ${color}`; - setTimeout(() => { - el.style.boxShadow = ""; - }, durationMs); + el.style.boxShadow = `0 0 18px 6px ${color}`; + setTimeout(() => { el.style.boxShadow = ''; }, durationMs); } /* Karte bewegen: Slot A → Slot B */ @@ -815,7 +709,7 @@ function applyMoveEvent(ev) { if (!entry) return; const fromEl = document.getElementById(ev.from); - const toEl = document.getElementById(ev.to); + const toEl = document.getElementById(ev.to); if (!fromEl || !toEl) return; /* boardState aktualisieren */ @@ -823,11 +717,8 @@ function applyMoveEvent(ev) { boardState[ev.to] = entry; /* DOM aktualisieren */ - fromEl.classList.remove("slot-occupied"); - fromEl.innerHTML = - '\u2736' + - ev.from.split("-slot-")[1] + - ""; + fromEl.classList.remove('slot-occupied'); + fromEl.innerHTML = '\u2736' + ev.from.split('-slot-')[1] + ''; renderCardOnBoard(toEl, entry.card, entry.owner); @@ -837,37 +728,35 @@ function applyMoveEvent(ev) { /* Angriff: Verteidigung des Ziels aktualisieren */ function applyAttackEvent(ev) { const attackerEntry = boardState[ev.from]; - const targetEntry = boardState[ev.to]; + const targetEntry = boardState[ev.to]; if (!attackerEntry || !targetEntry) return; /* Verteidigung im boardState aktualisieren */ targetEntry.card = { ...targetEntry.card, defends: ev.remainingDef }; /* Visuelles Feedback */ - flashSlot(ev.from, "rgba(255,200,50,0.7)", 250); // Angreifer leuchtet gold - flashSlot(ev.to, "rgba(220,50,50,0.85)", 350); // Ziel leuchtet rot + flashSlot(ev.from, 'rgba(255,200,50,0.7)', 250); // Angreifer leuchtet gold + flashSlot(ev.to, 'rgba(220,50,50,0.85)', 350); // Ziel leuchtet rot /* Verteidigungswert im DOM sofort aktualisieren */ const targetEl = document.getElementById(ev.to); if (targetEl) { - const defEl = targetEl.querySelector(".cs-def"); + const defEl = targetEl.querySelector('.cs-def'); if (defEl) { defEl.textContent = ev.remainingDef; - defEl.style.color = ev.remainingDef <= 2 ? "#e74c3c" : ""; + defEl.style.color = ev.remainingDef <= 2 ? '#e74c3c' : ''; } } - console.log( - `[Combat] Angriff: ${attackerEntry.card.name} → ${targetEntry.card.name} (${ev.damage} Schaden, verbleibend: ${ev.remainingDef})`, - ); + console.log(`[Combat] Angriff: ${attackerEntry.card.name} → ${targetEntry.card.name} (${ev.damage} Schaden, verbleibend: ${ev.remainingDef})`); } /* Karte stirbt */ function applyDieEvent(ev) { const entry = boardState[ev.slotId]; - const name = entry?.card?.name ?? "???"; + const name = entry?.card?.name ?? '???'; - flashSlot(ev.slotId, "rgba(200,50,50,0.9)", 500); + flashSlot(ev.slotId, 'rgba(200,50,50,0.9)', 500); setTimeout(() => { clearBoardSlot(ev.slotId); @@ -878,11 +767,11 @@ function applyDieEvent(ev) { /* Finales Board nach Kampfphase vollständig anwenden (Sicherheits-Sync) */ function applyFinalBoard(finalBoard) { /* Erst alles leeren */ - Object.keys(boardState).forEach((sid) => clearBoardSlot(sid)); + Object.keys(boardState).forEach(sid => clearBoardSlot(sid)); /* Dann neuen Zustand setzen */ if (!finalBoard || !finalBoard.length) return; - finalBoard.forEach((entry) => { + finalBoard.forEach(entry => { const slotEl = document.getElementById(entry.boardSlot); if (!slotEl) return; boardState[entry.boardSlot] = { card: entry.card, owner: entry.owner }; @@ -891,12 +780,12 @@ function applyFinalBoard(finalBoard) { } /* Haupt-Handler für combat_phase */ -socket.on("combat_phase", (data) => { - const events = data.events ?? []; +socket.on('combat_phase', data => { + const events = data.events ?? []; const finalBoard = data.finalBoard ?? []; console.log(`[Combat] Kampfphase startet: ${events.length} Events`); - showCombatBanner("\u2694\uFE0F KAMPFPHASE"); + showCombatBanner('\u2694\uFE0F KAMPFPHASE'); if (events.length === 0) { /* Keine Karten im Spiel → direkt final sync */ @@ -907,16 +796,18 @@ socket.on("combat_phase", (data) => { /* Events sequenziell abarbeiten */ let delay = 600; // kurze Pause nach Banner - events.forEach((ev) => { + events.forEach(ev => { const thisDelay = delay; - if (ev.type === "move") { + if (ev.type === 'move') { delay += COMBAT_DELAY_MOVE; setTimeout(() => applyMoveEvent(ev), thisDelay); - } else if (ev.type === "attack") { + + } else if (ev.type === 'attack') { delay += COMBAT_DELAY_ATTACK; setTimeout(() => applyAttackEvent(ev), thisDelay); - } else if (ev.type === "die") { + + } else if (ev.type === 'die') { delay += COMBAT_DELAY_DIE; setTimeout(() => applyDieEvent(ev), thisDelay); } @@ -926,45 +817,41 @@ socket.on("combat_phase", (data) => { delay += 500; setTimeout(() => { applyFinalBoard(finalBoard); - console.log("[Combat] Kampfphase abgeschlossen, Board synchronisiert."); + console.log('[Combat] Kampfphase abgeschlossen, Board synchronisiert.'); }, delay); }); + /* ═══════════════════════════════════════════════════════════ AVATAR HP – Anzeige & Animationen ═══════════════════════════════════════════════════════════ */ function updateHpDisplay(slot, currentHp, maxHp) { - const isLeft = slot === (window._leftSlot || "player1"); - const orbEl = document.getElementById(isLeft ? "orbLeft" : "orbRight"); - const hpEl = document.getElementById(isLeft ? "hpLeft" : "hpRight"); - const avEl = document.getElementById(isLeft ? "avLeft" : "avRight"); + const isLeft = slot === (window._leftSlot || 'player1'); + const orbEl = document.getElementById(isLeft ? 'orbLeft' : 'orbRight'); + const hpEl = document.getElementById(isLeft ? 'hpLeft' : 'hpRight'); + const avEl = document.getElementById(isLeft ? 'avLeft' : 'avRight'); if (orbEl) orbEl.textContent = currentHp; - if (hpEl) hpEl.textContent = currentHp; + if (hpEl) hpEl.textContent = currentHp; /* Farbe je nach HP-Prozent */ - const pct = maxHp > 0 ? currentHp / maxHp : 0; - const color = pct > 0.5 ? "#e74c3c" : pct > 0.25 ? "#e67e22" : "#8b0000"; + const pct = maxHp > 0 ? currentHp / maxHp : 0; + const color = pct > 0.5 ? '#e74c3c' : pct > 0.25 ? '#e67e22' : '#8b0000'; if (orbEl) { orbEl.style.background = `radial-gradient(circle at 40% 35%, ${color}, #3a0000)`; - orbEl.style.boxShadow = `0 0 12px ${color}cc`; + orbEl.style.boxShadow = `0 0 12px ${color}cc`; } /* Avatar-Schüttelanimation + roter Flash */ if (avEl) { - avEl.style.transition = "transform 0.07s ease"; - avEl.style.transform = "scale(1.07)"; - setTimeout(() => { - avEl.style.transform = "scale(0.96)"; - }, 70); - setTimeout(() => { - avEl.style.transform = ""; - }, 150); + avEl.style.transition = 'transform 0.07s ease'; + avEl.style.transform = 'scale(1.07)'; + setTimeout(() => { avEl.style.transform = 'scale(0.96)'; }, 70); + setTimeout(() => { avEl.style.transform = ''; }, 150); - const flash = document.createElement("div"); - flash.style.cssText = - "position:absolute;inset:0;border-radius:inherit;background:rgba(220,50,50,0.4);z-index:20;pointer-events:none;"; + const flash = document.createElement('div'); + flash.style.cssText = 'position:absolute;inset:0;border-radius:inherit;background:rgba(220,50,50,0.4);z-index:20;pointer-events:none;'; avEl.appendChild(flash); setTimeout(() => flash.remove(), 320); } @@ -972,29 +859,28 @@ function updateHpDisplay(slot, currentHp, maxHp) { function applyHpFromEvent(data) { if (!data.hp || !data.maxHp) return; - ["player1", "player2"].forEach((slot) => { - if (data.hp[slot] != null) - updateHpDisplay(slot, data.hp[slot], data.maxHp[slot] ?? data.hp[slot]); + ['player1','player2'].forEach(slot => { + if (data.hp[slot] != null) updateHpDisplay(slot, data.hp[slot], data.maxHp[slot] ?? data.hp[slot]); }); } /* Initiale HP beim Spielstart */ -socket.on("hp_init", (data) => { +socket.on('hp_init', data => { applyHpFromEvent(data); - console.log("[HP] Init:", data.hp); + console.log('[HP] Init:', data.hp); }); /* Avatar getroffen */ -socket.on("avatar_damaged", (data) => { +socket.on('avatar_damaged', data => { const { slot, damage, remainingHp, maxHp } = data; updateHpDisplay(slot, remainingHp, maxHp); console.log(`[HP] ${slot} -${damage} → ${remainingHp}/${maxHp}`); /* Schadens-Zahl einblenden */ - const isLeft = slot === (window._leftSlot || "player1"); - const avEl = document.getElementById(isLeft ? "avLeft" : "avRight"); + const isLeft = slot === (window._leftSlot || 'player1'); + const avEl = document.getElementById(isLeft ? 'avLeft' : 'avRight'); if (avEl) { - const dmg = document.createElement("div"); + const dmg = document.createElement('div'); dmg.textContent = `-${damage}`; dmg.style.cssText = ` position:absolute;top:15%;left:50%;transform:translateX(-50%); @@ -1007,23 +893,70 @@ socket.on("avatar_damaged", (data) => { } }); +/* ═══════════════════════════════════════════════════════════ + KI-MATCH SETUP (ht_ai_setup) + Setzt Links/Rechts explizit ohne seed-basierten Flip. + Nur für Daily-KI-Matches – umgeht den ready_status Flip. +═══════════════════════════════════════════════════════════ */ +socket.on('ht_ai_setup', data => { + const { playerSlot, leftSlot, playerName, aiName, hp, maxHp } = data; + + /* Links/Rechts festlegen */ + window._leftSlot = leftSlot || 'player1'; + amILeftPlayer = mySlot === window._leftSlot; + + /* Namen setzen */ + const nameLeftEl = document.getElementById('nameLeft'); + const nameRightEl = document.getElementById('nameRight'); + if (nameLeftEl) nameLeftEl.textContent = amILeftPlayer ? (playerName || 'Du') : (aiName || 'Wächter'); + if (nameRightEl) nameRightEl.textContent = amILeftPlayer ? (aiName || 'Wächter') : (playerName || 'Du'); + + /* Avatar-Platzhalter */ + ['avLeft', 'avRight'].forEach(avId => { + const av = document.getElementById(avId); + const ph = av?.querySelector('.av-placeholder'); + const name = avId === 'avLeft' ? nameLeftEl?.textContent : nameRightEl?.textContent; + if (ph) ph.innerHTML = `
${name}
`; + }); + + /* Board-Seite festlegen */ + const board = document.querySelector('.board'); + if (board) { + board.classList.remove('my-side-left', 'my-side-right'); + board.classList.add(amILeftPlayer ? 'my-side-left' : 'my-side-right'); + } + + /* Avatar-Farben: Spieler=blau, KI=rot */ + const avL = document.getElementById('avLeft'); + const avR = document.getElementById('avRight'); + if (avL) { avL.style.borderColor = 'rgba(60,140,255,.95)'; avL.style.boxShadow = '0 0 28px rgba(60,140,255,.55),inset 0 0 20px rgba(0,0,0,.5)'; } + if (avR) { avR.style.borderColor = 'rgba(220,60,60,.95)'; avR.style.boxShadow = '0 0 28px rgba(220,60,60,.55),inset 0 0 20px rgba(0,0,0,.5)'; } + + /* HP initialisieren */ + if (hp && maxHp) applyHpFromEvent({ hp, maxHp }); + + /* Board-Lock entfernen */ + document.getElementById('board-lock-overlay')?.remove(); + + console.log(`[KI] Setup: ich=${mySlot}, links=${window._leftSlot}, amILeft=${amILeftPlayer}`); +}); + /* ═══════════════════════════════════════════════════════════ AUFGEBEN ═══════════════════════════════════════════════════════════ */ function closeToArena() { if (window.parent && window.parent !== window) { - window.parent.document.getElementById("arena-backdrop")?.remove(); - window.parent.document.getElementById("arena-popup")?.remove(); + window.parent.document.getElementById('arena-backdrop')?.remove(); + window.parent.document.getElementById('arena-popup')?.remove(); } else { - window.location.href = "/launcher"; + window.location.href = '/launcher'; } } function handleAufgeben() { - const modal = document.createElement("div"); - modal.id = "surrender-modal"; - modal.style.cssText = - "position:fixed;inset:0;z-index:9999;background:rgba(0,0,0,0.8);display:flex;align-items:center;justify-content:center;"; + const modal = document.createElement('div'); + modal.id = 'surrender-modal'; + modal.style.cssText = 'position:fixed;inset:0;z-index:9999;background:rgba(0,0,0,0.8);display:flex;align-items:center;justify-content:center;'; modal.innerHTML = `
\uD83C\uDFF3\uFE0F
@@ -1035,21 +968,18 @@ function handleAufgeben() {
`; document.body.appendChild(modal); - document - .getElementById("surrender-no") - .addEventListener("click", () => modal.remove()); - document.getElementById("surrender-yes").addEventListener("click", () => { + document.getElementById('surrender-no').addEventListener('click', () => modal.remove()); + document.getElementById('surrender-yes').addEventListener('click', () => { modal.remove(); - socket.emit("player_surrender", { matchId, slot: mySlot }); + socket.emit('player_surrender', { matchId, slot: mySlot }); showSurrenderMessage(false); }); } function showSurrenderMessage(iWon) { - const overlay = document.createElement("div"); - overlay.id = "surrender-result-overlay"; - overlay.style.cssText = - "position:fixed;inset:0;z-index:9999;background:rgba(0,0,0,0.92);display:flex;flex-direction:column;align-items:center;justify-content:center;font-family:'Cinzel',serif;animation:fadeIn 0.4s ease;"; + const overlay = document.createElement('div'); + overlay.id = 'surrender-result-overlay'; + overlay.style.cssText = "position:fixed;inset:0;z-index:9999;background:rgba(0,0,0,0.92);display:flex;flex-direction:column;align-items:center;justify-content:center;font-family:'Cinzel',serif;animation:fadeIn 0.4s ease;"; overlay.innerHTML = iWon ? `
\uD83C\uDFC6
SIEG!
@@ -1062,13 +992,11 @@ function showSurrenderMessage(iWon) {
Keine Punkte f\u00FCr dieses Match.
`; document.body.appendChild(overlay); - document - .getElementById("surrender-close-btn") - .addEventListener("click", closeToArena); + document.getElementById('surrender-close-btn').addEventListener('click', closeToArena); setTimeout(closeToArena, 8000); } -socket.on("player_surrendered", (data) => { +socket.on('player_surrendered', data => { if (data.slot !== mySlot) showSurrenderMessage(true); }); @@ -1076,27 +1004,27 @@ socket.on("player_surrendered", (data) => { MATCH-ERGEBNIS OVERLAY ═══════════════════════════════════════════════════════════ */ function showResultOverlay(won, data) { - const overlay = document.getElementById("match-result-overlay"); - const titleEl = document.getElementById("result-title"); - const pointsEl = document.getElementById("result-points"); - titleEl.textContent = won ? "\u2694\uFE0F SIEG!" : "\uD83D\uDC80 NIEDERLAGE"; - titleEl.className = "result-title " + (won ? "win" : "lose"); - pointsEl.textContent = "Punkte werden berechnet\u2026"; - overlay.classList.add("show"); + const overlay = document.getElementById('match-result-overlay'); + const titleEl = document.getElementById('result-title'); + const pointsEl = document.getElementById('result-points'); + titleEl.textContent = won ? '\u2694\uFE0F SIEG!' : '\uD83D\uDC80 NIEDERLAGE'; + titleEl.className = 'result-title ' + (won ? 'win' : 'lose'); + pointsEl.textContent = 'Punkte werden berechnet\u2026'; + overlay.classList.add('show'); if (data) updateResultWithPoints(data); } function updateResultWithPoints(data) { /* Verhindert doppeltes Aufrufen */ - if (document.getElementById("match-end-overlay")) return; + if (document.getElementById('match-end-overlay')) return; - const img = data.won ? "/images/victory.jpeg" : "/images/defeat.jpg"; + const img = data.won ? '/images/victory.jpeg' : '/images/defeat.jpeg'; const awarded = data.awarded ?? 0; /* Fade-in Keyframe einmalig anlegen */ - if (!document.getElementById("_matchEndStyle")) { - const st = document.createElement("style"); - st.id = "_matchEndStyle"; + if (!document.getElementById('_matchEndStyle')) { + const st = document.createElement('style'); + st.id = '_matchEndStyle'; st.textContent = ` @keyframes matchEndFadeIn { from{opacity:0} to{opacity:1} } @keyframes matchPtsSlideUp { from{opacity:0;transform:translateY(20px)} to{opacity:1;transform:translateY(0)} }`; @@ -1104,14 +1032,16 @@ function updateResultWithPoints(data) { } /* Punkte-Text aufbauen */ - const ptsLine = - awarded > 0 ? "+" + awarded + " Arena-Punkte" : "Keine Punkte"; - const lvlLine = data.level_up ? "⬆ LEVEL UP! → Level " + data.new_level : ""; + const ptsLine = awarded > 0 + ? '+' + awarded + ' Arena-Punkte' + : 'Keine Punkte'; + const lvlLine = data.level_up + ? '⬆ LEVEL UP! → Level ' + data.new_level + : ''; - const overlay = document.createElement("div"); - overlay.id = "match-end-overlay"; - overlay.style.cssText = - "position:fixed;inset:0;z-index:9999;background:#000;animation:matchEndFadeIn 0.5s ease forwards;"; + const overlay = document.createElement('div'); + overlay.id = 'match-end-overlay'; + overlay.style.cssText = 'position:fixed;inset:0;z-index:9999;background:#000;animation:matchEndFadeIn 0.5s ease forwards;'; overlay.innerHTML = `
${ptsLine}
- ${lvlLine ? `
${lvlLine}
` : ""} + ${lvlLine ? `
${lvlLine}
` : ''}
`; document.body.appendChild(overlay); /* Aktuellen Punktestand nachladen und anzeigen */ - fetch("/api/points/me") - .then((r) => r.json()) - .then((me) => { - const info = overlay.querySelector("div > div:first-child"); - if (info && awarded > 0) { - info.insertAdjacentHTML( - "afterend", - `
Gesamt: ${me.arena_points} Pts • Level ${me.level}
`, - ); - } - }) - .catch(() => {}); + fetch('/api/points/me').then(r => r.json()).then(me => { + const info = overlay.querySelector('div > div:first-child'); + if (info && awarded > 0) { + info.insertAdjacentHTML('afterend', + `
Gesamt: ${me.arena_points} Pts • Level ${me.level}
` + ); + } + }).catch(() => {}); /* Nach 3 Sekunden zur Arena weiterleiten */ setTimeout(() => closeToArena(), 3000); } -function closePopup() { - closeToArena(); -} +function closePopup() { closeToArena(); } -socket.on("match_result", (data) => updateResultWithPoints(data)); -socket.on("match_cancelled", () => closePopup()); +socket.on('match_result', data => updateResultWithPoints(data)); +socket.on('match_cancelled', () => closePopup()); -document - .getElementById("result-close-btn") - .addEventListener("click", closePopup); +document.getElementById('result-close-btn').addEventListener('click', closePopup); /* ═══════════════════════════════════════════════════════════ AVATAR @@ -1162,14 +1084,11 @@ function loadAvatar(input, imgId, parentId) { const file = input.files[0]; if (!file) return; const r = new FileReader(); - r.onload = (e) => { + r.onload = e => { const img = document.getElementById(imgId); img.src = e.target.result; - img.style.display = "block"; - document - .getElementById(parentId) - ?.querySelector(".av-placeholder") - ?.style.setProperty("display", "none"); + img.style.display = 'block'; + document.getElementById(parentId)?.querySelector('.av-placeholder')?.style.setProperty('display', 'none'); }; r.readAsDataURL(file); } @@ -1177,17 +1096,7 @@ function loadAvatar(input, imgId, parentId) { /* ═══════════════════════════════════════════════════════════ EVENT-LISTENER ═══════════════════════════════════════════════════════════ */ -document.getElementById("bereit-btn")?.addEventListener("click", handleBereit); -document - .getElementById("aufgeben-btn") - ?.addEventListener("click", handleAufgeben); -document - .getElementById("fileInputLeft") - ?.addEventListener("change", function () { - loadAvatar(this, "avImgL", "avLeft"); - }); -document - .getElementById("fileInputRight") - ?.addEventListener("change", function () { - loadAvatar(this, "avImgR", "avRight"); - }); +document.getElementById('bereit-btn')?.addEventListener('click', handleBereit); +document.getElementById('aufgeben-btn')?.addEventListener('click', handleAufgeben); +document.getElementById('fileInputLeft')?.addEventListener('change', function () { loadAvatar(this, 'avImgL', 'avLeft'); }); +document.getElementById('fileInputRight')?.addEventListener('change', function () { loadAvatar(this, 'avImgR', 'avRight'); }); diff --git a/sockets/1vKI_daily.socket.js b/sockets/1vKI_daily.socket.js index cd2f1c7..0d6ec6d 100644 --- a/sockets/1vKI_daily.socket.js +++ b/sockets/1vKI_daily.socket.js @@ -437,8 +437,9 @@ function registerHimmelstorHandlers(io, socket) { const aiRoom = htAiRooms.get(matchId); if (!aiRoom) return; - // Socket aktualisieren (nach Reconnect) + // Socket + Namen aktualisieren aiRoom.playerSocketId = socket.id; + const myIngameName = data.playerName || 'Du'; const ioRoom = io._arenaRooms?.get(matchId); if (ioRoom) ioRoom.sockets.player1 = socket.id; @@ -456,23 +457,27 @@ function registerHimmelstorHandlers(io, socket) { // HP initialisieren socket.emit('hp_init', { hp: aiRoom.hp, maxHp: aiRoom.maxHp }); - // Spieler braucht "bereit" nicht – direkt Zug starten - // (Wir simulieren ready_status so dass der Client den leftSlot setzt) + // ht_ai_setup: setzt links/rechts explizit ohne seed-basierten Flip setTimeout(() => { - socket.emit('ready_status', { - readyCount: 2, - readySlots: ['player1', 'player2'], + socket.emit('ht_ai_setup', { + playerSlot : aiRoom.playerSlot, // 'player1' + leftSlot : aiRoom.leftSlot, // 'player1' (Spieler immer links) + playerName : myIngameName || 'Du', + aiName : GUARD_NAMES[aiRoom.station] || `Wächter ${aiRoom.station}`, + hp : aiRoom.hp, + maxHp : aiRoom.maxHp, }); - }, 200); + }, 250); + // Zug starten (nach Setup-Animation) setTimeout(() => { socket.emit('turn_change', { - activeSlot: aiRoom.playerSlot, // Spieler fängt an + activeSlot: aiRoom.playerSlot, boardSync : [], hp : aiRoom.hp, maxHp : aiRoom.maxHp, }); - }, 600); + }, 700); console.log(`[HT] arena_join KI-Match: ${matchId}`); }); diff --git a/sockets/arena.socket.js b/sockets/arena.socket.js index e216064..2e4983e 100644 --- a/sockets/arena.socket.js +++ b/sockets/arena.socket.js @@ -790,6 +790,7 @@ function registerArenaHandlers(io, socket) { socket.on("start_turn_request", async (data) => { const { matchId, starterSlot } = data; if (!matchId || !starterSlot) return; + if (htAiMatchIds.has(matchId)) return; // KI-Match: turn wird von himmelstor.socket gestartet if (!io._turnInit) io._turnInit = new Set(); if (io._turnInit.has(matchId)) return; io._turnInit.add(matchId);