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)
121 core.register_chatcommand("admin", {
122 description = "Show the name of the server owner",
123 func = function(name)
124 local admin = core.settings:get("name")
126 return true, "The administrator of this server is " .. admin .. "."
128 return false, "There's no administrator named in the config file."
133 core.register_chatcommand("privs", {
135 description = "Show privileges of yourself or another player",
136 func = function(caller, param)
138 local name = (param ~= "" and param or caller)
139 if not core.player_exists(name) then
140 return false, "Player " .. name .. " does not exist."
142 return true, "Privileges of " .. name .. ": "
143 .. core.privs_to_string(
144 core.get_player_privs(name), ' ')
148 core.register_chatcommand("haspriv", {
149 params = "<privilege>",
150 description = "Return list of all online players with privilege.",
151 privs = {basic_privs = true},
152 func = function(caller, param)
155 return false, "Invalid parameters (see /help haspriv)"
157 if not core.registered_privileges[param] then
158 return false, "Unknown privilege!"
160 local privs = core.string_to_privs(param)
161 local players_with_priv = {}
162 for _, player in pairs(core.get_connected_players()) do
163 local player_name = player:get_player_name()
164 if core.check_player_privs(player_name, privs) then
165 table.insert(players_with_priv, player_name)
168 return true, "Players online with the \"" .. param .. "\" privilege: " ..
169 table.concat(players_with_priv, ", ")
173 local function handle_grant_command(caller, grantname, grantprivstr)
174 local caller_privs = core.get_player_privs(caller)
175 if not (caller_privs.privs or caller_privs.basic_privs) then
176 return false, "Your privileges are insufficient."
179 if not core.get_auth_handler().get_auth(grantname) then
180 return false, "Player " .. grantname .. " does not exist."
182 local grantprivs = core.string_to_privs(grantprivstr)
183 if grantprivstr == "all" then
184 grantprivs = core.registered_privileges
186 local privs = core.get_player_privs(grantname)
187 local privs_unknown = ""
189 core.string_to_privs(core.settings:get("basic_privs") or "interact,shout")
190 for priv, _ in pairs(grantprivs) do
191 if not basic_privs[priv] and not caller_privs.privs then
192 return false, "Your privileges are insufficient."
194 if not core.registered_privileges[priv] then
195 privs_unknown = privs_unknown .. "Unknown privilege: " .. priv .. "\n"
199 if privs_unknown ~= "" then
200 return false, privs_unknown
202 for priv, _ in pairs(grantprivs) do
203 -- call the on_grant callbacks
204 core.run_priv_callbacks(grantname, priv, caller, "grant")
206 core.set_player_privs(grantname, privs)
207 core.log("action", caller..' granted ('..core.privs_to_string(grantprivs, ', ')..') privileges to '..grantname)
208 if grantname ~= caller then
209 core.chat_send_player(grantname, caller
210 .. " granted you privileges: "
211 .. core.privs_to_string(grantprivs, ' '))
213 return true, "Privileges of " .. grantname .. ": "
214 .. core.privs_to_string(
215 core.get_player_privs(grantname), ' ')
218 core.register_chatcommand("grant", {
219 params = "<name> (<privilege> | all)",
220 description = "Give privileges to player",
221 func = function(name, param)
222 local grantname, grantprivstr = string.match(param, "([^ ]+) (.+)")
223 if not grantname or not grantprivstr then
224 return false, "Invalid parameters (see /help grant)"
226 return handle_grant_command(name, grantname, grantprivstr)
230 core.register_chatcommand("grantme", {
231 params = "<privilege> | all",
232 description = "Grant privileges to yourself",
233 func = function(name, param)
235 return false, "Invalid parameters (see /help grantme)"
237 return handle_grant_command(name, name, param)
241 core.register_chatcommand("revoke", {
242 params = "<name> (<privilege> | all)",
243 description = "Remove privileges from player",
245 func = function(name, param)
246 if not core.check_player_privs(name, {privs=true}) and
247 not core.check_player_privs(name, {basic_privs=true}) then
248 return false, "Your privileges are insufficient."
250 local revoke_name, revoke_priv_str = string.match(param, "([^ ]+) (.+)")
251 if not revoke_name or not revoke_priv_str then
252 return false, "Invalid parameters (see /help revoke)"
253 elseif not core.get_auth_handler().get_auth(revoke_name) then
254 return false, "Player " .. revoke_name .. " does not exist."
256 local revoke_privs = core.string_to_privs(revoke_priv_str)
257 local privs = core.get_player_privs(revoke_name)
259 core.string_to_privs(core.settings:get("basic_privs") or "interact,shout")
260 for priv, _ in pairs(revoke_privs) do
261 if not basic_privs[priv] and
262 not core.check_player_privs(name, {privs=true}) then
263 return false, "Your privileges are insufficient."
266 if revoke_priv_str == "all" then
270 for priv, _ in pairs(revoke_privs) do
275 for priv, _ in pairs(revoke_privs) do
276 -- call the on_revoke callbacks
277 core.run_priv_callbacks(revoke_name, priv, name, "revoke")
280 core.set_player_privs(revoke_name, privs)
281 core.log("action", name..' revoked ('
282 ..core.privs_to_string(revoke_privs, ', ')
283 ..') privileges from '..revoke_name)
284 if revoke_name ~= name then
285 core.chat_send_player(revoke_name, name
286 .. " revoked privileges from you: "
287 .. core.privs_to_string(revoke_privs, ' '))
289 return true, "Privileges of " .. revoke_name .. ": "
290 .. core.privs_to_string(
291 core.get_player_privs(revoke_name), ' ')
295 core.register_chatcommand("setpassword", {
296 params = "<name> <password>",
297 description = "Set player's password",
298 privs = {password=true},
299 func = function(name, param)
300 local toname, raw_password = string.match(param, "^([^ ]+) +(.+)$")
302 toname = param:match("^([^ ]+) *$")
307 return false, "Name field required"
310 local act_str_past, act_str_pres
311 if not raw_password then
312 core.set_player_password(toname, "")
313 act_str_past = "cleared"
314 act_str_pres = "clears"
316 core.set_player_password(toname,
317 core.get_password_hash(toname,
320 act_str_pres = "sets"
323 if toname ~= name then
324 core.chat_send_player(toname, "Your password was "
325 .. act_str_past .. " by " .. name)
328 core.log("action", name .. " " .. act_str_pres ..
329 " password of " .. toname .. ".")
331 return true, "Password of player \"" .. toname .. "\" " .. act_str_past
335 core.register_chatcommand("clearpassword", {
337 description = "Set empty password for a player",
338 privs = {password=true},
339 func = function(name, param)
342 return false, "Name field required"
344 core.set_player_password(toname, '')
346 core.log("action", name .. " clears password of " .. toname .. ".")
348 return true, "Password of player \"" .. toname .. "\" cleared"
352 core.register_chatcommand("auth_reload", {
354 description = "Reload authentication data",
355 privs = {server=true},
356 func = function(name, param)
357 local done = core.auth_reload()
358 return done, (done and "Done." or "Failed.")
362 core.register_chatcommand("remove_player", {
364 description = "Remove a player's data",
365 privs = {server=true},
366 func = function(name, param)
369 return false, "Name field required"
372 local rc = core.remove_player(toname)
375 core.log("action", name .. " removed player data of " .. toname .. ".")
376 return true, "Player \"" .. toname .. "\" removed."
378 return true, "No such player \"" .. toname .. "\" to remove."
380 return true, "Player \"" .. toname .. "\" is connected, cannot remove."
383 return false, "Unhandled remove_player return code " .. rc .. ""
387 core.register_chatcommand("teleport", {
388 params = "<X>,<Y>,<Z> | <to_name> | (<name> <X>,<Y>,<Z>) | (<name> <to_name>)",
389 description = "Teleport to position or player",
390 privs = {teleport=true},
391 func = function(name, param)
392 -- Returns (pos, true) if found, otherwise (pos, false)
393 local function find_free_position_near(pos)
400 for _, d in ipairs(tries) do
401 local p = {x = pos.x+d.x, y = pos.y+d.y, z = pos.z+d.z}
402 local n = core.get_node_or_nil(p)
404 local def = core.registered_nodes[n.name]
405 if def and not def.walkable then
414 p.x, p.y, p.z = string.match(param, "^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
418 if p.x and p.y and p.z then
421 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
422 return false, "Cannot teleport out of map bounds!"
424 local teleportee = core.get_player_by_name(name)
426 teleportee:set_pos(p)
427 return true, "Teleporting to "..core.pos_to_string(p)
431 local target_name = param:match("^([^ ]+)$")
432 local teleportee = core.get_player_by_name(name)
436 local target = core.get_player_by_name(target_name)
442 if teleportee and p then
443 p = find_free_position_near(p)
444 teleportee:set_pos(p)
445 return true, "Teleporting to " .. target_name
446 .. " at "..core.pos_to_string(p)
449 if not core.check_player_privs(name, {bring=true}) then
450 return false, "You don't have permission to teleport other players (missing bring privilege)"
455 local teleportee_name
456 teleportee_name, p.x, p.y, p.z = param:match(
457 "^([^ ]+) +([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
458 p.x, p.y, p.z = tonumber(p.x), tonumber(p.y), tonumber(p.z)
459 if teleportee_name then
460 teleportee = core.get_player_by_name(teleportee_name)
462 if teleportee and p.x and p.y and p.z then
463 teleportee:set_pos(p)
464 return true, "Teleporting " .. teleportee_name
465 .. " to " .. core.pos_to_string(p)
470 teleportee_name, target_name = string.match(param, "^([^ ]+) +([^ ]+)$")
471 if teleportee_name then
472 teleportee = core.get_player_by_name(teleportee_name)
475 local target = core.get_player_by_name(target_name)
480 if teleportee and p then
481 p = find_free_position_near(p)
482 teleportee:set_pos(p)
483 return true, "Teleporting " .. teleportee_name
484 .. " to " .. target_name
485 .. " at " .. core.pos_to_string(p)
488 return false, 'Invalid parameters ("' .. param
489 .. '") or player not found (see /help teleport)'
493 core.register_chatcommand("set", {
494 params = "([-n] <name> <value>) | <name>",
495 description = "Set or read server configuration setting",
496 privs = {server=true},
497 func = function(name, param)
498 local arg, setname, setvalue = string.match(param, "(-[n]) ([^ ]+) (.+)")
499 if arg and arg == "-n" and setname and setvalue then
500 core.settings:set(setname, setvalue)
501 return true, setname .. " = " .. setvalue
504 setname, setvalue = string.match(param, "([^ ]+) (.+)")
505 if setname and setvalue then
506 if not core.settings:get(setname) then
507 return false, "Failed. Use '/set -n <name> <value>' to create a new setting."
509 core.settings:set(setname, setvalue)
510 return true, setname .. " = " .. setvalue
513 setname = string.match(param, "([^ ]+)")
515 setvalue = core.settings:get(setname)
517 setvalue = "<not set>"
519 return true, setname .. " = " .. setvalue
522 return false, "Invalid parameters (see /help set)."
526 local function emergeblocks_callback(pos, action, num_calls_remaining, ctx)
527 if ctx.total_blocks == 0 then
528 ctx.total_blocks = num_calls_remaining + 1
529 ctx.current_blocks = 0
531 ctx.current_blocks = ctx.current_blocks + 1
533 if ctx.current_blocks == ctx.total_blocks then
534 core.chat_send_player(ctx.requestor_name,
535 string.format("Finished emerging %d blocks in %.2fms.",
536 ctx.total_blocks, (os.clock() - ctx.start_time) * 1000))
540 local function emergeblocks_progress_update(ctx)
541 if ctx.current_blocks ~= ctx.total_blocks then
542 core.chat_send_player(ctx.requestor_name,
543 string.format("emergeblocks update: %d/%d blocks emerged (%.1f%%)",
544 ctx.current_blocks, ctx.total_blocks,
545 (ctx.current_blocks / ctx.total_blocks) * 100))
547 core.after(2, emergeblocks_progress_update, ctx)
551 core.register_chatcommand("emergeblocks", {
552 params = "(here [<radius>]) | (<pos1> <pos2>)",
553 description = "Load (or, if nonexistent, generate) map blocks "
554 .. "contained in area pos1 to pos2 (<pos1> and <pos2> must be in parentheses)",
555 privs = {server=true},
556 func = function(name, param)
557 local p1, p2 = parse_range_str(name, param)
565 start_time = os.clock(),
566 requestor_name = name
569 core.emerge_area(p1, p2, emergeblocks_callback, context)
570 core.after(2, emergeblocks_progress_update, context)
572 return true, "Started emerge of area ranging from " ..
573 core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
577 core.register_chatcommand("deleteblocks", {
578 params = "(here [<radius>]) | (<pos1> <pos2>)",
579 description = "Delete map blocks contained in area pos1 to pos2 "
580 .. "(<pos1> and <pos2> must be in parentheses)",
581 privs = {server=true},
582 func = function(name, param)
583 local p1, p2 = parse_range_str(name, param)
588 if core.delete_area(p1, p2) then
589 return true, "Successfully cleared area ranging from " ..
590 core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
592 return false, "Failed to clear one or more blocks in area"
597 core.register_chatcommand("fixlight", {
598 params = "(here [<radius>]) | (<pos1> <pos2>)",
599 description = "Resets lighting in the area between pos1 and 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.fix_light(p1, p2) then
609 return true, "Successfully reset light in the area ranging from " ..
610 core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
612 return false, "Failed to load one or more blocks in area"
617 core.register_chatcommand("mods", {
619 description = "List mods installed on the server",
621 func = function(name, param)
622 return true, table.concat(core.get_modnames(), ", ")
626 local function handle_give_command(cmd, giver, receiver, stackstring)
627 core.log("action", giver .. " invoked " .. cmd
628 .. ', stackstring="' .. stackstring .. '"')
629 local itemstack = ItemStack(stackstring)
630 if itemstack:is_empty() then
631 return false, "Cannot give an empty item"
632 elseif (not itemstack:is_known()) or (itemstack:get_name() == "unknown") then
633 return false, "Cannot give an unknown item"
634 -- Forbid giving 'ignore' due to unwanted side effects
635 elseif itemstack:get_name() == "ignore" then
636 return false, "Giving 'ignore' is not allowed"
638 local receiverref = core.get_player_by_name(receiver)
639 if receiverref == nil then
640 return false, receiver .. " is not a known player"
642 local leftover = receiverref:get_inventory():add_item("main", itemstack)
644 if leftover:is_empty() then
646 elseif leftover:get_count() == itemstack:get_count() then
647 partiality = "could not be "
649 partiality = "partially "
651 -- The actual item stack string may be different from what the "giver"
652 -- entered (e.g. big numbers are always interpreted as 2^16-1).
653 stackstring = itemstack:to_string()
654 if giver == receiver then
655 local msg = "%q %sadded to inventory."
656 return true, msg:format(stackstring, partiality)
658 core.chat_send_player(receiver, ("%q %sadded to inventory.")
659 :format(stackstring, partiality))
660 local msg = "%q %sadded to %s's inventory."
661 return true, msg:format(stackstring, partiality, receiver)
665 core.register_chatcommand("give", {
666 params = "<name> <ItemString> [<count> [<wear>]]",
667 description = "Give item to player",
669 func = function(name, param)
670 local toname, itemstring = string.match(param, "^([^ ]+) +(.+)$")
671 if not toname or not itemstring then
672 return false, "Name and ItemString required"
674 return handle_give_command("/give", name, toname, itemstring)
678 core.register_chatcommand("giveme", {
679 params = "<ItemString> [<count> [<wear>]]",
680 description = "Give item to yourself",
682 func = function(name, param)
683 local itemstring = string.match(param, "(.+)$")
684 if not itemstring then
685 return false, "ItemString required"
687 return handle_give_command("/giveme", name, name, itemstring)
691 core.register_chatcommand("spawnentity", {
692 params = "<EntityName> [<X>,<Y>,<Z>]",
693 description = "Spawn entity at given (or your) position",
694 privs = {give=true, interact=true},
695 func = function(name, param)
696 local entityname, p = string.match(param, "^([^ ]+) *(.*)$")
697 if not entityname then
698 return false, "EntityName required"
700 core.log("action", ("%s invokes /spawnentity, entityname=%q")
701 :format(name, entityname))
702 local player = core.get_player_by_name(name)
703 if player == nil then
704 core.log("error", "Unable to spawn entity, player is nil")
705 return false, "Unable to spawn entity, player is nil"
707 if not core.registered_entities[entityname] then
708 return false, "Cannot spawn an unknown entity"
713 p = core.string_to_pos(p)
715 return false, "Invalid parameters ('" .. param .. "')"
719 core.add_entity(p, entityname)
720 return true, ("%q spawned."):format(entityname)
724 core.register_chatcommand("pulverize", {
726 description = "Destroy item in hand",
727 func = function(name, param)
728 local player = core.get_player_by_name(name)
730 core.log("error", "Unable to pulverize, no player.")
731 return false, "Unable to pulverize, no player."
733 local wielded_item = player:get_wielded_item()
734 if wielded_item:is_empty() then
735 return false, "Unable to pulverize, no item in hand."
737 core.log("action", name .. " pulverized \"" ..
738 wielded_item:get_name() .. " " .. wielded_item:get_count() .. "\"")
739 player:set_wielded_item(nil)
740 return true, "An item was pulverized."
745 core.rollback_punch_callbacks = {}
747 core.register_on_punchnode(function(pos, node, puncher)
748 local name = puncher and puncher:get_player_name()
749 if name and core.rollback_punch_callbacks[name] then
750 core.rollback_punch_callbacks[name](pos, node, puncher)
751 core.rollback_punch_callbacks[name] = nil
755 core.register_chatcommand("rollback_check", {
756 params = "[<range>] [<seconds>] [<limit>]",
757 description = "Check who last touched a node or a node near it"
758 .. " within the time specified by <seconds>. Default: range = 0,"
759 .. " seconds = 86400 = 24h, limit = 5",
760 privs = {rollback=true},
761 func = function(name, param)
762 if not core.settings:get_bool("enable_rollback_recording") then
763 return false, "Rollback functions are disabled."
765 local range, seconds, limit =
766 param:match("(%d+) *(%d*) *(%d*)")
767 range = tonumber(range) or 0
768 seconds = tonumber(seconds) or 86400
769 limit = tonumber(limit) or 5
771 return false, "That limit is too high!"
774 core.rollback_punch_callbacks[name] = function(pos, node, puncher)
775 local name = puncher:get_player_name()
776 core.chat_send_player(name, "Checking " .. core.pos_to_string(pos) .. "...")
777 local actions = core.rollback_get_node_actions(pos, range, seconds, limit)
779 core.chat_send_player(name, "Rollback functions are disabled")
782 local num_actions = #actions
783 if num_actions == 0 then
784 core.chat_send_player(name, "Nobody has touched"
785 .. " the specified location in "
786 .. seconds .. " seconds")
789 local time = os.time()
790 for i = num_actions, 1, -1 do
791 local action = actions[i]
792 core.chat_send_player(name,
793 ("%s %s %s -> %s %d seconds ago.")
795 core.pos_to_string(action.pos),
803 return true, "Punch a node (range=" .. range .. ", seconds="
804 .. seconds .. "s, limit=" .. limit .. ")"
808 core.register_chatcommand("rollback", {
809 params = "(<name> [<seconds>]) | (:<actor> [<seconds>])",
810 description = "Revert actions of a player. Default for <seconds> is 60",
811 privs = {rollback=true},
812 func = function(name, param)
813 if not core.settings:get_bool("enable_rollback_recording") then
814 return false, "Rollback functions are disabled."
816 local target_name, seconds = string.match(param, ":([^ ]+) *(%d*)")
817 if not target_name then
819 player_name, seconds = string.match(param, "([^ ]+) *(%d*)")
820 if not player_name then
821 return false, "Invalid parameters. See /help rollback"
822 .. " and /help rollback_check."
824 target_name = "player:"..player_name
826 seconds = tonumber(seconds) or 60
827 core.chat_send_player(name, "Reverting actions of "
828 .. target_name .. " since "
829 .. seconds .. " seconds.")
830 local success, log = core.rollback_revert_actions_by(
831 target_name, seconds)
834 response = "(log is too long to show)\n"
836 for _, line in pairs(log) do
837 response = response .. line .. "\n"
840 response = response .. "Reverting actions "
841 .. (success and "succeeded." or "FAILED.")
842 return success, response
846 core.register_chatcommand("status", {
847 description = "Show server status",
848 func = function(name, param)
849 local status = core.get_server_status(name, false)
850 if status and status ~= "" then
853 return false, "This command was disabled by a mod or game"
857 core.register_chatcommand("time", {
858 params = "[<0..23>:<0..59> | <0..24000>]",
859 description = "Show or set time of day",
861 func = function(name, param)
863 local current_time = math.floor(core.get_timeofday() * 1440)
864 local minutes = current_time % 60
865 local hour = (current_time - minutes) / 60
866 return true, ("Current time is %d:%02d"):format(hour, minutes)
868 local player_privs = core.get_player_privs(name)
869 if not player_privs.settime then
870 return false, "You don't have permission to run this command " ..
871 "(missing privilege: settime)."
873 local hour, minute = param:match("^(%d+):(%d+)$")
875 local new_time = tonumber(param)
877 return false, "Invalid time."
879 -- Backward compatibility.
880 core.set_timeofday((new_time % 24000) / 24000)
881 core.log("action", name .. " sets time to " .. new_time)
882 return true, "Time of day changed."
884 hour = tonumber(hour)
885 minute = tonumber(minute)
886 if hour < 0 or hour > 23 then
887 return false, "Invalid hour (must be between 0 and 23 inclusive)."
888 elseif minute < 0 or minute > 59 then
889 return false, "Invalid minute (must be between 0 and 59 inclusive)."
891 core.set_timeofday((hour * 60 + minute) / 1440)
892 core.log("action", ("%s sets time to %d:%02d"):format(name, hour, minute))
893 return true, "Time of day changed."
897 core.register_chatcommand("days", {
898 description = "Show day count since world creation",
899 func = function(name, param)
900 return true, "Current day is " .. core.get_day_count()
904 core.register_chatcommand("shutdown", {
905 params = "[<delay_in_seconds> | -1] [reconnect] [<message>]",
906 description = "Shutdown server (-1 cancels a delayed shutdown)",
907 privs = {server=true},
908 func = function(name, param)
909 local delay, reconnect, message
910 delay, param = param:match("^%s*(%S+)(.*)")
912 reconnect, param = param:match("^%s*(%S+)(.*)")
914 message = param and param:match("^%s*(.+)") or ""
915 delay = tonumber(delay) or 0
918 core.log("action", name .. " shuts down server")
919 core.chat_send_all("*** Server shutting down (operator request).")
921 core.request_shutdown(message:trim(), core.is_yes(reconnect), delay)
925 core.register_chatcommand("ban", {
927 description = "Ban the IP of a player or show the ban list",
929 func = function(name, param)
931 local ban_list = core.get_ban_list()
932 if ban_list == "" then
933 return true, "The ban list is empty."
935 return true, "Ban list: " .. ban_list
938 if not core.get_player_by_name(param) then
939 return false, "Player is not online."
941 if not core.ban_player(param) then
942 return false, "Failed to ban player."
944 local desc = core.get_ban_description(param)
945 core.log("action", name .. " bans " .. desc .. ".")
946 return true, "Banned " .. desc .. "."
950 core.register_chatcommand("unban", {
951 params = "<name> | <IP_address>",
952 description = "Remove IP ban belonging to a player/IP",
954 func = function(name, param)
955 if not core.unban_player_or_ip(param) then
956 return false, "Failed to unban player/IP."
958 core.log("action", name .. " unbans " .. param)
959 return true, "Unbanned " .. param
963 core.register_chatcommand("kick", {
964 params = "<name> [<reason>]",
965 description = "Kick a player",
967 func = function(name, param)
968 local tokick, reason = param:match("([^ ]+) (.+)")
969 tokick = tokick or param
970 if not core.kick_player(tokick, reason) then
971 return false, "Failed to kick player " .. tokick
973 local log_reason = ""
975 log_reason = " with reason \"" .. reason .. "\""
977 core.log("action", name .. " kicks " .. tokick .. log_reason)
978 return true, "Kicked " .. tokick
982 core.register_chatcommand("clearobjects", {
983 params = "[full | quick]",
984 description = "Clear all objects in world",
985 privs = {server=true},
986 func = function(name, param)
988 if param == "" or param == "quick" then
989 options.mode = "quick"
990 elseif param == "full" then
991 options.mode = "full"
993 return false, "Invalid usage, see /help clearobjects."
996 core.log("action", name .. " clears all objects ("
997 .. options.mode .. " mode).")
998 core.chat_send_all("Clearing all objects. This may take a long time."
999 .. " You may experience a timeout. (by "
1001 core.clear_objects(options)
1002 core.log("action", "Object clearing done.")
1003 core.chat_send_all("*** Cleared all objects.")
1007 core.register_chatcommand("msg", {
1008 params = "<name> <message>",
1009 description = "Send a direct message to a player",
1010 privs = {shout=true},
1011 func = function(name, param)
1012 local sendto, message = param:match("^(%S+)%s(.+)$")
1014 return false, "Invalid usage, see /help msg."
1016 if not core.get_player_by_name(sendto) then
1017 return false, "The player " .. sendto
1018 .. " is not online."
1020 core.log("action", "DM from " .. name .. " to " .. sendto
1022 core.chat_send_player(sendto, "DM from " .. name .. ": "
1024 return true, "Message sent."
1028 core.register_chatcommand("last-login", {
1029 params = "[<name>]",
1030 description = "Get the last login time of a player or yourself",
1031 func = function(name, param)
1035 local pauth = core.get_auth_handler().get_auth(param)
1036 if pauth and pauth.last_login then
1037 -- Time in UTC, ISO 8601 format
1038 return true, "Last login time was " ..
1039 os.date("!%Y-%m-%dT%H:%M:%SZ", pauth.last_login)
1041 return false, "Last login time is unknown"
1045 core.register_chatcommand("clearinv", {
1046 params = "[<name>]",
1047 description = "Clear the inventory of yourself or another player",
1048 func = function(name, param)
1050 if param and param ~= "" and param ~= name then
1051 if not core.check_player_privs(name, {server=true}) then
1052 return false, "You don't have permission"
1053 .. " to clear another player's inventory (missing privilege: server)"
1055 player = core.get_player_by_name(param)
1056 core.chat_send_player(param, name.." cleared your inventory.")
1058 player = core.get_player_by_name(name)
1062 player:get_inventory():set_list("main", {})
1063 player:get_inventory():set_list("craft", {})
1064 player:get_inventory():set_list("craftpreview", {})
1065 core.log("action", name.." clears "..player:get_player_name().."'s inventory")
1066 return true, "Cleared "..player:get_player_name().."'s inventory."
1068 return false, "Player must be online to clear inventory!"
1073 local function handle_kill_command(killer, victim)
1074 if core.settings:get_bool("enable_damage") == false then
1075 return false, "Players can't be killed, damage has been disabled."
1077 local victimref = core.get_player_by_name(victim)
1078 if victimref == nil then
1079 return false, string.format("Player %s is not online.", victim)
1080 elseif victimref:get_hp() <= 0 then
1081 if killer == victim then
1082 return false, "You are already dead."
1084 return false, string.format("%s is already dead.", victim)
1087 if not killer == victim then
1088 core.log("action", string.format("%s killed %s", killer, victim))
1092 return true, string.format("%s has been killed.", victim)
1095 core.register_chatcommand("kill", {
1096 params = "[<name>]",
1097 description = "Kill player or yourself",
1098 privs = {server=true},
1099 func = function(name, param)
1100 return handle_kill_command(name, param == "" and name or param)