"use strict"; const MapManager = (function(){ const MapManager = function(hollow_bond){ const self = this, frontier_selected_keys = new Set(); let maximum_chunks = 0, maximum_complete_layer = 0, frontier_layer = 0; this.current_chunk = {x : 0, y : 0, z : 0}; // Colección de Chunks activos indexados por "x_y_z" this.chunks = {}; this.frames = {}; this.rooms = {}; this.holes = {}; this.stairs = {}; this.elevators = {}; const get_chunk_key = (x, y, z) => `${x}_${y}_${z}`; this.get_chunk = (x, y, z) => self.chunks[get_chunk_key(x, y, z)] || null; this.clear = () => { self.chunks = {}; self.frames = {}; self.rooms = {}; self.holes = {}; self.stairs = {}; self.elevators = {}; }; /** * Destruye y libera un Chunk de la memoria activa. */ const unload_chunk = (x, y, z) => { const key = get_chunk_key(x, y, z), visualizer = ( hollow_bond.inspector.on ? hollow_bond.inspector : hollow_bond.dungeon.on ? hollow_bond.dungeon : null); if(!self.chunks[key]) return; // Avisamos a DungeonManager si está activo para que elimine los meshes 3D visualizer && visualizer.remove_chunk(self.chunks[key]); // Limpiamos referencias de frames y entidades de ese Chunk for(const id in self.chunks[key].frames) delete self.frames[id]; for(const id in self.chunks[key].rooms) delete self.rooms[id]; for(const id in self.chunks[key].holes) delete self.holes[id]; for(const id in self.chunks[key].stairs) delete self.stairs[id]; for(const id in self.chunks[key].elevators) delete self.elevators[id]; delete self.chunks[key]; }; /** * Actualiza el centro del streaming. * Carga los nuevos vecinos que toquen y descarga los que queden lejos. */ this.update_active_center = (cx, cy, cz, callback = null) => { const newly_created = [], pending_targets = [ [0, 0, 0], // Centro actual [-1, 0, 0], // Oeste [1, 0, 0], // Este [0, -1, 0], // Abajo [0, 1, 0], // Arriba [0, 0, -1], // Norte [0, 0, 1] // Sur ].map(([ox, oy, oz]) => [cx + ox, cy + oy, cz + oz]).filter(([tx, ty, tz]) => ( !self.chunks[get_chunk_key(tx, ty, tz)] && chunk_exists(tx, ty, tz) )), process_uploads = () => { for(const key in self.chunks) Math.max(Math.abs(self.chunks[key].x - cx), Math.abs(self.chunks[key].y - cy), Math.abs(self.chunks[key].z - cz)) > 2 && unload_chunk(self.chunks[key].x, self.chunks[key].y, self.chunks[key].z); }; self.current_chunk.x = cx; self.current_chunk.y = cy; self.current_chunk.z = cz; if(callback){ let i = 0; const process_next = () => { if(i < pending_targets.length) generate_chunk(...pending_targets[i ++], chunk => { chunk && newly_created.push(chunk); process_next(); }); else{ process_uploads(); callback(newly_created); }; }; process_next(); return; }; for(const [tx, ty, tz] of pending_targets){ const chunk = generate_chunk(tx, ty, tz); chunk && newly_created.push(chunk); }; process_uploads(); return newly_created; }; /** * Calcula el límite de capas completas y la selección exacta de la capa frontera. */ const setup_budget_layers = () => { frontier_selected_keys.clear(); if(maximum_chunks <= 0) return; // 1. Caso base: 1 Chunk exacto (solo el origen) if(maximum_chunks === 1){ maximum_complete_layer = 0; frontier_layer = 0; return; }; // Buscamos si caben cubos completos mayores: (2*L + 1)^3 let layer = 0, leftover; // Capa 0 está completa (el Chunk 0, 0, 0) maximum_complete_layer = 0; frontier_layer = 1; // for(; Math.pow(2 * (layer + 1) + 1, 3) <= maximum_chunks; layer ++); while(Math.pow(2 * (layer + 1) + 1, 3) <= maximum_chunks){ layer++; } maximum_complete_layer = layer; // Capa 100% llena frontier_layer = layer + 1; // Capa frontera leftover = maximum_chunks - Math.pow(2 * maximum_complete_layer + 1, 3); if(leftover <= 0) return; // 2. Recolectamos todas las coordenadas de la cáscara cúbica frontier_layer const candidates = []; for(let cx = -frontier_layer; cx <= frontier_layer; cx ++) for(let cy = -frontier_layer; cy <= frontier_layer; cy ++) for(let cz = -frontier_layer; cz <= frontier_layer; cz ++) Math.max(Math.abs(cx), Math.abs(cy), Math.abs(cz)) === frontier_layer && candidates.push({ key : `${cx}_${cy}_${cz}`, manhattan : Math.abs(cx) + Math.abs(cy) + Math.abs(cz), score : hollow_bond.hash.chunk_presence(cx, cy, cz) }); // 3. Ordenamos: primero los que tienen contacto de cara (menor distancia Manhattan), // y dentro de ellos por su score determinista del hash. candidates.sort((a, b) => ( a.manhattan !== b.manhattan ? a.manhattan - b.manhattan : a.score - b.score)); // 4. Seleccionamos exactamente 'leftover' for(let i = 0, count = Math.min(leftover, candidates.length); i < count; i ++) frontier_selected_keys.add(candidates[i].key); }; const chunk_exists = (cx, cy, cz) => { // Modo Infinito if(maximum_chunks <= 0) return true; const layer = Math.max(Math.abs(cx), Math.abs(cy), Math.abs(cz)); return ( layer <= maximum_complete_layer ? true : layer > frontier_layer ? false : frontier_selected_keys.has(`${cx}_${cy}_${cz}`)); }; const generate_chunk = (x, y, z, callback = null) => { // Si el Chunk no existe en el presupuesto, no se crea if(!chunk_exists(x, y, z)){ callback && callback(); return null; }; const key = get_chunk_key(x, y, z); if(self.chunks[key]){ callback && callback(); return self.chunks[key]; }; const visualizer = ( hollow_bond.inspector.on ? hollow_bond.inspector : hollow_bond.dungeon.on ? hollow_bond.dungeon : null), on_chunk_ready = () => { Object.assign(self.frames, self.chunks[key].frames); Object.assign(self.rooms, self.chunks[key].rooms); Object.assign(self.holes, self.chunks[key].holes); Object.assign(self.stairs, self.chunks[key].stairs); Object.assign(self.elevators, self.chunks[key].elevators); visualizer && visualizer.sync_chunk(self.chunks[key]); callback && callback(); }; self.chunks[key] = new ChunkModel(hollow_bond, { x : x, y : y, z : z, neighbors : { west : chunk_exists(x - 1, y, z), east : chunk_exists(x + 1, y, z), down : chunk_exists(x, y - 1, z), up : chunk_exists(x, y + 1, z), north : chunk_exists(x, y, z - 1), south : chunk_exists(x, y, z + 1) } }, callback ? on_chunk_ready : null); callback || on_chunk_ready(); return self.chunks[key]; }; this.generate = (seed = null, new_maximum_chunks = 0, callback = null) => { self.clear(); if(seed !== null) hollow_bond.set_seed(seed); maximum_chunks = new_maximum_chunks; setup_budget_layers(); if(callback) return new Promise(resolve => { self.update_active_center(0, 0, 0, () => { console.log(self.chunks); callback(self.chunks); resolve(self.chunks); }); }); self.update_active_center(0, 0, 0); console.log(self.chunks); return self.chunks; }; this.generate_asynchronous = (seed = null, new_maximum_chunks = 0, callback = null) => self.generate(seed, new_maximum_chunks, callback || (() => {})); const constructor = () => {}; constructor(); }; return MapManager; })();