Imported from trollstream "ContentDB"
commit
2575c10121
|
@ -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
|
|
@ -0,0 +1,275 @@
|
|||
Minetest mod library: poschangelib
|
||||
==================================
|
||||
|
||||
* version 0.5
|
||||
* Licence LGPLv2 or, at your discretion, any later version.
|
||||
|
||||
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
|
||||
- Set stomping on nodes
|
||||
- Add footprints
|
||||
- Configuration/Performances tweaking
|
||||
- Debugging
|
||||
|
||||
|
||||
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 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
|
||||
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 neighbour 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, meta)
|
||||
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, the rough way
|
||||
-------------------------------------------------------
|
||||
|
||||
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'})
|
||||
|
||||
|
||||
|
||||
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. See "More on metadata" below.
|
||||
|
||||
|
||||
|
||||
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.
|
||||
|
||||
_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.
|
||||
|
||||
|
||||
|
||||
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.
|
||||
|
||||
teleported
|
||||
Is true when the player was moving too fast. The interpolation is then not computed.
|
||||
|
||||
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.
|
||||
|
||||
covered
|
||||
Is true when a non-walkable non-air node is present above this node (like grass).
|
||||
Covered is accurate only with full nodes. For half-filled node, you'll have to check
|
||||
by hand.
|
||||
|
||||
|
||||
|
||||
Set stomping on nodes
|
||||
---------------------
|
||||
|
||||
Stomping is a dedicated subset of walk listeners that allows to replace a node by an
|
||||
other when a player walks on it.
|
||||
|
||||
It is required to be able to declare multiple outputs without messing with one
|
||||
another. And just for ease of use.
|
||||
|
||||
Stomping are registered with poschangelib.register_stomp. It takes 3 parameters:
|
||||
|
||||
poschangelib.register_stomp:
|
||||
- source_node_name: the name of the node that can be stomped. It can be a table
|
||||
with multiple node names to declare the same stomping behaviour to multiple
|
||||
nodes at once.
|
||||
- stomp_node_name: the name of the replacement node, or a function.
|
||||
- stomp_desc: stomping parameters.
|
||||
|
||||
The stomp description is a table that can contains the following set of keys:
|
||||
|
||||
stomp_desc:
|
||||
- chance: inverted chance that the stomp occurs (default 1)
|
||||
- duration: time in second after which the stomp reverts.
|
||||
When not set, the stomp is forever. If set it will override duration_min and
|
||||
duration_max.
|
||||
- duration_min: same as duration but to add some randomness for each node.
|
||||
- duration_max: same as duration but to add some randomness for each node.
|
||||
- priority: the priority rank. The lower, the more important it is (default 100)
|
||||
- name: name that is used as walk listener name.
|
||||
Default is <source>__to__<stomp> and is rather indigest but probably unique.
|
||||
It has no default when using a function in stomp_node_name and must be set.
|
||||
- source_node: set it if you want the stomp to revert to an other node than the
|
||||
original.
|
||||
|
||||
When multiple stompings are registered for the same node, only the first
|
||||
triggered is applied. This is when priority comes into play. When a player walks
|
||||
on a node that can be stomped, a roll is made for each stomp in order of
|
||||
priority (the lowest priority first). If the roll succeeds, the node is replaced
|
||||
and the next stomps are not run.
|
||||
|
||||
When using a function instead of a stomp node name, this function is a regular
|
||||
player walk listener. It must return a node or nil (i.e. {name = <name>,
|
||||
param = <param> etc}). If it returns nil, the stomp is not done and the priority
|
||||
check is not stopped (see just below). When using only a node name, all other
|
||||
node values are kept.
|
||||
|
||||
|
||||
|
||||
|
||||
Add footprints
|
||||
--------------
|
||||
|
||||
Use poschangelib.register_footprint to quickly register footprinted nodes and
|
||||
the stomping associated to it. The function takes 2 parameters:
|
||||
|
||||
register_footprint:
|
||||
- node_name: the name of the node to extend, or a table to register multiple
|
||||
footprints with the same stomp_desc
|
||||
- stomp_desc: see above.
|
||||
|
||||
The stomp description can have dedicated keys and values:
|
||||
|
||||
- footprint_texture: set it to use an other texture than the one embedded.
|
||||
|
||||
A new node will be registered with most of it's description copied from the
|
||||
original node. It's top texture will have the footprint layer on it and the
|
||||
stomping behaviour will be automatically created.
|
||||
|
||||
poschangelib.register_footprint returns the footprinted node name(s). If you
|
||||
pass nested tables in node_name, the same nesting is returned.
|
||||
|
||||
|
||||
|
||||
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.
|
||||
|
||||
|
||||
|
||||
Debugging
|
||||
---------
|
||||
|
||||
With the server privileges, you can list available stompings for the node you
|
||||
are currently on. Use /stomp.
|
||||
|
||||
If there is only one stomping available, it is triggered. If there are multiple
|
||||
stomps, it prints the list of stomping names. Use /stomp X to trigger the Xth
|
||||
stomp.
|
|
@ -0,0 +1,322 @@
|
|||
poschangelib = {
|
||||
player_pos_listeners = {},
|
||||
walk_listeners = {},
|
||||
}
|
||||
|
||||
dofile(minetest.get_modpath(minetest.get_current_modname()) .. '/register.lua')
|
||||
|
||||
--[[
|
||||
-- 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.settings:get('poschangelib_check_interval')) or 0.3
|
||||
end
|
||||
function poschangelib.setting_teleport_range()
|
||||
return tonumber(minetest.settings:get('poschangelib_teleport_range')) or 10
|
||||
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 = {}
|
||||
local function set_listener_triggered(listener_name, pos)
|
||||
if not triggered_listeners[listener_name] then
|
||||
triggered_listeners[listener_name] = {}
|
||||
end
|
||||
table.insert(triggered_listeners[listener_name], pos)
|
||||
end
|
||||
|
||||
--- 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(listener_name, pos)
|
||||
-- Check if not aleady called
|
||||
if triggered_listeners[listener_name] then
|
||||
for _, trigg_pos in ipairs(triggered_listeners[listener_name]) do
|
||||
if vector.equals(trigg_pos, pos) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Other checks will come here when required
|
||||
return true
|
||||
end
|
||||
|
||||
local function copy_trigger_meta(meta)
|
||||
local new_meta = {}
|
||||
for i, key in pairs({'interpolated', 'teleported', 'source',
|
||||
'source_level', 'redo', 'covered'}) do
|
||||
new_meta[key] = meta[key]
|
||||
end
|
||||
if meta.player_pos then
|
||||
new_meta.player_pos = vector.new(meta.player_pos.x, meta.player_pos.y, meta.player_pos.z)
|
||||
end
|
||||
return new_meta
|
||||
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, trigger_meta)
|
||||
for name, callback in pairs(poschangelib.player_pos_listeners) do
|
||||
if is_callable(name, pos) then
|
||||
callback(player, old_pos, pos, trigger_meta)
|
||||
set_listener_triggered(name, pos)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Trigger a walk listener by it's name.
|
||||
-- Never called directly, use trigger_player_walk_listener_by_* functions
|
||||
local function trigger_player_walk_listeners(trigger_name, player, pos, node, node_def, trigger_meta)
|
||||
for listener_name, callback in pairs(poschangelib.walk_listeners[trigger_name]) do
|
||||
if is_callable(listener_name, pos) then
|
||||
callback(player, pos, node, node_def, trigger_meta)
|
||||
set_listener_triggered(listener_name, pos)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Check if a walk listener can be triggered by node name and trigger it.
|
||||
-- Trigger meta is copied and extended before being passed to the listeners.
|
||||
local function trigger_player_walk_listeners_by_node_name(player, pos, node, node_def, trigger_meta)
|
||||
if poschangelib.walk_listeners[node.name] then
|
||||
local new_meta = copy_trigger_meta(trigger_meta)
|
||||
new_meta.source = node.name
|
||||
trigger_player_walk_listeners(node.name, player, pos, node, node_def, new_meta)
|
||||
end
|
||||
end
|
||||
|
||||
--- Check if a walk listener can be triggered by node groups and trigger it.
|
||||
-- Trigger meta is copied and extended before being passed to the listeners.
|
||||
local function trigger_player_walk_listeners_by_node_group(player, pos, node, node_def, trigger_meta)
|
||||
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 poschangelib.walk_listeners[group_name] then
|
||||
local new_meta = copy_trigger_meta(trigger_meta)
|
||||
new_meta.source = group
|
||||
new_meta.source_level = level
|
||||
trigger_player_walk_listeners(group_name, player, pos, node, node_def, new_meta)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function trigger_on_walk(player, pos, node, node_def, trigger_meta)
|
||||
if node_def._on_walk then
|
||||
node_def._on_walk(pos, node, player, copy_trigger_meta(trigger_meta))
|
||||
elseif node_def.on_walk then
|
||||
node_def.on_walk(pos, node, player, copy_trigger_meta(trigger_meta))
|
||||
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)
|
||||
|
||||
--- 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)
|
||||
-- Check for teleportation
|
||||
local teleport_range = poschangelib.setting_teleport_range()
|
||||
local dX = math.abs(distance.x)
|
||||
local dY = math.abs(distance.y)
|
||||
local dZ = math.abs(distance.z)
|
||||
if (dX + dY + dZ <= 1) or
|
||||
(teleport_range > 0 and dX + dY + dZ > teleport_range) then
|
||||
-- Next step will reach end_pos
|
||||
-- or teleported
|
||||
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 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:get_pos())
|
||||
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 = {pos}
|
||||
elseif pos then
|
||||
-- Check for position change
|
||||
if not vector.equals(old_pos, pos) then
|
||||
ret = poschangelib.get_path(old_pos, 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, 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
|
||||
local node_below = minetest.get_node(pos)
|
||||
local node_def = minetest.registered_nodes[node_below.name]
|
||||
if not node_def then return end -- Unknown node, don't crash
|
||||
-- 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.
|
||||
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
|
||||
-- on it (not 100% accurate)
|
||||
local node_above = node_below
|
||||
local node_above_def = node_def
|
||||
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 then return end
|
||||
if not node_def.walkable then return end -- truely not walking
|
||||
-- We have checked the node above, see if it covers the one below
|
||||
-- and trigger walk for that node.
|
||||
if node_above.name ~= 'air' then
|
||||
trigger_player_walk_listeners_by_node_name(player, pos, node_above, node_above_def, trigger_meta)
|
||||
trigger_player_walk_listeners_by_node_group(player, pos, node_above, node_above_def, trigger_meta)
|
||||
trigger_on_walk(player, pos, node_above, node_above_def, trigger_meta)
|
||||
-- Set covered for the node below
|
||||
trigger_meta.covered = true
|
||||
end
|
||||
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
|
||||
-- Triggers
|
||||
trigger_player_walk_listeners_by_node_name(player, pos_below, node_below, node_def, trigger_meta)
|
||||
trigger_player_walk_listeners_by_node_group(player, pos_below, node_below, node_def, trigger_meta)
|
||||
trigger_on_walk(player, pos_below, node_below, node_def, trigger_meta)
|
||||
end
|
||||
|
||||
|
||||
dofile(minetest.get_modpath(minetest.get_current_modname()) .. '/stomping.lua')
|
||||
--[[
|
||||
-- Main loop
|
||||
--]]
|
||||
|
||||
local function loop()
|
||||
local teleport_range = poschangelib.setting_teleport_range()
|
||||
-- Player checks
|
||||
for _, player in ipairs(minetest.get_connected_players()) do
|
||||
local poss = get_updated_positions(player)
|
||||
if poss then
|
||||
local pos_count = table.getn(poss)
|
||||
if pos_count == 1 then
|
||||
-- Moved from nil to a given position
|
||||
trigger_player_position_listeners(player, nil, poss[0])
|
||||
elseif pos_count == 2 then
|
||||
-- Non-interpolated movement
|
||||
local teleported = false
|
||||
local trigger_meta = {}
|
||||
if teleport_range > 0 and vector.distance(poss[1], poss[2]) >= teleport_range then
|
||||
trigger_meta.teleported = true
|
||||
end
|
||||
trigger_player_position_listeners(player, poss[1], poss[2], trigger_meta)
|
||||
check_on_walk_triggers(player, poss[1], poss[2], trigger_meta)
|
||||
else
|
||||
-- Interpolated movement
|
||||
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 = {}
|
||||
end
|
||||
end
|
||||
minetest.after(poschangelib.setting_check_interval(), loop)
|
||||
end
|
||||
minetest.after(poschangelib.setting_check_interval(), loop)
|
|
@ -0,0 +1,64 @@
|
|||
--[[
|
||||
-- Player position 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 poschangelib.player_pos_listeners[name] then
|
||||
minetest.log('error', 'Player pos listener ' .. name .. ' is already registered')
|
||||
return
|
||||
end
|
||||
poschangelib.player_pos_listeners[name] = callback
|
||||
end
|
||||
|
||||
--- Remove a registered callback. It won't be called anymore.
|
||||
function poschangelib.remove_player_pos_listener(name)
|
||||
if poschangelib.player_pos_listeners[name] then
|
||||
poschangelib.player_pos_listeners[name] = nil
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
-- 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 poschangelib.walk_listeners[nodename] then
|
||||
poschangelib.walk_listeners[nodename] = {}
|
||||
end
|
||||
if poschangelib.walk_listeners[nodename][name] then
|
||||
minetest.log('error', 'Walk listener ' .. name .. ' is already registered')
|
||||
end
|
||||
poschangelib.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 poschangelib.walk_listeners[nodename] and poschangelib.walk_listeners[nodename][name] then
|
||||
poschangelib.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
|
||||
poschangelib.walk_listeners[nodename] = nil
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,8 @@
|
|||
# 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
|
||||
|
||||
# Distance between two checks that is considered to be a teleportation and won't
|
||||
# compute interpolated positions between last known position and current position.
|
||||
# Set to 0 to disable checking.
|
||||
poschangelib_teleport_range (Teleport range) int 10 0
|
|
@ -0,0 +1,286 @@
|
|||
--[[
|
||||
This file contains the stomping layer.
|
||||
It is dedicated to transform a node to an other when walked on.
|
||||
--]]
|
||||
|
||||
local function table_copy(table)
|
||||
local orig_type = type(table)
|
||||
local copy = {}
|
||||
if orig_type ~= 'table' then return table end
|
||||
for orig_key, orig_value in next, table, nil do
|
||||
copy[orig_key] = table_copy(orig_value)
|
||||
end
|
||||
return copy
|
||||
end
|
||||
|
||||
|
||||
--- Store all registered stomped nodes indexed by source node name
|
||||
-- For every node name there can be a list of stomping descriptions, ordered
|
||||
-- by priority in ascending order.
|
||||
local stomps = {}
|
||||
|
||||
--- Get default stomp name to use with listeners.
|
||||
local function get_stomp_name(source_node_name, stomp_node_name, mod_name)
|
||||
if not mod_name then
|
||||
mod_name = minetest.get_current_modname()
|
||||
end
|
||||
return mod_name .. ':' .. source_node_name .. '__to__' .. stomp_node_name
|
||||
end
|
||||
|
||||
function poschangelib.get_footprint_node_name(source_node_name, mod_name)
|
||||
if not mod_name then
|
||||
-- current_modname is the caller mod, not always poschangelib
|
||||
mod_name = minetest.get_current_modname()
|
||||
end
|
||||
local node_mod_name = string.sub(source_node_name, 1, string.find(source_node_name, ':'))
|
||||
if node_mod_name == mod_name then
|
||||
return source_node_name .. '_with_footprint'
|
||||
else
|
||||
return mod_name .. ':' .. string.gsub(source_node_name, ':', '__') .. '_with_footprint'
|
||||
end
|
||||
end
|
||||
|
||||
--- poschangelib walk callback
|
||||
local function walk_listener(player, pos, node, desc, trigger_meta)
|
||||
poschangelib.chance_stomp(player, pos, node, desc, trigger_meta)
|
||||
end
|
||||
|
||||
--- Random roll and do the stomp if it succeeds.
|
||||
function poschangelib.chance_stomp(player, pos, node, node_desc, trigger_meta)
|
||||
local stomp_desc = stomps[node.name]
|
||||
if not stomp_desc then
|
||||
minetest.log('warning', 'No stomping data found for node ' .. node.name)
|
||||
return
|
||||
end
|
||||
for i, s_desc in ipairs(stomp_desc) do
|
||||
if (math.random() * s_desc.chance) < 1.0 then
|
||||
poschangelib.do_stomp(player, pos, node, node_desc, s_desc, trigger_meta)
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Actually do the stomp: replace the stomped node.
|
||||
-- @param player The player the triggered the stomp.
|
||||
-- @param pos Position of the stomped node.
|
||||
-- @param node Node being stomped.
|
||||
-- @param node_desc Description of the node being stomped.
|
||||
-- @param stomp_desc Optional stomp description. If not provided it looks for it.
|
||||
-- @param trigger_meta Optional trigger meta, passed by walk listeners.
|
||||
function poschangelib.do_stomp(player, pos, node, node_desc, stomp_desc, trigger_meta)
|
||||
if not stomp_desc then
|
||||
stomp_desc = stomps[node.name]
|
||||
if stomp_desc then stomp_desc = stomp_desc[1] end
|
||||
end
|
||||
if not stomp_desc then
|
||||
minetest.log('warning', 'No stomping data found for node ' .. node.name)
|
||||
return
|
||||
end
|
||||
if not trigger_meta then trigger_meta = {} end
|
||||
if type(stomp_desc.dest_node_name) == 'function' then
|
||||
local dest_node = stomp_desc.dest_node_name(player, pos, node, trigger_meta)
|
||||
if not dest_node then return end
|
||||
if not dest_node.name then
|
||||
minetest.log('error', 'Stomping: function did not set node name for ' .. node.name)
|
||||
return
|
||||
end
|
||||
minetest.set_node(pos, dest_node)
|
||||
else
|
||||
local new_node = minetest.get_node(pos)
|
||||
new_node.name = stomp_desc.dest_node_name
|
||||
minetest.set_node(pos, new_node)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
-- Revert timer, used in node registration.
|
||||
--]]
|
||||
|
||||
function poschangelib.change_node(pos, stomped_node_name, reverted_node_name)
|
||||
-- Check if the node is still the right one
|
||||
local node = minetest.get_node(pos)
|
||||
if (node.name ~= stomped_node_name) then return end
|
||||
-- Replace it while keeping param, param2 and other things
|
||||
node.name = reverted_node_name
|
||||
minetest.set_node(pos, node)
|
||||
end
|
||||
|
||||
--[[
|
||||
-- Node registration
|
||||
--]]
|
||||
|
||||
--- Set the default values for a stomp_desc.
|
||||
-- stomp_desc.dest_node_name must be set.
|
||||
local function stomp_desc_defaults(source_node_name, stomp_desc)
|
||||
if not stomp_desc.chance then stomp_desc.chance = 1 end
|
||||
if not stomp_desc.source_node then
|
||||
stomp_desc.source_node = source_node_name
|
||||
end
|
||||
if not stomp_desc.priority then stomp_desc.priority = 100 end
|
||||
if stomp_desc.duration then
|
||||
stomp_desc.duration_min = stomp_desc.duration
|
||||
stomp_desc.duration_max = stomp_desc.duration
|
||||
stomp_desc.duration = nil
|
||||
end
|
||||
if not stomp_desc.priority then stomp_desc.priority = 100 end
|
||||
if not stomp_desc.name then
|
||||
stomp_desc.name = get_stomp_name(source_node_name, stomp_desc.dest_node_name)
|
||||
end
|
||||
end
|
||||
|
||||
--- Register a footprinted version of a node
|
||||
function poschangelib.register_footprints(node_name, stomp_desc)
|
||||
if type(node_name) == 'table' then
|
||||
-- Register all nodes from the table
|
||||
local names = {}
|
||||
for i, name in pairs(node_name) do
|
||||
table.insert(names, poschangelib.register_footprints(name, stomp_desc))
|
||||
end
|
||||
return names
|
||||
end
|
||||
-- Single node registration
|
||||
local desc = minetest.registered_nodes[node_name]
|
||||
if not desc then
|
||||
minetest.log('error', 'Trying to register footprints for unknow node ' .. node_name)
|
||||
return
|
||||
end
|
||||
local stomped_node_name = poschangelib.get_footprint_node_name(node_name)
|
||||
-- Use a copy of stomp desc to keep it unchanged outside the function
|
||||
local local_stomp_desc = table_copy(stomp_desc)
|
||||
local_stomp_desc.dest_node_name = stomped_node_name
|
||||
stomp_desc_defaults(node_name, local_stomp_desc)
|
||||
local stomped_node_desc = table_copy(desc)
|
||||
stomped_node_desc.description = desc.description .. ' With Footprint'
|
||||
-- Add footprint on top of the node texture
|
||||
local footprint_texture = 'poschangelib_footprint.png'
|
||||
if local_stomp_desc.footprint_texture then
|
||||
footprint_texture = local_stomp_desc.footprint_texture
|
||||
end
|
||||
if type(desc.tiles[1]) == 'table' then
|
||||
-- Replace top texture
|
||||
stomped_node_desc.tiles[1].name = desc.tiles[1].name .. '^' .. footprint_texture
|
||||
else
|
||||
-- Put footprints on top and keep the original texture for the rest
|
||||
stomped_node_desc.tiles[1] = desc.tiles[1] .. '^' .. footprint_texture
|
||||
stomped_node_desc.tiles[2] = desc.tiles[1]
|
||||
end
|
||||
-- Revert timer
|
||||
if local_stomp_desc.duration_min then
|
||||
if not desc.on_timer then
|
||||
stomped_node_desc.on_timer = function(pos, elapsed)
|
||||
poschangelib.change_node(pos, stomped_node_name, node_name)
|
||||
end
|
||||
end
|
||||
if desc.on_construct then
|
||||
stomped_node_desc.on_construct = function(pos)
|
||||
desc.on_construct(pos)
|
||||
minetest.get_node_timer(pos):start(math.random(local_stomp_desc.duration_min, local_stomp_desc.duration_max))
|
||||
end
|
||||
else
|
||||
stomped_node_desc.on_construct = function(pos) minetest.get_node_timer(pos):start(math.random(local_stomp_desc.duration_min, local_stomp_desc.duration_max)) end
|
||||
end
|
||||
end
|
||||
-- Drop the original node when dug
|
||||
if not desc.drop then
|
||||
stomped_node_desc.drop = node_name
|
||||
end
|
||||
-- Register
|
||||
minetest.register_node(stomped_node_name, stomped_node_desc)
|
||||
poschangelib.register_stomp(node_name, stomped_node_name, local_stomp_desc)
|
||||
-- Stomp to itself to reset the timer on restomp
|
||||
poschangelib.register_stomp(stomped_node_name, stomped_node_name, local_stomp_desc)
|
||||
return stomped_node_name
|
||||
end
|
||||
|
||||
--- Register a stomped node that has a chance to be transformed from the source.
|
||||
-- @param source_node_name The name of the node before it is stomped
|
||||
-- @param stomp chance Inverted chance that the source node is stomped on walking.
|
||||
-- One of X.
|
||||
-- @param stomp_node_name The name of the node after it is stomped
|
||||
function poschangelib.register_stomp(source_node_name, stomp_node_name, stomp_desc)
|
||||
if type(stomp_node_name) == 'function' and not stomp_desc.name then
|
||||
minetest.log('error', 'No stomp name given with a function for ' .. source_node_name)
|
||||
return
|
||||
end
|
||||
if type(source_node_name) == 'table' then
|
||||
for i, node_name in ipairs(source_node_name) do
|
||||
poschangelib.register_stomp(node_name, stomp_node_name, stomp_desc)
|
||||
end
|
||||
return
|
||||
end
|
||||
if not stomps[source_node_name] then
|
||||
stomps[source_node_name] = {}
|
||||
end
|
||||
local local_stomp_desc = table_copy(stomp_desc)
|
||||
local_stomp_desc.dest_node_name = stomp_node_name
|
||||
stomp_desc_defaults(source_node_name, local_stomp_desc)
|
||||
-- Insert in stomps
|
||||
local inserted = false
|
||||
local i = 1
|
||||
-- insert while keeping ascending priority order
|
||||
while i <= #stomps[source_node_name] and not inserted do
|
||||
if stomps[source_node_name][i].priority > local_stomp_desc.priority then
|
||||
table.insert(stomps[source_node_name], i, local_stomp_desc)
|
||||
inserted = true
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
-- not inserted: there is no other stomp for this node, insert it.
|
||||
if not inserted then table.insert(stomps[source_node_name], local_stomp_desc) end
|
||||
poschangelib.add_player_walk_listener(local_stomp_desc.name, walk_listener, {source_node_name})
|
||||
end
|
||||
|
||||
-- Manually trigger an stomp if it exists and if the chance test passes.
|
||||
-- @return False if no stomp is registered, true otherwise.
|
||||
function poschangelib.trigger_stomp(player, pos_to_stomp, chance_factor)
|
||||
local node = minetest.get_node(pos_to_stomp)
|
||||
local node_desc = minetest.registered_nodes[node.name]
|
||||
if not node_desc or not stomps[node.name] or #stomps[node.name] == 0 then
|
||||
return false
|
||||
end
|
||||
local stomp_desc = stomps[node.name]
|
||||
if not stomp_desc then
|
||||
return false
|
||||
end
|
||||
if chance_factor == nil then chance_factor = 1.0 end
|
||||
for i, s_desc in ipairs(stomp_desc) do
|
||||
if (math.random() * s_desc.chance) < (1.0 * chance_factor) then
|
||||
poschangelib.do_stomp(player, pos_to_stomp, node, node_desc, s_desc)
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
minetest.register_chatcommand('stomp', {
|
||||
func = function(name, param)
|
||||
local player = minetest.get_player_by_name(name)
|
||||
if not player then return false, 'Player not found' end
|
||||
if not minetest.check_player_privs(player, {server=true}) then return false, 'Stomp requires server privileges' end
|
||||
local pos = player:get_pos()
|
||||
local node_pos = {['x'] = pos.x, ['y'] = pos.y - 1, ['z'] = pos.z}
|
||||
local node = minetest.get_node(node_pos)
|
||||
local node_desc = minetest.registered_nodes[node.name]
|
||||
if not node_desc then return end -- unknown node
|
||||
if not stomps[node.name] or #stomps[node.name] == 0 then
|
||||
return false, 'No stomping data found for ' .. node.name
|
||||
elseif #stomps[node.name] > 1 then
|
||||
local num = tonumber(param)
|
||||
if num and num > 0 and num <= #stomps[node.name] then
|
||||
poschangelib.do_stomp(player, node_pos, node, node_desc, stomps[node.name][num])
|
||||
return true
|
||||
end
|
||||
local local_stomps = stomps[node.name]
|
||||
minetest.chat_send_player(name, 'Multiple stomping data found for ' .. node.name)
|
||||
minetest.chat_send_player(name, 'Use /stomp X to choose which one to trigger.')
|
||||
for i, v in ipairs(local_stomps) do
|
||||
minetest.chat_send_player(name, ' ' .. i .. ') ' .. local_stomps[i].name)
|
||||
end
|
||||
return false
|
||||
else
|
||||
poschangelib.do_stomp(player, node_pos, node, node_desc, stomps[node.name][1])
|
||||
return true
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 131 B |
Loading…
Reference in New Issue