Alpha-v0.0.2a #1
|
@ -0,0 +1,13 @@
|
||||||
|
Copyright 2015-2016 raymoo
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
|
@ -0,0 +1,205 @@
|
||||||
|
# Player Monoids
|
||||||
|
|
||||||
|
This is a small library for managing global player state, so that changes made
|
||||||
|
from different mods do not result in unexpected behavior. The README gives an
|
||||||
|
introduction to the mod, but you might want to reference API.md along the way.
|
||||||
|
This mod, combined with playereffects, deprecates monoidal_effects.
|
||||||
|
|
||||||
|
Global Player State
|
||||||
|
===================
|
||||||
|
Players have behavior-affecting state that can be modified through mods. A couple
|
||||||
|
examples are physics overrides and armor groups. If more than one mod tries to
|
||||||
|
change them, it can result in unexpected results.
|
||||||
|
|
||||||
|
For example, a player could be
|
||||||
|
under a speed boost effect from a playereffects effect, and then sleep in a bed.
|
||||||
|
The bed sets the player's speed to 0, and sets it back to 1 when they get out.
|
||||||
|
Because the beds mod had no way of knowing that the player was supposed to have
|
||||||
|
a speed boost, it effectively removed it. One hack to "fix" it would be to save
|
||||||
|
the player's speed and restore it on wakeup, but this would have its own problems
|
||||||
|
if the effect wears off in bed. The beds mod would restore the boosted speed,
|
||||||
|
which wouldn't be removed, since the effect already went away. Thus an exploit
|
||||||
|
allowing a permanent (until log out) speed boost is introduced.
|
||||||
|
|
||||||
|
Player Monoids manages this by creating layers (monoids) on top of player state,
|
||||||
|
which can keep track of different changes and combine them usefully.
|
||||||
|
|
||||||
|
Monoids
|
||||||
|
=======
|
||||||
|
|
||||||
|
Creation
|
||||||
|
--------
|
||||||
|
A monoid in Player Monoids is an interface to one piece of player state. For
|
||||||
|
example, you could have one monoid covering physics overrides, and another
|
||||||
|
covering fly privilege. You could define a speed monoids like this:
|
||||||
|
```
|
||||||
|
-- The values in my speed monoid must be speed multipliers (numbers).
|
||||||
|
mymod.speed_monoid = player_monoids.make_monoid({
|
||||||
|
combine = function(speed1, speed2)
|
||||||
|
return speed1 * speed2
|
||||||
|
end,
|
||||||
|
fold = function(tab)
|
||||||
|
local res = 1
|
||||||
|
for _, speed in pairs(tab) do
|
||||||
|
res = res * speed
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
identity = 1,
|
||||||
|
apply = function(speed, player)
|
||||||
|
local override = player:get_physics_override()
|
||||||
|
override.speed = speed
|
||||||
|
player:set_physics_override(override)
|
||||||
|
end,
|
||||||
|
on_change = function() return end,
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
This says that two speed multipliers can be combined by multiplication, that
|
||||||
|
1 can be used as a neutral element, and that the "interpretation" of the speed
|
||||||
|
multiplier is to set the player's speed physics override to that value. It also
|
||||||
|
says that nothing in particular needs to happen when the speed changes, other
|
||||||
|
than applying the new speed multiplier.
|
||||||
|
|
||||||
|
Use
|
||||||
|
---
|
||||||
|
To add or remove change through a monoid, you must use the ```add_change```
|
||||||
|
and ```del_change``` methods. For example, you could speed the player up
|
||||||
|
temporarily like this:
|
||||||
|
```
|
||||||
|
-- Zoom!
|
||||||
|
local zoom_id = mymod.speed_monoid:add_change(some_player, 10)
|
||||||
|
|
||||||
|
minetest.after(5,function() mymod.speed_monoid:del_change(some_player, zoom_id) end)
|
||||||
|
```
|
||||||
|
You could also specify a string ID to use, instead of the numerical one that
|
||||||
|
is automatically provided:
|
||||||
|
```
|
||||||
|
-- Zoom Mk. II
|
||||||
|
mymod.speed_monoid:add_change(some_player, 10, "mymod:zoom")
|
||||||
|
|
||||||
|
minetest.after(5,function() mymod.speed_monoid:del_change(some_player, "mymod:zoom") end)
|
||||||
|
```
|
||||||
|
|
||||||
|
Reading Values
|
||||||
|
--------------
|
||||||
|
You can use ```monoid:value(player)``` to read the current value of the monoid,
|
||||||
|
for that player. This could be useful if it doesn't just represent built-in
|
||||||
|
player state. For example, it could represent gardening skill, and you might use
|
||||||
|
it to calculate the chance of success when harvesting spices.
|
||||||
|
|
||||||
|
Nesting Monoids
|
||||||
|
---------------
|
||||||
|
You may have already noticed one limitation of this design. That is, for each
|
||||||
|
kind of player state, you can only combine state changes in one way. If the
|
||||||
|
standard speed monoid combines speed multipliers by multiplication, you cannot
|
||||||
|
change it to instead choose the highest speed multiplier. Unfortunately, there
|
||||||
|
is currently no way change this - you will have to hope that the given monoid
|
||||||
|
combines in a useful way. However, it is possible to manage a subset of the
|
||||||
|
values in a custom way.
|
||||||
|
|
||||||
|
Suppose that a speed monoid (```mymod.speed_monoid```) already exists, using
|
||||||
|
multiplication, but you want to write a mod with speed boosts, and only apply
|
||||||
|
the strongest boost. Most of it could be done the same way:
|
||||||
|
```
|
||||||
|
-- My speed boosts monoid takes speed multipliers (numbers) that are at least 1.
|
||||||
|
newmod.speed_boosts = player_monoids.make_monoid({
|
||||||
|
combine = function(speed1, speed2)
|
||||||
|
return speed1 * speed2
|
||||||
|
end,
|
||||||
|
fold = function(tab)
|
||||||
|
local res = 1
|
||||||
|
for _, speed in pairs(tab) do
|
||||||
|
res = res * speed
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
identity = 1,
|
||||||
|
apply = ???
|
||||||
|
on_change = function() return end,
|
||||||
|
})
|
||||||
|
```
|
||||||
|
But we cannot just change the player speed in ```apply```, otherwise we will
|
||||||
|
break compatibility with the original speed monoid! The trick here is to use
|
||||||
|
the original monoid as a proxy for our effects.
|
||||||
|
```
|
||||||
|
apply = function(speed, player)
|
||||||
|
mymod.speed_monoid:add_change(player, speed, "newmod:speed_boosts")
|
||||||
|
end
|
||||||
|
```
|
||||||
|
This means the speed boosts we control can be limited to the strongest boost, but
|
||||||
|
the resulting boost will still play nice with speed effects from other mods.
|
||||||
|
You could even add another "nested monoid" just for speed maluses, that takes
|
||||||
|
the worst speed drain and applies it as a multiplier.
|
||||||
|
|
||||||
|
Standard Monoids
|
||||||
|
================
|
||||||
|
In the spirit of compatibility, this mod provides some canonical monoids for
|
||||||
|
commonly used player state. They combine values in a way that should allow
|
||||||
|
different mods to affect player state fairly. If you make another monoid handling
|
||||||
|
the same state as one of these, you will break compatibility with any mods using
|
||||||
|
the standard monoid.
|
||||||
|
|
||||||
|
Physics Overrides
|
||||||
|
-----------------
|
||||||
|
These monoids set the multiplier of the override they are named after. All three
|
||||||
|
take non-negative numbers as values and combine them with multiplication. They
|
||||||
|
are:
|
||||||
|
* ```player_monoids.speed```
|
||||||
|
* ```player_monoids.jump```
|
||||||
|
* ```player_monoids.gravity```
|
||||||
|
|
||||||
|
Privileges
|
||||||
|
----------
|
||||||
|
These monoids set privileges that affect the player's ordinary gameplay. They
|
||||||
|
take booleans as input and combine them with logical or. They are:
|
||||||
|
* ```player_monoids.fly```
|
||||||
|
* ```player_monoids.noclip```
|
||||||
|
|
||||||
|
Other
|
||||||
|
-----
|
||||||
|
* ```player_monoids.collisionbox``` - Sets the player's collisionbox. Values are
|
||||||
|
3D multiplier vectors, which are combined with component-wise multiplication.
|
||||||
|
* ```player_monoids.visual_size``` - Sets the player's collisionbox. Values are
|
||||||
|
2D multiplier vectors (x and y), which are combined with component-wise
|
||||||
|
multiplication.
|
||||||
|
|
||||||
|
Use with playereffects
|
||||||
|
======================
|
||||||
|
Player Monoids does not provide anything special for persistent effects with
|
||||||
|
limited lifetime. By using monoids with Wuzzy's playereffects, you can easily
|
||||||
|
create temporary effects that stack with each other. As an example, an effect
|
||||||
|
that gives the player 2x speed:
|
||||||
|
```
|
||||||
|
local speed = player_monoids.speed
|
||||||
|
|
||||||
|
local function apply(player)
|
||||||
|
speed:add_change(player, 2, "mymod:2x_speed")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function cancel(player)
|
||||||
|
speed:del_change(player, "mymod:2x_speed")
|
||||||
|
end
|
||||||
|
|
||||||
|
local groups = { "mymod:2x_speed" }
|
||||||
|
|
||||||
|
playereffects.register_effect_type("mymod:2x_speed", "2x Speed", groups, apply, cancel)
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that this effect does NOT use the "speed" effect group. As long as other
|
||||||
|
speed effects use the speed monoid, we do not want them to be cancelled, since
|
||||||
|
the goal is to combine the effects together. It does use a singleton group to
|
||||||
|
prevent multiple instances of the same effect. I think that playereffects require
|
||||||
|
effects to belong to at least one group, but I am not sure.
|
||||||
|
|
||||||
|
Caveats
|
||||||
|
=======
|
||||||
|
* If the global state managed by a monoid is modified by something other than
|
||||||
|
the monoid, you will have the same problem as when two mods both independently
|
||||||
|
try to modify global state without going through a monoid.
|
||||||
|
* This includes playereffects effects that affect global player state without
|
||||||
|
going through a monoid.
|
||||||
|
* You will also get problems if you use multiple monoids to manage the same
|
||||||
|
global state.
|
||||||
|
* The order that different effects get combined together is based on key order,
|
||||||
|
which may not be predictable. So you should try to make your monoids commutative
|
||||||
|
in addition to associative, or at least not care if the order of two changes
|
||||||
|
is swapped.
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"name": "player_monoids",
|
||||||
|
"description": "Library for making player state changes combinable\n",
|
||||||
|
"keywords": [
|
||||||
|
"player_monoids",
|
||||||
|
"monoid",
|
||||||
|
"monoids",
|
||||||
|
"effect",
|
||||||
|
"playereffects"
|
||||||
|
],
|
||||||
|
"homepage": "https://github.com/raymoo/player_monoids",
|
||||||
|
"forum": "https://forum.minetest.net/viewtopic.php?f=9&t=14895",
|
||||||
|
"screenshots": [
|
||||||
|
"https://example.com/screenshot1.png"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
"raymoo"
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
player_monoids is a library for managing global player state, such as physics
|
||||||
|
overrides or player visual size.
|
|
@ -0,0 +1,118 @@
|
||||||
|
-- Copyright (c) raymoo 2016
|
||||||
|
-- Licensed under Apache 2.0 license. See COPYING for details.
|
||||||
|
|
||||||
|
-- Any documentation here are internal details, please avoid using them in your
|
||||||
|
-- mod.
|
||||||
|
|
||||||
|
local modpath = minetest.get_modpath(minetest.get_current_modname()) .. "/"
|
||||||
|
|
||||||
|
player_monoids = {}
|
||||||
|
|
||||||
|
local mon_meta = {}
|
||||||
|
|
||||||
|
mon_meta.__index = mon_meta
|
||||||
|
|
||||||
|
local nop = function() end
|
||||||
|
|
||||||
|
-- A monoid object is a table with the following fields:
|
||||||
|
-- def: The monoid definition
|
||||||
|
-- player_map: A map from player names to their effect tables. Effect tables
|
||||||
|
-- are maps from effect IDs to values.
|
||||||
|
-- value_cache: A map from player names to the cached value for the monoid.
|
||||||
|
-- next_id: The next unique ID to assign an effect.
|
||||||
|
|
||||||
|
local function monoid(def)
|
||||||
|
local mon = {}
|
||||||
|
|
||||||
|
local actual_def = {}
|
||||||
|
|
||||||
|
for k, v in pairs(def) do
|
||||||
|
actual_def[k] = v
|
||||||
|
end
|
||||||
|
|
||||||
|
if not actual_def.apply then
|
||||||
|
actual_def.apply = nop
|
||||||
|
end
|
||||||
|
|
||||||
|
if not actual_def.on_change then
|
||||||
|
actual_def.on_change = nop
|
||||||
|
end
|
||||||
|
|
||||||
|
mon.def = actual_def
|
||||||
|
|
||||||
|
local p_map = {}
|
||||||
|
mon.player_map = p_map
|
||||||
|
|
||||||
|
mon.next_id = 1
|
||||||
|
|
||||||
|
local v_cache = {}
|
||||||
|
mon.value_cache = v_cache
|
||||||
|
|
||||||
|
setmetatable(mon, mon_meta)
|
||||||
|
|
||||||
|
minetest.register_on_leaveplayer(function(player)
|
||||||
|
local p_name = player:get_player_name()
|
||||||
|
p_map[p_name] = nil
|
||||||
|
v_cache[p_name] = nil
|
||||||
|
end)
|
||||||
|
|
||||||
|
return mon
|
||||||
|
end
|
||||||
|
|
||||||
|
player_monoids.monoid = monoid
|
||||||
|
|
||||||
|
function mon_meta:add_change(player, value)
|
||||||
|
local p_name = player:get_player_name()
|
||||||
|
|
||||||
|
local def = self.def
|
||||||
|
|
||||||
|
local p_effects = self.player_map[p_name]
|
||||||
|
if p_effects == nil then
|
||||||
|
p_effects = {}
|
||||||
|
self.player_map[p_name] = p_effects
|
||||||
|
end
|
||||||
|
|
||||||
|
local actual_id
|
||||||
|
|
||||||
|
if id then
|
||||||
|
actual_id = id
|
||||||
|
else
|
||||||
|
actual_id = self.next_id
|
||||||
|
self.next_id = actual_id + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local old_total = self.value_cache[p_name]
|
||||||
|
p_effects[actual_id] = value
|
||||||
|
local new_total = def.fold(p_effects)
|
||||||
|
self.value_cache[p_name] = new_total
|
||||||
|
|
||||||
|
def.apply(new_total, player)
|
||||||
|
def.on_change(old_total, new_total, player)
|
||||||
|
|
||||||
|
return actual_id
|
||||||
|
end
|
||||||
|
|
||||||
|
function mon_meta:del_change(player, id)
|
||||||
|
local p_name = player:get_player_name()
|
||||||
|
|
||||||
|
local def = self.def
|
||||||
|
|
||||||
|
local p_effects = self.player_map[p_name]
|
||||||
|
if p_effects == nil then return end
|
||||||
|
|
||||||
|
local old_total = self.value_cache[p_name]
|
||||||
|
p_effects[id] = nil
|
||||||
|
local new_total = def.fold(p_effects)
|
||||||
|
self.value_cache[p_name] = new_total
|
||||||
|
|
||||||
|
def.apply(new_total, player)
|
||||||
|
def.on_change(old_total, new_total, player)
|
||||||
|
end
|
||||||
|
|
||||||
|
function mon_meta:value(player)
|
||||||
|
local p_name = player:get_player_name()
|
||||||
|
return self.value_cache[p_name] or self.def.identity
|
||||||
|
end
|
||||||
|
|
||||||
|
dofile(modpath .. "standard_monoids.lua")
|
||||||
|
dofile(modpath .. "test.lua")
|
|
@ -0,0 +1 @@
|
||||||
|
name=player_monoids
|
|
@ -0,0 +1,180 @@
|
||||||
|
-- Standard effect monoids, to provide canonicity.
|
||||||
|
|
||||||
|
local function mult(x, y) return x * y end
|
||||||
|
|
||||||
|
local function mult_fold(elems)
|
||||||
|
local tot = 1
|
||||||
|
|
||||||
|
for k,v in pairs(elems) do
|
||||||
|
tot = tot * v
|
||||||
|
end
|
||||||
|
|
||||||
|
return tot
|
||||||
|
end
|
||||||
|
|
||||||
|
local function v_mult(v1, v2)
|
||||||
|
local res = {}
|
||||||
|
|
||||||
|
for k, v in pairs(v1) do
|
||||||
|
res[k] = v * v2[k]
|
||||||
|
end
|
||||||
|
|
||||||
|
return res
|
||||||
|
end
|
||||||
|
|
||||||
|
local function v_mult_fold(identity)
|
||||||
|
return function(elems)
|
||||||
|
local tot = identity
|
||||||
|
|
||||||
|
for k, v in pairs(elems) do
|
||||||
|
tot = v_mult(tot, v)
|
||||||
|
end
|
||||||
|
|
||||||
|
return tot
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local monoid = player_monoids.monoid
|
||||||
|
|
||||||
|
-- Speed monoid. Effect values are speed multipliers. Must be nonnegative
|
||||||
|
-- numbers.
|
||||||
|
player_monoids.speed = monoid({
|
||||||
|
combine = function(x, y) return x * y end,
|
||||||
|
fold = function(elems)
|
||||||
|
local res = 1
|
||||||
|
for k, v in pairs(elems) do
|
||||||
|
res = res * v
|
||||||
|
end
|
||||||
|
|
||||||
|
return res
|
||||||
|
end,
|
||||||
|
identity = 1,
|
||||||
|
apply = function(mult, player)
|
||||||
|
local ov = player:get_physics_override()
|
||||||
|
ov.speed = mult
|
||||||
|
player:set_physics_override(ov)
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
-- Jump monoid. Effect values are jump multipliers. Must be nonnegative
|
||||||
|
-- numbers.
|
||||||
|
player_monoids.jump = monoid({
|
||||||
|
combine = function(x, y) return x * y end,
|
||||||
|
fold = function(elems)
|
||||||
|
local res = 1
|
||||||
|
for k, v in pairs(elems) do
|
||||||
|
res = res * v
|
||||||
|
end
|
||||||
|
|
||||||
|
return res
|
||||||
|
end,
|
||||||
|
identity = 1,
|
||||||
|
apply = function(mult, player)
|
||||||
|
local ov = player:get_physics_override()
|
||||||
|
ov.jump = mult
|
||||||
|
player:set_physics_override(ov)
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
-- Gravity monoid. Effect values are gravity multipliers.
|
||||||
|
player_monoids.gravity = monoid({
|
||||||
|
combine = function(x, y) return x * y end,
|
||||||
|
fold = function(elems)
|
||||||
|
local res = 1
|
||||||
|
for k, v in pairs(elems) do
|
||||||
|
res = res * v
|
||||||
|
end
|
||||||
|
|
||||||
|
return res
|
||||||
|
end,
|
||||||
|
identity = 1,
|
||||||
|
apply = function(mult, player)
|
||||||
|
local ov = player:get_physics_override()
|
||||||
|
ov.gravity = mult
|
||||||
|
player:set_physics_override(ov)
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
-- Fly ability monoid. The values are booleans, which are combined by or. A true
|
||||||
|
-- value indicates having the ability to fly.
|
||||||
|
player_monoids.fly = monoid({
|
||||||
|
combine = function(p, q) return p or q end,
|
||||||
|
fold = function(elems)
|
||||||
|
for k, v in pairs(elems) do
|
||||||
|
if v then return true end
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end,
|
||||||
|
identity = false,
|
||||||
|
apply = function(can_fly, player)
|
||||||
|
local p_name = player:get_player_name()
|
||||||
|
local privs = minetest.get_player_privs(p_name)
|
||||||
|
|
||||||
|
if can_fly then
|
||||||
|
privs.fly = true
|
||||||
|
else
|
||||||
|
privs.fly = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
minetest.set_player_privs(p_name, privs)
|
||||||
|
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
-- Noclip ability monoid. Works the same as fly monoid.
|
||||||
|
player_monoids.noclip = monoid({
|
||||||
|
combine = function(p, q) return p or q end,
|
||||||
|
fold = function(elems)
|
||||||
|
for k, v in pairs(elems) do
|
||||||
|
if v then return true end
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end,
|
||||||
|
identity = false,
|
||||||
|
apply = function(can_noclip, player)
|
||||||
|
local p_name = player:get_player_name()
|
||||||
|
local privs = minetest.get_player_privs(p_name)
|
||||||
|
|
||||||
|
if can_noclip then
|
||||||
|
privs.noclip = true
|
||||||
|
else
|
||||||
|
privs.noclip = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
minetest.set_player_privs(p_name, privs)
|
||||||
|
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
local def_col_scale = { x=0.3, y=1, z=0.3 }
|
||||||
|
|
||||||
|
-- Collisionbox scaling factor. Values are a vector of x, y, z multipliers.
|
||||||
|
player_monoids.collisionbox = monoid({
|
||||||
|
combine = v_mult,
|
||||||
|
fold = v_mult_fold({x=1, y=1, z=1}),
|
||||||
|
identity = {x=1, y=1, z=1},
|
||||||
|
apply = function(multiplier, player)
|
||||||
|
local v = vector.multiply(def_col_scale, multiplier)
|
||||||
|
|
||||||
|
player:set_properties({
|
||||||
|
collisionbox = { -v.x, -v.y, -v.z, v.z, v.y, v.z }
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
player_monoids.visual_size = monoid({
|
||||||
|
combine = v_mult,
|
||||||
|
fold = v_mult_fold({x=1, y=1}),
|
||||||
|
identity = {x=1, y=1},
|
||||||
|
apply = function(multiplier, player)
|
||||||
|
player:set_properties({
|
||||||
|
visual_size = multiplier
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
})
|
|
@ -0,0 +1,27 @@
|
||||||
|
|
||||||
|
local speed = player_monoids.speed
|
||||||
|
|
||||||
|
minetest.register_privilege("monoid_master", {
|
||||||
|
description = "Allows testing of player monoids.",
|
||||||
|
give_to_singleplayer = false,
|
||||||
|
})
|
||||||
|
|
||||||
|
local function test(player)
|
||||||
|
local ch_id = speed:add_change(player, 10)
|
||||||
|
local p_name = player:get_player_name()
|
||||||
|
|
||||||
|
minetest.chat_send_player(p_name, "Your speed is: " .. speed:value(player))
|
||||||
|
|
||||||
|
minetest.after(3, function()
|
||||||
|
speed:del_change(player, ch_id)
|
||||||
|
minetest.chat_send_player(p_name, "Your speed is: " .. speed:value(player))
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
minetest.register_chatcommand("test_monoids", {
|
||||||
|
description = "Runs a test on monoids",
|
||||||
|
privs = { monoid_master = true },
|
||||||
|
func = function(p_name)
|
||||||
|
test(minetest.get_player_by_name(p_name))
|
||||||
|
end,
|
||||||
|
})
|
Loading…
Reference in New Issue