Permalink
-- Minetest: builtin/game/chatcommands.lua | |
-- | |
-- Chat command handler | |
-- | |
core.chatcommands = core.registered_chatcommands -- BACKWARDS COMPATIBILITY | |
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) | |
local success, message = cmd_def.func(name, param) | |
if message then | |
core.chat_send_player(name, message) | |
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 | |
-- Run after register_chatcommand and its register_on_chat_message | |
-- Before any chattcommands that should be profiled | |
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 | |
-- | |
core.register_chatcommand("me", { | |
params = "<action>", | |
description = "Display chat action (e.g., '/me orders a pizza' displays" | |
.. " '<player name> orders a pizza')", | |
privs = {shout=true}, | |
func = function(name, param) | |
core.chat_send_all("* " .. name .. " " .. param) | |
end, | |
}) | |
core.register_chatcommand("admin", { | |
description = "Show the name of the server owner", | |
func = function(name) | |
local admin = minetest.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, | |
}) | |
core.register_chatcommand("privs", { | |
params = "<name>", | |
description = "Print privileges of player", | |
func = function(caller, param) | |
param = param:trim() | |
local name = (param ~= "" and param or caller) | |
return true, "Privileges of " .. name .. ": " | |
.. core.privs_to_string( | |
core.get_player_privs(name), ' ') | |
end, | |
}) | |
local function handle_grant_command(caller, grantname, grantprivstr) | |
local caller_privs = minetest.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 | |
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 | |
core.register_chatcommand("grant", { | |
params = "<name> <privilege>|all", | |
description = "Give privilege to player", | |
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) | |
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, | |
}) | |
core.register_chatcommand("revoke", { | |
params = "<name> <privilege>|all", | |
description = "Remove privilege from player", | |
privs = {}, | |
func = function(name, param) | |
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) | |
local basic_privs = | |
core.string_to_privs(core.settings:get("basic_privs") or "interact,shout") | |
for priv, _ in pairs(revoke_privs) do | |
if not basic_privs[priv] and | |
not core.check_player_privs(name, {privs=true}) then | |
return false, "Your privileges are insufficient." | |
end | |
end | |
if revoke_priv_str == "all" then | |
privs = {} | |
else | |
for priv, _ in pairs(revoke_privs) do | |
privs[priv] = nil | |
end | |
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), ' ') | |
end, | |
}) | |
core.register_chatcommand("setpassword", { | |
params = "<name> <password>", | |
description = "Set player's password", | |
privs = {password=true}, | |
func = function(name, param) | |
local toname, raw_password = string.match(param, "^([^ ]+) +(.+)$") | |
if not toname then | |
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 = "?" | |
if not raw_password then | |
core.set_player_password(toname, "") | |
act_str_past = "cleared" | |
act_str_pres = "clears" | |
else | |
core.set_player_password(toname, | |
core.get_password_hash(toname, | |
raw_password)) | |
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 .. ".") | |
return true, "Password of player \"" .. toname .. "\" " .. act_str_past | |
end, | |
}) | |
core.register_chatcommand("clearpassword", { | |
params = "<name>", | |
description = "Set empty password", | |
privs = {password=true}, | |
func = function(name, param) | |
local toname = param | |
if toname == "" then | |
return false, "Name field required" | |
end | |
core.set_player_password(toname, '') | |
core.log("action", name .. " clears password of " .. toname .. ".") | |
return true, "Password of player \"" .. toname .. "\" cleared" | |
end, | |
}) | |
core.register_chatcommand("auth_reload", { | |
params = "", | |
description = "Reload authentication data", | |
privs = {server=true}, | |
func = function(name, param) | |
local done = core.auth_reload() | |
return done, (done and "Done." or "Failed.") | |
end, | |
}) | |
core.register_chatcommand("remove_player", { | |
params = "<name>", | |
description = "Remove player 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, | |
}) | |
core.register_chatcommand("teleport", { | |
params = "<X>,<Y>,<Z> | <to_name> | <name> <X>,<Y>,<Z> | <name> <to_name>", | |
description = "Teleport to player or position", | |
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} | |
local n = core.get_node_or_nil(p) | |
if n and n.name then | |
local def = core.registered_nodes[n.name] | |
if def and not def.walkable then | |
return p, true | |
end | |
end | |
end | |
return pos, false | |
end | |
local teleportee = nil | |
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 | |
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 | |
teleportee = core.get_player_by_name(name) | |
if teleportee then | |
teleportee:setpos(p) | |
return true, "Teleporting to "..core.pos_to_string(p) | |
end | |
end | |
local teleportee = nil | |
local p = nil | |
local target_name = nil | |
target_name = param:match("^([^ ]+)$") | |
teleportee = core.get_player_by_name(name) | |
if target_name then | |
local target = core.get_player_by_name(target_name) | |
if target then | |
p = target:getpos() | |
end | |
end | |
if teleportee and p then | |
p = find_free_position_near(p) | |
teleportee:setpos(p) | |
return true, "Teleporting to " .. target_name | |
.. " at "..core.pos_to_string(p) | |
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 = nil | |
local p = {} | |
local teleportee_name = nil | |
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:setpos(p) | |
return true, "Teleporting " .. teleportee_name | |
.. " to " .. core.pos_to_string(p) | |
end | |
local teleportee = nil | |
local p = nil | |
local teleportee_name = nil | |
local target_name = 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:getpos() | |
end | |
end | |
if teleportee and p then | |
p = find_free_position_near(p) | |
teleportee:setpos(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)' | |
end, | |
}) | |
core.register_chatcommand("set", { | |
params = "[-n] <name> <value> | <name>", | |
description = "Set or read server configuration setting", | |
privs = {server=true}, | |
func = function(name, param) | |
local arg, setname, setvalue = string.match(param, "(-[n]) ([^ ]+) (.+)") | |
if arg and arg == "-n" and setname and setvalue then | |
core.settings:set(setname, setvalue) | |
return true, setname .. " = " .. setvalue | |
end | |
local 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." | |
end | |
core.settings:set(setname, setvalue) | |
return true, setname .. " = " .. setvalue | |
end | |
local setname = string.match(param, "([^ ]+)") | |
if setname then | |
local 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, | |
}) | |
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>)", | |
description = "Load (or, if nonexistent, generate) map blocks " | |
.. "contained in area pos1 to pos2", | |
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", | |
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, | |
}) | |
core.register_chatcommand("fixlight", { | |
params = "(here [radius]) | (<pos1> <pos2>)", | |
description = "Resets lighting in the area between pos1 and pos2", | |
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, | |
}) | |
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() then | |
return false, "Cannot give an unknown item" | |
end | |
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) | |
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 | |
return true, ("%q %sadded to inventory.") | |
: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) | |
end | |
end | |
core.register_chatcommand("give", { | |
params = "<name> <ItemString>", | |
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, | |
}) | |
core.register_chatcommand("giveme", { | |
params = "<ItemString>", | |
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, | |
}) | |
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)) | |
local player = core.get_player_by_name(name) | |
if player == nil then | |
core.log("error", "Unable to spawn entity, player is nil") | |
return false, "Unable to spawn entity, player is nil" | |
end | |
if p == "" then | |
p = player:getpos() | |
else | |
p = core.string_to_pos(p) | |
if p == nil then | |
return false, "Invalid parameters ('" .. param .. "')" | |
end | |
end | |
p.y = p.y + 1 | |
core.add_entity(p, entityname) | |
return true, ("%q spawned."):format(entityname) | |
end, | |
}) | |
core.register_chatcommand("pulverize", { | |
params = "", | |
description = "Destroy item in hand", | |
func = function(name, param) | |
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 | |
if player:get_wielded_item():is_empty() then | |
return false, "Unable to pulverize, no item in hand." | |
end | |
player:set_wielded_item(nil) | |
return true, "An item was pulverized." | |
end, | |
}) | |
-- Key = player name | |
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 | |
core.rollback_punch_callbacks[name](pos, node, puncher) | |
core.rollback_punch_callbacks[name] = nil | |
end | |
end) | |
core.register_chatcommand("rollback_check", { | |
params = "[<range>] [<seconds>] [limit]", | |
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 | |
local range, seconds, limit = | |
param:match("(%d+) *(%d*) *(%d*)") | |
range = tonumber(range) or 0 | |
seconds = tonumber(seconds) or 86400 | |
limit = tonumber(limit) or 5 | |
if limit > 100 then | |
return false, "That limit is too high!" | |
end | |
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) .. "...") | |
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 | |
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 | |
local time = os.time() | |
for i = num_actions, 1, -1 do | |
local action = actions[i] | |
core.chat_send_player(name, | |
("%s %s %s -> %s %d seconds ago.") | |
:format( | |
core.pos_to_string(action.pos), | |
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, | |
}) | |
core.register_chatcommand("rollback", { | |
params = "<player name> [<seconds>] | :<actor> [<seconds>]", | |
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 | |
local player_name = nil | |
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.") | |
local success, log = core.rollback_revert_actions_by( | |
target_name, seconds) | |
local response = "" | |
if #log > 100 then | |
response = "(log is too long to show)\n" | |
else | |
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, | |
}) | |
core.register_chatcommand("status", { | |
description = "Print server status", | |
func = function(name, param) | |
return true, core.get_server_status() | |
end, | |
}) | |
core.register_chatcommand("time", { | |
params = "<0..23>:<0..59> | <0..24000>", | |
description = "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 | |
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 = "Display day count", | |
func = function(name, param) | |
return true, "Current day is " .. core.get_day_count() | |
end | |
}) | |
core.register_chatcommand("shutdown", { | |
description = "Shutdown server", | |
params = "[delay_in_seconds(0..inf) or -1 for cancel] [reconnect] [message]", | |
privs = {server=true}, | |
func = function(name, param) | |
local delay, reconnect, message = param:match("([^ ][-]?[0-9]+)([^ ]+)(.*)") | |
message = message or "" | |
if delay ~= "" then | |
delay = tonumber(param) or 0 | |
else | |
delay = 0 | |
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, | |
}) | |
core.register_chatcommand("ban", { | |
params = "<name>", | |
description = "Ban IP of player", | |
privs = {ban=true}, | |
func = function(name, param) | |
if param == "" then | |
return true, "Ban list: " .. core.get_ban_list() | |
end | |
if not core.get_player_by_name(param) then | |
return false, "No such player." | |
end | |
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, | |
}) | |
core.register_chatcommand("unban", { | |
params = "<name/ip>", | |
description = "Remove IP ban", | |
privs = {ban=true}, | |
func = function(name, param) | |
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, | |
}) | |
core.register_chatcommand("kick", { | |
params = "<name> [reason]", | |
description = "Kick a player", | |
privs = {kick=true}, | |
func = function(name, param) | |
local tokick, reason = param:match("([^ ]+) (.+)") | |
tokick = tokick or param | |
if not core.kick_player(tokick, reason) then | |
return false, "Failed to kick player " .. tokick | |
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 | |
end, | |
}) | |
core.register_chatcommand("clearobjects", { | |
params = "[full|quick]", | |
description = "Clear all objects in world", | |
privs = {server=true}, | |
func = function(name, param) | |
local options = {} | |
if param == "" or param == "full" then | |
options.mode = "full" | |
elseif param == "quick" then | |
options.mode = "quick" | |
else | |
return false, "Invalid usage, see /help clearobjects." | |
end | |
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 .. ")") | |
core.clear_objects(options) | |
core.log("action", "Object clearing done.") | |
core.chat_send_all("*** Cleared all objects.") | |
end, | |
}) | |
core.register_chatcommand("msg", { | |
params = "<name> <message>", | |
description = "Send a private message", | |
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", "PM from " .. name .. " to " .. sendto | |
.. ": " .. message) | |
core.chat_send_player(sendto, "PM from " .. name .. ": " | |
.. message) | |
return true, "Message sent." | |
end, | |
}) | |
core.register_chatcommand("last-login", { | |
params = "[name]", | |
description = "Get the last login time of a player", | |
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, | |
}) |