1 -- Minetest: builtin/game/chat.lua
4 -- Chat message formatter
7 -- Implemented in Lua to allow redefinition
8 function core.format_chat_message(name, message)
9 local str = core.settings:get("chat_message_format")
10 local error_str = "Invalid chat message format - missing %s"
13 str, i = str:gsub("@name", name, 1)
15 error(error_str:format("@name"), 2)
18 str, i = str:gsub("@message", message, 1)
20 error(error_str:format("@message"), 2)
23 str = str:gsub("@timestamp", os.date("%H:%M:%S", os.time()), 1)
29 -- Chat command handler
32 core.chatcommands = core.registered_chatcommands -- BACKWARDS COMPATIBILITY
34 core.register_on_chat_message(function(name, message)
35 if message:sub(1,1) ~= "/" then
39 local cmd, param = string.match(message, "^/([^ ]+) *(.*)")
41 core.chat_send_player(name, "-!- Empty command")
47 local cmd_def = core.registered_chatcommands[cmd]
49 core.chat_send_player(name, "-!- Invalid command: " .. cmd)
52 local has_privs, missing_privs = core.check_player_privs(name, cmd_def.privs)
54 core.set_last_run_mod(cmd_def.mod_origin)
55 local _, result = cmd_def.func(name, param)
57 core.chat_send_player(name, result)
60 core.chat_send_player(name, "You don't have permission"
61 .. " to run this command (missing privileges: "
62 .. table.concat(missing_privs, ", ") .. ")")
64 return true -- Handled chat message
67 if core.settings:get_bool("profiler.load") then
68 -- Run after register_chatcommand and its register_on_chat_message
69 -- Before any chatcommands that should be profiled
70 profiler.init_chatcommand()
73 -- Parses a "range" string in the format of "here (number)" or
74 -- "(x1, y1, z1) (x2, y2, z2)", returning two position vectors
75 local function parse_range_str(player_name, str)
77 local args = str:split(" ")
79 if args[1] == "here" then
80 p1, p2 = core.get_player_radius_area(player_name, tonumber(args[2]))
82 return false, "Unable to get player " .. player_name .. " position"
85 p1, p2 = core.string_to_area(str)
87 return false, "Incorrect area format. Expected: (x1,y1,z1) (x2,y2,z2)"
97 core.register_chatcommand("me", {
99 description = "Show chat action (e.g., '/me orders a pizza' displays"
100 .. " '<player name> orders a pizza')",
101 privs = {shout=true},
102 func = function(name, param)
103 core.chat_send_all("* " .. name .. " " .. param)
107 core.register_chatcommand("admin", {
108 description = "Show the name of the server owner",
109 func = function(name)
110 local admin = core.settings:get("name")
112 return true, "The administrator of this server is " .. admin .. "."
114 return false, "There's no administrator named in the config file."
119 core.register_chatcommand("privs", {
121 description = "Show privileges of yourself or another player",
122 func = function(caller, param)
124 local name = (param ~= "" and param or caller)
125 if not core.player_exists(name) then
126 return false, "Player " .. name .. " does not exist."
128 return true, "Privileges of " .. name .. ": "
129 .. core.privs_to_string(
130 core.get_player_privs(name), ' ')
134 core.register_chatcommand("haspriv", {
135 params = "<privilege>",
136 description = "Return list of all online players with privilege.",
137 privs = {basic_privs = true},
138 func = function(caller, param)
141 return false, "Invalid parameters (see /help haspriv)"
143 if not core.registered_privileges[param] then
144 return false, "Unknown privilege!"
146 local privs = core.string_to_privs(param)
147 local players_with_priv = {}
148 for _, player in pairs(core.get_connected_players()) do
149 local player_name = player:get_player_name()
150 if core.check_player_privs(player_name, privs) then
151 table.insert(players_with_priv, player_name)
154 return true, "Players online with the \"" .. param .. "\" privilege: " ..
155 table.concat(players_with_priv, ", ")
159 local function handle_grant_command(caller, grantname, grantprivstr)
160 local caller_privs = core.get_player_privs(caller)
161 if not (caller_privs.privs or caller_privs.basic_privs) then
162 return false, "Your privileges are insufficient."
165 if not core.get_auth_handler().get_auth(grantname) then
166 return false, "Player " .. grantname .. " does not exist."
168 local grantprivs = core.string_to_privs(grantprivstr)
169 if grantprivstr == "all" then
170 grantprivs = core.registered_privileges
172 local privs = core.get_player_privs(grantname)
173 local privs_unknown = ""
175 core.string_to_privs(core.settings:get("basic_privs") or "interact,shout")
176 for priv, _ in pairs(grantprivs) do
177 if not basic_privs[priv] and not caller_privs.privs then
178 return false, "Your privileges are insufficient."
180 if not core.registered_privileges[priv] then
181 privs_unknown = privs_unknown .. "Unknown privilege: " .. priv .. "\n"
185 if privs_unknown ~= "" then
186 return false, privs_unknown
188 for priv, _ in pairs(grantprivs) do
189 -- call the on_grant callbacks
190 core.run_priv_callbacks(grantname, priv, caller, "grant")
192 core.set_player_privs(grantname, privs)
193 core.log("action", caller..' granted ('..core.privs_to_string(grantprivs, ', ')..') privileges to '..grantname)
194 if grantname ~= caller then
195 core.chat_send_player(grantname, caller
196 .. " granted you privileges: "
197 .. core.privs_to_string(grantprivs, ' '))
199 return true, "Privileges of " .. grantname .. ": "
200 .. core.privs_to_string(
201 core.get_player_privs(grantname), ' ')
204 core.register_chatcommand("grant", {
205 params = "<name> (<privilege> | all)",
206 description = "Give privileges to player",
207 func = function(name, param)
208 local grantname, grantprivstr = string.match(param, "([^ ]+) (.+)")
209 if not grantname or not grantprivstr then
210 return false, "Invalid parameters (see /help grant)"
212 return handle_grant_command(name, grantname, grantprivstr)
216 core.register_chatcommand("grantme", {
217 params = "<privilege> | all",
218 description = "Grant privileges to yourself",
219 func = function(name, param)
221 return false, "Invalid parameters (see /help grantme)"
223 return handle_grant_command(name, name, param)
227 core.register_chatcommand("revoke", {
228 params = "<name> (<privilege> | all)",
229 description = "Remove privileges from player",
231 func = function(name, param)
232 if not core.check_player_privs(name, {privs=true}) and
233 not core.check_player_privs(name, {basic_privs=true}) then
234 return false, "Your privileges are insufficient."
236 local revoke_name, revoke_priv_str = string.match(param, "([^ ]+) (.+)")
237 if not revoke_name or not revoke_priv_str then
238 return false, "Invalid parameters (see /help revoke)"
239 elseif not core.get_auth_handler().get_auth(revoke_name) then
240 return false, "Player " .. revoke_name .. " does not exist."
242 local revoke_privs = core.string_to_privs(revoke_priv_str)
243 local privs = core.get_player_privs(revoke_name)
245 core.string_to_privs(core.settings:get("basic_privs") or "interact,shout")
246 for priv, _ in pairs(revoke_privs) do
247 if not basic_privs[priv] and
248 not core.check_player_privs(name, {privs=true}) then
249 return false, "Your privileges are insufficient."
252 if revoke_priv_str == "all" then
256 for priv, _ in pairs(revoke_privs) do
261 for priv, _ in pairs(revoke_privs) do
262 -- call the on_revoke callbacks
263 core.run_priv_callbacks(revoke_name, priv, name, "revoke")
266 core.set_player_privs(revoke_name, privs)
267 core.log("action", name..' revoked ('
268 ..core.privs_to_string(revoke_privs, ', ')
269 ..') privileges from '..revoke_name)
270 if revoke_name ~= name then
271 core.chat_send_player(revoke_name, name
272 .. " revoked privileges from you: "
273 .. core.privs_to_string(revoke_privs, ' '))
275 return true, "Privileges of " .. revoke_name .. ": "
276 .. core.privs_to_string(
277 core.get_player_privs(revoke_name), ' ')
281 core.register_chatcommand("setpassword", {
282 params = "<name> <password>",
283 description = "Set player's password",
284 privs = {password=true},
285 func = function(name, param)
286 local toname, raw_password = string.match(param, "^([^ ]+) +(.+)$")
288 toname = param:match("^([^ ]+) *$")
293 return false, "Name field required"
296 local act_str_past, act_str_pres
297 if not raw_password then
298 core.set_player_password(toname, "")
299 act_str_past = "cleared"
300 act_str_pres = "clears"
302 core.set_player_password(toname,
303 core.get_password_hash(toname,
306 act_str_pres = "sets"
309 if toname ~= name then
310 core.chat_send_player(toname, "Your password was "
311 .. act_str_past .. " by " .. name)
314 core.log("action", name .. " " .. act_str_pres ..
315 " password of " .. toname .. ".")
317 return true, "Password of player \"" .. toname .. "\" " .. act_str_past
321 core.register_chatcommand("clearpassword", {
323 description = "Set empty password for a player",
324 privs = {password=true},
325 func = function(name, param)
328 return false, "Name field required"
330 core.set_player_password(toname, '')
332 core.log("action", name .. " clears password of " .. toname .. ".")
334 return true, "Password of player \"" .. toname .. "\" cleared"
338 core.register_chatcommand("auth_reload", {
340 description = "Reload authentication data",
341 privs = {server=true},
342 func = function(name, param)
343 local done = core.auth_reload()
344 return done, (done and "Done." or "Failed.")
348 core.register_chatcommand("remove_player", {
350 description = "Remove a player's data",
351 privs = {server=true},
352 func = function(name, param)
355 return false, "Name field required"
358 local rc = core.remove_player(toname)
361 core.log("action", name .. " removed player data of " .. toname .. ".")
362 return true, "Player \"" .. toname .. "\" removed."
364 return true, "No such player \"" .. toname .. "\" to remove."
366 return true, "Player \"" .. toname .. "\" is connected, cannot remove."
369 return false, "Unhandled remove_player return code " .. rc .. ""
373 core.register_chatcommand("teleport", {
374 params = "<X>,<Y>,<Z> | <to_name> | (<name> <X>,<Y>,<Z>) | (<name> <to_name>)",
375 description = "Teleport to position or player",
376 privs = {teleport=true},
377 func = function(name, param)
378 -- Returns (pos, true) if found, otherwise (pos, false)
379 local function find_free_position_near(pos)
386 for _, d in ipairs(tries) do
387 local p = {x = pos.x+d.x, y = pos.y+d.y, z = pos.z+d.z}
388 local n = core.get_node_or_nil(p)
390 local def = core.registered_nodes[n.name]
391 if def and not def.walkable then
400 p.x, p.y, p.z = string.match(param, "^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
404 if p.x and p.y and p.z then
407 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
408 return false, "Cannot teleport out of map bounds!"
410 local teleportee = core.get_player_by_name(name)
412 teleportee:set_pos(p)
413 return true, "Teleporting to "..core.pos_to_string(p)
417 local target_name = param:match("^([^ ]+)$")
418 local teleportee = core.get_player_by_name(name)
422 local target = core.get_player_by_name(target_name)
428 if teleportee and p then
429 p = find_free_position_near(p)
430 teleportee:set_pos(p)
431 return true, "Teleporting to " .. target_name
432 .. " at "..core.pos_to_string(p)
435 if not core.check_player_privs(name, {bring=true}) then
436 return false, "You don't have permission to teleport other players (missing bring privilege)"
441 local teleportee_name
442 teleportee_name, p.x, p.y, p.z = param:match(
443 "^([^ ]+) +([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
444 p.x, p.y, p.z = tonumber(p.x), tonumber(p.y), tonumber(p.z)
445 if teleportee_name then
446 teleportee = core.get_player_by_name(teleportee_name)
448 if teleportee and p.x and p.y and p.z then
449 teleportee:set_pos(p)
450 return true, "Teleporting " .. teleportee_name
451 .. " to " .. core.pos_to_string(p)
456 teleportee_name, target_name = string.match(param, "^([^ ]+) +([^ ]+)$")
457 if teleportee_name then
458 teleportee = core.get_player_by_name(teleportee_name)
461 local target = core.get_player_by_name(target_name)
466 if teleportee and p then
467 p = find_free_position_near(p)
468 teleportee:set_pos(p)
469 return true, "Teleporting " .. teleportee_name
470 .. " to " .. target_name
471 .. " at " .. core.pos_to_string(p)
474 return false, 'Invalid parameters ("' .. param
475 .. '") or player not found (see /help teleport)'
479 core.register_chatcommand("set", {
480 params = "([-n] <name> <value>) | <name>",
481 description = "Set or read server configuration setting",
482 privs = {server=true},
483 func = function(name, param)
484 local arg, setname, setvalue = string.match(param, "(-[n]) ([^ ]+) (.+)")
485 if arg and arg == "-n" and setname and setvalue then
486 core.settings:set(setname, setvalue)
487 return true, setname .. " = " .. setvalue
490 setname, setvalue = string.match(param, "([^ ]+) (.+)")
491 if setname and setvalue then
492 if not core.settings:get(setname) then
493 return false, "Failed. Use '/set -n <name> <value>' to create a new setting."
495 core.settings:set(setname, setvalue)
496 return true, setname .. " = " .. setvalue
499 setname = string.match(param, "([^ ]+)")
501 setvalue = core.settings:get(setname)
503 setvalue = "<not set>"
505 return true, setname .. " = " .. setvalue
508 return false, "Invalid parameters (see /help set)."
512 local function emergeblocks_callback(pos, action, num_calls_remaining, ctx)
513 if ctx.total_blocks == 0 then
514 ctx.total_blocks = num_calls_remaining + 1
515 ctx.current_blocks = 0
517 ctx.current_blocks = ctx.current_blocks + 1
519 if ctx.current_blocks == ctx.total_blocks then
520 core.chat_send_player(ctx.requestor_name,
521 string.format("Finished emerging %d blocks in %.2fms.",
522 ctx.total_blocks, (os.clock() - ctx.start_time) * 1000))
526 local function emergeblocks_progress_update(ctx)
527 if ctx.current_blocks ~= ctx.total_blocks then
528 core.chat_send_player(ctx.requestor_name,
529 string.format("emergeblocks update: %d/%d blocks emerged (%.1f%%)",
530 ctx.current_blocks, ctx.total_blocks,
531 (ctx.current_blocks / ctx.total_blocks) * 100))
533 core.after(2, emergeblocks_progress_update, ctx)
537 core.register_chatcommand("emergeblocks", {
538 params = "(here [<radius>]) | (<pos1> <pos2>)",
539 description = "Load (or, if nonexistent, generate) map blocks "
540 .. "contained in area pos1 to pos2 (<pos1> and <pos2> must be in parentheses)",
541 privs = {server=true},
542 func = function(name, param)
543 local p1, p2 = parse_range_str(name, param)
551 start_time = os.clock(),
552 requestor_name = name
555 core.emerge_area(p1, p2, emergeblocks_callback, context)
556 core.after(2, emergeblocks_progress_update, context)
558 return true, "Started emerge of area ranging from " ..
559 core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
563 core.register_chatcommand("deleteblocks", {
564 params = "(here [<radius>]) | (<pos1> <pos2>)",
565 description = "Delete map blocks contained in area pos1 to pos2 "
566 .. "(<pos1> and <pos2> must be in parentheses)",
567 privs = {server=true},
568 func = function(name, param)
569 local p1, p2 = parse_range_str(name, param)
574 if core.delete_area(p1, p2) then
575 return true, "Successfully cleared area ranging from " ..
576 core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
578 return false, "Failed to clear one or more blocks in area"
583 core.register_chatcommand("fixlight", {
584 params = "(here [<radius>]) | (<pos1> <pos2>)",
585 description = "Resets lighting in the area between pos1 and pos2 "
586 .. "(<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)
594 if core.fix_light(p1, p2) then
595 return true, "Successfully reset light in the area ranging from " ..
596 core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
598 return false, "Failed to load one or more blocks in area"
603 core.register_chatcommand("mods", {
605 description = "List mods installed on the server",
607 func = function(name, param)
608 return true, table.concat(core.get_modnames(), ", ")
612 local function handle_give_command(cmd, giver, receiver, stackstring)
613 core.log("action", giver .. " invoked " .. cmd
614 .. ', stackstring="' .. stackstring .. '"')
615 local itemstack = ItemStack(stackstring)
616 if itemstack:is_empty() then
617 return false, "Cannot give an empty item"
618 elseif (not itemstack:is_known()) or (itemstack:get_name() == "unknown") then
619 return false, "Cannot give an unknown item"
620 -- Forbid giving 'ignore' due to unwanted side effects
621 elseif itemstack:get_name() == "ignore" then
622 return false, "Giving 'ignore' is not allowed"
624 local receiverref = core.get_player_by_name(receiver)
625 if receiverref == nil then
626 return false, receiver .. " is not a known player"
628 local leftover = receiverref:get_inventory():add_item("main", itemstack)
630 if leftover:is_empty() then
632 elseif leftover:get_count() == itemstack:get_count() then
633 partiality = "could not be "
635 partiality = "partially "
637 -- The actual item stack string may be different from what the "giver"
638 -- entered (e.g. big numbers are always interpreted as 2^16-1).
639 stackstring = itemstack:to_string()
640 if giver == receiver then
641 local msg = "%q %sadded to inventory."
642 return true, msg:format(stackstring, partiality)
644 core.chat_send_player(receiver, ("%q %sadded to inventory.")
645 :format(stackstring, partiality))
646 local msg = "%q %sadded to %s's inventory."
647 return true, msg:format(stackstring, partiality, receiver)
651 core.register_chatcommand("give", {
652 params = "<name> <ItemString> [<count> [<wear>]]",
653 description = "Give item to player",
655 func = function(name, param)
656 local toname, itemstring = string.match(param, "^([^ ]+) +(.+)$")
657 if not toname or not itemstring then
658 return false, "Name and ItemString required"
660 return handle_give_command("/give", name, toname, itemstring)
664 core.register_chatcommand("giveme", {
665 params = "<ItemString> [<count> [<wear>]]",
666 description = "Give item to yourself",
668 func = function(name, param)
669 local itemstring = string.match(param, "(.+)$")
670 if not itemstring then
671 return false, "ItemString required"
673 return handle_give_command("/giveme", name, name, itemstring)
677 core.register_chatcommand("spawnentity", {
678 params = "<EntityName> [<X>,<Y>,<Z>]",
679 description = "Spawn entity at given (or your) position",
680 privs = {give=true, interact=true},
681 func = function(name, param)
682 local entityname, p = string.match(param, "^([^ ]+) *(.*)$")
683 if not entityname then
684 return false, "EntityName required"
686 core.log("action", ("%s invokes /spawnentity, entityname=%q")
687 :format(name, entityname))
688 local player = core.get_player_by_name(name)
689 if player == nil then
690 core.log("error", "Unable to spawn entity, player is nil")
691 return false, "Unable to spawn entity, player is nil"
693 if not core.registered_entities[entityname] then
694 return false, "Cannot spawn an unknown entity"
699 p = core.string_to_pos(p)
701 return false, "Invalid parameters ('" .. param .. "')"
705 core.add_entity(p, entityname)
706 return true, ("%q spawned."):format(entityname)
710 core.register_chatcommand("pulverize", {
712 description = "Destroy item in hand",
713 func = function(name, param)
714 local player = core.get_player_by_name(name)
716 core.log("error", "Unable to pulverize, no player.")
717 return false, "Unable to pulverize, no player."
719 local wielded_item = player:get_wielded_item()
720 if wielded_item:is_empty() then
721 return false, "Unable to pulverize, no item in hand."
723 core.log("action", name .. " pulverized \"" ..
724 wielded_item:get_name() .. " " .. wielded_item:get_count() .. "\"")
725 player:set_wielded_item(nil)
726 return true, "An item was pulverized."
731 core.rollback_punch_callbacks = {}
733 core.register_on_punchnode(function(pos, node, puncher)
734 local name = puncher and puncher:get_player_name()
735 if name and core.rollback_punch_callbacks[name] then
736 core.rollback_punch_callbacks[name](pos, node, puncher)
737 core.rollback_punch_callbacks[name] = nil
741 core.register_chatcommand("rollback_check", {
742 params = "[<range>] [<seconds>] [<limit>]",
743 description = "Check who last touched a node or a node near it"
744 .. " within the time specified by <seconds>. Default: range = 0,"
745 .. " seconds = 86400 = 24h, limit = 5",
746 privs = {rollback=true},
747 func = function(name, param)
748 if not core.settings:get_bool("enable_rollback_recording") then
749 return false, "Rollback functions are disabled."
751 local range, seconds, limit =
752 param:match("(%d+) *(%d*) *(%d*)")
753 range = tonumber(range) or 0
754 seconds = tonumber(seconds) or 86400
755 limit = tonumber(limit) or 5
757 return false, "That limit is too high!"
760 core.rollback_punch_callbacks[name] = function(pos, node, puncher)
761 local name = puncher:get_player_name()
762 core.chat_send_player(name, "Checking " .. core.pos_to_string(pos) .. "...")
763 local actions = core.rollback_get_node_actions(pos, range, seconds, limit)
765 core.chat_send_player(name, "Rollback functions are disabled")
768 local num_actions = #actions
769 if num_actions == 0 then
770 core.chat_send_player(name, "Nobody has touched"
771 .. " the specified location in "
772 .. seconds .. " seconds")
775 local time = os.time()
776 for i = num_actions, 1, -1 do
777 local action = actions[i]
778 core.chat_send_player(name,
779 ("%s %s %s -> %s %d seconds ago.")
781 core.pos_to_string(action.pos),
789 return true, "Punch a node (range=" .. range .. ", seconds="
790 .. seconds .. "s, limit=" .. limit .. ")"
794 core.register_chatcommand("rollback", {
795 params = "(<name> [<seconds>]) | (:<actor> [<seconds>])",
796 description = "Revert actions of a player. Default for <seconds> is 60",
797 privs = {rollback=true},
798 func = function(name, param)
799 if not core.settings:get_bool("enable_rollback_recording") then
800 return false, "Rollback functions are disabled."
802 local target_name, seconds = string.match(param, ":([^ ]+) *(%d*)")
803 if not target_name then
805 player_name, seconds = string.match(param, "([^ ]+) *(%d*)")
806 if not player_name then
807 return false, "Invalid parameters. See /help rollback"
808 .. " and /help rollback_check."
810 target_name = "player:"..player_name
812 seconds = tonumber(seconds) or 60
813 core.chat_send_player(name, "Reverting actions of "
814 .. target_name .. " since "
815 .. seconds .. " seconds.")
816 local success, log = core.rollback_revert_actions_by(
817 target_name, seconds)
820 response = "(log is too long to show)\n"
822 for _, line in pairs(log) do
823 response = response .. line .. "\n"
826 response = response .. "Reverting actions "
827 .. (success and "succeeded." or "FAILED.")
828 return success, response
832 core.register_chatcommand("status", {
833 description = "Show server status",
834 func = function(name, param)
835 local status = core.get_server_status(name, false)
836 if status and status ~= "" then
839 return false, "This command was disabled by a mod or game"
843 core.register_chatcommand("time", {
844 params = "[<0..23>:<0..59> | <0..24000>]",
845 description = "Show or set time of day",
847 func = function(name, param)
849 local current_time = math.floor(core.get_timeofday() * 1440)
850 local minutes = current_time % 60
851 local hour = (current_time - minutes) / 60
852 return true, ("Current time is %d:%02d"):format(hour, minutes)
854 local player_privs = core.get_player_privs(name)
855 if not player_privs.settime then
856 return false, "You don't have permission to run this command " ..
857 "(missing privilege: settime)."
859 local hour, minute = param:match("^(%d+):(%d+)$")
861 local new_time = tonumber(param)
863 return false, "Invalid time."
865 -- Backward compatibility.
866 core.set_timeofday((new_time % 24000) / 24000)
867 core.log("action", name .. " sets time to " .. new_time)
868 return true, "Time of day changed."
870 hour = tonumber(hour)
871 minute = tonumber(minute)
872 if hour < 0 or hour > 23 then
873 return false, "Invalid hour (must be between 0 and 23 inclusive)."
874 elseif minute < 0 or minute > 59 then
875 return false, "Invalid minute (must be between 0 and 59 inclusive)."
877 core.set_timeofday((hour * 60 + minute) / 1440)
878 core.log("action", ("%s sets time to %d:%02d"):format(name, hour, minute))
879 return true, "Time of day changed."
883 core.register_chatcommand("days", {
884 description = "Show day count since world creation",
885 func = function(name, param)
886 return true, "Current day is " .. core.get_day_count()
890 core.register_chatcommand("shutdown", {
891 params = "[<delay_in_seconds> | -1] [reconnect] [<message>]",
892 description = "Shutdown server (-1 cancels a delayed shutdown)",
893 privs = {server=true},
894 func = function(name, param)
895 local delay, reconnect, message
896 delay, param = param:match("^%s*(%S+)(.*)")
898 reconnect, param = param:match("^%s*(%S+)(.*)")
900 message = param and param:match("^%s*(.+)") or ""
901 delay = tonumber(delay) or 0
904 core.log("action", name .. " shuts down server")
905 core.chat_send_all("*** Server shutting down (operator request).")
907 core.request_shutdown(message:trim(), core.is_yes(reconnect), delay)
911 core.register_chatcommand("ban", {
913 description = "Ban the IP of a player or show the ban list",
915 func = function(name, param)
917 local ban_list = core.get_ban_list()
918 if ban_list == "" then
919 return true, "The ban list is empty."
921 return true, "Ban list: " .. ban_list
924 if not core.get_player_by_name(param) then
925 return false, "Player is not online."
927 if not core.ban_player(param) then
928 return false, "Failed to ban player."
930 local desc = core.get_ban_description(param)
931 core.log("action", name .. " bans " .. desc .. ".")
932 return true, "Banned " .. desc .. "."
936 core.register_chatcommand("unban", {
937 params = "<name> | <IP_address>",
938 description = "Remove IP ban belonging to a player/IP",
940 func = function(name, param)
941 if not core.unban_player_or_ip(param) then
942 return false, "Failed to unban player/IP."
944 core.log("action", name .. " unbans " .. param)
945 return true, "Unbanned " .. param
949 core.register_chatcommand("kick", {
950 params = "<name> [<reason>]",
951 description = "Kick a player",
953 func = function(name, param)
954 local tokick, reason = param:match("([^ ]+) (.+)")
955 tokick = tokick or param
956 if not core.kick_player(tokick, reason) then
957 return false, "Failed to kick player " .. tokick
959 local log_reason = ""
961 log_reason = " with reason \"" .. reason .. "\""
963 core.log("action", name .. " kicks " .. tokick .. log_reason)
964 return true, "Kicked " .. tokick
968 core.register_chatcommand("clearobjects", {
969 params = "[full | quick]",
970 description = "Clear all objects in world",
971 privs = {server=true},
972 func = function(name, param)
974 if param == "" or param == "quick" then
975 options.mode = "quick"
976 elseif param == "full" then
977 options.mode = "full"
979 return false, "Invalid usage, see /help clearobjects."
982 core.log("action", name .. " clears all objects ("
983 .. options.mode .. " mode).")
984 core.chat_send_all("Clearing all objects. This may take a long time."
985 .. " You may experience a timeout. (by "
987 core.clear_objects(options)
988 core.log("action", "Object clearing done.")
989 core.chat_send_all("*** Cleared all objects.")
993 core.register_chatcommand("msg", {
994 params = "<name> <message>",
995 description = "Send a direct message to a player",
996 privs = {shout=true},
997 func = function(name, param)
998 local sendto, message = param:match("^(%S+)%s(.+)$")
1000 return false, "Invalid usage, see /help msg."
1002 if not core.get_player_by_name(sendto) then
1003 return false, "The player " .. sendto
1004 .. " is not online."
1006 core.log("action", "DM from " .. name .. " to " .. sendto
1008 core.chat_send_player(sendto, "DM from " .. name .. ": "
1010 return true, "Message sent."
1014 core.register_chatcommand("last-login", {
1015 params = "[<name>]",
1016 description = "Get the last login time of a player or yourself",
1017 func = function(name, param)
1021 local pauth = core.get_auth_handler().get_auth(param)
1022 if pauth and pauth.last_login then
1023 -- Time in UTC, ISO 8601 format
1024 return true, "Last login time was " ..
1025 os.date("!%Y-%m-%dT%H:%M:%SZ", pauth.last_login)
1027 return false, "Last login time is unknown"
1031 core.register_chatcommand("clearinv", {
1032 params = "[<name>]",
1033 description = "Clear the inventory of yourself or another player",
1034 func = function(name, param)
1036 if param and param ~= "" and param ~= name then
1037 if not core.check_player_privs(name, {server=true}) then
1038 return false, "You don't have permission"
1039 .. " to clear another player's inventory (missing privilege: server)"
1041 player = core.get_player_by_name(param)
1042 core.chat_send_player(param, name.." cleared your inventory.")
1044 player = core.get_player_by_name(name)
1048 player:get_inventory():set_list("main", {})
1049 player:get_inventory():set_list("craft", {})
1050 player:get_inventory():set_list("craftpreview", {})
1051 core.log("action", name.." clears "..player:get_player_name().."'s inventory")
1052 return true, "Cleared "..player:get_player_name().."'s inventory."
1054 return false, "Player must be online to clear inventory!"
1059 local function handle_kill_command(killer, victim)
1060 if core.settings:get_bool("enable_damage") == false then
1061 return false, "Players can't be killed, damage has been disabled."
1063 local victimref = core.get_player_by_name(victim)
1064 if victimref == nil then
1065 return false, string.format("Player %s is not online.", victim)
1066 elseif victimref:get_hp() <= 0 then
1067 if killer == victim then
1068 return false, "You are already dead."
1070 return false, string.format("%s is already dead.", victim)
1073 if not killer == victim then
1074 core.log("action", string.format("%s killed %s", killer, victim))
1078 return true, string.format("%s has been killed.", victim)
1081 core.register_chatcommand("kill", {
1082 params = "[<name>]",
1083 description = "Kill player or yourself",
1084 privs = {server=true},
1085 func = function(name, param)
1086 return handle_kill_command(name, param == "" and name or param)