// 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 `
${p.username} ${p.class}
${p.hp} / ${p.max_hp} HP ${p.alive ? pct + "%" : "mort"}
`; }).join(""); } // Helpers UI function setStatus(text, cls = "") { const el = $("status"); el.textContent = text; el.className = "status " + cls; } function log(text, cls = "") { const pre = $("log"); const ts = new Date().toLocaleTimeString("fr"); const line = document.createElement("span"); line.className = cls; line.textContent = `[${ts}] ${text}\n`; pre.appendChild(line); pre.scrollTop = pre.scrollHeight; } function $(id) { return document.getElementById(id); } let _lobbyPlayers = []; function renderLobbyPlayers(players) { _lobbyPlayers = players; const container = $("lobby-players"); if (!players.length) { container.innerHTML = `Aucun joueur`; return; } container.innerHTML = players.map(p => `
${p.username ?? p.id ?? "?"} ${p.class ?? p.player_class ?? "—"} ${p.ready ? `` : ""} ${p.host ? `[chef]` : ""}
`).join(""); } function refreshLobbyFromEvent(eventType, data) { // re-render best-effort sans refetch complet if (eventType === "player_joined") { _lobbyPlayers.push(data); } else if (eventType === "player_left") { _lobbyPlayers = _lobbyPlayers.filter(p => p.id !== data.id); } renderLobbyPlayers(_lobbyPlayers); } // Event listeners $("username-btn").addEventListener("click", () => { const v = $("username-input").value.trim(); if (v) send("set_username", { username: v }); }); $("username-input").addEventListener("keydown", e => { if (e.key === "Enter") $("username-btn").click(); }); $("create-btn").addEventListener("click", () => send("create_lobby")); $("join-btn").addEventListener("click", () => { const code = $("code-input").value.trim().toUpperCase(); if (code) send("join_lobby", { code }); }); $("code-input").addEventListener("keydown", e => { if (e.key === "Enter") $("join-btn").click(); }); ["kael", "seris", "aldric"].forEach(cls => { $(`class-${cls}`).addEventListener("click", () => { send("select_class", { class: cls }); }); }); $("ready-btn").addEventListener("click", () => { $("ready-btn").disabled = true; send("ready"); }); $("start-btn").addEventListener("click", () => send("start_game")); // Activer Ready dès qu'une classe est sélectionnée ["kael", "seris", "aldric"].forEach(cls => { $(`class-${cls}`).addEventListener("click", () => { $("ready-btn").disabled = false; $("ready-btn").classList.remove("active"); $("ready-btn").textContent = "Prêt"; }); }); // Init connect();