// Game dashboard: load character profile and populate stats. let energyInterval = null; let energyData = null; // store latest server energy data for sync /** * Refresh energy state from the server at a fixed interval. * Keeps client-side countdown in sync with the authoritative server. */ function startEnergySync() { if (energyInterval) clearInterval(energyInterval); // Sync every 60s (2/3 of a regen tick) to stay in sync with server energyInterval = setInterval(() => { loadCharacter(); }, 60000); } /** * Update the energy bar and countdown display. * @param {number} current - current energy value * @param {number} max - max energy * @param {number} nextTickSec - seconds until next +1 energy tick (server-derived) */ function updateEnergyBar(current, max, nextTickSec) { energyData = { current, max, nextTickSec }; // Update text const energyEl = document.getElementById('char-energy'); if (energyEl) energyEl.textContent = `${current} / ${max}`; // Update bar fill const barEl = document.getElementById('energy-bar'); if (barEl) { const pct = Math.round((current / max) * 100); barEl.style.width = `${pct}%`; barEl.title = `${current}/${max} energy`; } // Update countdown const timerEl = document.getElementById('energy-timer'); if (timerEl) { if (current >= max) { timerEl.textContent = 'Energy full'; } else { const mins = Math.floor(nextTickSec / 60); const secs = nextTickSec % 60; timerEl.textContent = `Next +1 in ${mins}:${secs.toString().padStart(2, '0')}`; } } } /** * Start a client-side countdown that decrements until the next energy tick. * When it reaches zero, refetch from server to get authoritative state. */ function startCountdown() { if (!energyData || energyData.current >= energyData.max || energyData.nextTickSec <= 0) { return; } let remaining = energyData.nextTickSec; if (energyInterval) clearInterval(energyInterval); energyInterval = setInterval(() => { remaining--; if (remaining <= 0) { clearInterval(energyInterval); energyInterval = null; loadCharacter(); return; } const mins = Math.floor(remaining / 60); const secs = remaining % 60; const timerEl = document.getElementById('energy-timer'); if (timerEl) { timerEl.textContent = `Next +1 in ${mins}:${secs.toString().padStart(2, '0')}`; } }, 1000); } async function loadCharacter() { try { const data = await api.get('/character'); if (!data) { // No character yet — redirect to create-hero window.location.href = '/create-hero.html'; return; } const { character, derived, energy } = data; // Show stats, hide loading document.getElementById('hero-loading').style.display = 'none'; document.getElementById('hero-stats').style.display = 'block'; // Basic info document.getElementById('player-name').textContent = character.name; document.getElementById('char-name').textContent = character.name; document.getElementById('char-class').textContent = character.class.charAt(0).toUpperCase() + character.class.slice(1); document.getElementById('char-level').textContent = character.level; document.getElementById('char-hp').textContent = `${character.hp_current} / ${derived.maxHp}`; document.getElementById('char-energy').textContent = `${energy.current} / ${energy.max}`; document.getElementById('char-gold').textContent = character.gold; document.getElementById('char-xp').textContent = `${character.xp} / ${xpToNext(character.level)}`; document.getElementById('char-str').textContent = character.str; document.getElementById('char-agi').textContent = character.agi; document.getElementById('char-int').textContent = character.int; document.getElementById('char-vit').textContent = character.vit; document.getElementById('derived-atk').textContent = derived.attack; document.getElementById('derived-def').textContent = derived.defense; document.getElementById('derived-spd').textContent = derived.speed; document.getElementById('derived-crit').textContent = derived.critChance; // Update energy bar and start countdown updateEnergyBar(energy.current, energy.max, energy.nextTickInSec); startCountdown(); } catch (err) { // Not authenticated — redirect to login window.location.href = '/login.html'; } } function xpToNext(level) { return Math.floor(100 * Math.pow(level, 1.5)); } // Start periodic energy sync from server (every 60s) startEnergySync(); // Logout handler document.getElementById('logout-btn')?.addEventListener('click', async (e) => { e.preventDefault(); try { await api.post('/auth/logout', {}); } catch (_) {} window.location.href = '/login.html'; }); document.addEventListener('DOMContentLoaded', loadCharacter);