poschangelib/init.lua
2017-07-25 20:15:53 +02:00

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)