mirror of
https://files.creativekara.fr/git/poschangelib.git
synced 2024-12-04 21:13:50 +01:00
Interpolation
This commit is contained in:
parent
1ba52f42d7
commit
9c52feed90
33
README.txt
33
README.txt
@ -39,8 +39,8 @@ 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.
|
||||
The my_callback is a function that takes 4 arguments: the player, last known position,
|
||||
new position and some metadata.
|
||||
|
||||
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
|
||||
@ -52,7 +52,7 @@ When on teleporting, programatic moves with setpos or moving fast it may be far
|
||||
|
||||
Quick code sample:
|
||||
|
||||
local function my_callback(player, old_pos, new_pos)
|
||||
local function my_callback(player, old_pos, new_pos, meta)
|
||||
if old_pos == nil then
|
||||
minetest.chat_send_player(player:get_player_name(), 'Welcome to the world!')
|
||||
else
|
||||
@ -156,6 +156,33 @@ _on_walk is affected by the same issue about non-filled nodes. You can use the 4
|
||||
argument to check the trigger metadata to adjust your callback.
|
||||
|
||||
|
||||
|
||||
More on metadata
|
||||
----------------
|
||||
|
||||
The metadata are a table that can contain the following elements:
|
||||
|
||||
interpolated
|
||||
Is true when the position was assumed and not observed. Most of the time because the
|
||||
player moved too fast to check all nodes in real time.
|
||||
|
||||
player_pos
|
||||
Is set for walk listeners, it contains the player's position. Not set when
|
||||
interpolated.
|
||||
|
||||
source
|
||||
Contains the name of the node or group that triggered the walk listener.
|
||||
This is one of thoses passed on registration.
|
||||
|
||||
source_level
|
||||
Contains the level of the group when source is a node group.
|
||||
|
||||
redo
|
||||
Is true when it was detected that the listener was previously called on that position.
|
||||
See <Watch player walking on particular nodes, the fine way>.
|
||||
|
||||
|
||||
|
||||
Configuration/Performances tweaking
|
||||
-----------------------------------
|
||||
|
||||
|
115
init.lua
115
init.lua
@ -17,6 +17,12 @@ end
|
||||
-- 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 = {}
|
||||
local function set_listener_triggered(name, pos)
|
||||
if not triggered_listeners.name then
|
||||
triggered_listeners.name = {}
|
||||
end
|
||||
table.insert(triggered_listeners.name, pos)
|
||||
end
|
||||
|
||||
--- Internal utility to create an empty table on first registration.
|
||||
-- @param mothertable The main table that will hold other tables.
|
||||
@ -30,9 +36,15 @@ local function get_subtable_or_create(mothertable, item)
|
||||
end
|
||||
|
||||
--- Check if a listener can be triggered
|
||||
local function is_callable(name)
|
||||
local function is_callable(name, pos)
|
||||
-- Check if not aleady called
|
||||
if triggered_listeners.name then return false end
|
||||
if triggered_listeners.name then
|
||||
for _, trigg_pos in ipairs(triggered_listeners.name) do
|
||||
if vector.equals(trigg_pos, pos) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Other checks will come here when required
|
||||
return true
|
||||
end
|
||||
@ -64,11 +76,11 @@ 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)
|
||||
local function trigger_player_position_listeners(player, old_pos, pos, trigger_meta)
|
||||
for name, callback in pairs(player_listeners) do
|
||||
if is_callable(name) then
|
||||
callback(player, old_pos, pos)
|
||||
triggered_listeners[name] = true
|
||||
if is_callable(name, pos) then
|
||||
callback(player, old_pos, pos, trigger_meta)
|
||||
set_listener_triggered(name, pos)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -120,9 +132,9 @@ end
|
||||
|
||||
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
|
||||
if is_callable(name, pos) then
|
||||
callback(player, pos, node, node_def, trigger_meta)
|
||||
triggered_listeners[trigger_name] = true
|
||||
set_listener_triggered(trigger_name, pos)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -138,20 +150,79 @@ 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)
|
||||
|
||||
--- Erratically get a path from start_pos and end_pos. This won't be 100%
|
||||
-- accurate for many reasons.
|
||||
-- - We don't know if a node is passable or not.
|
||||
-- - There may be multiple options to get from one point to an other with the
|
||||
-- same length
|
||||
-- - The player may not even walk straight
|
||||
-- This function is recursive, start will move toward end.
|
||||
-- @param start_pos Full coortinate of starting point (recursive)
|
||||
-- @param end_pos The goal
|
||||
-- @param path Empty at start, will contains all points between start and end
|
||||
-- at the last call, then return up all the way to the first call.
|
||||
function poschangelib.get_path(start_pos, end_pos, path)
|
||||
-- Try to get closer to end_pos by moving one block in the axis that
|
||||
-- is the further from end. If at the same distance for more than one
|
||||
-- axis, pick randomly between them.
|
||||
if path == nil then path = {} end
|
||||
table.insert(path, start_pos)
|
||||
local distance = vector.subtract(end_pos, start_pos)
|
||||
local dX = math.abs(distance.x)
|
||||
local dY = math.abs(distance.y)
|
||||
local dZ = math.abs(distance.z)
|
||||
if (dX + dY + dZ <= 1) then -- Next step will reach end_pos
|
||||
table.insert(path, end_pos)
|
||||
return path
|
||||
end
|
||||
local d = {} -- List of candidates axis for next move
|
||||
if dX >= dY and dX >= dZ then table.insert(d, 'x') end
|
||||
if dY >= dX and dY >= dZ then table.insert(d, 'y') end
|
||||
if dZ >= dX and dZ >= dY then table.insert(d, 'z') end
|
||||
local axis = d[math.random(1, table.getn(d))]
|
||||
local next_pos = nil
|
||||
if axis == 'x' then
|
||||
if distance.x > 0 then
|
||||
next_pos = vector.add(start_pos, vector.new(1,0,0))
|
||||
else
|
||||
next_pos = vector.add(start_pos, vector.new(-1,0,0))
|
||||
end
|
||||
elseif axis == 'y' then
|
||||
if distance.y > 0 then
|
||||
next_pos = vector.add(start_pos, vector.new(0,1,0))
|
||||
else
|
||||
next_pos = vector.add(start_pos, vector.new(0,-1,0))
|
||||
end
|
||||
elseif axis == 'z' then
|
||||
if distance.z > 0 then
|
||||
next_pos = vector.add(start_pos, vector.new(0,0,1))
|
||||
else
|
||||
next_pos = vector.add(start_pos, vector.new(0,0,-1))
|
||||
end
|
||||
end
|
||||
if axis == nil then
|
||||
minetest.log('error', 'poschangelib interpolator is lost')
|
||||
return path
|
||||
end
|
||||
return poschangelib.get_path(next_pos, end_pos, path)
|
||||
end
|
||||
|
||||
--- 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
|
||||
-- @returns List of positions from last known to current
|
||||
-- (with guessed interpolation) 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}
|
||||
ret = {pos}
|
||||
elseif pos then
|
||||
-- Check for position change
|
||||
if not vector.equals(old_pos, pos) then
|
||||
ret = {old = old_pos, new = pos}
|
||||
ret = poschangelib.get_path(old_pos, pos)
|
||||
end
|
||||
end
|
||||
player_last_pos[player:get_player_name()] = pos
|
||||
@ -159,7 +230,8 @@ local function get_updated_positions(player)
|
||||
end
|
||||
|
||||
--- Check and call on_walk triggers if required.
|
||||
local function check_on_walk_triggers(player, old_pos, pos, raw_pos)
|
||||
local function check_on_walk_triggers(player, old_pos, pos, trigger_meta)
|
||||
if trigger_meta == nil then trigger_meta = {} end
|
||||
-- Get the node at current player position to check if in mid-air
|
||||
-- or on a half-filled node.
|
||||
local pos_below = pos
|
||||
@ -169,7 +241,9 @@ local function check_on_walk_triggers(player, old_pos, pos, raw_pos)
|
||||
-- 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 trigger_meta.interpolated then
|
||||
trigger_meta.player_pos = pos
|
||||
end
|
||||
if not node_def.walkable then
|
||||
-- Player not standing in a non-filled node
|
||||
-- Check node below, if walkable consider the player is walking
|
||||
@ -228,9 +302,18 @@ local function loop(dtime)
|
||||
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.old, poss.new, player:getpos())
|
||||
if table.getn(poss) == 1 then
|
||||
trigger_player_position_listeners(player, nil, poss[0])
|
||||
else
|
||||
local poss_end_couple = table.getn(poss) - 1
|
||||
for i = 1, poss_end_couple do
|
||||
local trigger_meta = {}
|
||||
if i > 1 and i <= poss_end_couple then
|
||||
trigger_meta.interpolated = true
|
||||
end
|
||||
trigger_player_position_listeners(player, poss[i], poss[i+1], trigger_meta)
|
||||
check_on_walk_triggers(player, poss[i], poss[i+1], trigger_meta)
|
||||
end
|
||||
end
|
||||
-- Reset the triggered listener to allow the next player to trigger them
|
||||
triggered_listeners = {}
|
||||
|
Loading…
Reference in New Issue
Block a user