/
chewy
/
BDZ_GenAI
Обзор
Документация
Войти
/
chewy
/
BDZ_GenAI
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
scripts/ui.js
362 строки
16 KB
pichugin.sa
Раскидал все по папкам, чтобы нормально выглядел репозиторий
16 ноя 2025, 21:55
16 ноя 2025, 21:55
bd854ee
Код
Авторство
О чём код?
// ui.js - handles HUD, minimap, rule engine, inputs, and renders game view using engine state import { canvas, ctx, world, platforms, trees, boxes, portals, player, items, mobs, npcs, locations, rectsOverlap, roundedRect, resizeCanvas, pulses } from './engine.js'; import * as HUD from '../hud.js'; import * as MINIMAP from './minimap.js'; import * as LANG from './language.js'; import * as NOTIF from './notify.js'; import * as MODALS from './ui.modals.js'; import * as RENDERER from '../renderer.js'; // ensure global reactions array exists to avoid undefined filter/map errors in drawScene window.__reactions = window.__reactions || []; // re-export modal APIs from ui.modals.js for backwards compatibility export const initContractsUI = MODALS.initContractsUI; export const openContracts = MODALS.openContracts; export const closeContracts = MODALS.closeContracts; export const initCodexUI = MODALS.initCodexUI; export const openCodex = MODALS.openCodex; export const closeCodex = MODALS.closeCodex; export const renderCodexPanel = MODALS.renderCodexPanel; // Re-export Manual modal API so other modules (input-handlers, main) can toggle the manual export const initManual = MODALS.initManual; export const openManual = MODALS.openManual; export const closeManual = MODALS.closeManual; // respond to Event Log clicks: open appropriate UI (journal/codex/contracts) document.addEventListener('eventlog:click', (ev)=>{ const d = ev && ev.detail ? ev.detail : {}; try{ if(d.target === 'codex'){ openCodex(); } else if(d.target === 'contracts'){ openContracts(); } else if(d.target === 'manual' || d.target === 'journal'){ openManual(); } else if(d.entryId){ // try to focus journal list entry if present (journalList) const jl = document.getElementById('journalList'); if(jl){ // simple heuristic: add an anchored entry and scroll into view const li = document.createElement('li'); li.textContent = d.title || ('Event: ' + (d.type||'')); jl.appendChild(li); li.scrollIntoView({behavior:'smooth', block:'center'}); } else { openManual(); } } else { // default open manual for unknown openManual(); } }catch(e){} }); // hook Manual button document.addEventListener('click', (e)=>{ if(e.target && e.target.id === 'openManual'){ openManual(); } if(e.target && e.target.id === 'openCodex'){ openCodex(); } if(e.target && e.target.id === '__openCodexBtn'){ openCodex(); } }); /* hook rep events to provide HUD glow and rule-break feedback */ document.addEventListener('rep:gain', (e)=>{ const repEl = document.getElementById('reputation'); if(repEl){ repEl.classList.add('reputation-glow'); setTimeout(()=> repEl.classList.remove('reputation-glow'), 900); } }); document.addEventListener('rep:lose', (e)=>{ // brief HUD vibrate + scene darken const hudEl = document.querySelector('.hud'); const sceneEl = document.querySelector('.scene'); if(hudEl){ hudEl.classList.add('vibrate'); setTimeout(()=> hudEl.classList.remove('vibrate'), 420); } if(sceneEl){ sceneEl.classList.add('rule-break'); setTimeout(()=> sceneEl.classList.remove('rule-break'), 700); } }); export const minimap = document.getElementById('minimap'); export const mctx = minimap.getContext('2d'); let W = canvas.clientWidth, H = canvas.clientHeight; export let cam = HUD.cam; export let scaleSetting = HUD.scaleSetting; /* teleport fade state for animated transition */ /* portals/teleport removed: teleportFade disabled (no teleport transitions) */ let teleportFade = { active:false, t:0, duration:0.6, target:null }; // startTeleport: animate a brief fade and move player; used by main.js portal handler export function startTeleport(portal){ if(!portal) return; if(teleportFade.active) return; teleportFade.active = true; teleportFade.t = 0; teleportFade.target = portal; const overlay = document.createElement('div'); overlay.className = 'manual-overlay'; overlay.style.background = 'rgba(8,8,10,0)'; overlay.style.pointerEvents = 'none'; overlay.style.transition = 'background 260ms ease'; overlay.style.zIndex = 1400; document.body.appendChild(overlay); requestAnimationFrame(()=>{ overlay.style.background = 'rgba(8,8,10,0.9)'; setTimeout(()=>{ try{ // perform instant move (safe) import('./engine.js').then(({player})=>{ player.x = portal.to.x || player.x; player.y = portal.to.y || player.y; // sync globals used by minimap/UI window.__playerMoved = true; }).catch(()=>{}); }catch(e){} // fade back out overlay.style.background = 'rgba(8,8,10,0)'; setTimeout(()=> { try{ document.body.removeChild(overlay); }catch(e){} teleportFade.active = false; teleportFade.target = null; }, 260); }, 260); }); } /* keep exported for external callers */ // (no duplicate export statement here) const scaleMap = {1:0.6,2:1,3:1.6}; const scaleLabelMap = {1:'Tiny',2:'Normal',3:'Giant'}; /* tombstone: setScale, U_camTargetZoom, registerNpcs moved to hud.js */ // removed function setScale() {} // removed export let U_camTargetZoom // removed function registerNpcs() {} // add small top-right status display for time & weather function ensureWorldStatus(){ if(document.getElementById('__worldStatus')) return; const s = document.createElement('div'); s.id='__worldStatus'; s.className='world-status panel'; s.style.position='absolute'; s.style.right='12px'; s.style.top='12px'; s.style.pointerEvents='none'; s.innerHTML = `<div style="display:flex;align-items:center;gap:10px"><div id="wsIcon">☀️</div><div><div id="wsLabel" style="font-weight:700;font-size:13px">Day</div><div id="wsSub" style="font-size:12px;color:var(--muted)">Clear</div></div></div>`; document.querySelector('.scene') && document.querySelector('.scene').appendChild(s); } export function updateWorldStatus(){ ensureWorldStatus(); const icon = document.getElementById('wsIcon'); const label = document.getElementById('wsLabel'); const sub = document.getElementById('wsSub'); const phase = (window.__engine && window.__engine.dayPhase) || (window.__dayPhase || 'day'); const weather = (window.__engine && window.__engine.weather && window.__engine.weather.kind) || (window.__weatherState && window.__weatherState.kind) || 'clear'; const iconMap = { morning:'🌅', day:'☀️', evening:'🌇', night:'🌙' }; const weatherMap = { clear:'Ясно', fog:'Туман', rain:'Дождь' }; icon.textContent = iconMap[phase] || '☀️'; label.textContent = phase.charAt(0).toUpperCase() + phase.slice(1); sub.textContent = weatherMap[weather] || weather; // transient location message (if recently entered) - listen for location:entered to set a short-lived message const locMsgElId = '__locMsg'; let locMsg = document.getElementById(locMsgElId); if(!locMsg){ locMsg = document.createElement('div'); locMsg.id = locMsgElId; locMsg.style.fontSize='12px'; locMsg.style.color='var(--muted)'; locMsg.style.marginTop='6px'; locMsg.style.pointerEvents='none'; const statusEl = document.getElementById('__worldStatus'); if(statusEl) statusEl.appendChild(locMsg); } // keep it hidden by default; event listener will populate it // (listener is registered once below) // toggle scene night class so CSS vars adjust tree/UI colors for nighttime readability try{ const sceneEl = document.querySelector('.scene'); if(sceneEl) sceneEl.classList.toggle('night', phase === 'night'); }catch(e){} // show active world event and remaining timer (if present) const evBoxId = '__worldEventBox'; let evBox = document.getElementById(evBoxId); if(!evBox){ evBox = document.createElement('div'); evBox.id = evBoxId; evBox.style.fontSize='12px'; evBox.style.color='var(--muted)'; evBox.style.marginTop='6px'; evBox.style.pointerEvents='none'; const statusEl = document.getElementById('__worldStatus'); if(statusEl) statusEl.appendChild(evBox); } const we = window.__worldEvent || null; if(we){ const secs = Math.max(0, Math.ceil((we.expiresAt - Date.now())/1000)); evBox.textContent = `${we.name} — ${secs}s`; evBox.style.display = 'block'; } else { evBox.style.display = 'none'; } } // register a one-time listener to show location-entered messages in the status area document.addEventListener('location:entered', (ev)=>{ try{ const loc = ev && ev.detail && ev.detail.location; if(!loc) return; const msgEl = document.getElementById('__locMsg'); if(!msgEl) return; const typeLabel = (loc.type === 'temple') ? 'Храм' : ((loc.type === 'market') ? 'Рынок' : ((loc.type === 'village') ? 'Деревня' : (loc.type || 'Местность'))); msgEl.textContent = `${typeLabel}: ${loc.name || ''}`; msgEl.style.opacity = '1'; // also show toast for clarity try{ const NOTIF = window.__notifyModule || null; }catch(e){} // prefer existing notify module try{ const n = (typeof NOTIF !== 'undefined' && NOTIF && typeof NOTIF.notify === 'function') ? NOTIF : null; }catch(e){} // use the imported NOTIF if available in this module try{ // fade after 3200ms setTimeout(()=> { msgEl.textContent = ''; }, 3200); }catch(e){} }catch(e){} }); export function drawScene(W_px, H_px){ // delegate to renderer RENDERER.drawScene(W_px, H_px); } /* Input handling */ /* tombstone: duplicated input wiring removed — delegated to input-handlers.js */ // removed duplicated keydown/keyup blocks from earlier refactor /* re-export split modules for backwards compatibility */ export const registerNpcs = HUD.registerNpcs; export const setScale = HUD.setScale; export const U_camTargetZoom = HUD.U_camTargetZoom; export const drawMinimap = MINIMAP.drawMinimap; export const resizeMinimap = MINIMAP.resizeMinimap; export const LanguageSystem = LANG.LanguageSystem; export const RuleEngine = HUD.RuleEngine; export const updateHUD = HUD.updateHUD; export const notify = NOTIF.notify; export const flashIcon = NOTIF.flashIcon; export const saveGame = NOTIF.saveGame; export const loadGame = NOTIF.loadGame; export const startTeleportExport = startTeleport; /* Rule Engine & HUD */ /* tombstone: RuleEngine moved to hud.js */ // removed RuleEngine object {} /* LanguageSystem (gesture mini-game) */ /* tombstone: LanguageSystem moved to language.js */ // removed LanguageSystem object {} // --- add HUD update + small utility helpers used by main.js and other modules --- /* tombstone: updateHUD moved to hud.js */ // removed function updateHUD() {} /* lightweight toast notifier used across code */ /* tombstone: notify moved to notify.js */ // removed function notify() {} /* small visual flash for HUD icons (id of element) */ /* tombstone: flashIcon moved to notify.js */ // removed function flashIcon() {} /* minimal save/load to preserve knowledge & reputation used on death/restart */ /* tombstone: saveGame/loadGame moved to notify.js */ // removed function saveGame() {} // removed function loadGame() {} /* --- added: lightweight village timer & NPC updater used by main loop --- */ export function checkVillageTimers(){ // detect when player enters a location area and auto-discover rules (temple -> silence, village/market -> greeting) const locs = window.__locations || []; if(!locs.length) return; // keep seen set on window to avoid repeated triggers window.___seenLocations = window.___seenLocations || new Set(); for(const loc of locs){ const lx = loc.x, ly = loc.y, lw = loc.w, lh = loc.h; const playerRect = {x: player.x, y: player.y, w: player.w, h: player.h}; const inside = rectsOverlap(playerRect, {x:lx,y:ly,w:lw,h:lh}); if(inside && !window.___seenLocations.has(loc.id)){ window.___seenLocations.add(loc.id); // temple: discover silence rule if(loc.type === 'temple'){ try{ HUD.RuleEngine.discover('silence'); HUD.RuleEngine.changeRep('MiniTribe', 1); }catch(e){} } // village or market: discover greeting rule if(loc.type === 'village' || loc.type === 'market'){ try{ HUD.RuleEngine.discover('greeting'); HUD.RuleEngine.changeRep('MiniTribe', 1); }catch(e){} } // notify UI and status bar about entering this location try{ document.dispatchEvent(new CustomEvent('location:entered',{detail:{location:loc}})); }catch(e){} } } return; } export function updateNpcs(dt){ // simple route-following for NPCs to avoid "not a function" errors and provide basic motion const npcs = window.__npcs || []; for(const n of npcs){ if(!n.route || n.route.length === 0) continue; const target = n.route[n.routeIndex % n.route.length]; const dx = target.x - n.x, dy = target.y - n.y; const d = Math.hypot(dx,dy) || 1; const sightMod = window.__npcSightModifier || 1.0; n.vision = (n.vision || 160) * sightMod; // slow patrol at night const phase = window.__dayPhase || 'day'; const patrolMul = (phase === 'night') ? 0.6 : 1.0; const speed = (n.routeSpeed || 24) * patrolMul * (dt || 0.016); if(d < 2){ n.routeIndex = (n.routeIndex + 1) % n.route.length; } else { n.x += (dx / d) * speed; n.y += (dy / d) * speed; } // Vision handling: NPCs have a vision radius; if player is sneaking they do not notice within this radius n.vision = n.vision || 160; try{ const pdx = (player.x + player.w/2) - (n.x + n.w/2); const pdy = (player.y + player.h/2) - (n.y + n.h/2); const pdist = Math.hypot(pdx,pdy); // when player is sneaking, NPCs ignore player within vision radius (no emoji, no reactions) if(player.sneak && pdist < n.vision){ // ensure any immediate reaction is suppressed; continue continue; } }catch(e){} } } // add a small teleport update stub so modules referencing updateTeleport won't throw when portals are disabled export function updateTeleport(dt){ // placeholder: teleport transitions removed in refactor; keep noop to satisfy exporters return; } export const __teleportUpdate = updateTeleport; /* small convenience: when player picks first item / teleports / learns phrase, show tooltip - listen for internal events */ document.addEventListener('player:first:pickup', ()=> HUD.showFirstTooltip('pickup','Picked up first item — open Journal to inspect',3000)); document.addEventListener('player:first:learn', ()=> HUD.showFirstTooltip('learn','You learned a phrase — check Journal',3000)); // ensure direct Manual button hookup (safer than delegated click) document.addEventListener('DOMContentLoaded', ()=>{ const btn = document.getElementById('openManual'); if(btn) btn.addEventListener('click', (e)=> { e.preventDefault(); openManual(); }); }); // invoke modal init wiring on DOMContentLoaded document.addEventListener('DOMContentLoaded', ()=> { MODALS.initOnDOMContentLoaded(); }); // hook to allow external modules to update contract progress (simple helper) export function progressContract(kind, amount=1){ const list = window.__contracts || []; for(const c of list){ if(c.status==='accepted' && c.kind === kind){ c.progress = Math.min(c.goal, (c.progress||0) + amount); if(c.progress >= c.goal){ c.status='completed'; // give reward if(c.reward && c.reward.rep) HUD.RuleEngine.changeRep('MiniTribe', c.reward.rep); if(c.reward && c.reward.item) { const it = {id:'ct_' + (Math.random()*1e6|0), x: window.__engine.player.x, y: window.__engine.player.y, name: c.reward.item, tags:[]}; window.__engine.items.push(it); NOTIF.notify('Награда: '+c.reward.item,'success'); } if(c.reward && c.reward.phrase && window.__LanguageSystem){ window.__LanguageSystem.learnedPhrases.push(c.reward.phrase); NOTIF.notify('Новая фраза выучена','info'); } NOTIF.notify('Контракт завершён: '+c.title, 'success'); } } } window.__contracts = list.slice(); localStorage.setItem('rogue_proto_contracts', JSON.stringify(window.__contracts)); updateHUD(); } /* expose for other modules */ export const openContractsPanel = openContracts; export const closeContractsPanel = closeContracts; // ensure Codex UI is kept up-to-date when dictionary opens document.addEventListener('DOMContentLoaded', ()=>{ renderCodexPanel(); });