Builtin: Backport MT5 Game

This commit is contained in:
MoNTE48 2020-02-04 17:43:19 +01:00
parent b3a34dd850
commit fe5941bdf7
10 changed files with 497 additions and 203 deletions

View File

@ -200,9 +200,6 @@ function table.indexof(list, val)
return -1
end
assert(table.indexof({"foo", "bar"}, "foo") == 1)
assert(table.indexof({"foo", "bar"}, "baz") == -1)
--------------------------------------------------------------------------------
if INIT ~= "client" then
function file_exists(filename)
@ -220,8 +217,6 @@ function string:trim()
return (self:gsub("^%s*(.-)%s*$", "%1"))
end
assert(string.trim("\n \t\tfoo bar\t ") == "foo bar")
--------------------------------------------------------------------------------
function math.hypot(x, y)
local t
@ -245,6 +240,20 @@ function math.sign(x, tolerance)
return 0
end
--------------------------------------------------------------------------------
function math.factorial(x)
assert(x % 1 == 0 and x >= 0, "factorial expects a non-negative integer")
if x >= 171 then
-- 171! is greater than the biggest double, no need to calculate
return math.huge
end
local v = 1
for k = 2, x do
v = v * k
end
return v
end
--------------------------------------------------------------------------------
function get_last_folder(text,count)
local parts = text:split(DIR_DELIM)
@ -462,6 +471,12 @@ function core.explode_scrollbar_event(evt)
return retval
end
--------------------------------------------------------------------------------
function core.rgba(r, g, b, a)
return a and string.format("#%02X%02X%02X%02X", r, g, b, a) or
string.format("#%02X%02X%02X", r, g, b)
end
--------------------------------------------------------------------------------
function core.pos_to_string(pos, decimal_places)
local x = pos.x
@ -500,10 +515,6 @@ function core.string_to_pos(value)
return nil
end
assert(core.string_to_pos("10.0, 5, -2").x == 10)
assert(core.string_to_pos("( 10.0, 5, -2)").z == -2)
assert(core.string_to_pos("asd, 5, -2)") == nil)
--------------------------------------------------------------------------------
function core.string_to_area(value)
local p1, p2 = unpack(value:split(") ("))
@ -545,6 +556,39 @@ function table.copy(t, seen)
end
return n
end
function table.insert_all(t, other)
for i=1, #other do
t[#t + 1] = other[i]
end
return t
end
function table.key_value_swap(t)
local ti = {}
for k,v in pairs(t) do
ti[v] = k
end
return ti
end
function table.shuffle(t, from, to, random)
from = from or 1
to = to or #t
random = random or math.random
local n = to - from + 1
while n > 1 do
local r = from + n-1
local l = from + random(0, n-1)
t[l], t[r] = t[r], t[l]
n = n-1
end
end
--------------------------------------------------------------------------------
-- mainmenu only functions
--------------------------------------------------------------------------------
@ -629,6 +673,13 @@ end
-- Returns the exact coordinate of a pointed surface
--------------------------------------------------------------------------------
function core.pointed_thing_to_face_pos(placer, pointed_thing)
-- Avoid crash in some situations when player is inside a node, causing
-- 'above' to equal 'under'.
if vector.equals(pointed_thing.above, pointed_thing.under) then
return pointed_thing.under
end
local eye_height = placer:get_properties().eye_height
local eye_offset_first = placer:get_eye_offset()
local node_pos = pointed_thing.under
local camera_pos = placer:get_pos()
@ -648,7 +699,7 @@ function core.pointed_thing_to_face_pos(placer, pointed_thing)
end
local fine_pos = {[nc] = node_pos[nc] + offset}
camera_pos.y = camera_pos.y + 1.625 + eye_offset_first.y / 10
camera_pos.y = camera_pos.y + eye_height + eye_offset_first.y / 10
local f = (node_pos[nc] + offset - camera_pos[nc]) / look_dir[nc]
for i = 1, #oc do
@ -656,3 +707,25 @@ function core.pointed_thing_to_face_pos(placer, pointed_thing)
end
return fine_pos
end
function core.string_to_privs(str, delim)
assert(type(str) == "string")
delim = delim or ','
local privs = {}
for _, priv in pairs(string.split(str, delim)) do
privs[priv:trim()] = true
end
return privs
end
function core.privs_to_string(privs, delim)
assert(type(privs) == "table")
delim = delim or ','
local list = {}
for priv, bool in pairs(privs) do
if bool then
list[#list + 1] = priv
end
end
return table.concat(list, delim)
end

View File

@ -82,18 +82,15 @@ end
core.builtin_auth_handler = {
get_auth = function(name)
assert(type(name) == "string")
-- Figure out what password to use for a new player (singleplayer
-- always has an empty password, otherwise use default, which is
-- usually empty too)
local new_password_hash = ""
-- If not in authentication table, return nil
if not core.auth_table[name] then
local auth_entry = core.auth_table[name]
-- If no such auth found, return nil
if not auth_entry then
return nil
end
-- Figure out what privileges the player should have.
-- Take a copy of the privilege table
local privileges = {}
for priv, _ in pairs(core.auth_table[name].privileges) do
for priv, _ in pairs(auth_entry.privileges) do
privileges[priv] = true
end
-- If singleplayer, give all privileges except those marked as give_to_singleplayer = false
@ -106,15 +103,17 @@ core.builtin_auth_handler = {
-- For the admin, give everything
elseif name == core.settings:get("name") then
for priv, def in pairs(core.registered_privileges) do
privileges[priv] = true
if def.give_to_admin then
privileges[priv] = true
end
end
end
-- All done
return {
password = core.auth_table[name].password,
password = auth_entry.password,
privileges = privileges,
-- Is set to nil if unknown
last_login = core.auth_table[name].last_login,
last_login = auth_entry.last_login,
}
end,
create_auth = function(name, password)
@ -130,12 +129,13 @@ core.builtin_auth_handler = {
set_password = function(name, password)
assert(type(name) == "string")
assert(type(password) == "string")
if not core.auth_table[name] then
local auth_entry = core.auth_table[name]
if not auth_entry then
core.log("action", "[AUTH] Setting password for new player " .. name)
core.builtin_auth_handler.create_auth(name, password)
else
core.log("action", "[AUTH] Setting password for existing player " .. name)
core.auth_table[name].password = password
auth_entry.password = password
end
return true
end,
@ -143,12 +143,28 @@ core.builtin_auth_handler = {
core.log("action", "[AUTH] Setting privileges for player " .. name)
assert(type(name) == "string")
assert(type(privileges) == "table")
if not core.auth_table[name] then
core.builtin_auth_handler.create_auth(name,
local auth_entry = core.auth_table[name]
if not auth_entry then
auth_entry = core.builtin_auth_handler.create_auth(name,
core.get_password_hash(name,
core.settings:get("default_password")))
end
core.auth_table[name].privileges = privileges
-- Run grant callbacks
for priv, _ in pairs(privileges) do
if not auth_entry.privileges[priv] then
core.run_priv_callbacks(name, priv, nil, "grant")
end
end
-- Run revoke callbacks
for priv, _ in pairs(auth_entry.privileges) do
if not privileges[priv] then
core.run_priv_callbacks(name, priv, nil, "revoke")
end
end
auth_entry.privileges = privileges
core.notify_authentication_modified(name)
end,
reload = function()
@ -163,10 +179,36 @@ core.builtin_auth_handler = {
end,
record_login = function(name)
assert(type(name) == "string")
assert(core.auth_table[name]).last_login = os.time()
local auth_entry = core.auth_table[name]
assert(auth_entry)
auth_entry.last_login = os.time()
end,
}
core.register_on_prejoinplayer(function(name, ip)
if core.registered_auth_handler ~= nil then
return -- Don't do anything if custom auth handler registered
end
local auth_entry = core.auth_table
if auth_entry[name] ~= nil then
return
end
local name_lower = name:lower()
for k in pairs(auth_entry) do
if k:lower() == name_lower then
return string.format("\nYou can not register as '%s'! "..
"Another player called '%s' is already registered. "..
"Please check the spelling if it's your account "..
"or use a different name.", name, k)
end
end
end)
--
-- Authentication API
--
function core.register_authentication_handler(handler)
if core.registered_auth_handler then
error("Add-on authentication handler already registered by "..core.registered_auth_handler_modname)
@ -198,7 +240,6 @@ core.auth_commit = auth_pass("commit")
core.auth_reload()
local record_login = auth_pass("record_login")
core.register_on_joinplayer(function(player)
record_login(player:get_player_name())
end)
@ -207,23 +248,6 @@ core.register_on_shutdown(function()
core.auth_commit()
end)
core.register_on_prejoinplayer(function(name, ip)
local auth = core.auth_table
if auth[name] ~= nil then
return
end
local name_lower = name:lower()
for k in pairs(auth) do
if k:lower() == name_lower then
return string.format("\nYou can not register as '%s'! "..
"Another player called '%s' is already registered. "..
"Please check the spelling if it's your account "..
"or use a different name.", name, k)
end
end
end)
-- Autosave
if not core.is_singleplayer() then
local save_interval = 600

View File

@ -1,4 +1,4 @@
-- Minetest: builtin/game/chatcommands.lua
-- Minetest: builtin/game/chat.lua
--
-- Chat command handler
@ -27,9 +27,9 @@ core.register_on_chat_message(function(name, message)
local has_privs, missing_privs = core.check_player_privs(name, cmd_def.privs)
if has_privs then
core.set_last_run_mod(cmd_def.mod_origin)
local success, message = cmd_def.func(name, param)
if message then
core.chat_send_player(name, message)
local _, result = cmd_def.func(name, param)
if result then
core.chat_send_player(name, result)
end
else
core.chat_send_player(name, "You don't have permission"
@ -41,7 +41,7 @@ end)
if core.settings:get_bool("profiler.load") then
-- Run after register_chatcommand and its register_on_chat_message
-- Before any chattcommands that should be profiled
-- Before any chatcommands that should be profiled
profiler.init_chatcommand()
end
@ -80,7 +80,7 @@ core.register_chatcommand_alias = register_chatcommand_alias
--
core.register_chatcommand("me", {
params = "<action>",
description = "Display chat action (e.g., '/me orders a pizza' displays"
description = "Show chat action (e.g., '/me orders a pizza' displays"
.. " '<player name> orders a pizza')",
privs = {shout = true},
func = function(name, param)
@ -93,7 +93,7 @@ core.register_chatcommand("admin", {
func = function(name)
local admin = core.settings:get("name")
if admin then
return true, "The administrator of this server is "..admin.."."
return true, "The administrator of this server is " .. admin .. "."
else
return false, "There's no administrator named in the config file."
end
@ -102,16 +102,44 @@ core.register_chatcommand("admin", {
core.register_chatcommand("privs", {
params = "[<name>]",
description = "Print privileges of player",
description = "Show privileges of yourself or another player",
func = function(caller, param)
param = param:trim()
local name = (param ~= "" and param or caller)
if not core.player_exists(name) then
return false, "Player " .. name .. " does not exist."
end
return true, "Privileges of " .. name .. ": "
.. core.privs_to_string(
core.get_player_privs(name), ", ")
end
})
core.register_chatcommand("haspriv", {
params = "<privilege>",
description = "Return list of all online players with privilege.",
privs = {basic_privs = true},
func = function(caller, param)
param = param:trim()
if param == "" then
return false, "Invalid parameters (see /help haspriv)"
end
if not core.registered_privileges[param] then
return false, "Unknown privilege!"
end
local privs = core.string_to_privs(param)
local players_with_priv = {}
for _, player in pairs(core.get_connected_players()) do
local player_name = player:get_player_name()
if core.check_player_privs(player_name, privs) then
table.insert(players_with_priv, player_name)
end
end
return true, "Players online with the \"" .. param .. "\" privilege: " ..
table.concat(players_with_priv, ", ")
end
})
local function handle_grant_command(caller, grantname, grantprivstr)
local caller_privs = core.get_player_privs(caller)
if not (caller_privs.privs or caller_privs.basic_privs) then
@ -141,6 +169,10 @@ local function handle_grant_command(caller, grantname, grantprivstr)
if privs_unknown ~= "" then
return false, privs_unknown
end
for priv, _ in pairs(grantprivs) do
-- call the on_grant callbacks
core.run_priv_callbacks(grantname, priv, caller, "grant")
end
core.set_player_privs(grantname, privs)
core.log("action", caller..' granted ('..core.privs_to_string(grantprivs, ', ')..') privileges to '..grantname)
if grantname ~= caller then
@ -166,7 +198,7 @@ core.register_chatcommand("grant", {
})
core.register_chatcommand("grantme", {
params = "<privilege>|all",
params = "<privilege> | all",
description = "Grant privileges to yourself",
func = function(name, param)
if param == "" then
@ -178,7 +210,7 @@ core.register_chatcommand("grantme", {
core.register_chatcommand("revoke", {
params = "<name> (<privilege> | all)",
description = "Remove privilege from player",
description = "Remove privileges from player",
privs = {},
func = function(name, param)
if not core.check_player_privs(name, {privs = true}) and
@ -202,12 +234,19 @@ core.register_chatcommand("revoke", {
end
end
if revoke_priv_str == "all" then
revoke_privs = privs
privs = {}
else
for priv, _ in pairs(revoke_privs) do
privs[priv] = nil
end
end
for priv, _ in pairs(revoke_privs) do
-- call the on_revoke callbacks
core.run_priv_callbacks(revoke_name, priv, name, "revoke")
end
core.set_player_privs(revoke_name, privs)
core.log("action", name..' revoked ('
..core.privs_to_string(revoke_privs, ', ')
@ -233,11 +272,12 @@ core.register_chatcommand("setpassword", {
toname = param:match("^([^ ]+) *$")
raw_password = nil
end
if not toname then
return false, "Name field required"
end
local act_str_past = "?"
local act_str_pres = "?"
local act_str_past, act_str_pres
if not raw_password then
core.set_player_password(toname, "")
act_str_past = "cleared"
@ -249,13 +289,14 @@ core.register_chatcommand("setpassword", {
act_str_past = "set"
act_str_pres = "sets"
end
if toname ~= name then
core.chat_send_player(toname, "Your password was "
.. act_str_past .. " by " .. name)
end
core.log("action", name .. " " .. act_str_pres
.. " password of " .. toname .. ".")
core.log("action", name .. " " .. act_str_pres ..
" password of " .. toname .. ".")
return true, "Password of player \"" .. toname .. "\" " .. act_str_past
end
@ -263,7 +304,7 @@ core.register_chatcommand("setpassword", {
core.register_chatcommand("clearpassword", {
params = "<name>",
description = "Set empty password",
description = "Set empty password for a player",
privs = {password = true},
func = function(name, param)
local toname = param
@ -289,7 +330,7 @@ core.register_chatcommand("auth_reload", {
core.register_chatcommand("remove_player", {
params = "<name>",
description = "Remove player data",
description = "Remove a player's data",
privs = {server = true},
func = function(name, param)
local toname = param
@ -323,7 +364,7 @@ core.register_chatcommand("auth_save", {
core.register_chatcommand("teleport", {
params = "<X>,<Y>,<Z> | <to_name> | (<name> <X>,<Y>,<Z>) | (<name> <to_name>)",
description = "Teleport to player or position",
description = "Teleport to position or player",
privs = {teleport = true},
func = function(name, param)
-- Returns (pos, true) if found, otherwise (pos, false)
@ -347,7 +388,6 @@ core.register_chatcommand("teleport", {
return pos, false
end
local teleportee
local p = {}
p.x, p.y, p.z = string.match(param, "^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
p.x = tonumber(p.x)
@ -358,37 +398,37 @@ core.register_chatcommand("teleport", {
if p.x < -lm or p.x > lm or p.y < -lm or p.y > lm or p.z < -lm or p.z > lm then
return false, "Cannot teleport out of map bounds!"
end
teleportee = core.get_player_by_name(name)
local teleportee = core.get_player_by_name(name)
if teleportee then
teleportee:set_pos(p)
return true, "Teleporting to " .. core.pos_to_string(vector.round(p))
return true, "Teleporting to " .. core.pos_to_string(p, 1)
end
end
local teleportee
local p
local target_name
target_name = param:match("^([^ ]+)$")
teleportee = core.get_player_by_name(name)
local target_name = param:match("^([^ ]+)$")
local teleportee = core.get_player_by_name(name)
p = nil
if target_name then
local target = core.get_player_by_name(target_name)
if target then
p = target:get_pos()
end
end
if teleportee and p then
p = find_free_position_near(p)
teleportee:set_pos(p)
return true, "Teleporting to " .. target_name
.. " at " .. core.pos_to_string(vector.round(p))
.. " at " .. core.pos_to_string(p, 1)
end
if not core.check_player_privs(name, {bring = true}) then
return false, "You don't have permission to teleport other players (missing bring privilege)"
end
local teleportee
local p = {}
teleportee = nil
p = {}
local teleportee_name
teleportee_name, p.x, p.y, p.z = param:match(
"^([^ ]+) +([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
@ -399,13 +439,11 @@ core.register_chatcommand("teleport", {
if teleportee and p.x and p.y and p.z then
teleportee:set_pos(p)
return true, "Teleporting " .. teleportee_name
.. " to " .. core.pos_to_string(vector.round(p))
.. " to " .. core.pos_to_string(p, 1)
end
local teleportee
local p
local teleportee_name
local target_name
teleportee = nil
p = nil
teleportee_name, target_name = string.match(param, "^([^ ]+) +([^ ]+)$")
if teleportee_name then
teleportee = core.get_player_by_name(teleportee_name)
@ -421,7 +459,7 @@ core.register_chatcommand("teleport", {
teleportee:set_pos(p)
return true, "Teleporting " .. teleportee_name
.. " to " .. target_name
.. " at " .. core.pos_to_string(vector.round(p))
.. " at " .. core.pos_to_string(p, 1)
end
return false, 'Invalid parameters ("' .. param
@ -440,7 +478,8 @@ core.register_chatcommand("set", {
core.settings:set(setname, setvalue)
return true, setname .. " = " .. setvalue
end
local setname, setvalue = string.match(param, "([^ ]+) (.+)")
setname, setvalue = string.match(param, "([^ ]+) (.+)")
if setname and setvalue then
if not core.settings:get(setname) then
return false, "Failed. Use '/set -n <name> <value>' to create a new setting."
@ -448,14 +487,16 @@ core.register_chatcommand("set", {
core.settings:set(setname, setvalue)
return true, setname .. " = " .. setvalue
end
local setname = string.match(param, "([^ ]+)")
setname = string.match(param, "([^ ]+)")
if setname then
local setvalue = core.settings:get(setname)
setvalue = core.settings:get(setname)
if not setvalue then
setvalue = "<not set>"
end
return true, setname .. " = " .. setvalue
end
return false, "Invalid parameters (see /help set)."
end
})
@ -563,15 +604,12 @@ local function handle_give_command(cmd, giver, receiver, stackstring)
core.log("action", giver .. " invoked " .. cmd
.. ', stackstring="' .. stackstring .. '"')
local ritems = core.registered_items
local rnodes = core.registered_nodes
if not string.match(stackstring, ":") and
not ritems[stackstring] and not rnodes[stackstring] then
if not string.match(stackstring, ":") and not ritems[stackstring] then
local modslist = core.get_modnames()
local namestring = stackstring:match("(%w+)")
table.insert(modslist, 1, "default")
for _, modname in pairs(modslist) do
local namecheck = modname .. ":" .. namestring
if ritems[namecheck] or rnodes[namecheck] then
local namecheck = modname .. ":" .. stackstring:match("(%w+)")
if ritems[namecheck] then
stackstring = modname .. ":" .. stackstring
break
end
@ -580,8 +618,11 @@ local function handle_give_command(cmd, giver, receiver, stackstring)
local itemstack = ItemStack(stackstring)
if itemstack:is_empty() then
return false, "Cannot give an empty item"
elseif not itemstack:is_known() then
elseif (not itemstack:is_known()) or (itemstack:get_name() == "unknown") then
return false, "Cannot give an unknown item"
-- Forbid giving 'ignore' due to unwanted side effects
elseif itemstack:get_name() == "ignore" then
return false, "Giving 'ignore' is not allowed"
end
local receiverref = core.get_player_by_name(receiver)
if receiverref == nil then
@ -600,18 +641,18 @@ local function handle_give_command(cmd, giver, receiver, stackstring)
-- entered (e.g. big numbers are always interpreted as 2^16-1).
stackstring = itemstack:to_string()
if giver == receiver then
return true, ("%q %sadded to inventory.")
:format(stackstring, partiality)
local msg = "%q %sadded to inventory."
return true, msg:format(stackstring, partiality)
else
core.chat_send_player(receiver, ("%q %sadded to inventory.")
:format(stackstring, partiality))
return true, ("%q %sadded to %s's inventory.")
:format(stackstring, partiality, receiver)
local msg = "%q %sadded to %s's inventory."
return true, msg:format(stackstring, partiality, receiver)
end
end
core.register_chatcommand("give", {
params = "<name> <ItemString>",
params = "<name> <ItemString> [<count> [<wear>]]",
description = "Give item to player",
privs = {give = true},
func = function(name, param)
@ -624,7 +665,7 @@ core.register_chatcommand("give", {
})
core.register_chatcommand("giveme", {
params = "<ItemString>",
params = "<ItemString> [<count> [<wear>]]",
description = "Give item to yourself",
privs = {give = true},
func = function(name, param)
@ -652,6 +693,9 @@ core.register_chatcommand("spawnentity", {
core.log("error", "Unable to spawn entity, player is nil")
return false, "Unable to spawn entity, player is nil"
end
if not core.registered_entities[entityname] then
return false, "Cannot spawn an unknown entity"
end
if p == "" then
p = player:get_pos()
else
@ -686,8 +730,8 @@ core.register_chatcommand("pulverize", {
core.rollback_punch_callbacks = {}
core.register_on_punchnode(function(pos, node, puncher)
local name = puncher:get_player_name()
if core.rollback_punch_callbacks[name] then
local name = puncher and puncher:get_player_name()
if name and core.rollback_punch_callbacks[name] then
core.rollback_punch_callbacks[name](pos, node, puncher)
core.rollback_punch_callbacks[name] = nil
end
@ -785,16 +829,20 @@ core.register_chatcommand("rollback", {
})
core.register_chatcommand("status", {
description = "Print server status",
description = "Show server status",
privs = {server = true},
func = function(name, param)
return true, core.get_server_status()
local status = core.get_server_status(name, false)
if status and status ~= "" then
return true, status
end
return false, "This command was disabled by a mod or game"
end
})
core.register_chatcommand("settime", {
params = "<0..23>:<0..59> | <0..24000>",
description = "Set time of day",
core.register_chatcommand("time", {
params = "[<0..23>:<0..59> | <0..24000>]",
description = "Show or set time of day",
privs = {},
func = function(name, param)
if param == "" then
@ -831,10 +879,10 @@ core.register_chatcommand("settime", {
return true, "Time of day changed."
end
})
register_chatcommand_alias("time", "settime")
register_chatcommand_alias("settime", "time")
core.register_chatcommand("days", {
description = "Display day count",
description = "Show day count since world creation",
func = function(name, param)
return true, "Current day is " .. core.get_day_count()
end
@ -845,13 +893,15 @@ core.register_chatcommand("shutdown", {
description = "Shutdown server (-1 cancels a delayed shutdown)",
privs = {server = true},
func = function(name, param)
local delay, reconnect, message = param:match("([^ ][-]?[0-9]+)([^ ]+)(.*)")
message = message or ""
local delay, reconnect, message
delay, param = param:match("^%s*(%S+)(.*)")
if param then
reconnect, param = param:match("^%s*(%S+)(.*)")
end
message = param and param:match("^%s*(.+)") or ""
delay = tonumber(delay) or 0
if delay ~= "" then
delay = tonumber(param) or 0
else
delay = 0
if delay == 0 then
core.log("action", name .. " shuts down server")
core.chat_send_all("*** Server shutting down (operator request).")
end
@ -860,15 +910,20 @@ core.register_chatcommand("shutdown", {
})
core.register_chatcommand("ban", {
params = "<name>",
description = "Ban IP of player",
params = "[<name>]",
description = "Ban the IP of a player or show the ban list",
privs = {ban = true},
func = function(name, param)
if param == "" then
return true, "Ban list: " .. core.get_ban_list()
local ban_list = core.get_ban_list()
if ban_list == "" then
return true, "The ban list is empty."
else
return true, "Ban list: " .. ban_list
end
end
if not core.get_player_by_name(param) then
return false, "No such player."
return false, "Player is not online."
end
if not core.ban_player(param) then
return false, "Failed to ban player."
@ -881,7 +936,7 @@ core.register_chatcommand("ban", {
core.register_chatcommand("unban", {
params = "<name> | <IP_address>",
description = "Remove IP ban",
description = "Remove IP ban belonging to a player/IP",
privs = {ban = true},
func = function(name, param)
if not core.unban_player_or_ip(param) then
@ -912,7 +967,7 @@ core.register_chatcommand("kick", {
})
core.register_chatcommand("clearobjects", {
params = "[full|quick]",
params = "[full | quick]",
description = "Clear all objects in world",
privs = {server = true},
func = function(name, param)
@ -957,10 +1012,11 @@ core.register_chatcommand("msg", {
end
})
register_chatcommand_alias("m", "msg")
register_chatcommand_alias("pm", "msg")
core.register_chatcommand("last-login", {
params = "[<name>]",
description = "Get the last login time of a player",
description = "Get the last login time of a player or yourself",
func = function(name, param)
if param == "" then
param = name
@ -983,7 +1039,7 @@ core.register_chatcommand("clearinv", {
if param and param ~= "" and param ~= name then
if not core.check_player_privs(name, {server = true}) then
return false, "You don't have permission"
.. " to run this command (missing privilege: server)"
.. " to clear another player's inventory (missing privilege: server)"
end
player = core.get_player_by_name(param)
core.chat_send_player(param, name.." cleared your inventory.")
@ -1059,13 +1115,9 @@ core.register_chatcommand("setspawn", {
if not player then
return false
end
local pos = vector.round(player:get_pos())
local x = pos.x
local y = pos.y
local z = pos.z
local pos_to_string = x .. "," .. y .. "," .. z
core.settings:set("static_spawnpoint", pos_to_string)
return true, "Setting spawn point to (" .. pos_to_string .. ")"
local pos = minetest.pos_to_string(player:get_pos(), 1)
core.settings:set("static_spawnpoint", pos)
return true, "The spawn point are set to (" .. pos .. ")"
end
})

View File

@ -21,6 +21,10 @@ core.EMERGE_GENERATED = 4
-- constants.h
-- Size of mapblocks in nodes
core.MAP_BLOCKSIZE = 16
-- Default maximal HP of a player
core.PLAYER_MAX_HP_DEFAULT = 20
-- Default maximal breath of a player
core.PLAYER_MAX_BREATH_DEFAULT = 11
-- light.h
-- Maximum value for node 'light_source' parameter

View File

@ -17,3 +17,8 @@ function core.create_detached_inventory(name, callbacks, player_name)
core.detached_inventories[name] = stuff
return core.create_detached_inventory_raw(name, player_name)
end
function core.remove_detached_inventory(name)
core.detached_inventories[name] = nil
return core.remove_detached_inventory_raw(name)
end

View File

@ -8,6 +8,9 @@ local blocks_forceloaded
local blocks_temploaded = {}
local total_forceloaded = 0
-- true, if the forceloaded blocks got changed (flag for persistence on-disk)
local forceload_blocks_changed = false
local BLOCKSIZE = core.MAP_BLOCKSIZE
local function get_blockpos(pos)
return {
@ -31,6 +34,9 @@ local function get_relevant_tables(transient)
end
function core.forceload_block(pos, transient)
-- set changed flag
forceload_blocks_changed = true
local blockpos = get_blockpos(pos)
local hash = core.hash_node_position(blockpos)
local relevant_table, other_table = get_relevant_tables(transient)
@ -51,6 +57,9 @@ function core.forceload_block(pos, transient)
end
function core.forceload_free_block(pos, transient)
-- set changed flag
forceload_blocks_changed = true
local blockpos = get_blockpos(pos)
local hash = core.hash_node_position(blockpos)
local relevant_table, other_table = get_relevant_tables(transient)
@ -95,6 +104,28 @@ core.after(5, function()
end
end)
core.register_on_shutdown(function()
-- persists the currently forceloaded blocks to disk
local function persist_forceloaded_blocks()
write_file(wpath.."/force_loaded.txt", blocks_forceloaded)
end)
end
-- periodical forceload persistence
local function periodically_persist_forceloaded_blocks()
-- only persist if the blocks actually changed
if forceload_blocks_changed then
persist_forceloaded_blocks()
-- reset changed flag
forceload_blocks_changed = false
end
-- recheck after some time
core.after(10, periodically_persist_forceloaded_blocks)
end
-- persist periodically
core.after(5, periodically_persist_forceloaded_blocks)
-- persist on shutdown
core.register_on_shutdown(persist_forceloaded_blocks)

View File

@ -39,26 +39,41 @@ function core.check_player_privs(name, ...)
return true, ""
end
local player_list = {}
core.register_on_joinplayer(function(player)
local player_name = player:get_player_name()
player_list[player_name] = player
if not core.is_singleplayer() then
core.chat_send_all("=> " .. player_name .. " has joined the server")
end
end)
core.register_on_leaveplayer(function(player, timed_out)
local player_name = player:get_player_name()
player_list[player_name] = nil
function core.send_join_message(player_name)
core.chat_send_all("=> " .. player_name .. " has joined the server")
end
function core.send_leave_message(player_name, timed_out)
local announcement = "<= " .. player_name .. " left the server"
if timed_out then
announcement = announcement .. " (timed out)"
end
core.chat_send_all(announcement)
end
core.register_on_joinplayer(function(player)
local player_name = player:get_player_name()
player_list[player_name] = player
if not core.is_singleplayer() then
core.send_join_message(player_name)
end
end)
core.register_on_leaveplayer(function(player, timed_out)
local player_name = player:get_player_name()
player_list[player_name] = nil
core.send_leave_message(player_name, timed_out)
end)
function core.get_connected_players()
local temp_table = {}
for _, value in pairs(player_list) do
@ -83,8 +98,10 @@ function core.player_exists(name)
return core.get_auth_handler().get_auth(name) ~= nil
end
-- Returns two position vectors representing a box of `radius` in each
-- direction centered around the player corresponding to `player_name`
function core.get_player_radius_area(player_name, radius)
local player = core.get_player_by_name(player_name)
if player == nil then
@ -115,19 +132,23 @@ function core.is_valid_pos(pos)
end
function core.hash_node_position(pos)
return (pos.z+32768)*65536*65536 + (pos.y+32768)*65536 + pos.x+32768
return (pos.z + 32768) * 65536 * 65536
+ (pos.y + 32768) * 65536
+ pos.x + 32768
end
function core.get_position_from_hash(hash)
local pos = {}
pos.x = (hash%65536) - 32768
hash = math.floor(hash/65536)
pos.y = (hash%65536) - 32768
hash = math.floor(hash/65536)
pos.z = (hash%65536) - 32768
pos.x = (hash % 65536) - 32768
hash = math.floor(hash / 65536)
pos.y = (hash % 65536) - 32768
hash = math.floor(hash / 65536)
pos.z = (hash % 65536) - 32768
return pos
end
function core.get_item_group(name, group)
if not core.registered_items[name] or not
core.registered_items[name].groups[group] then
@ -136,11 +157,13 @@ function core.get_item_group(name, group)
return core.registered_items[name].groups[group]
end
function core.get_node_group(name, group)
core.log("deprecated", "Deprecated usage of get_node_group, use get_item_group instead")
return core.get_item_group(name, group)
end
function core.setting_get_pos(name)
local value = core.settings:get(name)
if not value then
@ -150,11 +173,11 @@ function core.setting_get_pos(name)
end
-- To be overriden by protection mods
function core.is_protected()
return false
end
-- To be overriden by protection mods
function core.is_protected_action()
return false
end
@ -165,8 +188,8 @@ function core.record_protection_violation(pos, name)
end
end
-- Checks if specified volume intersects a protected volume
-- Backport from Minetest 5.0
function core.is_area_protected(minp, maxp, player_name, interval)
-- 'interval' is the largest allowed interval for the 3D lattice of checks.

View File

@ -11,11 +11,14 @@ function core.register_privilege(name, param)
if def.give_to_singleplayer == nil then
def.give_to_singleplayer = true
end
if def.give_to_admin == nil then
def.give_to_admin = def.give_to_singleplayer
end
if def.description == nil then
def.description = "(no description)"
end
end
local def = {}
local def
if type(param) == "table" then
def = param
else
@ -36,7 +39,7 @@ core.register_privilege("basic_privs", "Can modify 'shout' and 'interact' privil
core.register_privilege("privs", "Can modify privileges")
core.register_privilege("teleport", {
description = "Can use /teleport command",
description = "Can teleport self",
give_to_singleplayer = creative
})
core.register_privilege("bring", {
@ -44,12 +47,13 @@ core.register_privilege("bring", {
give_to_singleplayer = false
})
core.register_privilege("settime", {
description = "Can use /time",
description = "Can set the time of day using /time",
give_to_singleplayer = creative
})
core.register_privilege("server", {
description = "Can do server maintenance stuff",
give_to_singleplayer = false
give_to_singleplayer = false,
give_to_admin = true
})
core.register_privilege("protection_bypass", {
description = "Can bypass node protection in the world",
@ -57,11 +61,13 @@ core.register_privilege("protection_bypass", {
})
core.register_privilege("ban", {
description = "Can ban and unban players",
give_to_singleplayer = false
give_to_singleplayer = false,
give_to_admin = true
})
core.register_privilege("kick", {
description = "Can kick players",
give_to_singleplayer = false
give_to_singleplayer = false,
give_to_admin = true
})
core.register_privilege("give", {
description = "Can use /give and /giveme",
@ -72,15 +78,15 @@ core.register_privilege("password", {
give_to_singleplayer = false
})
core.register_privilege("fly", {
description = "Can fly using the free_move mode",
description = "Can use fly mode",
give_to_singleplayer = creative
})
core.register_privilege("fast", {
description = "Can walk fast using the fast_move mode",
description = "Can use fast mode",
give_to_singleplayer = creative
})
core.register_privilege("noclip", {
description = "Can fly through walls",
description = "Can fly through solid nodes using noclip mode",
give_to_singleplayer = false
})
core.register_privilege("rollback", {
@ -93,7 +99,8 @@ core.register_privilege("zoom", {
})
core.register_privilege("debug", {
description = "Allows enabling various debug options that may affect gameplay",
give_to_singleplayer = false
give_to_singleplayer = false,
give_to_admin = true
})
core.register_privilege("weather", {
description = "Allows changing the weather",

View File

@ -79,6 +79,7 @@ end
function core.register_abm(spec)
-- Add to core.registered_abms
assert(type(spec.action) == "function", "Required field 'action' of type function")
core.registered_abms[#core.registered_abms + 1] = spec
spec.mod_origin = core.get_current_modname() or "??"
end
@ -86,6 +87,7 @@ end
function core.register_lbm(spec)
-- Add to core.registered_lbms
check_modname_prefix(spec.name)
assert(type(spec.action) == "function", "Required field 'action' of type function")
core.registered_lbms[#core.registered_lbms + 1] = spec
spec.mod_origin = core.get_current_modname() or "??"
end
@ -106,7 +108,7 @@ function core.register_entity(name, prototype)
end
-- Intllib
Sl = intllib.make_gettext_pair("locales");
Sl = intllib.make_gettext_pair("locales")
function core.register_item(name, itemdef)
-- Check name
@ -185,7 +187,7 @@ function core.register_item(name, itemdef)
--core.log("Registering item: " .. itemdef.name)
core.registered_items[itemdef.name] = itemdef
core.registered_aliases[itemdef.name] = nil
register_item_raw(itemdef)
register_item_raw(itemdef)
end
function core.unregister_item(name)
@ -309,18 +311,17 @@ end
-- Alias the forbidden item names to "" so they can't be
-- created via itemstrings (e.g. /give)
local name
for name in pairs(forbidden_item_names) do
core.registered_aliases[name] = ""
register_alias_raw(name, "")
end
-- Deprecated:
-- Obsolete:
-- Aliases for core.register_alias (how ironic...)
--core.alias_node = core.register_alias
--core.alias_tool = core.register_alias
--core.alias_craftitem = core.register_alias
-- core.alias_node = core.register_alias
-- core.alias_tool = core.register_alias
-- core.alias_craftitem = core.register_alias
--
-- Built-in node definitions. Also defined in C.
@ -429,10 +430,6 @@ function core.run_callbacks(callbacks, mode, ...)
local origin = core.callback_origins[callbacks[i]]
if origin then
core.set_last_run_mod(origin.mod)
--print("Running " .. tostring(callbacks[i]) ..
-- " (a " .. origin.name .. " callback in " .. origin.mod .. ")")
else
--print("No data associated with callback")
end
local cb_ret = callbacks[i](...)
@ -460,6 +457,18 @@ function core.run_callbacks(callbacks, mode, ...)
return ret
end
function core.run_priv_callbacks(name, priv, caller, method)
local def = core.registered_privileges[priv]
if not def or not def["on_" .. method] or
not def["on_" .. method](name, caller) then
for _, func in ipairs(core["registered_on_priv_" .. method]) do
if not func(name, caller, priv) then
break
end
end
end
end
--
-- Callback registration
--
@ -521,11 +530,11 @@ end
core.registered_on_player_hpchanges = { modifiers = { }, loggers = { } }
function core.registered_on_player_hpchange(player, hp_change)
local last = false
function core.registered_on_player_hpchange(player, hp_change, reason)
local last
for i = #core.registered_on_player_hpchanges.modifiers, 1, -1 do
local func = core.registered_on_player_hpchanges.modifiers[i]
hp_change, last = func(player, hp_change)
hp_change, last = func(player, hp_change, reason)
if type(hp_change) ~= "number" then
local debuginfo = debug.getinfo(func)
error("The register_on_hp_changes function has to return a number at " ..
@ -536,7 +545,7 @@ function core.registered_on_player_hpchange(player, hp_change)
end
end
for i, func in ipairs(core.registered_on_player_hpchanges.loggers) do
func(player, hp_change)
func(player, hp_change, reason)
end
return hp_change
end
@ -578,11 +587,12 @@ core.registered_craft_predicts, core.register_craft_predict = make_registration(
core.registered_on_protection_violation, core.register_on_protection_violation = make_registration()
core.registered_on_item_eats, core.register_on_item_eat = make_registration()
core.registered_on_punchplayers, core.register_on_punchplayer = make_registration()
core.registered_on_priv_grant, core.register_on_priv_grant = make_registration()
core.registered_on_priv_revoke, core.register_on_priv_revoke = make_registration()
-- Player step iteration
players_per_step = core.settings:get("players_per_globalstep")
players_per_step = players_per_step and tonumber(players_per_step) or 20
players_per_step = tonumber(core.settings:get("players_per_globalstep")) or 20
local player_iter
local player_iter_forced

View File

@ -2234,45 +2234,101 @@ For the following functions `x` can be either a vector or a number:
* Returns a scaled vector or Schur quotient.
Helper functions
----------------
* `dump2(obj, name="_", dumped={})`
* Return object serialized as a string, handles reference loops
* `dump(obj, dumped={})`
* Return object serialized as a string
-----------------
* `dump2(obj, name, dumped)`: returns a string which makes `obj`
human-readable, handles reference loops.
* `obj`: arbitrary variable
* `name`: string, default: `"_"`
* `dumped`: table, default: `{}`
* `dump(obj, dumped)`: returns a string which makes `obj` human-readable
* `obj`: arbitrary variable
* `dumped`: table, default: `{}`
* `math.hypot(x, y)`
* Get the hypotenuse of a triangle with legs x and y.
Useful for distance calculation.
* `math.sign(x, tolerance)`
* `math.sign(x, tolerance)`: returns `-1`, `0` or `1`
* Get the sign of a number.
Optional: Also returns `0` when the absolute value is within the tolerance (default: `0`)
* `string.split(str, separator=",", include_empty=false, max_splits=-1, sep_is_pattern=false)`
* If `max_splits` is negative, do not limit splits.
* `sep_is_pattern` specifies if separator is a plain string or a pattern (regex).
* e.g. `string:split("a,b", ",") == {"a","b"}`
* `string:trim()`
* e.g. `string.trim("\n \t\tfoo bar\t ") == "foo bar"`
* `minetest.wrap_text(str, limit, [as_table])`: returns a string or table
* Adds newlines to the string to keep it within the specified character limit
Note that returned lines may be longer than the limit since it only splits at word borders.
* limit: Maximal amount of characters in one line
* as_table: optional, if true return table of lines instead of string
* `minetest.pos_to_string({x=X,y=Y,z=Z}, decimal_places))`: returns string `"(X,Y,Z)"`
* Convert position to a printable string
Optional: 'decimal_places' will round the x, y and z of the pos to the given decimal place.
* `minetest.string_to_pos(string)`: returns a position
* Same but in reverse. Returns `nil` if the string can't be parsed to a position.
* tolerance: number, default: `0.0`
* If the absolute value of `x` is within the `tolerance` or `x` is NaN,
`0` is returned.
* `math.factorial(x)`: returns the factorial of `x`
* `string.split(str, separator, include_empty, max_splits, sep_is_pattern)`
* `separator`: string, default: `","`
* `include_empty`: boolean, default: `false`
* `max_splits`: number, if it's negative, splits aren't limited,
default: `-1`
* `sep_is_pattern`: boolean, it specifies whether separator is a plain
string or a pattern (regex), default: `false`
* e.g. `"a,b":split","` returns `{"a","b"}`
* `string:trim()`: returns the string without whitespace pre- and suffixes
* e.g. `"\n \t\tfoo bar\t ":trim()` returns `"foo bar"`
* `minetest.wrap_text(str, limit, as_table)`: returns a string or table
* Adds newlines to the string to keep it within the specified character
limit
* Note that the returned lines may be longer than the limit since it only
splits at word borders.
* `limit`: number, maximal amount of characters in one line
* `as_table`: boolean, if set to true, a table of lines instead of a string
is returned, default: `false`
* `minetest.pos_to_string(pos, decimal_places)`: returns string `"(X,Y,Z)"`
* `pos`: table {x=X, y=Y, z=Z}
* Converts the position `pos` to a human-readable, printable string
* `decimal_places`: number, if specified, the x, y and z values of
the position are rounded to the given decimal place.
* `minetest.string_to_pos(string)`: returns a position or `nil`
* Same but in reverse.
* If the string can't be parsed to a position, nothing is returned.
* `minetest.string_to_area("(X1, Y1, Z1) (X2, Y2, Z2)")`: returns two positions
* Converts a string representing an area box into two positions
* `minetest.formspec_escape(string)`: returns a string
* escapes the characters "[", "]", "\", "," and ";", which can not be used in formspecs
* escapes the characters "[", "]", "\", "," and ";", which can not be used
in formspecs.
* `minetest.is_yes(arg)`
* returns true if passed 'y', 'yes', 'true' or a number that isn't zero.
* `minetest.is_nan(arg)`
* returns true when the passed number represents NaN.
* `minetest.get_us_time()`
* returns time with microsecond precision. May not return wall time.
* `table.copy(table)`: returns a table
* returns a deep copy of `table`
* `minetest.pointed_thing_to_face_pos(placer, pointed_thing)`: returns a position
* `table.indexof(list, val)`: returns the smallest numerical index containing
the value `val` in the table `list`. Non-numerical indices are ignored.
If `val` could not be found, `-1` is returned. `list` must not have
negative indices.
* `table.insert_all(table, other_table)`:
* Appends all values in `other_table` to `table` - uses `#table + 1` to
find new indices.
* `table.key_value_swap(t)`: returns a table with keys and values swapped
* If multiple keys in `t` map to the same value, the result is undefined.
* `table.shuffle(table, [from], [to], [random_func])`:
* Shuffles elements `from` to `to` in `table` in place
* `from` defaults to `1`
* `to` defaults to `#table`
* `random_func` defaults to `math.random`. This function receives two
integers as arguments and should return a random integer inclusively
between them.
* `minetest.pointed_thing_to_face_pos(placer, pointed_thing)`: returns a
position.
* returns the exact position on the surface of a pointed node
* `minetest.get_dig_params(groups, tool_capabilities)`: Simulates a tool
that digs a node.
Returns a table with the following fields:
* `diggable`: `true` if node can be dug, `false` otherwise.
* `time`: Time it would take to dig the node.
* `wear`: How much wear would be added to the tool.
`time` and `wear` are meaningless if node's not diggable
Parameters:
* `groups`: Table of the node groups of the node that would be dug
* `tool_capabilities`: Tool capabilities table of the tool
* `minetest.get_hit_params(groups, tool_capabilities [, time_from_last_punch])`:
Simulates an item that punches an object.
Returns a table with the following fields:
* `hp`: How much damage the punch would cause.
* `wear`: How much wear would be added to the tool.
Parameters:
* `groups`: Damage groups of the object
* `tool_capabilities`: Tool capabilities table of the item
* `time_from_last_punch`: time in seconds since last punch action
`minetest` namespace reference
------------------------------
@ -2800,12 +2856,17 @@ and `minetest.auth_reload` call the authetification handler.
* `{type="player", name="celeron55"}`
* `{type="node", pos={x=, y=, z=}}`
* `{type="detached", name="creative"}`
* `minetest.create_detached_inventory(name, callbacks, [player_name])`: returns an `InvRef`
* callbacks: See "Detached inventory callbacks"
* `player_name`: Make detached inventory available to one player exclusively,
by default they will be sent to every player (even if not used).
Note that this parameter is mostly just a workaround and will be removed in future releases.
* `minetest.create_detached_inventory(name, callbacks, [player_name])`: returns
an `InvRef`.
* `callbacks`: See [Detached inventory callbacks]
* `player_name`: Make detached inventory available to one player
exclusively, by default they will be sent to every player (even if not
used).
Note that this parameter is mostly just a workaround and will be removed
in future releases.
* Creates a detached inventory. If it already exists, it is cleared.
* `minetest.remove_detached_inventory(name)`
* Returns a `boolean` indicating whether the removal succeeded.
* `minetest.do_item_eat(hp_change, replace_with_item, poison, itemstack, user, pointed_thing)`:
returns left over ItemStack
* See `minetest.item_eat` and `minetest.register_on_item_eat`
@ -2912,8 +2973,8 @@ and `minetest.auth_reload` call the authetification handler.
}
* `minetest.handle_node_drops(pos, drops, digger)`
* `drops`: list of itemstrings
* Handles drops from nodes after digging: Default action is to put them into
digger's inventory
* Handles drops from nodes after digging: Default action is to put them
into digger's inventory.
* Can be overridden to get different functionality (e.g. dropping items on
ground)
* `minetest.itemstring_with_palette(item, palette_index)`: returns an item
@ -3166,6 +3227,10 @@ These functions return the leftover itemstack.
* See documentation on `minetest.compress()` for supported compression methods.
* currently supported.
* `...` indicates method-specific arguments. Currently, no methods use this.
* `minetest.rgba(red, green, blue[, alpha])`: returns a string
* Each argument is a 8 Bit unsigned integer
* Returns the ColorString from rgb or rgba values
* Example: `minetest.rgba(10, 20, 30, 40)`, returns `"#0A141E28"`
* `minetest.encode_base64(string)`: returns string encoded in base64
* Encodes a string in base64.
* `minetest.decode_base64(string)`: returns string