/
CodeByKate
/
lastserver-game
Обзор
Документация
Войти
/
CodeByKate
/
lastserver-game
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
ui.js
253 строки
10 KB
CodeByKate
upload files
21 дек 2025, 21:44
21 дек 2025, 21:44
136e74f
Код
Авторство
О чём код?
/* ui.js HUD and ability cooldown UI updated to be dynamic per-frame, with progress bars, instant messages when abilities are on cooldown, and per-frame stability coloring. */ export default class UI { constructor(engine){ this.engine = engine; this.raf = null; this.msgTimer = 0; // DOM refs this.dom = { currentPlayer: document.getElementById('current-player'), stabilityFill: document.getElementById('stability-fill'), stabilityText: document.getElementById('stability-text'), decisions: document.getElementById('decisions-count'), pauseBtn: document.getElementById('pause-btn'), restartBtn: document.getElementById('restart-btn'), canvas: document.getElementById('game-canvas'), overlay: document.getElementById('overlay'), overlayBox: document.getElementById('overlay-box'), leraCd: document.getElementById('lera-cd'), maxCd: document.getElementById('max-cd'), startHint: document.getElementById('start-hint'), levelTitle: document.getElementById('level-title') }; // ensure cooldown bars and message element this._ensureCooldownBars(); this._ensureMessageArea(); } _ensureCooldownBars(){ if(this.dom.leraCd && !document.getElementById('lera-cd-bar')){ const bar = document.createElement('div'); bar.style.width = '120px'; bar.style.height = '10px'; bar.style.background = 'rgba(255,255,255,0.05)'; bar.style.borderRadius = '6px'; bar.style.overflow = 'hidden'; bar.style.display = 'inline-block'; bar.style.verticalAlign = 'middle'; bar.style.marginLeft = '8px'; bar.id = 'lera-cd-bar'; const fill = document.createElement('div'); fill.id = 'lera-cd-fill'; fill.style.height = '100%'; fill.style.width = '0%'; fill.style.background = 'linear-gradient(90deg,#39d353,#8ef08e)'; bar.appendChild(fill); this.dom.leraCd.appendChild(bar); } if(this.dom.maxCd && !document.getElementById('max-cd-bar')){ const bar = document.createElement('div'); bar.style.width = '120px'; bar.style.height = '10px'; bar.style.background = 'rgba(255,255,255,0.05)'; bar.style.borderRadius = '6px'; bar.style.overflow = 'hidden'; bar.style.display = 'inline-block'; bar.style.verticalAlign = 'middle'; bar.style.marginLeft = '8px'; bar.id = 'max-cd-bar'; const fill = document.createElement('div'); fill.id = 'max-cd-fill'; fill.style.height = '100%'; fill.style.width = '0%'; fill.style.background = 'linear-gradient(90deg,#b36cff,#ff9fe6)'; bar.appendChild(fill); this.dom.maxCd.appendChild(bar); } } _ensureMessageArea(){ // transient message area for "ability recharging" feedback if(!document.getElementById('ability-msg')){ const el = document.createElement('div'); el.id = 'ability-msg'; el.style.position = 'absolute'; el.style.left = '50%'; el.style.bottom = '18px'; el.style.transform = 'translateX(-50%)'; el.style.padding = '6px 10px'; el.style.background = 'rgba(0,0,0,0.5)'; el.style.color = '#ffd9d9'; el.style.borderRadius = '6px'; el.style.fontSize = '13px'; el.style.pointerEvents = 'none'; el.style.display = 'none'; document.getElementById('game-root').appendChild(el); this.dom.abilityMsg = el; } else { this.dom.abilityMsg = document.getElementById('ability-msg'); } } bindDOM(){ // Buttons this.dom.pauseBtn.addEventListener('click', ()=>{ this.engine.pause(); this.dom.pauseBtn.textContent = this.engine.paused ? 'Продолжить' : 'Пауза'; }); this.dom.restartBtn.addEventListener('click', ()=>{ this.engine.restart(); this.dom.pauseBtn.textContent = 'Пауза'; if(this.dom.startHint) this.dom.startHint.style.display = 'none'; }); // ability key feedback: show short message if pressed during cooldown window.addEventListener('keydown', (e)=>{ const ents = this.engine.entitiesModule; if(!ents) return; if(e.key === 'e' || e.key === 'E'){ if(ents.leraAbility && ents.leraAbility.cooldownRemaining > 0){ this._showMessage(`Перезарядка: ${Math.ceil(ents.leraAbility.cooldownRemaining)} сек`); this.engine._triggerFlash('red', 120); } } else if(e.key === 'q' || e.key === 'Q'){ if(ents.maxBug && ents.maxBug.cooldownRemaining > 0){ this._showMessage(`Перезарядка: ${Math.ceil(ents.maxBug.cooldownRemaining)} сек`); this.engine._triggerFlash('red', 120); } } else if(e.key === 'Tab'){ // small visual update on switch this._updateHUD(); } }); // start the per-frame HUD loop const loop = (t)=>{ this._updateHUD(); this._glitchLoopFrame(); this.raf = requestAnimationFrame(loop); }; loop(); } _showMessage(text, duration=1.0){ if(!this.dom.abilityMsg) return; this.dom.abilityMsg.textContent = text; this.dom.abilityMsg.style.display = 'block'; this.msgTimer = duration; // fade out handled in _updateHUD each frame } _refreshOverlay(){ // Keep existing overlay behavior (mirror engine flags) if(this.engine.gameOver){ this.dom.overlay.classList.remove('hidden'); this.dom.overlayBox.style.fontSize = '40px'; this.dom.overlayBox.style.whiteSpace = 'pre-wrap'; this.dom.overlay.style.pointerEvents = 'auto'; if(this.engine.gameOverReason === 'decay'){ this.dom.overlayBox.style.color = '#ff3333'; this.dom.overlayBox.textContent = 'Сервер отключён навсегда.\nR — ПЕРЕЗАПУСК'; } else if(this.engine.gameOverReason === 'fall'){ this.dom.overlayBox.style.color = '#ff3333'; this.dom.overlayBox.textContent = 'ВЫ ПРОВАЛИЛИСЬ В ЦИФРОВУЮ ПУСТОТУ.\nR — ПЕРЕЗАПУСК'; } else { this.dom.overlayBox.style.color = '#ff3333'; this.dom.overlayBox.textContent = 'Игра окончена.\nR — ПЕРЕЗАПУСК'; } } else if(this.engine.paused){ this.dom.overlay.classList.remove('hidden'); this.dom.overlayBox.style.color = '#ffffff'; this.dom.overlayBox.style.fontSize = '40px'; this.dom.overlayBox.style.whiteSpace = 'normal'; this.dom.overlayBox.textContent = 'ПАУЗА'; this.dom.overlay.style.pointerEvents = 'auto'; } else if(this.engine.win){ this.dom.overlay.classList.remove('hidden'); this.dom.overlayBox.style.color = '#8ef08e'; this.dom.overlayBox.style.fontSize = '40px'; this.dom.overlayBox.style.whiteSpace = 'normal'; this.dom.overlayBox.textContent = 'УРОВЕНЬ ПРОЙДЕН… ПОКА'; this.dom.overlay.style.pointerEvents = 'auto'; } else { this.dom.overlay.classList.add('hidden'); this.dom.overlay.style.pointerEvents = 'none'; this.dom.overlayBox.textContent = ''; } } _updateHUD(){ const ents = this.engine.entitiesModule; if(!ents) return; // overlay refresh this._refreshOverlay(); // active player label const name = (ents.current === 'lera') ? 'Лера' : 'Макс'; if(this.dom.currentPlayer) this.dom.currentPlayer.textContent = name; // stability percent (int) const pct = Math.max(0, Math.ceil((this.engine.decayTimer / this.engine.decayTimerMax) * 100)); if(this.dom.stabilityFill) this.dom.stabilityFill.style.width = pct + '%'; if(this.dom.stabilityText) this.dom.stabilityText.textContent = pct + '%'; // color mapping let colorStart = '#39d353', colorEnd = '#8ef08e'; if(pct < 30){ colorStart = '#ff3b3b'; colorEnd = '#ffb36b'; } else if(pct < 60){ colorStart = '#ffd966'; colorEnd = '#ffb36b'; } if(this.dom.stabilityFill) this.dom.stabilityFill.style.background = `linear-gradient(90deg, ${colorStart}, ${colorEnd})`; // decisions if(this.dom.decisions) this.dom.decisions.textContent = this.engine.decisions; // cooldowns: show dynamic seconds and progress bars (fill left->right = used) const lera = ents.leraAbility; const maxb = ents.maxBug; if(lera){ const rem = Math.ceil(lera.cooldownRemaining); this.dom.leraCd.textContent = rem > 0 ? `E: ${rem} сек` : 'E: готово'; const barFill = document.getElementById('lera-cd-fill'); if(barFill) { const pctFill = lera.cooldown > 0 ? (1 - (Math.max(0, lera.cooldownRemaining) / lera.cooldown)) * 100 : 0; barFill.style.width = `${pctFill}%`; } } if(maxb){ const rem = Math.ceil(maxb.cooldownRemaining); this.dom.maxCd.textContent = rem > 0 ? `Q: ${rem} сек` : 'Q: готово'; const barFill = document.getElementById('max-cd-fill'); if(barFill) { const pctFill = maxb.cooldown > 0 ? (1 - (Math.max(0, maxb.cooldownRemaining) / maxb.cooldown)) * 100 : 0; barFill.style.width = `${pctFill}%`; } } // transient message fading if(this.msgTimer > 0){ this.msgTimer -= 1/60; this.dom.abilityMsg.style.display = 'block'; this.dom.abilityMsg.style.opacity = Math.min(1, this.msgTimer * 2); if(this.msgTimer <= 0){ this.dom.abilityMsg.style.display = 'none'; } } } _glitchLoopFrame(){ const canvas = this.dom.canvas; const ents = this.engine.entitiesModule; if(!ents) return; const g = ents.glitchTimer; // also use MaxBug active state to enforce glitch const maxActive = ents.maxBug && ents.maxBug.active; if(g > 0.2 || maxActive) canvas.classList.add('glitch'); else canvas.classList.remove('glitch'); } _glitchLoop(){ // legacy - not used; kept to match original API } }