// Debug client — gestion WebSocket, lobby et affichage live du game state const WS_URL = `ws://${location.hostname}:8000/ws`; let ws = null; let isHost = false; let inLobby = false; let gameStarted = false; let myConnId = null; // identifié via lobby_created / lobby_joined let lastTickTime = null; let lastTick = null; // Connexion function connect() { ws = new WebSocket(WS_URL); ws.onopen = () => setStatus("Connecté", "ok"); ws.onclose = () => { setStatus("Déconnecté", "error"); gameStarted = false; inLobby = false; }; ws.onerror = () => setStatus("Erreur WebSocket", "error"); ws.onmessage = (e) => { try { handleMessage(JSON.parse(e.data)); } catch { log("⚠ message non-JSON reçu"); } }; } function send(type, data = {}) { if (ws?.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type, ...data })); } } // Dispatch des messages function handleMessage(msg) { if (msg.type === "game_state") { updateGameState(msg); return; // pas de log à 20 Hz } if (msg.type === "error") { log(`⚠ ${msg.code}: ${msg.message}`, "err"); return; } log(`← ${msg.type}: ${JSON.stringify(msg).slice(0, 100)}`); switch (msg.type) { case "username_set": onUsernameSet(msg); break; case "lobby_created": onLobbyCreated(msg); break; case "lobby_joined": onLobbyJoined(msg); break; case "player_joined": onPlayerJoined(msg); break; case "player_left": onPlayerLeft(msg); break; case "class_selected": onClassSelected(msg); break; case "player_ready": onPlayerReady(msg); break; case "game_starting": onGameStarting(msg); break; } } // Handlers lobby function onUsernameSet(msg) { $("create-btn").disabled = false; $("join-btn").disabled = false; log(`Pseudo défini : ${msg.username}`, "ok"); } function onLobbyCreated(msg) { isHost = true; inLobby = true; $("code-input").value = msg.code; $("class-kael").disabled = false; $("class-seris").disabled = false; $("class-aldric").disabled = false; $("start-btn").disabled = false; log(`Lobby créé — code : ${msg.code}`, "ok"); renderLobbyPlayers([{ username: $("username-input").value, player_class: null, ready: false, host: true }]); } function onLobbyJoined(msg) { inLobby = true; $("code-input").value = msg.code; $("class-kael").disabled = false; $("class-seris").disabled = false; $("class-aldric").disabled = false; renderLobbyPlayers(msg.players); } function onPlayerJoined(msg) { refreshLobbyFromEvent("player_joined", msg.player); } function onPlayerLeft(msg) { refreshLobbyFromEvent("player_left", { id: msg.player_id }); } function onClassSelected(msg) { // met à jour le bouton de classe si c'est nous if (msg.player_id === myConnId || !myConnId) { ["kael", "seris", "aldric"].forEach(c => { $(`class-${c}`).classList.toggle("active", c === msg.class); }); } } function onPlayerReady(msg) { if (msg.player_id === myConnId || !myConnId) { $("ready-btn").classList.add("active"); $("ready-btn").textContent = "✓ Prêt"; } } function onGameStarting(msg) { gameStarted = true; log(`▶ Partie démarrée ! (countdown: ${msg.countdown}s)`, "ok"); $("streaming").textContent = "● streaming 20 Hz"; $("streaming").style.color = "#4caf50"; } // Game state function updateGameState(msg) { // Tick + estimation du tick rate const now = performance.now(); if (lastTick !== null && lastTickTime !== null) { const dt = now - lastTickTime; const rate = (1000 / dt).toFixed(0); $("tick-rate").textContent = `~${rate}/s`; } lastTick = msg.tick; lastTickTime = now; $("tick").textContent = msg.tick; // Soulgate const sg = msg.soulgate; const sgPct = Math.round(sg.hp / sg.max_hp * 100); $("sg-hp").textContent = `${sg.hp} / ${sg.max_hp}`; $("sg-pct").textContent = `${sgPct}%`; $("sg-bar").style.width = `${sgPct}%`; // Vague const w = msg.wave; const badge = $("wave-info"); badge.innerHTML = `Vague ${w.number} — ${w.state}${w.enemies_remaining > 0 ? ` (${w.enemies_remaining} ennemis)` : ""}`; // Joueurs const container = $("players-list"); container.innerHTML = msg.players.map(p => { const pct = p.alive ? Math.round(p.hp / p.max_hp * 100) : 0; return `