Version 0.1

This commit is contained in:
Karamel 2017-07-25 20:15:53 +02:00
commit 39b6acf793
5 changed files with 357 additions and 0 deletions

14
LICENSE Normal file

@ -0,0 +1,14 @@
License of source code
GNU Lesser General Public License, version 2.1
Copyright (C) 2017 Karamel <karamel@creativekara.fr>
With knowledge from various Minetest developers, modders and documenters
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:
https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html

129
README.txt Normal file

@ -0,0 +1,129 @@
Minetest mod library: poschangelib
==================================
version 0.1
See LICENSE for license information
This lib adds utilities to watch player movements and trigger things when they are
spotted moving.
It does nothing by itself but aim to ease event based upon players or item
moving.
All positions are rounded to node position (integer coordinates).
Summary
- General warning
- Watch players' movements
- Watch players walking on particular nodes
- Add _on_walk on nodes
- Configuration/Performances tweaking
General warning
---------------
This mod may be resources consuming. The mods relying upon this lib should use
small functions not to decrease the server performances too much.
The more functions are provided, the more the server can lag (but probably a little
less than running every of them without the lib).
Watch player's movements
------------------------
Use poschangelib.add_player_pos_listener(name, my_callback)
Name is the identifier the listener, to use in remove_player_pos_listener. You should
follow the naming convention like for node names. See http://dev.minetest.net/Intro
The my_callback is a function that takes 3 arguments: the player, last known position
and new position.
On first call (once a player joins) the last known position will be nil. If your
listener does something in that case, it will be called shortly after the player
reconnects. It may so be triggered twice from the same position, before leaving and
after joining.
Be aware that the new position may not always be a neigrbor of the old one.
When on teleporting, programatic moves with setpos or moving fast it may be far away.
Quick code sample:
local function my_callback(player, old_pos, new_pos)
if old_pos == nil then
minetest.chat_send_player(player:get_player_name(), 'Welcome to the world!')
else
minetest.chat_send_player(player:get_player_name(),
"You are now at x:" .. new_pos.x .. ", y:" .. new_pos.y ..
"z:" .. new_pos.z)
end
end
poschangelib.add_player_pos_listener("sample:pos_listener", my_callback)
Watch player walking on particular nodes
----------------------------------------
Use poschangelib.add_player_walk_listener(name, my_callback, nodenames)
The name is used in the same way as for player position listeners. It aims at reducing
the number of time the stepped node is fetched to share it accross all listeners.
The callback is a function that takes 4 arguments: the player, the position, the node
stepped on and that node description.
See http://dev.minetest.net/minetest.register_node for node description.
You can register the listener for a list of node name or groups, in the same way you
do it to register an ABM. See http://dev.minetest.net/register_abm
For example:
local function flop(player, pos, node, desc)
minetest.chat_send_player(player:get_player_name(), 'Flop flop')
end
poschangelib.add_player_walk_listener('sample:flop', flop, {'default:dirt_with_grass'})
local function toptop(player, pos, node, desc)
minetest.chat_send_player(player:get_player_name(), 'Top top top')
end
poschangelib.add_player_walk_listener('sample:top', toptop, {'group:choppy'})
Add _on_walk_over to nodes
-------------------------
This behaviour is ported from the walkover mod only for compatibility.
https://forum.minetest.net/viewtopic.php?f=9&t=15991
A new node property can be added in node definitions:
_on_walk_over = <function>
This function takes the position, the node and the player as argument.
For compatibility with walkover, you can use on_walk_over (without the underscore
prefix) but it is discouraged as stated in the forum post. This support may be dropped
at any time when most mods have updated the name.
A performance tweak disable on walk checks if there is no player walk listener.
To be sure the check is done if you are only using _on_walk_over, you may register a
dummy listener on a unknown node name.
Configuration/Performances tweaking
-----------------------------------
The lib checks for position at a given interval. Default is every 0.3 seconds.
This can be changed by setting poschangelib.check_interval in minetest.conf
or in advanced settings.
Setting a lower value will make the lib more accurate but will be more demanding
on resources (down to 0.05 which is a every server tick).
If the server is lagging, try increasing the interval. If the server can afford
more precise checks you can decrease the value.

0
depends.txt Normal file

211
init.lua Normal file

@ -0,0 +1,211 @@
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)

3
settingtypes.txt Normal file

@ -0,0 +1,3 @@
# Interval in seconds between position checking
# The lesser it is, the more accurate it is but also the more resources demanding.
poschangelib.check_interval (Check interval) float 0.3 0.05