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 return true, "Privileges of " .. name .. ": "
101 .. core.privs_to_string(
102 core.get_player_privs(name), ' ')
106 core.register_chatcommand("hasprivs", {
107 params = "<privilege>",
108 description = "Return list of all online players with privilege.",
109 privs = {basic_privs = true},
110 func = function(caller, param)
113 return false, "Invalid parameters (see /help hasprivs)"
115 if not core.registered_privileges[param] then
116 return false, "Unknown privilege!"
118 local privs = core.string_to_privs(param)
119 local players_with_privs = {}
120 for _, player in pairs(core.get_connected_players()) do
121 local player_name = player:get_player_name()
122 if core.check_player_privs(player_name, privs) then
123 table.insert(players_with_privs, player_name)
126 return true, "Players online with the \"" .. param .. "\" priv: " ..
127 table.concat(players_with_privs, ", ")
131 local function handle_grant_command(caller, grantname, grantprivstr)
132 local caller_privs = core.get_player_privs(caller)
133 if not (caller_privs.privs or caller_privs.basic_privs) then
134 return false, "Your privileges are insufficient."
137 if not core.get_auth_handler().get_auth(grantname) then
138 return false, "Player " .. grantname .. " does not exist."
140 local grantprivs = core.string_to_privs(grantprivstr)
141 if grantprivstr == "all" then
142 grantprivs = core.registered_privileges
144 local privs = core.get_player_privs(grantname)
145 local privs_unknown = ""
147 core.string_to_privs(core.settings:get("basic_privs") or "interact,shout")
148 for priv, _ in pairs(grantprivs) do
149 if not basic_privs[priv] and not caller_privs.privs then
150 return false, "Your privileges are insufficient."
152 if not core.registered_privileges[priv] then
153 privs_unknown = privs_unknown .. "Unknown privilege: " .. priv .. "\n"
157 if privs_unknown ~= "" then
158 return false, privs_unknown
160 for priv, _ in pairs(grantprivs) do
161 core.run_priv_callbacks(grantname, priv, caller, "grant")
163 core.set_player_privs(grantname, privs)
164 core.log("action", caller..' granted ('..core.privs_to_string(grantprivs, ', ')..') privileges to '..grantname)
165 if grantname ~= caller then
166 core.chat_send_player(grantname, caller
167 .. " granted you privileges: "
168 .. core.privs_to_string(grantprivs, ' '))
170 return true, "Privileges of " .. grantname .. ": "
171 .. core.privs_to_string(
172 core.get_player_privs(grantname), ' ')
175 core.register_chatcommand("grant", {
176 params = "<name> (<privilege> | all)",
177 description = "Give privileges to player",
178 func = function(name, param)
179 local grantname, grantprivstr = string.match(param, "([^ ]+) (.+)")
180 if not grantname or not grantprivstr then
181 return false, "Invalid parameters (see /help grant)"
183 return handle_grant_command(name, grantname, grantprivstr)
187 core.register_chatcommand("grantme", {
188 params = "<privilege> | all",
189 description = "Grant privileges to yourself",
190 func = function(name, param)
192 return false, "Invalid parameters (see /help grantme)"
194 return handle_grant_command(name, name, param)
198 core.register_chatcommand("revoke", {
199 params = "<name> (<privilege> | all)",
200 description = "Remove privileges from player",
202 func = function(name, param)
203 if not core.check_player_privs(name, {privs=true}) and
204 not core.check_player_privs(name, {basic_privs=true}) then
205 return false, "Your privileges are insufficient."
207 local revoke_name, revoke_priv_str = string.match(param, "([^ ]+) (.+)")
208 if not revoke_name or not revoke_priv_str then
209 return false, "Invalid parameters (see /help revoke)"
210 elseif not core.get_auth_handler().get_auth(revoke_name) then
211 return false, "Player " .. revoke_name .. " does not exist."
213 local revoke_privs = core.string_to_privs(revoke_priv_str)
214 local privs = core.get_player_privs(revoke_name)
216 core.string_to_privs(core.settings:get("basic_privs") or "interact,shout")
217 for priv, _ in pairs(revoke_privs) do
218 if not basic_privs[priv] and
219 not core.check_player_privs(name, {privs=true}) then
220 return false, "Your privileges are insufficient."
223 if revoke_priv_str == "all" then
227 for priv, _ in pairs(revoke_privs) do
232 for priv, _ in pairs(revoke_privs) do
233 core.run_priv_callbacks(revoke_name, priv, name, "revoke")
236 core.set_player_privs(revoke_name, privs)
237 core.log("action", name..' revoked ('
238 ..core.privs_to_string(revoke_privs, ', ')
239 ..') privileges from '..revoke_name)
240 if revoke_name ~= name then
241 core.chat_send_player(revoke_name, name
242 .. " revoked privileges from you: "
243 .. core.privs_to_string(revoke_privs, ' '))
245 return true, "Privileges of " .. revoke_name .. ": "
246 .. core.privs_to_string(
247 core.get_player_privs(revoke_name), ' ')
251 core.register_chatcommand("setpassword", {
252 params = "<name> <password>",
253 description = "Set player's password",
254 privs = {password=true},
255 func = function(name, param)
256 local toname, raw_password = string.match(param, "^([^ ]+) +(.+)$")
258 toname = param:match("^([^ ]+) *$")
262 return false, "Name field required"
264 local act_str_past = "?"
265 local act_str_pres = "?"
266 if not raw_password then
267 core.set_player_password(toname, "")
268 act_str_past = "cleared"
269 act_str_pres = "clears"
271 core.set_player_password(toname,
272 core.get_password_hash(toname,
275 act_str_pres = "sets"
277 if toname ~= name then
278 core.chat_send_player(toname, "Your password was "
279 .. act_str_past .. " by " .. name)
282 core.log("action", name .. " " .. act_str_pres
283 .. " password of " .. toname .. ".")
285 return true, "Password of player \"" .. toname .. "\" " .. act_str_past
289 core.register_chatcommand("clearpassword", {
291 description = "Set empty password for a player",
292 privs = {password=true},
293 func = function(name, param)
296 return false, "Name field required"
298 core.set_player_password(toname, '')
300 core.log("action", name .. " clears password of " .. toname .. ".")
302 return true, "Password of player \"" .. toname .. "\" cleared"
306 core.register_chatcommand("auth_reload", {
308 description = "Reload authentication data",
309 privs = {server=true},
310 func = function(name, param)
311 local done = core.auth_reload()
312 return done, (done and "Done." or "Failed.")
316 core.register_chatcommand("remove_player", {
318 description = "Remove a player's data",
319 privs = {server=true},
320 func = function(name, param)
323 return false, "Name field required"
326 local rc = core.remove_player(toname)
329 core.log("action", name .. " removed player data of " .. toname .. ".")
330 return true, "Player \"" .. toname .. "\" removed."
332 return true, "No such player \"" .. toname .. "\" to remove."
334 return true, "Player \"" .. toname .. "\" is connected, cannot remove."
337 return false, "Unhandled remove_player return code " .. rc .. ""
341 core.register_chatcommand("teleport", {
342 params = "<X>,<Y>,<Z> | <to_name> | (<name> <X>,<Y>,<Z>) | (<name> <to_name>)",
343 description = "Teleport to position or player",
344 privs = {teleport=true},
345 func = function(name, param)
346 -- Returns (pos, true) if found, otherwise (pos, false)
347 local function find_free_position_near(pos)
354 for _, d in ipairs(tries) do
355 local p = {x = pos.x+d.x, y = pos.y+d.y, z = pos.z+d.z}
356 local n = core.get_node_or_nil(p)
358 local def = core.registered_nodes[n.name]
359 if def and not def.walkable then
367 local teleportee = nil
369 p.x, p.y, p.z = string.match(param, "^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
373 if p.x and p.y and p.z then
375 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
376 return false, "Cannot teleport out of map bounds!"
378 teleportee = core.get_player_by_name(name)
381 return true, "Teleporting to "..core.pos_to_string(p)
385 local teleportee = nil
387 local target_name = nil
388 target_name = param:match("^([^ ]+)$")
389 teleportee = core.get_player_by_name(name)
391 local target = core.get_player_by_name(target_name)
396 if teleportee and p then
397 p = find_free_position_near(p)
399 return true, "Teleporting to " .. target_name
400 .. " at "..core.pos_to_string(p)
403 if not core.check_player_privs(name, {bring=true}) then
404 return false, "You don't have permission to teleport other players (missing bring privilege)"
407 local teleportee = nil
409 local teleportee_name = nil
410 teleportee_name, p.x, p.y, p.z = param:match(
411 "^([^ ]+) +([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
412 p.x, p.y, p.z = tonumber(p.x), tonumber(p.y), tonumber(p.z)
413 if teleportee_name then
414 teleportee = core.get_player_by_name(teleportee_name)
416 if teleportee and p.x and p.y and p.z then
418 return true, "Teleporting " .. teleportee_name
419 .. " to " .. core.pos_to_string(p)
422 local teleportee = nil
424 local teleportee_name = nil
425 local target_name = nil
426 teleportee_name, target_name = string.match(param, "^([^ ]+) +([^ ]+)$")
427 if teleportee_name then
428 teleportee = core.get_player_by_name(teleportee_name)
431 local target = core.get_player_by_name(target_name)
436 if teleportee and p then
437 p = find_free_position_near(p)
439 return true, "Teleporting " .. teleportee_name
440 .. " to " .. target_name
441 .. " at " .. core.pos_to_string(p)
444 return false, 'Invalid parameters ("' .. param
445 .. '") or player not found (see /help teleport)'
449 core.register_chatcommand("set", {
450 params = "([-n] <name> <value>) | <name>",
451 description = "Set or read server configuration setting",
452 privs = {server=true},
453 func = function(name, param)
454 local arg, setname, setvalue = string.match(param, "(-[n]) ([^ ]+) (.+)")
455 if arg and arg == "-n" and setname and setvalue then
456 core.settings:set(setname, setvalue)
457 return true, setname .. " = " .. setvalue
459 local setname, setvalue = string.match(param, "([^ ]+) (.+)")
460 if setname and setvalue then
461 if not core.settings:get(setname) then
462 return false, "Failed. Use '/set -n <name> <value>' to create a new setting."
464 core.settings:set(setname, setvalue)
465 return true, setname .. " = " .. setvalue
467 local setname = string.match(param, "([^ ]+)")
469 local setvalue = core.settings:get(setname)
471 setvalue = "<not set>"
473 return true, setname .. " = " .. setvalue
475 return false, "Invalid parameters (see /help set)."
479 local function emergeblocks_callback(pos, action, num_calls_remaining, ctx)
480 if ctx.total_blocks == 0 then
481 ctx.total_blocks = num_calls_remaining + 1
482 ctx.current_blocks = 0
484 ctx.current_blocks = ctx.current_blocks + 1
486 if ctx.current_blocks == ctx.total_blocks then
487 core.chat_send_player(ctx.requestor_name,
488 string.format("Finished emerging %d blocks in %.2fms.",
489 ctx.total_blocks, (os.clock() - ctx.start_time) * 1000))
493 local function emergeblocks_progress_update(ctx)
494 if ctx.current_blocks ~= ctx.total_blocks then
495 core.chat_send_player(ctx.requestor_name,
496 string.format("emergeblocks update: %d/%d blocks emerged (%.1f%%)",
497 ctx.current_blocks, ctx.total_blocks,
498 (ctx.current_blocks / ctx.total_blocks) * 100))
500 core.after(2, emergeblocks_progress_update, ctx)
504 core.register_chatcommand("emergeblocks", {
505 params = "(here [<radius>]) | (<pos1> <pos2>)",
506 description = "Load (or, if nonexistent, generate) map blocks "
507 .. "contained in area pos1 to pos2 (<pos1> and <pos2> must be in parentheses)",
508 privs = {server=true},
509 func = function(name, param)
510 local p1, p2 = parse_range_str(name, param)
518 start_time = os.clock(),
519 requestor_name = name
522 core.emerge_area(p1, p2, emergeblocks_callback, context)
523 core.after(2, emergeblocks_progress_update, context)
525 return true, "Started emerge of area ranging from " ..
526 core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
530 core.register_chatcommand("deleteblocks", {
531 params = "(here [<radius>]) | (<pos1> <pos2>)",
532 description = "Delete map blocks contained in area pos1 to pos2 "
533 .. "(<pos1> and <pos2> must be in parentheses)",
534 privs = {server=true},
535 func = function(name, param)
536 local p1, p2 = parse_range_str(name, param)
541 if core.delete_area(p1, p2) then
542 return true, "Successfully cleared area ranging from " ..
543 core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
545 return false, "Failed to clear one or more blocks in area"
550 core.register_chatcommand("fixlight", {
551 params = "(here [<radius>]) | (<pos1> <pos2>)",
552 description = "Resets lighting in the area between pos1 and pos2 "
553 .. "(<pos1> and <pos2> must be in parentheses)",
554 privs = {server = true},
555 func = function(name, param)
556 local p1, p2 = parse_range_str(name, param)
561 if core.fix_light(p1, p2) then
562 return true, "Successfully reset light in the area ranging from " ..
563 core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
565 return false, "Failed to load one or more blocks in area"
570 core.register_chatcommand("mods", {
572 description = "List mods installed on the server",
574 func = function(name, param)
575 return true, table.concat(core.get_modnames(), ", ")
579 local function handle_give_command(cmd, giver, receiver, stackstring)
580 core.log("action", giver .. " invoked " .. cmd
581 .. ', stackstring="' .. stackstring .. '"')
582 local itemstack = ItemStack(stackstring)
583 if itemstack:is_empty() then
584 return false, "Cannot give an empty item"
585 elseif (not itemstack:is_known()) or (itemstack:get_name() == "unknown") then
586 return false, "Cannot give an unknown item"
587 -- Forbid giving 'ignore' due to unwanted side effects
588 elseif itemstack:get_name() == "ignore" then
589 return false, "Giving 'ignore' is not allowed"
591 local receiverref = core.get_player_by_name(receiver)
592 if receiverref == nil then
593 return false, receiver .. " is not a known player"
595 local leftover = receiverref:get_inventory():add_item("main", itemstack)
597 if leftover:is_empty() then
599 elseif leftover:get_count() == itemstack:get_count() then
600 partiality = "could not be "
602 partiality = "partially "
604 -- The actual item stack string may be different from what the "giver"
605 -- entered (e.g. big numbers are always interpreted as 2^16-1).
606 stackstring = itemstack:to_string()
607 if giver == receiver then
608 local msg = "%q %sadded to inventory."
609 return true, msg:format(stackstring, partiality)
611 core.chat_send_player(receiver, ("%q %sadded to inventory.")
612 :format(stackstring, partiality))
613 local msg = "%q %sadded to %s's inventory."
614 return true, msg:format(stackstring, partiality, receiver)
618 core.register_chatcommand("give", {
619 params = "<name> <ItemString> [<count> [<wear>]]",
620 description = "Give item to player",
622 func = function(name, param)
623 local toname, itemstring = string.match(param, "^([^ ]+) +(.+)$")
624 if not toname or not itemstring then
625 return false, "Name and ItemString required"
627 return handle_give_command("/give", name, toname, itemstring)
631 core.register_chatcommand("giveme", {
632 params = "<ItemString> [<count> [<wear>]]",
633 description = "Give item to yourself",
635 func = function(name, param)
636 local itemstring = string.match(param, "(.+)$")
637 if not itemstring then
638 return false, "ItemString required"
640 return handle_give_command("/giveme", name, name, itemstring)
644 core.register_chatcommand("spawnentity", {
645 params = "<EntityName> [<X>,<Y>,<Z>]",
646 description = "Spawn entity at given (or your) position",
647 privs = {give=true, interact=true},
648 func = function(name, param)
649 local entityname, p = string.match(param, "^([^ ]+) *(.*)$")
650 if not entityname then
651 return false, "EntityName required"
653 core.log("action", ("%s invokes /spawnentity, entityname=%q")
654 :format(name, entityname))
655 local player = core.get_player_by_name(name)
656 if player == nil then
657 core.log("error", "Unable to spawn entity, player is nil")
658 return false, "Unable to spawn entity, player is nil"
660 if not core.registered_entities[entityname] then
661 return false, "Cannot spawn an unknown entity"
666 p = core.string_to_pos(p)
668 return false, "Invalid parameters ('" .. param .. "')"
672 core.add_entity(p, entityname)
673 return true, ("%q spawned."):format(entityname)
677 core.register_chatcommand("pulverize", {
679 description = "Destroy item in hand",
680 func = function(name, param)
681 local player = core.get_player_by_name(name)
683 core.log("error", "Unable to pulverize, no player.")
684 return false, "Unable to pulverize, no player."
686 local wielded_item = player:get_wielded_item()
687 if wielded_item:is_empty() then
688 return false, "Unable to pulverize, no item in hand."
690 core.log("action", name .. " pulverized \"" ..
691 wielded_item:get_name() .. " " .. wielded_item:get_count() .. "\"")
692 player:set_wielded_item(nil)
693 return true, "An item was pulverized."
698 core.rollback_punch_callbacks = {}
700 core.register_on_punchnode(function(pos, node, puncher)
701 local name = puncher and puncher:get_player_name()
702 if name and core.rollback_punch_callbacks[name] then
703 core.rollback_punch_callbacks[name](pos, node, puncher)
704 core.rollback_punch_callbacks[name] = nil
708 core.register_chatcommand("rollback_check", {
709 params = "[<range>] [<seconds>] [<limit>]",
710 description = "Check who last touched a node or a node near it"
711 .. " within the time specified by <seconds>. Default: range = 0,"
712 .. " seconds = 86400 = 24h, limit = 5",
713 privs = {rollback=true},
714 func = function(name, param)
715 if not core.settings:get_bool("enable_rollback_recording") then
716 return false, "Rollback functions are disabled."
718 local range, seconds, limit =
719 param:match("(%d+) *(%d*) *(%d*)")
720 range = tonumber(range) or 0
721 seconds = tonumber(seconds) or 86400
722 limit = tonumber(limit) or 5
724 return false, "That limit is too high!"
727 core.rollback_punch_callbacks[name] = function(pos, node, puncher)
728 local name = puncher:get_player_name()
729 core.chat_send_player(name, "Checking " .. core.pos_to_string(pos) .. "...")
730 local actions = core.rollback_get_node_actions(pos, range, seconds, limit)
732 core.chat_send_player(name, "Rollback functions are disabled")
735 local num_actions = #actions
736 if num_actions == 0 then
737 core.chat_send_player(name, "Nobody has touched"
738 .. " the specified location in "
739 .. seconds .. " seconds")
742 local time = os.time()
743 for i = num_actions, 1, -1 do
744 local action = actions[i]
745 core.chat_send_player(name,
746 ("%s %s %s -> %s %d seconds ago.")
748 core.pos_to_string(action.pos),
756 return true, "Punch a node (range=" .. range .. ", seconds="
757 .. seconds .. "s, limit=" .. limit .. ")"
761 core.register_chatcommand("rollback", {
762 params = "(<name> [<seconds>]) | (:<actor> [<seconds>])",
763 description = "Revert actions of a player. Default for <seconds> is 60",
764 privs = {rollback=true},
765 func = function(name, param)
766 if not core.settings:get_bool("enable_rollback_recording") then
767 return false, "Rollback functions are disabled."
769 local target_name, seconds = string.match(param, ":([^ ]+) *(%d*)")
770 if not target_name then
771 local player_name = nil
772 player_name, seconds = string.match(param, "([^ ]+) *(%d*)")
773 if not player_name then
774 return false, "Invalid parameters. See /help rollback"
775 .. " and /help rollback_check."
777 target_name = "player:"..player_name
779 seconds = tonumber(seconds) or 60
780 core.chat_send_player(name, "Reverting actions of "
781 .. target_name .. " since "
782 .. seconds .. " seconds.")
783 local success, log = core.rollback_revert_actions_by(
784 target_name, seconds)
787 response = "(log is too long to show)\n"
789 for _, line in pairs(log) do
790 response = response .. line .. "\n"
793 response = response .. "Reverting actions "
794 .. (success and "succeeded." or "FAILED.")
795 return success, response
799 core.register_chatcommand("status", {
800 description = "Show server status",
801 func = function(name, param)
802 return true, core.get_server_status()
806 core.register_chatcommand("time", {
807 params = "[<0..23>:<0..59> | <0..24000>]",
808 description = "Show or set time of day",
810 func = function(name, param)
812 local current_time = math.floor(core.get_timeofday() * 1440)
813 local minutes = current_time % 60
814 local hour = (current_time - minutes) / 60
815 return true, ("Current time is %d:%02d"):format(hour, minutes)
817 local player_privs = core.get_player_privs(name)
818 if not player_privs.settime then
819 return false, "You don't have permission to run this command " ..
820 "(missing privilege: settime)."
822 local hour, minute = param:match("^(%d+):(%d+)$")
824 local new_time = tonumber(param)
826 return false, "Invalid time."
828 -- Backward compatibility.
829 core.set_timeofday((new_time % 24000) / 24000)
830 core.log("action", name .. " sets time to " .. new_time)
831 return true, "Time of day changed."
833 hour = tonumber(hour)
834 minute = tonumber(minute)
835 if hour < 0 or hour > 23 then
836 return false, "Invalid hour (must be between 0 and 23 inclusive)."
837 elseif minute < 0 or minute > 59 then
838 return false, "Invalid minute (must be between 0 and 59 inclusive)."
840 core.set_timeofday((hour * 60 + minute) / 1440)
841 core.log("action", ("%s sets time to %d:%02d"):format(name, hour, minute))
842 return true, "Time of day changed."
846 core.register_chatcommand("days", {
847 description = "Show day count since world creation",
848 func = function(name, param)
849 return true, "Current day is " .. core.get_day_count()
853 core.register_chatcommand("shutdown", {
854 params = "[<delay_in_seconds> | -1] [reconnect] [<message>]",
855 description = "Shutdown server (-1 cancels a delayed shutdown)",
856 privs = {server=true},
857 func = function(name, param)
858 local delay, reconnect, message
859 delay, param = param:match("^%s*(%S+)(.*)")
861 reconnect, param = param:match("^%s*(%S+)(.*)")
863 message = param and param:match("^%s*(.+)") or ""
864 delay = tonumber(delay) or 0
867 core.log("action", name .. " shuts down server")
868 core.chat_send_all("*** Server shutting down (operator request).")
870 core.request_shutdown(message:trim(), core.is_yes(reconnect), delay)
874 core.register_chatcommand("ban", {
875 params = "[<name> | <IP_address>]",
876 description = "Ban player or show ban list",
878 func = function(name, param)
880 local ban_list = core.get_ban_list()
881 if ban_list == "" then
882 return true, "The ban list is empty."
884 return true, "Ban list: " .. ban_list
887 if not core.get_player_by_name(param) then
888 return false, "No such player."
890 if not core.ban_player(param) then
891 return false, "Failed to ban player."
893 local desc = core.get_ban_description(param)
894 core.log("action", name .. " bans " .. desc .. ".")
895 return true, "Banned " .. desc .. "."
899 core.register_chatcommand("unban", {
900 params = "<name> | <IP_address>",
901 description = "Remove player ban",
903 func = function(name, param)
904 if not core.unban_player_or_ip(param) then
905 return false, "Failed to unban player/IP."
907 core.log("action", name .. " unbans " .. param)
908 return true, "Unbanned " .. param
912 core.register_chatcommand("kick", {
913 params = "<name> [<reason>]",
914 description = "Kick a player",
916 func = function(name, param)
917 local tokick, reason = param:match("([^ ]+) (.+)")
918 tokick = tokick or param
919 if not core.kick_player(tokick, reason) then
920 return false, "Failed to kick player " .. tokick
922 local log_reason = ""
924 log_reason = " with reason \"" .. reason .. "\""
926 core.log("action", name .. " kicks " .. tokick .. log_reason)
927 return true, "Kicked " .. tokick
931 core.register_chatcommand("clearobjects", {
932 params = "[full | quick]",
933 description = "Clear all objects in world",
934 privs = {server=true},
935 func = function(name, param)
937 if param == "" or param == "quick" then
938 options.mode = "quick"
939 elseif param == "full" then
940 options.mode = "full"
942 return false, "Invalid usage, see /help clearobjects."
945 core.log("action", name .. " clears all objects ("
946 .. options.mode .. " mode).")
947 core.chat_send_all("Clearing all objects. This may take long."
948 .. " You may experience a timeout. (by "
950 core.clear_objects(options)
951 core.log("action", "Object clearing done.")
952 core.chat_send_all("*** Cleared all objects.")
956 core.register_chatcommand("msg", {
957 params = "<name> <message>",
958 description = "Send a private message",
959 privs = {shout=true},
960 func = function(name, param)
961 local sendto, message = param:match("^(%S+)%s(.+)$")
963 return false, "Invalid usage, see /help msg."
965 if not core.get_player_by_name(sendto) then
966 return false, "The player " .. sendto
969 core.log("action", "PM from " .. name .. " to " .. sendto
971 core.chat_send_player(sendto, "PM from " .. name .. ": "
973 return true, "Message sent."
977 core.register_chatcommand("last-login", {
979 description = "Get the last login time of a player or yourself",
980 func = function(name, param)
984 local pauth = core.get_auth_handler().get_auth(param)
985 if pauth and pauth.last_login then
986 -- Time in UTC, ISO 8601 format
987 return true, "Last login time was " ..
988 os.date("!%Y-%m-%dT%H:%M:%SZ", pauth.last_login)
990 return false, "Last login time is unknown"
994 core.register_chatcommand("clearinv", {
996 description = "Clear the inventory of yourself or another player",
997 func = function(name, param)
999 if param and param ~= "" and param ~= name then
1000 if not core.check_player_privs(name, {server=true}) then
1001 return false, "You don't have permission"
1002 .. " to clear another player's inventory (missing privilege: server)"
1004 player = core.get_player_by_name(param)
1005 core.chat_send_player(param, name.." cleared your inventory.")
1007 player = core.get_player_by_name(name)
1011 player:get_inventory():set_list("main", {})
1012 player:get_inventory():set_list("craft", {})
1013 player:get_inventory():set_list("craftpreview", {})
1014 core.log("action", name.." clears "..player:get_player_name().."'s inventory")
1015 return true, "Cleared "..player:get_player_name().."'s inventory."
1017 return false, "Player must be online to clear inventory!"
1022 local function handle_kill_command(killer, victim)
1023 if core.settings:get_bool("enable_damage") == false then
1024 return false, "Players can't be killed, damage has been disabled."
1026 local victimref = core.get_player_by_name(victim)
1027 if victimref == nil then
1028 return false, string.format("Player %s is not online.", victim)
1029 elseif victimref:get_hp() <= 0 then
1030 if killer == victim then
1031 return false, "You are already dead."
1033 return false, string.format("%s is already dead.", victim)
1036 if not killer == victim then
1037 core.log("action", string.format("%s killed %s", killer, victim))
1041 return true, string.format("%s has been killed.", victim)
1044 core.register_chatcommand("kill", {
1045 params = "[<name>]",
1046 description = "Kill player or yourself",
1047 privs = {server=true},
1048 func = function(name, param)
1049 return handle_kill_command(name, param == "" and name or param)