1 -- Minetest: builtin/game/chat.lua
3 -- Helper function that implements search and replace without pattern matching
4 -- Returns the string and a boolean indicating whether or not the string was modified
5 local function safe_gsub(s, replace, with)
6 local i1, i2 = s:find(replace, 1, true)
11 return s:sub(1, i1 - 1) .. with .. s:sub(i2 + 1), true
15 -- Chat message formatter
18 -- Implemented in Lua to allow redefinition
19 function core.format_chat_message(name, message)
20 local error_str = "Invalid chat message format - missing %s"
21 local str = core.settings:get("chat_message_format")
25 str, replaced = safe_gsub(str, "@name", name)
27 error(error_str:format("@name"), 2)
31 str = safe_gsub(str, "@timestamp", os.date("%H:%M:%S", os.time()))
33 -- Insert the message into the string only after finishing all other processing
34 str, replaced = safe_gsub(str, "@message", message)
36 error(error_str:format("@message"), 2)
43 -- Chat command handler
46 core.chatcommands = core.registered_chatcommands -- BACKWARDS COMPATIBILITY
48 core.register_on_chat_message(function(name, message)
49 if message:sub(1,1) ~= "/" then
53 local cmd, param = string.match(message, "^/([^ ]+) *(.*)")
55 core.chat_send_player(name, "-!- Empty command")
61 local cmd_def = core.registered_chatcommands[cmd]
63 core.chat_send_player(name, "-!- Invalid command: " .. cmd)
66 local has_privs, missing_privs = core.check_player_privs(name, cmd_def.privs)
68 core.set_last_run_mod(cmd_def.mod_origin)
69 local success, result = cmd_def.func(name, param)
70 if success == false and result == nil then
71 core.chat_send_player(name, "-!- Invalid command usage")
72 local help_def = core.registered_chatcommands["help"]
74 local _, helpmsg = help_def.func(name, cmd)
76 core.chat_send_player(name, helpmsg)
80 core.chat_send_player(name, result)
83 core.chat_send_player(name, "You don't have permission"
84 .. " to run this command (missing privileges: "
85 .. table.concat(missing_privs, ", ") .. ")")
87 return true -- Handled chat message
90 if core.settings:get_bool("profiler.load") then
91 -- Run after register_chatcommand and its register_on_chat_message
92 -- Before any chatcommands that should be profiled
93 profiler.init_chatcommand()
96 -- Parses a "range" string in the format of "here (number)" or
97 -- "(x1, y1, z1) (x2, y2, z2)", returning two position vectors
98 local function parse_range_str(player_name, str)
100 local args = str:split(" ")
102 if args[1] == "here" then
103 p1, p2 = core.get_player_radius_area(player_name, tonumber(args[2]))
105 return false, "Unable to get player " .. player_name .. " position"
108 p1, p2 = core.string_to_area(str)
110 return false, "Incorrect area format. Expected: (x1,y1,z1) (x2,y2,z2)"
120 core.register_chatcommand("me", {
122 description = "Show chat action (e.g., '/me orders a pizza' displays"
123 .. " '<player name> orders a pizza')",
124 privs = {shout=true},
125 func = function(name, param)
126 core.chat_send_all("* " .. name .. " " .. param)
131 core.register_chatcommand("admin", {
132 description = "Show the name of the server owner",
133 func = function(name)
134 local admin = core.settings:get("name")
136 return true, "The administrator of this server is " .. admin .. "."
138 return false, "There's no administrator named in the config file."
143 core.register_chatcommand("privs", {
145 description = "Show privileges of yourself or another player",
146 func = function(caller, param)
148 local name = (param ~= "" and param or caller)
149 if not core.player_exists(name) then
150 return false, "Player " .. name .. " does not exist."
152 return true, "Privileges of " .. name .. ": "
153 .. core.privs_to_string(
154 core.get_player_privs(name), ", ")
158 core.register_chatcommand("haspriv", {
159 params = "<privilege>",
160 description = "Return list of all online players with privilege.",
161 privs = {basic_privs = true},
162 func = function(caller, param)
165 return false, "Invalid parameters (see /help haspriv)"
167 if not core.registered_privileges[param] then
168 return false, "Unknown privilege!"
170 local privs = core.string_to_privs(param)
171 local players_with_priv = {}
172 for _, player in pairs(core.get_connected_players()) do
173 local player_name = player:get_player_name()
174 if core.check_player_privs(player_name, privs) then
175 table.insert(players_with_priv, player_name)
178 return true, "Players online with the \"" .. param .. "\" privilege: " ..
179 table.concat(players_with_priv, ", ")
183 local function handle_grant_command(caller, grantname, grantprivstr)
184 local caller_privs = core.get_player_privs(caller)
185 if not (caller_privs.privs or caller_privs.basic_privs) then
186 return false, "Your privileges are insufficient."
189 if not core.get_auth_handler().get_auth(grantname) then
190 return false, "Player " .. grantname .. " does not exist."
192 local grantprivs = core.string_to_privs(grantprivstr)
193 if grantprivstr == "all" then
194 grantprivs = core.registered_privileges
196 local privs = core.get_player_privs(grantname)
197 local privs_unknown = ""
199 core.string_to_privs(core.settings:get("basic_privs") or "interact,shout")
200 for priv, _ in pairs(grantprivs) do
201 if not basic_privs[priv] and not caller_privs.privs then
202 return false, "Your privileges are insufficient."
204 if not core.registered_privileges[priv] then
205 privs_unknown = privs_unknown .. "Unknown privilege: " .. priv .. "\n"
209 if privs_unknown ~= "" then
210 return false, privs_unknown
212 for priv, _ in pairs(grantprivs) do
213 -- call the on_grant callbacks
214 core.run_priv_callbacks(grantname, priv, caller, "grant")
216 core.set_player_privs(grantname, privs)
217 core.log("action", caller..' granted ('..core.privs_to_string(grantprivs, ', ')..') privileges to '..grantname)
218 if grantname ~= caller then
219 core.chat_send_player(grantname, caller
220 .. " granted you privileges: "
221 .. core.privs_to_string(grantprivs, ' '))
223 return true, "Privileges of " .. grantname .. ": "
224 .. core.privs_to_string(
225 core.get_player_privs(grantname), ' ')
228 core.register_chatcommand("grant", {
229 params = "<name> (<privilege> | all)",
230 description = "Give privileges to player",
231 func = function(name, param)
232 local grantname, grantprivstr = string.match(param, "([^ ]+) (.+)")
233 if not grantname or not grantprivstr then
234 return false, "Invalid parameters (see /help grant)"
236 return handle_grant_command(name, grantname, grantprivstr)
240 core.register_chatcommand("grantme", {
241 params = "<privilege> | all",
242 description = "Grant privileges to yourself",
243 func = function(name, param)
245 return false, "Invalid parameters (see /help grantme)"
247 return handle_grant_command(name, name, param)
251 local function handle_revoke_command(caller, revokename, revokeprivstr)
252 local caller_privs = core.get_player_privs(caller)
253 if not (caller_privs.privs or caller_privs.basic_privs) then
254 return false, "Your privileges are insufficient."
257 if not core.get_auth_handler().get_auth(revokename) then
258 return false, "Player " .. revokename .. " does not exist."
261 local revokeprivs = core.string_to_privs(revokeprivstr)
262 local privs = core.get_player_privs(revokename)
264 core.string_to_privs(core.settings:get("basic_privs") or "interact,shout")
265 for priv, _ in pairs(revokeprivs) do
266 if not basic_privs[priv] and not caller_privs.privs then
267 return false, "Your privileges are insufficient."
271 if revokeprivstr == "all" then
275 for priv, _ in pairs(revokeprivs) do
280 for priv, _ in pairs(revokeprivs) do
281 -- call the on_revoke callbacks
282 core.run_priv_callbacks(revokename, priv, caller, "revoke")
285 core.set_player_privs(revokename, privs)
286 core.log("action", caller..' revoked ('
287 ..core.privs_to_string(revokeprivs, ', ')
288 ..') privileges from '..revokename)
289 if revokename ~= caller then
290 core.chat_send_player(revokename, caller
291 .. " revoked privileges from you: "
292 .. core.privs_to_string(revokeprivs, ' '))
294 return true, "Privileges of " .. revokename .. ": "
295 .. core.privs_to_string(
296 core.get_player_privs(revokename), ' ')
299 core.register_chatcommand("revoke", {
300 params = "<name> (<privilege> | all)",
301 description = "Remove privileges from player",
303 func = function(name, param)
304 local revokename, revokeprivstr = string.match(param, "([^ ]+) (.+)")
305 if not revokename or not revokeprivstr then
306 return false, "Invalid parameters (see /help revoke)"
308 return handle_revoke_command(name, revokename, revokeprivstr)
312 core.register_chatcommand("revokeme", {
313 params = "<privilege> | all",
314 description = "Revoke privileges from yourself",
316 func = function(name, param)
318 return false, "Invalid parameters (see /help revokeme)"
320 return handle_revoke_command(name, name, param)
324 core.register_chatcommand("setpassword", {
325 params = "<name> <password>",
326 description = "Set player's password",
327 privs = {password=true},
328 func = function(name, param)
329 local toname, raw_password = string.match(param, "^([^ ]+) +(.+)$")
331 toname = param:match("^([^ ]+) *$")
336 return false, "Name field required"
339 local act_str_past, act_str_pres
340 if not raw_password then
341 core.set_player_password(toname, "")
342 act_str_past = "cleared"
343 act_str_pres = "clears"
345 core.set_player_password(toname,
346 core.get_password_hash(toname,
349 act_str_pres = "sets"
352 if toname ~= name then
353 core.chat_send_player(toname, "Your password was "
354 .. act_str_past .. " by " .. name)
357 core.log("action", name .. " " .. act_str_pres ..
358 " password of " .. toname .. ".")
360 return true, "Password of player \"" .. toname .. "\" " .. act_str_past
364 core.register_chatcommand("clearpassword", {
366 description = "Set empty password for a player",
367 privs = {password=true},
368 func = function(name, param)
371 return false, "Name field required"
373 core.set_player_password(toname, '')
375 core.log("action", name .. " clears password of " .. toname .. ".")
377 return true, "Password of player \"" .. toname .. "\" cleared"
381 core.register_chatcommand("auth_reload", {
383 description = "Reload authentication data",
384 privs = {server=true},
385 func = function(name, param)
386 local done = core.auth_reload()
387 return done, (done and "Done." or "Failed.")
391 core.register_chatcommand("remove_player", {
393 description = "Remove a player's data",
394 privs = {server=true},
395 func = function(name, param)
398 return false, "Name field required"
401 local rc = core.remove_player(toname)
404 core.log("action", name .. " removed player data of " .. toname .. ".")
405 return true, "Player \"" .. toname .. "\" removed."
407 return true, "No such player \"" .. toname .. "\" to remove."
409 return true, "Player \"" .. toname .. "\" is connected, cannot remove."
412 return false, "Unhandled remove_player return code " .. rc .. ""
416 core.register_chatcommand("teleport", {
417 params = "<X>,<Y>,<Z> | <to_name> | (<name> <X>,<Y>,<Z>) | (<name> <to_name>)",
418 description = "Teleport to position or player",
419 privs = {teleport=true},
420 func = function(name, param)
421 -- Returns (pos, true) if found, otherwise (pos, false)
422 local function find_free_position_near(pos)
429 for _, d in ipairs(tries) do
430 local p = {x = pos.x+d.x, y = pos.y+d.y, z = pos.z+d.z}
431 local n = core.get_node_or_nil(p)
433 local def = core.registered_nodes[n.name]
434 if def and not def.walkable then
443 p.x, p.y, p.z = string.match(param, "^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
447 if p.x and p.y and p.z then
450 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
451 return false, "Cannot teleport out of map bounds!"
453 local teleportee = core.get_player_by_name(name)
455 if teleportee:get_attach() then
456 return false, "Can't teleport, you're attached to an object!"
458 teleportee:set_pos(p)
459 return true, "Teleporting to "..core.pos_to_string(p)
463 local target_name = param:match("^([^ ]+)$")
464 local teleportee = core.get_player_by_name(name)
468 local target = core.get_player_by_name(target_name)
474 if teleportee and p then
475 if teleportee:get_attach() then
476 return false, "Can't teleport, you're attached to an object!"
478 p = find_free_position_near(p)
479 teleportee:set_pos(p)
480 return true, "Teleporting to " .. target_name
481 .. " at "..core.pos_to_string(p)
484 if not core.check_player_privs(name, {bring=true}) then
485 return false, "You don't have permission to teleport other players (missing bring privilege)"
490 local teleportee_name
491 teleportee_name, p.x, p.y, p.z = param:match(
492 "^([^ ]+) +([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
493 p.x, p.y, p.z = tonumber(p.x), tonumber(p.y), tonumber(p.z)
494 if teleportee_name then
495 teleportee = core.get_player_by_name(teleportee_name)
497 if teleportee and p.x and p.y and p.z then
498 if teleportee:get_attach() then
499 return false, "Can't teleport, player is attached to an object!"
501 teleportee:set_pos(p)
502 return true, "Teleporting " .. teleportee_name
503 .. " to " .. core.pos_to_string(p)
508 teleportee_name, target_name = string.match(param, "^([^ ]+) +([^ ]+)$")
509 if teleportee_name then
510 teleportee = core.get_player_by_name(teleportee_name)
513 local target = core.get_player_by_name(target_name)
518 if teleportee and p then
519 if teleportee:get_attach() then
520 return false, "Can't teleport, player is attached to an object!"
522 p = find_free_position_near(p)
523 teleportee:set_pos(p)
524 return true, "Teleporting " .. teleportee_name
525 .. " to " .. target_name
526 .. " at " .. core.pos_to_string(p)
529 return false, 'Invalid parameters ("' .. param
530 .. '") or player not found (see /help teleport)'
534 core.register_chatcommand("set", {
535 params = "([-n] <name> <value>) | <name>",
536 description = "Set or read server configuration setting",
537 privs = {server=true},
538 func = function(name, param)
539 local arg, setname, setvalue = string.match(param, "(-[n]) ([^ ]+) (.+)")
540 if arg and arg == "-n" and setname and setvalue then
541 core.settings:set(setname, setvalue)
542 return true, setname .. " = " .. setvalue
545 setname, setvalue = string.match(param, "([^ ]+) (.+)")
546 if setname and setvalue then
547 if not core.settings:get(setname) then
548 return false, "Failed. Use '/set -n <name> <value>' to create a new setting."
550 core.settings:set(setname, setvalue)
551 return true, setname .. " = " .. setvalue
554 setname = string.match(param, "([^ ]+)")
556 setvalue = core.settings:get(setname)
558 setvalue = "<not set>"
560 return true, setname .. " = " .. setvalue
563 return false, "Invalid parameters (see /help set)."
567 local function emergeblocks_callback(pos, action, num_calls_remaining, ctx)
568 if ctx.total_blocks == 0 then
569 ctx.total_blocks = num_calls_remaining + 1
570 ctx.current_blocks = 0
572 ctx.current_blocks = ctx.current_blocks + 1
574 if ctx.current_blocks == ctx.total_blocks then
575 core.chat_send_player(ctx.requestor_name,
576 string.format("Finished emerging %d blocks in %.2fms.",
577 ctx.total_blocks, (os.clock() - ctx.start_time) * 1000))
581 local function emergeblocks_progress_update(ctx)
582 if ctx.current_blocks ~= ctx.total_blocks then
583 core.chat_send_player(ctx.requestor_name,
584 string.format("emergeblocks update: %d/%d blocks emerged (%.1f%%)",
585 ctx.current_blocks, ctx.total_blocks,
586 (ctx.current_blocks / ctx.total_blocks) * 100))
588 core.after(2, emergeblocks_progress_update, ctx)
592 core.register_chatcommand("emergeblocks", {
593 params = "(here [<radius>]) | (<pos1> <pos2>)",
594 description = "Load (or, if nonexistent, generate) map blocks "
595 .. "contained in area pos1 to pos2 (<pos1> and <pos2> must be in parentheses)",
596 privs = {server=true},
597 func = function(name, param)
598 local p1, p2 = parse_range_str(name, param)
606 start_time = os.clock(),
607 requestor_name = name
610 core.emerge_area(p1, p2, emergeblocks_callback, context)
611 core.after(2, emergeblocks_progress_update, context)
613 return true, "Started emerge of area ranging from " ..
614 core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
618 core.register_chatcommand("deleteblocks", {
619 params = "(here [<radius>]) | (<pos1> <pos2>)",
620 description = "Delete map blocks contained in area pos1 to pos2 "
621 .. "(<pos1> and <pos2> must be in parentheses)",
622 privs = {server=true},
623 func = function(name, param)
624 local p1, p2 = parse_range_str(name, param)
629 if core.delete_area(p1, p2) then
630 return true, "Successfully cleared area ranging from " ..
631 core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
633 return false, "Failed to clear one or more blocks in area"
638 core.register_chatcommand("fixlight", {
639 params = "(here [<radius>]) | (<pos1> <pos2>)",
640 description = "Resets lighting in the area between pos1 and pos2 "
641 .. "(<pos1> and <pos2> must be in parentheses)",
642 privs = {server = true},
643 func = function(name, param)
644 local p1, p2 = parse_range_str(name, param)
649 if core.fix_light(p1, p2) then
650 return true, "Successfully reset light in the area ranging from " ..
651 core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
653 return false, "Failed to load one or more blocks in area"
658 core.register_chatcommand("mods", {
660 description = "List mods installed on the server",
662 func = function(name, param)
663 return true, table.concat(core.get_modnames(), ", ")
667 local function handle_give_command(cmd, giver, receiver, stackstring)
668 core.log("action", giver .. " invoked " .. cmd
669 .. ', stackstring="' .. stackstring .. '"')
670 local itemstack = ItemStack(stackstring)
671 if itemstack:is_empty() then
672 return false, "Cannot give an empty item"
673 elseif (not itemstack:is_known()) or (itemstack:get_name() == "unknown") then
674 return false, "Cannot give an unknown item"
675 -- Forbid giving 'ignore' due to unwanted side effects
676 elseif itemstack:get_name() == "ignore" then
677 return false, "Giving 'ignore' is not allowed"
679 local receiverref = core.get_player_by_name(receiver)
680 if receiverref == nil then
681 return false, receiver .. " is not a known player"
683 local leftover = receiverref:get_inventory():add_item("main", itemstack)
685 if leftover:is_empty() then
687 elseif leftover:get_count() == itemstack:get_count() then
688 partiality = "could not be "
690 partiality = "partially "
692 -- The actual item stack string may be different from what the "giver"
693 -- entered (e.g. big numbers are always interpreted as 2^16-1).
694 stackstring = itemstack:to_string()
695 if giver == receiver then
696 local msg = "%q %sadded to inventory."
697 return true, msg:format(stackstring, partiality)
699 core.chat_send_player(receiver, ("%q %sadded to inventory.")
700 :format(stackstring, partiality))
701 local msg = "%q %sadded to %s's inventory."
702 return true, msg:format(stackstring, partiality, receiver)
706 core.register_chatcommand("give", {
707 params = "<name> <ItemString> [<count> [<wear>]]",
708 description = "Give item to player",
710 func = function(name, param)
711 local toname, itemstring = string.match(param, "^([^ ]+) +(.+)$")
712 if not toname or not itemstring then
713 return false, "Name and ItemString required"
715 return handle_give_command("/give", name, toname, itemstring)
719 core.register_chatcommand("giveme", {
720 params = "<ItemString> [<count> [<wear>]]",
721 description = "Give item to yourself",
723 func = function(name, param)
724 local itemstring = string.match(param, "(.+)$")
725 if not itemstring then
726 return false, "ItemString required"
728 return handle_give_command("/giveme", name, name, itemstring)
732 core.register_chatcommand("spawnentity", {
733 params = "<EntityName> [<X>,<Y>,<Z>]",
734 description = "Spawn entity at given (or your) position",
735 privs = {give=true, interact=true},
736 func = function(name, param)
737 local entityname, p = string.match(param, "^([^ ]+) *(.*)$")
738 if not entityname then
739 return false, "EntityName required"
741 core.log("action", ("%s invokes /spawnentity, entityname=%q")
742 :format(name, entityname))
743 local player = core.get_player_by_name(name)
744 if player == nil then
745 core.log("error", "Unable to spawn entity, player is nil")
746 return false, "Unable to spawn entity, player is nil"
748 if not core.registered_entities[entityname] then
749 return false, "Cannot spawn an unknown entity"
754 p = core.string_to_pos(p)
756 return false, "Invalid parameters ('" .. param .. "')"
760 local obj = core.add_entity(p, entityname)
761 local msg = obj and "%q spawned." or "%q failed to spawn."
762 return true, msg:format(entityname)
766 core.register_chatcommand("pulverize", {
768 description = "Destroy item in hand",
769 func = function(name, param)
770 local player = core.get_player_by_name(name)
772 core.log("error", "Unable to pulverize, no player.")
773 return false, "Unable to pulverize, no player."
775 local wielded_item = player:get_wielded_item()
776 if wielded_item:is_empty() then
777 return false, "Unable to pulverize, no item in hand."
779 core.log("action", name .. " pulverized \"" ..
780 wielded_item:get_name() .. " " .. wielded_item:get_count() .. "\"")
781 player:set_wielded_item(nil)
782 return true, "An item was pulverized."
787 core.rollback_punch_callbacks = {}
789 core.register_on_punchnode(function(pos, node, puncher)
790 local name = puncher and puncher:get_player_name()
791 if name and core.rollback_punch_callbacks[name] then
792 core.rollback_punch_callbacks[name](pos, node, puncher)
793 core.rollback_punch_callbacks[name] = nil
797 core.register_chatcommand("rollback_check", {
798 params = "[<range>] [<seconds>] [<limit>]",
799 description = "Check who last touched a node or a node near it"
800 .. " within the time specified by <seconds>. Default: range = 0,"
801 .. " seconds = 86400 = 24h, limit = 5. Set <seconds> to inf for no time limit",
802 privs = {rollback=true},
803 func = function(name, param)
804 if not core.settings:get_bool("enable_rollback_recording") then
805 return false, "Rollback functions are disabled."
807 local range, seconds, limit =
808 param:match("(%d+) *(%d*) *(%d*)")
809 range = tonumber(range) or 0
810 seconds = tonumber(seconds) or 86400
811 limit = tonumber(limit) or 5
813 return false, "That limit is too high!"
816 core.rollback_punch_callbacks[name] = function(pos, node, puncher)
817 local name = puncher:get_player_name()
818 core.chat_send_player(name, "Checking " .. core.pos_to_string(pos) .. "...")
819 local actions = core.rollback_get_node_actions(pos, range, seconds, limit)
821 core.chat_send_player(name, "Rollback functions are disabled")
824 local num_actions = #actions
825 if num_actions == 0 then
826 core.chat_send_player(name, "Nobody has touched"
827 .. " the specified location in "
828 .. seconds .. " seconds")
831 local time = os.time()
832 for i = num_actions, 1, -1 do
833 local action = actions[i]
834 core.chat_send_player(name,
835 ("%s %s %s -> %s %d seconds ago.")
837 core.pos_to_string(action.pos),
845 return true, "Punch a node (range=" .. range .. ", seconds="
846 .. seconds .. "s, limit=" .. limit .. ")"
850 core.register_chatcommand("rollback", {
851 params = "(<name> [<seconds>]) | (:<actor> [<seconds>])",
852 description = "Revert actions of a player. Default for <seconds> is 60. Set <seconds> to inf for no time limit",
853 privs = {rollback=true},
854 func = function(name, param)
855 if not core.settings:get_bool("enable_rollback_recording") then
856 return false, "Rollback functions are disabled."
858 local target_name, seconds = string.match(param, ":([^ ]+) *(%d*)")
859 if not target_name then
861 player_name, seconds = string.match(param, "([^ ]+) *(%d*)")
862 if not player_name then
863 return false, "Invalid parameters. See /help rollback"
864 .. " and /help rollback_check."
866 target_name = "player:"..player_name
868 seconds = tonumber(seconds) or 60
869 core.chat_send_player(name, "Reverting actions of "
870 .. target_name .. " since "
871 .. seconds .. " seconds.")
872 local success, log = core.rollback_revert_actions_by(
873 target_name, seconds)
876 response = "(log is too long to show)\n"
878 for _, line in pairs(log) do
879 response = response .. line .. "\n"
882 response = response .. "Reverting actions "
883 .. (success and "succeeded." or "FAILED.")
884 return success, response
888 core.register_chatcommand("status", {
889 description = "Show server status",
890 func = function(name, param)
891 local status = core.get_server_status(name, false)
892 if status and status ~= "" then
895 return false, "This command was disabled by a mod or game"
899 core.register_chatcommand("time", {
900 params = "[<0..23>:<0..59> | <0..24000>]",
901 description = "Show or set time of day",
903 func = function(name, param)
905 local current_time = math.floor(core.get_timeofday() * 1440)
906 local minutes = current_time % 60
907 local hour = (current_time - minutes) / 60
908 return true, ("Current time is %d:%02d"):format(hour, minutes)
910 local player_privs = core.get_player_privs(name)
911 if not player_privs.settime then
912 return false, "You don't have permission to run this command " ..
913 "(missing privilege: settime)."
915 local hour, minute = param:match("^(%d+):(%d+)$")
917 local new_time = tonumber(param)
919 return false, "Invalid time."
921 -- Backward compatibility.
922 core.set_timeofday((new_time % 24000) / 24000)
923 core.log("action", name .. " sets time to " .. new_time)
924 return true, "Time of day changed."
926 hour = tonumber(hour)
927 minute = tonumber(minute)
928 if hour < 0 or hour > 23 then
929 return false, "Invalid hour (must be between 0 and 23 inclusive)."
930 elseif minute < 0 or minute > 59 then
931 return false, "Invalid minute (must be between 0 and 59 inclusive)."
933 core.set_timeofday((hour * 60 + minute) / 1440)
934 core.log("action", ("%s sets time to %d:%02d"):format(name, hour, minute))
935 return true, "Time of day changed."
939 core.register_chatcommand("days", {
940 description = "Show day count since world creation",
941 func = function(name, param)
942 return true, "Current day is " .. core.get_day_count()
946 core.register_chatcommand("shutdown", {
947 params = "[<delay_in_seconds> | -1] [reconnect] [<message>]",
948 description = "Shutdown server (-1 cancels a delayed shutdown)",
949 privs = {server=true},
950 func = function(name, param)
951 local delay, reconnect, message
952 delay, param = param:match("^%s*(%S+)(.*)")
954 reconnect, param = param:match("^%s*(%S+)(.*)")
956 message = param and param:match("^%s*(.+)") or ""
957 delay = tonumber(delay) or 0
960 core.log("action", name .. " shuts down server")
961 core.chat_send_all("*** Server shutting down (operator request).")
963 core.request_shutdown(message:trim(), core.is_yes(reconnect), delay)
968 core.register_chatcommand("ban", {
970 description = "Ban the IP of a player or show the ban list",
972 func = function(name, param)
974 local ban_list = core.get_ban_list()
975 if ban_list == "" then
976 return true, "The ban list is empty."
978 return true, "Ban list: " .. ban_list
981 if not core.get_player_by_name(param) then
982 return false, "Player is not online."
984 if not core.ban_player(param) then
985 return false, "Failed to ban player."
987 local desc = core.get_ban_description(param)
988 core.log("action", name .. " bans " .. desc .. ".")
989 return true, "Banned " .. desc .. "."
993 core.register_chatcommand("unban", {
994 params = "<name> | <IP_address>",
995 description = "Remove IP ban belonging to a player/IP",
997 func = function(name, param)
998 if not core.unban_player_or_ip(param) then
999 return false, "Failed to unban player/IP."
1001 core.log("action", name .. " unbans " .. param)
1002 return true, "Unbanned " .. param
1006 core.register_chatcommand("kick", {
1007 params = "<name> [<reason>]",
1008 description = "Kick a player",
1009 privs = {kick=true},
1010 func = function(name, param)
1011 local tokick, reason = param:match("([^ ]+) (.+)")
1012 tokick = tokick or param
1013 if not core.kick_player(tokick, reason) then
1014 return false, "Failed to kick player " .. tokick
1016 local log_reason = ""
1018 log_reason = " with reason \"" .. reason .. "\""
1020 core.log("action", name .. " kicks " .. tokick .. log_reason)
1021 return true, "Kicked " .. tokick
1025 core.register_chatcommand("clearobjects", {
1026 params = "[full | quick]",
1027 description = "Clear all objects in world",
1028 privs = {server=true},
1029 func = function(name, param)
1031 if param == "" or param == "quick" then
1032 options.mode = "quick"
1033 elseif param == "full" then
1034 options.mode = "full"
1036 return false, "Invalid usage, see /help clearobjects."
1039 core.log("action", name .. " clears all objects ("
1040 .. options.mode .. " mode).")
1041 core.chat_send_all("Clearing all objects. This may take a long time."
1042 .. " You may experience a timeout. (by "
1044 core.clear_objects(options)
1045 core.log("action", "Object clearing done.")
1046 core.chat_send_all("*** Cleared all objects.")
1051 core.register_chatcommand("msg", {
1052 params = "<name> <message>",
1053 description = "Send a direct message to a player",
1054 privs = {shout=true},
1055 func = function(name, param)
1056 local sendto, message = param:match("^(%S+)%s(.+)$")
1058 return false, "Invalid usage, see /help msg."
1060 if not core.get_player_by_name(sendto) then
1061 return false, "The player " .. sendto
1062 .. " is not online."
1064 core.log("action", "DM from " .. name .. " to " .. sendto
1066 core.chat_send_player(sendto, "DM from " .. name .. ": "
1068 return true, "Message sent."
1072 core.register_chatcommand("last-login", {
1073 params = "[<name>]",
1074 description = "Get the last login time of a player or yourself",
1075 func = function(name, param)
1079 local pauth = core.get_auth_handler().get_auth(param)
1080 if pauth and pauth.last_login and pauth.last_login ~= -1 then
1081 -- Time in UTC, ISO 8601 format
1082 return true, param.."'s last login time was " ..
1083 os.date("!%Y-%m-%dT%H:%M:%SZ", pauth.last_login)
1085 return false, param.."'s last login time is unknown"
1089 core.register_chatcommand("clearinv", {
1090 params = "[<name>]",
1091 description = "Clear the inventory of yourself or another player",
1092 func = function(name, param)
1094 if param and param ~= "" and param ~= name then
1095 if not core.check_player_privs(name, {server=true}) then
1096 return false, "You don't have permission"
1097 .. " to clear another player's inventory (missing privilege: server)"
1099 player = core.get_player_by_name(param)
1100 core.chat_send_player(param, name.." cleared your inventory.")
1102 player = core.get_player_by_name(name)
1106 player:get_inventory():set_list("main", {})
1107 player:get_inventory():set_list("craft", {})
1108 player:get_inventory():set_list("craftpreview", {})
1109 core.log("action", name.." clears "..player:get_player_name().."'s inventory")
1110 return true, "Cleared "..player:get_player_name().."'s inventory."
1112 return false, "Player must be online to clear inventory!"
1117 local function handle_kill_command(killer, victim)
1118 if core.settings:get_bool("enable_damage") == false then
1119 return false, "Players can't be killed, damage has been disabled."
1121 local victimref = core.get_player_by_name(victim)
1122 if victimref == nil then
1123 return false, string.format("Player %s is not online.", victim)
1124 elseif victimref:get_hp() <= 0 then
1125 if killer == victim then
1126 return false, "You are already dead."
1128 return false, string.format("%s is already dead.", victim)
1131 if not killer == victim then
1132 core.log("action", string.format("%s killed %s", killer, victim))
1136 return true, string.format("%s has been killed.", victim)
1139 core.register_chatcommand("kill", {
1140 params = "[<name>]",
1141 description = "Kill player or yourself",
1142 privs = {server=true},
1143 func = function(name, param)
1144 return handle_kill_command(name, param == "" and name or param)