mirror of
https://files.creativekara.fr/git/poschangelib.git
synced 2024-11-19 21:53:52 +01:00
212 lines
6.5 KiB
Lua
212 lines
6.5 KiB
Lua
poschangelib = {}
|
|
|
|
--[[
|
|
-- File table of contents
|
|
-- 1. Settings and utilities
|
|
-- 2. Player position listener functions
|
|
-- 3. On walk listener functions
|
|
-- 4. Tools for main loop
|
|
-- 5. Main loop
|
|
--]]
|
|
|
|
function poschangelib.setting_check_interval()
|
|
return tonumber(minetest.setting_get('poschangelib.check_interval')) or 0.3
|
|
end
|
|
|
|
--- Table of already called listeners in main loop to prevent triggering them
|
|
-- more than once per loop (player) if they are registered for more than one event
|
|
-- (for example triggered on walk on multiple groups)
|
|
local triggered_listeners = {}
|
|
|
|
--- Internal utility to create an empty table on first registration.
|
|
-- @param mothertable The main table that will hold other tables.
|
|
-- @param item Key in the main table that should hold a table.
|
|
-- @return The table in mothertable.item, created if nil.
|
|
local function get_subtable_or_create(mothertable, item)
|
|
if mothertable.item == nil then
|
|
mothertable.item = {}
|
|
end
|
|
return mothertable.item
|
|
end
|
|
|
|
--- Check if a listener can be triggered
|
|
local function is_callable(name)
|
|
-- Check if not aleady called
|
|
if triggered_listeners.name then return false end
|
|
-- Other checks will come here when required
|
|
return true
|
|
end
|
|
|
|
|
|
--[[
|
|
-- Player position listeners
|
|
--]]
|
|
|
|
--- Callback table filled with add_player_pos_listener.
|
|
local player_listeners = {}
|
|
|
|
--- Register a callback that will be called everytime a player moves.
|
|
-- @param name Unique name of the callback. Used to remove.
|
|
-- @param callback Callback function. Take <player>, <old_pos>, <new_pos> arguments.
|
|
-- The first call will have <old_pos> set to nil.
|
|
function poschangelib.add_player_pos_listener(name, callback)
|
|
if player_listeners[name] then
|
|
minetest.log('error', 'Player pos listener ' .. name .. ' is already registered')
|
|
return
|
|
end
|
|
player_listeners[name] = callback
|
|
end
|
|
|
|
--- Remove a registered callback. It won't be called anymore.
|
|
function poschangelib.remove_player_pos_listener(name)
|
|
if player_listeners[name] then player_listeners[name] = nil end
|
|
end
|
|
|
|
--- Trigger registered callbacks if not already triggered.
|
|
-- Reset triggered_listeners to be able to recall the callback.
|
|
local function trigger_player_position_listeners(player, old_pos, pos)
|
|
for name, callback in pairs(player_listeners) do
|
|
if is_callable(name) then
|
|
callback(player, old_pos, pos)
|
|
triggered_listeners[name] = true
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
--[[
|
|
-- Walk listeners
|
|
--]]
|
|
|
|
--- Callback tables filled with add_player_walk_listener.
|
|
-- Indexed by node groups as "group:name" or node names.
|
|
local walk_listeners = {}
|
|
|
|
--- Register a callback that will be called everytime a player moves on a block.
|
|
-- @param callback Callback function. Takes <player>, <pos>, <node>,
|
|
-- <node definition> as arguments.
|
|
-- Node is the node below the player's position.
|
|
-- @param nodenames List of node names or group (with 'group:X') to observe.
|
|
-- The callback will be triggered only if the block has the same name or
|
|
-- has one of these groups.
|
|
function poschangelib.add_player_walk_listener(name, callback, nodenames)
|
|
for _, nodename in ipairs(nodenames) do
|
|
if not walk_listeners[nodename] then walk_listeners[nodename] = {} end
|
|
if walk_listeners[nodename][name] then
|
|
minetest.log('error', 'Walk listener ' .. name .. ' is already registered')
|
|
end
|
|
walk_listeners[nodename][name] = callback
|
|
end
|
|
end
|
|
|
|
function poschangelib.remove_player_walk_listener(name, nodenames)
|
|
local counts = {}
|
|
for _, nodename in ipairs(nodenames) do
|
|
if not counts[nodename] then counts[nodename] = 0 end
|
|
counts[nodename] = counts[nodename] + 1
|
|
if walk_listeners[nodename] and walk_listeners[nodename][name] then
|
|
walk_listeners[nodename][name] = nil
|
|
counts[nodename] = counts[nodename] - 1
|
|
end
|
|
end
|
|
-- If no listener left for the group, remove the group
|
|
-- to be able to skip node check if there are none left
|
|
for _, nodename in pairs(counts) do
|
|
if counts[nodename] == 0 then
|
|
walk_listeners[nodename] = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
local function trigger_player_walk_listeners(player, pos, node, node_def, nodename)
|
|
for name, callback in pairs(walk_listeners[nodename]) do
|
|
if is_callable(name) then
|
|
callback(player, pos, node, node_def)
|
|
triggered_listeners[name] = true
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
--[[
|
|
-- Tools for main loop
|
|
--]]
|
|
|
|
--- Table of last rounded registered position of each players.
|
|
local player_last_pos = {}
|
|
local function remove_last_pos_on_leave(player)
|
|
player_last_pos[player:get_player_name()] = nil
|
|
end
|
|
minetest.register_on_leaveplayer(remove_last_pos_on_leave)
|
|
--- Check if position has changed for the player.
|
|
-- @param player The player object.
|
|
-- @returns {old_pos, new_pos} if the position has changed, nil otherwise
|
|
local function get_updated_positions(player)
|
|
local pos = vector.round(player:getpos())
|
|
local old_pos = player_last_pos[player:get_player_name()]
|
|
local ret = nil
|
|
if old_pos == nil then
|
|
-- Position of the player was set
|
|
ret = {old = old_pos, new = pos}
|
|
elseif pos then
|
|
-- Check for position change
|
|
if not vector.equals(old_pos, pos) then
|
|
ret = {old = old_pos, new = pos}
|
|
end
|
|
end
|
|
player_last_pos[player:get_player_name()] = pos
|
|
return ret
|
|
end
|
|
|
|
--- Check and call on_walk triggers if required.
|
|
local function check_on_walk_triggers(player, pos)
|
|
local pos_below = vector.new(pos.x, pos.y - 1, pos.z)
|
|
local node_below = minetest.get_node(pos_below)
|
|
local node_def = minetest.registered_nodes[node_below.name]
|
|
-- Trigger by node name
|
|
if walk_listeners[node_below.name] then
|
|
trigger_player_walk_listeners(player, pos_below, node_below, node_def, node_below.name)
|
|
end
|
|
-- Trigger by group
|
|
local groups_below = node_def.groups
|
|
if groups_below then
|
|
for group, level in pairs(groups_below) do
|
|
local group_name = 'group:' .. group
|
|
if level > 0 and walk_listeners[group_name] then
|
|
trigger_player_walk_listeners(player, pos_below, node_below, node_def, group_name)
|
|
end
|
|
end
|
|
end
|
|
-- Trigger _on_walk
|
|
if node_def._on_walk then
|
|
node_def._on_walk(pos_below, node_below, player)
|
|
elseif node_below.on_walk then
|
|
node_def.on_walk(pos_below, node_below, player)
|
|
end
|
|
end
|
|
|
|
--[[
|
|
-- Main loop
|
|
--]]
|
|
|
|
local timer = 0
|
|
local function loop(dtime)
|
|
timer = timer + dtime
|
|
-- Wait loop
|
|
if timer + dtime < poschangelib.setting_check_interval() then return end
|
|
timer = 0
|
|
-- Player checks
|
|
for _, player in ipairs(minetest.get_connected_players()) do
|
|
local poss = get_updated_positions(player)
|
|
if poss then
|
|
trigger_player_position_listeners(player, poss.old, poss.new)
|
|
if poss.old then -- Don't trigger on join
|
|
check_on_walk_triggers(player, poss.new)
|
|
end
|
|
-- Reset the triggered listener to allow the next player to trigger them
|
|
triggered_listeners = {}
|
|
end
|
|
end
|
|
end
|
|
minetest.register_globalstep(loop)
|