X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=builtin%2Fgame%2Fchatcommands.lua;h=24792bc8a3e0a920a235e03272e41cc075745fe9;hb=0b0075e6ad0b3110cabdfc92cedb0a24d2b5ec42;hp=39425f0eb24f595865b34a91d79e79f9033dcdd7;hpb=e7706593c6cbbab97667387e7c6cdb493ec2a98d;p=dragonfireclient.git diff --git a/builtin/game/chatcommands.lua b/builtin/game/chatcommands.lua index 39425f0eb..24792bc8a 100644 --- a/builtin/game/chatcommands.lua +++ b/builtin/game/chatcommands.lua @@ -10,6 +10,7 @@ function core.register_chatcommand(cmd, def) def.params = def.params or "" def.description = def.description or "" def.privs = def.privs or {} + def.mod_origin = core.get_current_modname() or "??" core.chatcommands[cmd] = def end @@ -24,6 +25,7 @@ core.register_on_chat_message(function(name, message) 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) @@ -36,6 +38,33 @@ 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 -- @@ -48,13 +77,25 @@ core.register_chatcommand("me", { 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 @@ -68,7 +109,7 @@ core.register_chatcommand("help", { local cmds = {} for cmd, def in pairs(core.chatcommands) do if core.check_player_privs(name, def.privs) then - table.insert(cmds, cmd) + cmds[#cmds + 1] = cmd end end table.sort(cmds) @@ -79,7 +120,7 @@ core.register_chatcommand("help", { local cmds = {} for cmd, def in pairs(core.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) @@ -87,7 +128,7 @@ 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") @@ -133,8 +174,10 @@ core.register_chatcommand("grant", { 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 priv ~= "interact" and priv ~= "shout" and + if not basic_privs[priv] and not core.check_player_privs(name, {privs=true}) then return false, "Your privileges are insufficient." end @@ -175,8 +218,10 @@ core.register_chatcommand("revoke", { 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 @@ -216,21 +261,28 @@ core.register_chatcommand("setpassword", { if not toname then 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" + act_str_past = "set" + act_str_pres = "sets" end if toname ~= name then core.chat_send_player(toname, "Your password was " - .. actstr .. " by " .. name) + .. act_str_past .. " by " .. name) end - return true, "Password of player \"" .. toname .. "\" " .. actstr + + core.log("action", name .. " " .. act_str_pres + .. " password of " .. toname .. ".") + + return true, "Password of player \"" .. toname .. "\" " .. act_str_past end, }) @@ -239,11 +291,14 @@ core.register_chatcommand("clearpassword", { description = "set empty password", privs = {password=true}, func = function(name, param) - toname = 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, }) @@ -290,12 +345,18 @@ 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 local p = nil local target_name = nil @@ -313,45 +374,47 @@ core.register_chatcommand("teleport", { return true, "Teleporting to " .. target_name .. " at "..core.pos_to_string(p) 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 = 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) + + 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)' @@ -388,6 +451,76 @@ core.register_chatcommand("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]) | ( )", + description = "starts loading (or generating, if inexistent) 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]) | ( )", + 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("mods", { params = "", description = "List mods installed on the server", @@ -411,6 +544,7 @@ local function handle_give_command(cmd, giver, receiver, stackstring) 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 @@ -459,22 +593,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) @@ -516,6 +657,9 @@ core.register_chatcommand("rollback_check", { .. " seconds=86400=24h, limit=5)", privs = {rollback=true}, func = function(name, param) + if not core.setting_getbool("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 @@ -529,6 +673,10 @@ core.register_chatcommand("rollback_check", { 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" @@ -560,6 +708,9 @@ core.register_chatcommand("rollback", { 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 + return false, "Rollback functions are disabled." + end local target_name, seconds = string.match(param, ":([^ ]+) *(%d*)") if not target_name then local player_name = nil @@ -598,23 +749,52 @@ core.register_chatcommand("status", { }) core.register_chatcommand("time", { - params = "<0...24000>", + params = "<0..23>:<0..59> | <0..24000>", description = "set time of day", - privs = {settime=true}, + privs = {}, func = function(name, param) if param == "" then - return false, "Missing time." - end - local newtime = tonumber(param) - if newtime == nil then - return false, "Invalid time." - end - core.set_timeofday((newtime % 24000) / 24000) - core.log("action", name .. " sets time " .. newtime) + 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", privs = {server=true}, @@ -668,20 +848,35 @@ core.register_chatcommand("kick", { if not core.kick_player(tokick, reason) then return false, "Failed to kick player " .. tokick end - core.log("action", name .. " kicked " .. tokick) + 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) - 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, @@ -708,3 +903,19 @@ core.register_chatcommand("msg", { 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, +})