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 chatcommands 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 = "Show 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 = core.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 = "Show privileges of yourself or another player",
97 func = function(caller, param)
99 local name = (param ~= "" and param or caller)
100 if not core.player_exists(name) then
101 return false, "Player " .. name .. " does not exist."
103 return true, "Privileges of " .. name .. ": "
104 .. core.privs_to_string(
105 core.get_player_privs(name), ' ')
109 core.register_chatcommand("haspriv", {
110 params = "<privilege>",
111 description = "Return list of all online players with privilege.",
112 privs = {basic_privs = true},
113 func = function(caller, param)
116 return false, "Invalid parameters (see /help haspriv)"
118 if not core.registered_privileges[param] then
119 return false, "Unknown privilege!"
121 local privs = core.string_to_privs(param)
122 local players_with_priv = {}
123 for _, player in pairs(core.get_connected_players()) do
124 local player_name = player:get_player_name()
125 if core.check_player_privs(player_name, privs) then
126 table.insert(players_with_priv, player_name)
129 return true, "Players online with the \"" .. param .. "\" privilege: " ..
130 table.concat(players_with_priv, ", ")
134 local function handle_grant_command(caller, grantname, grantprivstr)
135 local caller_privs = core.get_player_privs(caller)
136 if not (caller_privs.privs or caller_privs.basic_privs) then
137 return false, "Your privileges are insufficient."
140 if not core.get_auth_handler().get_auth(grantname) then
141 return false, "Player " .. grantname .. " does not exist."
143 local grantprivs = core.string_to_privs(grantprivstr)
144 if grantprivstr == "all" then
145 grantprivs = core.registered_privileges
147 local privs = core.get_player_privs(grantname)
148 local privs_unknown = ""
150 core.string_to_privs(core.settings:get("basic_privs") or "interact,shout")
151 for priv, _ in pairs(grantprivs) do
152 if not basic_privs[priv] and not caller_privs.privs then
153 return false, "Your privileges are insufficient."
155 if not core.registered_privileges[priv] then
156 privs_unknown = privs_unknown .. "Unknown privilege: " .. priv .. "\n"
160 if privs_unknown ~= "" then
161 return false, privs_unknown
163 for priv, _ in pairs(grantprivs) do
164 core.run_priv_callbacks(grantname, priv, caller, "grant")
166 core.set_player_privs(grantname, privs)
167 core.log("action", caller..' granted ('..core.privs_to_string(grantprivs, ', ')..') privileges to '..grantname)
168 if grantname ~= caller then
169 core.chat_send_player(grantname, caller
170 .. " granted you privileges: "
171 .. core.privs_to_string(grantprivs, ' '))
173 return true, "Privileges of " .. grantname .. ": "
174 .. core.privs_to_string(
175 core.get_player_privs(grantname), ' ')
178 core.register_chatcommand("grant", {
179 params = "<name> (<privilege> | all)",
180 description = "Give privileges to player",
181 func = function(name, param)
182 local grantname, grantprivstr = string.match(param, "([^ ]+) (.+)")
183 if not grantname or not grantprivstr then
184 return false, "Invalid parameters (see /help grant)"
186 return handle_grant_command(name, grantname, grantprivstr)
190 core.register_chatcommand("grantme", {
191 params = "<privilege> | all",
192 description = "Grant privileges to yourself",
193 func = function(name, param)
195 return false, "Invalid parameters (see /help grantme)"
197 return handle_grant_command(name, name, param)
201 core.register_chatcommand("revoke", {
202 params = "<name> (<privilege> | all)",
203 description = "Remove privileges from player",
205 func = function(name, param)
206 if not core.check_player_privs(name, {privs=true}) and
207 not core.check_player_privs(name, {basic_privs=true}) then
208 return false, "Your privileges are insufficient."
210 local revoke_name, revoke_priv_str = string.match(param, "([^ ]+) (.+)")
211 if not revoke_name or not revoke_priv_str then
212 return false, "Invalid parameters (see /help revoke)"
213 elseif not core.get_auth_handler().get_auth(revoke_name) then
214 return false, "Player " .. revoke_name .. " does not exist."
216 local revoke_privs = core.string_to_privs(revoke_priv_str)
217 local privs = core.get_player_privs(revoke_name)
219 core.string_to_privs(core.settings:get("basic_privs") or "interact,shout")
220 for priv, _ in pairs(revoke_privs) do
221 if not basic_privs[priv] and
222 not core.check_player_privs(name, {privs=true}) then
223 return false, "Your privileges are insufficient."
226 if revoke_priv_str == "all" then
230 for priv, _ in pairs(revoke_privs) do
235 for priv, _ in pairs(revoke_privs) do
236 core.run_priv_callbacks(revoke_name, priv, name, "revoke")
239 core.set_player_privs(revoke_name, privs)
240 core.log("action", name..' revoked ('
241 ..core.privs_to_string(revoke_privs, ', ')
242 ..') privileges from '..revoke_name)
243 if revoke_name ~= name then
244 core.chat_send_player(revoke_name, name
245 .. " revoked privileges from you: "
246 .. core.privs_to_string(revoke_privs, ' '))
248 return true, "Privileges of " .. revoke_name .. ": "
249 .. core.privs_to_string(
250 core.get_player_privs(revoke_name), ' ')
254 core.register_chatcommand("setpassword", {
255 params = "<name> <password>",
256 description = "Set player's password",
257 privs = {password=true},
258 func = function(name, param)
259 local toname, raw_password = string.match(param, "^([^ ]+) +(.+)$")
261 toname = param:match("^([^ ]+) *$")
265 return false, "Name field required"
267 local act_str_past = "?"
268 local act_str_pres = "?"
269 if not raw_password then
270 core.set_player_password(toname, "")
271 act_str_past = "cleared"
272 act_str_pres = "clears"
274 core.set_player_password(toname,
275 core.get_password_hash(toname,
278 act_str_pres = "sets"
280 if toname ~= name then
281 core.chat_send_player(toname, "Your password was "
282 .. act_str_past .. " by " .. name)
285 core.log("action", name .. " " .. act_str_pres
286 .. " password of " .. toname .. ".")
288 return true, "Password of player \"" .. toname .. "\" " .. act_str_past
292 core.register_chatcommand("clearpassword", {
294 description = "Set empty password for a player",
295 privs = {password=true},
296 func = function(name, param)
299 return false, "Name field required"
301 core.set_player_password(toname, '')
303 core.log("action", name .. " clears password of " .. toname .. ".")
305 return true, "Password of player \"" .. toname .. "\" cleared"
309 core.register_chatcommand("auth_reload", {
311 description = "Reload authentication data",
312 privs = {server=true},
313 func = function(name, param)
314 local done = core.auth_reload()
315 return done, (done and "Done." or "Failed.")
319 core.register_chatcommand("remove_player", {
321 description = "Remove a player's data",
322 privs = {server=true},
323 func = function(name, param)
326 return false, "Name field required"
329 local rc = core.remove_player(toname)
332 core.log("action", name .. " removed player data of " .. toname .. ".")
333 return true, "Player \"" .. toname .. "\" removed."
335 return true, "No such player \"" .. toname .. "\" to remove."
337 return true, "Player \"" .. toname .. "\" is connected, cannot remove."
340 return false, "Unhandled remove_player return code " .. rc .. ""
344 core.register_chatcommand("teleport", {
345 params = "<X>,<Y>,<Z> | <to_name> | (<name> <X>,<Y>,<Z>) | (<name> <to_name>)",
346 description = "Teleport to position or player",
347 privs = {teleport=true},
348 func = function(name, param)
349 -- Returns (pos, true) if found, otherwise (pos, false)
350 local function find_free_position_near(pos)
357 for _, d in ipairs(tries) do
358 local p = {x = pos.x+d.x, y = pos.y+d.y, z = pos.z+d.z}
359 local n = core.get_node_or_nil(p)
361 local def = core.registered_nodes[n.name]
362 if def and not def.walkable then
370 local teleportee = nil
372 p.x, p.y, p.z = string.match(param, "^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
376 if p.x and p.y and p.z then
378 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
379 return false, "Cannot teleport out of map bounds!"
381 teleportee = core.get_player_by_name(name)
383 teleportee:set_pos(p)
384 return true, "Teleporting to "..core.pos_to_string(p)
388 local teleportee = nil
390 local target_name = nil
391 target_name = param:match("^([^ ]+)$")
392 teleportee = core.get_player_by_name(name)
394 local target = core.get_player_by_name(target_name)
399 if teleportee and p then
400 p = find_free_position_near(p)
401 teleportee:set_pos(p)
402 return true, "Teleporting to " .. target_name
403 .. " at "..core.pos_to_string(p)
406 if not core.check_player_privs(name, {bring=true}) then
407 return false, "You don't have permission to teleport other players (missing bring privilege)"
410 local teleportee = nil
412 local teleportee_name = nil
413 teleportee_name, p.x, p.y, p.z = param:match(
414 "^([^ ]+) +([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
415 p.x, p.y, p.z = tonumber(p.x), tonumber(p.y), tonumber(p.z)
416 if teleportee_name then
417 teleportee = core.get_player_by_name(teleportee_name)
419 if teleportee and p.x and p.y and p.z then
420 teleportee:set_pos(p)
421 return true, "Teleporting " .. teleportee_name
422 .. " to " .. core.pos_to_string(p)
425 local teleportee = nil
427 local teleportee_name = nil
428 local target_name = nil
429 teleportee_name, target_name = string.match(param, "^([^ ]+) +([^ ]+)$")
430 if teleportee_name then
431 teleportee = core.get_player_by_name(teleportee_name)
434 local target = core.get_player_by_name(target_name)
439 if teleportee and p then
440 p = find_free_position_near(p)
441 teleportee:set_pos(p)
442 return true, "Teleporting " .. teleportee_name
443 .. " to " .. target_name
444 .. " at " .. core.pos_to_string(p)
447 return false, 'Invalid parameters ("' .. param
448 .. '") or player not found (see /help teleport)'
452 core.register_chatcommand("set", {
453 params = "([-n] <name> <value>) | <name>",
454 description = "Set or read server configuration setting",
455 privs = {server=true},
456 func = function(name, param)
457 local arg, setname, setvalue = string.match(param, "(-[n]) ([^ ]+) (.+)")
458 if arg and arg == "-n" and setname and setvalue then
459 core.settings:set(setname, setvalue)
460 return true, setname .. " = " .. setvalue
462 local setname, setvalue = string.match(param, "([^ ]+) (.+)")
463 if setname and setvalue then
464 if not core.settings:get(setname) then
465 return false, "Failed. Use '/set -n <name> <value>' to create a new setting."
467 core.settings:set(setname, setvalue)
468 return true, setname .. " = " .. setvalue
470 local setname = string.match(param, "([^ ]+)")
472 local setvalue = core.settings:get(setname)
474 setvalue = "<not set>"
476 return true, setname .. " = " .. setvalue
478 return false, "Invalid parameters (see /help set)."
482 local function emergeblocks_callback(pos, action, num_calls_remaining, ctx)
483 if ctx.total_blocks == 0 then
484 ctx.total_blocks = num_calls_remaining + 1
485 ctx.current_blocks = 0
487 ctx.current_blocks = ctx.current_blocks + 1
489 if ctx.current_blocks == ctx.total_blocks then
490 core.chat_send_player(ctx.requestor_name,
491 string.format("Finished emerging %d blocks in %.2fms.",
492 ctx.total_blocks, (os.clock() - ctx.start_time) * 1000))
496 local function emergeblocks_progress_update(ctx)
497 if ctx.current_blocks ~= ctx.total_blocks then
498 core.chat_send_player(ctx.requestor_name,
499 string.format("emergeblocks update: %d/%d blocks emerged (%.1f%%)",
500 ctx.current_blocks, ctx.total_blocks,
501 (ctx.current_blocks / ctx.total_blocks) * 100))
503 core.after(2, emergeblocks_progress_update, ctx)
507 core.register_chatcommand("emergeblocks", {
508 params = "(here [<radius>]) | (<pos1> <pos2>)",
509 description = "Load (or, if nonexistent, generate) map blocks "
510 .. "contained in area pos1 to pos2 (<pos1> and <pos2> must be in parentheses)",
511 privs = {server=true},
512 func = function(name, param)
513 local p1, p2 = parse_range_str(name, param)
521 start_time = os.clock(),
522 requestor_name = name
525 core.emerge_area(p1, p2, emergeblocks_callback, context)
526 core.after(2, emergeblocks_progress_update, context)
528 return true, "Started emerge of area ranging from " ..
529 core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
533 core.register_chatcommand("deleteblocks", {
534 params = "(here [<radius>]) | (<pos1> <pos2>)",
535 description = "Delete map blocks contained in area pos1 to pos2 "
536 .. "(<pos1> and <pos2> must be in parentheses)",
537 privs = {server=true},
538 func = function(name, param)
539 local p1, p2 = parse_range_str(name, param)
544 if core.delete_area(p1, p2) then
545 return true, "Successfully cleared area ranging from " ..
546 core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
548 return false, "Failed to clear one or more blocks in area"
553 core.register_chatcommand("fixlight", {
554 params = "(here [<radius>]) | (<pos1> <pos2>)",
555 description = "Resets lighting in the area between pos1 and pos2 "
556 .. "(<pos1> and <pos2> must be in parentheses)",
557 privs = {server = true},
558 func = function(name, param)
559 local p1, p2 = parse_range_str(name, param)
564 if core.fix_light(p1, p2) then
565 return true, "Successfully reset light in the area ranging from " ..
566 core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
568 return false, "Failed to load one or more blocks in area"
573 core.register_chatcommand("mods", {
575 description = "List mods installed on the server",
577 func = function(name, param)
578 return true, table.concat(core.get_modnames(), ", ")
582 local function handle_give_command(cmd, giver, receiver, stackstring)
583 core.log("action", giver .. " invoked " .. cmd
584 .. ', stackstring="' .. stackstring .. '"')
585 local itemstack = ItemStack(stackstring)
586 if itemstack:is_empty() then
587 return false, "Cannot give an empty item"
588 elseif (not itemstack:is_known()) or (itemstack:get_name() == "unknown") then
589 return false, "Cannot give an unknown item"
590 -- Forbid giving 'ignore' due to unwanted side effects
591 elseif itemstack:get_name() == "ignore" then
592 return false, "Giving 'ignore' is not allowed"
594 local receiverref = core.get_player_by_name(receiver)
595 if receiverref == nil then
596 return false, receiver .. " is not a known player"
598 local leftover = receiverref:get_inventory():add_item("main", itemstack)
600 if leftover:is_empty() then
602 elseif leftover:get_count() == itemstack:get_count() then
603 partiality = "could not be "
605 partiality = "partially "
607 -- The actual item stack string may be different from what the "giver"
608 -- entered (e.g. big numbers are always interpreted as 2^16-1).
609 stackstring = itemstack:to_string()
610 if giver == receiver then
611 local msg = "%q %sadded to inventory."
612 return true, msg:format(stackstring, partiality)
614 core.chat_send_player(receiver, ("%q %sadded to inventory.")
615 :format(stackstring, partiality))
616 local msg = "%q %sadded to %s's inventory."
617 return true, msg:format(stackstring, partiality, receiver)
621 core.register_chatcommand("give", {
622 params = "<name> <ItemString> [<count> [<wear>]]",
623 description = "Give item to player",
625 func = function(name, param)
626 local toname, itemstring = string.match(param, "^([^ ]+) +(.+)$")
627 if not toname or not itemstring then
628 return false, "Name and ItemString required"
630 return handle_give_command("/give", name, toname, itemstring)
634 core.register_chatcommand("giveme", {
635 params = "<ItemString> [<count> [<wear>]]",
636 description = "Give item to yourself",
638 func = function(name, param)
639 local itemstring = string.match(param, "(.+)$")
640 if not itemstring then
641 return false, "ItemString required"
643 return handle_give_command("/giveme", name, name, itemstring)
647 core.register_chatcommand("spawnentity", {
648 params = "<EntityName> [<X>,<Y>,<Z>]",
649 description = "Spawn entity at given (or your) position",
650 privs = {give=true, interact=true},
651 func = function(name, param)
652 local entityname, p = string.match(param, "^([^ ]+) *(.*)$")
653 if not entityname then
654 return false, "EntityName required"
656 core.log("action", ("%s invokes /spawnentity, entityname=%q")
657 :format(name, entityname))
658 local player = core.get_player_by_name(name)
659 if player == nil then
660 core.log("error", "Unable to spawn entity, player is nil")
661 return false, "Unable to spawn entity, player is nil"
663 if not core.registered_entities[entityname] then
664 return false, "Cannot spawn an unknown entity"
669 p = core.string_to_pos(p)
671 return false, "Invalid parameters ('" .. param .. "')"
675 core.add_entity(p, entityname)
676 return true, ("%q spawned."):format(entityname)
680 core.register_chatcommand("pulverize", {
682 description = "Destroy item in hand",
683 func = function(name, param)
684 local player = core.get_player_by_name(name)
686 core.log("error", "Unable to pulverize, no player.")
687 return false, "Unable to pulverize, no player."
689 local wielded_item = player:get_wielded_item()
690 if wielded_item:is_empty() then
691 return false, "Unable to pulverize, no item in hand."
693 core.log("action", name .. " pulverized \"" ..
694 wielded_item:get_name() .. " " .. wielded_item:get_count() .. "\"")
695 player:set_wielded_item(nil)
696 return true, "An item was pulverized."
701 core.rollback_punch_callbacks = {}
703 core.register_on_punchnode(function(pos, node, puncher)
704 local name = puncher and puncher:get_player_name()
705 if name and core.rollback_punch_callbacks[name] then
706 core.rollback_punch_callbacks[name](pos, node, puncher)
707 core.rollback_punch_callbacks[name] = nil
711 core.register_chatcommand("rollback_check", {
712 params = "[<range>] [<seconds>] [<limit>]",
713 description = "Check who last touched a node or a node near it"
714 .. " within the time specified by <seconds>. Default: range = 0,"
715 .. " seconds = 86400 = 24h, limit = 5",
716 privs = {rollback=true},
717 func = function(name, param)
718 if not core.settings:get_bool("enable_rollback_recording") then
719 return false, "Rollback functions are disabled."
721 local range, seconds, limit =
722 param:match("(%d+) *(%d*) *(%d*)")
723 range = tonumber(range) or 0
724 seconds = tonumber(seconds) or 86400
725 limit = tonumber(limit) or 5
727 return false, "That limit is too high!"
730 core.rollback_punch_callbacks[name] = function(pos, node, puncher)
731 local name = puncher:get_player_name()
732 core.chat_send_player(name, "Checking " .. core.pos_to_string(pos) .. "...")
733 local actions = core.rollback_get_node_actions(pos, range, seconds, limit)
735 core.chat_send_player(name, "Rollback functions are disabled")
738 local num_actions = #actions
739 if num_actions == 0 then
740 core.chat_send_player(name, "Nobody has touched"
741 .. " the specified location in "
742 .. seconds .. " seconds")
745 local time = os.time()
746 for i = num_actions, 1, -1 do
747 local action = actions[i]
748 core.chat_send_player(name,
749 ("%s %s %s -> %s %d seconds ago.")
751 core.pos_to_string(action.pos),
759 return true, "Punch a node (range=" .. range .. ", seconds="
760 .. seconds .. "s, limit=" .. limit .. ")"
764 core.register_chatcommand("rollback", {
765 params = "(<name> [<seconds>]) | (:<actor> [<seconds>])",
766 description = "Revert actions of a player. Default for <seconds> is 60",
767 privs = {rollback=true},
768 func = function(name, param)
769 if not core.settings:get_bool("enable_rollback_recording") then
770 return false, "Rollback functions are disabled."
772 local target_name, seconds = string.match(param, ":([^ ]+) *(%d*)")
773 if not target_name then
774 local player_name = nil
775 player_name, seconds = string.match(param, "([^ ]+) *(%d*)")
776 if not player_name then
777 return false, "Invalid parameters. See /help rollback"
778 .. " and /help rollback_check."
780 target_name = "player:"..player_name
782 seconds = tonumber(seconds) or 60
783 core.chat_send_player(name, "Reverting actions of "
784 .. target_name .. " since "
785 .. seconds .. " seconds.")
786 local success, log = core.rollback_revert_actions_by(
787 target_name, seconds)
790 response = "(log is too long to show)\n"
792 for _, line in pairs(log) do
793 response = response .. line .. "\n"
796 response = response .. "Reverting actions "
797 .. (success and "succeeded." or "FAILED.")
798 return success, response
802 core.register_chatcommand("status", {
803 description = "Show server status",
804 func = function(name, param)
805 local status = core.get_server_status(name, false)
806 if status and status ~= "" then
809 return false, "This command was disabled by a mod or game"
813 core.register_chatcommand("time", {
814 params = "[<0..23>:<0..59> | <0..24000>]",
815 description = "Show or set time of day",
817 func = function(name, param)
819 local current_time = math.floor(core.get_timeofday() * 1440)
820 local minutes = current_time % 60
821 local hour = (current_time - minutes) / 60
822 return true, ("Current time is %d:%02d"):format(hour, minutes)
824 local player_privs = core.get_player_privs(name)
825 if not player_privs.settime then
826 return false, "You don't have permission to run this command " ..
827 "(missing privilege: settime)."
829 local hour, minute = param:match("^(%d+):(%d+)$")
831 local new_time = tonumber(param)
833 return false, "Invalid time."
835 -- Backward compatibility.
836 core.set_timeofday((new_time % 24000) / 24000)
837 core.log("action", name .. " sets time to " .. new_time)
838 return true, "Time of day changed."
840 hour = tonumber(hour)
841 minute = tonumber(minute)
842 if hour < 0 or hour > 23 then
843 return false, "Invalid hour (must be between 0 and 23 inclusive)."
844 elseif minute < 0 or minute > 59 then
845 return false, "Invalid minute (must be between 0 and 59 inclusive)."
847 core.set_timeofday((hour * 60 + minute) / 1440)
848 core.log("action", ("%s sets time to %d:%02d"):format(name, hour, minute))
849 return true, "Time of day changed."
853 core.register_chatcommand("days", {
854 description = "Show day count since world creation",
855 func = function(name, param)
856 return true, "Current day is " .. core.get_day_count()
860 core.register_chatcommand("shutdown", {
861 params = "[<delay_in_seconds> | -1] [reconnect] [<message>]",
862 description = "Shutdown server (-1 cancels a delayed shutdown)",
863 privs = {server=true},
864 func = function(name, param)
865 local delay, reconnect, message
866 delay, param = param:match("^%s*(%S+)(.*)")
868 reconnect, param = param:match("^%s*(%S+)(.*)")
870 message = param and param:match("^%s*(.+)") or ""
871 delay = tonumber(delay) or 0
874 core.log("action", name .. " shuts down server")
875 core.chat_send_all("*** Server shutting down (operator request).")
877 core.request_shutdown(message:trim(), core.is_yes(reconnect), delay)
881 core.register_chatcommand("ban", {
882 params = "[<name> | <IP_address>]",
883 description = "Ban player or show ban list",
885 func = function(name, param)
887 local ban_list = core.get_ban_list()
888 if ban_list == "" then
889 return true, "The ban list is empty."
891 return true, "Ban list: " .. ban_list
894 if not core.get_player_by_name(param) then
895 return false, "No such player."
897 if not core.ban_player(param) then
898 return false, "Failed to ban player."
900 local desc = core.get_ban_description(param)
901 core.log("action", name .. " bans " .. desc .. ".")
902 return true, "Banned " .. desc .. "."
906 core.register_chatcommand("unban", {
907 params = "<name> | <IP_address>",
908 description = "Remove player ban",
910 func = function(name, param)
911 if not core.unban_player_or_ip(param) then
912 return false, "Failed to unban player/IP."
914 core.log("action", name .. " unbans " .. param)
915 return true, "Unbanned " .. param
919 core.register_chatcommand("kick", {
920 params = "<name> [<reason>]",
921 description = "Kick a player",
923 func = function(name, param)
924 local tokick, reason = param:match("([^ ]+) (.+)")
925 tokick = tokick or param
926 if not core.kick_player(tokick, reason) then
927 return false, "Failed to kick player " .. tokick
929 local log_reason = ""
931 log_reason = " with reason \"" .. reason .. "\""
933 core.log("action", name .. " kicks " .. tokick .. log_reason)
934 return true, "Kicked " .. tokick
938 core.register_chatcommand("clearobjects", {
939 params = "[full | quick]",
940 description = "Clear all objects in world",
941 privs = {server=true},
942 func = function(name, param)
944 if param == "" or param == "quick" then
945 options.mode = "quick"
946 elseif param == "full" then
947 options.mode = "full"
949 return false, "Invalid usage, see /help clearobjects."
952 core.log("action", name .. " clears all objects ("
953 .. options.mode .. " mode).")
954 core.chat_send_all("Clearing all objects. This may take long."
955 .. " You may experience a timeout. (by "
957 core.clear_objects(options)
958 core.log("action", "Object clearing done.")
959 core.chat_send_all("*** Cleared all objects.")
963 core.register_chatcommand("msg", {
964 params = "<name> <message>",
965 description = "Send a private message",
966 privs = {shout=true},
967 func = function(name, param)
968 local sendto, message = param:match("^(%S+)%s(.+)$")
970 return false, "Invalid usage, see /help msg."
972 if not core.get_player_by_name(sendto) then
973 return false, "The player " .. sendto
976 core.log("action", "PM from " .. name .. " to " .. sendto
978 core.chat_send_player(sendto, "PM from " .. name .. ": "
980 return true, "Message sent."
984 core.register_chatcommand("last-login", {
986 description = "Get the last login time of a player or yourself",
987 func = function(name, param)
991 local pauth = core.get_auth_handler().get_auth(param)
992 if pauth and pauth.last_login then
993 -- Time in UTC, ISO 8601 format
994 return true, "Last login time was " ..
995 os.date("!%Y-%m-%dT%H:%M:%SZ", pauth.last_login)
997 return false, "Last login time is unknown"
1001 core.register_chatcommand("clearinv", {
1002 params = "[<name>]",
1003 description = "Clear the inventory of yourself or another player",
1004 func = function(name, param)
1006 if param and param ~= "" and param ~= name then
1007 if not core.check_player_privs(name, {server=true}) then
1008 return false, "You don't have permission"
1009 .. " to clear another player's inventory (missing privilege: server)"
1011 player = core.get_player_by_name(param)
1012 core.chat_send_player(param, name.." cleared your inventory.")
1014 player = core.get_player_by_name(name)
1018 player:get_inventory():set_list("main", {})
1019 player:get_inventory():set_list("craft", {})
1020 player:get_inventory():set_list("craftpreview", {})
1021 core.log("action", name.." clears "..player:get_player_name().."'s inventory")
1022 return true, "Cleared "..player:get_player_name().."'s inventory."
1024 return false, "Player must be online to clear inventory!"
1029 local function handle_kill_command(killer, victim)
1030 if core.settings:get_bool("enable_damage") == false then
1031 return false, "Players can't be killed, damage has been disabled."
1033 local victimref = core.get_player_by_name(victim)
1034 if victimref == nil then
1035 return false, string.format("Player %s is not online.", victim)
1036 elseif victimref:get_hp() <= 0 then
1037 if killer == victim then
1038 return false, "You are already dead."
1040 return false, string.format("%s is already dead.", victim)
1043 if not killer == victim then
1044 core.log("action", string.format("%s killed %s", killer, victim))
1048 return true, string.format("%s has been killed.", victim)
1051 core.register_chatcommand("kill", {
1052 params = "[<name>]",
1053 description = "Kill player or yourself",
1054 privs = {server=true},
1055 func = function(name, param)
1056 return handle_kill_command(name, param == "" and name or param)