/
chewy
/
BDZ_GenAI
Обзор
Документация
Войти
/
chewy
/
BDZ_GenAI
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
scripts/hud.js
235 строк
10 KB
pichugin.sa
Раскидал все по папкам, чтобы нормально выглядел репозиторий
16 ноя 2025, 21:55
16 ноя 2025, 21:55
bd854ee
Код
Авторство
О чём код?
// hud.js - HUD controls, scale handling, RuleEngine and registerNpcs import { player } from './engine.js'; import * as NOTIF from './scripts/notify.js'; export let cam = {x:0,y:0,zoom:1}; export let scaleSetting = 2; export let U_camTargetZoom = cam.zoom || 1; // CONFIG block (matching engine defaults, central place for UI/transition timings) export const UI_CONFIG = { zoomDuration: 0.4, // seconds spriteTweenDuration: 0.4 // seconds }; // expose display-friendly time/weather helpers export function getDayPhaseLabel(){ return (typeof window.__engine !== 'undefined' && window.__engine.dayPhase) ? window.__engine.dayPhase : dayPhase; } export function getWeatherLabel(){ return (typeof window.__engine !== 'undefined' && window.__engine.weather) ? window.__engine.weather.kind : weather.kind; } export function setScale(n){ const prev = scaleSetting; scaleSetting = Math.max(1,Math.min(3,n)); const scaleMap = {1:0.6,2:1,3:1.6}; U_camTargetZoom = scaleMap[scaleSetting]; NOTIF.notify('Установлен размер: ' + ({1:'Tiny',2:'Normal',3:'Giant'}[scaleSetting]), 'info', 1200); document.getElementById('scaleValue') && (document.getElementById('scaleValue').textContent = scaleSetting); document.querySelectorAll('.size-btn').forEach(b=>{ const s = parseInt(b.dataset.size); if(s === scaleSetting) b.classList.add('active'); else b.classList.remove('active'); }); // smooth camera zoom tween (time-based) const start = cam.zoom; const target = U_camTargetZoom; const duration = UI_CONFIG.zoomDuration; const t0 = performance.now(); (function tick(){ const now = performance.now(); const t = Math.min(1, (now - t0) / (duration * 1000)); cam.zoom = start + (target - start) * (t); if(t < 1) requestAnimationFrame(tick); })(); // player hitbox & sprite scale tween (animate w/h change) try{ import('./engine.js').then(({player, CONFIG})=>{ const startW = player.w, startH = player.h; const hw = CONFIG.hitbox[scaleSetting] || {w: player.w, h: player.h}; const endW = hw.w, endH = hw.h; const s0 = performance.now(); (function pt(){ const pnow = performance.now(); const tt = Math.min(1, (pnow - s0) / (UI_CONFIG.spriteTweenDuration * 1000)); player.w = startW + (endW - startW) * tt; player.h = startH + (endH - startH) * tt; player.scale = scaleSetting; if(tt < 1) requestAnimationFrame(pt); })(); }); }catch(e){} } /* small first-action tooltip management */ let shownTooltips = {}; export function showFirstTooltip(key, text, dur=2800){ if(shownTooltips[key]) return; shownTooltips[key]=true; const t = document.createElement('div'); t.className='first-tooltip'; t.textContent = text; document.body.appendChild(t); requestAnimationFrame(()=> t.classList.add('show')); setTimeout(()=> { t.classList.remove('show'); setTimeout(()=>{ try{ document.body.removeChild(t);}catch(e){} },360); }, dur); } export function registerNpcs(list){ window.__npcs = list || window.__npcs || []; return window.__npcs; } // toggle sneak mode helper (called by input handlers) export function playerSneakToggle(on){ try{ // import player reference from engine import('./engine.js').then(m=>{ const p = m.player; p.mode = on ? 'sneak' : 'normal'; p.sneak = !!on; p.opacity = on ? 0.9 : 1.0; // small notify & UI update if(on) { document.getElementById('sneakIndicator') && document.getElementById('sneakIndicator').classList.add('active'); } else { document.getElementById('sneakIndicator') && document.getElementById('sneakIndicator').classList.remove('active'); } m.player = p; }).catch(()=>{}); }catch(e){} } /* Rule Engine */ export const RuleEngine = { reputation: 0, factions: { MiniTribe: 0, GiantFolk: 0 }, rules: { silence: { id:'silence', name:'Silence', desc:'Don\'t make noise in the temple. Moving fast costs reputation.', learned:false }, greeting:{ id:'greeting', name:'Greeting', desc:'Greet villagers when entering the village (press G). Failure costs reputation.', learned:false } }, discover(id){ const r = this.rules[id]; if(r && !r.learned){ r.learned = true; NOTIF.notify('Открыто правило: ' + r.name, 'info'); } }, changeRep(factionOrAmount, amount){ const prevRep = this.reputation; if(typeof factionOrAmount === 'number'){ this.reputation += factionOrAmount; document.getElementById('reputation') && (document.getElementById('reputation').textContent = 'Reputation: ' + this.reputation); if(Math.abs(factionOrAmount) >= 8) NOTIF.notify('Сильное изменение репутации: ' + factionOrAmount, factionOrAmount > 0 ? 'success' : 'warn'); return; } const faction = factionOrAmount; const delta = amount || 0; if(!this.factions.hasOwnProperty(faction)) return; this.factions[faction] = Math.max(-100, Math.min(100, this.factions[faction] + delta)); this.reputation = Math.round((this.factions.MiniTribe + this.factions.GiantFolk)/2); document.getElementById('reputation') && (document.getElementById('reputation').textContent = 'Reputation: ' + this.reputation); document.getElementById('minitribeRep') && (document.getElementById('minitribeRep').textContent = this.factions.MiniTribe); document.getElementById('giantfolkRep') && (document.getElementById('giantfolkRep').textContent = this.factions.GiantFolk); if(delta > 0) { NOTIF.notify(faction + ' +'+delta+' репутации', 'success'); // emit DOM event for positive rep (glow) document.dispatchEvent(new CustomEvent('rep:gain',{detail:{faction,delta}})); } else if(delta < 0) { NOTIF.notify(faction + ' '+delta+' репутации', 'warn'); // emit DOM event for negative rep (violation: vibrate/darken) document.dispatchEvent(new CustomEvent('rep:lose',{detail:{faction,delta}})); } if(this.factions[faction] <= -30) NOTIF.notify('Репутация с ' + faction + ' сильно упала!', 'warn', 2200); } }; /* updateHUD - minimal fast DOM refresh */ export function updateHUD(){ const hpFill = document.getElementById('hpFill'); const hpValue = document.getElementById('hpValue'); const xpValue = document.getElementById('xpValue'); const sneakEl = document.getElementById('sneakIndicator'); const scaleValueEl = document.getElementById('scaleValue'); const hp = Math.max(0, Math.min(100, player.health || 0)); if(hpFill) hpFill.style.width = (hp)+'%'; if(hpValue) hpValue.textContent = Math.round(hp); if(xpValue) xpValue.textContent = Math.round(player.xp || 0); if(sneakEl){ if(player.sneak) sneakEl.classList.add('active'); else sneakEl.classList.remove('active'); } if(scaleValueEl) scaleValueEl.textContent = scaleSetting; // small HUD hint: active effects listing (compact) const rep = document.getElementById('hpValue'); const effEl = document.getElementById('activeEffectsList'); if(!effEl){ const p = document.createElement('div'); p.id='activeEffectsList'; p.style.fontSize='12px'; p.style.marginTop='6px'; const parent = document.querySelector('.side .panel') || document.body; parent.appendChild(p); } const pe = (player.effects || []).map(e=> e.kind + (e.t?(' ('+Math.ceil(e.t)+'s)'):'')).join(', '); document.getElementById('activeEffectsList').textContent = pe ? 'Эффекты: ' + pe : ''; // Contracts compact tracker in HUD (show first accepted or first available) let tracker = document.getElementById('__contractTracker'); if(!tracker){ tracker = document.createElement('div'); tracker.id='__contractTracker'; tracker.style.fontSize='13px'; tracker.style.marginTop='8px'; const parent = document.querySelector('.hud .panel') || document.querySelector('.hud'); parent.appendChild(tracker); } const cList = window.__contracts || []; const active = cList.find(c=> c.status === 'accepted') || cList.find(c=> c.status === 'available'); if(active){ tracker.textContent = active.title + ' — ' + (active.progress || 0) + '/' + active.goal + (active.status==='completed' ? ' (Completed)' : (active.status==='accepted' ? ' (Active)' : ' (Available)')); } else { tracker.textContent = 'Контрактов нет'; } // suspicion mini-bar update: compute average suspiciousness from nearby NPCs (simple heuristic) try{ const susEl = document.getElementById('suspicionBar'); if(susEl){ let sus = 0; let cnt = 0; const npcs = window.__npcs || []; for(const n of npcs){ if(typeof n.suspicion === 'number'){ sus += n.suspicion; cnt++; } } const avg = cnt ? Math.min(100, Math.round(sus / cnt)) : 0; susEl.querySelector('::after'); // noop to avoid lint susEl.style.setProperty('--sus-pct', avg + '%'); susEl.querySelector && (susEl.style.position = 'relative'); // set inner bar via pseudo-element width by manipulating style sheet fallback: use inline child let inner = susEl.querySelector('.suspicion-fill'); if(!inner){ inner = document.createElement('div'); inner.className = 'suspicion-fill'; inner.style.height='100%'; inner.style.width='0%'; inner.style.transition='width 220ms ease'; inner.style.background='linear-gradient(90deg,#ffd54f,#ff6b6b)'; inner.style.position='absolute'; inner.style.left='0'; inner.style.top='0'; susEl.appendChild(inner); } inner.style.width = avg + '%'; } }catch(e){} // Stamina bar UI const stEl = document.getElementById('staminaFill'); const stVal = document.getElementById('staminaValue'); const st = Math.max(0, Math.min(100, Math.round(player.stamina || 0))); if(stEl) stEl.style.width = st + '%'; if(stVal) stVal.textContent = st; // inventory rendering: show names + counts in inventoryList try{ const invEl = document.getElementById('inventoryList'); if(invEl){ invEl.innerHTML = ''; const inv = (player.inventory || []); if(inv.length === 0){ const li = document.createElement('li'); li.style.color = '#999'; li.textContent = 'Пусто'; invEl.appendChild(li); } else { // group by name const map = {}; for(const it of inv){ const name = it.name || (it.id || 'Item'); map[name] = (map[name] || 0) + 1; } Object.keys(map).forEach(name=>{ const li = document.createElement('li'); li.textContent = name + (map[name] > 1 ? ' ×' + map[name] : ''); invEl.appendChild(li); }); } } }catch(e){} }