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 if player:get_wielded_item():is_empty() then
687 return false, "Unable to pulverize, no item in hand."
689 player:set_wielded_item(nil)
690 return true, "An item was pulverized."
695 core.rollback_punch_callbacks = {}
697 core.register_on_punchnode(function(pos, node, puncher)
698 local name = puncher and puncher:get_player_name()
699 if name and core.rollback_punch_callbacks[name] then
700 core.rollback_punch_callbacks[name](pos, node, puncher)
701 core.rollback_punch_callbacks[name] = nil
705 core.register_chatcommand("rollback_check", {
706 params = "[<range>] [<seconds>] [<limit>]",
707 description = "Check who last touched a node or a node near it"
708 .. " within the time specified by <seconds>. Default: range = 0,"
709 .. " seconds = 86400 = 24h, limit = 5",
710 privs = {rollback=true},
711 func = function(name, param)
712 if not core.settings:get_bool("enable_rollback_recording") then
713 return false, "Rollback functions are disabled."
715 local range, seconds, limit =
716 param:match("(%d+) *(%d*) *(%d*)")
717 range = tonumber(range) or 0
718 seconds = tonumber(seconds) or 86400
719 limit = tonumber(limit) or 5
721 return false, "That limit is too high!"
724 core.rollback_punch_callbacks[name] = function(pos, node, puncher)
725 local name = puncher:get_player_name()
726 core.chat_send_player(name, "Checking " .. core.pos_to_string(pos) .. "...")
727 local actions = core.rollback_get_node_actions(pos, range, seconds, limit)
729 core.chat_send_player(name, "Rollback functions are disabled")
732 local num_actions = #actions
733 if num_actions == 0 then
734 core.chat_send_player(name, "Nobody has touched"
735 .. " the specified location in "
736 .. seconds .. " seconds")
739 local time = os.time()
740 for i = num_actions, 1, -1 do
741 local action = actions[i]
742 core.chat_send_player(name,
743 ("%s %s %s -> %s %d seconds ago.")
745 core.pos_to_string(action.pos),
753 return true, "Punch a node (range=" .. range .. ", seconds="
754 .. seconds .. "s, limit=" .. limit .. ")"
758 core.register_chatcommand("rollback", {
759 params = "(<name> [<seconds>]) | (:<actor> [<seconds>])",
760 description = "Revert actions of a player. Default for <seconds> is 60",
761 privs = {rollback=true},
762 func = function(name, param)
763 if not core.settings:get_bool("enable_rollback_recording") then
764 return false, "Rollback functions are disabled."
766 local target_name, seconds = string.match(param, ":([^ ]+) *(%d*)")
767 if not target_name then
768 local player_name = nil
769 player_name, seconds = string.match(param, "([^ ]+) *(%d*)")
770 if not player_name then
771 return false, "Invalid parameters. See /help rollback"
772 .. " and /help rollback_check."
774 target_name = "player:"..player_name
776 seconds = tonumber(seconds) or 60
777 core.chat_send_player(name, "Reverting actions of "
778 .. target_name .. " since "
779 .. seconds .. " seconds.")
780 local success, log = core.rollback_revert_actions_by(
781 target_name, seconds)
784 response = "(log is too long to show)\n"
786 for _, line in pairs(log) do
787 response = response .. line .. "\n"
790 response = response .. "Reverting actions "
791 .. (success and "succeeded." or "FAILED.")
792 return success, response
796 core.register_chatcommand("status", {
797 description = "Show server status",
798 func = function(name, param)
799 return true, core.get_server_status()
803 core.register_chatcommand("time", {
804 params = "[<0..23>:<0..59> | <0..24000>]",
805 description = "Show or set time of day",
807 func = function(name, param)
809 local current_time = math.floor(core.get_timeofday() * 1440)
810 local minutes = current_time % 60
811 local hour = (current_time - minutes) / 60
812 return true, ("Current time is %d:%02d"):format(hour, minutes)
814 local player_privs = core.get_player_privs(name)
815 if not player_privs.settime then
816 return false, "You don't have permission to run this command " ..
817 "(missing privilege: settime)."
819 local hour, minute = param:match("^(%d+):(%d+)$")
821 local new_time = tonumber(param)
823 return false, "Invalid time."
825 -- Backward compatibility.
826 core.set_timeofday((new_time % 24000) / 24000)
827 core.log("action", name .. " sets time to " .. new_time)
828 return true, "Time of day changed."
830 hour = tonumber(hour)
831 minute = tonumber(minute)
832 if hour < 0 or hour > 23 then
833 return false, "Invalid hour (must be between 0 and 23 inclusive)."
834 elseif minute < 0 or minute > 59 then
835 return false, "Invalid minute (must be between 0 and 59 inclusive)."
837 core.set_timeofday((hour * 60 + minute) / 1440)
838 core.log("action", ("%s sets time to %d:%02d"):format(name, hour, minute))
839 return true, "Time of day changed."
843 core.register_chatcommand("days", {
844 description = "Show day count since world creation",
845 func = function(name, param)
846 return true, "Current day is " .. core.get_day_count()
850 core.register_chatcommand("shutdown", {
851 params = "[<delay_in_seconds> | -1] [reconnect] [<message>]",
852 description = "Shutdown server (-1 cancels a delayed shutdown)",
853 privs = {server=true},
854 func = function(name, param)
855 local delay, reconnect, message
856 delay, param = param:match("^%s*(%S+)(.*)")
858 reconnect, param = param:match("^%s*(%S+)(.*)")
860 message = param and param:match("^%s*(.+)") or ""
861 delay = tonumber(delay) or 0
864 core.log("action", name .. " shuts down server")
865 core.chat_send_all("*** Server shutting down (operator request).")
867 core.request_shutdown(message:trim(), core.is_yes(reconnect), delay)
871 core.register_chatcommand("ban", {
872 params = "[<name> | <IP_address>]",
873 description = "Ban player or show ban list",
875 func = function(name, param)
877 local ban_list = core.get_ban_list()
878 if ban_list == "" then
879 return true, "The ban list is empty."
881 return true, "Ban list: " .. ban_list
884 if not core.get_player_by_name(param) then
885 return false, "No such player."
887 if not core.ban_player(param) then
888 return false, "Failed to ban player."
890 local desc = core.get_ban_description(param)
891 core.log("action", name .. " bans " .. desc .. ".")
892 return true, "Banned " .. desc .. "."
896 core.register_chatcommand("unban", {
897 params = "<name> | <IP_address>",
898 description = "Remove player ban",
900 func = function(name, param)
901 if not core.unban_player_or_ip(param) then
902 return false, "Failed to unban player/IP."
904 core.log("action", name .. " unbans " .. param)
905 return true, "Unbanned " .. param
909 core.register_chatcommand("kick", {
910 params = "<name> [<reason>]",
911 description = "Kick a player",
913 func = function(name, param)
914 local tokick, reason = param:match("([^ ]+) (.+)")
915 tokick = tokick or param
916 if not core.kick_player(tokick, reason) then
917 return false, "Failed to kick player " .. tokick
919 local log_reason = ""
921 log_reason = " with reason \"" .. reason .. "\""
923 core.log("action", name .. " kicks " .. tokick .. log_reason)
924 return true, "Kicked " .. tokick
928 core.register_chatcommand("clearobjects", {
929 params = "[full | quick]",
930 description = "Clear all objects in world",
931 privs = {server=true},
932 func = function(name, param)
934 if param == "" or param == "quick" then
935 options.mode = "quick"
936 elseif param == "full" then
937 options.mode = "full"
939 return false, "Invalid usage, see /help clearobjects."
942 core.log("action", name .. " clears all objects ("
943 .. options.mode .. " mode).")
944 core.chat_send_all("Clearing all objects. This may take long."
945 .. " You may experience a timeout. (by "
947 core.clear_objects(options)
948 core.log("action", "Object clearing done.")
949 core.chat_send_all("*** Cleared all objects.")
953 core.register_chatcommand("msg", {
954 params = "<name> <message>",
955 description = "Send a private message",
956 privs = {shout=true},
957 func = function(name, param)
958 local sendto, message = param:match("^(%S+)%s(.+)$")
960 return false, "Invalid usage, see /help msg."
962 if not core.get_player_by_name(sendto) then
963 return false, "The player " .. sendto
966 core.log("action", "PM from " .. name .. " to " .. sendto
968 core.chat_send_player(sendto, "PM from " .. name .. ": "
970 return true, "Message sent."
974 core.register_chatcommand("last-login", {
976 description = "Get the last login time of a player or yourself",
977 func = function(name, param)
981 local pauth = core.get_auth_handler().get_auth(param)
982 if pauth and pauth.last_login then
983 -- Time in UTC, ISO 8601 format
984 return true, "Last login time was " ..
985 os.date("!%Y-%m-%dT%H:%M:%SZ", pauth.last_login)
987 return false, "Last login time is unknown"
991 core.register_chatcommand("clearinv", {
993 description = "Clear the inventory of yourself or another player",
994 func = function(name, param)
996 if param and param ~= "" and param ~= name then
997 if not core.check_player_privs(name, {server=true}) then
998 return false, "You don't have permission"
999 .. " to clear another player's inventory (missing privilege: server)"
1001 player = core.get_player_by_name(param)
1002 core.chat_send_player(param, name.." cleared your inventory.")
1004 player = core.get_player_by_name(name)
1008 player:get_inventory():set_list("main", {})
1009 player:get_inventory():set_list("craft", {})
1010 player:get_inventory():set_list("craftpreview", {})
1011 core.log("action", name.." clears "..player:get_player_name().."'s inventory")
1012 return true, "Cleared "..player:get_player_name().."'s inventory."
1014 return false, "Player must be online to clear inventory!"
1019 local function handle_kill_command(killer, victim)
1020 if core.settings:get_bool("enable_damage") == false then
1021 return false, "Players can't be killed, damage has been disabled."
1023 local victimref = core.get_player_by_name(victim)
1024 if victimref == nil then
1025 return false, string.format("Player %s is not online.", victim)
1026 elseif victimref:get_hp() <= 0 then
1027 if killer == victim then
1028 return false, "You are already dead."
1030 return false, string.format("%s is already dead.", victim)
1033 if not killer == victim then
1034 core.log("action", string.format("%s killed %s", killer, victim))
1038 return true, string.format("%s has been killed.", victim)
1041 core.register_chatcommand("kill", {
1042 params = "[<name>]",
1043 description = "Kill player or yourself",
1044 privs = {server=true},
1045 func = function(name, param)
1046 return handle_kill_command(name, param == "" and name or param)