grechka-game/srcv3/world/world-storage3d.js

83 lines
3.6 KiB
JavaScript

// ==================== ХРАНИЛИЩЕ МИРА 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 };