diff --git a/res/generators/default.lua b/res/generators/default.lua index 991a54f8..6ecf5f48 100644 --- a/res/generators/default.lua +++ b/res/generators/default.lua @@ -1,3 +1,10 @@ +layers = { + {block="base:grass_block", height=1}, + {block="base:dirt", height=1}, + {block="base:stone", height=-1}, + {block="base:bazalt", height=1}, +} + function generate_heightmap(x, y, w, h) local umap = Heightmap(w, h) local vmap = Heightmap(w, h) diff --git a/src/content/ContentBuilder.cpp b/src/content/ContentBuilder.cpp index b637532e..46d4f909 100644 --- a/src/content/ContentBuilder.cpp +++ b/src/content/ContentBuilder.cpp @@ -88,5 +88,9 @@ std::unique_ptr ContentBuilder::build() { def->rt.placingBlock = content->blocks.require(def->placingBlock).rt.id; } + for (auto& [name, def] : content->generators.getDefs()) { + def->script->prepare(content.get()); + } + return content; } diff --git a/src/content/ContentLoader.cpp b/src/content/ContentLoader.cpp index fef55cdb..c08e1c43 100644 --- a/src/content/ContentLoader.cpp +++ b/src/content/ContentLoader.cpp @@ -473,7 +473,11 @@ void ContentLoader::load() { auto& def = builder.generators.create(full); - loadGenerator(def, full, name); + try { + loadGenerator(def, full, name); + } catch (const std::runtime_error& err) { + throw std::runtime_error("generator '"+full+"': "+err.what()); + } } } diff --git a/src/logic/scripting/lua/lua_util.hpp b/src/logic/scripting/lua/lua_util.hpp index ce77c429..f9fadecd 100644 --- a/src/logic/scripting/lua/lua_util.hpp +++ b/src/logic/scripting/lua/lua_util.hpp @@ -496,17 +496,26 @@ namespace lua { } int pushvalue(lua::State*, const dynamic::Value& value); + + [[nodiscard]] dynamic::Value tovalue(lua::State*, int idx); inline bool getfield(lua::State* L, const std::string& name, int idx = -1) { lua_getfield(L, idx, name.c_str()); - if (isnil(L, -1)) { + if (isnil(L, idx)) { pop(L); return false; } return true; } + inline int requirefield(lua::State* L, const std::string& name, int idx = -1) { + if (getfield(L, name, idx)) { + return 1; + } + throw std::runtime_error("object has no member '"+name+"'"); + } + inline bool hasfield(lua::State* L, const std::string& name, int idx = -1) { lua_getfield(L, idx, name.c_str()); if (isnil(L, -1)) { diff --git a/src/logic/scripting/scripting.cpp b/src/logic/scripting/scripting.cpp index b50880bd..51345fbb 100644 --- a/src/logic/scripting/scripting.cpp +++ b/src/logic/scripting/scripting.cpp @@ -686,8 +686,11 @@ void scripting::load_entity_component( class LuaGeneratorScript : public GeneratorScript { scriptenv env; + std::vector layers; + uint lastLayersHeight; public: - LuaGeneratorScript(scriptenv env) : env(std::move(env)) {} + LuaGeneratorScript(scriptenv env, std::vector layers, uint lastLayersHeight) + : env(std::move(env)), layers(std::move(layers)), lastLayersHeight(lastLayersHeight) {} std::shared_ptr generateHeightmap( const glm::ivec2& offset, const glm::ivec2& size @@ -706,6 +709,20 @@ public: lua::pop(L); return std::make_shared(size.x, size.y); } + + void prepare(const Content* content) override { + for (auto& layer : layers) { + layer.rt.id = content->blocks.require(layer.block).rt.id; + } + } + + const std::vector& getLayers() const override { + return layers; + } + + uint getLastLayersHeight() const override { + return lastLayersHeight; + } }; std::unique_ptr scripting::load_generator( @@ -713,7 +730,46 @@ std::unique_ptr scripting::load_generator( ) { auto env = create_environment(); load_script(*env, "generator", file); - return std::make_unique(std::move(env)); + + auto L = lua::get_main_thread(); + lua::pushenv(L, *env); + + uint lastLayersHeight = 0; + bool hasResizeableLayer = false; + + std::vector layers; + if (lua::getfield(L, "layers")) { + int len = lua::objlen(L, -1); + for (int i = 1; i <= len; i++) { + try { + lua::rawgeti(L, i); + lua::requirefield(L, "block"); + auto name = lua::require_string(L, -1); + lua::pop(L); + lua::requirefield(L, "height"); + int height = lua::tointeger(L, -1); + lua::pop(L, 2); + + if (hasResizeableLayer) { + lastLayersHeight += height; + } + if (height == -1) { + if (hasResizeableLayer) { + throw std::runtime_error("only one resizeable layer allowed"); + } + hasResizeableLayer = true; + } + layers.push_back(BlocksLayer {name, height, {}}); + } catch (const std::runtime_error& err) { + lua::pop(L, 2); + throw std::runtime_error( + "layer #"+std::to_string(i)+": "+err.what()); + } + } + lua::pop(L); + } + lua::pop(L); + return std::make_unique(std::move(env), std::move(layers), lastLayersHeight); } void scripting::load_world_script( diff --git a/src/world/generator/GeneratorDef.hpp b/src/world/generator/GeneratorDef.hpp index 608d0749..5bfd6717 100644 --- a/src/world/generator/GeneratorDef.hpp +++ b/src/world/generator/GeneratorDef.hpp @@ -6,12 +6,30 @@ #include "typedefs.hpp" #include "maths/Heightmap.hpp" +class Content; + +struct BlocksLayer { + std::string block; + int height; + + struct { + blockid_t id; + } rt; +}; + class GeneratorScript { public: virtual ~GeneratorScript() = default; virtual std::shared_ptr generateHeightmap( const glm::ivec2& offset, const glm::ivec2& size) = 0; + + virtual const std::vector& getLayers() const = 0; + + /// @brief Total height of all layers after resizeable one + virtual uint getLastLayersHeight() const = 0; + + virtual void prepare(const Content* content) = 0; }; struct GeneratorDef { diff --git a/src/world/generator/WorldGenerator.cpp b/src/world/generator/WorldGenerator.cpp index 41454fff..d1b3884f 100644 --- a/src/world/generator/WorldGenerator.cpp +++ b/src/world/generator/WorldGenerator.cpp @@ -1,5 +1,7 @@ #include "WorldGenerator.hpp" +#include + #include "content/Content.hpp" #include "voxels/Block.hpp" #include "voxels/Chunk.hpp" @@ -14,21 +16,32 @@ WorldGenerator::WorldGenerator( #include "util/timeutil.hpp" void WorldGenerator::generate(voxel* voxels, int chunkX, int chunkZ, int seed) { - timeutil::ScopeLogTimer log(555); auto heightmap = def.script->generateHeightmap( {chunkX*CHUNK_W, chunkZ*CHUNK_D}, {CHUNK_W, CHUNK_D} ); - auto& baseStone = content->blocks.require("base:stone"); - auto& baseWater = content->blocks.require("base:water"); + timeutil::ScopeLogTimer log(555); + auto values = heightmap->getValues(); + const auto& layers = def.script->getLayers(); + uint lastLayersHeight = def.script->getLastLayersHeight(); + auto baseWater = content->blocks.require("base:water").rt.id; + + std::memset(voxels, 0, sizeof(voxel) * CHUNK_VOL); + for (uint z = 0; z < CHUNK_D; z++) { for (uint x = 0; x < CHUNK_W; x++) { - auto height = heightmap->getValues()[z * CHUNK_W + x] * 255 + 10; - for (uint y = 0; y < CHUNK_H; y++) { - voxels[vox_index(x, y, z)].state = {}; - if (y > height) { - voxels[vox_index(x, y, z)].id = y <= 64 ? baseWater.rt.id : 0; - } else { - voxels[vox_index(x, y, z)].id = baseStone.rt.id; + int height = values[z * CHUNK_W + x] * 255 + 10; + for (uint y = height+1; y < 64; y++) { + voxels[vox_index(x, y, z)].id = baseWater; + } + + uint y = height; + for (const auto& layer : layers) { + uint layerHeight = layer.height; + if (layerHeight == -1) { + layerHeight = y - lastLayersHeight + 1; + } + for (uint i = 0; i < layerHeight; i++, y--) { + voxels[vox_index(x, y, z)].id = layer.rt.id; } } }