From f3126d8bcfd6ddc651c7c4a1b58635eadff31699 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Sun, 23 Aug 2026 21:06:26 +0200 Subject: [PATCH] feat(core): add prefab structure templates and block stamping --- core/include/vf/Structures.h | 33 +++++++++ core/src/Structures.cpp | 132 +++++++++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 core/include/vf/Structures.h create mode 100644 core/src/Structures.cpp diff --git a/core/include/vf/Structures.h b/core/include/vf/Structures.h new file mode 100644 index 0000000..a478390 --- /dev/null +++ b/core/include/vf/Structures.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include +#include "World.h" + +namespace vf { + +enum class StructureKind : uint8_t { + OakTree = 0, + PineTree = 1, + Hut = 2, +}; + +struct StructureEdit { + Vec3i offset; // relative to the structure base (first cell above ground) + Block block; +}; + +// Deterministic 32-bit mix of a seed and a coordinate; used to seed per-structure +// variation so the same seed and position always yield the same build. +uint32_t structureHash(uint32_t seed, int x, int y, int z); + +// Builds the block edits for a structure. Pure: identical (kind, seed) always +// produce the identical edit list, in the same order. +std::vector buildStructure(StructureKind kind, uint32_t seed); + +// Writes edits into the world at base + offset. Leaves yield to existing solid +// blocks (they only fill air); trunk, planks, roof and the lantern overwrite. +void stampStructure(World& world, const Vec3i& base, const std::vector& edits, + int maxY); + +} // namespace vf diff --git a/core/src/Structures.cpp b/core/src/Structures.cpp new file mode 100644 index 0000000..3cea3c9 --- /dev/null +++ b/core/src/Structures.cpp @@ -0,0 +1,132 @@ +#include "vf/Structures.h" + +#include + +namespace vf { + +namespace { + +struct Rng { + uint32_t state; + uint32_t next() { + state ^= state << 13; + state ^= state >> 17; + state ^= state << 5; + return state; + } + int range(int lo, int hi) { return lo + static_cast(next() % static_cast(hi - lo + 1)); } + bool chance(int percent) { return static_cast(next() % 100u) < percent; } +}; + +void addTrunk(std::vector& edits, int height) { + for (int y = 0; y < height; ++y) edits.push_back({{0, y, 0}, Block::Wood}); +} + +std::vector buildOak(Rng& rng) { + std::vector edits; + int trunk = rng.range(4, 6); + addTrunk(edits, trunk); + + int canopyBottom = trunk - 2; + for (int dy = 0; dy <= 2; ++dy) { + int radius = dy == 2 ? 1 : 2; + int y = canopyBottom + dy; + for (int dx = -radius; dx <= radius; ++dx) { + for (int dz = -radius; dz <= radius; ++dz) { + if (dx == 0 && dz == 0 && y < trunk) continue; // trunk occupies the core + int manhattan = std::abs(dx) + std::abs(dz); + if (manhattan > radius + (dy == 0 ? 1 : 0)) continue; + if (manhattan == radius + 1 && rng.chance(50)) continue; // ragged edge + edits.push_back({{dx, y, dz}, Block::Leaves}); + } + } + } + edits.push_back({{0, trunk, 0}, Block::Leaves}); // crown + return edits; +} + +std::vector buildPine(Rng& rng) { + std::vector edits; + int trunk = rng.range(6, 9); + addTrunk(edits, trunk); + + for (int y = 2; y <= trunk; ++y) { + int fromTop = trunk - y; + int radius = fromTop >= 3 ? 2 : (fromTop >= 1 ? 1 : 0); + for (int dx = -radius; dx <= radius; ++dx) + for (int dz = -radius; dz <= radius; ++dz) { + if (std::abs(dx) + std::abs(dz) > radius) continue; + if (dx == 0 && dz == 0 && y < trunk) continue; + edits.push_back({{dx, y, dz}, Block::Leaves}); + } + } + edits.push_back({{0, trunk + 1, 0}, Block::Leaves}); // tip + return edits; +} + +std::vector buildHut(Rng& rng) { + std::vector edits; + int height = rng.range(3, 4); + const int half = 2; + + for (int dx = -half; dx <= half; ++dx) + for (int dz = -half; dz <= half; ++dz) + edits.push_back({{dx, 0, dz}, Block::Wood}); // floor + + for (int y = 1; y <= height; ++y) + for (int dx = -half; dx <= half; ++dx) + for (int dz = -half; dz <= half; ++dz) { + bool perimeter = std::abs(dx) == half || std::abs(dz) == half; + if (!perimeter) continue; + bool door = dx == 0 && dz == -half && y <= 2; // doorway on the -z wall + if (door) continue; + edits.push_back({{dx, y, dz}, Block::Wood}); + } + + for (int dx = -half; dx <= half; ++dx) + for (int dz = -half; dz <= half; ++dz) + edits.push_back({{dx, height + 1, dz}, Block::Leaves}); // thatched roof + + edits.push_back({{0, height, 0}, Block::Glowstone}); // lantern (distinct marker) + (void)rng; + return edits; +} + +} // namespace + +uint32_t structureHash(uint32_t seed, int x, int y, int z) { + uint32_t h = seed * 2654435761u; + h ^= static_cast(x) * 0x9e3779b9u; + h = (h << 13) | (h >> 19); + h ^= static_cast(y) * 0x85ebca6bu; + h = (h << 7) | (h >> 25); + h ^= static_cast(z) * 0xc2b2ae35u; + h ^= h >> 16; + h *= 0x7feb352du; + h ^= h >> 15; + return h; +} + +std::vector buildStructure(StructureKind kind, uint32_t seed) { + Rng rng{seed ? seed : 0x9e3779b9u}; + switch (kind) { + case StructureKind::OakTree: return buildOak(rng); + case StructureKind::PineTree: return buildPine(rng); + case StructureKind::Hut: return buildHut(rng); + } + return {}; +} + +void stampStructure(World& world, const Vec3i& base, const std::vector& edits, + int maxY) { + for (const StructureEdit& edit : edits) { + int gx = base.x + edit.offset.x; + int gy = base.y + edit.offset.y; + int gz = base.z + edit.offset.z; + if (gy < 0 || gy >= maxY) continue; + if (edit.block == Block::Leaves && world.blockAt(gx, gy, gz) != Block::Air) continue; + world.setBlock(gx, gy, gz, edit.block); + } +} + +} // namespace vf