X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=builtin%2Fgame%2Fchatcommands.lua;h=cbfe2fb257bb82412a26fa0228f1116f71e0d352;hb=41bc0efe717b0543fe5988018a43ca2fffc52c9d;hp=6c5fea69388c27c6939beb2f2d3edf7e7a17de88;hpb=c4359ff65cd8e4e754442b9f2ef7051a8eaa4241;p=minetest.git diff --git a/builtin/game/chatcommands.lua b/builtin/game/chatcommands.lua index 6c5fea693..cbfe2fb25 100644 --- a/builtin/game/chatcommands.lua +++ b/builtin/game/chatcommands.lua @@ -1,254 +1,321 @@ --- Minetest: builtin/chatcommands.lua +-- Minetest: builtin/game/chatcommands.lua -- -- Chat command handler -- -core.chatcommands = {} -function core.register_chatcommand(cmd, def) - def = def or {} - def.params = def.params or "" - def.description = def.description or "" - def.privs = def.privs or {} - core.chatcommands[cmd] = def -end +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 param then - param = "" + if not cmd then + core.chat_send_player(name, "-!- Empty command") + return true end - local cmd_def = core.chatcommands[cmd] - if cmd_def then - local has_privs, missing_privs = core.check_player_privs(name, cmd_def.privs) - if has_privs then - cmd_def.func(name, param) - else - core.chat_send_player(name, "You don't have permission to run this command (missing privileges: "..table.concat(missing_privs, ", ")..")") + + 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 - return true -- handled chat message + else + core.chat_send_player(name, "You don't have permission" + .. " to run this command (missing privileges: " + .. table.concat(missing_privs, ", ") .. ")") end - return false + 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 = "", - description = "chat action (eg. /me orders a pizza)", + description = "Display chat action (e.g., '/me orders a pizza' displays" + .. " ' orders a pizza')", privs = {shout=true}, func = function(name, param) core.chat_send_all("* " .. name .. " " .. param) end, }) -core.register_chatcommand("help", { - privs = {}, - params = "(nothing)/all/privs/", - description = "Get help for commands or list privileges", - func = function(name, param) - local format_help_line = function(cmd, def) - local msg = "/"..cmd - if def.params and def.params ~= "" then msg = msg .. " " .. def.params end - if def.description and def.description ~= "" then msg = msg .. ": " .. def.description end - return msg - end - if param == "" then - local msg = "" - cmds = {} - for cmd, def in pairs(core.chatcommands) do - if core.check_player_privs(name, def.privs) then - table.insert(cmds, cmd) - end - end - core.chat_send_player(name, "Available commands: "..table.concat(cmds, " ")) - core.chat_send_player(name, "Use '/help ' to get more information, or '/help all' to list everything.") - elseif param == "all" then - core.chat_send_player(name, "Available commands:") - for cmd, def in pairs(core.chatcommands) do - if core.check_player_privs(name, def.privs) then - core.chat_send_player(name, format_help_line(cmd, def)) - end - end - elseif param == "privs" then - core.chat_send_player(name, "Available privileges:") - for priv, def in pairs(core.registered_privileges) do - core.chat_send_player(name, priv..": "..def.description) - 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 - local cmd = param - def = core.chatcommands[cmd] - if not def then - core.chat_send_player(name, "Command not available: "..cmd) - else - core.chat_send_player(name, format_help_line(cmd, def)) - end + return false, "There's no administrator named in the config file." end end, }) + core.register_chatcommand("privs", { - params = "", - description = "print out privileges of player", - func = function(name, param) - if param == "" then - param = name - else - --[[if not core.check_player_privs(name, {privs=true}) then - core.chat_send_player(name, "Privileges of "..param.." are hidden from you.") - return - end]] - end - core.chat_send_player(name, "Privileges of "..param..": "..core.privs_to_string(core.get_player_privs(param), ' ')) + params = "[]", + 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 + for priv, _ in pairs(grantprivs) do + 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 + core.register_chatcommand("grant", { - params = " |all", + params = " ( | all)", description = "Give privilege to 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 - core.chat_send_player(name, "Your privileges are insufficient.") - return - end local grantname, grantprivstr = string.match(param, "([^ ]+) (.+)") if not grantname or not grantprivstr then - core.chat_send_player(name, "Invalid parameters (see /help grant)") - return - elseif not core.auth_table[grantname] then - core.chat_send_player(name, "Player "..grantname.." does not exist.") - return - 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_known = true - for priv, _ in pairs(grantprivs) do - if priv ~= "interact" and priv ~= "shout" and priv ~= "interact_extra" and not core.check_player_privs(name, {privs=true}) then - core.chat_send_player(name, "Your privileges are insufficient.") - return - end - if not core.registered_privileges[priv] then - core.chat_send_player(name, "Unknown privilege: "..priv) - privs_known = false - end - privs[priv] = true - end - if not privs_known then - return + return false, "Invalid parameters (see /help grant)" end - core.set_player_privs(grantname, privs) - core.log(name..' granted ('..core.privs_to_string(grantprivs, ', ')..') privileges to '..grantname) - core.chat_send_player(name, "Privileges of "..grantname..": "..core.privs_to_string(core.get_player_privs(grantname), ' ')) - if grantname ~= name then - core.chat_send_player(grantname, name.." granted you privileges: "..core.privs_to_string(grantprivs, ' ')) + return handle_grant_command(name, grantname, grantprivstr) + end, +}) + +core.register_chatcommand("grantme", { + params = " | 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 = " |all", + params = " ( | 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 - core.chat_send_player(name, "Your privileges are insufficient.") - return - end - local revokename, revokeprivstr = string.match(param, "([^ ]+) (.+)") - if not revokename or not revokeprivstr then - core.chat_send_player(name, "Invalid parameters (see /help revoke)") - return - elseif not core.auth_table[revokename] then - core.chat_send_player(name, "Player "..revokename.." does not exist.") - return - end - local revokeprivs = core.string_to_privs(revokeprivstr) - local privs = core.get_player_privs(revokename) - for priv, _ in pairs(revokeprivs) do - if priv ~= "interact" and priv ~= "shout" and priv ~= "interact_extra" and not core.check_player_privs(name, {privs=true}) then - core.chat_send_player(name, "Your privileges are insufficient.") - return + 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 revokeprivstr == "all" then + if revoke_priv_str == "all" then + revoke_privs = privs privs = {} else - for priv, _ in pairs(revokeprivs) do + for priv, _ in pairs(revoke_privs) do privs[priv] = nil end end - core.set_player_privs(revokename, privs) - core.log(name..' revoked ('..core.privs_to_string(revokeprivs, ', ')..') privileges from '..revokename) - core.chat_send_player(name, "Privileges of "..revokename..": "..core.privs_to_string(core.get_player_privs(revokename), ' ')) - if revokename ~= name then - core.chat_send_player(revokename, name.." revoked privileges from you: "..core.privs_to_string(revokeprivs, ' ')) + + for priv, _ in pairs(revoke_privs) do + 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), ' ') end, }) + core.register_chatcommand("setpassword", { params = " ", - description = "set given 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 = string.match(param, "^([^ ]+) *$") + toname = param:match("^([^ ]+) *$") raw_password = nil end if not toname then - core.chat_send_player(name, "Name field required") - return + return false, "Name field required" end - local actstr = "?" + local act_str_past = "?" + local act_str_pres = "?" if not raw_password then core.set_player_password(toname, "") - actstr = "cleared" + act_str_past = "cleared" + act_str_pres = "clears" else - core.set_player_password(toname, core.get_password_hash(toname, raw_password)) - actstr = "set" + core.set_player_password(toname, + core.get_password_hash(toname, + raw_password)) + act_str_past = "set" + act_str_pres = "sets" end - core.chat_send_player(name, "Password of player \""..toname.."\" "..actstr) if toname ~= name then - core.chat_send_player(toname, "Your password was "..actstr.." by "..name) + 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 = "", - description = "set empty password", + description = "Set empty password", privs = {password=true}, func = function(name, param) - toname = param + local toname = param if toname == "" then - core.chat_send_player(name, "Name field required") - return + return false, "Name field required" end core.set_player_password(toname, '') - core.chat_send_player(name, "Password of player \""..toname.."\" cleared") + + 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", + description = "Reload authentication data", privs = {server=true}, func = function(name, param) local done = core.auth_reload() - if done then - core.chat_send_player(name, "Done.") - else - core.chat_send_player(name, "Failed.") + return done, (done and "Done." or "Failed.") + end, +}) + +core.register_chatcommand("remove_player", { + params = "", + 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 = ",, | | ,, | ", - description = "teleport to given position", + params = ",, | | ( ,,) | ( )", + description = "Teleport to player or position", privs = {teleport=true}, func = function(name, param) -- Returns (pos, true) if found, otherwise (pos, false) @@ -278,18 +345,63 @@ core.register_chatcommand("teleport", { 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 - core.chat_send_player(name, "Teleporting to ("..p.x..", "..p.y..", "..p.z..")") teleportee:setpos(p) - return + 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 - target_name = string.match(param, "^([^ ]+)$") - teleportee = core.get_player_by_name(name) + 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 @@ -298,125 +410,162 @@ core.register_chatcommand("teleport", { end if teleportee and p then p = find_free_position_near(p) - core.chat_send_player(name, "Teleporting to "..target_name.." at ("..p.x..", "..p.y..", "..p.z..")") teleportee:setpos(p) - return - end - - if core.check_player_privs(name, {bring=true}) then - local teleportee = nil - local p = {} - local teleportee_name = nil - teleportee_name, 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 teleportee_name then - teleportee = core.get_player_by_name(teleportee_name) - end - if teleportee and p.x and p.y and p.z then - core.chat_send_player(name, "Teleporting "..teleportee_name.." to ("..p.x..", "..p.y..", "..p.z..")") - teleportee:setpos(p) - return - 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) - core.chat_send_player(name, "Teleporting "..teleportee_name.." to "..target_name.." at ("..p.x..", "..p.y..", "..p.z..")") - teleportee:setpos(p) - return - end + return true, "Teleporting " .. teleportee_name + .. " to " .. target_name + .. " at " .. core.pos_to_string(p) end - core.chat_send_player(name, "Invalid parameters (\""..param.."\") or player not found (see /help teleport)") - return + return false, 'Invalid parameters ("' .. param + .. '") or player not found (see /help teleport)' end, }) core.register_chatcommand("set", { - params = "[-n] | ", - description = "set or read server configuration setting", + params = "([-n] ) | ", + 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.setting_set(setname, setvalue) - core.chat_send_player(name, setname.." = "..setvalue) - return + core.settings:set(setname, setvalue) + return true, setname .. " = " .. setvalue end local setname, setvalue = string.match(param, "([^ ]+) (.+)") if setname and setvalue then - if not core.setting_get(setname) then - core.chat_send_player(name, "Failed. Use '/set -n ' to create a new setting.") - return + if not core.settings:get(setname) then + return false, "Failed. Use '/set -n ' to create a new setting." end - core.setting_set(setname, setvalue) - core.chat_send_player(name, setname.." = "..setvalue) - return + core.settings:set(setname, setvalue) + return true, setname .. " = " .. setvalue end local setname = string.match(param, "([^ ]+)") if setname then - local setvalue = core.setting_get(setname) + local setvalue = core.settings:get(setname) if not setvalue then setvalue = "" end - core.chat_send_player(name, setname.." = "..setvalue) - return + 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 []) | ( )", + description = "Load (or, if nonexistent, generate) map blocks " + .. "contained in area pos1 to pos2 ( and 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 []) | ( )", + description = "Delete map blocks contained in area pos1 to pos2 " + .. "( and 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, +}) + +core.register_chatcommand("fixlight", { + params = "(here []) | ( )", + description = "Resets lighting in the area between pos1 and pos2 " + .. "( and 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.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 - core.chat_send_player(name, "Invalid parameters (see /help set)") end, }) core.register_chatcommand("mods", { params = "", - description = "lists mods installed on the server", + description = "List mods installed on the server", privs = {}, func = function(name, param) - local response = "" - local modnames = core.get_modnames() - for i, mod in ipairs(modnames) do - response = response .. mod - -- Add space if not at the end - if i ~= #modnames then - response = response .. " " - end - end - core.chat_send_player(name, response) + 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..'"') - core.log(cmd..' invoked, stackstring="'..stackstring..'"') + core.log("action", giver .. " invoked " .. cmd + .. ', stackstring="' .. stackstring .. '"') local itemstack = ItemStack(stackstring) if itemstack:is_empty() then - core.chat_send_player(giver, 'error: cannot give an empty item') - return + return false, "Cannot give an empty item" elseif not itemstack:is_known() then - core.chat_send_player(giver, 'error: cannot give an unknown item') - return + return false, "Cannot give an unknown item" end local receiverref = core.get_player_by_name(receiver) if receiverref == nil then - core.chat_send_player(giver, receiver..' is not a known player') - return + 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 @@ -428,81 +577,89 @@ 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 - core.chat_send_player(giver, '"'..stackstring - ..'" '..partiality..'added to inventory.'); + return true, ("%q %sadded to inventory.") + :format(stackstring, partiality) else - core.chat_send_player(giver, '"'..stackstring - ..'" '..partiality..'added to '..receiver..'\'s inventory.'); - core.chat_send_player(receiver, '"'..stackstring - ..'" '..partiality..'added to inventory.'); + 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 = " ", - description = "give item to player", + params = " ", + 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 - core.chat_send_player(name, "name and itemstring required") - return + return false, "Name and ItemString required" end - handle_give_command("/give", name, toname, itemstring) + return handle_give_command("/give", name, toname, itemstring) end, }) + core.register_chatcommand("giveme", { - params = "", - description = "give item to yourself", + params = "", + description = "Give item to yourself", privs = {give=true}, func = function(name, param) local itemstring = string.match(param, "(.+)$") if not itemstring then - core.chat_send_player(name, "itemstring required") - return + return false, "ItemString required" end - handle_give_command("/giveme", name, name, itemstring) + return handle_give_command("/giveme", name, name, itemstring) end, }) + core.register_chatcommand("spawnentity", { - params = "", - description = "spawn entity at your position", + params = " [,,]", + description = "Spawn entity at given (or your) position", privs = {give=true, interact=true}, func = function(name, param) - local entityname = string.match(param, "(.+)$") + local entityname, p = string.match(param, "^([^ ]+) *(.*)$") if not entityname then - core.chat_send_player(name, "entityname required") - return + return false, "EntityName required" end - core.log("action", '/spawnentity invoked, entityname="'..entityname..'"') + 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 true -- Handled chat message + return false, "Unable to spawn entity, player is nil" + end + if not minetest.registered_entities[entityname] then + return false, "Cannot spawn an unknown entity" + 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 - local p = player:getpos() p.y = p.y + 1 core.add_entity(p, entityname) - core.chat_send_player(name, '"'..entityname - ..'" spawned.'); + return true, ("%q spawned."):format(entityname) end, }) + core.register_chatcommand("pulverize", { params = "", - description = "delete item in hand", - privs = {}, + description = "Destroy item in hand", func = function(name, param) local player = core.get_player_by_name(name) - if player == nil then - core.log("error", "Unable to pulverize, player is nil") - return true -- Handled chat message + 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 - core.chat_send_player(name, 'Unable to pulverize, no item in hand.') - else - player:set_wielded_item(nil) - core.chat_send_player(name, 'An item was pulverized.') + return false, "Unable to pulverize, no item in hand." end + player:set_wielded_item(nil) + return true, "An item was pulverized." end, }) @@ -510,39 +667,45 @@ 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 end) core.register_chatcommand("rollback_check", { - params = "[] [] [limit]", - description = "check who has last touched a node or near it, ".. - "max. ago (default range=0, seconds=86400=24h, limit=5)", + params = "[] [] []", + description = "Check who last touched a node or a node near it" + .. " within the time specified by . 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 - core.chat_send_player(name, "That limit is too high!") - return + return false, "That limit is too high!" end - core.chat_send_player(name, "Punch a node (range=".. - range..", seconds="..seconds.."s, limit="..limit..")") 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).."...") + 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") + core.chat_send_player(name, "Nobody has touched" + .. " the specified location in " + .. seconds .. " seconds") return end local time = os.time() @@ -558,148 +721,196 @@ core.register_chatcommand("rollback_check", { time - action.time)) end end + + return true, "Punch a node (range=" .. range .. ", seconds=" + .. seconds .. "s, limit=" .. limit .. ")" end, }) core.register_chatcommand("rollback", { - params = " [] | : []", - description = "revert actions of a player; default for is 60", + params = "( []) | (: [])", + description = "Revert actions of a player. Default for 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 - core.chat_send_player(name, "Invalid parameters. See /help rollback and /help rollback_check") - return + 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.") + 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 - core.chat_send_player(name, "(log is too long to show)") + response = "(log is too long to show)\n" else for _, line in pairs(log) do - core.chat_send_player(name, line) + response = response .. line .. "\n" end end - if success then - core.chat_send_player(name, "Reverting actions succeeded.") - else - core.chat_send_player(name, "Reverting actions FAILED.") - end + response = response .. "Reverting actions " + .. (success and "succeeded." or "FAILED.") + return success, response end, }) core.register_chatcommand("status", { - params = "", - description = "print server status line", - privs = {}, + description = "Print server status", func = function(name, param) - core.chat_send_player(name, core.get_server_status()) + return true, core.get_server_status() end, }) core.register_chatcommand("time", { - params = "<0...24000>", - description = "set time of day", - privs = {settime=true}, + params = "<0..23>:<0..59> | <0..24000>", + description = "Set time of day", + privs = {}, func = function(name, param) if param == "" then - core.chat_send_player(name, "Missing parameter") - return - end - local newtime = tonumber(param) - if newtime == nil then - core.chat_send_player(name, "Invalid time") - else - core.set_timeofday((newtime % 24000) / 24000) - core.chat_send_player(name, "Time of day changed.") - core.log("action", name .. " sets time " .. newtime) - end + 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", { - params = "", - description = "shutdown server", + params = "[ | -1] [reconnect] []", + description = "Shutdown server (-1 cancels a delayed shutdown)", privs = {server=true}, func = function(name, param) - core.log("action", name .. " shuts down server") - core.request_shutdown() - core.chat_send_all("*** Server shutting down (operator request).") + 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 = "", - description = "ban IP of player", + description = "Ban IP of player", privs = {ban=true}, func = function(name, param) if param == "" then - core.chat_send_player(name, "Ban list: " .. core.get_ban_list()) - return + return true, "Ban list: " .. core.get_ban_list() end if not core.get_player_by_name(param) then - core.chat_send_player(name, "No such player") - return + return false, "No such player." end if not core.ban_player(param) then - core.chat_send_player(name, "Failed to ban player") - else - local desc = core.get_ban_description(param) - core.chat_send_player(name, "Banned " .. desc .. ".") - core.log("action", name .. " bans " .. desc .. ".") + 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 = "", - description = "remove IP ban", + params = " | ", + description = "Remove IP ban", privs = {ban=true}, func = function(name, param) if not core.unban_player_or_ip(param) then - core.chat_send_player(name, "Failed to unban player/IP") - else - core.chat_send_player(name, "Unbanned " .. param) - core.log("action", name .. " unbans " .. param) + return false, "Failed to unban player/IP." end + core.log("action", name .. " unbans " .. param) + return true, "Unbanned " .. param end, }) core.register_chatcommand("kick", { - params = " [reason]", - description = "kick a player", + params = " []", + description = "Kick a player", privs = {kick=true}, func = function(name, param) - local tokick, reason = string.match(param, "([^ ]+) (.+)") - if not tokick then - tokick = param - end + local tokick, reason = param:match("([^ ]+) (.+)") + tokick = tokick or param if not core.kick_player(tokick, reason) then - core.chat_send_player(name, "Failed to kick player " .. tokick) - else - core.chat_send_player(name, "kicked " .. tokick) - core.log("action", name .. " kicked " .. tokick) + 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 = "", - description = "clear all objects in world", + params = "[full | quick]", + description = "Clear all objects in world", privs = {server=true}, func = function(name, param) - core.log("action", name .. " clears all objects") - core.chat_send_all("Clearing all objects. This may take long. You may experience a timeout. (by " .. name .. ")") - core.clear_objects() - core.log("action", "object clearing done") + 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, }) @@ -709,17 +920,63 @@ core.register_chatcommand("msg", { description = "Send a private message", privs = {shout=true}, func = function(name, param) - local found, _, sendto, message = param:find("^([^%s]+)%s(.+)$") - if found then - if core.get_player_by_name(sendto) then - core.log("action", "PM from "..name.." to "..sendto..": "..message) - core.chat_send_player(sendto, "PM from "..name..": "..message) - core.chat_send_player(name, "Message sent") - else - core.chat_send_player(name, "The player "..sendto.." is not online") + 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 = "[]", + 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, +}) + +core.register_chatcommand("clearinv", { + params = "[]", + 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 run this command (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 - core.chat_send_player(name, "Invalid usage, see /help msg") + return false, "Player must be online to clear inventory!" end end, })