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 local status = core.get_server_status(name, false)
803 if status and status ~= "" then
806 return false, "This command was disabled by a mod or game"
810 core.register_chatcommand("time", {
811 params = "[<0..23>:<0..59> | <0..24000>]",
812 description = "Show or set time of day",
814 func = function(name, param)
816 local current_time = math.floor(core.get_timeofday() * 1440)
817 local minutes = current_time % 60
818 local hour = (current_time - minutes) / 60
819 return true, ("Current time is %d:%02d"):format(hour, minutes)
821 local player_privs = core.get_player_privs(name)
822 if not player_privs.settime then
823 return false, "You don't have permission to run this command " ..
824 "(missing privilege: settime)."
826 local hour, minute = param:match("^(%d+):(%d+)$")
828 local new_time = tonumber(param)
830 return false, "Invalid time."
832 -- Backward compatibility.
833 core.set_timeofday((new_time % 24000) / 24000)
834 core.log("action", name .. " sets time to " .. new_time)
835 return true, "Time of day changed."
837 hour = tonumber(hour)
838 minute = tonumber(minute)
839 if hour < 0 or hour > 23 then
840 return false, "Invalid hour (must be between 0 and 23 inclusive)."
841 elseif minute < 0 or minute > 59 then
842 return false, "Invalid minute (must be between 0 and 59 inclusive)."
844 core.set_timeofday((hour * 60 + minute) / 1440)
845 core.log("action", ("%s sets time to %d:%02d"):format(name, hour, minute))
846 return true, "Time of day changed."
850 core.register_chatcommand("days", {
851 description = "Show day count since world creation",
852 func = function(name, param)
853 return true, "Current day is " .. core.get_day_count()
857 core.register_chatcommand("shutdown", {
858 params = "[<delay_in_seconds> | -1] [reconnect] [<message>]",
859 description = "Shutdown server (-1 cancels a delayed shutdown)",
860 privs = {server=true},
861 func = function(name, param)
862 local delay, reconnect, message
863 delay, param = param:match("^%s*(%S+)(.*)")
865 reconnect, param = param:match("^%s*(%S+)(.*)")
867 message = param and param:match("^%s*(.+)") or ""
868 delay = tonumber(delay) or 0
871 core.log("action", name .. " shuts down server")
872 core.chat_send_all("*** Server shutting down (operator request).")
874 core.request_shutdown(message:trim(), core.is_yes(reconnect), delay)
878 core.register_chatcommand("ban", {
879 params = "[<name> | <IP_address>]",
880 description = "Ban player or show ban list",
882 func = function(name, param)
884 local ban_list = core.get_ban_list()
885 if ban_list == "" then
886 return true, "The ban list is empty."
888 return true, "Ban list: " .. ban_list
891 if not core.get_player_by_name(param) then
892 return false, "No such player."
894 if not core.ban_player(param) then
895 return false, "Failed to ban player."
897 local desc = core.get_ban_description(param)
898 core.log("action", name .. " bans " .. desc .. ".")
899 return true, "Banned " .. desc .. "."
903 core.register_chatcommand("unban", {
904 params = "<name> | <IP_address>",
905 description = "Remove player ban",
907 func = function(name, param)
908 if not core.unban_player_or_ip(param) then
909 return false, "Failed to unban player/IP."
911 core.log("action", name .. " unbans " .. param)
912 return true, "Unbanned " .. param
916 core.register_chatcommand("kick", {
917 params = "<name> [<reason>]",
918 description = "Kick a player",
920 func = function(name, param)
921 local tokick, reason = param:match("([^ ]+) (.+)")
922 tokick = tokick or param
923 if not core.kick_player(tokick, reason) then
924 return false, "Failed to kick player " .. tokick
926 local log_reason = ""
928 log_reason = " with reason \"" .. reason .. "\""
930 core.log("action", name .. " kicks " .. tokick .. log_reason)
931 return true, "Kicked " .. tokick
935 core.register_chatcommand("clearobjects", {
936 params = "[full | quick]",
937 description = "Clear all objects in world",
938 privs = {server=true},
939 func = function(name, param)
941 if param == "" or param == "quick" then
942 options.mode = "quick"
943 elseif param == "full" then
944 options.mode = "full"
946 return false, "Invalid usage, see /help clearobjects."
949 core.log("action", name .. " clears all objects ("
950 .. options.mode .. " mode).")
951 core.chat_send_all("Clearing all objects. This may take long."
952 .. " You may experience a timeout. (by "
954 core.clear_objects(options)
955 core.log("action", "Object clearing done.")
956 core.chat_send_all("*** Cleared all objects.")
960 core.register_chatcommand("msg", {
961 params = "<name> <message>",
962 description = "Send a private message",
963 privs = {shout=true},
964 func = function(name, param)
965 local sendto, message = param:match("^(%S+)%s(.+)$")
967 return false, "Invalid usage, see /help msg."
969 if not core.get_player_by_name(sendto) then
970 return false, "The player " .. sendto
973 core.log("action", "PM from " .. name .. " to " .. sendto
975 core.chat_send_player(sendto, "PM from " .. name .. ": "
977 return true, "Message sent."
981 core.register_chatcommand("last-login", {
983 description = "Get the last login time of a player or yourself",
984 func = function(name, param)
988 local pauth = core.get_auth_handler().get_auth(param)
989 if pauth and pauth.last_login then
990 -- Time in UTC, ISO 8601 format
991 return true, "Last login time was " ..
992 os.date("!%Y-%m-%dT%H:%M:%SZ", pauth.last_login)
994 return false, "Last login time is unknown"
998 core.register_chatcommand("clearinv", {
1000 description = "Clear the inventory of yourself or another player",
1001 func = function(name, param)
1003 if param and param ~= "" and param ~= name then
1004 if not core.check_player_privs(name, {server=true}) then
1005 return false, "You don't have permission"
1006 .. " to clear another player's inventory (missing privilege: server)"
1008 player = core.get_player_by_name(param)
1009 core.chat_send_player(param, name.." cleared your inventory.")
1011 player = core.get_player_by_name(name)
1015 player:get_inventory():set_list("main", {})
1016 player:get_inventory():set_list("craft", {})
1017 player:get_inventory():set_list("craftpreview", {})
1018 core.log("action", name.." clears "..player:get_player_name().."'s inventory")
1019 return true, "Cleared "..player:get_player_name().."'s inventory."
1021 return false, "Player must be online to clear inventory!"
1026 local function handle_kill_command(killer, victim)
1027 if core.settings:get_bool("enable_damage") == false then
1028 return false, "Players can't be killed, damage has been disabled."
1030 local victimref = core.get_player_by_name(victim)
1031 if victimref == nil then
1032 return false, string.format("Player %s is not online.", victim)
1033 elseif victimref:get_hp() <= 0 then
1034 if killer == victim then
1035 return false, "You are already dead."
1037 return false, string.format("%s is already dead.", victim)
1040 if not killer == victim then
1041 core.log("action", string.format("%s killed %s", killer, victim))
1045 return true, string.format("%s has been killed.", victim)
1048 core.register_chatcommand("kill", {
1049 params = "[<name>]",
1050 description = "Kill player or yourself",
1051 privs = {server=true},
1052 func = function(name, param)
1053 return handle_kill_command(name, param == "" and name or param)