Files
Emberfall/public/js/game.js
T
2026-10-20 20:33:35 +11:00

140 lines
4.8 KiB
JavaScript

// 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);