-- Misc. API functions
--
+-- This must match the implementation in src/script/common/c_converter.h
function core.hash_node_position(pos)
- return (pos.z + 32768) * 65536 * 65536
- + (pos.y + 32768) * 65536
- + pos.x + 32768
+ return (pos.z + 0x8000) * 0x100000000 + (pos.y + 0x8000) * 0x10000 + (pos.x + 0x8000)
end
core.registered_allow_player_inventory_actions, core.register_allow_player_inventory_action = make_registration()
core.registered_on_rightclickplayers, core.register_on_rightclickplayer = make_registration()
core.registered_on_liquid_transformed, core.register_on_liquid_transformed = make_registration()
+core.registered_on_mapblocks_changed, core.register_on_mapblocks_changed = make_registration()
+
+core.register_on_mods_loaded(function()
+ core.after(0, function()
+ setmetatable(core.registered_on_mapblocks_changed, {
+ __newindex = function()
+ error("on_mapblocks_changed callbacks must be registered at load time")
+ end,
+ })
+ end)
+end)
--
-- Compatibility for on_mapgen_init()
register_on_item_eat = 0,
register_on_punchplayer = 0,
register_on_player_hpchange = 0,
+ register_on_mapblocks_changed = 0,
}
---
* `pos_list` is an array of all modified positions.
* `node_list` is an array of the old node that was previously at the position
with the corresponding index in pos_list.
+* `minetest.register_on_mapblocks_changed(function(modified_blocks, modified_block_count))`
+ * Called soon after any nodes or node metadata have been modified. No
+ modifications will be missed, but there may be false positives.
+ * Will never be called more than once per server step.
+ * `modified_blocks` is the set of modified mapblock position hashes. These
+ are in the same format as those produced by `minetest.hash_node_position`,
+ and can be converted to positions with `minetest.get_position_from_hash`.
+ The set is a table where the keys are hashes and the values are `true`.
+ * `modified_block_count` is the number of entries in the set.
+ * Note: callbacks must be registered at mod load time.
Setting-related
---------------
minetest.emerge_area(max_edge, max_edge:add(1), emerge_block, max_finished)
end
unittests.register("test_mapgen_edges", test_mapgen_edges, {map=true, async=true})
+
+local finish_test_on_mapblocks_changed
+minetest.register_on_mapblocks_changed(function(modified_blocks, modified_block_count)
+ if finish_test_on_mapblocks_changed then
+ finish_test_on_mapblocks_changed(modified_blocks, modified_block_count)
+ finish_test_on_mapblocks_changed = nil
+ end
+end)
+local function test_on_mapblocks_changed(cb, player, pos)
+ local bp1 = (pos / minetest.MAP_BLOCKSIZE):floor()
+ local bp2 = bp1:add(1)
+ for _, bp in ipairs({bp1, bp2}) do
+ -- Make a modification in the block.
+ local p = bp * minetest.MAP_BLOCKSIZE
+ minetest.load_area(p)
+ local meta = minetest.get_meta(p)
+ meta:set_int("test_on_mapblocks_changed", meta:get_int("test_on_mapblocks_changed") + 1)
+ end
+ finish_test_on_mapblocks_changed = function(modified_blocks, modified_block_count)
+ if modified_block_count < 2 then
+ return cb("Expected at least two mapblocks to be recorded as modified")
+ end
+ if not modified_blocks[minetest.hash_node_position(bp1)] or
+ not modified_blocks[minetest.hash_node_position(bp2)] then
+ return cb("The expected mapblocks were not recorded as modified")
+ end
+ cb()
+ end
+end
+unittests.register("test_on_mapblocks_changed", test_on_mapblocks_changed, {map=true, async=true})
size_t write_array_slice_float(lua_State *L, int table_index, float *data,
v3u16 data_size, v3u16 slice_offset, v3u16 slice_size);
+
+// This must match the implementation in builtin/game/misc_s.lua
+// Note that this returns a floating point result as Lua integers are 32-bit
+inline lua_Number hash_node_position(v3s16 pos)
+{
+ return (((s64)pos.Z + 0x8000L) << 32)
+ | (((s64)pos.Y + 0x8000L) << 16)
+ | ((s64)pos.X + 0x8000L);
+}
runCallbacks(2, RUN_CALLBACKS_MODE_FIRST);
}
+
+void ScriptApiEnv::on_mapblocks_changed(const std::unordered_set<v3s16> &set)
+{
+ SCRIPTAPI_PRECHECKHEADER
+
+ // Get core.registered_on_mapblocks_changed
+ lua_getglobal(L, "core");
+ lua_getfield(L, -1, "registered_on_mapblocks_changed");
+ luaL_checktype(L, -1, LUA_TTABLE);
+ lua_remove(L, -2);
+
+ // Convert the set to a set of position hashes
+ lua_createtable(L, 0, set.size());
+ for(const v3s16 &p : set) {
+ lua_pushnumber(L, hash_node_position(p));
+ lua_pushboolean(L, true);
+ lua_rawset(L, -3);
+ }
+ lua_pushinteger(L, set.size());
+
+ runCallbacks(2, RUN_CALLBACKS_MODE_FIRST);
+}
+
+bool ScriptApiEnv::has_on_mapblocks_changed()
+{
+ SCRIPTAPI_PRECHECKHEADER
+
+ // Get core.registered_on_mapblocks_changed
+ lua_getglobal(L, "core");
+ lua_getfield(L, -1, "registered_on_mapblocks_changed");
+ luaL_checktype(L, -1, LUA_TTABLE);
+ return lua_objlen(L, -1) > 0;
+}
#include "cpp_api/s_base.h"
#include "irr_v3d.h"
#include "mapnode.h"
+#include <unordered_set>
#include <vector>
class ServerEnvironment;
// Called after liquid transform changes
void on_liquid_transformed(const std::vector<std::pair<v3s16, MapNode>> &list);
+ // Called after mapblock changes
+ void on_mapblocks_changed(const std::unordered_set<v3s16> &set);
+
+ // Determines whether there are any on_mapblocks_changed callbacks
+ bool has_on_mapblocks_changed();
+
void initializeEnvironment(ServerEnvironment *env);
};
m_list = std::move(newlist);
}
+/*
+ OnMapblocksChangedReceiver
+*/
+
+void OnMapblocksChangedReceiver::onMapEditEvent(const MapEditEvent &event)
+{
+ assert(receiving);
+ for (const v3s16 &p : event.modified_blocks) {
+ modified_blocks.insert(p);
+ }
+}
+
/*
ServerEnvironment
*/
m_player_database = openPlayerDatabase(player_backend_name, m_path_world, conf);
m_auth_database = openAuthDatabase(auth_backend_name, m_path_world, conf);
+
+ if (m_map && m_script->has_on_mapblocks_changed()) {
+ m_map->addEventReceiver(&m_on_mapblocks_changed_receiver);
+ m_on_mapblocks_changed_receiver.receiving = true;
+ }
}
ServerEnvironment::~ServerEnvironment()
// Send outdated detached inventories
m_server->sendDetachedInventories(PEER_ID_INEXISTENT, true);
+ // Notify mods of modified mapblocks
+ if (m_on_mapblocks_changed_receiver.receiving &&
+ !m_on_mapblocks_changed_receiver.modified_blocks.empty()) {
+ std::unordered_set<v3s16> modified_blocks;
+ std::swap(modified_blocks, m_on_mapblocks_changed_receiver.modified_blocks);
+ m_script->on_mapblocks_changed(modified_blocks);
+ }
+
const auto end_time = porting::getTimeUs();
m_step_time_counter->increment(end_time - start_time);
}
#include "activeobject.h"
#include "environment.h"
-#include "mapnode.h"
+#include "map.h"
#include "settings.h"
#include "server/activeobjectmgr.h"
#include "util/numeric.h"
#include <random>
class IGameDef;
-class ServerMap;
struct GameParams;
-class MapBlock;
class RemotePlayer;
class PlayerDatabase;
class AuthDatabase;
std::set<v3s16> m_forceloaded_list;
};
+/*
+ ServerEnvironment::m_on_mapblocks_changed_receiver
+*/
+struct OnMapblocksChangedReceiver : public MapEventReceiver {
+ std::unordered_set<v3s16> modified_blocks;
+ bool receiving = false;
+
+ void onMapEditEvent(const MapEditEvent &event) override;
+};
+
/*
Operation mode for ServerEnvironment::clearObjects()
*/
Server *m_server;
// Active Object Manager
server::ActiveObjectMgr m_ao_manager;
+ // on_mapblocks_changed map event receiver
+ OnMapblocksChangedReceiver m_on_mapblocks_changed_receiver;
// World path
const std::string m_path_world;
// Outgoing network message buffer for active objects