Try to fix walking on non-filled nodes. Version 0.2.

This commit is contained in:
Karamel 2017-07-31 23:49:01 +02:00
parent 39b6acf793
commit 1ba52f42d7
2 changed files with 90 additions and 19 deletions

@ -1,6 +1,6 @@
Minetest mod library: poschangelib
==================================
version 0.1
version 0.2
See LICENSE for license information
@ -65,8 +65,8 @@ poschangelib.add_player_pos_listener("sample:pos_listener", my_callback)
Watch player walking on particular nodes
----------------------------------------
Watch player walking on particular nodes, the rough way
-------------------------------------------------------
Use poschangelib.add_player_walk_listener(name, my_callback, nodenames)
@ -93,6 +93,50 @@ poschangelib.add_player_walk_listener('sample:top', toptop, {'group:choppy'})
Watch player walking on particular nodes, the fine way
------------------------------------------------------
When dealing with non-filled blocks like slab and snow, the trigger may give some
false positives and be triggered twice for the same movement. This is because you can
hook to a nearby full block and stand above snow without touching it, which messes
with the walk detection of regular blocks (which checks for walkable nodes).
Moreover it can't be enough. With the example of slabs, lower slabs can be triggered
by hanging to a nearby full block and should not be triggered that way, but higher
slabs must be considered like full blocks, because the player is walking on the above
node.
If you don't require an accurate checking, just ignore the call when trigger_meta.redo
is true like in the example below:
local function toptop(player, pos, node, desc, trigger_meta)
if trigger_meta.redo then return end
... do your regular stuff
end
If you want to make fine position checking, you can use the 5th argument which holds
the trigger metadata. It is a table with the following keys
- redo: always true when defined
- player_pos: rounded position of the player
- source: either the node name or the group that ran the trigger
- source_level: when source is a group, the associated level
When trigger_meta.player_pos is equal to pos it means the player is currently inside
the node. It can be true only with non-filled blocks. With full blocks, player_pos is
always one unit above.
After hanging nearby, the player will trigger the callback when falling on that block.
When it happens the redo flag is raised in the meta for the callback to know if it
should ignore it (because it has already processed the effects) or proceed because the
previous call was ignored by checking player_pos.
For example to roughly prevent false positive with snow:
local function snow_walk(player, pos, node, desc, trigger_meta)
if trigger_meta.player_pos != pos then return end
... do your regular stuff
Add _on_walk_over to nodes
-------------------------
@ -108,10 +152,8 @@ For compatibility with walkover, you can use on_walk_over (without the underscor
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.
_on_walk is affected by the same issue about non-filled nodes. You can use the 4th
argument to check the trigger metadata to adjust your callback.
Configuration/Performances tweaking

@ -118,11 +118,11 @@ function poschangelib.remove_player_walk_listener(name, nodenames)
end
end
local function trigger_player_walk_listeners(player, pos, node, node_def, nodename)
for name, callback in pairs(walk_listeners[nodename]) do
local function trigger_player_walk_listeners(trigger_name, player, pos, node, node_def, trigger_meta)
for name, callback in pairs(walk_listeners[trigger_name]) do
if is_callable(name) then
callback(player, pos, node, node_def)
triggered_listeners[name] = true
callback(player, pos, node, node_def, trigger_meta)
triggered_listeners[trigger_name] = true
end
end
end
@ -159,13 +159,40 @@ local function get_updated_positions(player)
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 function check_on_walk_triggers(player, old_pos, pos, raw_pos)
-- Get the node at current player position to check if in mid-air
-- or on a half-filled node.
local pos_below = pos
local node_below = minetest.get_node(pos)
local node_def = minetest.registered_nodes[node_below.name]
-- When the feet are not directly on the node below, the player may be
-- in-air or standing on a non-filled walkable block.
-- Pass this information to the listener in case they want a fine
-- collision checking.
local trigger_meta = { player_pos = pos }
if not node_def.walkable then
-- Player not standing in a non-filled node
-- Check node below, if walkable consider the player is walking
-- on it (not 100% accurate, if requested the listener will have
-- to check feet_y for more)
pos_below = vector.new(pos.x, pos.y - 1, pos.z)
node_below = minetest.get_node(pos_below)
node_def = minetest.registered_nodes[node_below.name]
if not node_def.walkable then return end -- truely not walking
else
-- Player standing inside a walkable node (like a slab or snow).
-- But when coming from above (hooked to a nearby filled node)
-- it may have already been triggered (but maybe ignored because
-- it had a fine collision check).
if old_pos.y - 1 == pos.y then
-- Already triggered from above, pass this information
trigger_meta.redo = true
end
end
-- 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)
trigger_meta.source = node_below.name
trigger_player_walk_listeners(node_below.name, player, pos_below, node_below, node_def, trigger_meta)
end
-- Trigger by group
local groups_below = node_def.groups
@ -173,15 +200,17 @@ local function check_on_walk_triggers(player, pos)
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)
trigger_meta.source = group
trigger_meta.source_level = level
trigger_player_walk_listeners(group_name, player, pos_below, node_below, node_def, trigger_meta)
end
end
end
-- Trigger _on_walk
if node_def._on_walk then
node_def._on_walk(pos_below, node_below, player)
node_def._on_walk(pos_below, node_below, player, trigger_meta)
elseif node_below.on_walk then
node_def.on_walk(pos_below, node_below, player)
node_def.on_walk(pos_below, node_below, player, trigger_meta)
end
end
@ -201,7 +230,7 @@ local function loop(dtime)
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)
check_on_walk_triggers(player, poss.old, poss.new, player:getpos())
end
-- Reset the triggered listener to allow the next player to trigger them
triggered_listeners = {}