From 3cac17d23e2e0bfe74f1f627a4e206fec981fa4a Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 13 Feb 2024 22:47:30 +0100 Subject: [PATCH] Lua on each mapgen thread (#13092) --- .luacheckrc | 1 + builtin/emerge/env.lua | 61 +++++ builtin/emerge/init.lua | 21 ++ builtin/emerge/register.lua | 54 ++++ builtin/game/misc.lua | 7 +- builtin/init.lua | 8 +- doc/lua_api.md | 134 ++++++++-- .../mods/unittests/inside_mapgen_env.lua | 32 +++ games/devtest/mods/unittests/misc.lua | 30 +++ src/emerge.cpp | 150 +++++------ src/emerge.h | 7 + src/emerge_internal.h | 115 ++++++++ src/mapgen/mapgen.cpp | 58 ++++- src/mapgen/mapgen.h | 30 ++- src/mapgen/mg_decoration.cpp | 8 +- src/script/CMakeLists.txt | 1 + src/script/cpp_api/CMakeLists.txt | 1 + src/script/cpp_api/s_base.h | 9 +- src/script/cpp_api/s_mapgen.cpp | 76 ++++++ src/script/cpp_api/s_mapgen.h | 40 +++ src/script/lua_api/l_base.cpp | 5 + src/script/lua_api/l_base.h | 3 +- src/script/lua_api/l_env.cpp | 190 +++++++++++++- src/script/lua_api/l_env.h | 38 +++ src/script/lua_api/l_mapgen.cpp | 246 ++++++++++++++---- src/script/lua_api/l_mapgen.h | 21 +- src/script/lua_api/l_server.cpp | 21 ++ src/script/lua_api/l_server.h | 3 + src/script/lua_api/l_vmanip.cpp | 20 +- src/script/scripting_emerge.cpp | 93 +++++++ src/script/scripting_emerge.h | 37 +++ src/server.h | 2 + 32 files changed, 1329 insertions(+), 193 deletions(-) create mode 100644 builtin/emerge/env.lua create mode 100644 builtin/emerge/init.lua create mode 100644 builtin/emerge/register.lua create mode 100644 games/devtest/mods/unittests/inside_mapgen_env.lua create mode 100644 src/emerge_internal.h create mode 100644 src/script/cpp_api/s_mapgen.cpp create mode 100644 src/script/cpp_api/s_mapgen.h create mode 100644 src/script/scripting_emerge.cpp create mode 100644 src/script/scripting_emerge.h diff --git a/.luacheckrc b/.luacheckrc index 54ece656a..9dfa76d01 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -17,6 +17,7 @@ read_globals = { "VoxelArea", "profiler", "Settings", + "PerlinNoise", "PerlinNoiseMap", string = {fields = {"split", "trim"}}, table = {fields = {"copy", "getn", "indexof", "insert_all"}}, diff --git a/builtin/emerge/env.lua b/builtin/emerge/env.lua new file mode 100644 index 000000000..43848082a --- /dev/null +++ b/builtin/emerge/env.lua @@ -0,0 +1,61 @@ +-- Reimplementations of some environment function on vmanips, since this is +-- what the emerge environment operates on + +-- core.vmanip = -- set by C++ + +function core.set_node(pos, node) + return core.vmanip:set_node_at(pos, node) +end + +function core.bulk_set_node(pos_list, node) + local vm = core.vmanip + local set_node_at = vm.set_node_at + for _, pos in ipairs(pos_list) do + if not set_node_at(vm, pos, node) then + return false + end + end + return true +end + +core.add_node = core.set_node + +-- we don't deal with metadata currently +core.swap_node = core.set_node + +function core.remove_node(pos) + return core.vmanip:set_node_at(pos, {name="air"}) +end + +function core.get_node(pos) + return core.vmanip:get_node_at(pos) +end + +function core.get_node_or_nil(pos) + local node = core.vmanip:get_node_at(pos) + return node.name ~= "ignore" and node +end + +function core.get_perlin(seed, octaves, persist, spread) + local params + if type(seed) == "table" then + params = table.copy(seed) + else + assert(type(seed) == "number") + params = { + seed = seed, + octaves = octaves, + persist = persist, + spread = {x=spread, y=spread, z=spread}, + } + end + params.seed = core.get_seed(params.seed) -- add mapgen seed + return PerlinNoise(params) +end + + +function core.get_perlin_map(params, size) + local params2 = table.copy(params) + params2.seed = core.get_seed(params.seed) -- add mapgen seed + return PerlinNoiseMap(params2, size) +end diff --git a/builtin/emerge/init.lua b/builtin/emerge/init.lua new file mode 100644 index 000000000..cb7d9266a --- /dev/null +++ b/builtin/emerge/init.lua @@ -0,0 +1,21 @@ +local gamepath = core.get_builtin_path() .. "game" .. DIR_DELIM +local commonpath = core.get_builtin_path() .. "common" .. DIR_DELIM +local epath = core.get_builtin_path() .. "emerge" .. DIR_DELIM + +local builtin_shared = {} + +-- Import parts shared with "game" environment +dofile(gamepath .. "constants.lua") +assert(loadfile(commonpath .. "item_s.lua"))(builtin_shared) +dofile(gamepath .. "misc_s.lua") +dofile(gamepath .. "features.lua") +dofile(gamepath .. "voxelarea.lua") + +-- Now for our own stuff +assert(loadfile(commonpath .. "register.lua"))(builtin_shared) +assert(loadfile(epath .. "register.lua"))(builtin_shared) +dofile(epath .. "env.lua") + +builtin_shared.cache_content_ids() + +core.log("info", "Initialized emerge Lua environment") diff --git a/builtin/emerge/register.lua b/builtin/emerge/register.lua new file mode 100644 index 000000000..308fe4d7e --- /dev/null +++ b/builtin/emerge/register.lua @@ -0,0 +1,54 @@ +local builtin_shared = ... + +-- Copy all the registration tables over +do + local all = assert(core.transferred_globals) + core.transferred_globals = nil + + all.registered_nodes = {} + all.registered_craftitems = {} + all.registered_tools = {} + for k, v in pairs(all.registered_items) do + -- Disable further modification + setmetatable(v, {__newindex = {}}) + -- Reassemble the other tables + if v.type == "node" then + getmetatable(v).__index = all.nodedef_default + all.registered_nodes[k] = v + elseif v.type == "craft" then + getmetatable(v).__index = all.craftitemdef_default + all.registered_craftitems[k] = v + elseif v.type == "tool" then + getmetatable(v).__index = all.tooldef_default + all.registered_tools[k] = v + else + getmetatable(v).__index = all.noneitemdef_default + end + end + + for k, v in pairs(all) do + core[k] = v + end +end + +-- For tables that are indexed by item name: +-- If table[X] does not exist, default to table[core.registered_aliases[X]] +local alias_metatable = { + __index = function(t, name) + return rawget(t, core.registered_aliases[name]) + end +} +setmetatable(core.registered_items, alias_metatable) +setmetatable(core.registered_nodes, alias_metatable) +setmetatable(core.registered_craftitems, alias_metatable) +setmetatable(core.registered_tools, alias_metatable) + +-- +-- Callbacks +-- + +local make_registration = builtin_shared.make_registration + +core.registered_on_mods_loaded, core.register_on_mods_loaded = make_registration() +core.registered_on_generateds, core.register_on_generated = make_registration() +core.registered_on_shutdown, core.register_on_shutdown = make_registration() diff --git a/builtin/game/misc.lua b/builtin/game/misc.lua index a30c42fa0..dc394a00c 100644 --- a/builtin/game/misc.lua +++ b/builtin/game/misc.lua @@ -237,8 +237,8 @@ end core.dynamic_media_callbacks = {} --- Transfer of certain globals into async environment --- see builtin/async/game.lua for the other side +-- Transfer of certain globals into seconday Lua environments +-- see builtin/async/game.lua or builtin/emerge/register.lua for the unpacking local function copy_filtering(t, seen) if type(t) == "userdata" or type(t) == "function" then @@ -261,6 +261,9 @@ function core.get_globals_to_transfer() local all = { registered_items = copy_filtering(core.registered_items), registered_aliases = core.registered_aliases, + registered_biomes = core.registered_biomes, + registered_ores = core.registered_ores, + registered_decorations = core.registered_decorations, nodedef_default = copy_filtering(core.nodedef_default), craftitemdef_default = copy_filtering(core.craftitemdef_default), diff --git a/builtin/init.lua b/builtin/init.lua index e03c2c6de..b62dbf07a 100644 --- a/builtin/init.lua +++ b/builtin/init.lua @@ -31,8 +31,6 @@ minetest = core -- Load other files local scriptdir = core.get_builtin_path() -local gamepath = scriptdir .. "game" .. DIR_DELIM -local clientpath = scriptdir .. "client" .. DIR_DELIM local commonpath = scriptdir .. "common" .. DIR_DELIM local asyncpath = scriptdir .. "async" .. DIR_DELIM @@ -42,7 +40,7 @@ dofile(commonpath .. "serialize.lua") dofile(commonpath .. "misc_helpers.lua") if INIT == "game" then - dofile(gamepath .. "init.lua") + dofile(scriptdir .. "game" .. DIR_DELIM .. "init.lua") assert(not core.get_http_api) elseif INIT == "mainmenu" then local mm_script = core.settings:get("main_menu_script") @@ -67,7 +65,9 @@ elseif INIT == "async" then elseif INIT == "async_game" then dofile(asyncpath .. "game.lua") elseif INIT == "client" then - dofile(clientpath .. "init.lua") + dofile(scriptdir .. "client" .. DIR_DELIM .. "init.lua") +elseif INIT == "emerge" then + dofile(scriptdir .. "emerge" .. DIR_DELIM .. "init.lua") else error(("Unrecognized builtin initialization type %s!"):format(tostring(INIT))) end diff --git a/doc/lua_api.md b/doc/lua_api.md index 589967a68..432c720b9 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -4679,6 +4679,7 @@ differences: into it; it's not necessary to call `VoxelManip:read_from_map()`. Note that the region of map it has loaded is NOT THE SAME as the `minp`, `maxp` parameters of `on_generated()`. Refer to `minetest.get_mapgen_object` docs. + Once you're done you still need to call `VoxelManip:write_to_map()` * The `on_generated()` callbacks of some mods may place individual nodes in the generated area using non-VoxelManip map modification methods. Because the @@ -4875,10 +4876,10 @@ Mapgen objects ============== A mapgen object is a construct used in map generation. Mapgen objects can be -used by an `on_generate` callback to speed up operations by avoiding +used by an `on_generated` callback to speed up operations by avoiding unnecessary recalculations, these can be retrieved using the `minetest.get_mapgen_object()` function. If the requested Mapgen object is -unavailable, or `get_mapgen_object()` was called outside of an `on_generate()` +unavailable, or `get_mapgen_object()` was called outside of an `on_generated` callback, `nil` is returned. The following Mapgen objects are currently available: @@ -4910,12 +4911,14 @@ generated chunk by the current mapgen. ### `gennotify` -Returns a table mapping requested generation notification types to arrays of -positions at which the corresponding generated structures are located within -the current chunk. To enable the capture of positions of interest to be recorded -call `minetest.set_gen_notify()` first. +Returns a table. You need to announce your interest in a specific +field by calling `minetest.set_gen_notify()` *before* map generation happens. -Possible fields of the returned table are: +* key = string: generation notification type +* value = list of positions (usually) + * Exceptions are denoted in the listing below. + +Available generation notification types: * `dungeon`: bottom center position of dungeon rooms * `temple`: as above but for desert temples (mgv6 only) @@ -4923,7 +4926,12 @@ Possible fields of the returned table are: * `cave_end` * `large_cave_begin` * `large_cave_end` -* `decoration#id` (see below) +* `custom`: data originating from [Mapgen environment] (Lua API) + * This is a table. + * key = user-defined ID (string) + * value = arbitrary Lua value +* `decoration#id`: decorations + * (see below) Decorations have a key in the format of `"decoration#id"`, where `id` is the numeric unique decoration ID as returned by `minetest.get_decoration_id()`. @@ -5587,8 +5595,10 @@ Call these functions only at load time! * `minetest.register_on_punchnode(function(pos, node, puncher, pointed_thing))` * Called when a node is punched * `minetest.register_on_generated(function(minp, maxp, blockseed))` - * Called after generating a piece of world. Modifying nodes inside the area - is a bit faster than usual. + * Called after generating a piece of world between `minp` and `maxp`. + * **Avoid using this** whenever possible. As with other callbacks this blocks + the main thread and introduces noticable latency. + Consider [Mapgen environment] for an alternative. * `minetest.register_on_newplayer(function(ObjectRef))` * Called when a new player enters the world for the first time * `minetest.register_on_punchplayer(function(player, hitter, time_from_last_punch, tool_capabilities, dir, damage))` @@ -6004,20 +6014,18 @@ Environment access * `minetest.get_voxel_manip([pos1, pos2])` * Return voxel manipulator object. * Loads the manipulator from the map if positions are passed. -* `minetest.set_gen_notify(flags, {deco_ids})` +* `minetest.set_gen_notify(flags, [deco_ids], [custom_ids])` * Set the types of on-generate notifications that should be collected. - * `flags` is a flag field with the available flags: - * dungeon - * temple - * cave_begin - * cave_end - * large_cave_begin - * large_cave_end - * decoration - * The second parameter is a list of IDs of decorations which notification + * `flags`: flag field, see [`gennotify`] for available generation notification types. + * The following parameters are optional: + * `deco_ids` is a list of IDs of decorations which notification is requested for. + * `custom_ids` is a list of user-defined IDs (strings) which are + requested. By convention these should be the mod name with an optional + colon and specifier added, e.g. `"default"` or `"default:dungeon_loot"` * `minetest.get_gen_notify()` - * Returns a flagstring and a table with the `deco_id`s. + * Returns a flagstring, a table with the `deco_id`s and a table with + user-defined IDs. * `minetest.get_decoration_id(decoration_name)` * Returns the decoration ID number for the provided decoration name string, or `nil` on failure. @@ -6573,6 +6581,86 @@ Variables: * with all functions and userdata values replaced by `true`, calling any callbacks here is obviously not possible +Mapgen environment +------------------ + +The engine runs the map generator on separate threads, each of these also has +a Lua environment. Its primary purpose is to allow mods to operate on newly +generated parts of the map to e.g. generate custom structures. +Internally it is referred to as "emerge environment". + +Refer to [Async environment] for the usual disclaimer on what environment isolation entails. + +The map generator threads, which also contain the above mentioned Lua environment, +are initialized after all mods have been loaded by the server. After that the +registered scripts (not all mods!) - see below - are run during initialization of +the mapgen environment. After that only callbacks happen. The mapgen env +does not have a global step or timer. + +* `minetest.register_mapgen_script(path)`: + * Register a path to a Lua file to be imported when a mapgen environment + is initialized. Run in order of registration. + +### List of APIs exclusive to the mapgen env + +* `minetest.register_on_generated(function(vmanip, minp, maxp, blockseed))` + * Called after the engine mapgen finishes a chunk but before it is written to + the map. + * Chunk data resides in `vmanip`. Other parts of the map are not accessible. + The area of the chunk if comprised of `minp` and `maxp`, note that is smaller + than the emerged area of the VoxelManip. + Note: calling `read_from_map()` or `write_to_map()` on the VoxelManipulator object + is not necessary and is disallowed. + * `blockseed`: 64-bit seed number used for this chunk +* `minetest.save_gen_notify(id, data)` + * Saves data for retrieval using the gennotify mechanism (see [Mapgen objects]). + * Data is bound to the chunk that is currently being processed, so this function + only makes sense inside the `on_generated` callback. + * `id`: user-defined ID (a string) + By convention these should be the mod name with an optional + colon and specifier added, e.g. `"default"` or `"default:dungeon_loot"` + * `data`: any Lua object (will be serialized, no userdata allowed) + * returns `true` if the data was remembered. That is if `minetest.set_gen_notify` + was called with the same user-defined ID before. + +### List of APIs available in the mapgen env + +Classes: +* `AreaStore` +* `ItemStack` +* `PerlinNoise` +* `PerlinNoiseMap` +* `PseudoRandom` +* `PcgRandom` +* `SecureRandom` +* `VoxelArea` +* `VoxelManip` + * only given by callbacks; cannot access rest of map +* `Settings` + +Functions: +* Standalone helpers such as logging, filesystem, encoding, + hashing or compression APIs +* `minetest.request_insecure_environment` (same restrictions apply) +* `minetest.get_biome_id`, `get_biome_name`, `get_heat`, `get_humidity`, + `get_biome_data`, `get_mapgen_object`, `get_mapgen_params`, `get_mapgen_edges`, + `get_mapgen_setting`, `get_noiseparams`, `get_decoration_id` and more +* `minetest.get_node`, `set_node`, `find_node_near`, `find_nodes_in_area`, + `spawn_tree` and similar + * these only operate on the current chunk (if inside a callback) + +Variables: +* `minetest.settings` +* `minetest.registered_items`, `registered_nodes`, `registered_tools`, + `registered_craftitems` and `registered_aliases` + * with all functions and userdata values replaced by `true`, calling any + callbacks here is obviously not possible +* `minetest.registered_biomes`, `registered_ores`, `registered_decorations` + +Note that node metadata does not exist in the mapgen env, we suggest deferring +setting any metadata you need to the `on_generated` callback in the regular env. +You can use the gennotify mechanism to transfer this information. + Server ------ @@ -7081,10 +7169,6 @@ Global tables * Map of registered decoration definitions, indexed by the `name` field. * If `name` is nil, the key is the object handle returned by `minetest.register_decoration`. -* `minetest.registered_schematics` - * Map of registered schematic definitions, indexed by the `name` field. - * If `name` is nil, the key is the object handle returned by - `minetest.register_schematic`. * `minetest.registered_chatcommands` * Map of registered chat command definitions, indexed by name * `minetest.registered_privileges` diff --git a/games/devtest/mods/unittests/inside_mapgen_env.lua b/games/devtest/mods/unittests/inside_mapgen_env.lua new file mode 100644 index 000000000..a8df004de --- /dev/null +++ b/games/devtest/mods/unittests/inside_mapgen_env.lua @@ -0,0 +1,32 @@ +core.log("info", "Hello World") + +local function do_tests() + assert(core == minetest) + -- stuff that should not be here + assert(not core.get_player_by_name) + assert(not core.object_refs) + -- stuff that should be here + assert(core.register_on_generated) + assert(core.get_node) + assert(core.spawn_tree) + assert(ItemStack) + local meta = ItemStack():get_meta() + assert(type(meta) == "userdata") + assert(type(meta.set_tool_capabilities) == "function") + assert(core.registered_items[""]) + assert(core.save_gen_notify) + -- alias handling + assert(core.registered_items["unittests:steel_ingot_alias"].name == + "unittests:steel_ingot") + -- fallback to item defaults + assert(core.registered_items["unittests:description_test"].on_place == true) +end + +-- there's no (usable) communcation path between mapgen and the regular env +-- so we just run the test unconditionally +do_tests() + +core.register_on_generated(function(vm, pos1, pos2, blockseed) + local n = tonumber(core.get_mapgen_setting("chunksize")) * 16 - 1 + assert(pos2:subtract(pos1) == vector.new(n, n, n)) +end) diff --git a/games/devtest/mods/unittests/misc.lua b/games/devtest/mods/unittests/misc.lua index 6181d7617..30d0804a0 100644 --- a/games/devtest/mods/unittests/misc.lua +++ b/games/devtest/mods/unittests/misc.lua @@ -1,3 +1,6 @@ +core.register_mapgen_script(core.get_modpath(core.get_current_modname()) .. + DIR_DELIM .. "inside_mapgen_env.lua") + local function test_pseudo_random() -- We have comprehensive unit tests in C++, this is just to make sure the API code isn't messing up local gen1 = PseudoRandom(13) @@ -204,3 +207,30 @@ local function test_on_mapblocks_changed(cb, player, pos) end end unittests.register("test_on_mapblocks_changed", test_on_mapblocks_changed, {map=true, async=true}) + +local function test_gennotify_api() + local DECO_ID = 123 + local UD_ID = "unittests:dummy" + + -- the engine doesn't check if the id is actually valid, maybe it should + core.set_gen_notify({decoration=true}, {DECO_ID}) + + core.set_gen_notify({custom=true}, nil, {UD_ID}) + + local flags, deco, custom = core.get_gen_notify() + local function ff(flag) + return (" " .. flags .. " "):match("[ ,]" .. flag .. "[ ,]") ~= nil + end + assert(ff("decoration"), "'decoration' flag missing") + assert(ff("custom"), "'custom' flag missing") + assert(table.indexof(deco, DECO_ID) > 0) + assert(table.indexof(custom, UD_ID) > 0) + + core.set_gen_notify({decoration=false, custom=false}) + + flags, deco, custom = core.get_gen_notify() + assert(not ff("decoration") and not ff("custom")) + assert(#deco == 0, "deco ids not empty") + assert(#custom == 0, "custom ids not empty") +end +unittests.register("test_gennotify_api", test_gennotify_api) diff --git a/src/emerge.cpp b/src/emerge.cpp index 5d096c5b0..bef1e34ac 100644 --- a/src/emerge.cpp +++ b/src/emerge.cpp @@ -19,19 +19,16 @@ with this program; if not, write to the Free Software Foundation, Inc., */ -#include "emerge.h" +#include "emerge_internal.h" #include -#include #include "util/container.h" -#include "util/thread.h" -#include "threading/event.h" - #include "config.h" #include "constants.h" #include "environment.h" #include "irrlicht_changes/printing.h" +#include "filesys.h" #include "log.h" #include "map.h" #include "mapblock.h" @@ -42,76 +39,11 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "nodedef.h" #include "profiler.h" #include "scripting_server.h" +#include "scripting_emerge.h" #include "server.h" #include "settings.h" #include "voxel.h" -class EmergeThread : public Thread { -public: - bool enable_mapgen_debug_info; - int id; - - EmergeThread(Server *server, int ethreadid); - ~EmergeThread() = default; - - void *run(); - void signal(); - - // Requires queue mutex held - bool pushBlock(const v3s16 &pos); - - void cancelPendingItems(); - -protected: - - void runCompletionCallbacks( - const v3s16 &pos, EmergeAction action, - const EmergeCallbackList &callbacks); - -private: - Server *m_server; - ServerMap *m_map; - EmergeManager *m_emerge; - Mapgen *m_mapgen; - - Event m_queue_event; - std::queue m_block_queue; - - bool popBlockEmerge(v3s16 *pos, BlockEmergeData *bedata); - - EmergeAction getBlockOrStartGen( - const v3s16 &pos, bool allow_gen, MapBlock **block, BlockMakeData *data); - MapBlock *finishGen(v3s16 pos, BlockMakeData *bmdata, - std::map *modified_blocks); - - friend class EmergeManager; -}; - -class MapEditEventAreaIgnorer -{ -public: - MapEditEventAreaIgnorer(VoxelArea *ignorevariable, const VoxelArea &a): - m_ignorevariable(ignorevariable) - { - if(m_ignorevariable->getVolume() == 0) - *m_ignorevariable = a; - else - m_ignorevariable = NULL; - } - - ~MapEditEventAreaIgnorer() - { - if(m_ignorevariable) - { - assert(m_ignorevariable->getVolume() != 0); - *m_ignorevariable = VoxelArea(); - } - } - -private: - VoxelArea *m_ignorevariable; -}; - EmergeParams::~EmergeParams() { infostream << "EmergeParams: destroying " << this << std::endl; @@ -131,6 +63,7 @@ EmergeParams::EmergeParams(EmergeManager *parent, const BiomeGen *biomegen, enable_mapgen_debug_info(parent->enable_mapgen_debug_info), gen_notify_on(parent->gen_notify_on), gen_notify_on_deco_ids(&parent->gen_notify_on_deco_ids), + gen_notify_on_custom(&parent->gen_notify_on_custom), biomemgr(biomemgr->clone()), oremgr(oremgr->clone()), decomgr(decomgr->clone()), schemmgr(schemmgr->clone()) { @@ -518,9 +451,10 @@ EmergeThread::EmergeThread(Server *server, int ethreadid) : enable_mapgen_debug_info(false), id(ethreadid), m_server(server), - m_map(NULL), - m_emerge(NULL), - m_mapgen(NULL) + m_map(nullptr), + m_emerge(nullptr), + m_mapgen(nullptr), + m_trans_liquid(nullptr) { m_name = "Emerge-" + itos(ethreadid); } @@ -641,13 +575,13 @@ MapBlock *EmergeThread::finishGen(v3s16 pos, BlockMakeData *bmdata, v3s16(1,1,1) * (MAP_BLOCKSIZE - 1); // Ignore map edit events, they will not need to be sent - // to anybody because the block hasn't been sent to anybody + // to anyone because the block hasn't been sent yet. MapEditEventAreaIgnorer ign( &m_server->m_ignore_map_edit_events_area, VoxelArea(minp, maxp)); /* - Run Lua on_generated callbacks + Run Lua on_generated callbacks in the server environment */ try { m_server->getScriptIface()->environment_OnGenerated( @@ -674,6 +608,36 @@ MapBlock *EmergeThread::finishGen(v3s16 pos, BlockMakeData *bmdata, } +bool EmergeThread::initScripting() +{ + m_script = std::make_unique(this); + + try { + m_script->loadMod(Server::getBuiltinLuaPath() + DIR_DELIM + "init.lua", + BUILTIN_MOD_NAME); + m_script->checkSetByBuiltin(); + } catch (const ModError &e) { + errorstream << "Execution of mapgen base environment failed." << std::endl; + m_server->setAsyncFatalError(e.what()); + return false; + } + + const auto &list = m_server->m_mapgen_init_files; + try { + for (auto &it : list) + m_script->loadMod(it.second, it.first); + + m_script->on_mods_loaded(); + } catch (const ModError &e) { + errorstream << "Failed to load mod script inside mapgen environment." << std::endl; + m_server->setAsyncFatalError(e.what()); + return false; + } + + return true; +} + + void *EmergeThread::run() { BEGIN_DEBUG_EXCEPTION_HANDLER @@ -686,6 +650,11 @@ void *EmergeThread::run() m_mapgen = m_emerge->m_mapgens[id]; enable_mapgen_debug_info = m_emerge->enable_mapgen_debug_info; + if (!initScripting()) { + m_script.reset(); + stop(); // do not enter main loop + } + try { while (!stopRequested()) { BlockEmergeData bedata; @@ -706,6 +675,9 @@ void *EmergeThread::run() action = getBlockOrStartGen(pos, allow_gen, &block, &bmdata); if (action == EMERGE_GENERATED) { + bool error = false; + m_trans_liquid = &bmdata.transforming_liquid; + { ScopeProfiler sp(g_profiler, "EmergeThread: Mapgen::makeChunk", SPT_AVG); @@ -713,9 +685,24 @@ void *EmergeThread::run() m_mapgen->makeChunk(&bmdata); } - block = finishGen(pos, &bmdata, &modified_blocks); - if (!block) + { + ScopeProfiler sp(g_profiler, + "EmergeThread: Lua on_generated", SPT_AVG); + + try { + m_script->on_generated(&bmdata); + } catch (const LuaError &e) { + m_server->setAsyncFatalError(e); + error = true; + } + } + + if (!error) + block = finishGen(pos, &bmdata, &modified_blocks); + if (!block || error) action = EMERGE_ERRORED; + + m_trans_liquid = nullptr; } runCompletionCallbacks(pos, action, bedata.callbacks); @@ -752,6 +739,13 @@ void *EmergeThread::run() m_server->setAsyncFatalError(err.str()); } + try { + if (m_script) + m_script->on_shutdown(); + } catch (const ModError &e) { + m_server->setAsyncFatalError(e.what()); + } + cancelPendingItems(); END_DEBUG_EXCEPTION_HANDLER diff --git a/src/emerge.h b/src/emerge.h index 1bac4b708..ace9f6e46 100644 --- a/src/emerge.h +++ b/src/emerge.h @@ -107,6 +107,7 @@ public: u32 gen_notify_on; const std::set *gen_notify_on_deco_ids; // shared + const std::set *gen_notify_on_custom; // shared BiomeGen *biomegen; BiomeManager *biomemgr; @@ -114,6 +115,11 @@ public: DecorationManager *decomgr; SchematicManager *schemmgr; + inline GenerateNotifier createNotifier() const { + return GenerateNotifier(gen_notify_on, gen_notify_on_deco_ids, + gen_notify_on_custom); + } + private: EmergeParams(EmergeManager *parent, const BiomeGen *biomegen, const BiomeManager *biomemgr, @@ -134,6 +140,7 @@ public: // Generation Notify u32 gen_notify_on = 0; std::set gen_notify_on_deco_ids; + std::set gen_notify_on_custom; // Parameters passed to mapgens owned by ServerMap // TODO(hmmmm): Remove this after mapgen helper methods using them diff --git a/src/emerge_internal.h b/src/emerge_internal.h new file mode 100644 index 000000000..439c8227b --- /dev/null +++ b/src/emerge_internal.h @@ -0,0 +1,115 @@ +/* +Minetest +Copyright (C) 2010-2013 kwolekr, Ryan Kwolek + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +/******************************************************************/ +/* may only be included by emerge.cpp or emerge scripting related */ +/******************************************************************/ + +#include "emerge.h" + +#include + +#include "util/thread.h" +#include "threading/event.h" + +class Server; +class ServerMap; +class Mapgen; + +class EmergeManager; +class EmergeScripting; + +class EmergeThread : public Thread { +public: + bool enable_mapgen_debug_info; + int id; + + EmergeThread(Server *server, int ethreadid); + ~EmergeThread() = default; + + void *run(); + void signal(); + + // Requires queue mutex held + bool pushBlock(const v3s16 &pos); + + void cancelPendingItems(); + + EmergeManager *getEmergeManager() { return m_emerge; } + Mapgen *getMapgen() { return m_mapgen; } + +protected: + + void runCompletionCallbacks( + const v3s16 &pos, EmergeAction action, + const EmergeCallbackList &callbacks); + +private: + Server *m_server; + ServerMap *m_map; + EmergeManager *m_emerge; + Mapgen *m_mapgen; + + std::unique_ptr m_script; + // read from scripting: + UniqueQueue *m_trans_liquid; //< non-null only when generating a mapblock + + Event m_queue_event; + std::queue m_block_queue; + + bool initScripting(); + + bool popBlockEmerge(v3s16 *pos, BlockEmergeData *bedata); + + EmergeAction getBlockOrStartGen( + const v3s16 &pos, bool allow_gen, MapBlock **block, BlockMakeData *data); + MapBlock *finishGen(v3s16 pos, BlockMakeData *bmdata, + std::map *modified_blocks); + + friend class EmergeManager; + friend class EmergeScripting; + friend class ModApiMapgen; +}; + +// Scoped helper to set Server::m_ignore_map_edit_events_area +class MapEditEventAreaIgnorer +{ +public: + MapEditEventAreaIgnorer(VoxelArea *ignorevariable, const VoxelArea &a): + m_ignorevariable(ignorevariable) + { + if (m_ignorevariable->getVolume() == 0) + *m_ignorevariable = a; + else + m_ignorevariable = nullptr; + } + + ~MapEditEventAreaIgnorer() + { + if (m_ignorevariable) { + assert(m_ignorevariable->getVolume() != 0); + *m_ignorevariable = VoxelArea(); + } + } + +private: + VoxelArea *m_ignorevariable; +}; diff --git a/src/mapgen/mapgen.cpp b/src/mapgen/mapgen.cpp index a4557037e..80ffebc9e 100644 --- a/src/mapgen/mapgen.cpp +++ b/src/mapgen/mapgen.cpp @@ -70,6 +70,7 @@ FlagDesc flagdesc_gennotify[] = { {"large_cave_begin", 1 << GENNOTIFY_LARGECAVE_BEGIN}, {"large_cave_end", 1 << GENNOTIFY_LARGECAVE_END}, {"decoration", 1 << GENNOTIFY_DECORATION}, + {"custom", 1 << GENNOTIFY_CUSTOM}, {NULL, 0} }; @@ -108,7 +109,7 @@ static_assert( //// Mapgen::Mapgen(int mapgenid, MapgenParams *params, EmergeParams *emerge) : - gennotify(emerge->gen_notify_on, emerge->gen_notify_on_deco_ids) + gennotify(emerge->createNotifier()) { id = mapgenid; water_level = params->water_level; @@ -980,39 +981,67 @@ void MapgenBasic::generateDungeons(s16 max_stone_y) //// GenerateNotifier::GenerateNotifier(u32 notify_on, - const std::set *notify_on_deco_ids) + const std::set *notify_on_deco_ids, + const std::set *notify_on_custom) { m_notify_on = notify_on; m_notify_on_deco_ids = notify_on_deco_ids; + m_notify_on_custom = notify_on_custom; } -bool GenerateNotifier::addEvent(GenNotifyType type, v3s16 pos, u32 id) +bool GenerateNotifier::addEvent(GenNotifyType type, v3s16 pos) { - if (!(m_notify_on & (1 << type))) - return false; - - if (type == GENNOTIFY_DECORATION && - m_notify_on_deco_ids->find(id) == m_notify_on_deco_ids->cend()) + assert(type != GENNOTIFY_DECORATION && type != GENNOTIFY_CUSTOM); + if (!shouldNotifyOn(type)) return false; GenNotifyEvent gne; gne.type = type; gne.pos = pos; - gne.id = id; - m_notify_events.push_back(gne); + m_notify_events.emplace_back(std::move(gne)); + return true; +} + +bool GenerateNotifier::addDecorationEvent(v3s16 pos, u32 id) +{ + if (!shouldNotifyOn(GENNOTIFY_DECORATION)) + return false; + // check if data relating to this decoration was requested + assert(m_notify_on_deco_ids); + if (m_notify_on_deco_ids->find(id) == m_notify_on_deco_ids->cend()) + return false; + + GenNotifyEvent gne; + gne.type = GENNOTIFY_DECORATION; + gne.pos = pos; + gne.id = id; + m_notify_events.emplace_back(std::move(gne)); + return true; +} + + +bool GenerateNotifier::setCustom(const std::string &key, const std::string &value) +{ + if (!shouldNotifyOn(GENNOTIFY_CUSTOM)) + return false; + // check if this key was requested to be saved + assert(m_notify_on_custom); + if (m_notify_on_custom->count(key) == 0) + return false; + + m_notify_custom[key] = value; return true; } void GenerateNotifier::getEvents( - std::map > &event_map) + std::map> &event_map) const { - std::list::iterator it; + for (auto &gn : m_notify_events) { + assert(gn.type != GENNOTIFY_CUSTOM); // never stored in this list - for (it = m_notify_events.begin(); it != m_notify_events.end(); ++it) { - GenNotifyEvent &gn = *it; std::string name = (gn.type == GENNOTIFY_DECORATION) ? "decoration#"+ itos(gn.id) : flagdesc_gennotify[gn.type].name; @@ -1025,6 +1054,7 @@ void GenerateNotifier::getEvents( void GenerateNotifier::clearEvents() { m_notify_events.clear(); + m_notify_custom.clear(); } diff --git a/src/mapgen/mapgen.h b/src/mapgen/mapgen.h index b1d1a1bf0..d2d21a9fc 100644 --- a/src/mapgen/mapgen.h +++ b/src/mapgen/mapgen.h @@ -76,29 +76,41 @@ enum GenNotifyType { GENNOTIFY_LARGECAVE_BEGIN, GENNOTIFY_LARGECAVE_END, GENNOTIFY_DECORATION, + GENNOTIFY_CUSTOM, // user-defined data NUM_GENNOTIFY_TYPES }; -struct GenNotifyEvent { - GenNotifyType type; - v3s16 pos; - u32 id; -}; - class GenerateNotifier { public: + struct GenNotifyEvent { + GenNotifyType type; + v3s16 pos; + u32 id; // for GENNOTIFY_DECORATION + }; + // Use only for temporary Mapgen objects with no map generation! GenerateNotifier() = default; - GenerateNotifier(u32 notify_on, const std::set *notify_on_deco_ids); + // normal constructor + GenerateNotifier(u32 notify_on, const std::set *notify_on_deco_ids, + const std::set *notify_on_custom); - bool addEvent(GenNotifyType type, v3s16 pos, u32 id=0); - void getEvents(std::map > &event_map); + bool addEvent(GenNotifyType type, v3s16 pos); + bool addDecorationEvent(v3s16 pos, u32 deco_id); + bool setCustom(const std::string &key, const std::string &value); + void getEvents(std::map> &map) const; + const StringMap &getCustomData() const { return m_notify_custom; } void clearEvents(); private: u32 m_notify_on = 0; const std::set *m_notify_on_deco_ids = nullptr; + const std::set *m_notify_on_custom = nullptr; std::list m_notify_events; + StringMap m_notify_custom; + + inline bool shouldNotifyOn(GenNotifyType type) const { + return m_notify_on & (1 << type); + } }; // Order must match the order of 'static MapgenDesc g_reg_mapgens[]' in mapgen.cpp diff --git a/src/mapgen/mg_decoration.cpp b/src/mapgen/mg_decoration.cpp index 75764a9f1..e9e249220 100644 --- a/src/mapgen/mg_decoration.cpp +++ b/src/mapgen/mg_decoration.cpp @@ -236,8 +236,7 @@ size_t Decoration::placeDeco(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax) v3s16 pos(x, y, z); if (generate(mg->vm, &ps, pos, false)) - mg->gennotify.addEvent( - GENNOTIFY_DECORATION, pos, index); + mg->gennotify.addDecorationEvent(pos, index); } } @@ -249,8 +248,7 @@ size_t Decoration::placeDeco(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax) v3s16 pos(x, y, z); if (generate(mg->vm, &ps, pos, true)) - mg->gennotify.addEvent( - GENNOTIFY_DECORATION, pos, index); + mg->gennotify.addDecorationEvent(pos, index); } } } else { // Heightmap decorations @@ -273,7 +271,7 @@ size_t Decoration::placeDeco(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax) v3s16 pos(x, y, z); if (generate(mg->vm, &ps, pos, false)) - mg->gennotify.addEvent(GENNOTIFY_DECORATION, pos, index); + mg->gennotify.addDecorationEvent(pos, index); } } } diff --git a/src/script/CMakeLists.txt b/src/script/CMakeLists.txt index bebe2f037..ccb5785bd 100644 --- a/src/script/CMakeLists.txt +++ b/src/script/CMakeLists.txt @@ -5,6 +5,7 @@ add_subdirectory(lua_api) # Used by server and client set(common_SCRIPT_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/scripting_server.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/scripting_emerge.cpp ${common_SCRIPT_COMMON_SRCS} ${common_SCRIPT_CPP_API_SRCS} ${common_SCRIPT_LUA_API_SRCS} diff --git a/src/script/cpp_api/CMakeLists.txt b/src/script/cpp_api/CMakeLists.txt index 3cfd7709a..d9d157d5f 100644 --- a/src/script/cpp_api/CMakeLists.txt +++ b/src/script/cpp_api/CMakeLists.txt @@ -5,6 +5,7 @@ set(common_SCRIPT_CPP_API_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/s_env.cpp ${CMAKE_CURRENT_SOURCE_DIR}/s_inventory.cpp ${CMAKE_CURRENT_SOURCE_DIR}/s_item.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/s_mapgen.cpp ${CMAKE_CURRENT_SOURCE_DIR}/s_modchannels.cpp ${CMAKE_CURRENT_SOURCE_DIR}/s_node.cpp ${CMAKE_CURRENT_SOURCE_DIR}/s_nodemeta.cpp diff --git a/src/script/cpp_api/s_base.h b/src/script/cpp_api/s_base.h index fb152b371..9ce2f5c6b 100644 --- a/src/script/cpp_api/s_base.h +++ b/src/script/cpp_api/s_base.h @@ -61,13 +61,15 @@ enum class ScriptingType: u8 { Async, // either mainmenu (client) or ingame (server) Client, MainMenu, - Server + Server, + Emerge }; class Server; #ifndef SERVER class Client; #endif +class EmergeThread; class IGameDef; class Environment; class GUIEngine; @@ -158,6 +160,9 @@ protected: void setGuiEngine(GUIEngine* guiengine) { m_guiengine = guiengine; } #endif + EmergeThread* getEmergeThread() { return m_emerge; } + void setEmergeThread(EmergeThread *emerge) { m_emerge = emerge; } + void objectrefGetOrCreate(lua_State *L, ServerActiveObject *cobj); void pushPlayerHPChangeReason(lua_State *L, const PlayerHPChangeReason& reason); @@ -180,5 +185,7 @@ private: #ifndef SERVER GUIEngine *m_guiengine = nullptr; #endif + EmergeThread *m_emerge = nullptr; + ScriptingType m_type; }; diff --git a/src/script/cpp_api/s_mapgen.cpp b/src/script/cpp_api/s_mapgen.cpp new file mode 100644 index 000000000..c363ce2c2 --- /dev/null +++ b/src/script/cpp_api/s_mapgen.cpp @@ -0,0 +1,76 @@ +/* +Minetest +Copyright (C) 2022 sfan5 + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "cpp_api/s_mapgen.h" +#include "cpp_api/s_internal.h" +#include "common/c_converter.h" +#include "lua_api/l_vmanip.h" +#include "emerge.h" + +void ScriptApiMapgen::on_mods_loaded() +{ + SCRIPTAPI_PRECHECKHEADER + + // Get registered shutdown hooks + lua_getglobal(L, "core"); + lua_getfield(L, -1, "registered_on_mods_loaded"); + // Call callbacks + runCallbacks(0, RUN_CALLBACKS_MODE_FIRST); +} + +void ScriptApiMapgen::on_shutdown() +{ + SCRIPTAPI_PRECHECKHEADER + + // Get registered shutdown hooks + lua_getglobal(L, "core"); + lua_getfield(L, -1, "registered_on_shutdown"); + // Call callbacks + runCallbacks(0, RUN_CALLBACKS_MODE_FIRST); +} + +void ScriptApiMapgen::on_generated(BlockMakeData *bmdata) +{ + SCRIPTAPI_PRECHECKHEADER + + v3s16 minp = bmdata->blockpos_min * MAP_BLOCKSIZE; + v3s16 maxp = bmdata->blockpos_max * MAP_BLOCKSIZE + + v3s16(1,1,1) * (MAP_BLOCKSIZE - 1); + + LuaVoxelManip::create(L, bmdata->vmanip, true); + const int vmanip = lua_gettop(L); + + // Store vmanip globally (used by helpers) + lua_getglobal(L, "core"); + lua_pushvalue(L, vmanip); + lua_setfield(L, -2, "vmanip"); + + // Call callbacks + lua_getfield(L, -1, "registered_on_generateds"); + lua_pushvalue(L, vmanip); + push_v3s16(L, minp); + push_v3s16(L, maxp); + lua_pushnumber(L, bmdata->seed); + runCallbacks(4, RUN_CALLBACKS_MODE_FIRST); + lua_pop(L, 1); // return val + + // Unset core.vmanip again + lua_pushnil(L); + lua_setfield(L, -2, "vmanip"); +} diff --git a/src/script/cpp_api/s_mapgen.h b/src/script/cpp_api/s_mapgen.h new file mode 100644 index 000000000..e881ca04a --- /dev/null +++ b/src/script/cpp_api/s_mapgen.h @@ -0,0 +1,40 @@ +/* +Minetest +Copyright (C) 2022 sfan5 + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include "cpp_api/s_base.h" + +struct BlockMakeData; + +/* + * Note that this is the class defining the functions called inside the emerge + * Lua state, not the server one. + */ + +class ScriptApiMapgen : virtual public ScriptApiBase +{ +public: + + void on_mods_loaded(); + void on_shutdown(); + + // Called after generating a piece of map before writing it to the map + void on_generated(BlockMakeData *bmdata); +}; diff --git a/src/script/lua_api/l_base.cpp b/src/script/lua_api/l_base.cpp index 921589816..7012f2fcc 100644 --- a/src/script/lua_api/l_base.cpp +++ b/src/script/lua_api/l_base.cpp @@ -75,6 +75,11 @@ GUIEngine *ModApiBase::getGuiEngine(lua_State *L) } #endif +EmergeThread *ModApiBase::getEmergeThread(lua_State *L) +{ + return getScriptApiBase(L)->getEmergeThread(); +} + std::string ModApiBase::getCurrentModPath(lua_State *L) { lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME); diff --git a/src/script/lua_api/l_base.h b/src/script/lua_api/l_base.h index 329e2cc26..37132426a 100644 --- a/src/script/lua_api/l_base.h +++ b/src/script/lua_api/l_base.h @@ -34,7 +34,7 @@ extern "C" { class Client; class GUIEngine; #endif - +class EmergeThread; class ScriptApiBase; class Server; class Environment; @@ -49,6 +49,7 @@ public: static Client* getClient(lua_State *L); static GUIEngine* getGuiEngine(lua_State *L); #endif // !SERVER + static EmergeThread* getEmergeThread(lua_State *L); static IGameDef* getGameDef(lua_State *L); diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp index 869961056..39f36a31a 100644 --- a/src/script/lua_api/l_env.cpp +++ b/src/script/lua_api/l_env.cpp @@ -34,7 +34,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "daynightratio.h" #include "util/pointedthing.h" #include "mapgen/treegen.h" -#include "emerge.h" +#include "emerge_internal.h" #include "pathfinder.h" #include "face_position_cache.h" #include "remoteplayer.h" @@ -241,7 +241,7 @@ void LuaEmergeAreaCallback(v3s16 blockpos, EmergeAction action, void *param) delete state; } -// Exported functions +/* Exported functions */ // set_node(pos, node) // pos = {x=num, y=num, z=num} @@ -1538,3 +1538,189 @@ void ModApiEnv::InitializeClient(lua_State *L, int top) API_FCT(line_of_sight); API_FCT(raycast); } + +#define GET_VM_PTR \ + MMVManip *vm = getVManip(L); \ + if (!vm) \ + return 0 + +// get_node_max_level(pos) +int ModApiEnvVM::l_get_node_max_level(lua_State *L) +{ + GET_VM_PTR; + + v3s16 pos = read_v3s16(L, 1); + MapNode n = vm->getNodeNoExNoEmerge(pos); + lua_pushnumber(L, n.getMaxLevel(getGameDef(L)->ndef())); + return 1; +} + +// get_node_level(pos) +int ModApiEnvVM::l_get_node_level(lua_State *L) +{ + GET_VM_PTR; + + v3s16 pos = read_v3s16(L, 1); + MapNode n = vm->getNodeNoExNoEmerge(pos); + lua_pushnumber(L, n.getLevel(getGameDef(L)->ndef())); + return 1; +} + +// set_node_level(pos, level) +int ModApiEnvVM::l_set_node_level(lua_State *L) +{ + GET_VM_PTR; + + v3s16 pos = read_v3s16(L, 1); + u8 level = 1; + if (lua_isnumber(L, 2)) + level = lua_tonumber(L, 2); + MapNode n = vm->getNodeNoExNoEmerge(pos); + lua_pushnumber(L, n.setLevel(getGameDef(L)->ndef(), level)); + vm->setNodeNoEmerge(pos, n); + return 1; +} + +// add_node_level(pos, level) +int ModApiEnvVM::l_add_node_level(lua_State *L) +{ + GET_VM_PTR; + + v3s16 pos = read_v3s16(L, 1); + u8 level = 1; + if (lua_isnumber(L, 2)) + level = lua_tonumber(L, 2); + MapNode n = vm->getNodeNoExNoEmerge(pos); + lua_pushnumber(L, n.addLevel(getGameDef(L)->ndef(), level)); + vm->setNodeNoEmerge(pos, n); + return 1; +} + +// find_node_near(pos, radius, nodenames, [search_center]) +int ModApiEnvVM::l_find_node_near(lua_State *L) +{ + GET_VM_PTR; + + const NodeDefManager *ndef = getGameDef(L)->ndef(); + + v3s16 pos = read_v3s16(L, 1); + int radius = luaL_checkinteger(L, 2); + std::vector filter; + collectNodeIds(L, 3, ndef, filter); + int start_radius = (lua_isboolean(L, 4) && readParam(L, 4)) ? 0 : 1; + + auto getNode = [&vm] (v3s16 p) -> MapNode { + return vm->getNodeNoExNoEmerge(p); + }; + return findNodeNear(L, pos, radius, filter, start_radius, getNode); +} + +// find_nodes_in_area(minp, maxp, nodenames, [grouped]) +int ModApiEnvVM::l_find_nodes_in_area(lua_State *L) +{ + GET_VM_PTR; + + const NodeDefManager *ndef = getGameDef(L)->ndef(); + + v3s16 minp = read_v3s16(L, 1); + v3s16 maxp = read_v3s16(L, 2); + sortBoxVerticies(minp, maxp); + + checkArea(minp, maxp); + // avoid the loop going out-of-bounds + { + VoxelArea cropped = VoxelArea(minp, maxp).intersect(vm->m_area); + minp = cropped.MinEdge; + maxp = cropped.MaxEdge; + } + + std::vector filter; + collectNodeIds(L, 3, ndef, filter); + + bool grouped = lua_isboolean(L, 4) && readParam(L, 4); + + auto iterate = [&] (auto callback) { + for (s16 z = minp.Z; z <= maxp.Z; z++) + for (s16 y = minp.Y; y <= maxp.Y; y++) { + u32 vi = vm->m_area.index(minp.X, y, z); + for (s16 x = minp.X; x <= maxp.X; x++) { + v3s16 pos(x, y, z); + MapNode n = vm->m_data[vi]; + if (!callback(pos, n)) + return; + ++vi; + } + } + }; + return findNodesInArea(L, ndef, filter, grouped, iterate); +} + +// find_nodes_in_area_under_air(minp, maxp, nodenames) +int ModApiEnvVM::l_find_nodes_in_area_under_air(lua_State *L) +{ + GET_VM_PTR; + + const NodeDefManager *ndef = getGameDef(L)->ndef(); + + v3s16 minp = read_v3s16(L, 1); + v3s16 maxp = read_v3s16(L, 2); + sortBoxVerticies(minp, maxp); + checkArea(minp, maxp); + + std::vector filter; + collectNodeIds(L, 3, ndef, filter); + + auto getNode = [&vm] (v3s16 p) -> MapNode { + return vm->getNodeNoExNoEmerge(p); + }; + return findNodesInAreaUnderAir(L, minp, maxp, filter, getNode); +} + +// spawn_tree(pos, treedef) +int ModApiEnvVM::l_spawn_tree(lua_State *L) +{ + GET_VM_PTR; + + const NodeDefManager *ndef = getGameDef(L)->ndef(); + + v3s16 p0 = read_v3s16(L, 1); + + treegen::TreeDef tree_def; + if (!read_tree_def(L, 2, ndef, tree_def)) + return 0; + + treegen::error e; + if ((e = treegen::make_ltree(*vm, p0, ndef, tree_def)) != treegen::SUCCESS) { + if (e == treegen::UNBALANCED_BRACKETS) { + throw LuaError("spawn_tree(): closing ']' has no matching opening bracket"); + } else { + throw LuaError("spawn_tree(): unknown error"); + } + } + + lua_pushboolean(L, true); + return 1; +} + +MMVManip *ModApiEnvVM::getVManip(lua_State *L) +{ + auto emerge = getEmergeThread(L); + if (emerge) + return emerge->getMapgen()->vm; + return nullptr; +} + +void ModApiEnvVM::InitializeEmerge(lua_State *L, int top) +{ + // other, more trivial functions are in builtin/emerge/env.lua + API_FCT(get_node_max_level); + API_FCT(get_node_level); + API_FCT(set_node_level); + API_FCT(add_node_level); + API_FCT(find_node_near); + API_FCT(find_nodes_in_area); + API_FCT(find_nodes_in_area_under_air); + API_FCT(spawn_tree); +} + +#undef GET_VM_PTR diff --git a/src/script/lua_api/l_env.h b/src/script/lua_api/l_env.h index 0e1336962..327cf9f75 100644 --- a/src/script/lua_api/l_env.h +++ b/src/script/lua_api/l_env.h @@ -243,6 +243,44 @@ public: static void InitializeClient(lua_State *L, int top); }; +/* + * Duplicates of certain env APIs that operate not on the global + * map but on a VoxelManipulator. This is for emerge scripting. + */ +class ModApiEnvVM : public ModApiEnvBase { +private: + + // get_node_max_level(pos) + static int l_get_node_max_level(lua_State *L); + + // get_node_level(pos) + static int l_get_node_level(lua_State *L); + + // set_node_level(pos) + static int l_set_node_level(lua_State *L); + + // add_node_level(pos) + static int l_add_node_level(lua_State *L); + + // find_node_near(pos, radius, nodenames, [search_center]) + static int l_find_node_near(lua_State *L); + + // find_nodes_in_area(minp, maxp, nodenames, [grouped]) + static int l_find_nodes_in_area(lua_State *L); + + // find_surface_nodes_in_area(minp, maxp, nodenames) + static int l_find_nodes_in_area_under_air(lua_State *L); + + // spawn_tree(pos, treedef) + static int l_spawn_tree(lua_State *L); + + // Helper: get the vmanip we're operating on + static MMVManip *getVManip(lua_State *L); + +public: + static void InitializeEmerge(lua_State *L, int top); +}; + class LuaABM : public ActiveBlockModifier { private: int m_id; diff --git a/src/script/lua_api/l_mapgen.cpp b/src/script/lua_api/l_mapgen.cpp index 86e0c1c32..b8c6e7105 100644 --- a/src/script/lua_api/l_mapgen.cpp +++ b/src/script/lua_api/l_mapgen.cpp @@ -26,7 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/serialize.h" #include "server.h" #include "environment.h" -#include "emerge.h" +#include "emerge_internal.h" #include "mapgen/mg_biome.h" #include "mapgen/mg_ore.h" #include "mapgen/mg_decoration.h" @@ -482,7 +482,7 @@ int ModApiMapgen::l_get_biome_id(lua_State *L) const char *biome_str = luaL_checkstring(L, 1); - const BiomeManager *bmgr = getServer(L)->getEmergeManager()->getBiomeManager(); + const BiomeManager *bmgr = getEmergeManager(L)->getBiomeManager(); if (!bmgr) return 0; @@ -504,7 +504,7 @@ int ModApiMapgen::l_get_biome_name(lua_State *L) int biome_id = luaL_checkinteger(L, 1); - const BiomeManager *bmgr = getServer(L)->getEmergeManager()->getBiomeManager(); + const BiomeManager *bmgr = getEmergeManager(L)->getBiomeManager(); if (!bmgr) return 0; @@ -523,8 +523,7 @@ int ModApiMapgen::l_get_heat(lua_State *L) v3s16 pos = read_v3s16(L, 1); - const BiomeGen *biomegen = getServer(L)->getEmergeManager()->getBiomeGen(); - + const BiomeGen *biomegen = getBiomeGen(L); if (!biomegen || biomegen->getType() != BIOMEGEN_ORIGINAL) return 0; @@ -544,8 +543,7 @@ int ModApiMapgen::l_get_humidity(lua_State *L) v3s16 pos = read_v3s16(L, 1); - const BiomeGen *biomegen = getServer(L)->getEmergeManager()->getBiomeGen(); - + const BiomeGen *biomegen = getBiomeGen(L); if (!biomegen || biomegen->getType() != BIOMEGEN_ORIGINAL) return 0; @@ -565,7 +563,7 @@ int ModApiMapgen::l_get_biome_data(lua_State *L) v3s16 pos = read_v3s16(L, 1); - const BiomeGen *biomegen = getServer(L)->getEmergeManager()->getBiomeGen(); + const BiomeGen *biomegen = getBiomeGen(L); if (!biomegen) return 0; @@ -607,8 +605,7 @@ int ModApiMapgen::l_get_mapgen_object(lua_State *L) enum MapgenObject mgobj = (MapgenObject)mgobjint; - EmergeManager *emerge = getServer(L)->getEmergeManager(); - Mapgen *mg = emerge->getCurrentMapgen(); + Mapgen *mg = getMapgen(L); if (!mg) throw LuaError("Must only be called in a mapgen thread!"); @@ -683,8 +680,7 @@ int ModApiMapgen::l_get_mapgen_object(lua_State *L) return 1; } case MGOBJ_GENNOTIFY: { - std::map >event_map; - + std::map> event_map; mg->gennotify.getEvents(event_map); lua_createtable(L, 0, event_map.size()); @@ -699,6 +695,24 @@ int ModApiMapgen::l_get_mapgen_object(lua_State *L) lua_setfield(L, -2, it->first.c_str()); } + // push user-defined data + auto &custom_map = mg->gennotify.getCustomData(); + + lua_createtable(L, 0, custom_map.size()); + lua_getglobal(L, "core"); + lua_getfield(L, -1, "deserialize"); + lua_remove(L, -2); // remove 'core' + for (const auto &it : custom_map) { + lua_pushvalue(L, -1); // deserialize func + lua_pushlstring(L, it.second.c_str(), it.second.size()); + lua_pushboolean(L, true); + lua_call(L, 2, 1); + + lua_setfield(L, -3, it.first.c_str()); // put into table + } + lua_pop(L, 1); // remove func + lua_setfield(L, -2, "custom"); // put into top-level table + return 1; } } @@ -728,6 +742,31 @@ int ModApiMapgen::l_get_spawn_level(lua_State *L) } +// get_seed([add]) +int ModApiMapgen::l_get_seed(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + // This exists to + // 1. not duplicate the truncation logic from Mapgen::Mapgen() once more + // 2. because I don't trust myself to do it correctly in Lua + + auto *emerge = getEmergeManager(L); + if (!emerge || !emerge->mgparams) + return 0; + + int add = 0; + if (lua_isnumber(L, 1)) + add = luaL_checkint(L, 1); + + s32 seed = (s32)emerge->mgparams->seed; + seed += add; + + lua_pushinteger(L, seed); + return 1; +} + + int ModApiMapgen::l_get_mapgen_params(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -737,8 +776,8 @@ int ModApiMapgen::l_get_mapgen_params(lua_State *L) std::string value; - MapSettingsManager *settingsmgr = - getServer(L)->getEmergeManager()->map_settings_mgr; + const MapSettingsManager *settingsmgr = + getEmergeManager(L)->map_settings_mgr; lua_newtable(L); @@ -810,7 +849,8 @@ int ModApiMapgen::l_get_mapgen_edges(lua_State *L) { NO_MAP_LOCK_REQUIRED; - MapSettingsManager *settingsmgr = getServer(L)->getEmergeManager()->map_settings_mgr; + const MapSettingsManager *settingsmgr = + getEmergeManager(L)->map_settings_mgr; // MapSettingsManager::makeMapgenParams cannot be used here because it would // make mapgen settings immutable from then on. Mapgen settings should stay @@ -846,8 +886,8 @@ int ModApiMapgen::l_get_mapgen_setting(lua_State *L) NO_MAP_LOCK_REQUIRED; std::string value; - MapSettingsManager *settingsmgr = - getServer(L)->getEmergeManager()->map_settings_mgr; + const MapSettingsManager *settingsmgr = + getEmergeManager(L)->map_settings_mgr; const char *name = luaL_checkstring(L, 1); if (!settingsmgr->getMapSetting(name, &value)) @@ -863,8 +903,8 @@ int ModApiMapgen::l_get_mapgen_setting_noiseparams(lua_State *L) NO_MAP_LOCK_REQUIRED; NoiseParams np; - MapSettingsManager *settingsmgr = - getServer(L)->getEmergeManager()->map_settings_mgr; + const MapSettingsManager *settingsmgr = + getEmergeManager(L)->map_settings_mgr; const char *name = luaL_checkstring(L, 1); if (!settingsmgr->getMapSettingNoiseParams(name, &np)) @@ -964,7 +1004,7 @@ int ModApiMapgen::l_get_noiseparams(lua_State *L) } -// set_gen_notify(flags, {deco_id_table}) +// set_gen_notify(flags, {deco_ids}, {custom_ids}) int ModApiMapgen::l_set_gen_notify(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -986,11 +1026,26 @@ int ModApiMapgen::l_set_gen_notify(lua_State *L) } } + if (lua_istable(L, 3)) { + lua_pushnil(L); + while (lua_next(L, 3)) { + emerge->gen_notify_on_custom.insert(readParam(L, -1)); + lua_pop(L, 1); + } + } + + // Clear sets if relevant flag disabled + if ((emerge->gen_notify_on & (1 << GENNOTIFY_DECORATION)) == 0) + emerge->gen_notify_on_deco_ids.clear(); + if ((emerge->gen_notify_on & (1 << GENNOTIFY_CUSTOM)) == 0) + emerge->gen_notify_on_custom.clear(); + return 0; } // get_gen_notify() +// returns flagstring, {deco_ids}, {custom_ids}) int ModApiMapgen::l_get_gen_notify(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -999,13 +1054,43 @@ int ModApiMapgen::l_get_gen_notify(lua_State *L) push_flags_string(L, flagdesc_gennotify, emerge->gen_notify_on, emerge->gen_notify_on); - lua_newtable(L); + lua_createtable(L, emerge->gen_notify_on_deco_ids.size(), 0); int i = 1; - for (u32 gen_notify_on_deco_id : emerge->gen_notify_on_deco_ids) { - lua_pushnumber(L, gen_notify_on_deco_id); + for (u32 id : emerge->gen_notify_on_deco_ids) { + lua_pushnumber(L, id); lua_rawseti(L, -2, i++); } - return 2; + + lua_createtable(L, emerge->gen_notify_on_custom.size(), 0); + int j = 1; + for (const auto &id : emerge->gen_notify_on_custom) { + lua_pushstring(L, id.c_str()); + lua_rawseti(L, -2, j++); + } + + return 3; +} + + +// save_gen_notify(custom_id, data) [in emerge thread] +int ModApiMapgen::l_save_gen_notify(lua_State *L) +{ + auto *emerge = getEmergeThread(L); + + std::string key = readParam(L, 1); + + lua_getglobal(L, "core"); + lua_getfield(L, -1, "serialize"); + lua_remove(L, -2); // remove 'core' + lua_pushvalue(L, 2); + lua_call(L, 1, 1); + std::string val = readParam(L, -1); + lua_pop(L, 1); + + bool set = emerge->getMapgen()->gennotify.setCustom(key, val); + + lua_pushboolean(L, set); + return 1; } @@ -1020,8 +1105,7 @@ int ModApiMapgen::l_get_decoration_id(lua_State *L) return 0; const DecorationManager *dmgr = - getServer(L)->getEmergeManager()->getDecorationManager(); - + getEmergeManager(L)->getDecorationManager(); if (!dmgr) return 0; @@ -1452,20 +1536,26 @@ int ModApiMapgen::l_clear_registered_schematics(lua_State *L) } -// generate_ores(vm, p1, p2, [ore_id]) +// generate_ores(vm, p1, p2) int ModApiMapgen::l_generate_ores(lua_State *L) { NO_MAP_LOCK_REQUIRED; - EmergeManager *emerge = getServer(L)->getEmergeManager(); + auto *emerge = getEmergeManager(L); if (!emerge || !emerge->mgparams) return 0; + OreManager *oremgr; + if (auto mg = getMapgen(L)) + oremgr = mg->m_emerge->oremgr; + else + oremgr = emerge->oremgr; + Mapgen mg; // Intentionally truncates to s32, see Mapgen::Mapgen() mg.seed = (s32)emerge->mgparams->seed; mg.vm = checkObject(L, 1)->vm; - mg.ndef = getServer(L)->getNodeDefManager(); + 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; @@ -1475,26 +1565,32 @@ int ModApiMapgen::l_generate_ores(lua_State *L) u32 blockseed = Mapgen::getBlockSeed(pmin, mg.seed); - emerge->oremgr->placeAllOres(&mg, blockseed, pmin, pmax); + oremgr->placeAllOres(&mg, blockseed, pmin, pmax); return 0; } -// generate_decorations(vm, p1, p2, [deco_id]) +// generate_decorations(vm, p1, p2) int ModApiMapgen::l_generate_decorations(lua_State *L) { NO_MAP_LOCK_REQUIRED; - EmergeManager *emerge = getServer(L)->getEmergeManager(); + auto *emerge = getEmergeManager(L); if (!emerge || !emerge->mgparams) return 0; + DecorationManager *decomgr; + if (auto mg = getMapgen(L)) + decomgr = mg->m_emerge->decomgr; + else + decomgr = emerge->decomgr; + Mapgen mg; // Intentionally truncates to s32, see Mapgen::Mapgen() mg.seed = (s32)emerge->mgparams->seed; mg.vm = checkObject(L, 1)->vm; - mg.ndef = getServer(L)->getNodeDefManager(); + 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; @@ -1504,7 +1600,7 @@ int ModApiMapgen::l_generate_decorations(lua_State *L) u32 blockseed = Mapgen::getBlockSeed(pmin, mg.seed); - emerge->decomgr->placeAllDecos(&mg, blockseed, pmin, pmax); + decomgr->placeAllDecos(&mg, blockseed, pmin, pmax); return 0; } @@ -1629,7 +1725,11 @@ int ModApiMapgen::l_place_schematic_on_vmanip(lua_State *L) { NO_MAP_LOCK_REQUIRED; - SchematicManager *schemmgr = getServer(L)->getEmergeManager()->schemmgr; + SchematicManager *schemmgr; + if (auto mg = getMapgen(L)) + schemmgr = mg->m_emerge->schemmgr; + else + schemmgr = getServer(L)->getEmergeManager()->schemmgr; //// Read VoxelManip object MMVManip *vm = checkObject(L, 1)->vm; @@ -1677,7 +1777,7 @@ int ModApiMapgen::l_serialize_schematic(lua_State *L) { NO_MAP_LOCK_REQUIRED; - const SchematicManager *schemmgr = getServer(L)->getEmergeManager()->getSchematicManager(); + const SchematicManager *schemmgr = getEmergeManager(L)->getSchematicManager(); //// Read options bool use_comments = getboolfield_default(L, 3, "lua_use_comments", false); @@ -1727,8 +1827,7 @@ int ModApiMapgen::l_read_schematic(lua_State *L) { NO_MAP_LOCK_REQUIRED; - const SchematicManager *schemmgr = - getServer(L)->getEmergeManager()->getSchematicManager(); + const SchematicManager *schemmgr = getEmergeManager(L)->getSchematicManager(); const NodeDefManager *ndef = getGameDef(L)->ndef(); //// Read options @@ -1806,17 +1905,22 @@ int ModApiMapgen::l_read_schematic(lua_State *L) int ModApiMapgen::update_liquids(lua_State *L, MMVManip *vm) { - GET_ENV_PTR; + UniqueQueue *trans_liquid; + if (auto emerge = getEmergeThread(L)) { + trans_liquid = emerge->m_trans_liquid; + } else { + GET_ENV_PTR; + trans_liquid = &env->getServerMap().m_transforming_liquid; + } + assert(trans_liquid); - ServerMap *map = &(env->getServerMap()); - const NodeDefManager *ndef = getServer(L)->getNodeDefManager(); + const NodeDefManager *ndef = getGameDef(L)->ndef(); Mapgen mg; mg.vm = vm; mg.ndef = ndef; - mg.updateLiquid(&map->m_transforming_liquid, - vm->m_area.MinEdge, vm->m_area.MaxEdge); + mg.updateLiquid(trans_liquid, vm->m_area.MinEdge, vm->m_area.MaxEdge); return 0; } @@ -1824,7 +1928,7 @@ int ModApiMapgen::calc_lighting(lua_State *L, MMVManip *vm, v3s16 pmin, v3s16 pmax, bool propagate_shadow) { const NodeDefManager *ndef = getGameDef(L)->ndef(); - EmergeManager *emerge = getServer(L)->getEmergeManager(); + auto emerge = getEmergeManager(L); assert(vm->m_area.contains(VoxelArea(pmin, pmax))); @@ -1850,6 +1954,35 @@ int ModApiMapgen::set_lighting(lua_State *L, MMVManip *vm, return 0; } +const EmergeManager *ModApiMapgen::getEmergeManager(lua_State *L) +{ + auto emerge = getEmergeThread(L); + if (emerge) + return emerge->getEmergeManager(); + return getServer(L)->getEmergeManager(); +} + +const BiomeGen *ModApiMapgen::getBiomeGen(lua_State *L) +{ + // path 1: we're in the emerge environment + auto emerge = getEmergeThread(L); + if (emerge) + return emerge->getMapgen()->m_emerge->biomegen; + // path 2: we're in the server environment + auto manager = getServer(L)->getEmergeManager(); + return manager->getBiomeGen(); +} + +Mapgen *ModApiMapgen::getMapgen(lua_State *L) +{ + // path 1 + auto emerge = getEmergeThread(L); + if (emerge) + return emerge->getMapgen(); + // path 2 + return getServer(L)->getEmergeManager()->getCurrentMapgen(); +} + void ModApiMapgen::Initialize(lua_State *L, int top) { API_FCT(get_biome_id); @@ -1891,3 +2024,28 @@ void ModApiMapgen::Initialize(lua_State *L, int top) API_FCT(serialize_schematic); API_FCT(read_schematic); } + +void ModApiMapgen::InitializeEmerge(lua_State *L, int top) +{ + API_FCT(get_biome_id); + API_FCT(get_biome_name); + API_FCT(get_heat); + API_FCT(get_humidity); + API_FCT(get_biome_data); + API_FCT(get_mapgen_object); + + API_FCT(get_seed); + API_FCT(get_mapgen_params); + API_FCT(get_mapgen_edges); + API_FCT(get_mapgen_setting); + API_FCT(get_mapgen_setting_noiseparams); + API_FCT(get_noiseparams); + API_FCT(get_decoration_id); + API_FCT(save_gen_notify); + + API_FCT(generate_ores); + API_FCT(generate_decorations); + API_FCT(place_schematic_on_vmanip); + API_FCT(serialize_schematic); + API_FCT(read_schematic); +} diff --git a/src/script/lua_api/l_mapgen.h b/src/script/lua_api/l_mapgen.h index e7984b2dd..c540057b7 100644 --- a/src/script/lua_api/l_mapgen.h +++ b/src/script/lua_api/l_mapgen.h @@ -25,6 +25,9 @@ with this program; if not, write to the Free Software Foundation, Inc., typedef u16 biome_t; // copy from mg_biome.h to avoid an unnecessary include class MMVManip; +class BiomeManager; +class BiomeGen; +class Mapgen; class ModApiMapgen : public ModApiBase { @@ -68,6 +71,9 @@ private: // get_mapgen_edges([mapgen_limit[, chunksize]]) static int l_get_mapgen_edges(lua_State *L); + // get_seed([add]) + static int l_get_seed(lua_State *L); + // get_mapgen_setting(name) static int l_get_mapgen_setting(lua_State *L); @@ -86,12 +92,15 @@ private: // get_noiseparam_defaults(name) static int l_get_noiseparams(lua_State *L); - // set_gen_notify(flags, {deco_id_table}) + // set_gen_notify(flags, {deco_ids}, {ud_ids}) static int l_set_gen_notify(lua_State *L); // get_gen_notify() static int l_get_gen_notify(lua_State *L); + // save_gen_notify(ud_id, data) + static int l_save_gen_notify(lua_State *L); + // get_decoration_id(decoration_name) // returns the decoration ID as used in gennotify static int l_get_decoration_id(lua_State *L); @@ -158,8 +167,18 @@ private: static int set_lighting(lua_State *L, MMVManip *vm, v3s16 pmin, v3s16 pmax, u8 light); + // Helpers + + // get a read-only(!) EmergeManager + static const EmergeManager *getEmergeManager(lua_State *L); + // get the thread-local or global BiomeGen (still read-only) + static const BiomeGen *getBiomeGen(lua_State *L); + // get the thread-local mapgen + static Mapgen *getMapgen(lua_State *L); + public: static void Initialize(lua_State *L, int top); + static void InitializeEmerge(lua_State *L, int top); static struct EnumString es_BiomeTerrainType[]; static struct EnumString es_DecorationType[]; diff --git a/src/script/lua_api/l_server.cpp b/src/script/lua_api/l_server.cpp index 860b05d49..39de47231 100644 --- a/src/script/lua_api/l_server.cpp +++ b/src/script/lua_api/l_server.cpp @@ -667,6 +667,25 @@ int ModApiServer::l_register_async_dofile(lua_State *L) return 1; } +// register_mapgen_script(path) +int ModApiServer::l_register_mapgen_script(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + std::string path = readParam(L, 1); + CHECK_SECURE_PATH(L, path.c_str(), false); + + // Find currently running mod name (only at init time) + lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME); + if (!lua_isstring(L, -1)) + return 0; + std::string modname = readParam(L, -1); + + getServer(L)->m_mapgen_init_files.emplace_back(modname, path); + lua_pushboolean(L, true); + return 1; +} + // serialize_roundtrip(value) // Meant for unit testing the packer from Lua int ModApiServer::l_serialize_roundtrip(lua_State *L) @@ -730,6 +749,8 @@ void ModApiServer::Initialize(lua_State *L, int top) API_FCT(do_async_callback); API_FCT(register_async_dofile); API_FCT(serialize_roundtrip); + + API_FCT(register_mapgen_script); } void ModApiServer::InitializeAsync(lua_State *L, int top) diff --git a/src/script/lua_api/l_server.h b/src/script/lua_api/l_server.h index 29fea7677..33dd814b4 100644 --- a/src/script/lua_api/l_server.h +++ b/src/script/lua_api/l_server.h @@ -118,6 +118,9 @@ private: // register_async_dofile(path) static int l_register_async_dofile(lua_State *L); + // register_mapgen_script(path) + static int l_register_mapgen_script(lua_State *L); + // serialize_roundtrip(obj) static int l_serialize_roundtrip(lua_State *L); diff --git a/src/script/lua_api/l_vmanip.cpp b/src/script/lua_api/l_vmanip.cpp index da89f4e57..76b5aff0f 100644 --- a/src/script/lua_api/l_vmanip.cpp +++ b/src/script/lua_api/l_vmanip.cpp @@ -48,6 +48,9 @@ int LuaVoxelManip::l_read_from_map(lua_State *L) if (vm->isOrphan()) return 0; + if (getEmergeThread(L)) + throw LuaError("VoxelManip:read_from_map called in mapgen environment"); + v3s16 bp1 = getNodeBlockPos(check_v3s16(L, 2)); v3s16 bp2 = getNodeBlockPos(check_v3s16(L, 3)); sortBoxVerticies(bp1, bp2); @@ -110,14 +113,18 @@ int LuaVoxelManip::l_set_data(lua_State *L) int LuaVoxelManip::l_write_to_map(lua_State *L) { - GET_ENV_PTR; - LuaVoxelManip *o = checkObject(L, 1); bool update_light = !lua_isboolean(L, 2) || readParam(L, 2); if (o->vm->isOrphan()) return 0; + // This wouldn't work anyway as we have no env ptr, but it's still unsafe. + if (getEmergeThread(L)) + throw LuaError("VoxelManip:write_to_map called in mapgen environment"); + + GET_ENV_PTR; + ServerMap *map = &(env->getServerMap()); std::map modified_blocks; @@ -154,9 +161,8 @@ int LuaVoxelManip::l_set_node_at(lua_State *L) v3s16 pos = check_v3s16(L, 2); MapNode n = readnode(L, 3); - o->vm->setNodeNoEmerge(pos, n); - - return 0; + lua_pushboolean(L, o->vm->setNodeNoEmerge(pos, n)); + return 1; } int LuaVoxelManip::l_update_liquids(lua_State *L) @@ -193,8 +199,8 @@ int LuaVoxelManip::l_set_lighting(lua_State *L) { LuaVoxelManip *o = checkObject(L, 1); if (!o->is_mapgen_vm) { - warningstream << "VoxelManip:set_lighting called for a non-mapgen " - "VoxelManip object" << std::endl; + log_deprecated(L, "set_lighting called for a non-mapgen " + "VoxelManip object"); return 0; } diff --git a/src/script/scripting_emerge.cpp b/src/script/scripting_emerge.cpp new file mode 100644 index 000000000..3467b1495 --- /dev/null +++ b/src/script/scripting_emerge.cpp @@ -0,0 +1,93 @@ +/* +Minetest +Copyright (C) 2022 sfan5 + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "scripting_emerge.h" +#include "emerge_internal.h" +#include "server.h" +#include "settings.h" +#include "cpp_api/s_internal.h" +#include "common/c_packer.h" +#include "lua_api/l_areastore.h" +#include "lua_api/l_base.h" +#include "lua_api/l_craft.h" +#include "lua_api/l_env.h" +#include "lua_api/l_item.h" +#include "lua_api/l_itemstackmeta.h" +#include "lua_api/l_mapgen.h" +#include "lua_api/l_noise.h" +#include "lua_api/l_server.h" +#include "lua_api/l_util.h" +#include "lua_api/l_vmanip.h" +#include "lua_api/l_settings.h" + +extern "C" { +#include +} + +EmergeScripting::EmergeScripting(EmergeThread *parent): + ScriptApiBase(ScriptingType::Emerge) +{ + setGameDef(parent->m_server); + setEmergeThread(parent); + + SCRIPTAPI_PRECHECKHEADER + + if (g_settings->getBool("secure.enable_security")) + initializeSecurity(); + + lua_getglobal(L, "core"); + int top = lua_gettop(L); + + InitializeModApi(L, top); + + auto *data = ModApiBase::getServer(L)->m_lua_globals_data.get(); + assert(data); + script_unpack(L, data); + lua_setfield(L, top, "transferred_globals"); + + lua_pop(L, 1); + + // Push builtin initialization type + lua_pushstring(L, "emerge"); + lua_setglobal(L, "INIT"); +} + +void EmergeScripting::InitializeModApi(lua_State *L, int top) +{ + // Register reference classes (userdata) + ItemStackMetaRef::Register(L); + LuaAreaStore::Register(L); + LuaItemStack::Register(L); + LuaPerlinNoise::Register(L); + LuaPerlinNoiseMap::Register(L); + LuaPseudoRandom::Register(L); + LuaPcgRandom::Register(L); + LuaSecureRandom::Register(L); + LuaVoxelManip::Register(L); + LuaSettings::Register(L); + + // Initialize mod api modules + ModApiCraft::InitializeAsync(L, top); + ModApiEnvVM::InitializeEmerge(L, top); + ModApiItem::InitializeAsync(L, top); + ModApiMapgen::InitializeEmerge(L, top); + ModApiServer::InitializeAsync(L, top); + ModApiUtil::InitializeAsync(L, top); + // TODO ^ these should also be renamed to InitializeRO or such +} diff --git a/src/script/scripting_emerge.h b/src/script/scripting_emerge.h new file mode 100644 index 000000000..713dda7c2 --- /dev/null +++ b/src/script/scripting_emerge.h @@ -0,0 +1,37 @@ +/* +Minetest +Copyright (C) 2022 sfan5 + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once +#include "cpp_api/s_base.h" +#include "cpp_api/s_mapgen.h" +#include "cpp_api/s_security.h" + +class EmergeThread; + +class EmergeScripting: + virtual public ScriptApiBase, + public ScriptApiMapgen, + public ScriptApiSecurity +{ +public: + EmergeScripting(EmergeThread *parent); + +private: + void InitializeModApi(lua_State *L, int top); +}; diff --git a/src/server.h b/src/server.h index cc7bdc2f7..5ee85d0e6 100644 --- a/src/server.h +++ b/src/server.h @@ -418,6 +418,8 @@ public: // Lua files registered for init of async env, pair of modname + path std::vector> m_async_init_files; + // Identical but for mapgen env + std::vector> m_mapgen_init_files; // Data transferred into other Lua envs at init time std::unique_ptr m_lua_globals_data;