diff --git a/builtin/game/register.lua b/builtin/game/register.lua index 3e4b9be968ac7..b41fd0c3e55af 100644 --- a/builtin/game/register.lua +++ b/builtin/game/register.lua @@ -91,7 +91,14 @@ local function check_node_list(list, field) end function core.register_abm(spec) + if core.is_mods_loaded then + core.log("error", "Function register_abm cannot be called after mods are loaded.") + return + end -- Add to core.registered_abms + if spec.name then + check_modname_prefix(spec.name) + end check_node_list(spec.nodenames, "nodenames") check_node_list(spec.neighbors, "neighbors") assert(type(spec.action) == "function", "Required field 'action' of type function") @@ -99,7 +106,31 @@ function core.register_abm(spec) spec.mod_origin = core.get_current_modname() or "??" end +function core.override_abm(name, redefinition) + if core.is_mods_loaded then + core.log("error", "Function override_abm cannot be called after mods are loaded.") + return + end + -- Override abm in core.registered_abms + if redefinition.name ~= nil then + error("Attempt to redefine abm name of "..name.." to "..dump(redefinition.name), 2) + end + check_node_list(redefinition.nodenames, "nodenames") + for _, abm in pairs(core.registered_abms) do + if (abm.name == name) then + for k, v in pairs(redefinition) do + rawset(abm, k, v) + end + return + end + end +end + function core.register_lbm(spec) + if core.is_mods_loaded then + core.log("error", "Function register_lbm cannot be called after mods are loaded.") + return + end -- Add to core.registered_lbms check_modname_prefix(spec.name) check_node_list(spec.nodenames, "nodenames") @@ -113,7 +144,32 @@ function core.register_lbm(spec) spec.mod_origin = core.get_current_modname() or "??" end +function core.override_lbm(name, redefinition) + if core.is_mods_loaded then + core.log("error", "Function override_lbm cannot be called after mods are loaded.") + return + end + -- Override lbm in core.registered_lbms + if redefinition.name ~= nil then + error("Attempt to redefine lbm name of "..name.." to "..dump(redefinition.name), 2) + end + check_node_list(redefinition.nodenames, "nodenames") + assert(type(redefinition.action) == "function", "Required field 'action' of type function") + for _, lbm in pairs(core.registered_lbms) do + if (lbm.name == name) then + for k, v in pairs(redefinition) do + rawset(lbm, k, v) + end + return + end + end +end + function core.register_entity(name, prototype) + if core.is_mods_loaded then + core.log("error", "Function register_entity cannot be called after mods are loaded.") + return + end -- Check name if name == nil then error("Unable to register entity: Name is nil") @@ -128,7 +184,35 @@ function core.register_entity(name, prototype) prototype.mod_origin = core.get_current_modname() or "??" end +function core.override_entity(name, redefinition) + if core.is_mods_loaded then + core.log("error", "Function override_entity cannot be called after mods are loaded.") + return + end + if redefinition.name ~= nil then + error("Attempt to redefine entity name of "..name.." to "..dump(redefinition.name), 2) + end + local entity = core.registered_entities[name] + if not entity then + error("Attempt to override non-existent entity "..name, 2) + end + for k, v in pairs(redefinition) do + if k ~= "initial_properties" then + rawset(entity, k, v) + else + for k2, v2 in pairs(v) do + rawset(entity.initial_properties, k2, v2) + end + end + end + core.registered_entities[name] = entity +end + function core.register_item(name, itemdef) + if core.is_mods_loaded then + core.log("error", "Function register_item cannot be called after mods are loaded.") + return + end -- Check name if name == nil then error("Unable to register item: Name is nil") @@ -204,6 +288,10 @@ function core.register_item(name, itemdef) end function core.unregister_item(name) + if core.is_mods_loaded then + core.log("error", "Function unregister_item cannot be called after mods are loaded.") + return + end if not core.registered_items[name] then core.log("warning", "Not unregistering item " ..name.. " because it doesn't exist.") @@ -225,11 +313,19 @@ function core.unregister_item(name) end function core.register_node(name, nodedef) + if core.is_mods_loaded then + core.log("error", "Function register_node cannot be called after mods are loaded.") + return + end nodedef.type = "node" core.register_item(name, nodedef) end function core.register_craftitem(name, craftitemdef) + if core.is_mods_loaded then + core.log("error", "Function register_craftitem cannot be called after mods are loaded.") + return + end craftitemdef.type = "craft" -- BEGIN Legacy stuff @@ -242,6 +338,10 @@ function core.register_craftitem(name, craftitemdef) end function core.register_tool(name, tooldef) + if core.is_mods_loaded then + core.log("error", "Function register_tool cannot be called after mods are loaded.") + return + end tooldef.type = "tool" tooldef.stack_max = 1 @@ -293,6 +393,10 @@ function core.register_tool(name, tooldef) end function core.register_alias(name, convert_to) + if core.is_mods_loaded then + core.log("error", "Function register_alias cannot be called after mods are loaded.") + return + end if forbidden_item_names[name] then error("Unable to register alias: Name is forbidden: " .. name) end @@ -307,6 +411,10 @@ function core.register_alias(name, convert_to) end function core.register_alias_force(name, convert_to) + if core.is_mods_loaded then + core.log("error", "Function register_alias_force cannot be called after mods are loaded.") + return + end if forbidden_item_names[name] then error("Unable to register alias: Name is forbidden: " .. name) end diff --git a/doc/lua_api.md b/doc/lua_api.md index 6db358c85c5e5..19d1b9c32e038 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -5851,8 +5851,11 @@ Call these functions only at load time! `name` from `core.registered_items` and from the associated item table according to its nature: `core.registered_nodes`, etc. * `core.register_entity(name, entity definition)` +* `core.override_entity(name, entity definition)` * `core.register_abm(abm definition)` +* `core.override_abm(name, abm definition)` * `core.register_lbm(lbm definition)` +* `core.override_lbm(name, lbm definition)` * `core.register_alias(alias, original_name)` * Also use this to set the 'mapgen aliases' needed in a game for the core mapgens. See [Mapgen aliases] section above. @@ -9276,7 +9279,7 @@ Player properties need to be saved manually. Entity definition ----------------- -Used by `core.register_entity`. +Used by `core.register_entity` and `core.override_entity`. The entity definition table becomes a metatable of a newly created per-entity luaentity table, meaning its fields (e.g. `initial_properties`) will be shared between all instances of an entity. @@ -9314,7 +9317,7 @@ between all instances of an entity. ABM (ActiveBlockModifier) definition ------------------------------------ -Used by `core.register_abm`. +Used by `core.register_abm` and `core.override_abm`. ```lua { @@ -9322,6 +9325,10 @@ Used by `core.register_abm`. -- Descriptive label for profiling purposes (optional). -- Definitions with identical labels will be listed as one. + name = "modname:replace_legacy_door", + -- Optional filed, required for make ABM overridable. + -- Identifier of the ABM, should follow the modname: convention. + nodenames = {"default:lava_source"}, -- Apply `action` function to these nodes. -- `group:groupname` can also be used here. @@ -9368,7 +9375,7 @@ Used by `core.register_abm`. LBM (LoadingBlockModifier) definition ------------------------------------- -Used by `core.register_lbm`. +Used by `core.register_lbm` and `core.override_lbm`. A loading block modifier (LBM) is used to define a function that is called for specific nodes (defined by `nodenames`) when a mapblock which contains such nodes @@ -9386,7 +9393,8 @@ contain a matching node. -- Definitions with identical labels will be listed as one. name = "modname:replace_legacy_door", - -- Identifier of the LBM, should follow the modname: convention + -- Identifier of the LBM, should follow the modname: convention. + -- Also used for overriding LBM. nodenames = {"default:lava_source"}, -- List of node names to trigger the LBM on. diff --git a/games/devtest/mods/testabms/after_node.lua b/games/devtest/mods/testabms/after_node.lua index fa2b3ab1600ca..d0d9d8866a345 100644 --- a/games/devtest/mods/testabms/after_node.lua +++ b/games/devtest/mods/testabms/after_node.lua @@ -9,4 +9,3 @@ core.register_node("testabms:after_abm", { groups = { dig_immediate = 3 }, }) - diff --git a/games/devtest/mods/testabms/chances.lua b/games/devtest/mods/testabms/chances.lua index a84e75260fcae..06f22605aca9b 100644 --- a/games/devtest/mods/testabms/chances.lua +++ b/games/devtest/mods/testabms/chances.lua @@ -53,4 +53,3 @@ core.register_abm({ meta:set_string("infotext", "ABM testabsm:chance_20 changed this node.") end }) - diff --git a/games/devtest/mods/testabms/init.lua b/games/devtest/mods/testabms/init.lua index 8bf4975cf9738..0926f6498cda0 100644 --- a/games/devtest/mods/testabms/init.lua +++ b/games/devtest/mods/testabms/init.lua @@ -5,3 +5,4 @@ dofile(path.."/chances.lua") dofile(path.."/intervals.lua") dofile(path.."/min_max.lua") dofile(path.."/neighbors.lua") +dofile(path.."/override.lua") diff --git a/games/devtest/mods/testabms/intervals.lua b/games/devtest/mods/testabms/intervals.lua index 928406508b160..8f432f21d3352 100644 --- a/games/devtest/mods/testabms/intervals.lua +++ b/games/devtest/mods/testabms/intervals.lua @@ -53,4 +53,3 @@ core.register_abm({ meta:set_string("infotext", "ABM testabsm:interval_60 changed this node.") end }) - diff --git a/games/devtest/mods/testabms/min_max.lua b/games/devtest/mods/testabms/min_max.lua index b5df4e40ec00d..7b26b08d9d48e 100644 --- a/games/devtest/mods/testabms/min_max.lua +++ b/games/devtest/mods/testabms/min_max.lua @@ -55,4 +55,3 @@ core.register_abm({ meta:set_string("infotext", "ABM testabsm:max_y changed this node.") end }) - diff --git a/games/devtest/mods/testabms/neighbors.lua b/games/devtest/mods/testabms/neighbors.lua index 0ce21c23cec2f..ca79edfc2b52b 100644 --- a/games/devtest/mods/testabms/neighbors.lua +++ b/games/devtest/mods/testabms/neighbors.lua @@ -96,4 +96,3 @@ core.register_abm({ "ABM testabsm:required_missing_neighbor changed this node.") end }) - diff --git a/games/devtest/mods/testabms/override.lua b/games/devtest/mods/testabms/override.lua new file mode 100644 index 0000000000000..6c2fe0918e6af --- /dev/null +++ b/games/devtest/mods/testabms/override.lua @@ -0,0 +1,40 @@ +-- test ABMs with override + +local S = core.get_translator("testabms") + +-- ABM override +core.register_node("testabms:override", { + description = S("Node for test ABM override"), + drawtype = "normal", + tiles = { "testabms_wait_node.png" }, + + groups = { dig_immediate = 3 }, + + on_construct = function (pos) + local meta = core.get_meta(pos) + meta:set_string("infotext", "Waiting for ABM testabms:overrid") + end, +}) + +core.register_abm({ + label = "testabms:override", + name = "testabms:override", + nodenames = "testabms:override", + interval = 1000, + chance = 5000, + action = function (pos) + core.swap_node(pos, {name="testabms:after_abm"}) + local meta = core.get_meta(pos) + meta:set_string("infotext", "ABM testabms:override changed this node.") + end + }) + +core.override_abm("testabms:override", { + interval = 1, + chance = 1, + action = function (pos) + core.swap_node(pos, {name="testabms:after_abm"}) + local meta = core.get_meta(pos) + meta:set_string("infotext", "Override ABM testabms:override changed this node.") + end + }) diff --git a/games/devtest/mods/testlbms/README.md b/games/devtest/mods/testlbms/README.md new file mode 100644 index 0000000000000..0be7847bb882e --- /dev/null +++ b/games/devtest/mods/testlbms/README.md @@ -0,0 +1,5 @@ +# Test LBMs + +This mod contains a nodes and related LBM actions. +By placing these nodes, you can test basic LBM behaviours. + diff --git a/games/devtest/mods/testlbms/init.lua b/games/devtest/mods/testlbms/init.lua new file mode 100644 index 0000000000000..06d11d7fd2ee2 --- /dev/null +++ b/games/devtest/mods/testlbms/init.lua @@ -0,0 +1,45 @@ +local S = minetest.get_translator("testlbms") + +-- After LBM node +minetest.register_node("testlbms:after_lbm", { + description = S("After LBM processed node."), + drawtype = "normal", + tiles = { "testlbms_after_node.png" }, + + groups = { dig_immediate = 3 }, +}) + +-- LBM onload change +minetest.register_node("testlbms:onload_change", { + description = S("Node for test LBM"), + drawtype = "normal", + tiles = { "testlbms_wait_node.png" }, + + groups = { dig_immediate = 3 }, + + on_construct = function (pos) + local meta = minetest.get_meta(pos) + meta:set_string("infotext", "Waiting for LBM testlbms:chance_5") + end, +}) + +minetest.register_lbm({ + label = "testlbms:onload_change", + name = "testlbms:onload_change", + nodenames = "testlbms:onload_change", + run_at_every_load = true, + action = function (pos) + minetest.swap_node(pos, {name="testlbms:after_lbm"}) + local meta = minetest.get_meta(pos) + meta:set_string("infotext", "LBM testlbms:onload_change changed this node.") + end + }) + +minetest.override_lbm("testlbms:onload_change", { + action = function (pos) + minetest.swap_node(pos, {name="testlbms:after_lbm"}) + local meta = minetest.get_meta(pos) + meta:set_string("infotext", "Override LBM testlbms:onload_change changed this node.") + end, + }) + diff --git a/games/devtest/mods/testlbms/mod.conf b/games/devtest/mods/testlbms/mod.conf new file mode 100644 index 0000000000000..b94f5e1301e63 --- /dev/null +++ b/games/devtest/mods/testlbms/mod.conf @@ -0,0 +1,2 @@ +name = testlbms +description = Contains some nodes for test LBMs. diff --git a/games/devtest/mods/testlbms/textures/testlbms_after_node.png b/games/devtest/mods/testlbms/textures/testlbms_after_node.png new file mode 100644 index 0000000000000..9bdb1987d979a Binary files /dev/null and b/games/devtest/mods/testlbms/textures/testlbms_after_node.png differ diff --git a/games/devtest/mods/testlbms/textures/testlbms_wait_node.png b/games/devtest/mods/testlbms/textures/testlbms_wait_node.png new file mode 100644 index 0000000000000..3d26f0c07523b Binary files /dev/null and b/games/devtest/mods/testlbms/textures/testlbms_wait_node.png differ diff --git a/games/devtest/mods/unittests/entity.lua b/games/devtest/mods/unittests/entity.lua index af91a2a943544..0848225d46864 100644 --- a/games/devtest/mods/unittests/entity.lua +++ b/games/devtest/mods/unittests/entity.lua @@ -13,7 +13,7 @@ end core.register_entity("unittests:callbacks", { initial_properties = { - hp_max = 5, + hp_max = 1, visual = "upright_sprite", textures = { "unittests_callback.png" }, static_save = false, @@ -76,6 +76,13 @@ core.register_entity("unittests:callbacks", { end, }) +core.override_entity("unittests:callbacks", { + initial_properties = { + hp_max = 5, + }, + _lua_hp_max = 6, + }) + -- local function check_log(expect) @@ -234,3 +241,21 @@ local function test_get_bone_rot(_, pos) end end unittests.register("test_get_bone_rot", test_get_bone_rot, {map=true}) + +unittests.register("test_entity_override", function(_, pos) + log = {} + + local obj = core.add_entity(pos, "unittests:callbacks") + check_log({"on_activate(0)"}) + + -- check properties + local props = obj:get_properties() + assert(props.hp_max == 5) + assert(props.visual == "upright_sprite") + + -- check entity + local lua = obj:get_luaentity() + assert(lua._lua_hp_max == 6) + + obj:remove() +end, {map=true}) diff --git a/src/script/cpp_api/s_client.cpp b/src/script/cpp_api/s_client.cpp index 772bc24121af1..2c3c7e58cdaa9 100644 --- a/src/script/cpp_api/s_client.cpp +++ b/src/script/cpp_api/s_client.cpp @@ -25,6 +25,10 @@ void ScriptApiClient::on_mods_loaded() } catch (LuaError &e) { getClient()->setFatalError(e); } + // set is_mods_loaded + lua_getglobal(L, "core"); + lua_pushboolean(L, true); + lua_setfield(L, -2, "is_mods_loaded"); } void ScriptApiClient::on_shutdown() diff --git a/src/script/cpp_api/s_server.cpp b/src/script/cpp_api/s_server.cpp index faacf97141f7a..933ded7367496 100644 --- a/src/script/cpp_api/s_server.cpp +++ b/src/script/cpp_api/s_server.cpp @@ -147,6 +147,10 @@ void ScriptApiServer::on_mods_loaded() lua_getfield(L, -1, "registered_on_mods_loaded"); // Call callbacks runCallbacks(0, RUN_CALLBACKS_MODE_FIRST); + // set is_mods_loaded + lua_getglobal(L, "core"); + lua_pushboolean(L, true); + lua_setfield(L, -2, "is_mods_loaded"); } void ScriptApiServer::on_shutdown() diff --git a/src/unittest/test_servermodmanager.cpp b/src/unittest/test_servermodmanager.cpp index 500d0f2884950..65a33f027b84f 100644 --- a/src/unittest/test_servermodmanager.cpp +++ b/src/unittest/test_servermodmanager.cpp @@ -107,7 +107,7 @@ void TestServerModManager::testGetMods() ServerModManager sm(m_worlddir); const auto &mods = sm.getMods(); // `ls ./games/devtest/mods | wc -l` + 1 (test mod) - UASSERTEQ(std::size_t, mods.size(), 34 + 1); + UASSERTEQ(std::size_t, mods.size(), 35 + 1); // Ensure we found basenodes mod (part of devtest) // and test_mod (for testing MINETEST_MOD_PATH).