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 _, result = cmd_def.func(name, param)
71 core.chat_send_player(name, result)
74 core.chat_send_player(name, "You don't have permission"
75 .. " to run this command (missing privileges: "
76 .. table.concat(missing_privs, ", ") .. ")")
78 return true -- Handled chat message
81 if core.settings:get_bool("profiler.load") then
82 -- Run after register_chatcommand and its register_on_chat_message
83 -- Before any chatcommands that should be profiled
84 profiler.init_chatcommand()
87 -- Parses a "range" string in the format of "here (number)" or
88 -- "(x1, y1, z1) (x2, y2, z2)", returning two position vectors
89 local function parse_range_str(player_name, str)
91 local args = str:split(" ")
93 if args[1] == "here" then
94 p1, p2 = core.get_player_radius_area(player_name, tonumber(args[2]))
96 return false, "Unable to get player " .. player_name .. " position"
99 p1, p2 = core.string_to_area(str)
101 return false, "Incorrect area format. Expected: (x1,y1,z1) (x2,y2,z2)"
111 core.register_chatcommand("me", {
113 description = "Show chat action (e.g., '/me orders a pizza' displays"
114 .. " '<player name> orders a pizza')",
115 privs = {shout=true},
116 func = function(name, param)
117 core.chat_send_all("* " .. name .. " " .. param)
122 core.register_chatcommand("admin", {
123 description = "Show the name of the server owner",
124 func = function(name)
125 local admin = core.settings:get("name")
127 return true, "The administrator of this server is " .. admin .. "."
129 return false, "There's no administrator named in the config file."
134 core.register_chatcommand("privs", {
136 description = "Show privileges of yourself or another player",
137 func = function(caller, param)
139 local name = (param ~= "" and param or caller)
140 if not core.player_exists(name) then
141 return false, "Player " .. name .. " does not exist."
143 return true, "Privileges of " .. name .. ": "
144 .. core.privs_to_string(
145 core.get_player_privs(name), ", ")
149 core.register_chatcommand("haspriv", {
150 params = "<privilege>",
151 description = "Return list of all online players with privilege.",
152 privs = {basic_privs = true},
153 func = function(caller, param)
156 return false, "Invalid parameters (see /help haspriv)"
158 if not core.registered_privileges[param] then
159 return false, "Unknown privilege!"
161 local privs = core.string_to_privs(param)
162 local players_with_priv = {}
163 for _, player in pairs(core.get_connected_players()) do
164 local player_name = player:get_player_name()
165 if core.check_player_privs(player_name, privs) then
166 table.insert(players_with_priv, player_name)
169 return true, "Players online with the \"" .. param .. "\" privilege: " ..
170 table.concat(players_with_priv, ", ")
174 local function handle_grant_command(caller, grantname, grantprivstr)
175 local caller_privs = core.get_player_privs(caller)
176 if not (caller_privs.privs or caller_privs.basic_privs) then
177 return false, "Your privileges are insufficient."
180 if not core.get_auth_handler().get_auth(grantname) then
181 return false, "Player " .. grantname .. " does not exist."
183 local grantprivs = core.string_to_privs(grantprivstr)
184 if grantprivstr == "all" then
185 grantprivs = core.registered_privileges
187 local privs = core.get_player_privs(grantname)
188 local privs_unknown = ""
190 core.string_to_privs(core.settings:get("basic_privs") or "interact,shout")
191 for priv, _ in pairs(grantprivs) do
192 if not basic_privs[priv] and not caller_privs.privs then
193 return false, "Your privileges are insufficient."
195 if not core.registered_privileges[priv] then
196 privs_unknown = privs_unknown .. "Unknown privilege: " .. priv .. "\n"
200 if privs_unknown ~= "" then
201 return false, privs_unknown
203 for priv, _ in pairs(grantprivs) do
204 -- call the on_grant callbacks
205 core.run_priv_callbacks(grantname, priv, caller, "grant")
207 core.set_player_privs(grantname, privs)
208 core.log("action", caller..' granted ('..core.privs_to_string(grantprivs, ', ')..') privileges to '..grantname)
209 if grantname ~= caller then
210 core.chat_send_player(grantname, caller
211 .. " granted you privileges: "
212 .. core.privs_to_string(grantprivs, ' '))
214 return true, "Privileges of " .. grantname .. ": "
215 .. core.privs_to_string(
216 core.get_player_privs(grantname), ' ')
219 core.register_chatcommand("grant", {
220 params = "<name> (<privilege> | all)",
221 description = "Give privileges to player",
222 func = function(name, param)
223 local grantname, grantprivstr = string.match(param, "([^ ]+) (.+)")
224 if not grantname or not grantprivstr then
225 return false, "Invalid parameters (see /help grant)"
227 return handle_grant_command(name, grantname, grantprivstr)
231 core.register_chatcommand("grantme", {
232 params = "<privilege> | all",
233 description = "Grant privileges to yourself",
234 func = function(name, param)
236 return false, "Invalid parameters (see /help grantme)"
238 return handle_grant_command(name, name, param)
242 local function handle_revoke_command(caller, revokename, revokeprivstr)
243 local caller_privs = core.get_player_privs(caller)
244 if not (caller_privs.privs or caller_privs.basic_privs) then
245 return false, "Your privileges are insufficient."
248 if not core.get_auth_handler().get_auth(revokename) then
249 return false, "Player " .. revokename .. " does not exist."
252 local revokeprivs = core.string_to_privs(revokeprivstr)
253 local privs = core.get_player_privs(revokename)
255 core.string_to_privs(core.settings:get("basic_privs") or "interact,shout")
256 for priv, _ in pairs(revokeprivs) do
257 if not basic_privs[priv] and not caller_privs.privs then
258 return false, "Your privileges are insufficient."
262 if revokeprivstr == "all" then
266 for priv, _ in pairs(revokeprivs) do
271 for priv, _ in pairs(revokeprivs) do
272 -- call the on_revoke callbacks
273 core.run_priv_callbacks(revokename, priv, caller, "revoke")
276 core.set_player_privs(revokename, privs)
277 core.log("action", caller..' revoked ('
278 ..core.privs_to_string(revokeprivs, ', ')
279 ..') privileges from '..revokename)
280 if revokename ~= caller then
281 core.chat_send_player(revokename, caller
282 .. " revoked privileges from you: "
283 .. core.privs_to_string(revokeprivs, ' '))
285 return true, "Privileges of " .. revokename .. ": "
286 .. core.privs_to_string(
287 core.get_player_privs(revokename), ' ')
290 core.register_chatcommand("revoke", {
291 params = "<name> (<privilege> | all)",
292 description = "Remove privileges from player",
294 func = function(name, param)
295 local revokename, revokeprivstr = string.match(param, "([^ ]+) (.+)")
296 if not revokename or not revokeprivstr then
297 return false, "Invalid parameters (see /help revoke)"
299 return handle_revoke_command(name, revokename, revokeprivstr)
303 core.register_chatcommand("revokeme", {
304 params = "<privilege> | all",
305 description = "Revoke privileges from yourself",
307 func = function(name, param)
309 return false, "Invalid parameters (see /help revokeme)"
311 return handle_revoke_command(name, name, param)
315 core.register_chatcommand("setpassword", {
316 params = "<name> <password>",
317 description = "Set player's password",
318 privs = {password=true},
319 func = function(name, param)
320 local toname, raw_password = string.match(param, "^([^ ]+) +(.+)$")
322 toname = param:match("^([^ ]+) *$")
327 return false, "Name field required"
330 local act_str_past, act_str_pres
331 if not raw_password then
332 core.set_player_password(toname, "")
333 act_str_past = "cleared"
334 act_str_pres = "clears"
336 core.set_player_password(toname,
337 core.get_password_hash(toname,
340 act_str_pres = "sets"
343 if toname ~= name then
344 core.chat_send_player(toname, "Your password was "
345 .. act_str_past .. " by " .. name)
348 core.log("action", name .. " " .. act_str_pres ..
349 " password of " .. toname .. ".")
351 return true, "Password of player \"" .. toname .. "\" " .. act_str_past
355 core.register_chatcommand("clearpassword", {
357 description = "Set empty password for a player",
358 privs = {password=true},
359 func = function(name, param)
362 return false, "Name field required"
364 core.set_player_password(toname, '')
366 core.log("action", name .. " clears password of " .. toname .. ".")
368 return true, "Password of player \"" .. toname .. "\" cleared"
372 core.register_chatcommand("auth_reload", {
374 description = "Reload authentication data",
375 privs = {server=true},
376 func = function(name, param)
377 local done = core.auth_reload()
378 return done, (done and "Done." or "Failed.")
382 core.register_chatcommand("remove_player", {
384 description = "Remove a player's data",
385 privs = {server=true},
386 func = function(name, param)
389 return false, "Name field required"
392 local rc = core.remove_player(toname)
395 core.log("action", name .. " removed player data of " .. toname .. ".")
396 return true, "Player \"" .. toname .. "\" removed."
398 return true, "No such player \"" .. toname .. "\" to remove."
400 return true, "Player \"" .. toname .. "\" is connected, cannot remove."
403 return false, "Unhandled remove_player return code " .. rc .. ""
407 core.register_chatcommand("teleport", {
408 params = "<X>,<Y>,<Z> | <to_name> | (<name> <X>,<Y>,<Z>) | (<name> <to_name>)",
409 description = "Teleport to position or player",
410 privs = {teleport=true},
411 func = function(name, param)
412 -- Returns (pos, true) if found, otherwise (pos, false)
413 local function find_free_position_near(pos)
420 for _, d in ipairs(tries) do
421 local p = {x = pos.x+d.x, y = pos.y+d.y, z = pos.z+d.z}
422 local n = core.get_node_or_nil(p)
424 local def = core.registered_nodes[n.name]
425 if def and not def.walkable then
434 p.x, p.y, p.z = string.match(param, "^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
438 if p.x and p.y and p.z then
441 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
442 return false, "Cannot teleport out of map bounds!"
444 local teleportee = core.get_player_by_name(name)
446 if teleportee:get_attach() then
447 return false, "Can't teleport, you're attached to an object!"
449 teleportee:set_pos(p)
450 return true, "Teleporting to "..core.pos_to_string(p)
454 local target_name = param:match("^([^ ]+)$")
455 local teleportee = core.get_player_by_name(name)
459 local target = core.get_player_by_name(target_name)
465 if teleportee and p then
466 if teleportee:get_attach() then
467 return false, "Can't teleport, you're attached to an object!"
469 p = find_free_position_near(p)
470 teleportee:set_pos(p)
471 return true, "Teleporting to " .. target_name
472 .. " at "..core.pos_to_string(p)
475 if not core.check_player_privs(name, {bring=true}) then
476 return false, "You don't have permission to teleport other players (missing bring privilege)"
481 local teleportee_name
482 teleportee_name, p.x, p.y, p.z = param:match(
483 "^([^ ]+) +([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
484 p.x, p.y, p.z = tonumber(p.x), tonumber(p.y), tonumber(p.z)
485 if teleportee_name then
486 teleportee = core.get_player_by_name(teleportee_name)
488 if teleportee and p.x and p.y and p.z then
489 if teleportee:get_attach() then
490 return false, "Can't teleport, player is attached to an object!"
492 teleportee:set_pos(p)
493 return true, "Teleporting " .. teleportee_name
494 .. " to " .. core.pos_to_string(p)
499 teleportee_name, target_name = string.match(param, "^([^ ]+) +([^ ]+)$")
500 if teleportee_name then
501 teleportee = core.get_player_by_name(teleportee_name)
504 local target = core.get_player_by_name(target_name)
509 if teleportee and p then
510 if teleportee:get_attach() then
511 return false, "Can't teleport, player is attached to an object!"
513 p = find_free_position_near(p)
514 teleportee:set_pos(p)
515 return true, "Teleporting " .. teleportee_name
516 .. " to " .. target_name
517 .. " at " .. core.pos_to_string(p)
520 return false, 'Invalid parameters ("' .. param
521 .. '") or player not found (see /help teleport)'
525 core.register_chatcommand("set", {
526 params = "([-n] <name> <value>) | <name>",
527 description = "Set or read server configuration setting",
528 privs = {server=true},
529 func = function(name, param)
530 local arg, setname, setvalue = string.match(param, "(-[n]) ([^ ]+) (.+)")
531 if arg and arg == "-n" and setname and setvalue then
532 core.settings:set(setname, setvalue)
533 return true, setname .. " = " .. setvalue
536 setname, setvalue = string.match(param, "([^ ]+) (.+)")
537 if setname and setvalue then
538 if not core.settings:get(setname) then
539 return false, "Failed. Use '/set -n <name> <value>' to create a new setting."
541 core.settings:set(setname, setvalue)
542 return true, setname .. " = " .. setvalue
545 setname = string.match(param, "([^ ]+)")
547 setvalue = core.settings:get(setname)
549 setvalue = "<not set>"
551 return true, setname .. " = " .. setvalue
554 return false, "Invalid parameters (see /help set)."
558 local function emergeblocks_callback(pos, action, num_calls_remaining, ctx)
559 if ctx.total_blocks == 0 then
560 ctx.total_blocks = num_calls_remaining + 1
561 ctx.current_blocks = 0
563 ctx.current_blocks = ctx.current_blocks + 1
565 if ctx.current_blocks == ctx.total_blocks then
566 core.chat_send_player(ctx.requestor_name,
567 string.format("Finished emerging %d blocks in %.2fms.",
568 ctx.total_blocks, (os.clock() - ctx.start_time) * 1000))
572 local function emergeblocks_progress_update(ctx)
573 if ctx.current_blocks ~= ctx.total_blocks then
574 core.chat_send_player(ctx.requestor_name,
575 string.format("emergeblocks update: %d/%d blocks emerged (%.1f%%)",
576 ctx.current_blocks, ctx.total_blocks,
577 (ctx.current_blocks / ctx.total_blocks) * 100))
579 core.after(2, emergeblocks_progress_update, ctx)
583 core.register_chatcommand("emergeblocks", {
584 params = "(here [<radius>]) | (<pos1> <pos2>)",
585 description = "Load (or, if nonexistent, generate) map blocks "
586 .. "contained in area pos1 to pos2 (<pos1> and <pos2> must be in parentheses)",
587 privs = {server=true},
588 func = function(name, param)
589 local p1, p2 = parse_range_str(name, param)
597 start_time = os.clock(),
598 requestor_name = name
601 core.emerge_area(p1, p2, emergeblocks_callback, context)
602 core.after(2, emergeblocks_progress_update, context)
604 return true, "Started emerge of area ranging from " ..
605 core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
609 core.register_chatcommand("deleteblocks", {
610 params = "(here [<radius>]) | (<pos1> <pos2>)",
611 description = "Delete map blocks contained in area pos1 to pos2 "
612 .. "(<pos1> and <pos2> must be in parentheses)",
613 privs = {server=true},
614 func = function(name, param)
615 local p1, p2 = parse_range_str(name, param)
620 if core.delete_area(p1, p2) then
621 return true, "Successfully cleared area ranging from " ..
622 core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
624 return false, "Failed to clear one or more blocks in area"
629 core.register_chatcommand("fixlight", {
630 params = "(here [<radius>]) | (<pos1> <pos2>)",
631 description = "Resets lighting in the area between pos1 and pos2 "
632 .. "(<pos1> and <pos2> must be in parentheses)",
633 privs = {server = true},
634 func = function(name, param)
635 local p1, p2 = parse_range_str(name, param)
640 if core.fix_light(p1, p2) then
641 return true, "Successfully reset light in the area ranging from " ..
642 core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
644 return false, "Failed to load one or more blocks in area"
649 core.register_chatcommand("mods", {
651 description = "List mods installed on the server",
653 func = function(name, param)
654 return true, table.concat(core.get_modnames(), ", ")
658 local function handle_give_command(cmd, giver, receiver, stackstring)
659 core.log("action", giver .. " invoked " .. cmd
660 .. ', stackstring="' .. stackstring .. '"')
661 local itemstack = ItemStack(stackstring)
662 if itemstack:is_empty() then
663 return false, "Cannot give an empty item"
664 elseif (not itemstack:is_known()) or (itemstack:get_name() == "unknown") then
665 return false, "Cannot give an unknown item"
666 -- Forbid giving 'ignore' due to unwanted side effects
667 elseif itemstack:get_name() == "ignore" then
668 return false, "Giving 'ignore' is not allowed"
670 local receiverref = core.get_player_by_name(receiver)
671 if receiverref == nil then
672 return false, receiver .. " is not a known player"
674 local leftover = receiverref:get_inventory():add_item("main", itemstack)
676 if leftover:is_empty() then
678 elseif leftover:get_count() == itemstack:get_count() then
679 partiality = "could not be "
681 partiality = "partially "
683 -- The actual item stack string may be different from what the "giver"
684 -- entered (e.g. big numbers are always interpreted as 2^16-1).
685 stackstring = itemstack:to_string()
686 if giver == receiver then
687 local msg = "%q %sadded to inventory."
688 return true, msg:format(stackstring, partiality)
690 core.chat_send_player(receiver, ("%q %sadded to inventory.")
691 :format(stackstring, partiality))
692 local msg = "%q %sadded to %s's inventory."
693 return true, msg:format(stackstring, partiality, receiver)
697 core.register_chatcommand("give", {
698 params = "<name> <ItemString> [<count> [<wear>]]",
699 description = "Give item to player",
701 func = function(name, param)
702 local toname, itemstring = string.match(param, "^([^ ]+) +(.+)$")
703 if not toname or not itemstring then
704 return false, "Name and ItemString required"
706 return handle_give_command("/give", name, toname, itemstring)
710 core.register_chatcommand("giveme", {
711 params = "<ItemString> [<count> [<wear>]]",
712 description = "Give item to yourself",
714 func = function(name, param)
715 local itemstring = string.match(param, "(.+)$")
716 if not itemstring then
717 return false, "ItemString required"
719 return handle_give_command("/giveme", name, name, itemstring)
723 core.register_chatcommand("spawnentity", {
724 params = "<EntityName> [<X>,<Y>,<Z>]",
725 description = "Spawn entity at given (or your) position",
726 privs = {give=true, interact=true},
727 func = function(name, param)
728 local entityname, p = string.match(param, "^([^ ]+) *(.*)$")
729 if not entityname then
730 return false, "EntityName required"
732 core.log("action", ("%s invokes /spawnentity, entityname=%q")
733 :format(name, entityname))
734 local player = core.get_player_by_name(name)
735 if player == nil then
736 core.log("error", "Unable to spawn entity, player is nil")
737 return false, "Unable to spawn entity, player is nil"
739 if not core.registered_entities[entityname] then
740 return false, "Cannot spawn an unknown entity"
745 p = core.string_to_pos(p)
747 return false, "Invalid parameters ('" .. param .. "')"
751 local obj = core.add_entity(p, entityname)
752 local msg = obj and "%q spawned." or "%q failed to spawn."
753 return true, msg:format(entityname)
757 core.register_chatcommand("pulverize", {
759 description = "Destroy item in hand",
760 func = function(name, param)
761 local player = core.get_player_by_name(name)
763 core.log("error", "Unable to pulverize, no player.")
764 return false, "Unable to pulverize, no player."
766 local wielded_item = player:get_wielded_item()
767 if wielded_item:is_empty() then
768 return false, "Unable to pulverize, no item in hand."
770 core.log("action", name .. " pulverized \"" ..
771 wielded_item:get_name() .. " " .. wielded_item:get_count() .. "\"")
772 player:set_wielded_item(nil)
773 return true, "An item was pulverized."
778 core.rollback_punch_callbacks = {}
780 core.register_on_punchnode(function(pos, node, puncher)
781 local name = puncher and puncher:get_player_name()
782 if name and core.rollback_punch_callbacks[name] then
783 core.rollback_punch_callbacks[name](pos, node, puncher)
784 core.rollback_punch_callbacks[name] = nil
788 core.register_chatcommand("rollback_check", {
789 params = "[<range>] [<seconds>] [<limit>]",
790 description = "Check who last touched a node or a node near it"
791 .. " within the time specified by <seconds>. Default: range = 0,"
792 .. " seconds = 86400 = 24h, limit = 5. Set <seconds> to inf for no time limit",
793 privs = {rollback=true},
794 func = function(name, param)
795 if not core.settings:get_bool("enable_rollback_recording") then
796 return false, "Rollback functions are disabled."
798 local range, seconds, limit =
799 param:match("(%d+) *(%d*) *(%d*)")
800 range = tonumber(range) or 0
801 seconds = tonumber(seconds) or 86400
802 limit = tonumber(limit) or 5
804 return false, "That limit is too high!"
807 core.rollback_punch_callbacks[name] = function(pos, node, puncher)
808 local name = puncher:get_player_name()
809 core.chat_send_player(name, "Checking " .. core.pos_to_string(pos) .. "...")
810 local actions = core.rollback_get_node_actions(pos, range, seconds, limit)
812 core.chat_send_player(name, "Rollback functions are disabled")
815 local num_actions = #actions
816 if num_actions == 0 then
817 core.chat_send_player(name, "Nobody has touched"
818 .. " the specified location in "
819 .. seconds .. " seconds")
822 local time = os.time()
823 for i = num_actions, 1, -1 do
824 local action = actions[i]
825 core.chat_send_player(name,
826 ("%s %s %s -> %s %d seconds ago.")
828 core.pos_to_string(action.pos),
836 return true, "Punch a node (range=" .. range .. ", seconds="
837 .. seconds .. "s, limit=" .. limit .. ")"
841 core.register_chatcommand("rollback", {
842 params = "(<name> [<seconds>]) | (:<actor> [<seconds>])",
843 description = "Revert actions of a player. Default for <seconds> is 60. Set <seconds> to inf for no time limit",
844 privs = {rollback=true},
845 func = function(name, param)
846 if not core.settings:get_bool("enable_rollback_recording") then
847 return false, "Rollback functions are disabled."
849 local target_name, seconds = string.match(param, ":([^ ]+) *(%d*)")
850 if not target_name then
852 player_name, seconds = string.match(param, "([^ ]+) *(%d*)")
853 if not player_name then
854 return false, "Invalid parameters. See /help rollback"
855 .. " and /help rollback_check."
857 target_name = "player:"..player_name
859 seconds = tonumber(seconds) or 60
860 core.chat_send_player(name, "Reverting actions of "
861 .. target_name .. " since "
862 .. seconds .. " seconds.")
863 local success, log = core.rollback_revert_actions_by(
864 target_name, seconds)
867 response = "(log is too long to show)\n"
869 for _, line in pairs(log) do
870 response = response .. line .. "\n"
873 response = response .. "Reverting actions "
874 .. (success and "succeeded." or "FAILED.")
875 return success, response
879 core.register_chatcommand("status", {
880 description = "Show server status",
881 func = function(name, param)
882 local status = core.get_server_status(name, false)
883 if status and status ~= "" then
886 return false, "This command was disabled by a mod or game"
890 core.register_chatcommand("time", {
891 params = "[<0..23>:<0..59> | <0..24000>]",
892 description = "Show or set time of day",
894 func = function(name, param)
896 local current_time = math.floor(core.get_timeofday() * 1440)
897 local minutes = current_time % 60
898 local hour = (current_time - minutes) / 60
899 return true, ("Current time is %d:%02d"):format(hour, minutes)
901 local player_privs = core.get_player_privs(name)
902 if not player_privs.settime then
903 return false, "You don't have permission to run this command " ..
904 "(missing privilege: settime)."
906 local hour, minute = param:match("^(%d+):(%d+)$")
908 local new_time = tonumber(param)
910 return false, "Invalid time."
912 -- Backward compatibility.
913 core.set_timeofday((new_time % 24000) / 24000)
914 core.log("action", name .. " sets time to " .. new_time)
915 return true, "Time of day changed."
917 hour = tonumber(hour)
918 minute = tonumber(minute)
919 if hour < 0 or hour > 23 then
920 return false, "Invalid hour (must be between 0 and 23 inclusive)."
921 elseif minute < 0 or minute > 59 then
922 return false, "Invalid minute (must be between 0 and 59 inclusive)."
924 core.set_timeofday((hour * 60 + minute) / 1440)
925 core.log("action", ("%s sets time to %d:%02d"):format(name, hour, minute))
926 return true, "Time of day changed."
930 core.register_chatcommand("days", {
931 description = "Show day count since world creation",
932 func = function(name, param)
933 return true, "Current day is " .. core.get_day_count()
937 core.register_chatcommand("shutdown", {
938 params = "[<delay_in_seconds> | -1] [reconnect] [<message>]",
939 description = "Shutdown server (-1 cancels a delayed shutdown)",
940 privs = {server=true},
941 func = function(name, param)
942 local delay, reconnect, message
943 delay, param = param:match("^%s*(%S+)(.*)")
945 reconnect, param = param:match("^%s*(%S+)(.*)")
947 message = param and param:match("^%s*(.+)") or ""
948 delay = tonumber(delay) or 0
951 core.log("action", name .. " shuts down server")
952 core.chat_send_all("*** Server shutting down (operator request).")
954 core.request_shutdown(message:trim(), core.is_yes(reconnect), delay)
959 core.register_chatcommand("ban", {
961 description = "Ban the IP of a player or show the ban list",
963 func = function(name, param)
965 local ban_list = core.get_ban_list()
966 if ban_list == "" then
967 return true, "The ban list is empty."
969 return true, "Ban list: " .. ban_list
972 if not core.get_player_by_name(param) then
973 return false, "Player is not online."
975 if not core.ban_player(param) then
976 return false, "Failed to ban player."
978 local desc = core.get_ban_description(param)
979 core.log("action", name .. " bans " .. desc .. ".")
980 return true, "Banned " .. desc .. "."
984 core.register_chatcommand("unban", {
985 params = "<name> | <IP_address>",
986 description = "Remove IP ban belonging to a player/IP",
988 func = function(name, param)
989 if not core.unban_player_or_ip(param) then
990 return false, "Failed to unban player/IP."
992 core.log("action", name .. " unbans " .. param)
993 return true, "Unbanned " .. param
997 core.register_chatcommand("kick", {
998 params = "<name> [<reason>]",
999 description = "Kick a player",
1000 privs = {kick=true},
1001 func = function(name, param)
1002 local tokick, reason = param:match("([^ ]+) (.+)")
1003 tokick = tokick or param
1004 if not core.kick_player(tokick, reason) then
1005 return false, "Failed to kick player " .. tokick
1007 local log_reason = ""
1009 log_reason = " with reason \"" .. reason .. "\""
1011 core.log("action", name .. " kicks " .. tokick .. log_reason)
1012 return true, "Kicked " .. tokick
1016 core.register_chatcommand("clearobjects", {
1017 params = "[full | quick]",
1018 description = "Clear all objects in world",
1019 privs = {server=true},
1020 func = function(name, param)
1022 if param == "" or param == "quick" then
1023 options.mode = "quick"
1024 elseif param == "full" then
1025 options.mode = "full"
1027 return false, "Invalid usage, see /help clearobjects."
1030 core.log("action", name .. " clears all objects ("
1031 .. options.mode .. " mode).")
1032 core.chat_send_all("Clearing all objects. This may take a long time."
1033 .. " You may experience a timeout. (by "
1035 core.clear_objects(options)
1036 core.log("action", "Object clearing done.")
1037 core.chat_send_all("*** Cleared all objects.")
1042 core.register_chatcommand("msg", {
1043 params = "<name> <message>",
1044 description = "Send a direct message to a player",
1045 privs = {shout=true},
1046 func = function(name, param)
1047 local sendto, message = param:match("^(%S+)%s(.+)$")
1049 return false, "Invalid usage, see /help msg."
1051 if not core.get_player_by_name(sendto) then
1052 return false, "The player " .. sendto
1053 .. " is not online."
1055 core.log("action", "DM from " .. name .. " to " .. sendto
1057 core.chat_send_player(sendto, "DM from " .. name .. ": "
1059 return true, "Message sent."
1063 core.register_chatcommand("last-login", {
1064 params = "[<name>]",
1065 description = "Get the last login time of a player or yourself",
1066 func = function(name, param)
1070 local pauth = core.get_auth_handler().get_auth(param)
1071 if pauth and pauth.last_login and pauth.last_login ~= -1 then
1072 -- Time in UTC, ISO 8601 format
1073 return true, "Last login time was " ..
1074 os.date("!%Y-%m-%dT%H:%M:%SZ", pauth.last_login)
1076 return false, "Last login time is unknown"
1080 core.register_chatcommand("clearinv", {
1081 params = "[<name>]",
1082 description = "Clear the inventory of yourself or another player",
1083 func = function(name, param)
1085 if param and param ~= "" and param ~= name then
1086 if not core.check_player_privs(name, {server=true}) then
1087 return false, "You don't have permission"
1088 .. " to clear another player's inventory (missing privilege: server)"
1090 player = core.get_player_by_name(param)
1091 core.chat_send_player(param, name.." cleared your inventory.")
1093 player = core.get_player_by_name(name)
1097 player:get_inventory():set_list("main", {})
1098 player:get_inventory():set_list("craft", {})
1099 player:get_inventory():set_list("craftpreview", {})
1100 core.log("action", name.." clears "..player:get_player_name().."'s inventory")
1101 return true, "Cleared "..player:get_player_name().."'s inventory."
1103 return false, "Player must be online to clear inventory!"
1108 local function handle_kill_command(killer, victim)
1109 if core.settings:get_bool("enable_damage") == false then
1110 return false, "Players can't be killed, damage has been disabled."
1112 local victimref = core.get_player_by_name(victim)
1113 if victimref == nil then
1114 return false, string.format("Player %s is not online.", victim)
1115 elseif victimref:get_hp() <= 0 then
1116 if killer == victim then
1117 return false, "You are already dead."
1119 return false, string.format("%s is already dead.", victim)
1122 if not killer == victim then
1123 core.log("action", string.format("%s killed %s", killer, victim))
1127 return true, string.format("%s has been killed.", victim)
1130 core.register_chatcommand("kill", {
1131 params = "[<name>]",
1132 description = "Kill player or yourself",
1133 privs = {server=true},
1134 func = function(name, param)
1135 return handle_kill_command(name, param == "" and name or param)