// ==================== ХРАНИЛИЩЕ МИРА 3D — GrechkaCraft v3 ==================== // Ключ блока: "x,y,z". Единый источник для рендера, физики, сети и сейвов. // Мир по всем трём осям: X — вперёд, Y — вверх, Z — вбок (глубина мира). import { BLOCKS } from '../data/blocks.js'; import { state } from '../core/state.js'; import { occSet, occDensity, occRebuildFrom, occClear, OCC_EMPTY } from './occ3d.js'; const grid = state.grid; // Map: "x,y,z" => { gx, gy, gz, t, dead } const blocks = state.blocks; // Array для перебора/рендера function k3(gx, gy, gz) { return gx + ',' + gy + ',' + gz; } function getBlock(gx, gy, gz) { const b = grid.get(k3(gx, gy, gz)); if (b && b.dead) return null; return b || null; } function hasBlock(gx, gy, gz) { return grid.has(k3(gx, gy, gz)); } function isSolid(gx, gy, gz) { const b = getBlock(gx, gy, gz); if (!b) return false; const def = BLOCKS[b.t]; return !!def.solid && !def.fluid && !def.decor && !def.climbable; } // Непрозрачный ли блок (для отсечения внутренних граней не используется — мы рендерим кубы) function isOpaque(gx, gy, gz) { const b = getBlock(gx, gy, gz); if (!b) return false; const def = BLOCKS[b.t]; return !!def.solid && !def.fluid && !def.decor && !def.climbable; } // Третий аргумент isPlayerPlaced. Четвёртый — bumpWorld: при генерации мира // версию рендера дёргать нельзя (тысячи блоков за кадр = полная перестройка // мешей и дёрганье текстур). Меняем мир только при действиях игрока. function setBlock(gx, gy, gz, t, isPlayerPlaced = false, bumpWorld = true) { const key = k3(gx, gy, gz); if (grid.has(key)) return false; const b = { gx, gy, gz, t, dead: false, active: false, fuse: 0 }; grid.set(key, b); blocks.push(b); // Окклюзивная сетка обязана знать про новый блок: без этого внутренние // грани соседей останутся видны (или пропадут видимые блоки). occSet(gx, gy, gz, occDensity(t)); if (isPlayerPlaced) { state.placedBlocks.push({ gx, gy, gz, t }); } if (bumpWorld) { state.blocksVersion = (state.blocksVersion || 0) + 1; } return true; } // Версия рендера: бамп делает только игрок (постановка/разрушение блока) function touchWorld() { state.blocksVersion = (state.blocksVersion || 0) + 1; } function removeBlock(gx, gy, gz) { const key = k3(gx, gy, gz); const b = grid.get(key); if (!b) return null; if (BLOCKS[b.t].unbreakable) return null; grid.delete(key); b.dead = true; // Клетка освободилась — сбрасываем окклюзию, иначе пустота продолжит // считаться непрозрачной и пропадёт блок вокруг дырки. occSet(gx, gy, gz, OCC_EMPTY); const wasPlayerPlaced = state.placedBlocks.some(pb => pb.gx === gx && pb.gy === gy && pb.gz === gz); if (wasPlayerPlaced) { state.placedBlocks = state.placedBlocks.filter(pb => !(pb.gx === gx && pb.gy === gy && pb.gz === gz)); } else { state.removedBlocks.push({ gx, gy, gz }); } state.blocksVersion = (state.blocksVersion || 0) + 1; return b; } export { grid, blocks, k3, getBlock, hasBlock, isSolid, isOpaque, setBlock, removeBlock, touchWorld, occRebuildFrom, occClear };