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() then
561 return false, "Cannot give an unknown item"
563 local receiverref = core.get_player_by_name(receiver)
564 if receiverref == nil then
565 return false, receiver .. " is not a known player"
567 local leftover = receiverref:get_inventory():add_item("main", itemstack)
569 if leftover:is_empty() then
571 elseif leftover:get_count() == itemstack:get_count() then
572 partiality = "could not be "
574 partiality = "partially "
576 -- The actual item stack string may be different from what the "giver"
577 -- entered (e.g. big numbers are always interpreted as 2^16-1).
578 stackstring = itemstack:to_string()
579 if giver == receiver then
580 return true, ("%q %sadded to inventory.")
581 :format(stackstring, partiality)
583 core.chat_send_player(receiver, ("%q %sadded to inventory.")
584 :format(stackstring, partiality))
585 return true, ("%q %sadded to %s's inventory.")
586 :format(stackstring, partiality, receiver)
590 core.register_chatcommand("give", {
591 params = "<name> <ItemString> [<count> [<wear>]]",
592 description = "Give item to player",
594 func = function(name, param)
595 local toname, itemstring = string.match(param, "^([^ ]+) +(.+)$")
596 if not toname or not itemstring then
597 return false, "Name and ItemString required"
599 return handle_give_command("/give", name, toname, itemstring)
603 core.register_chatcommand("giveme", {
604 params = "<ItemString> [<count> [<wear>]]",
605 description = "Give item to yourself",
607 func = function(name, param)
608 local itemstring = string.match(param, "(.+)$")
609 if not itemstring then
610 return false, "ItemString required"
612 return handle_give_command("/giveme", name, name, itemstring)
616 core.register_chatcommand("spawnentity", {
617 params = "<EntityName> [<X>,<Y>,<Z>]",
618 description = "Spawn entity at given (or your) position",
619 privs = {give=true, interact=true},
620 func = function(name, param)
621 local entityname, p = string.match(param, "^([^ ]+) *(.*)$")
622 if not entityname then
623 return false, "EntityName required"
625 core.log("action", ("%s invokes /spawnentity, entityname=%q")
626 :format(name, entityname))
627 local player = core.get_player_by_name(name)
628 if player == nil then
629 core.log("error", "Unable to spawn entity, player is nil")
630 return false, "Unable to spawn entity, player is nil"
632 if not core.registered_entities[entityname] then
633 return false, "Cannot spawn an unknown entity"
638 p = core.string_to_pos(p)
640 return false, "Invalid parameters ('" .. param .. "')"
644 core.add_entity(p, entityname)
645 return true, ("%q spawned."):format(entityname)
649 core.register_chatcommand("pulverize", {
651 description = "Destroy item in hand",
652 func = function(name, param)
653 local player = core.get_player_by_name(name)
655 core.log("error", "Unable to pulverize, no player.")
656 return false, "Unable to pulverize, no player."
658 if player:get_wielded_item():is_empty() then
659 return false, "Unable to pulverize, no item in hand."
661 player:set_wielded_item(nil)
662 return true, "An item was pulverized."
667 core.rollback_punch_callbacks = {}
669 core.register_on_punchnode(function(pos, node, puncher)
670 local name = puncher and puncher:get_player_name()
671 if name and core.rollback_punch_callbacks[name] then
672 core.rollback_punch_callbacks[name](pos, node, puncher)
673 core.rollback_punch_callbacks[name] = nil
677 core.register_chatcommand("rollback_check", {
678 params = "[<range>] [<seconds>] [<limit>]",
679 description = "Check who last touched a node or a node near it"
680 .. " within the time specified by <seconds>. Default: range = 0,"
681 .. " seconds = 86400 = 24h, limit = 5",
682 privs = {rollback=true},
683 func = function(name, param)
684 if not core.settings:get_bool("enable_rollback_recording") then
685 return false, "Rollback functions are disabled."
687 local range, seconds, limit =
688 param:match("(%d+) *(%d*) *(%d*)")
689 range = tonumber(range) or 0
690 seconds = tonumber(seconds) or 86400
691 limit = tonumber(limit) or 5
693 return false, "That limit is too high!"
696 core.rollback_punch_callbacks[name] = function(pos, node, puncher)
697 local name = puncher:get_player_name()
698 core.chat_send_player(name, "Checking " .. core.pos_to_string(pos) .. "...")
699 local actions = core.rollback_get_node_actions(pos, range, seconds, limit)
701 core.chat_send_player(name, "Rollback functions are disabled")
704 local num_actions = #actions
705 if num_actions == 0 then
706 core.chat_send_player(name, "Nobody has touched"
707 .. " the specified location in "
708 .. seconds .. " seconds")
711 local time = os.time()
712 for i = num_actions, 1, -1 do
713 local action = actions[i]
714 core.chat_send_player(name,
715 ("%s %s %s -> %s %d seconds ago.")
717 core.pos_to_string(action.pos),
725 return true, "Punch a node (range=" .. range .. ", seconds="
726 .. seconds .. "s, limit=" .. limit .. ")"
730 core.register_chatcommand("rollback", {
731 params = "(<name> [<seconds>]) | (:<actor> [<seconds>])",
732 description = "Revert actions of a player. Default for <seconds> is 60",
733 privs = {rollback=true},
734 func = function(name, param)
735 if not core.settings:get_bool("enable_rollback_recording") then
736 return false, "Rollback functions are disabled."
738 local target_name, seconds = string.match(param, ":([^ ]+) *(%d*)")
739 if not target_name then
740 local player_name = nil
741 player_name, seconds = string.match(param, "([^ ]+) *(%d*)")
742 if not player_name then
743 return false, "Invalid parameters. See /help rollback"
744 .. " and /help rollback_check."
746 target_name = "player:"..player_name
748 seconds = tonumber(seconds) or 60
749 core.chat_send_player(name, "Reverting actions of "
750 .. target_name .. " since "
751 .. seconds .. " seconds.")
752 local success, log = core.rollback_revert_actions_by(
753 target_name, seconds)
756 response = "(log is too long to show)\n"
758 for _, line in pairs(log) do
759 response = response .. line .. "\n"
762 response = response .. "Reverting actions "
763 .. (success and "succeeded." or "FAILED.")
764 return success, response
768 core.register_chatcommand("status", {
769 description = "Show server status",
770 func = function(name, param)
771 return true, core.get_server_status()
775 core.register_chatcommand("time", {
776 params = "[<0..23>:<0..59> | <0..24000>]",
777 description = "Show or set time of day",
779 func = function(name, param)
781 local current_time = math.floor(core.get_timeofday() * 1440)
782 local minutes = current_time % 60
783 local hour = (current_time - minutes) / 60
784 return true, ("Current time is %d:%02d"):format(hour, minutes)
786 local player_privs = core.get_player_privs(name)
787 if not player_privs.settime then
788 return false, "You don't have permission to run this command " ..
789 "(missing privilege: settime)."
791 local hour, minute = param:match("^(%d+):(%d+)$")
793 local new_time = tonumber(param)
795 return false, "Invalid time."
797 -- Backward compatibility.
798 core.set_timeofday((new_time % 24000) / 24000)
799 core.log("action", name .. " sets time to " .. new_time)
800 return true, "Time of day changed."
802 hour = tonumber(hour)
803 minute = tonumber(minute)
804 if hour < 0 or hour > 23 then
805 return false, "Invalid hour (must be between 0 and 23 inclusive)."
806 elseif minute < 0 or minute > 59 then
807 return false, "Invalid minute (must be between 0 and 59 inclusive)."
809 core.set_timeofday((hour * 60 + minute) / 1440)
810 core.log("action", ("%s sets time to %d:%02d"):format(name, hour, minute))
811 return true, "Time of day changed."
815 core.register_chatcommand("days", {
816 description = "Show day count since world creation",
817 func = function(name, param)
818 return true, "Current day is " .. core.get_day_count()
822 core.register_chatcommand("shutdown", {
823 params = "[<delay_in_seconds> | -1] [reconnect] [<message>]",
824 description = "Shutdown server (-1 cancels a delayed shutdown)",
825 privs = {server=true},
826 func = function(name, param)
827 local delay, reconnect, message = param:match("([^ ][-]?[0-9]+)([^ ]+)(.*)")
828 message = message or ""
831 delay = tonumber(delay) or 0
834 core.log("action", name .. " shuts down server")
835 core.chat_send_all("*** Server shutting down (operator request).")
837 core.request_shutdown(message:trim(), core.is_yes(reconnect), delay)
841 core.register_chatcommand("ban", {
842 params = "[<name> | <IP_address>]",
843 description = "Ban player or show ban list",
845 func = function(name, param)
847 local ban_list = core.get_ban_list()
848 if ban_list == "" then
849 return true, "The ban list is empty."
851 return true, "Ban list: " .. ban_list
854 if not core.get_player_by_name(param) then
855 return false, "No such player."
857 if not core.ban_player(param) then
858 return false, "Failed to ban player."
860 local desc = core.get_ban_description(param)
861 core.log("action", name .. " bans " .. desc .. ".")
862 return true, "Banned " .. desc .. "."
866 core.register_chatcommand("unban", {
867 params = "<name> | <IP_address>",
868 description = "Remove player ban",
870 func = function(name, param)
871 if not core.unban_player_or_ip(param) then
872 return false, "Failed to unban player/IP."
874 core.log("action", name .. " unbans " .. param)
875 return true, "Unbanned " .. param
879 core.register_chatcommand("kick", {
880 params = "<name> [<reason>]",
881 description = "Kick a player",
883 func = function(name, param)
884 local tokick, reason = param:match("([^ ]+) (.+)")
885 tokick = tokick or param
886 if not core.kick_player(tokick, reason) then
887 return false, "Failed to kick player " .. tokick
889 local log_reason = ""
891 log_reason = " with reason \"" .. reason .. "\""
893 core.log("action", name .. " kicks " .. tokick .. log_reason)
894 return true, "Kicked " .. tokick
898 core.register_chatcommand("clearobjects", {
899 params = "[full | quick]",
900 description = "Clear all objects in world",
901 privs = {server=true},
902 func = function(name, param)
904 if param == "" or param == "quick" then
905 options.mode = "quick"
906 elseif param == "full" then
907 options.mode = "full"
909 return false, "Invalid usage, see /help clearobjects."
912 core.log("action", name .. " clears all objects ("
913 .. options.mode .. " mode).")
914 core.chat_send_all("Clearing all objects. This may take long."
915 .. " You may experience a timeout. (by "
917 core.clear_objects(options)
918 core.log("action", "Object clearing done.")
919 core.chat_send_all("*** Cleared all objects.")
923 core.register_chatcommand("msg", {
924 params = "<name> <message>",
925 description = "Send a private message",
926 privs = {shout=true},
927 func = function(name, param)
928 local sendto, message = param:match("^(%S+)%s(.+)$")
930 return false, "Invalid usage, see /help msg."
932 if not core.get_player_by_name(sendto) then
933 return false, "The player " .. sendto
936 core.log("action", "PM from " .. name .. " to " .. sendto
938 core.chat_send_player(sendto, "PM from " .. name .. ": "
940 return true, "Message sent."
944 core.register_chatcommand("last-login", {
946 description = "Get the last login time of a player or yourself",
947 func = function(name, param)
951 local pauth = core.get_auth_handler().get_auth(param)
952 if pauth and pauth.last_login then
953 -- Time in UTC, ISO 8601 format
954 return true, "Last login time was " ..
955 os.date("!%Y-%m-%dT%H:%M:%SZ", pauth.last_login)
957 return false, "Last login time is unknown"
961 core.register_chatcommand("clearinv", {
963 description = "Clear the inventory of yourself or another player",
964 func = function(name, param)
966 if param and param ~= "" and param ~= name then
967 if not core.check_player_privs(name, {server=true}) then
968 return false, "You don't have permission"
969 .. " to clear another player's inventory (missing privilege: server)"
971 player = core.get_player_by_name(param)
972 core.chat_send_player(param, name.." cleared your inventory.")
974 player = core.get_player_by_name(name)
978 player:get_inventory():set_list("main", {})
979 player:get_inventory():set_list("craft", {})
980 player:get_inventory():set_list("craftpreview", {})
981 core.log("action", name.." clears "..player:get_player_name().."'s inventory")
982 return true, "Cleared "..player:get_player_name().."'s inventory."
984 return false, "Player must be online to clear inventory!"
989 local function handle_kill_command(killer, victim)
990 if core.settings:get_bool("enable_damage") == false then
991 return false, "Players can't be killed, damage has been disabled."
993 local victimref = core.get_player_by_name(victim)
994 if victimref == nil then
995 return false, string.format("Player %s is not online.", victim)
996 elseif victimref:get_hp() <= 0 then
997 if killer == victim then
998 return false, "You are already dead."
1000 return false, string.format("%s is already dead.", victim)
1003 if not killer == victim then
1004 core.log("action", string.format("%s killed %s", killer, victim))
1008 return true, string.format("%s has been killed.", victim)
1011 core.register_chatcommand("kill", {
1012 params = "[<name>]",
1013 description = "Kill player or yourself",
1014 privs = {server=true},
1015 func = function(name, param)
1016 return handle_kill_command(name, param == "" and name or param)