219 lines
7.6 KiB
JavaScript
219 lines
7.6 KiB
JavaScript
// hud.js : maj des elements html du hud (hp, cd, vagues, upgrades, end game)
|
|
|
|
import { UPGRADE_CATALOG, ABILITY_COOLDOWNS, DISPLACEMENT_COOLDOWNS, SCORE_PER_KILL, SCORE_PER_SOUL, SCORE_VICTORY, SCORE_PER_WAVE } from './constants.js';
|
|
import { showEndGameForm } from './leaderboard.js';
|
|
import { send } from './network.js';
|
|
import { setInputBlocked } from './input.js';
|
|
|
|
|
|
const BUFF_LABELS = {
|
|
invulnerable: 'Invulnerable',
|
|
casting: 'Incantation',
|
|
flying: 'Vol',
|
|
intangible: 'Intangible',
|
|
combat_buff: 'Buff combat',
|
|
damage_mult: 'Degats x3',
|
|
};
|
|
|
|
const WAVE_STATES = {
|
|
combat: 'Combat',
|
|
preparation: 'Preparation',
|
|
boss: 'Boss',
|
|
victory: 'Victoire',
|
|
};
|
|
|
|
|
|
export function updatePlayerHud(local) {
|
|
document.getElementById('hud-class-name').textContent = (local.class ?? '').toUpperCase();
|
|
document.getElementById('hud-souls').textContent = 'Ames : ' + (local.souls ?? 0);
|
|
|
|
const hpPct = local.max_hp > 0 ? Math.max(0, local.hp / local.max_hp * 100) : 0;
|
|
document.getElementById('hud-hp-fill').style.width = hpPct + '%';
|
|
document.getElementById('hud-hp-text').textContent = local.hp + ' / ' + local.max_hp;
|
|
|
|
_updateSkillCooldowns(local);
|
|
_updateBuffTags(local);
|
|
}
|
|
|
|
|
|
function _updateSkillCooldowns(local) {
|
|
const cds = local.cooldowns ?? {};
|
|
const abMaxes = ABILITY_COOLDOWNS[local.class] ?? [8, 12];
|
|
|
|
[['hud-s1', 0], ['hud-s2', 1]].forEach(([id, i]) => {
|
|
const el = document.getElementById(id);
|
|
const cd = cds['ability_' + (i + 1)] ?? 0;
|
|
const maxCd = abMaxes[i];
|
|
const fill = cd <= 0 ? 100 : (1 - cd / maxCd) * 100;
|
|
el.querySelector('.cd-fill').style.height = fill + '%';
|
|
el.classList.toggle('ready', cd <= 0);
|
|
el.classList.remove('locked');
|
|
});
|
|
|
|
const elE = document.getElementById('hud-se');
|
|
const cdE = cds['displacement'] ?? 0;
|
|
const maxCdE = DISPLACEMENT_COOLDOWNS[local.class] ?? 6;
|
|
const fillE = cdE <= 0 ? 100 : (1 - cdE / maxCdE) * 100;
|
|
elE.querySelector('.cd-fill').style.height = fillE + '%';
|
|
elE.classList.toggle('ready', cdE <= 0);
|
|
elE.classList.remove('locked', 'divine-ready');
|
|
}
|
|
|
|
|
|
function _updateBuffTags(local) {
|
|
const buffsEl = document.getElementById('hud-buffs');
|
|
buffsEl.innerHTML = '';
|
|
|
|
for (const b of (local.buffs ?? [])) {
|
|
const label = BUFF_LABELS[b.type];
|
|
if (!label) continue;
|
|
|
|
const tag = document.createElement('span');
|
|
tag.className = 'buff-tag ' + b.type;
|
|
tag.textContent = label;
|
|
buffsEl.appendChild(tag);
|
|
}
|
|
}
|
|
|
|
|
|
export function updateSoulgateBar(sg) {
|
|
if (!sg) return;
|
|
const pct = sg.max_hp > 0 ? Math.max(0, sg.hp / sg.max_hp * 100) : 0;
|
|
document.getElementById('sg-fill').style.width = pct + '%';
|
|
document.getElementById('sg-hp-text').textContent = sg.hp + ' / ' + sg.max_hp;
|
|
}
|
|
|
|
|
|
export function updateWaveInfo(wave) {
|
|
if (!wave) return;
|
|
document.getElementById('wave-label').textContent = 'Vague ' + wave.number + ' / 3';
|
|
document.getElementById('wave-state-label').textContent = WAVE_STATES[wave.state] ?? wave.state;
|
|
document.getElementById('wave-enemies').textContent = 'Ennemis : ' + (wave.enemies_remaining ?? 0);
|
|
}
|
|
|
|
|
|
export function updateBossBar(wave) {
|
|
const isBoss = wave.state === 'boss' && wave.boss_max_hp > 0;
|
|
document.getElementById('boss-bar').classList.toggle('hidden', !isBoss);
|
|
|
|
if (isBoss) {
|
|
document.getElementById('boss-bar-name').textContent = wave.boss_name.toUpperCase();
|
|
const pct = Math.max(0, wave.boss_hp / wave.boss_max_hp * 100);
|
|
document.getElementById('boss-bar-fill').style.width = pct + '%';
|
|
document.getElementById('boss-bar-hp').textContent = wave.boss_hp + ' / ' + wave.boss_max_hp;
|
|
}
|
|
}
|
|
|
|
|
|
export function updateUpgradePanel(wave, local) {
|
|
const inPrep = wave.state === 'preparation';
|
|
document.getElementById('upgrade-panel').classList.toggle('hidden', !inPrep);
|
|
// pendant la prep on bloque les inputs pour focus sur le menu
|
|
setInputBlocked(inPrep || gameOver);
|
|
|
|
if (inPrep) {
|
|
document.getElementById('prep-timer').textContent = Math.ceil(wave.prep_timer);
|
|
const souls = local?.souls ?? 0;
|
|
document.getElementById('soul-count').textContent = souls;
|
|
_updateUpgradeList(souls, local?.upgrades ?? {});
|
|
}
|
|
}
|
|
|
|
|
|
let _upgradeListBuilt = false;
|
|
let _upgradeClickBound = false;
|
|
|
|
function _updateUpgradeList(souls, upgrades) {
|
|
// on construit la liste 1 seule fois et on met juste a jour les stacks/disabled apres
|
|
// sinon on detruit les boutons pendant qu'on clique dessus
|
|
const list = document.getElementById('upgrade-list');
|
|
|
|
if (!_upgradeListBuilt) {
|
|
list.innerHTML = '';
|
|
for (const [id, spec] of Object.entries(UPGRADE_CATALOG)) {
|
|
const item = document.createElement('div');
|
|
item.className = 'upgrade-item';
|
|
item.dataset.id = id;
|
|
item.innerHTML = `
|
|
<div class="upgrade-info">
|
|
<div class="upgrade-name">${spec.name}</div>
|
|
<div class="upgrade-desc">${spec.desc}</div>
|
|
<div class="upgrade-stacks"></div>
|
|
</div>
|
|
<button class="upgrade-buy" data-id="${id}"></button>`;
|
|
list.appendChild(item);
|
|
}
|
|
_upgradeListBuilt = true;
|
|
}
|
|
|
|
if (!_upgradeClickBound) {
|
|
// Délégation : 1 seul listener sur la liste, ne meurt jamais avec les boutons
|
|
list.addEventListener('click', (e) => {
|
|
const btn = e.target.closest('.upgrade-buy');
|
|
if (!btn || btn.disabled) return;
|
|
send('player_upgrade', { upgrade_id: btn.dataset.id });
|
|
});
|
|
_upgradeClickBound = true;
|
|
}
|
|
|
|
for (const [id, spec] of Object.entries(UPGRADE_CATALOG)) {
|
|
const item = list.querySelector(`.upgrade-item[data-id="${id}"]`);
|
|
if (!item) continue;
|
|
const stacks = upgrades[id] ?? 0;
|
|
const maxed = stacks >= spec.max;
|
|
item.querySelector('.upgrade-stacks').textContent =
|
|
'\u25cf'.repeat(stacks) + '\u25cb'.repeat(spec.max - stacks);
|
|
const btn = item.querySelector('.upgrade-buy');
|
|
btn.disabled = maxed || souls < spec.cost;
|
|
btn.textContent = maxed ? 'MAX' : spec.cost + ' ames';
|
|
}
|
|
}
|
|
|
|
export function resetUpgradeList() {
|
|
_upgradeListBuilt = false;
|
|
_upgradeClickBound = false;
|
|
}
|
|
|
|
|
|
let gameOver = false;
|
|
|
|
export function resetGameOver() { gameOver = false; }
|
|
|
|
export function checkGameEnd(msg, gameStartTime, isHost = false, lobbyCode = '', submitterId = '') {
|
|
if (gameOver) return;
|
|
|
|
const victory = msg.wave?.state === 'victory';
|
|
const defeat = msg.soulgate?.hp === 0;
|
|
if (!victory && !defeat) return;
|
|
|
|
gameOver = true;
|
|
setInputBlocked(true);
|
|
|
|
const timeSecs = gameStartTime ? Math.floor((Date.now() - gameStartTime) / 1000) : 0;
|
|
const wavesCompleted = msg.wave?.number ?? 1;
|
|
const players = msg.players ?? [];
|
|
|
|
const totalKills = players.reduce((s, p) => s + (p.enemies_killed ?? 0), 0);
|
|
const totalSouls = players.reduce((s, p) => s + (p.souls ?? 0), 0);
|
|
const score = totalKills * SCORE_PER_KILL
|
|
+ totalSouls * SCORE_PER_SOUL
|
|
+ (victory ? SCORE_VICTORY : 0)
|
|
+ wavesCompleted * SCORE_PER_WAVE;
|
|
|
|
showEndGameForm({
|
|
victory,
|
|
score,
|
|
timeSecs,
|
|
wavesCompleted,
|
|
isHost,
|
|
lobbyCode,
|
|
submitterId,
|
|
players: players.map(p => ({
|
|
username: p.username,
|
|
class: p.class,
|
|
souls: p.souls ?? 0,
|
|
enemies_killed: p.enemies_killed ?? 0,
|
|
})),
|
|
});
|
|
}
|