1 -- Minetest: builtin/game/chatcommands.lua
4 -- Chat command handler
7 core.chatcommands = core.registered_chatcommands -- BACKWARDS COMPATIBILITY
9 core.register_on_chat_message(function(name, message)
10 if message:sub(1,1) ~= "/" then
14 local cmd, param = string.match(message, "^/([^ ]+) *(.*)")
16 core.chat_send_player(name, "-!- Empty command")
22 local cmd_def = core.registered_chatcommands[cmd]
24 core.chat_send_player(name, "-!- Invalid command: " .. cmd)
27 local has_privs, missing_privs = core.check_player_privs(name, cmd_def.privs)
29 core.set_last_run_mod(cmd_def.mod_origin)
30 local success, message = cmd_def.func(name, param)
32 core.chat_send_player(name, message)
35 core.chat_send_player(name, "You don't have permission"
36 .. " to run this command (missing privileges: "
37 .. table.concat(missing_privs, ", ") .. ")")
39 return true -- Handled chat message
42 if core.settings:get_bool("profiler.load") then
43 -- Run after register_chatcommand and its register_on_chat_message
44 -- Before any chattcommands that should be profiled
45 profiler.init_chatcommand()
48 -- Parses a "range" string in the format of "here (number)" or
49 -- "(x1, y1, z1) (x2, y2, z2)", returning two position vectors
50 local function parse_range_str(player_name, str)
52 local args = str:split(" ")
54 if args[1] == "here" then
55 p1, p2 = core.get_player_radius_area(player_name, tonumber(args[2]))
57 return false, "Unable to get player " .. player_name .. " position"
60 p1, p2 = core.string_to_area(str)
62 return false, "Incorrect area format. Expected: (x1,y1,z1) (x2,y2,z2)"
72 core.register_chatcommand("me", {
74 description = "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 local function handle_grant_command(caller, grantname, grantprivstr)
107 local caller_privs = core.get_player_privs(caller)
108 if not (caller_privs.privs or caller_privs.basic_privs) then
109 return false, "Your privileges are insufficient."
112 if not core.get_auth_handler().get_auth(grantname) then
113 return false, "Player " .. grantname .. " does not exist."
115 local grantprivs = core.string_to_privs(grantprivstr)
116 if grantprivstr == "all" then
117 grantprivs = core.registered_privileges
119 local privs = core.get_player_privs(grantname)
120 local privs_unknown = ""
122 core.string_to_privs(core.settings:get("basic_privs") or "interact,shout")
123 for priv, _ in pairs(grantprivs) do
124 if not basic_privs[priv] and not caller_privs.privs then
125 return false, "Your privileges are insufficient."
127 if not core.registered_privileges[priv] then
128 privs_unknown = privs_unknown .. "Unknown privilege: " .. priv .. "\n"
132 if privs_unknown ~= "" then
133 return false, privs_unknown
135 for priv, _ in pairs(grantprivs) do
136 core.run_priv_callbacks(grantname, priv, caller, "grant")
138 core.set_player_privs(grantname, privs)
139 core.log("action", caller..' granted ('..core.privs_to_string(grantprivs, ', ')..') privileges to '..grantname)
140 if grantname ~= caller then
141 core.chat_send_player(grantname, caller
142 .. " granted you privileges: "
143 .. core.privs_to_string(grantprivs, ' '))
145 return true, "Privileges of " .. grantname .. ": "
146 .. core.privs_to_string(
147 core.get_player_privs(grantname), ' ')
150 core.register_chatcommand("grant", {
151 params = "<name> (<privilege> | all)",
152 description = "Give privileges to player",
153 func = function(name, param)
154 local grantname, grantprivstr = string.match(param, "([^ ]+) (.+)")
155 if not grantname or not grantprivstr then
156 return false, "Invalid parameters (see /help grant)"
158 return handle_grant_command(name, grantname, grantprivstr)
162 core.register_chatcommand("grantme", {
163 params = "<privilege> | all",
164 description = "Grant privileges to yourself",
165 func = function(name, param)
167 return false, "Invalid parameters (see /help grantme)"
169 return handle_grant_command(name, name, param)
173 core.register_chatcommand("revoke", {
174 params = "<name> (<privilege> | all)",
175 description = "Remove privileges from player",
177 func = function(name, param)
178 if not core.check_player_privs(name, {privs=true}) and
179 not core.check_player_privs(name, {basic_privs=true}) then
180 return false, "Your privileges are insufficient."
182 local revoke_name, revoke_priv_str = string.match(param, "([^ ]+) (.+)")
183 if not revoke_name or not revoke_priv_str then
184 return false, "Invalid parameters (see /help revoke)"
185 elseif not core.get_auth_handler().get_auth(revoke_name) then
186 return false, "Player " .. revoke_name .. " does not exist."
188 local revoke_privs = core.string_to_privs(revoke_priv_str)
189 local privs = core.get_player_privs(revoke_name)
191 core.string_to_privs(core.settings:get("basic_privs") or "interact,shout")
192 for priv, _ in pairs(revoke_privs) do
193 if not basic_privs[priv] and
194 not core.check_player_privs(name, {privs=true}) then
195 return false, "Your privileges are insufficient."
198 if revoke_priv_str == "all" then
202 for priv, _ in pairs(revoke_privs) do
207 for priv, _ in pairs(revoke_privs) do
208 core.run_priv_callbacks(revoke_name, priv, name, "revoke")
211 core.set_player_privs(revoke_name, privs)
212 core.log("action", name..' revoked ('
213 ..core.privs_to_string(revoke_privs, ', ')
214 ..') privileges from '..revoke_name)
215 if revoke_name ~= name then
216 core.chat_send_player(revoke_name, name
217 .. " revoked privileges from you: "
218 .. core.privs_to_string(revoke_privs, ' '))
220 return true, "Privileges of " .. revoke_name .. ": "
221 .. core.privs_to_string(
222 core.get_player_privs(revoke_name), ' ')
226 core.register_chatcommand("setpassword", {
227 params = "<name> <password>",
228 description = "Set player's password",
229 privs = {password=true},
230 func = function(name, param)
231 local toname, raw_password = string.match(param, "^([^ ]+) +(.+)$")
233 toname = param:match("^([^ ]+) *$")
237 return false, "Name field required"
239 local act_str_past = "?"
240 local act_str_pres = "?"
241 if not raw_password then
242 core.set_player_password(toname, "")
243 act_str_past = "cleared"
244 act_str_pres = "clears"
246 core.set_player_password(toname,
247 core.get_password_hash(toname,
250 act_str_pres = "sets"
252 if toname ~= name then
253 core.chat_send_player(toname, "Your password was "
254 .. act_str_past .. " by " .. name)
257 core.log("action", name .. " " .. act_str_pres
258 .. " password of " .. toname .. ".")
260 return true, "Password of player \"" .. toname .. "\" " .. act_str_past
264 core.register_chatcommand("clearpassword", {
266 description = "Set empty password for a player",
267 privs = {password=true},
268 func = function(name, param)
271 return false, "Name field required"
273 core.set_player_password(toname, '')
275 core.log("action", name .. " clears password of " .. toname .. ".")
277 return true, "Password of player \"" .. toname .. "\" cleared"
281 core.register_chatcommand("auth_reload", {
283 description = "Reload authentication data",
284 privs = {server=true},
285 func = function(name, param)
286 local done = core.auth_reload()
287 return done, (done and "Done." or "Failed.")
291 core.register_chatcommand("remove_player", {
293 description = "Remove a player's data",
294 privs = {server=true},
295 func = function(name, param)
298 return false, "Name field required"
301 local rc = core.remove_player(toname)
304 core.log("action", name .. " removed player data of " .. toname .. ".")
305 return true, "Player \"" .. toname .. "\" removed."
307 return true, "No such player \"" .. toname .. "\" to remove."
309 return true, "Player \"" .. toname .. "\" is connected, cannot remove."
312 return false, "Unhandled remove_player return code " .. rc .. ""
316 core.register_chatcommand("teleport", {
317 params = "<X>,<Y>,<Z> | <to_name> | (<name> <X>,<Y>,<Z>) | (<name> <to_name>)",
318 description = "Teleport to position or player",
319 privs = {teleport=true},
320 func = function(name, param)
321 -- Returns (pos, true) if found, otherwise (pos, false)
322 local function find_free_position_near(pos)
329 for _, d in ipairs(tries) do
330 local p = {x = pos.x+d.x, y = pos.y+d.y, z = pos.z+d.z}
331 local n = core.get_node_or_nil(p)
333 local def = core.registered_nodes[n.name]
334 if def and not def.walkable then
342 local teleportee = nil
344 p.x, p.y, p.z = string.match(param, "^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
348 if p.x and p.y and p.z then
350 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
351 return false, "Cannot teleport out of map bounds!"
353 teleportee = core.get_player_by_name(name)
356 return true, "Teleporting to "..core.pos_to_string(p)
360 local teleportee = nil
362 local target_name = nil
363 target_name = param:match("^([^ ]+)$")
364 teleportee = core.get_player_by_name(name)
366 local target = core.get_player_by_name(target_name)
371 if teleportee and p then
372 p = find_free_position_near(p)
374 return true, "Teleporting to " .. target_name
375 .. " at "..core.pos_to_string(p)
378 if not core.check_player_privs(name, {bring=true}) then
379 return false, "You don't have permission to teleport other players (missing bring privilege)"
382 local teleportee = nil
384 local teleportee_name = nil
385 teleportee_name, p.x, p.y, p.z = param:match(
386 "^([^ ]+) +([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
387 p.x, p.y, p.z = tonumber(p.x), tonumber(p.y), tonumber(p.z)
388 if teleportee_name then
389 teleportee = core.get_player_by_name(teleportee_name)
391 if teleportee and p.x and p.y and p.z then
393 return true, "Teleporting " .. teleportee_name
394 .. " to " .. core.pos_to_string(p)
397 local teleportee = nil
399 local teleportee_name = nil
400 local target_name = nil
401 teleportee_name, target_name = string.match(param, "^([^ ]+) +([^ ]+)$")
402 if teleportee_name then
403 teleportee = core.get_player_by_name(teleportee_name)
406 local target = core.get_player_by_name(target_name)
411 if teleportee and p then
412 p = find_free_position_near(p)
414 return true, "Teleporting " .. teleportee_name
415 .. " to " .. target_name
416 .. " at " .. core.pos_to_string(p)
419 return false, 'Invalid parameters ("' .. param
420 .. '") or player not found (see /help teleport)'
424 core.register_chatcommand("set", {
425 params = "([-n] <name> <value>) | <name>",
426 description = "Set or read server configuration setting",
427 privs = {server=true},
428 func = function(name, param)
429 local arg, setname, setvalue = string.match(param, "(-[n]) ([^ ]+) (.+)")
430 if arg and arg == "-n" and setname and setvalue then
431 core.settings:set(setname, setvalue)
432 return true, setname .. " = " .. setvalue
434 local setname, setvalue = string.match(param, "([^ ]+) (.+)")
435 if setname and setvalue then
436 if not core.settings:get(setname) then
437 return false, "Failed. Use '/set -n <name> <value>' to create a new setting."
439 core.settings:set(setname, setvalue)
440 return true, setname .. " = " .. setvalue
442 local setname = string.match(param, "([^ ]+)")
444 local setvalue = core.settings:get(setname)
446 setvalue = "<not set>"
448 return true, setname .. " = " .. setvalue
450 return false, "Invalid parameters (see /help set)."
454 local function emergeblocks_callback(pos, action, num_calls_remaining, ctx)
455 if ctx.total_blocks == 0 then
456 ctx.total_blocks = num_calls_remaining + 1
457 ctx.current_blocks = 0
459 ctx.current_blocks = ctx.current_blocks + 1
461 if ctx.current_blocks == ctx.total_blocks then
462 core.chat_send_player(ctx.requestor_name,
463 string.format("Finished emerging %d blocks in %.2fms.",
464 ctx.total_blocks, (os.clock() - ctx.start_time) * 1000))
468 local function emergeblocks_progress_update(ctx)
469 if ctx.current_blocks ~= ctx.total_blocks then
470 core.chat_send_player(ctx.requestor_name,
471 string.format("emergeblocks update: %d/%d blocks emerged (%.1f%%)",
472 ctx.current_blocks, ctx.total_blocks,
473 (ctx.current_blocks / ctx.total_blocks) * 100))
475 core.after(2, emergeblocks_progress_update, ctx)
479 core.register_chatcommand("emergeblocks", {
480 params = "(here [<radius>]) | (<pos1> <pos2>)",
481 description = "Load (or, if nonexistent, generate) map blocks "
482 .. "contained in area pos1 to pos2 (<pos1> and <pos2> must be in parentheses)",
483 privs = {server=true},
484 func = function(name, param)
485 local p1, p2 = parse_range_str(name, param)
493 start_time = os.clock(),
494 requestor_name = name
497 core.emerge_area(p1, p2, emergeblocks_callback, context)
498 core.after(2, emergeblocks_progress_update, context)
500 return true, "Started emerge of area ranging from " ..
501 core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
505 core.register_chatcommand("deleteblocks", {
506 params = "(here [<radius>]) | (<pos1> <pos2>)",
507 description = "Delete map blocks contained in area pos1 to pos2 "
508 .. "(<pos1> and <pos2> must be in parentheses)",
509 privs = {server=true},
510 func = function(name, param)
511 local p1, p2 = parse_range_str(name, param)
516 if core.delete_area(p1, p2) then
517 return true, "Successfully cleared area ranging from " ..
518 core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
520 return false, "Failed to clear one or more blocks in area"
525 core.register_chatcommand("fixlight", {
526 params = "(here [<radius>]) | (<pos1> <pos2>)",
527 description = "Resets lighting in the area between pos1 and pos2 "
528 .. "(<pos1> and <pos2> must be in parentheses)",
529 privs = {server = true},
530 func = function(name, param)
531 local p1, p2 = parse_range_str(name, param)
536 if core.fix_light(p1, p2) then
537 return true, "Successfully reset light in the area ranging from " ..
538 core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
540 return false, "Failed to load one or more blocks in area"
545 core.register_chatcommand("mods", {
547 description = "List mods installed on the server",
549 func = function(name, param)
550 return true, table.concat(core.get_modnames(), ", ")
554 local function handle_give_command(cmd, giver, receiver, stackstring)
555 core.log("action", giver .. " invoked " .. cmd
556 .. ', stackstring="' .. stackstring .. '"')
557 local itemstack = ItemStack(stackstring)
558 if itemstack:is_empty() then
559 return false, "Cannot give an empty item"
560 elseif (not itemstack:is_known()) or (itemstack:get_name() == "unknown") then
561 return false, "Cannot give an unknown item"
562 -- Forbid giving 'ignore' due to unwanted side effects
563 elseif itemstack:get_name() == "ignore" then
564 return false, "Giving 'ignore' is not allowed"
566 local receiverref = core.get_player_by_name(receiver)
567 if receiverref == nil then
568 return false, receiver .. " is not a known player"
570 local leftover = receiverref:get_inventory():add_item("main", itemstack)
572 if leftover:is_empty() then
574 elseif leftover:get_count() == itemstack:get_count() then
575 partiality = "could not be "
577 partiality = "partially "
579 -- The actual item stack string may be different from what the "giver"
580 -- entered (e.g. big numbers are always interpreted as 2^16-1).
581 stackstring = itemstack:to_string()
582 if giver == receiver then
583 local msg = "%q %sadded to inventory."
584 return true, msg:format(stackstring, partiality)
586 core.chat_send_player(receiver, ("%q %sadded to inventory.")
587 :format(stackstring, partiality))
588 local msg = "%q %sadded to %s's inventory."
589 return true, msg:format(stackstring, partiality, receiver)
593 core.register_chatcommand("give", {
594 params = "<name> <ItemString> [<count> [<wear>]]",
595 description = "Give item to player",
597 func = function(name, param)
598 local toname, itemstring = string.match(param, "^([^ ]+) +(.+)$")
599 if not toname or not itemstring then
600 return false, "Name and ItemString required"
602 return handle_give_command("/give", name, toname, itemstring)
606 core.register_chatcommand("giveme", {
607 params = "<ItemString> [<count> [<wear>]]",
608 description = "Give item to yourself",
610 func = function(name, param)
611 local itemstring = string.match(param, "(.+)$")
612 if not itemstring then
613 return false, "ItemString required"
615 return handle_give_command("/giveme", name, name, itemstring)
619 core.register_chatcommand("spawnentity", {
620 params = "<EntityName> [<X>,<Y>,<Z>]",
621 description = "Spawn entity at given (or your) position",
622 privs = {give=true, interact=true},
623 func = function(name, param)
624 local entityname, p = string.match(param, "^([^ ]+) *(.*)$")
625 if not entityname then
626 return false, "EntityName required"
628 core.log("action", ("%s invokes /spawnentity, entityname=%q")
629 :format(name, entityname))
630 local player = core.get_player_by_name(name)
631 if player == nil then
632 core.log("error", "Unable to spawn entity, player is nil")
633 return false, "Unable to spawn entity, player is nil"
635 if not core.registered_entities[entityname] then
636 return false, "Cannot spawn an unknown entity"
641 p = core.string_to_pos(p)
643 return false, "Invalid parameters ('" .. param .. "')"
647 core.add_entity(p, entityname)
648 return true, ("%q spawned."):format(entityname)
652 core.register_chatcommand("pulverize", {
654 description = "Destroy item in hand",
655 func = function(name, param)
656 local player = core.get_player_by_name(name)
658 core.log("error", "Unable to pulverize, no player.")
659 return false, "Unable to pulverize, no player."
661 if player:get_wielded_item():is_empty() then
662 return false, "Unable to pulverize, no item in hand."
664 player:set_wielded_item(nil)
665 return true, "An item was pulverized."
670 core.rollback_punch_callbacks = {}
672 core.register_on_punchnode(function(pos, node, puncher)
673 local name = puncher and puncher:get_player_name()
674 if name and core.rollback_punch_callbacks[name] then
675 core.rollback_punch_callbacks[name](pos, node, puncher)
676 core.rollback_punch_callbacks[name] = nil
680 core.register_chatcommand("rollback_check", {
681 params = "[<range>] [<seconds>] [<limit>]",
682 description = "Check who last touched a node or a node near it"
683 .. " within the time specified by <seconds>. Default: range = 0,"
684 .. " seconds = 86400 = 24h, limit = 5",
685 privs = {rollback=true},
686 func = function(name, param)
687 if not core.settings:get_bool("enable_rollback_recording") then
688 return false, "Rollback functions are disabled."
690 local range, seconds, limit =
691 param:match("(%d+) *(%d*) *(%d*)")
692 range = tonumber(range) or 0
693 seconds = tonumber(seconds) or 86400
694 limit = tonumber(limit) or 5
696 return false, "That limit is too high!"
699 core.rollback_punch_callbacks[name] = function(pos, node, puncher)
700 local name = puncher:get_player_name()
701 core.chat_send_player(name, "Checking " .. core.pos_to_string(pos) .. "...")
702 local actions = core.rollback_get_node_actions(pos, range, seconds, limit)
704 core.chat_send_player(name, "Rollback functions are disabled")
707 local num_actions = #actions
708 if num_actions == 0 then
709 core.chat_send_player(name, "Nobody has touched"
710 .. " the specified location in "
711 .. seconds .. " seconds")
714 local time = os.time()
715 for i = num_actions, 1, -1 do
716 local action = actions[i]
717 core.chat_send_player(name,
718 ("%s %s %s -> %s %d seconds ago.")
720 core.pos_to_string(action.pos),
728 return true, "Punch a node (range=" .. range .. ", seconds="
729 .. seconds .. "s, limit=" .. limit .. ")"
733 core.register_chatcommand("rollback", {
734 params = "(<name> [<seconds>]) | (:<actor> [<seconds>])",
735 description = "Revert actions of a player. Default for <seconds> is 60",
736 privs = {rollback=true},
737 func = function(name, param)
738 if not core.settings:get_bool("enable_rollback_recording") then
739 return false, "Rollback functions are disabled."
741 local target_name, seconds = string.match(param, ":([^ ]+) *(%d*)")
742 if not target_name then
743 local player_name = nil
744 player_name, seconds = string.match(param, "([^ ]+) *(%d*)")
745 if not player_name then
746 return false, "Invalid parameters. See /help rollback"
747 .. " and /help rollback_check."
749 target_name = "player:"..player_name
751 seconds = tonumber(seconds) or 60
752 core.chat_send_player(name, "Reverting actions of "
753 .. target_name .. " since "
754 .. seconds .. " seconds.")
755 local success, log = core.rollback_revert_actions_by(
756 target_name, seconds)
759 response = "(log is too long to show)\n"
761 for _, line in pairs(log) do
762 response = response .. line .. "\n"
765 response = response .. "Reverting actions "
766 .. (success and "succeeded." or "FAILED.")
767 return success, response
771 core.register_chatcommand("status", {
772 description = "Show server status",
773 func = function(name, param)
774 return true, core.get_server_status()
778 core.register_chatcommand("time", {
779 params = "[<0..23>:<0..59> | <0..24000>]",
780 description = "Show or set time of day",
782 func = function(name, param)
784 local current_time = math.floor(core.get_timeofday() * 1440)
785 local minutes = current_time % 60
786 local hour = (current_time - minutes) / 60
787 return true, ("Current time is %d:%02d"):format(hour, minutes)
789 local player_privs = core.get_player_privs(name)
790 if not player_privs.settime then
791 return false, "You don't have permission to run this command " ..
792 "(missing privilege: settime)."
794 local hour, minute = param:match("^(%d+):(%d+)$")
796 local new_time = tonumber(param)
798 return false, "Invalid time."
800 -- Backward compatibility.
801 core.set_timeofday((new_time % 24000) / 24000)
802 core.log("action", name .. " sets time to " .. new_time)
803 return true, "Time of day changed."
805 hour = tonumber(hour)
806 minute = tonumber(minute)
807 if hour < 0 or hour > 23 then
808 return false, "Invalid hour (must be between 0 and 23 inclusive)."
809 elseif minute < 0 or minute > 59 then
810 return false, "Invalid minute (must be between 0 and 59 inclusive)."
812 core.set_timeofday((hour * 60 + minute) / 1440)
813 core.log("action", ("%s sets time to %d:%02d"):format(name, hour, minute))
814 return true, "Time of day changed."
818 core.register_chatcommand("days", {
819 description = "Show day count since world creation",
820 func = function(name, param)
821 return true, "Current day is " .. core.get_day_count()
825 core.register_chatcommand("shutdown", {
826 params = "[<delay_in_seconds> | -1] [reconnect] [<message>]",
827 description = "Shutdown server (-1 cancels a delayed shutdown)",
828 privs = {server=true},
829 func = function(name, param)
830 local delay, reconnect, message
831 delay, param = param:match("^%s*(%S+)(.*)")
833 reconnect, param = param:match("^%s*(%S+)(.*)")
835 message = param and param:match("^%s*(.+)") or ""
836 delay = tonumber(delay) or 0
839 core.log("action", name .. " shuts down server")
840 core.chat_send_all("*** Server shutting down (operator request).")
842 core.request_shutdown(message:trim(), core.is_yes(reconnect), delay)
846 core.register_chatcommand("ban", {
847 params = "[<name> | <IP_address>]",
848 description = "Ban player or show ban list",
850 func = function(name, param)
852 local ban_list = core.get_ban_list()
853 if ban_list == "" then
854 return true, "The ban list is empty."
856 return true, "Ban list: " .. ban_list
859 if not core.get_player_by_name(param) then
860 return false, "No such player."
862 if not core.ban_player(param) then
863 return false, "Failed to ban player."
865 local desc = core.get_ban_description(param)
866 core.log("action", name .. " bans " .. desc .. ".")
867 return true, "Banned " .. desc .. "."
871 core.register_chatcommand("unban", {
872 params = "<name> | <IP_address>",
873 description = "Remove player ban",
875 func = function(name, param)
876 if not core.unban_player_or_ip(param) then
877 return false, "Failed to unban player/IP."
879 core.log("action", name .. " unbans " .. param)
880 return true, "Unbanned " .. param
884 core.register_chatcommand("kick", {
885 params = "<name> [<reason>]",
886 description = "Kick a player",
888 func = function(name, param)
889 local tokick, reason = param:match("([^ ]+) (.+)")
890 tokick = tokick or param
891 if not core.kick_player(tokick, reason) then
892 return false, "Failed to kick player " .. tokick
894 local log_reason = ""
896 log_reason = " with reason \"" .. reason .. "\""
898 core.log("action", name .. " kicks " .. tokick .. log_reason)
899 return true, "Kicked " .. tokick
903 core.register_chatcommand("clearobjects", {
904 params = "[full | quick]",
905 description = "Clear all objects in world",
906 privs = {server=true},
907 func = function(name, param)
909 if param == "" or param == "quick" then
910 options.mode = "quick"
911 elseif param == "full" then
912 options.mode = "full"
914 return false, "Invalid usage, see /help clearobjects."
917 core.log("action", name .. " clears all objects ("
918 .. options.mode .. " mode).")
919 core.chat_send_all("Clearing all objects. This may take long."
920 .. " You may experience a timeout. (by "
922 core.clear_objects(options)
923 core.log("action", "Object clearing done.")
924 core.chat_send_all("*** Cleared all objects.")
928 core.register_chatcommand("msg", {
929 params = "<name> <message>",
930 description = "Send a private message",
931 privs = {shout=true},
932 func = function(name, param)
933 local sendto, message = param:match("^(%S+)%s(.+)$")
935 return false, "Invalid usage, see /help msg."
937 if not core.get_player_by_name(sendto) then
938 return false, "The player " .. sendto
941 core.log("action", "PM from " .. name .. " to " .. sendto
943 core.chat_send_player(sendto, "PM from " .. name .. ": "
945 return true, "Message sent."
949 core.register_chatcommand("last-login", {
951 description = "Get the last login time of a player or yourself",
952 func = function(name, param)
956 local pauth = core.get_auth_handler().get_auth(param)
957 if pauth and pauth.last_login then
958 -- Time in UTC, ISO 8601 format
959 return true, "Last login time was " ..
960 os.date("!%Y-%m-%dT%H:%M:%SZ", pauth.last_login)
962 return false, "Last login time is unknown"
966 core.register_chatcommand("clearinv", {
968 description = "Clear the inventory of yourself or another player",
969 func = function(name, param)
971 if param and param ~= "" and param ~= name then
972 if not core.check_player_privs(name, {server=true}) then
973 return false, "You don't have permission"
974 .. " to clear another player's inventory (missing privilege: server)"
976 player = core.get_player_by_name(param)
977 core.chat_send_player(param, name.." cleared your inventory.")
979 player = core.get_player_by_name(name)
983 player:get_inventory():set_list("main", {})
984 player:get_inventory():set_list("craft", {})
985 player:get_inventory():set_list("craftpreview", {})
986 core.log("action", name.." clears "..player:get_player_name().."'s inventory")
987 return true, "Cleared "..player:get_player_name().."'s inventory."
989 return false, "Player must be online to clear inventory!"
994 local function handle_kill_command(killer, victim)
995 if core.settings:get_bool("enable_damage") == false then
996 return false, "Players can't be killed, damage has been disabled."
998 local victimref = core.get_player_by_name(victim)
999 if victimref == nil then
1000 return false, string.format("Player %s is not online.", victim)
1001 elseif victimref:get_hp() <= 0 then
1002 if killer == victim then
1003 return false, "You are already dead."
1005 return false, string.format("%s is already dead.", victim)
1008 if not killer == victim then
1009 core.log("action", string.format("%s killed %s", killer, victim))
1013 return true, string.format("%s has been killed.", victim)
1016 core.register_chatcommand("kill", {
1017 params = "[<name>]",
1018 description = "Kill player or yourself",
1019 privs = {server=true},
1020 func = function(name, param)
1021 return handle_kill_command(name, param == "" and name or param)