From fc6bef7de66c699c9ce535bccc23d630e8d4d4ad Mon Sep 17 00:00:00 2001 From: Cora de la Mouche <73539712+corarona@users.noreply.github.com> Date: Sat, 20 Sep 2025 13:44:19 +0200 Subject: [PATCH] Extend core.generate_decorations to generate biomes respecting the biome map (#16397) Large structures which are generated in on_generated callbacks independently by Lua cannot influence decoration placement. This change enables such a callback to assume responsibility for generating decorations itself, presumably after structures are placed, by disabling decorations in mg_flags and executing core.generate_decorations. --------- Co-authored-by: Po Lu --- builtin/game/features.lua | 1 + doc/lua_api.md | 8 ++++- src/script/lua_api/l_mapgen.cpp | 58 +++++++++++++++++++++++++-------- 3 files changed, 52 insertions(+), 15 deletions(-) diff --git a/builtin/game/features.lua b/builtin/game/features.lua index a75319e70a..db70e9a667 100644 --- a/builtin/game/features.lua +++ b/builtin/game/features.lua @@ -50,6 +50,7 @@ core.features = { object_guids = true, on_timer_four_args = true, particlespawner_exclude_player = true, + generate_decorations_biomes = true, } function core.has_feature(arg) diff --git a/doc/lua_api.md b/doc/lua_api.md index 9b72edb182..9a233829fd 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -5847,6 +5847,8 @@ Utilities on_timer_four_args = true, -- `ParticleSpawner` definition supports `exclude_player` field (5.14.0) particlespawner_exclude_player = true, + -- core.generate_decorations() supports `use_mapgen_biomes` parameter (5.14.0) + generate_decorations_biomes = true, } ``` @@ -6739,10 +6741,14 @@ Environment access * Generate all registered ores within the VoxelManip `vm` and in the area from `pos1` to `pos2`. * `pos1` and `pos2` are optional and default to mapchunk minp and maxp. -* `core.generate_decorations(vm[, pos1, pos2])` +* `core.generate_decorations(vm[, pos1, pos2, [use_mapgen_biomes]])` * Generate all registered decorations within the VoxelManip `vm` and in the area from `pos1` to `pos2`. * `pos1` and `pos2` are optional and default to mapchunk minp and maxp. + * `use_mapgen_biomes` (optional boolean). For use in on_generated callbacks only. + If set to true, decorations are placed in respect to the biome map of the current chunk. + `pos1` and `pos2` must match the positions of the current chunk, or an error will be raised. + default: `false` * `core.clear_objects([options])` * Clear all objects in the environment * Takes an optional table as an argument with the field `mode`. diff --git a/src/script/lua_api/l_mapgen.cpp b/src/script/lua_api/l_mapgen.cpp index b704b3f270..8854fc3c71 100644 --- a/src/script/lua_api/l_mapgen.cpp +++ b/src/script/lua_api/l_mapgen.cpp @@ -1611,7 +1611,7 @@ int ModApiMapgen::l_generate_ores(lua_State *L) } -// generate_decorations(vm, p1, p2) +// generate_decorations(vm, p1, p2, use_mapgen_biomes) int ModApiMapgen::l_generate_decorations(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -1621,26 +1621,56 @@ int ModApiMapgen::l_generate_decorations(lua_State *L) return 0; DecorationManager *decomgr; - if (auto mg = getMapgen(L)) + Mapgen mg, *mgp = nullptr; + bool use_mapgen_biomes = readParam(L, 4, false); + MMVManip *oldvm = nullptr, *vm = checkObject(L, 1)->vm; + if (auto mg = getMapgen(L)) { decomgr = mg->m_emerge->decomgr; - else + if (use_mapgen_biomes) { + mgp = mg; + oldvm = mgp->vm; + if (!oldvm) { + goto no_vm; + } + } + } else { + if (use_mapgen_biomes) { + no_vm: + throw LuaError("use_mapgen_biomes specified outside a " + "map generation context"); + } decomgr = emerge->decomgr; + } + if (!mgp) { + mgp = &mg; + // Intentionally truncates to s32, see Mapgen::Mapgen() + mg.seed = (s32)emerge->mgparams->seed; + mg.ndef = emerge->ndef; + } - Mapgen mg; - // Intentionally truncates to s32, see Mapgen::Mapgen() - mg.seed = (s32)emerge->mgparams->seed; - mg.vm = checkObject(L, 1)->vm; - mg.ndef = emerge->ndef; - - v3s16 pmin = lua_istable(L, 2) ? check_v3s16(L, 2) : - mg.vm->m_area.MinEdge + v3s16(1,1,1) * MAP_BLOCKSIZE; - v3s16 pmax = lua_istable(L, 3) ? check_v3s16(L, 3) : - mg.vm->m_area.MaxEdge - v3s16(1,1,1) * MAP_BLOCKSIZE; + const v3s16 default_pmin = vm->m_area.MinEdge + MAP_BLOCKSIZE, + default_pmax = vm->m_area.MaxEdge - MAP_BLOCKSIZE; + v3s16 pmin = lua_istable(L, 2) ? check_v3s16(L, 2) : default_pmin; + v3s16 pmax = lua_istable(L, 3) ? check_v3s16(L, 3) : default_pmax; sortBoxVerticies(pmin, pmax); + if (use_mapgen_biomes) { + assert(oldvm); + const v3s16 required_pmin = oldvm->m_area.MinEdge + MAP_BLOCKSIZE, + required_pmax = oldvm->m_area.MaxEdge - MAP_BLOCKSIZE; + if (pmin != required_pmin || pmax != required_pmax) + throw LuaError("use_mapgen_biomes requires extents matching chunk area"); + } u32 blockseed = Mapgen::getBlockSeed(pmin, mg.seed); - decomgr->placeAllDecos(&mg, blockseed, pmin, pmax); + mgp->vm = vm; + try { + decomgr->placeAllDecos(mgp, blockseed, pmin, pmax); + } catch (...) { + mgp->vm = oldvm; + throw; + } + mgp->vm = oldvm; return 0; }