SOULGATE/client/js/debug.js
2026-05-04 03:42:47 +02:00

274 lines
7.9 KiB
JavaScript

// 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 = `<span class="wave-badge ${w.state}">Vague ${w.number}${w.state}${w.enemies_remaining > 0 ? ` (${w.enemies_remaining} ennemis)` : ""}</span>`;
// 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 `
<div class="player-card ${p.alive ? "" : "dead"}">
<div class="phead">
<span class="pname">${p.username}</span>
<span class="pclass">${p.class}</span>
</div>
<div style="display:flex; justify-content:space-between">
<span class="php">${p.hp} / ${p.max_hp} HP</span>
<span style="color:#444; font-size:11px">${p.alive ? pct + "%" : "mort"}</span>
</div>
<div class="hp-bar">
<div class="hp-fill ${p.alive ? p.class : "dead"}" style="width:${pct}%"></div>
</div>
</div>`;
}).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 = `<span style="color:#333">Aucun joueur</span>`;
return;
}
container.innerHTML = players.map(p => `
<div class="lobby-player">
<span class="pname">${p.username ?? p.id ?? "?"}</span>
<span class="pclass">${p.class ?? p.player_class ?? "—"}</span>
${p.ready ? `<span class="pready">✓</span>` : ""}
${p.host ? `<span class="phost">[chef]</span>` : ""}
</div>
`).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();