83 lines
3.6 KiB
JavaScript
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 };
|