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 teleportee:set_pos(p)
447 return true, "Teleporting to "..core.pos_to_string(p)
451 local target_name = param:match("^([^ ]+)$")
452 local teleportee = core.get_player_by_name(name)
456 local target = core.get_player_by_name(target_name)
462 if teleportee and p then
463 p = find_free_position_near(p)
464 teleportee:set_pos(p)
465 return true, "Teleporting to " .. target_name
466 .. " at "..core.pos_to_string(p)
469 if not core.check_player_privs(name, {bring=true}) then
470 return false, "You don't have permission to teleport other players (missing bring privilege)"
475 local teleportee_name
476 teleportee_name, p.x, p.y, p.z = param:match(
477 "^([^ ]+) +([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
478 p.x, p.y, p.z = tonumber(p.x), tonumber(p.y), tonumber(p.z)
479 if teleportee_name then
480 teleportee = core.get_player_by_name(teleportee_name)
482 if teleportee and p.x and p.y and p.z then
483 teleportee:set_pos(p)
484 return true, "Teleporting " .. teleportee_name
485 .. " to " .. core.pos_to_string(p)
490 teleportee_name, target_name = string.match(param, "^([^ ]+) +([^ ]+)$")
491 if teleportee_name then
492 teleportee = core.get_player_by_name(teleportee_name)
495 local target = core.get_player_by_name(target_name)
500 if teleportee and p then
501 p = find_free_position_near(p)
502 teleportee:set_pos(p)
503 return true, "Teleporting " .. teleportee_name
504 .. " to " .. target_name
505 .. " at " .. core.pos_to_string(p)
508 return false, 'Invalid parameters ("' .. param
509 .. '") or player not found (see /help teleport)'
513 core.register_chatcommand("set", {
514 params = "([-n] <name> <value>) | <name>",
515 description = "Set or read server configuration setting",
516 privs = {server=true},
517 func = function(name, param)
518 local arg, setname, setvalue = string.match(param, "(-[n]) ([^ ]+) (.+)")
519 if arg and arg == "-n" and setname and setvalue then
520 core.settings:set(setname, setvalue)
521 return true, setname .. " = " .. setvalue
524 setname, setvalue = string.match(param, "([^ ]+) (.+)")
525 if setname and setvalue then
526 if not core.settings:get(setname) then
527 return false, "Failed. Use '/set -n <name> <value>' to create a new setting."
529 core.settings:set(setname, setvalue)
530 return true, setname .. " = " .. setvalue
533 setname = string.match(param, "([^ ]+)")
535 setvalue = core.settings:get(setname)
537 setvalue = "<not set>"
539 return true, setname .. " = " .. setvalue
542 return false, "Invalid parameters (see /help set)."
546 local function emergeblocks_callback(pos, action, num_calls_remaining, ctx)
547 if ctx.total_blocks == 0 then
548 ctx.total_blocks = num_calls_remaining + 1
549 ctx.current_blocks = 0
551 ctx.current_blocks = ctx.current_blocks + 1
553 if ctx.current_blocks == ctx.total_blocks then
554 core.chat_send_player(ctx.requestor_name,
555 string.format("Finished emerging %d blocks in %.2fms.",
556 ctx.total_blocks, (os.clock() - ctx.start_time) * 1000))
560 local function emergeblocks_progress_update(ctx)
561 if ctx.current_blocks ~= ctx.total_blocks then
562 core.chat_send_player(ctx.requestor_name,
563 string.format("emergeblocks update: %d/%d blocks emerged (%.1f%%)",
564 ctx.current_blocks, ctx.total_blocks,
565 (ctx.current_blocks / ctx.total_blocks) * 100))
567 core.after(2, emergeblocks_progress_update, ctx)
571 core.register_chatcommand("emergeblocks", {
572 params = "(here [<radius>]) | (<pos1> <pos2>)",
573 description = "Load (or, if nonexistent, generate) map blocks "
574 .. "contained in area pos1 to pos2 (<pos1> and <pos2> must be in parentheses)",
575 privs = {server=true},
576 func = function(name, param)
577 local p1, p2 = parse_range_str(name, param)
585 start_time = os.clock(),
586 requestor_name = name
589 core.emerge_area(p1, p2, emergeblocks_callback, context)
590 core.after(2, emergeblocks_progress_update, context)
592 return true, "Started emerge of area ranging from " ..
593 core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
597 core.register_chatcommand("deleteblocks", {
598 params = "(here [<radius>]) | (<pos1> <pos2>)",
599 description = "Delete map blocks contained in area pos1 to pos2 "
600 .. "(<pos1> and <pos2> must be in parentheses)",
601 privs = {server=true},
602 func = function(name, param)
603 local p1, p2 = parse_range_str(name, param)
608 if core.delete_area(p1, p2) then
609 return true, "Successfully cleared area ranging from " ..
610 core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
612 return false, "Failed to clear one or more blocks in area"
617 core.register_chatcommand("fixlight", {
618 params = "(here [<radius>]) | (<pos1> <pos2>)",
619 description = "Resets lighting in the area between pos1 and pos2 "
620 .. "(<pos1> and <pos2> must be in parentheses)",
621 privs = {server = true},
622 func = function(name, param)
623 local p1, p2 = parse_range_str(name, param)
628 if core.fix_light(p1, p2) then
629 return true, "Successfully reset light in the area ranging from " ..
630 core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
632 return false, "Failed to load one or more blocks in area"
637 core.register_chatcommand("mods", {
639 description = "List mods installed on the server",
641 func = function(name, param)
642 return true, table.concat(core.get_modnames(), ", ")
646 local function handle_give_command(cmd, giver, receiver, stackstring)
647 core.log("action", giver .. " invoked " .. cmd
648 .. ', stackstring="' .. stackstring .. '"')
649 local itemstack = ItemStack(stackstring)
650 if itemstack:is_empty() then
651 return false, "Cannot give an empty item"
652 elseif (not itemstack:is_known()) or (itemstack:get_name() == "unknown") then
653 return false, "Cannot give an unknown item"
654 -- Forbid giving 'ignore' due to unwanted side effects
655 elseif itemstack:get_name() == "ignore" then
656 return false, "Giving 'ignore' is not allowed"
658 local receiverref = core.get_player_by_name(receiver)
659 if receiverref == nil then
660 return false, receiver .. " is not a known player"
662 local leftover = receiverref:get_inventory():add_item("main", itemstack)
664 if leftover:is_empty() then
666 elseif leftover:get_count() == itemstack:get_count() then
667 partiality = "could not be "
669 partiality = "partially "
671 -- The actual item stack string may be different from what the "giver"
672 -- entered (e.g. big numbers are always interpreted as 2^16-1).
673 stackstring = itemstack:to_string()
674 if giver == receiver then
675 local msg = "%q %sadded to inventory."
676 return true, msg:format(stackstring, partiality)
678 core.chat_send_player(receiver, ("%q %sadded to inventory.")
679 :format(stackstring, partiality))
680 local msg = "%q %sadded to %s's inventory."
681 return true, msg:format(stackstring, partiality, receiver)
685 core.register_chatcommand("give", {
686 params = "<name> <ItemString> [<count> [<wear>]]",
687 description = "Give item to player",
689 func = function(name, param)
690 local toname, itemstring = string.match(param, "^([^ ]+) +(.+)$")
691 if not toname or not itemstring then
692 return false, "Name and ItemString required"
694 return handle_give_command("/give", name, toname, itemstring)
698 core.register_chatcommand("giveme", {
699 params = "<ItemString> [<count> [<wear>]]",
700 description = "Give item to yourself",
702 func = function(name, param)
703 local itemstring = string.match(param, "(.+)$")
704 if not itemstring then
705 return false, "ItemString required"
707 return handle_give_command("/giveme", name, name, itemstring)
711 core.register_chatcommand("spawnentity", {
712 params = "<EntityName> [<X>,<Y>,<Z>]",
713 description = "Spawn entity at given (or your) position",
714 privs = {give=true, interact=true},
715 func = function(name, param)
716 local entityname, p = string.match(param, "^([^ ]+) *(.*)$")
717 if not entityname then
718 return false, "EntityName required"
720 core.log("action", ("%s invokes /spawnentity, entityname=%q")
721 :format(name, entityname))
722 local player = core.get_player_by_name(name)
723 if player == nil then
724 core.log("error", "Unable to spawn entity, player is nil")
725 return false, "Unable to spawn entity, player is nil"
727 if not core.registered_entities[entityname] then
728 return false, "Cannot spawn an unknown entity"
733 p = core.string_to_pos(p)
735 return false, "Invalid parameters ('" .. param .. "')"
739 local obj = core.add_entity(p, entityname)
740 local msg = obj and "%q spawned." or "%q failed to spawn."
741 return true, msg:format(entityname)
745 core.register_chatcommand("pulverize", {
747 description = "Destroy item in hand",
748 func = function(name, param)
749 local player = core.get_player_by_name(name)
751 core.log("error", "Unable to pulverize, no player.")
752 return false, "Unable to pulverize, no player."
754 local wielded_item = player:get_wielded_item()
755 if wielded_item:is_empty() then
756 return false, "Unable to pulverize, no item in hand."
758 core.log("action", name .. " pulverized \"" ..
759 wielded_item:get_name() .. " " .. wielded_item:get_count() .. "\"")
760 player:set_wielded_item(nil)
761 return true, "An item was pulverized."
766 core.rollback_punch_callbacks = {}
768 core.register_on_punchnode(function(pos, node, puncher)
769 local name = puncher and puncher:get_player_name()
770 if name and core.rollback_punch_callbacks[name] then
771 core.rollback_punch_callbacks[name](pos, node, puncher)
772 core.rollback_punch_callbacks[name] = nil
776 core.register_chatcommand("rollback_check", {
777 params = "[<range>] [<seconds>] [<limit>]",
778 description = "Check who last touched a node or a node near it"
779 .. " within the time specified by <seconds>. Default: range = 0,"
780 .. " seconds = 86400 = 24h, limit = 5",
781 privs = {rollback=true},
782 func = function(name, param)
783 if not core.settings:get_bool("enable_rollback_recording") then
784 return false, "Rollback functions are disabled."
786 local range, seconds, limit =
787 param:match("(%d+) *(%d*) *(%d*)")
788 range = tonumber(range) or 0
789 seconds = tonumber(seconds) or 86400
790 limit = tonumber(limit) or 5
792 return false, "That limit is too high!"
795 core.rollback_punch_callbacks[name] = function(pos, node, puncher)
796 local name = puncher:get_player_name()
797 core.chat_send_player(name, "Checking " .. core.pos_to_string(pos) .. "...")
798 local actions = core.rollback_get_node_actions(pos, range, seconds, limit)
800 core.chat_send_player(name, "Rollback functions are disabled")
803 local num_actions = #actions
804 if num_actions == 0 then
805 core.chat_send_player(name, "Nobody has touched"
806 .. " the specified location in "
807 .. seconds .. " seconds")
810 local time = os.time()
811 for i = num_actions, 1, -1 do
812 local action = actions[i]
813 core.chat_send_player(name,
814 ("%s %s %s -> %s %d seconds ago.")
816 core.pos_to_string(action.pos),
824 return true, "Punch a node (range=" .. range .. ", seconds="
825 .. seconds .. "s, limit=" .. limit .. ")"
829 core.register_chatcommand("rollback", {
830 params = "(<name> [<seconds>]) | (:<actor> [<seconds>])",
831 description = "Revert actions of a player. Default for <seconds> is 60",
832 privs = {rollback=true},
833 func = function(name, param)
834 if not core.settings:get_bool("enable_rollback_recording") then
835 return false, "Rollback functions are disabled."
837 local target_name, seconds = string.match(param, ":([^ ]+) *(%d*)")
838 if not target_name then
840 player_name, seconds = string.match(param, "([^ ]+) *(%d*)")
841 if not player_name then
842 return false, "Invalid parameters. See /help rollback"
843 .. " and /help rollback_check."
845 target_name = "player:"..player_name
847 seconds = tonumber(seconds) or 60
848 core.chat_send_player(name, "Reverting actions of "
849 .. target_name .. " since "
850 .. seconds .. " seconds.")
851 local success, log = core.rollback_revert_actions_by(
852 target_name, seconds)
855 response = "(log is too long to show)\n"
857 for _, line in pairs(log) do
858 response = response .. line .. "\n"
861 response = response .. "Reverting actions "
862 .. (success and "succeeded." or "FAILED.")
863 return success, response
867 core.register_chatcommand("status", {
868 description = "Show server status",
869 func = function(name, param)
870 local status = core.get_server_status(name, false)
871 if status and status ~= "" then
874 return false, "This command was disabled by a mod or game"
878 core.register_chatcommand("time", {
879 params = "[<0..23>:<0..59> | <0..24000>]",
880 description = "Show or set time of day",
882 func = function(name, param)
884 local current_time = math.floor(core.get_timeofday() * 1440)
885 local minutes = current_time % 60
886 local hour = (current_time - minutes) / 60
887 return true, ("Current time is %d:%02d"):format(hour, minutes)
889 local player_privs = core.get_player_privs(name)
890 if not player_privs.settime then
891 return false, "You don't have permission to run this command " ..
892 "(missing privilege: settime)."
894 local hour, minute = param:match("^(%d+):(%d+)$")
896 local new_time = tonumber(param)
898 return false, "Invalid time."
900 -- Backward compatibility.
901 core.set_timeofday((new_time % 24000) / 24000)
902 core.log("action", name .. " sets time to " .. new_time)
903 return true, "Time of day changed."
905 hour = tonumber(hour)
906 minute = tonumber(minute)
907 if hour < 0 or hour > 23 then
908 return false, "Invalid hour (must be between 0 and 23 inclusive)."
909 elseif minute < 0 or minute > 59 then
910 return false, "Invalid minute (must be between 0 and 59 inclusive)."
912 core.set_timeofday((hour * 60 + minute) / 1440)
913 core.log("action", ("%s sets time to %d:%02d"):format(name, hour, minute))
914 return true, "Time of day changed."
918 core.register_chatcommand("days", {
919 description = "Show day count since world creation",
920 func = function(name, param)
921 return true, "Current day is " .. core.get_day_count()
925 core.register_chatcommand("shutdown", {
926 params = "[<delay_in_seconds> | -1] [reconnect] [<message>]",
927 description = "Shutdown server (-1 cancels a delayed shutdown)",
928 privs = {server=true},
929 func = function(name, param)
930 local delay, reconnect, message
931 delay, param = param:match("^%s*(%S+)(.*)")
933 reconnect, param = param:match("^%s*(%S+)(.*)")
935 message = param and param:match("^%s*(.+)") or ""
936 delay = tonumber(delay) or 0
939 core.log("action", name .. " shuts down server")
940 core.chat_send_all("*** Server shutting down (operator request).")
942 core.request_shutdown(message:trim(), core.is_yes(reconnect), delay)
947 core.register_chatcommand("ban", {
949 description = "Ban the IP of a player or show the ban list",
951 func = function(name, param)
953 local ban_list = core.get_ban_list()
954 if ban_list == "" then
955 return true, "The ban list is empty."
957 return true, "Ban list: " .. ban_list
960 if not core.get_player_by_name(param) then
961 return false, "Player is not online."
963 if not core.ban_player(param) then
964 return false, "Failed to ban player."
966 local desc = core.get_ban_description(param)
967 core.log("action", name .. " bans " .. desc .. ".")
968 return true, "Banned " .. desc .. "."
972 core.register_chatcommand("unban", {
973 params = "<name> | <IP_address>",
974 description = "Remove IP ban belonging to a player/IP",
976 func = function(name, param)
977 if not core.unban_player_or_ip(param) then
978 return false, "Failed to unban player/IP."
980 core.log("action", name .. " unbans " .. param)
981 return true, "Unbanned " .. param
985 core.register_chatcommand("kick", {
986 params = "<name> [<reason>]",
987 description = "Kick a player",
989 func = function(name, param)
990 local tokick, reason = param:match("([^ ]+) (.+)")
991 tokick = tokick or param
992 if not core.kick_player(tokick, reason) then
993 return false, "Failed to kick player " .. tokick
995 local log_reason = ""
997 log_reason = " with reason \"" .. reason .. "\""
999 core.log("action", name .. " kicks " .. tokick .. log_reason)
1000 return true, "Kicked " .. tokick
1004 core.register_chatcommand("clearobjects", {
1005 params = "[full | quick]",
1006 description = "Clear all objects in world",
1007 privs = {server=true},
1008 func = function(name, param)
1010 if param == "" or param == "quick" then
1011 options.mode = "quick"
1012 elseif param == "full" then
1013 options.mode = "full"
1015 return false, "Invalid usage, see /help clearobjects."
1018 core.log("action", name .. " clears all objects ("
1019 .. options.mode .. " mode).")
1020 core.chat_send_all("Clearing all objects. This may take a long time."
1021 .. " You may experience a timeout. (by "
1023 core.clear_objects(options)
1024 core.log("action", "Object clearing done.")
1025 core.chat_send_all("*** Cleared all objects.")
1030 core.register_chatcommand("msg", {
1031 params = "<name> <message>",
1032 description = "Send a direct message to a player",
1033 privs = {shout=true},
1034 func = function(name, param)
1035 local sendto, message = param:match("^(%S+)%s(.+)$")
1037 return false, "Invalid usage, see /help msg."
1039 if not core.get_player_by_name(sendto) then
1040 return false, "The player " .. sendto
1041 .. " is not online."
1043 core.log("action", "DM from " .. name .. " to " .. sendto
1045 core.chat_send_player(sendto, "DM from " .. name .. ": "
1047 return true, "Message sent."
1051 core.register_chatcommand("last-login", {
1052 params = "[<name>]",
1053 description = "Get the last login time of a player or yourself",
1054 func = function(name, param)
1058 local pauth = core.get_auth_handler().get_auth(param)
1059 if pauth and pauth.last_login then
1060 -- Time in UTC, ISO 8601 format
1061 return true, "Last login time was " ..
1062 os.date("!%Y-%m-%dT%H:%M:%SZ", pauth.last_login)
1064 return false, "Last login time is unknown"
1068 core.register_chatcommand("clearinv", {
1069 params = "[<name>]",
1070 description = "Clear the inventory of yourself or another player",
1071 func = function(name, param)
1073 if param and param ~= "" and param ~= name then
1074 if not core.check_player_privs(name, {server=true}) then
1075 return false, "You don't have permission"
1076 .. " to clear another player's inventory (missing privilege: server)"
1078 player = core.get_player_by_name(param)
1079 core.chat_send_player(param, name.." cleared your inventory.")
1081 player = core.get_player_by_name(name)
1085 player:get_inventory():set_list("main", {})
1086 player:get_inventory():set_list("craft", {})
1087 player:get_inventory():set_list("craftpreview", {})
1088 core.log("action", name.." clears "..player:get_player_name().."'s inventory")
1089 return true, "Cleared "..player:get_player_name().."'s inventory."
1091 return false, "Player must be online to clear inventory!"
1096 local function handle_kill_command(killer, victim)
1097 if core.settings:get_bool("enable_damage") == false then
1098 return false, "Players can't be killed, damage has been disabled."
1100 local victimref = core.get_player_by_name(victim)
1101 if victimref == nil then
1102 return false, string.format("Player %s is not online.", victim)
1103 elseif victimref:get_hp() <= 0 then
1104 if killer == victim then
1105 return false, "You are already dead."
1107 return false, string.format("%s is already dead.", victim)
1110 if not killer == victim then
1111 core.log("action", string.format("%s killed %s", killer, victim))
1115 return true, string.format("%s has been killed.", victim)
1118 core.register_chatcommand("kill", {
1119 params = "[<name>]",
1120 description = "Kill player or yourself",
1121 privs = {server=true},
1122 func = function(name, param)
1123 return handle_kill_command(name, param == "" and name or param)