From 9ccaecddd24e31b22f068cfc28be4d018345e98c Mon Sep 17 00:00:00 2001 From: eliotbyte Date: Fri, 24 Oct 2025 15:51:20 +0300 Subject: [PATCH 1/3] add :block placement for single blocks --- .../scripting/scripting_world_generation.cpp | 31 +++++ src/world/generator/StructurePlacement.hpp | 14 ++- src/world/generator/WorldGenerator.cpp | 108 +++++++++++++++++- src/world/generator/WorldGenerator.hpp | 7 ++ 4 files changed, 153 insertions(+), 7 deletions(-) diff --git a/src/logic/scripting/scripting_world_generation.cpp b/src/logic/scripting/scripting_world_generation.cpp index 3199b46c..ffff47eb 100644 --- a/src/logic/scripting/scripting_world_generation.cpp +++ b/src/logic/scripting/scripting_world_generation.cpp @@ -152,6 +152,32 @@ public: placements.emplace_back(priority, LinePlacement {block, a, b, radius}); } + void perform_block(lua::State* L, std::vector& placements) { + rawgeti(L, 2); + blockid_t block = touinteger(L, -1); + pop(L); + + rawgeti(L, 3); + glm::ivec3 pos = tovec3(L, -1); + pop(L); + + uint8_t rotation = 0; + if (objlen(L, -1) >= 4) { + rawgeti(L, 4); + rotation = tointeger(L, -1) & 0b11; + pop(L); + } + + int priority = 0; + if (objlen(L, -1) >= 5) { + rawgeti(L, 5); + priority = tointeger(L, -1); + pop(L); + } + + placements.emplace_back(priority, BlockPlacement {block, pos, rotation}); + } + void perform_placement(lua::State* L, std::vector& placements) { rawgeti(L, 1); int structIndex = 0; @@ -162,6 +188,11 @@ public: perform_line(L, placements); return; + } else if (!std::strcmp(name, ":block")) { + pop(L); + + perform_block(L, placements); + return; } const auto& found = def.structuresIndices.find(name); if (found != def.structuresIndices.end()) { diff --git a/src/world/generator/StructurePlacement.hpp b/src/world/generator/StructurePlacement.hpp index 2ec7185e..c2c174ab 100644 --- a/src/world/generator/StructurePlacement.hpp +++ b/src/world/generator/StructurePlacement.hpp @@ -27,12 +27,22 @@ struct LinePlacement { } }; +struct BlockPlacement { + blockid_t block; + glm::ivec3 position; + uint8_t rotation; + + BlockPlacement(blockid_t block, glm::ivec3 position, uint8_t rotation) + : block(block), position(std::move(position)), rotation(rotation) { + } +}; + struct Placement { int priority; - std::variant placement; + std::variant placement; Placement( int priority, - std::variant placement + std::variant placement ) : priority(priority), placement(std::move(placement)) {} }; diff --git a/src/world/generator/WorldGenerator.cpp b/src/world/generator/WorldGenerator.cpp index ccd52db1..3125bff0 100644 --- a/src/world/generator/WorldGenerator.cpp +++ b/src/world/generator/WorldGenerator.cpp @@ -204,6 +204,46 @@ void WorldGenerator::placeLine(const LinePlacement& line, int priority) { } } +void WorldGenerator::placeBlock(const BlockPlacement& block, int priority) { + // Compute world-space AABB of the extended block to distribute to prototypes + const auto& indices = content.getIndices()->blocks; + const auto& def = indices.require(block.block); + const auto& rot = def.rotations.variants[block.rotation & 0b11]; + + glm::ivec3 minp = block.position; + glm::ivec3 maxp = block.position; + const auto size = def.size; + for (int sy = 0; sy < size.y; sy++) { + for (int sz = 0; sz < size.z; sz++) { + for (int sx = 0; sx < size.x; sx++) { + glm::ivec3 p = block.position; + p += rot.axes[0] * sx; + p += rot.axes[1] * sy; + p += rot.axes[2] * sz; + minp = glm::min(minp, p); + maxp = glm::max(maxp, p); + } + } + } + // inclusive-exclusive for max; expand by 1 to compute chunk coverage + maxp += glm::ivec3(1, 1, 1); + AABB aabb(minp, maxp); + int cxa = floordiv(aabb.a.x); + int cza = floordiv(aabb.a.z); + int cxb = floordiv(aabb.b.x); + int czb = floordiv(aabb.b.z); + for (int cz = cza; cz <= czb; cz++) { + for (int cx = cxa; cx <= cxb; cx++) { + const auto& found = prototypes.find({cx, cz}); + if (found != prototypes.end()) { + // position becomes relative to prototype chunk + glm::ivec3 rel = block.position - glm::ivec3(cx * CHUNK_W, 0, cz * CHUNK_D); + found->second->placements.emplace_back(priority, BlockPlacement{block.block, rel, block.rotation}); + } + } + } +} + void WorldGenerator::placeStructures( const std::vector& placements, ChunkPrototype& prototype, @@ -217,9 +257,11 @@ void WorldGenerator::placeStructures( continue; } placeStructure(*sp, placement.priority, chunkX, chunkZ); + } else if (auto lp = std::get_if(&placement.placement)) { + placeLine(*lp, placement.priority); } else { - const auto& line = std::get(placement.placement); - placeLine(line, placement.priority); + const auto& bp = std::get(placement.placement); + placeBlock(bp, placement.priority); } } } @@ -482,9 +524,10 @@ void WorldGenerator::generatePlacements( for (const auto& placement : placements) { if (auto structure = std::get_if(&placement.placement)) { generateStructure(prototype, *structure, voxels, chunkX, chunkZ); - } else { - const auto& line = std::get(placement.placement); - generateLine(prototype, line, voxels, chunkX, chunkZ); + } else if (auto line = std::get_if(&placement.placement)) { + generateLine(prototype, *line, voxels, chunkX, chunkZ); + } else if (auto block = std::get_if(&placement.placement)) { + generateBlock(prototype, *block, voxels, chunkX, chunkZ); } } } @@ -591,6 +634,61 @@ void WorldGenerator::generateLine( } } +void WorldGenerator::generateBlock( + const ChunkPrototype& prototype, + const BlockPlacement& placement, + voxel* voxels, + int chunkX, int chunkZ +) { + const auto& indices = content.getIndices()->blocks; + const auto& def = indices.require(placement.block); + + int cgx = chunkX * CHUNK_W; + int cgz = chunkZ * CHUNK_D; + + glm::ivec3 origin = placement.position; // already relative to chunk in placeBlock + + if (origin.x < 0 || origin.x >= CHUNK_W || + origin.z < 0 || origin.z >= CHUNK_D || + origin.y < 0 || origin.y >= CHUNK_H) { + return; + } + + // write origin voxel + auto& vox = voxels[vox_index(origin.x, origin.y, origin.z)]; + vox.id = placement.block; + vox.state = {}; + vox.state.rotation = placement.rotation & 0b11; + + // expand extended blocks + if (def.rt.extended) { + const auto& rot = def.rotations.variants[vox.state.rotation]; + const auto size = def.size; + for (int sy = 0; sy < size.y; sy++) { + for (int sz = 0; sz < size.z; sz++) { + for (int sx = 0; sx < size.x; sx++) { + if ((sx | sy | sz) == 0) continue; + glm::ivec3 pos = origin; + pos += rot.axes[0] * sx; + pos += rot.axes[1] * sy; + pos += rot.axes[2] * sz; + if (pos.x < 0 || pos.x >= CHUNK_W || + pos.y < 0 || pos.y >= CHUNK_H || + pos.z < 0 || pos.z >= CHUNK_D) { + continue; + } + struct voxel seg; + seg.id = placement.block; + seg.state = {}; + seg.state.rotation = vox.state.rotation; + seg.state.segment = ((sx > 0) | ((sy > 0) << 1) | ((sz > 0) << 2)); + voxels[vox_index(pos.x, pos.y, pos.z)] = seg; + } + } + } + } +} + WorldGenDebugInfo WorldGenerator::createDebugInfo() const { const auto& area = surroundMap.getArea(); const auto& levels = area.getBuffer(); diff --git a/src/world/generator/WorldGenerator.hpp b/src/world/generator/WorldGenerator.hpp index 7b64b84d..2dfa50b6 100644 --- a/src/world/generator/WorldGenerator.hpp +++ b/src/world/generator/WorldGenerator.hpp @@ -79,6 +79,7 @@ class WorldGenerator { ); void placeLine(const LinePlacement& line, int priority); + void placeBlock(const BlockPlacement& block, int priority); void generatePlacements( const ChunkPrototype& prototype, voxel* voxels, int x, int z @@ -89,6 +90,12 @@ class WorldGenerator { voxel* voxels, int x, int z ); + void generateBlock( + const ChunkPrototype& prototype, + const BlockPlacement& placement, + voxel* voxels, + int x, int z + ); void generateStructure( const ChunkPrototype& prototype, const StructurePlacement& placement, From 3074836bcccbe6812a286d4000b378a219c4f16d Mon Sep 17 00:00:00 2001 From: eliotbyte Date: Fri, 24 Oct 2025 15:58:33 +0300 Subject: [PATCH 2/3] docs: add :block placement to world generator --- doc/en/world-generator.md | 16 ++++++++++++++++ doc/ru/world-generator.md | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/doc/en/world-generator.md b/doc/en/world-generator.md index 40fd1b37..a173f442 100644 --- a/doc/en/world-generator.md +++ b/doc/en/world-generator.md @@ -427,6 +427,22 @@ Where: - point_a, point_b - vec3, vec3 positions of the start and end of the tunnel. - radius - radius of the tunnel in blocks +Single block: +```lua +{":block", block_id, position, [rotation], [priority]} +``` + +Where: +- block_id: numeric runtime id of the block to place. +- position: vec3 world position in blocks, relative to the current chunk start. +- rotation: 0–3, rotation around the Y axis. Default: 0. For extended blocks (size > 1), all segments use this rotation. +- priority: integer order. Higher values are placed later and overwrite lower‑priority placements. + +Notes: +- `:block` automatically expands extended blocks into all their segments and replaces any voxels occupying those cells. +- Placement is chunk‑border safe: the engine distributes the placement to all affected chunk prototypes based on the block’s size/AABB. +- Use `:block` for single blocks; use `:line` for tunnels or continuous lines. + ### Small structures placement ```lua diff --git a/doc/ru/world-generator.md b/doc/ru/world-generator.md index 7f7be5c4..5e0c50c1 100644 --- a/doc/ru/world-generator.md +++ b/doc/ru/world-generator.md @@ -430,6 +430,22 @@ end - точка_а, точка_б - vec3, vec3 позиции начала и конца тоннеля. - радиус - радиус тоннеля в блоках +Одиночный блок: +```lua +{":block", id_блока, позиция, [поворот], [приоритет]} +``` + +Где: +- id_блока: числовой runtime‑id блока, который нужно поставить. +- позиция: vec3 позиция в блоках относительно начала текущего чанка. +- поворот: 0–3, поворот вокруг оси Y. По умолчанию: 0. Для расширенных блоков (размер > 1) этот поворот применяется ко всем сегментам. +- приоритет: целое число. Бóльший приоритет ставится позже и перезаписывает более низкий. + +Примечания: +- `:block` автоматически раскладывает расширенные блоки на сегменты и заменяет любые блоки в занимаемых ячейках. +- Размещение корректно работает на границах чанков: движок сам разносит плейсмент по затрагиваемым прототипам на основе размера/AABB блока. +- `:block` используйте для точечных блоков; `:line` — для туннелей/линий. + ### Расстановка малых структур From b85c5e367a2adea57ab0c3111e3d3b0cc924322b Mon Sep 17 00:00:00 2001 From: eliotbyte Date: Fri, 24 Oct 2025 20:13:05 +0300 Subject: [PATCH 3/3] fix extended block placement across chunk borders --- src/world/generator/StructurePlacement.hpp | 5 ++-- src/world/generator/WorldGenerator.cpp | 35 +++++++++++----------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/world/generator/StructurePlacement.hpp b/src/world/generator/StructurePlacement.hpp index c2c174ab..d64b6d08 100644 --- a/src/world/generator/StructurePlacement.hpp +++ b/src/world/generator/StructurePlacement.hpp @@ -31,9 +31,10 @@ struct BlockPlacement { blockid_t block; glm::ivec3 position; uint8_t rotation; + bool mirror; - BlockPlacement(blockid_t block, glm::ivec3 position, uint8_t rotation) - : block(block), position(std::move(position)), rotation(rotation) { + BlockPlacement(blockid_t block, glm::ivec3 position, uint8_t rotation, bool mirror=false) + : block(block), position(std::move(position)), rotation(rotation), mirror(mirror) { } }; diff --git a/src/world/generator/WorldGenerator.cpp b/src/world/generator/WorldGenerator.cpp index 3125bff0..227b7d1d 100644 --- a/src/world/generator/WorldGenerator.cpp +++ b/src/world/generator/WorldGenerator.cpp @@ -238,7 +238,8 @@ void WorldGenerator::placeBlock(const BlockPlacement& block, int priority) { if (found != prototypes.end()) { // position becomes relative to prototype chunk glm::ivec3 rel = block.position - glm::ivec3(cx * CHUNK_W, 0, cz * CHUNK_D); - found->second->placements.emplace_back(priority, BlockPlacement{block.block, rel, block.rotation}); + bool owner = (cx == floordiv(block.position.x)) && (cz == floordiv(block.position.z)); + found->second->placements.emplace_back(priority, BlockPlacement{block.block, rel, block.rotation, !owner}); } } } @@ -643,26 +644,26 @@ void WorldGenerator::generateBlock( const auto& indices = content.getIndices()->blocks; const auto& def = indices.require(placement.block); - int cgx = chunkX * CHUNK_W; - int cgz = chunkZ * CHUNK_D; - - glm::ivec3 origin = placement.position; // already relative to chunk in placeBlock - - if (origin.x < 0 || origin.x >= CHUNK_W || - origin.z < 0 || origin.z >= CHUNK_D || - origin.y < 0 || origin.y >= CHUNK_H) { - return; + glm::ivec3 origin = placement.position; // relative; may be outside + int rotIndex = 0; + if (def.rotatable && def.rotations.variantsCount) { + rotIndex = placement.rotation % def.rotations.variantsCount; } - // write origin voxel - auto& vox = voxels[vox_index(origin.x, origin.y, origin.z)]; - vox.id = placement.block; - vox.state = {}; - vox.state.rotation = placement.rotation & 0b11; + // write origin only for owner chunk (mirror==false) and if inside bounds + if (!placement.mirror && + origin.x >= 0 && origin.x < CHUNK_W && + origin.y >= 0 && origin.y < CHUNK_H && + origin.z >= 0 && origin.z < CHUNK_D) { + auto& vox = voxels[vox_index(origin.x, origin.y, origin.z)]; + vox.id = placement.block; + vox.state = {}; + vox.state.rotation = rotIndex; + } // expand extended blocks if (def.rt.extended) { - const auto& rot = def.rotations.variants[vox.state.rotation]; + const auto& rot = def.rotations.variants[rotIndex]; const auto size = def.size; for (int sy = 0; sy < size.y; sy++) { for (int sz = 0; sz < size.z; sz++) { @@ -680,7 +681,7 @@ void WorldGenerator::generateBlock( struct voxel seg; seg.id = placement.block; seg.state = {}; - seg.state.rotation = vox.state.rotation; + seg.state.rotation = rotIndex; seg.state.segment = ((sx > 0) | ((sy > 0) << 1) | ((sz > 0) << 2)); voxels[vox_index(pos.x, pos.y, pos.z)] = seg; }