/
chewy
/
BDZ_GenAI
Обзор
Документация
Войти
/
chewy
/
BDZ_GenAI
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
scripts/main.js
255 строк
10 KB
pichugin.sa
Раскидал все по папкам, чтобы нормально выглядел репозиторий
16 ноя 2025, 21:55
16 ноя 2025, 21:55
bd854ee
Код
Авторство
О чём код?
// main.js - orchestrates resize, loop, and core update (uses engine + ui) import * as E from './engine.js'; import * as U from './ui.js'; import * as HUD from '../hud.js'; import * as LANG from './language.js'; import * as NOTIF from './notify.js'; import { CONFIG } from './engine.js'; import Ambient from './ambient.js'; import Events from './events.js'; import * as WorldGen from './worldgen.js'; import { updateBase } from '../loop-control.js'; import '../input-handlers.js'; // expose references for cross-module best-effort save/load and lookups window.__engine = { player: E.player, npcs: E.npcs, mobs: E.mobs }; window.__LanguageSystem = LANG.LanguageSystem; window.__RuleEngine = HUD.RuleEngine; // note: portals come from worldgen; do not override window.__portals here // register NPCs with UI render layer U.registerNpcs(E.npcs); const canvas = E.canvas; const ctx = E.ctx; function onResize(){ // don't assign to imported namespace (read-only) — just call the engine resize E.resizeCanvas(); U.resizeMinimap(); } window.addEventListener('resize', onResize); onResize(); let last = performance.now(); /* tombstone: moved the previous inline update() heavy body to loop-control.js */ // removed function updateBase(dt) { /* massive update loop extracted to loop-control.js */ } // helper: apply relic effects to player function applyRelicEffect(relic){ if(!E.player.effects) E.player.effects = []; const kind = relic.kind || 'restore'; if(kind === 'speed'){ E.player.effects.push({kind:'speed', mul:1.4, t:12}); NOTIF.notify('Реликвия: ускорение (+40%) на 12s', 'success'); } else if(kind === 'knowledge'){ // simulate learning a phrase const phrase = ['✦','☀','★','◆','✿'].slice(0,3).join(' '); window.__LanguageSystem && window.__LanguageSystem.learnedPhrases && window.__LanguageSystem.learnedPhrases.push(phrase); NOTIF.notify('Реликвия: знание — выучена фраза', 'info'); document.dispatchEvent(new CustomEvent('player:first:learn')); } else { // restore stamina/health E.player.stamina = Math.min(100, (E.player.stamina||100) + 40); E.player.health = Math.min(100, (E.player.health||100) + 18); NOTIF.notify('Реликвия: восстановление', 'success'); } } // interaction logic: E now also interacts with shrines/relics/chests (before language) document.addEventListener('keydown', (e)=>{ if(e.key.toLowerCase() === 'e'){ if(E.player.sneak) return; // cannot interact while sneaking // portals first: if near portal, teleport (respect requireE) for(const p of (window.__portals || [])){ if(E.rectsOverlap(E.player, {x:p.x-8,y:p.y-8,w:p.w+16,h:p.h+16})){ // start teleport immediately on E try{ // delegate to ui startTeleport via exported helper if available, else event if(typeof U.startTeleport === 'function'){ U.startTeleport(p); } else if(typeof U.startTeleportExport === 'function'){ U.startTeleportExport(p); } else { document.dispatchEvent(new CustomEvent('portal:enter',{detail:{portal:p}})); } }catch(err){} return; } } // items pickup: check items first (pick and store in inventory) for(let i = E.items.length - 1; i >= 0; i--){ const it = E.items[i]; if(E.rectsOverlap(E.player, {x:it.x-8,y:it.y-8,w:16,h:16})){ E.player.inventory = E.player.inventory || []; E.player.inventory.push(it); NOTIF.notify('Подобрано: ' + (it.name || 'Item'), 'success'); E.items.splice(i,1); window.__items = E.items.slice(); try{ document.dispatchEvent(new CustomEvent('player:first:pickup')); }catch(e){} return; } } // check nearby relics first (pickup) for(let i = E.relics.length - 1; i >= 0; i--){ const r = E.relics[i]; if(E.rectsOverlap(E.player, {x:r.x-8,y:r.y-8,w:r.w+16,h:r.h+16})){ applyRelicEffect(r); NOTIF.notify('Вы нашли реликвию: ' + (r.kind||'Artefact'), 'info'); E.relics.splice(i,1); window.__relics = E.relics.slice(); return; } } // chests: can open if chest.locked === false or has been safe for 30s (safeTimer <=0) for(const c of E.chests){ if(E.rectsOverlap(E.player, {x:c.x-8,y:c.y-8,w:c.w+16,h:c.h+16})){ if(c.locked){ if(c.safeTimer <= 0){ c.locked = false; } else { NOTIF.notify('Сундук заперт — не нарушайте правила ещё '+Math.ceil(c.safeTimer)+'s', 'warn'); return; } } // open chest: give random loot (item) const lootName = ['Gold','Fragment','Relic Shard','Old Map'][(Math.random()*4)|0]; E.items.push({id:'loot_'+(Math.random()*1e6|0), x: c.x+6, y: c.y-8, name: lootName, tags:[]}); NOTIF.notify('Вы открыли сундук — Вы нашли: ' + lootName, 'success'); // remove chest const idx = E.chests.indexOf(c); if(idx>=0) E.chests.splice(idx,1); window.__chests = E.chests.slice(); return; } } // shrine: activate random rule or bonus for(const s of E.shrines){ if(E.rectsOverlap(E.player, {x:s.x-12,y:s.y-12,w:s.w+24,h:s.h+24})){ // call shrine interaction helper try{ E.shrineInteract && E.shrineInteract(s); }catch(err){ console.error(err); } return; } } // Board: open Contracts UI if near for(const b of E.boards || []){ if(E.rectsOverlap(E.player, {x:b.x-12,y:b.y-12,w:b.w+24,h:b.h+24})){ // open contracts panel UI U.openContracts(); return; } } // fallback: language interaction (existing) for(const n of E.npcs){ const near = E.rectsOverlap(E.player, {x:n.x-24,y:n.y-24,w:n.w+48,h:n.h+48}); if(near){ U.LanguageSystem.openFor(n); break; } } } }); // ensure HUD shows initial faction values on start U.RuleEngine && U.RuleEngine.changeRep && U.RuleEngine.changeRep(0); // update seed display when world is generated (initial world generation triggers event from engine) window.addEventListener('world:generated', (ev)=>{ const v = document.getElementById('seedValue'); if(v) v.textContent = (ev && ev.detail && typeof ev.detail.seed !== 'undefined') ? ev.detail.seed : (E.seed || window.__worldSeed || '—'); }); // ensure initial seed is shown (engine.generateWorld runs on module load) const seedEl = document.getElementById('seedValue'); if(seedEl) seedEl.textContent = (E.seed || window.__worldSeed || '—'); // hook restart button const restartBtn = document.getElementById('restartWorld'); if(restartBtn) restartBtn.addEventListener('click', ()=>{ const newSeed = Math.floor(Math.random()*1e9); WorldGen.generateWorld(newSeed); U.resizeMinimap(); U.notify('Мир перегенерирован (seed: '+newSeed+')','info',1200); }); // initial world generation should use worldgen // previous engine.generateWorld call on module load is now handled here: if(typeof window.__worldSeed === 'undefined'){ WorldGen.generateWorld(Math.floor(Math.random()*1e9)); } // autosave: persist key "ScaleShift_save_v1" every 60s let autosaveTimer = 0; function doAutosave(){ try{ const state = { player: { x: E.player.x, y: E.player.y, w: E.player.w, h: E.player.h, stamina: E.player.stamina, health: E.player.health, scale: E.player.scale, inventory: E.player.inventory || [] }, scaleSetting: HUD.scaleSetting, reputation: HUD.RuleEngine ? HUD.RuleEngine.factions : {}, codex: (window.__LanguageSystem && window.__LanguageSystem.codex) || {}, seed: E.seed || window.__worldSeed, timeOfDay: E.timeOfDay || window.__engine && window.__engine.timeOfDay || 0 }; localStorage.setItem('ScaleShift_save_v1', JSON.stringify(state)); NOTIF.notify('Autosave выполнено', 'info', 900); }catch(e){ console.error('autosave failed', e); } } last = performance.now(); requestAnimationFrame(loop); // ensure engine init includes time/weather initialisation E.initTimeAndWeather && E.initTimeAndWeather(); // world event scheduler: periodic 60-120s random events with gameplay effects Events.startScheduler(); // REFACTOR: orchestration logic has been trimmed — heavy subroutines were extracted to loop-control.js and input-handlers.js // Tombstones below indicate intentionally-removed large helpers. /* tombstone: moved the previous inline update() heavy body to loop-control.js */ // removed function updateBase(dt) { /* massive update loop extracted to loop-control.js */ } /* tombstone: moved input key wiring & teleport bridging to input-handlers.js */ // removed block: window.addEventListener('keydown', ...) / window.addEventListener('keyup', ...) function loop(now){ const dt = Math.min(0.033, (now - last) / 1000); last = now; autosaveTimer += dt; if(autosaveTimer >= 60){ autosaveTimer = 0; doAutosave(); } // respect global pause flag (set when Manual modal is open) if(!window.__paused) updateBase(dt); U.drawScene(canvas.clientWidth, canvas.clientHeight); // auto-handle player death: save knowledge/progress then restart world and reload if(E.player.health <= 0){ // prevent multiple restarts in same frame by a flag on player if(!E.player._deadHandled){ E.player._deadHandled = true; const newSeed = Math.floor(Math.random()*1e9); // save current progress (knowledge/factions/inventory/quests) U.saveGame(); // regenerate world and immediately reload saved progress to preserve knowledge & progress E.generateWorld(newSeed); U.resizeMinimap(); // slight delay to ensure world generation finished before load setTimeout(()=> { U.loadGame(); U.notify('Вы погибли — мир перезапущен, знания сохранены', 'info', 1800); // clear death flag after short delay to allow future deaths setTimeout(()=> { E.player._deadHandled = false; }, 800); }, 120); } } requestAnimationFrame(loop); } // event bridge: allow ui to start teleport fade document.addEventListener('portal:enter', (ev)=>{ const p = ev && ev.detail && ev.detail.portal; if(!p) return; try{ // access internal startTeleport via function symbol if exposed U.startTeleport ? U.startTeleport(p) : (function(){ /* fallback: move instantly */ E.player.x = p.to.x; E.player.y = p.to.y; })(); }catch(e){} });