X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=builtin%2Fgame%2Fchatcommands.lua;h=5d59559728c6c672e925c7cf2b0d49b12eb7c99f;hb=7a4878cd0b563296b3f2718b79dc9af177dd4fd2;hp=fbc91ef01f90b28e3fcbe092bf48cc34c074901d;hpb=f26421228bbd31f02bf16b45a4b82be84f233e52;p=minetest.git diff --git a/builtin/game/chatcommands.lua b/builtin/game/chatcommands.lua index fbc91ef01..5d5955972 100644 --- a/builtin/game/chatcommands.lua +++ b/builtin/game/chatcommands.lua @@ -4,26 +4,33 @@ -- Chat command handler -- -core.chatcommands = {} +core.registered_chatcommands = {} +core.chatcommands = core.registered_chatcommands -- BACKWARDS COMPATIBILITY 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 + def.mod_origin = core.get_current_modname() or "??" + core.registered_chatcommands[cmd] = def end -if core.setting_getbool("mod_profiling") then - local tracefct = profiling_print_log - profiling_print_log = nil - core.register_chatcommand("save_mod_profile", - { - params = "", - description = "save mod profiling data to logfile " .. - "(depends on default loglevel)", - func = tracefct, - privs = { server=true } - }) +function core.unregister_chatcommand(name) + if core.registered_chatcommands[name] then + core.registered_chatcommands[name] = nil + else + core.log("warning", "Not unregistering chatcommand " ..name.. + " because it doesn't exist.") + end +end + +function core.override_chatcommand(name, redefinition) + local chatcommand = core.registered_chatcommands[name] + assert(chatcommand, "Attempt to override non-existent chatcommand "..name) + for k, v in pairs(redefinition) do + rawset(chatcommand, k, v) + end + core.registered_chatcommands[name] = chatcommand end core.register_on_chat_message(function(name, message) @@ -31,12 +38,13 @@ core.register_on_chat_message(function(name, message) if not param then param = "" end - local cmd_def = core.chatcommands[cmd] + local cmd_def = core.registered_chatcommands[cmd] if not cmd_def then return false 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) @@ -49,25 +57,65 @@ core.register_on_chat_message(function(name, message) return true -- Handled chat message end) +if core.setting_getbool("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("admin", { + description = "Show the name of the server owner", + func = function(name) + local admin = minetest.setting_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("help", { privs = {}, params = "[all/privs/]", description = "Get help for commands or list privileges", func = function(name, param) local function format_help_line(cmd, def) - local msg = "/"..cmd + local msg = core.colorize("#00ffff", "/"..cmd) if def.params and def.params ~= "" then msg = msg .. " " .. def.params end @@ -79,9 +127,9 @@ core.register_chatcommand("help", { if param == "" then local msg = "" local cmds = {} - for cmd, def in pairs(core.chatcommands) do + for cmd, def in pairs(core.registered_chatcommands) do if core.check_player_privs(name, def.privs) then - table.insert(cmds, cmd) + cmds[#cmds + 1] = cmd end end table.sort(cmds) @@ -90,9 +138,9 @@ core.register_chatcommand("help", { .. " or '/help all' to list everything." elseif param == "all" then local cmds = {} - for cmd, def in pairs(core.chatcommands) do + for cmd, def in pairs(core.registered_chatcommands) do if core.check_player_privs(name, def.privs) then - table.insert(cmds, format_help_line(cmd, def)) + cmds[#cmds + 1] = format_help_line(cmd, def) end end table.sort(cmds) @@ -100,13 +148,13 @@ core.register_chatcommand("help", { elseif param == "privs" then local privs = {} for priv, def in pairs(core.registered_privileges) do - table.insert(privs, priv .. ": " .. def.description) + privs[#privs + 1] = priv .. ": " .. def.description end table.sort(privs) return true, "Available privileges:\n"..table.concat(privs, "\n") else local cmd = param - local def = core.chatcommands[cmd] + local def = core.registered_chatcommands[cmd] if not def then return false, "Command not available: "..cmd else @@ -118,59 +166,80 @@ core.register_chatcommand("help", { core.register_chatcommand("privs", { params = "", - description = "print out privileges of player", - func = function(name, param) - param = (param ~= "" and param or name) - return true, "Privileges of " .. param .. ": " + 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(param), ' ') + 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.setting_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 = " |all", description = "Give privilege to player", 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 grantname, grantprivstr = string.match(param, "([^ ]+) (.+)") if not grantname or not grantprivstr then return false, "Invalid parameters (see /help grant)" - elseif not core.auth_table[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 = "" - for priv, _ in pairs(grantprivs) do - if priv ~= "interact" and priv ~= "shout" and - not core.check_player_privs(name, {privs=true}) 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", name..' granted ('..core.privs_to_string(grantprivs, ', ')..') privileges to '..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 true, "Privileges of " .. grantname .. ": " - .. core.privs_to_string( - core.get_player_privs(grantname), ' ') + return handle_grant_command(name, name, param) end, }) + core.register_chatcommand("revoke", { params = " |all", description = "Remove privilege from player", @@ -183,13 +252,15 @@ core.register_chatcommand("revoke", { 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.auth_table[revoke_name] then + 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.setting_get("basic_privs") or "interact,shout") for priv, _ in pairs(revoke_privs) do - if priv ~= "interact" and priv ~= "shout" and priv ~= "interact_extra" and + if not basic_privs[priv] and not core.check_player_privs(name, {privs=true}) then return false, "Your privileges are insufficient." end @@ -218,7 +289,7 @@ core.register_chatcommand("revoke", { 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, "^([^ ]+) +(.+)$") @@ -256,7 +327,7 @@ core.register_chatcommand("setpassword", { core.register_chatcommand("clearpassword", { params = "", - description = "set empty password", + description = "Set empty password", privs = {password=true}, func = function(name, param) local toname = param @@ -273,7 +344,7 @@ core.register_chatcommand("clearpassword", { 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() @@ -283,7 +354,7 @@ core.register_chatcommand("auth_reload", { core.register_chatcommand("teleport", { params = ",, | | ,, | ", - description = "teleport to given position", + description = "Teleport to player or position", privs = {teleport=true}, func = function(name, param) -- Returns (pos, true) if found, otherwise (pos, false) @@ -313,10 +384,16 @@ core.register_chatcommand("teleport", { p.x = tonumber(p.x) p.y = tonumber(p.y) p.z = tonumber(p.z) - teleportee = core.get_player_by_name(name) - if teleportee and p.x and p.y and p.z then - teleportee:setpos(p) - return true, "Teleporting to "..core.pos_to_string(p) + if p.x and p.y and p.z then + local lm = tonumber(minetest.setting_get("map_generation_limit") or 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 @@ -385,7 +462,7 @@ core.register_chatcommand("teleport", { core.register_chatcommand("set", { params = "[-n] | ", - description = "set or read server configuration setting", + description = "Set or read server configuration setting", privs = {server=true}, func = function(name, param) local arg, setname, setvalue = string.match(param, "(-[n]) ([^ ]+) (.+)") @@ -413,40 +490,65 @@ core.register_chatcommand("set", { end, }) -core.register_chatcommand("deleteblocks", { +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]) | ( )", - description = "delete map blocks contained in area pos1 to pos2", + description = "Load (or, if nonexistent, generate) map blocks " + .. "contained in area pos1 to pos2", privs = {server=true}, func = function(name, param) - local p1 = {} - local p2 = {} - local args = param:split(" ") - if args[1] == "here" then - local player = core.get_player_by_name(name) - if player == nil then - core.log("error", "player is nil") - return false, "Unable to get current position; player is nil" - end - p1 = player:getpos() - p2 = p1 + local p1, p2 = parse_range_str(name, param) + if p1 == false then + return false, p2 + end - if #args >= 2 then - local radius = tonumber(args[2]) or 0 - p1 = vector.add(p1, radius) - p2 = vector.subtract(p2, radius) - end - else - local pos1, pos2 = unpack(param:split(") (")) - if pos1 == nil or pos2 == nil then - return false, "Incorrect area format. Expected: (x1,y1,z1) (x2,y2,z2)" - end + local context = { + current_blocks = 0, + total_blocks = 0, + start_time = os.clock(), + requestor_name = name + } - p1 = core.string_to_pos(pos1 .. ")") - p2 = core.string_to_pos("(" .. pos2) + core.emerge_area(p1, p2, emergeblocks_callback, context) + core.after(2, emergeblocks_progress_update, context) - if p1 == nil or p2 == nil then - return false, "Incorrect area format. Expected: (x1,y1,z1) (x2,y2,z2)" - end + 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]) | ( )", + 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 @@ -505,7 +607,7 @@ end core.register_chatcommand("give", { params = " ", - description = "give item to player", + description = "Give item to player", privs = {give=true}, func = function(name, param) local toname, itemstring = string.match(param, "^([^ ]+) +(.+)$") @@ -518,7 +620,7 @@ core.register_chatcommand("give", { core.register_chatcommand("giveme", { params = "", - description = "give item to yourself", + description = "Give item to yourself", privs = {give=true}, func = function(name, param) local itemstring = string.match(param, "(.+)$") @@ -530,22 +632,29 @@ core.register_chatcommand("giveme", { }) 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 return false, "EntityName required" end - core.log("action", ("/spawnentity invoked, entityname=%q") - :format(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 false, "Unable to spawn entity, player is nil" end - local p = player:getpos() + 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) @@ -582,9 +691,9 @@ 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)", + 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.setting_getbool("enable_rollback_recording") then @@ -635,7 +744,7 @@ core.register_chatcommand("rollback_check", { core.register_chatcommand("rollback", { params = " [] | : []", - description = "revert actions of a player; default for is 60", + description = "Revert actions of a player. Default for is 60", privs = {rollback=true}, func = function(name, param) if not core.setting_getbool("enable_rollback_recording") then @@ -680,7 +789,7 @@ core.register_chatcommand("status", { core.register_chatcommand("time", { params = "<0..23>:<0..59> | <0..24000>", - description = "set time of day", + description = "Set time of day", privs = {}, func = function(name, param) if param == "" then @@ -689,7 +798,7 @@ core.register_chatcommand("time", { local hour = (current_time - minutes) / 60 return true, ("Current time is %d:%02d"):format(hour, minutes) end - local player_privs = minetest.get_player_privs(name) + 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)." @@ -713,18 +822,28 @@ core.register_chatcommand("time", { return false, "Invalid minute (must be between 0 and 59 inclusive)." end core.set_timeofday((hour * 60 + minute) / 1440) - core.log("action", name .. " sets time to " .. hour .. ":" .. minute) + 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", + description = "Shutdown server", + params = "[reconnect] [message]", 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 reconnect, message = param:match("([^ ]+)(.*)") + message = message or "" + core.request_shutdown(message:trim(), core.is_yes(reconnect)) end, }) @@ -750,7 +869,7 @@ core.register_chatcommand("ban", { core.register_chatcommand("unban", { params = "", - description = "remove IP ban", + description = "Remove IP ban", privs = {ban=true}, func = function(name, param) if not core.unban_player_or_ip(param) then @@ -763,7 +882,7 @@ core.register_chatcommand("unban", { core.register_chatcommand("kick", { params = " [reason]", - description = "kick a player", + description = "Kick a player", privs = {kick=true}, func = function(name, param) local tokick, reason = param:match("([^ ]+) (.+)") @@ -781,14 +900,25 @@ core.register_chatcommand("kick", { }) core.register_chatcommand("clearobjects", { - 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.") + 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() + core.clear_objects(options) core.log("action", "Object clearing done.") core.chat_send_all("*** Cleared all objects.") end,