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 (<pos1> and <pos2> must be in parentheses)",
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 .. "(<pos1> and <pos2> must be in parentheses)",
500 privs = {server=true},
501 func = function(name, param)
502 local p1, p2 = parse_range_str(name, param)
507 if core.delete_area(p1, p2) then
508 return true, "Successfully cleared area ranging from " ..
509 core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
511 return false, "Failed to clear one or more blocks in area"
516 core.register_chatcommand("fixlight", {
517 params = "(here [<radius>]) | (<pos1> <pos2>)",
518 description = "Resets lighting in the area between pos1 and pos2 "
519 .. "(<pos1> and <pos2> must be in parentheses)",
520 privs = {server = true},
521 func = function(name, param)
522 local p1, p2 = parse_range_str(name, param)
527 if core.fix_light(p1, p2) then
528 return true, "Successfully reset light in the area ranging from " ..
529 core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
531 return false, "Failed to load one or more blocks in area"
536 core.register_chatcommand("mods", {
538 description = "List mods installed on the server",
540 func = function(name, param)
541 return true, table.concat(core.get_modnames(), ", ")
545 local function handle_give_command(cmd, giver, receiver, stackstring)
546 core.log("action", giver .. " invoked " .. cmd
547 .. ', stackstring="' .. stackstring .. '"')
548 local itemstack = ItemStack(stackstring)
549 if itemstack:is_empty() then
550 return false, "Cannot give an empty item"
551 elseif not itemstack:is_known() then
552 return false, "Cannot give an unknown item"
554 local receiverref = core.get_player_by_name(receiver)
555 if receiverref == nil then
556 return false, receiver .. " is not a known player"
558 local leftover = receiverref:get_inventory():add_item("main", itemstack)
560 if leftover:is_empty() then
562 elseif leftover:get_count() == itemstack:get_count() then
563 partiality = "could not be "
565 partiality = "partially "
567 -- The actual item stack string may be different from what the "giver"
568 -- entered (e.g. big numbers are always interpreted as 2^16-1).
569 stackstring = itemstack:to_string()
570 if giver == receiver then
571 return true, ("%q %sadded to inventory.")
572 :format(stackstring, partiality)
574 core.chat_send_player(receiver, ("%q %sadded to inventory.")
575 :format(stackstring, partiality))
576 return true, ("%q %sadded to %s's inventory.")
577 :format(stackstring, partiality, receiver)
581 core.register_chatcommand("give", {
582 params = "<name> <ItemString>",
583 description = "Give item to player",
585 func = function(name, param)
586 local toname, itemstring = string.match(param, "^([^ ]+) +(.+)$")
587 if not toname or not itemstring then
588 return false, "Name and ItemString required"
590 return handle_give_command("/give", name, toname, itemstring)
594 core.register_chatcommand("giveme", {
595 params = "<ItemString>",
596 description = "Give item to yourself",
598 func = function(name, param)
599 local itemstring = string.match(param, "(.+)$")
600 if not itemstring then
601 return false, "ItemString required"
603 return handle_give_command("/giveme", name, name, itemstring)
607 core.register_chatcommand("spawnentity", {
608 params = "<EntityName> [<X>,<Y>,<Z>]",
609 description = "Spawn entity at given (or your) position",
610 privs = {give=true, interact=true},
611 func = function(name, param)
612 local entityname, p = string.match(param, "^([^ ]+) *(.*)$")
613 if not entityname then
614 return false, "EntityName required"
616 core.log("action", ("%s invokes /spawnentity, entityname=%q")
617 :format(name, entityname))
618 local player = core.get_player_by_name(name)
619 if player == nil then
620 core.log("error", "Unable to spawn entity, player is nil")
621 return false, "Unable to spawn entity, player is nil"
626 p = core.string_to_pos(p)
628 return false, "Invalid parameters ('" .. param .. "')"
632 core.add_entity(p, entityname)
633 return true, ("%q spawned."):format(entityname)
637 core.register_chatcommand("pulverize", {
639 description = "Destroy item in hand",
640 func = function(name, param)
641 local player = core.get_player_by_name(name)
643 core.log("error", "Unable to pulverize, no player.")
644 return false, "Unable to pulverize, no player."
646 if player:get_wielded_item():is_empty() then
647 return false, "Unable to pulverize, no item in hand."
649 player:set_wielded_item(nil)
650 return true, "An item was pulverized."
655 core.rollback_punch_callbacks = {}
657 core.register_on_punchnode(function(pos, node, puncher)
658 local name = puncher:get_player_name()
659 if core.rollback_punch_callbacks[name] then
660 core.rollback_punch_callbacks[name](pos, node, puncher)
661 core.rollback_punch_callbacks[name] = nil
665 core.register_chatcommand("rollback_check", {
666 params = "[<range>] [<seconds>] [<limit>]",
667 description = "Check who last touched a node or a node near it"
668 .. " within the time specified by <seconds>. Default: range = 0,"
669 .. " seconds = 86400 = 24h, limit = 5",
670 privs = {rollback=true},
671 func = function(name, param)
672 if not core.settings:get_bool("enable_rollback_recording") then
673 return false, "Rollback functions are disabled."
675 local range, seconds, limit =
676 param:match("(%d+) *(%d*) *(%d*)")
677 range = tonumber(range) or 0
678 seconds = tonumber(seconds) or 86400
679 limit = tonumber(limit) or 5
681 return false, "That limit is too high!"
684 core.rollback_punch_callbacks[name] = function(pos, node, puncher)
685 local name = puncher:get_player_name()
686 core.chat_send_player(name, "Checking " .. core.pos_to_string(pos) .. "...")
687 local actions = core.rollback_get_node_actions(pos, range, seconds, limit)
689 core.chat_send_player(name, "Rollback functions are disabled")
692 local num_actions = #actions
693 if num_actions == 0 then
694 core.chat_send_player(name, "Nobody has touched"
695 .. " the specified location in "
696 .. seconds .. " seconds")
699 local time = os.time()
700 for i = num_actions, 1, -1 do
701 local action = actions[i]
702 core.chat_send_player(name,
703 ("%s %s %s -> %s %d seconds ago.")
705 core.pos_to_string(action.pos),
713 return true, "Punch a node (range=" .. range .. ", seconds="
714 .. seconds .. "s, limit=" .. limit .. ")"
718 core.register_chatcommand("rollback", {
719 params = "(<name> [<seconds>]) | (:<actor> [<seconds>])",
720 description = "Revert actions of a player. Default for <seconds> is 60",
721 privs = {rollback=true},
722 func = function(name, param)
723 if not core.settings:get_bool("enable_rollback_recording") then
724 return false, "Rollback functions are disabled."
726 local target_name, seconds = string.match(param, ":([^ ]+) *(%d*)")
727 if not target_name then
728 local player_name = nil
729 player_name, seconds = string.match(param, "([^ ]+) *(%d*)")
730 if not player_name then
731 return false, "Invalid parameters. See /help rollback"
732 .. " and /help rollback_check."
734 target_name = "player:"..player_name
736 seconds = tonumber(seconds) or 60
737 core.chat_send_player(name, "Reverting actions of "
738 .. target_name .. " since "
739 .. seconds .. " seconds.")
740 local success, log = core.rollback_revert_actions_by(
741 target_name, seconds)
744 response = "(log is too long to show)\n"
746 for _, line in pairs(log) do
747 response = response .. line .. "\n"
750 response = response .. "Reverting actions "
751 .. (success and "succeeded." or "FAILED.")
752 return success, response
756 core.register_chatcommand("status", {
757 description = "Print server status",
758 func = function(name, param)
759 return true, core.get_server_status()
763 core.register_chatcommand("time", {
764 params = "<0..23>:<0..59> | <0..24000>",
765 description = "Set time of day",
767 func = function(name, param)
769 local current_time = math.floor(core.get_timeofday() * 1440)
770 local minutes = current_time % 60
771 local hour = (current_time - minutes) / 60
772 return true, ("Current time is %d:%02d"):format(hour, minutes)
774 local player_privs = core.get_player_privs(name)
775 if not player_privs.settime then
776 return false, "You don't have permission to run this command " ..
777 "(missing privilege: settime)."
779 local hour, minute = param:match("^(%d+):(%d+)$")
781 local new_time = tonumber(param)
783 return false, "Invalid time."
785 -- Backward compatibility.
786 core.set_timeofday((new_time % 24000) / 24000)
787 core.log("action", name .. " sets time to " .. new_time)
788 return true, "Time of day changed."
790 hour = tonumber(hour)
791 minute = tonumber(minute)
792 if hour < 0 or hour > 23 then
793 return false, "Invalid hour (must be between 0 and 23 inclusive)."
794 elseif minute < 0 or minute > 59 then
795 return false, "Invalid minute (must be between 0 and 59 inclusive)."
797 core.set_timeofday((hour * 60 + minute) / 1440)
798 core.log("action", ("%s sets time to %d:%02d"):format(name, hour, minute))
799 return true, "Time of day changed."
803 core.register_chatcommand("days", {
804 description = "Display day count",
805 func = function(name, param)
806 return true, "Current day is " .. core.get_day_count()
810 core.register_chatcommand("shutdown", {
811 params = "[<delay_in_seconds> | -1] [reconnect] [<message>]",
812 description = "Shutdown server (-1 cancels a delayed shutdown)",
813 privs = {server=true},
814 func = function(name, param)
815 local delay, reconnect, message = param:match("([^ ][-]?[0-9]+)([^ ]+)(.*)")
816 message = message or ""
819 delay = tonumber(param) or 0
822 core.log("action", name .. " shuts down server")
823 core.chat_send_all("*** Server shutting down (operator request).")
825 core.request_shutdown(message:trim(), core.is_yes(reconnect), delay)
829 core.register_chatcommand("ban", {
831 description = "Ban IP of player",
833 func = function(name, param)
835 return true, "Ban list: " .. core.get_ban_list()
837 if not core.get_player_by_name(param) then
838 return false, "No such player."
840 if not core.ban_player(param) then
841 return false, "Failed to ban player."
843 local desc = core.get_ban_description(param)
844 core.log("action", name .. " bans " .. desc .. ".")
845 return true, "Banned " .. desc .. "."
849 core.register_chatcommand("unban", {
850 params = "<name> | <IP_address>",
851 description = "Remove IP ban",
853 func = function(name, param)
854 if not core.unban_player_or_ip(param) then
855 return false, "Failed to unban player/IP."
857 core.log("action", name .. " unbans " .. param)
858 return true, "Unbanned " .. param
862 core.register_chatcommand("kick", {
863 params = "<name> [<reason>]",
864 description = "Kick a player",
866 func = function(name, param)
867 local tokick, reason = param:match("([^ ]+) (.+)")
868 tokick = tokick or param
869 if not core.kick_player(tokick, reason) then
870 return false, "Failed to kick player " .. tokick
872 local log_reason = ""
874 log_reason = " with reason \"" .. reason .. "\""
876 core.log("action", name .. " kicks " .. tokick .. log_reason)
877 return true, "Kicked " .. tokick
881 core.register_chatcommand("clearobjects", {
882 params = "[full | quick]",
883 description = "Clear all objects in world",
884 privs = {server=true},
885 func = function(name, param)
887 if param == "" or param == "full" then
888 options.mode = "full"
889 elseif param == "quick" then
890 options.mode = "quick"
892 return false, "Invalid usage, see /help clearobjects."
895 core.log("action", name .. " clears all objects ("
896 .. options.mode .. " mode).")
897 core.chat_send_all("Clearing all objects. This may take long."
898 .. " You may experience a timeout. (by "
900 core.clear_objects(options)
901 core.log("action", "Object clearing done.")
902 core.chat_send_all("*** Cleared all objects.")
906 core.register_chatcommand("msg", {
907 params = "<name> <message>",
908 description = "Send a private message",
909 privs = {shout=true},
910 func = function(name, param)
911 local sendto, message = param:match("^(%S+)%s(.+)$")
913 return false, "Invalid usage, see /help msg."
915 if not core.get_player_by_name(sendto) then
916 return false, "The player " .. sendto
919 core.log("action", "PM from " .. name .. " to " .. sendto
921 core.chat_send_player(sendto, "PM from " .. name .. ": "
923 return true, "Message sent."
927 core.register_chatcommand("last-login", {
929 description = "Get the last login time of a player",
930 func = function(name, param)
934 local pauth = core.get_auth_handler().get_auth(param)
935 if pauth and pauth.last_login then
936 -- Time in UTC, ISO 8601 format
937 return true, "Last login time was " ..
938 os.date("!%Y-%m-%dT%H:%M:%SZ", pauth.last_login)
940 return false, "Last login time is unknown"
944 core.register_chatcommand("clearinv", {
946 description = "Clear the inventory of yourself or another player",
947 func = function(name, param)
949 if param and param ~= "" and param ~= name then
950 if not core.check_player_privs(name, {server=true}) then
951 return false, "You don't have permission"
952 .. " to run this command (missing privilege: server)"
954 player = core.get_player_by_name(param)
955 core.chat_send_player(param, name.." cleared your inventory.")
957 player = core.get_player_by_name(name)
961 player:get_inventory():set_list("main", {})
962 player:get_inventory():set_list("craft", {})
963 player:get_inventory():set_list("craftpreview", {})
964 core.log("action", name.." clears "..player:get_player_name().."'s inventory")
965 return true, "Cleared "..player:get_player_name().."'s inventory."
967 return false, "Player must be online to clear inventory!"