minetest/builtin/game/chat.lua

1103 lines
33 KiB
Lua
Raw Normal View History

-- Minetest: builtin/game/chat.lua
-- Helper function that implements search and replace without pattern matching
-- Returns the string and a boolean indicating whether or not the string was modified
local function safe_gsub(s, replace, with)
local i1, i2 = s:find(replace, 1, true)
if not i1 then
return s, false
end
return s:sub(1, i1 - 1) .. with .. s:sub(i2 + 1), true
end
--
-- Chat message formatter
--
-- Implemented in Lua to allow redefinition
function core.format_chat_message(name, message)
local error_str = "Invalid chat message format - missing %s"
local str = core.settings:get("chat_message_format")
local replaced
-- Name
str, replaced = safe_gsub(str, "@name", name)
if not replaced then
error(error_str:format("@name"), 2)
end
-- Timestamp
str = safe_gsub(str, "@timestamp", os.date("%H:%M:%S", os.time()))
-- Insert the message into the string only after finishing all other processing
str, replaced = safe_gsub(str, "@message", message)
if not replaced then
error(error_str:format("@message"), 2)
end
return str
end
2012-04-01 11:37:41 +02:00
--
-- Chat command handler
2012-04-01 11:37:41 +02:00
--
core.chatcommands = core.registered_chatcommands -- BACKWARDS COMPATIBILITY
2014-04-28 03:02:48 +02:00
core.register_on_chat_message(function(name, message)
if message:sub(1,1) ~= "/" then
return
end
local cmd, param = string.match(message, "^/([^ ]+) *(.*)")
if not cmd then
core.chat_send_player(name, "-!- Empty command")
return true
end
param = param or ""
local cmd_def = core.registered_chatcommands[cmd]
if not cmd_def then
core.chat_send_player(name, "-!- Invalid command: " .. cmd)
return true
end
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)
2019-08-06 20:30:18 +02:00
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"
.. " to run this command (missing privileges: "
.. table.concat(missing_privs, ", ") .. ")")
end
return true -- Handled chat message
end)
if core.settings:get_bool("profiler.load") then
Builtin/profiler: Replace game profiler (#4245) Use the setting "profiler.load" to enable profiling. Other settings can be found in settingtypes.txt. * /profiler print [filter] - report statistics to in-game console * /profiler dump [filter] - report statistics to STDOUT and debug.txt * /profiler save [format [filter]] - saves statistics to a file in your worldpath * txt (default) - same treetable format as used by the dump and print commands * csv - ready for spreadsheet import * json - useful for adhoc D3 visualizations * json_pretty - line wrapped and intended json for humans * lua - serialized lua table of the profile-data, for adhoc scripts * /profiler reset - reset all gathered profile data. This can be helpful to discard of any startup measurements that often spike during loading or to get more useful min-values. [filter] allows limiting the output of the data via substring/pattern matching against the modname. Note: Serialized data structures might be subject to change with changed or added measurements. csv might be the most stable, due to flat structure. Changes to the previous version include: * Updated and extended API monitoring * Correct calculation of average (mean) values (undistorted by idleness) * Reduce instrumentation overhead. * Fix crashes related to missing parameters for the future and occasional DIV/0's. * Prevent issues caused by timetravel (overflow, timejump, NTP corrections) * Prevent modname clashes with internal names. * Measure each instrumentation individually and label based on registration order. * Labeling of ABM's and LBM's for easier classification. Giving several ABM's or LBM's the same label will treat them as one. Missing labels will be autogenerated based on name or registration order. * Configurable instrumentation and reporting. Skip e.g. builtin if you don't need it. * Profile the profiler to measure instrumentation overhead.
2016-07-12 21:51:10 +02:00
-- Run after register_chatcommand and its register_on_chat_message
-- Before any chatcommands that should be profiled
Builtin/profiler: Replace game profiler (#4245) Use the setting "profiler.load" to enable profiling. Other settings can be found in settingtypes.txt. * /profiler print [filter] - report statistics to in-game console * /profiler dump [filter] - report statistics to STDOUT and debug.txt * /profiler save [format [filter]] - saves statistics to a file in your worldpath * txt (default) - same treetable format as used by the dump and print commands * csv - ready for spreadsheet import * json - useful for adhoc D3 visualizations * json_pretty - line wrapped and intended json for humans * lua - serialized lua table of the profile-data, for adhoc scripts * /profiler reset - reset all gathered profile data. This can be helpful to discard of any startup measurements that often spike during loading or to get more useful min-values. [filter] allows limiting the output of the data via substring/pattern matching against the modname. Note: Serialized data structures might be subject to change with changed or added measurements. csv might be the most stable, due to flat structure. Changes to the previous version include: * Updated and extended API monitoring * Correct calculation of average (mean) values (undistorted by idleness) * Reduce instrumentation overhead. * Fix crashes related to missing parameters for the future and occasional DIV/0's. * Prevent issues caused by timetravel (overflow, timejump, NTP corrections) * Prevent modname clashes with internal names. * Measure each instrumentation individually and label based on registration order. * Labeling of ABM's and LBM's for easier classification. Giving several ABM's or LBM's the same label will treat them as one. Missing labels will be autogenerated based on name or registration order. * Configurable instrumentation and reporting. Skip e.g. builtin if you don't need it. * Profile the profiler to measure instrumentation overhead.
2016-07-12 21:51:10 +02:00
profiler.init_chatcommand()
end
-- Parses a "range" string in the format of "here (number)" or
-- "(x1, y1, z1) (x2, y2, z2)", returning two position vectors
local function parse_range_str(player_name, str)
local p1, p2
local args = str:split(" ")
if args[1] == "here" then
p1, p2 = core.get_player_radius_area(player_name, tonumber(args[2]))
if p1 == nil then
return false, "Unable to get player " .. player_name .. " position"
end
else
p1, p2 = core.string_to_area(str)
if p1 == nil then
return false, "Incorrect area format. Expected: (x1,y1,z1) (x2,y2,z2)"
end
end
return p1, p2
end
--
-- Chat commands
--
2014-04-28 03:02:48 +02:00
core.register_chatcommand("me", {
params = "<action>",
description = "Show chat action (e.g., '/me orders a pizza' displays"
2017-01-17 15:41:25 +01:00
.. " '<player name> orders a pizza')",
privs = {shout=true},
func = function(name, param)
2014-04-28 03:02:48 +02:00
core.chat_send_all("* " .. name .. " " .. param)
end,
})
core.register_chatcommand("admin", {
description = "Show the name of the server owner",
func = function(name)
local admin = core.settings:get("name")
if admin then
return true, "The administrator of this server is " .. admin .. "."
else
return false, "There's no administrator named in the config file."
end
end,
})
2014-04-28 03:02:48 +02:00
core.register_chatcommand("privs", {
params = "[<name>]",
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), ' ')
2012-04-01 11:37:41 +02:00
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
2019-08-06 20:30:18 +02:00
end
return true, "Players online with the \"" .. param .. "\" privilege: " ..
table.concat(players_with_priv, ", ")
2019-08-06 20:30:18 +02:00
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
return false, "Your privileges are insufficient."
end
if not core.get_auth_handler().get_auth(grantname) then
return false, "Player " .. grantname .. " does not exist."
end
local grantprivs = core.string_to_privs(grantprivstr)
if grantprivstr == "all" then
grantprivs = core.registered_privileges
end
local privs = core.get_player_privs(grantname)
local privs_unknown = ""
local basic_privs =
core.string_to_privs(core.settings:get("basic_privs") or "interact,shout")
for priv, _ in pairs(grantprivs) do
if not basic_privs[priv] and not caller_privs.privs then
return false, "Your privileges are insufficient."
end
if not core.registered_privileges[priv] then
privs_unknown = privs_unknown .. "Unknown privilege: " .. priv .. "\n"
end
privs[priv] = true
end
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
core.chat_send_player(grantname, caller
.. " granted you privileges: "
.. core.privs_to_string(grantprivs, ' '))
end
return true, "Privileges of " .. grantname .. ": "
.. core.privs_to_string(
core.get_player_privs(grantname), ' ')
end
2014-04-28 03:02:48 +02:00
core.register_chatcommand("grant", {
params = "<name> (<privilege> | all)",
description = "Give privileges to player",
2012-04-01 11:37:41 +02:00
func = function(name, param)
local grantname, grantprivstr = string.match(param, "([^ ]+) (.+)")
if not grantname or not grantprivstr then
return false, "Invalid parameters (see /help grant)"
end
return handle_grant_command(name, grantname, grantprivstr)
2012-04-01 11:37:41 +02:00
end,
})
core.register_chatcommand("grantme", {
params = "<privilege> | all",
description = "Grant privileges to yourself",
func = function(name, param)
if param == "" then
return false, "Invalid parameters (see /help grantme)"
end
return handle_grant_command(name, name, param)
end,
})
2014-04-28 03:02:48 +02:00
core.register_chatcommand("revoke", {
params = "<name> (<privilege> | all)",
description = "Remove privileges from player",
2012-04-09 17:57:41 +02:00
privs = {},
2012-04-01 11:37:41 +02:00
func = function(name, param)
2014-04-28 03:02:48 +02:00
if not core.check_player_privs(name, {privs=true}) and
not core.check_player_privs(name, {basic_privs=true}) then
return false, "Your privileges are insufficient."
end
local revoke_name, revoke_priv_str = string.match(param, "([^ ]+) (.+)")
if not revoke_name or not revoke_priv_str then
return false, "Invalid parameters (see /help revoke)"
elseif not core.get_auth_handler().get_auth(revoke_name) then
return false, "Player " .. revoke_name .. " does not exist."
end
local revoke_privs = core.string_to_privs(revoke_priv_str)
local privs = core.get_player_privs(revoke_name)
2016-04-09 17:07:45 +02:00
local basic_privs =
core.string_to_privs(core.settings:get("basic_privs") or "interact,shout")
for priv, _ in pairs(revoke_privs) do
2016-04-09 17:07:45 +02:00
if not basic_privs[priv] and
not core.check_player_privs(name, {privs=true}) then
return false, "Your privileges are insufficient."
2012-04-09 17:57:41 +02:00
end
end
if revoke_priv_str == "all" then
revoke_privs = privs
2012-04-01 11:37:41 +02:00
privs = {}
else
for priv, _ in pairs(revoke_privs) do
2012-04-01 11:37:41 +02:00
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, ', ')
..') privileges from '..revoke_name)
if revoke_name ~= name then
core.chat_send_player(revoke_name, name
.. " revoked privileges from you: "
.. core.privs_to_string(revoke_privs, ' '))
end
return true, "Privileges of " .. revoke_name .. ": "
.. core.privs_to_string(
core.get_player_privs(revoke_name), ' ')
2012-04-01 11:37:41 +02:00
end,
})
2014-04-28 03:02:48 +02:00
core.register_chatcommand("setpassword", {
2012-04-01 11:37:41 +02:00
params = "<name> <password>",
2017-01-17 15:41:25 +01:00
description = "Set player's password",
2012-04-01 11:37:41 +02:00
privs = {password=true},
func = function(name, param)
2012-06-16 22:35:11 +02:00
local toname, raw_password = string.match(param, "^([^ ]+) +(.+)$")
if not toname then
toname = param:match("^([^ ]+) *$")
2012-06-16 22:35:11 +02:00
raw_password = nil
end
2019-08-06 20:30:18 +02:00
2012-06-16 22:35:11 +02:00
if not toname then
return false, "Name field required"
2012-04-01 11:37:41 +02:00
end
2019-08-06 20:30:18 +02:00
local act_str_past, act_str_pres
2012-06-16 22:35:11 +02:00
if not raw_password then
2014-04-28 03:02:48 +02:00
core.set_player_password(toname, "")
act_str_past = "cleared"
act_str_pres = "clears"
2012-06-16 22:35:11 +02:00
else
core.set_player_password(toname,
core.get_password_hash(toname,
raw_password))
act_str_past = "set"
act_str_pres = "sets"
2012-06-16 22:35:11 +02:00
end
2019-08-06 20:30:18 +02:00
2012-06-16 22:35:11 +02:00
if toname ~= name then
core.chat_send_player(toname, "Your password was "
.. act_str_past .. " by " .. name)
2012-06-16 22:35:11 +02:00
end
2019-08-06 20:30:18 +02:00
core.log("action", name .. " " .. act_str_pres ..
" password of " .. toname .. ".")
return true, "Password of player \"" .. toname .. "\" " .. act_str_past
2012-04-01 11:37:41 +02:00
end,
})
2014-04-28 03:02:48 +02:00
core.register_chatcommand("clearpassword", {
2012-04-01 11:37:41 +02:00
params = "<name>",
description = "Set empty password for a player",
2012-04-01 11:37:41 +02:00
privs = {password=true},
func = function(name, param)
2014-11-26 13:48:43 +01:00
local toname = param
if toname == "" then
return false, "Name field required"
2012-06-16 22:35:11 +02:00
end
2014-04-28 03:02:48 +02:00
core.set_player_password(toname, '')
core.log("action", name .. " clears password of " .. toname .. ".")
return true, "Password of player \"" .. toname .. "\" cleared"
2012-04-01 11:37:41 +02:00
end,
})
2014-04-28 03:02:48 +02:00
core.register_chatcommand("auth_reload", {
2012-04-01 11:37:41 +02:00
params = "",
2017-01-17 15:41:25 +01:00
description = "Reload authentication data",
2012-04-01 11:37:41 +02:00
privs = {server=true},
func = function(name, param)
2014-04-28 03:02:48 +02:00
local done = core.auth_reload()
return done, (done and "Done." or "Failed.")
2012-04-01 11:37:41 +02:00
end,
})
core.register_chatcommand("remove_player", {
params = "<name>",
description = "Remove a player's data",
privs = {server=true},
func = function(name, param)
local toname = param
if toname == "" then
return false, "Name field required"
end
local rc = core.remove_player(toname)
if rc == 0 then
core.log("action", name .. " removed player data of " .. toname .. ".")
return true, "Player \"" .. toname .. "\" removed."
elseif rc == 1 then
return true, "No such player \"" .. toname .. "\" to remove."
elseif rc == 2 then
return true, "Player \"" .. toname .. "\" is connected, cannot remove."
end
return false, "Unhandled remove_player return code " .. rc .. ""
end,
})
2014-04-28 03:02:48 +02:00
core.register_chatcommand("teleport", {
params = "<X>,<Y>,<Z> | <to_name> | (<name> <X>,<Y>,<Z>) | (<name> <to_name>)",
description = "Teleport to position or player",
2012-04-01 11:37:41 +02:00
privs = {teleport=true},
func = function(name, param)
-- Returns (pos, true) if found, otherwise (pos, false)
local function find_free_position_near(pos)
local tries = {
{x=1,y=0,z=0},
{x=-1,y=0,z=0},
{x=0,y=0,z=1},
{x=0,y=0,z=-1},
}
for _, d in ipairs(tries) do
local p = {x = pos.x+d.x, y = pos.y+d.y, z = pos.z+d.z}
2014-04-28 03:02:48 +02:00
local n = core.get_node_or_nil(p)
if n and n.name then
2014-04-28 03:02:48 +02:00
local def = core.registered_nodes[n.name]
if def and not def.walkable then
return p, true
end
2012-04-01 11:37:41 +02:00
end
end
return pos, false
end
local p = {}
p.x, p.y, p.z = string.match(param, "^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
p.x = tonumber(p.x)
p.y = tonumber(p.y)
p.z = tonumber(p.z)
if p.x and p.y and p.z then
2019-08-06 20:30:18 +02:00
local lm = 31000
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
2019-08-06 20:30:18 +02:00
local teleportee = core.get_player_by_name(name)
if teleportee then
teleportee:set_pos(p)
return true, "Teleporting to "..core.pos_to_string(p)
end
2012-04-01 11:37:41 +02:00
end
2019-08-06 20:30:18 +02:00
local target_name = param:match("^([^ ]+)$")
local teleportee = core.get_player_by_name(name)
p = nil
2012-04-01 11:37:41 +02:00
if target_name then
2014-04-28 03:02:48 +02:00
local target = core.get_player_by_name(target_name)
2012-04-01 11:37:41 +02:00
if target then
p = target:get_pos()
2012-04-01 11:37:41 +02:00
end
end
2019-08-06 20:30:18 +02:00
2012-04-01 11:37:41 +02:00
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(p)
2012-04-01 11:37:41 +02:00
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
2019-08-06 20:30:18 +02:00
teleportee = nil
p = {}
local teleportee_name
teleportee_name, p.x, p.y, p.z = param:match(
"^([^ ]+) +([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
p.x, p.y, p.z = tonumber(p.x), tonumber(p.y), tonumber(p.z)
if teleportee_name then
teleportee = core.get_player_by_name(teleportee_name)
end
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(p)
end
2019-08-06 20:30:18 +02:00
teleportee = nil
p = nil
teleportee_name, target_name = string.match(param, "^([^ ]+) +([^ ]+)$")
if teleportee_name then
teleportee = core.get_player_by_name(teleportee_name)
end
if target_name then
local target = core.get_player_by_name(target_name)
if target then
p = target:get_pos()
2012-04-01 11:37:41 +02:00
end
end
if teleportee and p then
p = find_free_position_near(p)
teleportee:set_pos(p)
return true, "Teleporting " .. teleportee_name
.. " to " .. target_name
.. " at " .. core.pos_to_string(p)
end
return false, 'Invalid parameters ("' .. param
.. '") or player not found (see /help teleport)'
2012-04-01 11:37:41 +02:00
end,
})
2014-04-28 03:02:48 +02:00
core.register_chatcommand("set", {
params = "([-n] <name> <value>) | <name>",
2017-01-17 15:41:25 +01:00
description = "Set or read server configuration setting",
privs = {server=true},
func = function(name, param)
local arg, setname, setvalue = string.match(param, "(-[n]) ([^ ]+) (.+)")
2012-04-09 22:29:55 +02:00
if arg and arg == "-n" and setname and setvalue then
core.settings:set(setname, setvalue)
return true, setname .. " = " .. setvalue
end
2019-08-06 20:30:18 +02:00
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."
2012-04-01 11:37:41 +02:00
end
core.settings:set(setname, setvalue)
return true, setname .. " = " .. setvalue
2012-04-01 11:37:41 +02:00
end
2019-08-06 20:30:18 +02:00
setname = string.match(param, "([^ ]+)")
if setname then
2019-08-06 20:30:18 +02:00
setvalue = core.settings:get(setname)
if not setvalue then
setvalue = "<not set>"
end
return true, setname .. " = " .. setvalue
end
2019-08-06 20:30:18 +02:00
return false, "Invalid parameters (see /help set)."
end,
})
2012-04-01 11:37:41 +02:00
local function emergeblocks_callback(pos, action, num_calls_remaining, ctx)
if ctx.total_blocks == 0 then
ctx.total_blocks = num_calls_remaining + 1
ctx.current_blocks = 0
end
ctx.current_blocks = ctx.current_blocks + 1
if ctx.current_blocks == ctx.total_blocks then
core.chat_send_player(ctx.requestor_name,
string.format("Finished emerging %d blocks in %.2fms.",
ctx.total_blocks, (os.clock() - ctx.start_time) * 1000))
end
end
local function emergeblocks_progress_update(ctx)
if ctx.current_blocks ~= ctx.total_blocks then
core.chat_send_player(ctx.requestor_name,
string.format("emergeblocks update: %d/%d blocks emerged (%.1f%%)",
ctx.current_blocks, ctx.total_blocks,
(ctx.current_blocks / ctx.total_blocks) * 100))
core.after(2, emergeblocks_progress_update, ctx)
end
end
core.register_chatcommand("emergeblocks", {
params = "(here [<radius>]) | (<pos1> <pos2>)",
2017-01-17 15:41:25 +01:00
description = "Load (or, if nonexistent, generate) map blocks "
.. "contained in area pos1 to pos2 (<pos1> and <pos2> must be in parentheses)",
privs = {server=true},
func = function(name, param)
local p1, p2 = parse_range_str(name, param)
if p1 == false then
return false, p2
end
local context = {
current_blocks = 0,
total_blocks = 0,
start_time = os.clock(),
requestor_name = name
}
core.emerge_area(p1, p2, emergeblocks_callback, context)
core.after(2, emergeblocks_progress_update, context)
return true, "Started emerge of area ranging from " ..
core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
end,
})
core.register_chatcommand("deleteblocks", {
params = "(here [<radius>]) | (<pos1> <pos2>)",
description = "Delete map blocks contained in area pos1 to pos2 "
.. "(<pos1> and <pos2> must be in parentheses)",
privs = {server=true},
func = function(name, param)
local p1, p2 = parse_range_str(name, param)
if p1 == false then
return false, p2
end
if core.delete_area(p1, p2) then
return true, "Successfully cleared area ranging from " ..
core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
else
return false, "Failed to clear one or more blocks in area"
end
end,
})
2017-04-21 12:56:10 +02:00
core.register_chatcommand("fixlight", {
params = "(here [<radius>]) | (<pos1> <pos2>)",
description = "Resets lighting in the area between pos1 and pos2 "
.. "(<pos1> and <pos2> must be in parentheses)",
2017-04-21 12:56:10 +02:00
privs = {server = true},
func = function(name, param)
local p1, p2 = parse_range_str(name, param)
if p1 == false then
return false, p2
end
if core.fix_light(p1, p2) then
return true, "Successfully reset light in the area ranging from " ..
core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
else
return false, "Failed to load one or more blocks in area"
end
end,
})
2014-04-28 03:02:48 +02:00
core.register_chatcommand("mods", {
params = "",
description = "List mods installed on the server",
privs = {},
func = function(name, param)
return true, table.concat(core.get_modnames(), ", ")
end,
})
local function handle_give_command(cmd, giver, receiver, stackstring)
core.log("action", giver .. " invoked " .. cmd
.. ', stackstring="' .. stackstring .. '"')
local itemstack = ItemStack(stackstring)
if itemstack:is_empty() then
return false, "Cannot give an empty item"
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
2014-04-28 03:02:48 +02:00
local receiverref = core.get_player_by_name(receiver)
if receiverref == nil then
return false, receiver .. " is not a known player"
end
local leftover = receiverref:get_inventory():add_item("main", itemstack)
2014-11-26 13:48:43 +01:00
local partiality
if leftover:is_empty() then
partiality = ""
elseif leftover:get_count() == itemstack:get_count() then
partiality = "could not be "
else
partiality = "partially "
end
-- The actual item stack string may be different from what the "giver"
-- entered (e.g. big numbers are always interpreted as 2^16-1).
stackstring = itemstack:to_string()
if giver == receiver then
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))
local msg = "%q %sadded to %s's inventory."
return true, msg:format(stackstring, partiality, receiver)
end
end
2014-04-28 03:02:48 +02:00
core.register_chatcommand("give", {
params = "<name> <ItemString> [<count> [<wear>]]",
2017-01-17 15:41:25 +01:00
description = "Give item to player",
privs = {give=true},
func = function(name, param)
local toname, itemstring = string.match(param, "^([^ ]+) +(.+)$")
if not toname or not itemstring then
return false, "Name and ItemString required"
end
return handle_give_command("/give", name, toname, itemstring)
end,
})
2014-04-28 03:02:48 +02:00
core.register_chatcommand("giveme", {
params = "<ItemString> [<count> [<wear>]]",
2017-01-17 15:41:25 +01:00
description = "Give item to yourself",
privs = {give=true},
func = function(name, param)
local itemstring = string.match(param, "(.+)$")
if not itemstring then
return false, "ItemString required"
end
return handle_give_command("/giveme", name, name, itemstring)
end,
})
2014-04-28 03:02:48 +02:00
core.register_chatcommand("spawnentity", {
params = "<EntityName> [<X>,<Y>,<Z>]",
description = "Spawn entity at given (or your) position",
privs = {give=true, interact=true},
func = function(name, param)
local entityname, p = string.match(param, "^([^ ]+) *(.*)$")
if not entityname then
return false, "EntityName required"
end
core.log("action", ("%s invokes /spawnentity, entityname=%q")
:format(name, entityname))
2014-04-28 03:02:48 +02:00
local player = core.get_player_by_name(name)
if player == nil then
2014-04-28 03:02:48 +02:00
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
p = core.string_to_pos(p)
if p == nil then
return false, "Invalid parameters ('" .. param .. "')"
end
end
p.y = p.y + 1
2014-04-28 03:02:48 +02:00
core.add_entity(p, entityname)
return true, ("%q spawned."):format(entityname)
end,
})
2014-04-28 03:02:48 +02:00
core.register_chatcommand("pulverize", {
params = "",
description = "Destroy item in hand",
func = function(name, param)
2014-04-28 03:02:48 +02:00
local player = core.get_player_by_name(name)
if not player then
core.log("error", "Unable to pulverize, no player.")
return false, "Unable to pulverize, no player."
end
2018-06-30 12:53:43 +02:00
local wielded_item = player:get_wielded_item()
if wielded_item:is_empty() then
return false, "Unable to pulverize, no item in hand."
end
2018-06-30 12:53:43 +02:00
core.log("action", name .. " pulverized \"" ..
wielded_item:get_name() .. " " .. wielded_item:get_count() .. "\"")
2019-08-06 20:30:18 +02:00
player:set_wielded_item(nil)
return true, "An item was pulverized."
end,
})
-- Key = player name
2014-04-28 03:02:48 +02:00
core.rollback_punch_callbacks = {}
2014-04-28 03:02:48 +02:00
core.register_on_punchnode(function(pos, node, puncher)
local name = puncher and puncher:get_player_name()
if name and core.rollback_punch_callbacks[name] then
2014-04-28 03:02:48 +02:00
core.rollback_punch_callbacks[name](pos, node, puncher)
core.rollback_punch_callbacks[name] = nil
end
end)
2014-04-28 03:02:48 +02:00
core.register_chatcommand("rollback_check", {
params = "[<range>] [<seconds>] [<limit>]",
2017-01-17 15:41:25 +01:00
description = "Check who last touched a node or a node near it"
.. " within the time specified by <seconds>. Default: range = 0,"
.. " seconds = 86400 = 24h, limit = 5",
privs = {rollback=true},
func = function(name, param)
if not core.settings:get_bool("enable_rollback_recording") then
return false, "Rollback functions are disabled."
end
2013-11-12 22:13:00 +01:00
local range, seconds, limit =
param:match("(%d+) *(%d*) *(%d*)")
range = tonumber(range) or 0
seconds = tonumber(seconds) or 86400
2013-11-12 22:13:00 +01:00
limit = tonumber(limit) or 5
if limit > 100 then
return false, "That limit is too high!"
2013-11-12 22:13:00 +01:00
end
2014-04-28 03:02:48 +02:00
core.rollback_punch_callbacks[name] = function(pos, node, puncher)
local name = puncher:get_player_name()
core.chat_send_player(name, "Checking " .. core.pos_to_string(pos) .. "...")
2014-04-28 03:02:48 +02:00
local actions = core.rollback_get_node_actions(pos, range, seconds, limit)
if not actions then
core.chat_send_player(name, "Rollback functions are disabled")
return
end
2013-11-12 22:13:00 +01:00
local num_actions = #actions
if num_actions == 0 then
core.chat_send_player(name, "Nobody has touched"
.. " the specified location in "
.. seconds .. " seconds")
return
end
2013-11-12 22:13:00 +01:00
local time = os.time()
for i = num_actions, 1, -1 do
local action = actions[i]
2014-04-28 03:02:48 +02:00
core.chat_send_player(name,
2013-11-12 22:13:00 +01:00
("%s %s %s -> %s %d seconds ago.")
:format(
2014-04-28 03:02:48 +02:00
core.pos_to_string(action.pos),
2013-11-12 22:13:00 +01:00
action.actor,
action.oldnode.name,
action.newnode.name,
time - action.time))
end
end
return true, "Punch a node (range=" .. range .. ", seconds="
.. seconds .. "s, limit=" .. limit .. ")"
end,
})
2014-04-28 03:02:48 +02:00
core.register_chatcommand("rollback", {
params = "(<name> [<seconds>]) | (:<actor> [<seconds>])",
2017-01-17 15:41:25 +01:00
description = "Revert actions of a player. Default for <seconds> is 60",
privs = {rollback=true},
func = function(name, param)
if not core.settings:get_bool("enable_rollback_recording") then
return false, "Rollback functions are disabled."
end
local target_name, seconds = string.match(param, ":([^ ]+) *(%d*)")
if not target_name then
2019-08-06 20:30:18 +02:00
local player_name
player_name, seconds = string.match(param, "([^ ]+) *(%d*)")
if not player_name then
return false, "Invalid parameters. See /help rollback"
.. " and /help rollback_check."
end
target_name = "player:"..player_name
end
seconds = tonumber(seconds) or 60
core.chat_send_player(name, "Reverting actions of "
.. target_name .. " since "
.. seconds .. " seconds.")
2014-04-28 03:02:48 +02:00
local success, log = core.rollback_revert_actions_by(
target_name, seconds)
local response = ""
2013-11-12 22:13:00 +01:00
if #log > 100 then
response = "(log is too long to show)\n"
else
2013-11-12 22:13:00 +01:00
for _, line in pairs(log) do
response = response .. line .. "\n"
end
end
response = response .. "Reverting actions "
.. (success and "succeeded." or "FAILED.")
return success, response
end,
})
2014-04-28 03:02:48 +02:00
core.register_chatcommand("status", {
description = "Show server status",
func = function(name, param)
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,
})
2014-04-28 03:02:48 +02:00
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
local current_time = math.floor(core.get_timeofday() * 1440)
local minutes = current_time % 60
local hour = (current_time - minutes) / 60
return true, ("Current time is %d:%02d"):format(hour, minutes)
end
2015-10-31 00:35:27 +01:00
local player_privs = core.get_player_privs(name)
if not player_privs.settime then
return false, "You don't have permission to run this command " ..
"(missing privilege: settime)."
end
local hour, minute = param:match("^(%d+):(%d+)$")
if not hour then
local new_time = tonumber(param)
if not new_time then
return false, "Invalid time."
end
-- Backward compatibility.
core.set_timeofday((new_time % 24000) / 24000)
core.log("action", name .. " sets time to " .. new_time)
return true, "Time of day changed."
end
hour = tonumber(hour)
minute = tonumber(minute)
if hour < 0 or hour > 23 then
return false, "Invalid hour (must be between 0 and 23 inclusive)."
elseif minute < 0 or minute > 59 then
return false, "Invalid minute (must be between 0 and 59 inclusive)."
end
core.set_timeofday((hour * 60 + minute) / 1440)
core.log("action", ("%s sets time to %d:%02d"):format(name, hour, minute))
return true, "Time of day changed."
end,
})
core.register_chatcommand("days", {
description = "Show day count since world creation",
func = function(name, param)
return true, "Current day is " .. core.get_day_count()
end
})
2014-04-28 03:02:48 +02:00
core.register_chatcommand("shutdown", {
params = "[<delay_in_seconds> | -1] [reconnect] [<message>]",
description = "Shutdown server (-1 cancels a delayed shutdown)",
privs = {server=true},
func = function(name, param)
2018-06-11 13:43:12 +02:00
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
2018-06-11 13:43:12 +02:00
if delay == 0 then
core.log("action", name .. " shuts down server")
core.chat_send_all("*** Server shutting down (operator request).")
end
core.request_shutdown(message:trim(), core.is_yes(reconnect), delay)
end,
})
2014-04-28 03:02:48 +02:00
core.register_chatcommand("ban", {
params = "[<name> | <IP_address>]",
description = "Ban player or show ban list",
privs = {ban=true},
func = function(name, param)
if param == "" then
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
2014-04-28 03:02:48 +02:00
if not core.get_player_by_name(param) then
return false, "No such player."
end
2014-04-28 03:02:48 +02:00
if not core.ban_player(param) then
return false, "Failed to ban player."
end
local desc = core.get_ban_description(param)
core.log("action", name .. " bans " .. desc .. ".")
return true, "Banned " .. desc .. "."
end,
})
2014-04-28 03:02:48 +02:00
core.register_chatcommand("unban", {
params = "<name> | <IP_address>",
description = "Remove player ban",
privs = {ban=true},
func = function(name, param)
2014-04-28 03:02:48 +02:00
if not core.unban_player_or_ip(param) then
return false, "Failed to unban player/IP."
end
core.log("action", name .. " unbans " .. param)
return true, "Unbanned " .. param
end,
})
2014-04-28 03:02:48 +02:00
core.register_chatcommand("kick", {
params = "<name> [<reason>]",
2017-01-17 15:41:25 +01:00
description = "Kick a player",
2014-01-26 18:40:25 +01:00
privs = {kick=true},
func = function(name, param)
local tokick, reason = param:match("([^ ]+) (.+)")
tokick = tokick or param
2014-04-28 03:02:48 +02:00
if not core.kick_player(tokick, reason) then
return false, "Failed to kick player " .. tokick
2014-01-26 18:40:25 +01:00
end
local log_reason = ""
if reason then
log_reason = " with reason \"" .. reason .. "\""
end
core.log("action", name .. " kicks " .. tokick .. log_reason)
return true, "Kicked " .. tokick
2014-01-26 18:40:25 +01:00
end,
})
2014-04-28 03:02:48 +02:00
core.register_chatcommand("clearobjects", {
params = "[full | quick]",
2017-01-17 15:41:25 +01:00
description = "Clear all objects in world",
privs = {server=true},
func = function(name, param)
local options = {}
if param == "" or param == "quick" then
2016-02-08 22:20:04 +01:00
options.mode = "quick"
elseif param == "full" then
options.mode = "full"
2016-02-08 22:20:04 +01:00
else
return false, "Invalid usage, see /help clearobjects."
end
2016-02-09 08:56:40 +01:00
core.log("action", name .. " clears all objects ("
.. options.mode .. " mode).")
core.chat_send_all("Clearing all objects. This may take long."
.. " You may experience a timeout. (by "
.. name .. ")")
2016-02-08 22:20:04 +01:00
core.clear_objects(options)
core.log("action", "Object clearing done.")
2014-04-28 03:02:48 +02:00
core.chat_send_all("*** Cleared all objects.")
end,
})
2013-03-28 02:37:09 +01:00
2014-04-28 03:02:48 +02:00
core.register_chatcommand("msg", {
2013-03-28 02:37:09 +01:00
params = "<name> <message>",
description = "Send a direct message to a player",
2013-03-28 02:37:09 +01:00
privs = {shout=true},
func = function(name, param)
local sendto, message = param:match("^(%S+)%s(.+)$")
if not sendto then
return false, "Invalid usage, see /help msg."
end
if not core.get_player_by_name(sendto) then
return false, "The player " .. sendto
.. " is not online."
end
core.log("action", "DM from " .. name .. " to " .. sendto
.. ": " .. message)
core.chat_send_player(sendto, "DM from " .. name .. ": "
.. message)
return true, "Message sent."
2013-03-28 02:37:09 +01:00
end,
})
core.register_chatcommand("last-login", {
params = "[<name>]",
description = "Get the last login time of a player or yourself",
func = function(name, param)
if param == "" then
param = name
end
local pauth = core.get_auth_handler().get_auth(param)
if pauth and pauth.last_login then
-- Time in UTC, ISO 8601 format
return true, "Last login time was " ..
os.date("!%Y-%m-%dT%H:%M:%SZ", pauth.last_login)
end
return false, "Last login time is unknown"
end,
})
core.register_chatcommand("clearinv", {
params = "[<name>]",
description = "Clear the inventory of yourself or another player",
func = function(name, param)
local player
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 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.")
else
player = core.get_player_by_name(name)
end
if player then
player:get_inventory():set_list("main", {})
player:get_inventory():set_list("craft", {})
player:get_inventory():set_list("craftpreview", {})
core.log("action", name.." clears "..player:get_player_name().."'s inventory")
return true, "Cleared "..player:get_player_name().."'s inventory."
else
return false, "Player must be online to clear inventory!"
end
end,
})
local function handle_kill_command(killer, victim)
if core.settings:get_bool("enable_damage") == false then
return false, "Players can't be killed, damage has been disabled."
end
local victimref = core.get_player_by_name(victim)
if victimref == nil then
return false, string.format("Player %s is not online.", victim)
elseif victimref:get_hp() <= 0 then
if killer == victim then
return false, "You are already dead."
else
return false, string.format("%s is already dead.", victim)
end
end
if not killer == victim then
core.log("action", string.format("%s killed %s", killer, victim))
end
-- Kill victim
victimref:set_hp(0)
return true, string.format("%s has been killed.", victim)
end
core.register_chatcommand("kill", {
params = "[<name>]",
description = "Kill player or yourself",
privs = {server=true},
func = function(name, param)
return handle_kill_command(name, param == "" and name or param)
end,
})