1 -- Minetest: builtin/game/chatcommands.lua
4 -- Chat command handler
7 core.chatcommands = core.registered_chatcommands -- BACKWARDS COMPATIBILITY
9 core.register_on_chat_message(function(name, message)
10 if message:sub(1,1) ~= "/" then
14 local cmd, param = string.match(message, "^/([^ ]+) *(.*)")
16 core.chat_send_player(name, "-!- Empty command")
22 local cmd_def = core.registered_chatcommands[cmd]
24 core.chat_send_player(name, "-!- Invalid command: " .. cmd)
27 local has_privs, missing_privs = core.check_player_privs(name, cmd_def.privs)
29 core.set_last_run_mod(cmd_def.mod_origin)
30 local success, message = cmd_def.func(name, param)
32 core.chat_send_player(name, message)
35 core.chat_send_player(name, "You don't have permission"
36 .. " to run this command (missing privileges: "
37 .. table.concat(missing_privs, ", ") .. ")")
39 return true -- Handled chat message
42 if core.settings:get_bool("profiler.load") then
43 -- Run after register_chatcommand and its register_on_chat_message
44 -- Before any chattcommands that should be profiled
45 profiler.init_chatcommand()
48 -- Parses a "range" string in the format of "here (number)" or
49 -- "(x1, y1, z1) (x2, y2, z2)", returning two position vectors
50 local function parse_range_str(player_name, str)
52 local args = str:split(" ")
54 if args[1] == "here" then
55 p1, p2 = core.get_player_radius_area(player_name, tonumber(args[2]))
57 return false, "Unable to get player " .. player_name .. " position"
60 p1, p2 = core.string_to_area(str)
62 return false, "Incorrect area format. Expected: (x1,y1,z1) (x2,y2,z2)"
72 core.register_chatcommand("me", {
74 description = "Display chat action (e.g., '/me orders a pizza' displays"
75 .. " '<player name> orders a pizza')",
77 func = function(name, param)
78 core.chat_send_all("* " .. name .. " " .. param)
82 core.register_chatcommand("admin", {
83 description = "Show the name of the server owner",
85 local admin = minetest.settings:get("name")
87 return true, "The administrator of this server is "..admin.."."
89 return false, "There's no administrator named in the config file."
94 core.register_chatcommand("privs", {
96 description = "Print privileges of player",
97 func = function(caller, param)
99 local name = (param ~= "" and param or caller)
100 return true, "Privileges of " .. name .. ": "
101 .. core.privs_to_string(
102 core.get_player_privs(name), ' ')
106 local function handle_grant_command(caller, grantname, grantprivstr)
107 local caller_privs = minetest.get_player_privs(caller)
108 if not (caller_privs.privs or caller_privs.basic_privs) then
109 return false, "Your privileges are insufficient."
112 if not core.get_auth_handler().get_auth(grantname) then
113 return false, "Player " .. grantname .. " does not exist."
115 local grantprivs = core.string_to_privs(grantprivstr)
116 if grantprivstr == "all" then
117 grantprivs = core.registered_privileges
119 local privs = core.get_player_privs(grantname)
120 local privs_unknown = ""
122 core.string_to_privs(core.settings:get("basic_privs") or "interact,shout")
123 for priv, _ in pairs(grantprivs) do
124 if not basic_privs[priv] and not caller_privs.privs then
125 return false, "Your privileges are insufficient."
127 if not core.registered_privileges[priv] then
128 privs_unknown = privs_unknown .. "Unknown privilege: " .. priv .. "\n"
132 if privs_unknown ~= "" then
133 return false, privs_unknown
135 core.set_player_privs(grantname, privs)
136 core.log("action", caller..' granted ('..core.privs_to_string(grantprivs, ', ')..') privileges to '..grantname)
137 if grantname ~= caller then
138 core.chat_send_player(grantname, caller
139 .. " granted you privileges: "
140 .. core.privs_to_string(grantprivs, ' '))
142 return true, "Privileges of " .. grantname .. ": "
143 .. core.privs_to_string(
144 core.get_player_privs(grantname), ' ')
147 core.register_chatcommand("grant", {
148 params = "<name> <privilege>|all",
149 description = "Give privilege to player",
150 func = function(name, param)
151 local grantname, grantprivstr = string.match(param, "([^ ]+) (.+)")
152 if not grantname or not grantprivstr then
153 return false, "Invalid parameters (see /help grant)"
155 return handle_grant_command(name, grantname, grantprivstr)
159 core.register_chatcommand("grantme", {
160 params = "<privilege>|all",
161 description = "Grant privileges to yourself",
162 func = function(name, param)
164 return false, "Invalid parameters (see /help grantme)"
166 return handle_grant_command(name, name, param)
170 core.register_chatcommand("revoke", {
171 params = "<name> <privilege>|all",
172 description = "Remove privilege from player",
174 func = function(name, param)
175 if not core.check_player_privs(name, {privs=true}) and
176 not core.check_player_privs(name, {basic_privs=true}) then
177 return false, "Your privileges are insufficient."
179 local revoke_name, revoke_priv_str = string.match(param, "([^ ]+) (.+)")
180 if not revoke_name or not revoke_priv_str then
181 return false, "Invalid parameters (see /help revoke)"
182 elseif not core.get_auth_handler().get_auth(revoke_name) then
183 return false, "Player " .. revoke_name .. " does not exist."
185 local revoke_privs = core.string_to_privs(revoke_priv_str)
186 local privs = core.get_player_privs(revoke_name)
188 core.string_to_privs(core.settings:get("basic_privs") or "interact,shout")
189 for priv, _ in pairs(revoke_privs) do
190 if not basic_privs[priv] and
191 not core.check_player_privs(name, {privs=true}) then
192 return false, "Your privileges are insufficient."
195 if revoke_priv_str == "all" then
198 for priv, _ in pairs(revoke_privs) do
202 core.set_player_privs(revoke_name, privs)
203 core.log("action", name..' revoked ('
204 ..core.privs_to_string(revoke_privs, ', ')
205 ..') privileges from '..revoke_name)
206 if revoke_name ~= name then
207 core.chat_send_player(revoke_name, name
208 .. " revoked privileges from you: "
209 .. core.privs_to_string(revoke_privs, ' '))
211 return true, "Privileges of " .. revoke_name .. ": "
212 .. core.privs_to_string(
213 core.get_player_privs(revoke_name), ' ')
217 core.register_chatcommand("setpassword", {
218 params = "<name> <password>",
219 description = "Set player's password",
220 privs = {password=true},
221 func = function(name, param)
222 local toname, raw_password = string.match(param, "^([^ ]+) +(.+)$")
224 toname = param:match("^([^ ]+) *$")
228 return false, "Name field required"
230 local act_str_past = "?"
231 local act_str_pres = "?"
232 if not raw_password then
233 core.set_player_password(toname, "")
234 act_str_past = "cleared"
235 act_str_pres = "clears"
237 core.set_player_password(toname,
238 core.get_password_hash(toname,
241 act_str_pres = "sets"
243 if toname ~= name then
244 core.chat_send_player(toname, "Your password was "
245 .. act_str_past .. " by " .. name)
248 core.log("action", name .. " " .. act_str_pres
249 .. " password of " .. toname .. ".")
251 return true, "Password of player \"" .. toname .. "\" " .. act_str_past
255 core.register_chatcommand("clearpassword", {
257 description = "Set empty password",
258 privs = {password=true},
259 func = function(name, param)
262 return false, "Name field required"
264 core.set_player_password(toname, '')
266 core.log("action", name .. " clears password of " .. toname .. ".")
268 return true, "Password of player \"" .. toname .. "\" cleared"
272 core.register_chatcommand("auth_reload", {
274 description = "Reload authentication data",
275 privs = {server=true},
276 func = function(name, param)
277 local done = core.auth_reload()
278 return done, (done and "Done." or "Failed.")
282 core.register_chatcommand("remove_player", {
284 description = "Remove player data",
285 privs = {server=true},
286 func = function(name, param)
289 return false, "Name field required"
292 local rc = core.remove_player(toname)
295 core.log("action", name .. " removed player data of " .. toname .. ".")
296 return true, "Player \"" .. toname .. "\" removed."
298 return true, "No such player \"" .. toname .. "\" to remove."
300 return true, "Player \"" .. toname .. "\" is connected, cannot remove."
303 return false, "Unhandled remove_player return code " .. rc .. ""
307 core.register_chatcommand("teleport", {
308 params = "<X>,<Y>,<Z> | <to_name> | <name> <X>,<Y>,<Z> | <name> <to_name>",
309 description = "Teleport to player or position",
310 privs = {teleport=true},
311 func = function(name, param)
312 -- Returns (pos, true) if found, otherwise (pos, false)
313 local function find_free_position_near(pos)
320 for _, d in ipairs(tries) do
321 local p = {x = pos.x+d.x, y = pos.y+d.y, z = pos.z+d.z}
322 local n = core.get_node_or_nil(p)
324 local def = core.registered_nodes[n.name]
325 if def and not def.walkable then
333 local teleportee = nil
335 p.x, p.y, p.z = string.match(param, "^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
339 if p.x and p.y and p.z then
341 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
342 return false, "Cannot teleport out of map bounds!"
344 teleportee = core.get_player_by_name(name)
347 return true, "Teleporting to "..core.pos_to_string(p)
351 local teleportee = nil
353 local target_name = nil
354 target_name = param:match("^([^ ]+)$")
355 teleportee = core.get_player_by_name(name)
357 local target = core.get_player_by_name(target_name)
362 if teleportee and p then
363 p = find_free_position_near(p)
365 return true, "Teleporting to " .. target_name
366 .. " at "..core.pos_to_string(p)
369 if not core.check_player_privs(name, {bring=true}) then
370 return false, "You don't have permission to teleport other players (missing bring privilege)"
373 local teleportee = nil
375 local teleportee_name = nil
376 teleportee_name, p.x, p.y, p.z = param:match(
377 "^([^ ]+) +([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
378 p.x, p.y, p.z = tonumber(p.x), tonumber(p.y), tonumber(p.z)
379 if teleportee_name then
380 teleportee = core.get_player_by_name(teleportee_name)
382 if teleportee and p.x and p.y and p.z then
384 return true, "Teleporting " .. teleportee_name
385 .. " to " .. core.pos_to_string(p)
388 local teleportee = nil
390 local teleportee_name = nil
391 local target_name = nil
392 teleportee_name, target_name = string.match(param, "^([^ ]+) +([^ ]+)$")
393 if teleportee_name then
394 teleportee = core.get_player_by_name(teleportee_name)
397 local target = core.get_player_by_name(target_name)
402 if teleportee and p then
403 p = find_free_position_near(p)
405 return true, "Teleporting " .. teleportee_name
406 .. " to " .. target_name
407 .. " at " .. core.pos_to_string(p)
410 return false, 'Invalid parameters ("' .. param
411 .. '") or player not found (see /help teleport)'
415 core.register_chatcommand("set", {
416 params = "[-n] <name> <value> | <name>",
417 description = "Set or read server configuration setting",
418 privs = {server=true},
419 func = function(name, param)
420 local arg, setname, setvalue = string.match(param, "(-[n]) ([^ ]+) (.+)")
421 if arg and arg == "-n" and setname and setvalue then
422 core.settings:set(setname, setvalue)
423 return true, setname .. " = " .. setvalue
425 local setname, setvalue = string.match(param, "([^ ]+) (.+)")
426 if setname and setvalue then
427 if not core.settings:get(setname) then
428 return false, "Failed. Use '/set -n <name> <value>' to create a new setting."
430 core.settings:set(setname, setvalue)
431 return true, setname .. " = " .. setvalue
433 local setname = string.match(param, "([^ ]+)")
435 local setvalue = core.settings:get(setname)
437 setvalue = "<not set>"
439 return true, setname .. " = " .. setvalue
441 return false, "Invalid parameters (see /help set)."
445 local function emergeblocks_callback(pos, action, num_calls_remaining, ctx)
446 if ctx.total_blocks == 0 then
447 ctx.total_blocks = num_calls_remaining + 1
448 ctx.current_blocks = 0
450 ctx.current_blocks = ctx.current_blocks + 1
452 if ctx.current_blocks == ctx.total_blocks then
453 core.chat_send_player(ctx.requestor_name,
454 string.format("Finished emerging %d blocks in %.2fms.",
455 ctx.total_blocks, (os.clock() - ctx.start_time) * 1000))
459 local function emergeblocks_progress_update(ctx)
460 if ctx.current_blocks ~= ctx.total_blocks then
461 core.chat_send_player(ctx.requestor_name,
462 string.format("emergeblocks update: %d/%d blocks emerged (%.1f%%)",
463 ctx.current_blocks, ctx.total_blocks,
464 (ctx.current_blocks / ctx.total_blocks) * 100))
466 core.after(2, emergeblocks_progress_update, ctx)
470 core.register_chatcommand("emergeblocks", {
471 params = "(here [radius]) | (<pos1> <pos2>)",
472 description = "Load (or, if nonexistent, generate) map blocks "
473 .. "contained in area pos1 to pos2",
474 privs = {server=true},
475 func = function(name, param)
476 local p1, p2 = parse_range_str(name, param)
484 start_time = os.clock(),
485 requestor_name = name
488 core.emerge_area(p1, p2, emergeblocks_callback, context)
489 core.after(2, emergeblocks_progress_update, context)
491 return true, "Started emerge of area ranging from " ..
492 core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
496 core.register_chatcommand("deleteblocks", {
497 params = "(here [radius]) | (<pos1> <pos2>)",
498 description = "Delete map blocks contained in area pos1 to pos2",
499 privs = {server=true},
500 func = function(name, param)
501 local p1, p2 = parse_range_str(name, param)
506 if core.delete_area(p1, p2) then
507 return true, "Successfully cleared area ranging from " ..
508 core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
510 return false, "Failed to clear one or more blocks in area"
515 core.register_chatcommand("fixlight", {
516 params = "(here [radius]) | (<pos1> <pos2>)",
517 description = "Resets lighting in the area between pos1 and pos2",
518 privs = {server = true},
519 func = function(name, param)
520 local p1, p2 = parse_range_str(name, param)
525 if core.fix_light(p1, p2) then
526 return true, "Successfully reset light in the area ranging from " ..
527 core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
529 return false, "Failed to load one or more blocks in area"
534 core.register_chatcommand("mods", {
536 description = "List mods installed on the server",
538 func = function(name, param)
539 return true, table.concat(core.get_modnames(), ", ")
543 local function handle_give_command(cmd, giver, receiver, stackstring)
544 core.log("action", giver .. " invoked " .. cmd
545 .. ', stackstring="' .. stackstring .. '"')
546 local itemstack = ItemStack(stackstring)
547 if itemstack:is_empty() then
548 return false, "Cannot give an empty item"
549 elseif not itemstack:is_known() then
550 return false, "Cannot give an unknown item"
552 local receiverref = core.get_player_by_name(receiver)
553 if receiverref == nil then
554 return false, receiver .. " is not a known player"
556 local leftover = receiverref:get_inventory():add_item("main", itemstack)
558 if leftover:is_empty() then
560 elseif leftover:get_count() == itemstack:get_count() then
561 partiality = "could not be "
563 partiality = "partially "
565 -- The actual item stack string may be different from what the "giver"
566 -- entered (e.g. big numbers are always interpreted as 2^16-1).
567 stackstring = itemstack:to_string()
568 if giver == receiver then
569 return true, ("%q %sadded to inventory.")
570 :format(stackstring, partiality)
572 core.chat_send_player(receiver, ("%q %sadded to inventory.")
573 :format(stackstring, partiality))
574 return true, ("%q %sadded to %s's inventory.")
575 :format(stackstring, partiality, receiver)
579 core.register_chatcommand("give", {
580 params = "<name> <ItemString>",
581 description = "Give item to player",
583 func = function(name, param)
584 local toname, itemstring = string.match(param, "^([^ ]+) +(.+)$")
585 if not toname or not itemstring then
586 return false, "Name and ItemString required"
588 return handle_give_command("/give", name, toname, itemstring)
592 core.register_chatcommand("giveme", {
593 params = "<ItemString>",
594 description = "Give item to yourself",
596 func = function(name, param)
597 local itemstring = string.match(param, "(.+)$")
598 if not itemstring then
599 return false, "ItemString required"
601 return handle_give_command("/giveme", name, name, itemstring)
605 core.register_chatcommand("spawnentity", {
606 params = "<EntityName> [<X>,<Y>,<Z>]",
607 description = "Spawn entity at given (or your) position",
608 privs = {give=true, interact=true},
609 func = function(name, param)
610 local entityname, p = string.match(param, "^([^ ]+) *(.*)$")
611 if not entityname then
612 return false, "EntityName required"
614 core.log("action", ("%s invokes /spawnentity, entityname=%q")
615 :format(name, entityname))
616 local player = core.get_player_by_name(name)
617 if player == nil then
618 core.log("error", "Unable to spawn entity, player is nil")
619 return false, "Unable to spawn entity, player is nil"
624 p = core.string_to_pos(p)
626 return false, "Invalid parameters ('" .. param .. "')"
630 core.add_entity(p, entityname)
631 return true, ("%q spawned."):format(entityname)
635 core.register_chatcommand("pulverize", {
637 description = "Destroy item in hand",
638 func = function(name, param)
639 local player = core.get_player_by_name(name)
641 core.log("error", "Unable to pulverize, no player.")
642 return false, "Unable to pulverize, no player."
644 if player:get_wielded_item():is_empty() then
645 return false, "Unable to pulverize, no item in hand."
647 player:set_wielded_item(nil)
648 return true, "An item was pulverized."
653 core.rollback_punch_callbacks = {}
655 core.register_on_punchnode(function(pos, node, puncher)
656 local name = puncher:get_player_name()
657 if core.rollback_punch_callbacks[name] then
658 core.rollback_punch_callbacks[name](pos, node, puncher)
659 core.rollback_punch_callbacks[name] = nil
663 core.register_chatcommand("rollback_check", {
664 params = "[<range>] [<seconds>] [limit]",
665 description = "Check who last touched a node or a node near it"
666 .. " within the time specified by <seconds>. Default: range = 0,"
667 .. " seconds = 86400 = 24h, limit = 5",
668 privs = {rollback=true},
669 func = function(name, param)
670 if not core.settings:get_bool("enable_rollback_recording") then
671 return false, "Rollback functions are disabled."
673 local range, seconds, limit =
674 param:match("(%d+) *(%d*) *(%d*)")
675 range = tonumber(range) or 0
676 seconds = tonumber(seconds) or 86400
677 limit = tonumber(limit) or 5
679 return false, "That limit is too high!"
682 core.rollback_punch_callbacks[name] = function(pos, node, puncher)
683 local name = puncher:get_player_name()
684 core.chat_send_player(name, "Checking " .. core.pos_to_string(pos) .. "...")
685 local actions = core.rollback_get_node_actions(pos, range, seconds, limit)
687 core.chat_send_player(name, "Rollback functions are disabled")
690 local num_actions = #actions
691 if num_actions == 0 then
692 core.chat_send_player(name, "Nobody has touched"
693 .. " the specified location in "
694 .. seconds .. " seconds")
697 local time = os.time()
698 for i = num_actions, 1, -1 do
699 local action = actions[i]
700 core.chat_send_player(name,
701 ("%s %s %s -> %s %d seconds ago.")
703 core.pos_to_string(action.pos),
711 return true, "Punch a node (range=" .. range .. ", seconds="
712 .. seconds .. "s, limit=" .. limit .. ")"
716 core.register_chatcommand("rollback", {
717 params = "<player name> [<seconds>] | :<actor> [<seconds>]",
718 description = "Revert actions of a player. Default for <seconds> is 60",
719 privs = {rollback=true},
720 func = function(name, param)
721 if not core.settings:get_bool("enable_rollback_recording") then
722 return false, "Rollback functions are disabled."
724 local target_name, seconds = string.match(param, ":([^ ]+) *(%d*)")
725 if not target_name then
726 local player_name = nil
727 player_name, seconds = string.match(param, "([^ ]+) *(%d*)")
728 if not player_name then
729 return false, "Invalid parameters. See /help rollback"
730 .. " and /help rollback_check."
732 target_name = "player:"..player_name
734 seconds = tonumber(seconds) or 60
735 core.chat_send_player(name, "Reverting actions of "
736 .. target_name .. " since "
737 .. seconds .. " seconds.")
738 local success, log = core.rollback_revert_actions_by(
739 target_name, seconds)
742 response = "(log is too long to show)\n"
744 for _, line in pairs(log) do
745 response = response .. line .. "\n"
748 response = response .. "Reverting actions "
749 .. (success and "succeeded." or "FAILED.")
750 return success, response
754 core.register_chatcommand("status", {
755 description = "Print server status",
756 func = function(name, param)
757 return true, core.get_server_status()
761 core.register_chatcommand("time", {
762 params = "<0..23>:<0..59> | <0..24000>",
763 description = "Set time of day",
765 func = function(name, param)
767 local current_time = math.floor(core.get_timeofday() * 1440)
768 local minutes = current_time % 60
769 local hour = (current_time - minutes) / 60
770 return true, ("Current time is %d:%02d"):format(hour, minutes)
772 local player_privs = core.get_player_privs(name)
773 if not player_privs.settime then
774 return false, "You don't have permission to run this command " ..
775 "(missing privilege: settime)."
777 local hour, minute = param:match("^(%d+):(%d+)$")
779 local new_time = tonumber(param)
781 return false, "Invalid time."
783 -- Backward compatibility.
784 core.set_timeofday((new_time % 24000) / 24000)
785 core.log("action", name .. " sets time to " .. new_time)
786 return true, "Time of day changed."
788 hour = tonumber(hour)
789 minute = tonumber(minute)
790 if hour < 0 or hour > 23 then
791 return false, "Invalid hour (must be between 0 and 23 inclusive)."
792 elseif minute < 0 or minute > 59 then
793 return false, "Invalid minute (must be between 0 and 59 inclusive)."
795 core.set_timeofday((hour * 60 + minute) / 1440)
796 core.log("action", ("%s sets time to %d:%02d"):format(name, hour, minute))
797 return true, "Time of day changed."
801 core.register_chatcommand("days", {
802 description = "Display day count",
803 func = function(name, param)
804 return true, "Current day is " .. core.get_day_count()
808 core.register_chatcommand("shutdown", {
809 description = "Shutdown server",
810 params = "[delay_in_seconds(0..inf) or -1 for cancel] [reconnect] [message]",
811 privs = {server=true},
812 func = function(name, param)
813 local delay, reconnect, message = param:match("([^ ][-]?[0-9]+)([^ ]+)(.*)")
814 message = message or ""
817 delay = tonumber(param) or 0
820 core.log("action", name .. " shuts down server")
821 core.chat_send_all("*** Server shutting down (operator request).")
823 core.request_shutdown(message:trim(), core.is_yes(reconnect), delay)
827 core.register_chatcommand("ban", {
829 description = "Ban IP of player",
831 func = function(name, param)
833 return true, "Ban list: " .. core.get_ban_list()
835 if not core.get_player_by_name(param) then
836 return false, "No such player."
838 if not core.ban_player(param) then
839 return false, "Failed to ban player."
841 local desc = core.get_ban_description(param)
842 core.log("action", name .. " bans " .. desc .. ".")
843 return true, "Banned " .. desc .. "."
847 core.register_chatcommand("unban", {
848 params = "<name/ip>",
849 description = "Remove IP ban",
851 func = function(name, param)
852 if not core.unban_player_or_ip(param) then
853 return false, "Failed to unban player/IP."
855 core.log("action", name .. " unbans " .. param)
856 return true, "Unbanned " .. param
860 core.register_chatcommand("kick", {
861 params = "<name> [reason]",
862 description = "Kick a player",
864 func = function(name, param)
865 local tokick, reason = param:match("([^ ]+) (.+)")
866 tokick = tokick or param
867 if not core.kick_player(tokick, reason) then
868 return false, "Failed to kick player " .. tokick
870 local log_reason = ""
872 log_reason = " with reason \"" .. reason .. "\""
874 core.log("action", name .. " kicks " .. tokick .. log_reason)
875 return true, "Kicked " .. tokick
879 core.register_chatcommand("clearobjects", {
880 params = "[full|quick]",
881 description = "Clear all objects in world",
882 privs = {server=true},
883 func = function(name, param)
885 if param == "" or param == "full" then
886 options.mode = "full"
887 elseif param == "quick" then
888 options.mode = "quick"
890 return false, "Invalid usage, see /help clearobjects."
893 core.log("action", name .. " clears all objects ("
894 .. options.mode .. " mode).")
895 core.chat_send_all("Clearing all objects. This may take long."
896 .. " You may experience a timeout. (by "
898 core.clear_objects(options)
899 core.log("action", "Object clearing done.")
900 core.chat_send_all("*** Cleared all objects.")
904 core.register_chatcommand("msg", {
905 params = "<name> <message>",
906 description = "Send a private message",
907 privs = {shout=true},
908 func = function(name, param)
909 local sendto, message = param:match("^(%S+)%s(.+)$")
911 return false, "Invalid usage, see /help msg."
913 if not core.get_player_by_name(sendto) then
914 return false, "The player " .. sendto
917 core.log("action", "PM from " .. name .. " to " .. sendto
919 core.chat_send_player(sendto, "PM from " .. name .. ": "
921 return true, "Message sent."
925 core.register_chatcommand("last-login", {
927 description = "Get the last login time of a player",
928 func = function(name, param)
932 local pauth = core.get_auth_handler().get_auth(param)
933 if pauth and pauth.last_login then
934 -- Time in UTC, ISO 8601 format
935 return true, "Last login time was " ..
936 os.date("!%Y-%m-%dT%H:%M:%SZ", pauth.last_login)
938 return false, "Last login time is unknown"
942 core.register_chatcommand("clearinv", {
944 description = "Clear the inventory of yourself or another player",
945 func = function(name, param)
947 if param and param ~= "" and param ~= name then
948 if not core.check_player_privs(name, {server=true}) then
949 return false, "You don't have permission"
950 .. " to run this command (missing privilege: server)"
952 player = core.get_player_by_name(param)
953 core.chat_send_player(param, name.." cleared your inventory.")
955 player = core.get_player_by_name(name)
959 player:get_inventory():set_list("main", {})
960 player:get_inventory():set_list("craft", {})
961 player:get_inventory():set_list("craftpreview", {})
962 core.log("action", name.." clears "..player:get_player_name().."'s inventory")
963 return true, "Cleared "..player:get_player_name().."'s inventory."
965 return false, "Player must be online to clear inventory!"