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 -- Run core.registered_on_chatcommands callbacks.
62 if core.run_callbacks(core.registered_on_chatcommands, 5, name, cmd, param) then
66 local cmd_def = core.registered_chatcommands[cmd]
68 core.chat_send_player(name, "-!- Invalid command: " .. cmd)
71 local has_privs, missing_privs = core.check_player_privs(name, cmd_def.privs)
73 core.set_last_run_mod(cmd_def.mod_origin)
74 local success, result = cmd_def.func(name, param)
75 if success == false and result == nil then
76 core.chat_send_player(name, "-!- Invalid command usage")
77 local help_def = core.registered_chatcommands["help"]
79 local _, helpmsg = help_def.func(name, cmd)
81 core.chat_send_player(name, helpmsg)
85 core.chat_send_player(name, result)
88 core.chat_send_player(name, "You don't have permission"
89 .. " to run this command (missing privileges: "
90 .. table.concat(missing_privs, ", ") .. ")")
92 return true -- Handled chat message
95 if core.settings:get_bool("profiler.load") then
96 -- Run after register_chatcommand and its register_on_chat_message
97 -- Before any chatcommands that should be profiled
98 profiler.init_chatcommand()
101 -- Parses a "range" string in the format of "here (number)" or
102 -- "(x1, y1, z1) (x2, y2, z2)", returning two position vectors
103 local function parse_range_str(player_name, str)
105 local args = str:split(" ")
107 if args[1] == "here" then
108 p1, p2 = core.get_player_radius_area(player_name, tonumber(args[2]))
110 return false, "Unable to get player " .. player_name .. " position"
113 p1, p2 = core.string_to_area(str)
115 return false, "Incorrect area format. Expected: (x1,y1,z1) (x2,y2,z2)"
125 core.register_chatcommand("me", {
127 description = "Show chat action (e.g., '/me orders a pizza' displays"
128 .. " '<player name> orders a pizza')",
129 privs = {shout=true},
130 func = function(name, param)
131 core.chat_send_all("* " .. name .. " " .. param)
136 core.register_chatcommand("admin", {
137 description = "Show the name of the server owner",
138 func = function(name)
139 local admin = core.settings:get("name")
141 return true, "The administrator of this server is " .. admin .. "."
143 return false, "There's no administrator named in the config file."
148 core.register_chatcommand("privs", {
150 description = "Show privileges of yourself or another player",
151 func = function(caller, param)
153 local name = (param ~= "" and param or caller)
154 if not core.player_exists(name) then
155 return false, "Player " .. name .. " does not exist."
157 return true, "Privileges of " .. name .. ": "
158 .. core.privs_to_string(
159 core.get_player_privs(name), ", ")
163 core.register_chatcommand("haspriv", {
164 params = "<privilege>",
165 description = "Return list of all online players with privilege.",
166 privs = {basic_privs = true},
167 func = function(caller, param)
170 return false, "Invalid parameters (see /help haspriv)"
172 if not core.registered_privileges[param] then
173 return false, "Unknown privilege!"
175 local privs = core.string_to_privs(param)
176 local players_with_priv = {}
177 for _, player in pairs(core.get_connected_players()) do
178 local player_name = player:get_player_name()
179 if core.check_player_privs(player_name, privs) then
180 table.insert(players_with_priv, player_name)
183 return true, "Players online with the \"" .. param .. "\" privilege: " ..
184 table.concat(players_with_priv, ", ")
188 local function handle_grant_command(caller, grantname, grantprivstr)
189 local caller_privs = core.get_player_privs(caller)
190 if not (caller_privs.privs or caller_privs.basic_privs) then
191 return false, "Your privileges are insufficient."
194 if not core.get_auth_handler().get_auth(grantname) then
195 return false, "Player " .. grantname .. " does not exist."
197 local grantprivs = core.string_to_privs(grantprivstr)
198 if grantprivstr == "all" then
199 grantprivs = core.registered_privileges
201 local privs = core.get_player_privs(grantname)
202 local privs_unknown = ""
204 core.string_to_privs(core.settings:get("basic_privs") or "interact,shout")
205 for priv, _ in pairs(grantprivs) do
206 if not basic_privs[priv] and not caller_privs.privs then
207 return false, "Your privileges are insufficient."
209 if not core.registered_privileges[priv] then
210 privs_unknown = privs_unknown .. "Unknown privilege: " .. priv .. "\n"
214 if privs_unknown ~= "" then
215 return false, privs_unknown
217 for priv, _ in pairs(grantprivs) do
218 -- call the on_grant callbacks
219 core.run_priv_callbacks(grantname, priv, caller, "grant")
221 core.set_player_privs(grantname, privs)
222 core.log("action", caller..' granted ('..core.privs_to_string(grantprivs, ', ')..') privileges to '..grantname)
223 if grantname ~= caller then
224 core.chat_send_player(grantname, caller
225 .. " granted you privileges: "
226 .. core.privs_to_string(grantprivs, ' '))
228 return true, "Privileges of " .. grantname .. ": "
229 .. core.privs_to_string(
230 core.get_player_privs(grantname), ' ')
233 core.register_chatcommand("grant", {
234 params = "<name> (<privilege> | all)",
235 description = "Give privileges to player",
236 func = function(name, param)
237 local grantname, grantprivstr = string.match(param, "([^ ]+) (.+)")
238 if not grantname or not grantprivstr then
239 return false, "Invalid parameters (see /help grant)"
241 return handle_grant_command(name, grantname, grantprivstr)
245 core.register_chatcommand("grantme", {
246 params = "<privilege> | all",
247 description = "Grant privileges to yourself",
248 func = function(name, param)
250 return false, "Invalid parameters (see /help grantme)"
252 return handle_grant_command(name, name, param)
256 local function handle_revoke_command(caller, revokename, revokeprivstr)
257 local caller_privs = core.get_player_privs(caller)
258 if not (caller_privs.privs or caller_privs.basic_privs) then
259 return false, "Your privileges are insufficient."
262 if not core.get_auth_handler().get_auth(revokename) then
263 return false, "Player " .. revokename .. " does not exist."
266 local revokeprivs = core.string_to_privs(revokeprivstr)
267 local privs = core.get_player_privs(revokename)
269 core.string_to_privs(core.settings:get("basic_privs") or "interact,shout")
270 for priv, _ in pairs(revokeprivs) do
271 if not basic_privs[priv] and not caller_privs.privs then
272 return false, "Your privileges are insufficient."
276 if revokeprivstr == "all" then
280 for priv, _ in pairs(revokeprivs) do
285 for priv, _ in pairs(revokeprivs) do
286 -- call the on_revoke callbacks
287 core.run_priv_callbacks(revokename, priv, caller, "revoke")
290 core.set_player_privs(revokename, privs)
291 core.log("action", caller..' revoked ('
292 ..core.privs_to_string(revokeprivs, ', ')
293 ..') privileges from '..revokename)
294 if revokename ~= caller then
295 core.chat_send_player(revokename, caller
296 .. " revoked privileges from you: "
297 .. core.privs_to_string(revokeprivs, ' '))
299 return true, "Privileges of " .. revokename .. ": "
300 .. core.privs_to_string(
301 core.get_player_privs(revokename), ' ')
304 core.register_chatcommand("revoke", {
305 params = "<name> (<privilege> | all)",
306 description = "Remove privileges from player",
308 func = function(name, param)
309 local revokename, revokeprivstr = string.match(param, "([^ ]+) (.+)")
310 if not revokename or not revokeprivstr then
311 return false, "Invalid parameters (see /help revoke)"
313 return handle_revoke_command(name, revokename, revokeprivstr)
317 core.register_chatcommand("revokeme", {
318 params = "<privilege> | all",
319 description = "Revoke privileges from yourself",
321 func = function(name, param)
323 return false, "Invalid parameters (see /help revokeme)"
325 return handle_revoke_command(name, name, param)
329 core.register_chatcommand("setpassword", {
330 params = "<name> <password>",
331 description = "Set player's password",
332 privs = {password=true},
333 func = function(name, param)
334 local toname, raw_password = string.match(param, "^([^ ]+) +(.+)$")
336 toname = param:match("^([^ ]+) *$")
341 return false, "Name field required"
344 local act_str_past, act_str_pres
345 if not raw_password then
346 core.set_player_password(toname, "")
347 act_str_past = "cleared"
348 act_str_pres = "clears"
350 core.set_player_password(toname,
351 core.get_password_hash(toname,
354 act_str_pres = "sets"
357 if toname ~= name then
358 core.chat_send_player(toname, "Your password was "
359 .. act_str_past .. " by " .. name)
362 core.log("action", name .. " " .. act_str_pres ..
363 " password of " .. toname .. ".")
365 return true, "Password of player \"" .. toname .. "\" " .. act_str_past
369 core.register_chatcommand("clearpassword", {
371 description = "Set empty password for a player",
372 privs = {password=true},
373 func = function(name, param)
376 return false, "Name field required"
378 core.set_player_password(toname, '')
380 core.log("action", name .. " clears password of " .. toname .. ".")
382 return true, "Password of player \"" .. toname .. "\" cleared"
386 core.register_chatcommand("auth_reload", {
388 description = "Reload authentication data",
389 privs = {server=true},
390 func = function(name, param)
391 local done = core.auth_reload()
392 return done, (done and "Done." or "Failed.")
396 core.register_chatcommand("remove_player", {
398 description = "Remove a player's data",
399 privs = {server=true},
400 func = function(name, param)
403 return false, "Name field required"
406 local rc = core.remove_player(toname)
409 core.log("action", name .. " removed player data of " .. toname .. ".")
410 return true, "Player \"" .. toname .. "\" removed."
412 return true, "No such player \"" .. toname .. "\" to remove."
414 return true, "Player \"" .. toname .. "\" is connected, cannot remove."
417 return false, "Unhandled remove_player return code " .. rc .. ""
421 core.register_chatcommand("teleport", {
422 params = "<X>,<Y>,<Z> | <to_name> | (<name> <X>,<Y>,<Z>) | (<name> <to_name>)",
423 description = "Teleport to position or player",
424 privs = {teleport=true},
425 func = function(name, param)
426 -- Returns (pos, true) if found, otherwise (pos, false)
427 local function find_free_position_near(pos)
434 for _, d in ipairs(tries) do
435 local p = {x = pos.x+d.x, y = pos.y+d.y, z = pos.z+d.z}
436 local n = core.get_node_or_nil(p)
438 local def = core.registered_nodes[n.name]
439 if def and not def.walkable then
448 p.x, p.y, p.z = string.match(param, "^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
452 if p.x and p.y and p.z then
455 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
456 return false, "Cannot teleport out of map bounds!"
458 local teleportee = core.get_player_by_name(name)
460 if teleportee:get_attach() then
461 return false, "Can't teleport, you're attached to an object!"
463 teleportee:set_pos(p)
464 return true, "Teleporting to "..core.pos_to_string(p)
468 local target_name = param:match("^([^ ]+)$")
469 local teleportee = core.get_player_by_name(name)
473 local target = core.get_player_by_name(target_name)
479 if teleportee and p then
480 if teleportee:get_attach() then
481 return false, "Can't teleport, you're attached to an object!"
483 p = find_free_position_near(p)
484 teleportee:set_pos(p)
485 return true, "Teleporting to " .. target_name
486 .. " at "..core.pos_to_string(p)
489 if not core.check_player_privs(name, {bring=true}) then
490 return false, "You don't have permission to teleport other players (missing bring privilege)"
495 local teleportee_name
496 teleportee_name, p.x, p.y, p.z = param:match(
497 "^([^ ]+) +([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
498 p.x, p.y, p.z = tonumber(p.x), tonumber(p.y), tonumber(p.z)
499 if teleportee_name then
500 teleportee = core.get_player_by_name(teleportee_name)
502 if teleportee and p.x and p.y and p.z then
503 if teleportee:get_attach() then
504 return false, "Can't teleport, player is attached to an object!"
506 teleportee:set_pos(p)
507 return true, "Teleporting " .. teleportee_name
508 .. " to " .. core.pos_to_string(p)
513 teleportee_name, target_name = string.match(param, "^([^ ]+) +([^ ]+)$")
514 if teleportee_name then
515 teleportee = core.get_player_by_name(teleportee_name)
518 local target = core.get_player_by_name(target_name)
523 if teleportee and p then
524 if teleportee:get_attach() then
525 return false, "Can't teleport, player is attached to an object!"
527 p = find_free_position_near(p)
528 teleportee:set_pos(p)
529 return true, "Teleporting " .. teleportee_name
530 .. " to " .. target_name
531 .. " at " .. core.pos_to_string(p)
534 return false, 'Invalid parameters ("' .. param
535 .. '") or player not found (see /help teleport)'
539 core.register_chatcommand("set", {
540 params = "([-n] <name> <value>) | <name>",
541 description = "Set or read server configuration setting",
542 privs = {server=true},
543 func = function(name, param)
544 local arg, setname, setvalue = string.match(param, "(-[n]) ([^ ]+) (.+)")
545 if arg and arg == "-n" and setname and setvalue then
546 core.settings:set(setname, setvalue)
547 return true, setname .. " = " .. setvalue
550 setname, setvalue = string.match(param, "([^ ]+) (.+)")
551 if setname and setvalue then
552 if not core.settings:get(setname) then
553 return false, "Failed. Use '/set -n <name> <value>' to create a new setting."
555 core.settings:set(setname, setvalue)
556 return true, setname .. " = " .. setvalue
559 setname = string.match(param, "([^ ]+)")
561 setvalue = core.settings:get(setname)
563 setvalue = "<not set>"
565 return true, setname .. " = " .. setvalue
568 return false, "Invalid parameters (see /help set)."
572 local function emergeblocks_callback(pos, action, num_calls_remaining, ctx)
573 if ctx.total_blocks == 0 then
574 ctx.total_blocks = num_calls_remaining + 1
575 ctx.current_blocks = 0
577 ctx.current_blocks = ctx.current_blocks + 1
579 if ctx.current_blocks == ctx.total_blocks then
580 core.chat_send_player(ctx.requestor_name,
581 string.format("Finished emerging %d blocks in %.2fms.",
582 ctx.total_blocks, (os.clock() - ctx.start_time) * 1000))
586 local function emergeblocks_progress_update(ctx)
587 if ctx.current_blocks ~= ctx.total_blocks then
588 core.chat_send_player(ctx.requestor_name,
589 string.format("emergeblocks update: %d/%d blocks emerged (%.1f%%)",
590 ctx.current_blocks, ctx.total_blocks,
591 (ctx.current_blocks / ctx.total_blocks) * 100))
593 core.after(2, emergeblocks_progress_update, ctx)
597 core.register_chatcommand("emergeblocks", {
598 params = "(here [<radius>]) | (<pos1> <pos2>)",
599 description = "Load (or, if nonexistent, generate) map blocks "
600 .. "contained in area pos1 to pos2 (<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)
611 start_time = os.clock(),
612 requestor_name = name
615 core.emerge_area(p1, p2, emergeblocks_callback, context)
616 core.after(2, emergeblocks_progress_update, context)
618 return true, "Started emerge of area ranging from " ..
619 core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
623 core.register_chatcommand("deleteblocks", {
624 params = "(here [<radius>]) | (<pos1> <pos2>)",
625 description = "Delete map blocks contained in area pos1 to pos2 "
626 .. "(<pos1> and <pos2> must be in parentheses)",
627 privs = {server=true},
628 func = function(name, param)
629 local p1, p2 = parse_range_str(name, param)
634 if core.delete_area(p1, p2) then
635 return true, "Successfully cleared area ranging from " ..
636 core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
638 return false, "Failed to clear one or more blocks in area"
643 core.register_chatcommand("fixlight", {
644 params = "(here [<radius>]) | (<pos1> <pos2>)",
645 description = "Resets lighting in the area between pos1 and pos2 "
646 .. "(<pos1> and <pos2> must be in parentheses)",
647 privs = {server = true},
648 func = function(name, param)
649 local p1, p2 = parse_range_str(name, param)
654 if core.fix_light(p1, p2) then
655 return true, "Successfully reset light in the area ranging from " ..
656 core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
658 return false, "Failed to load one or more blocks in area"
663 core.register_chatcommand("mods", {
665 description = "List mods installed on the server",
667 func = function(name, param)
668 return true, table.concat(core.get_modnames(), ", ")
672 local function handle_give_command(cmd, giver, receiver, stackstring)
673 core.log("action", giver .. " invoked " .. cmd
674 .. ', stackstring="' .. stackstring .. '"')
675 local itemstack = ItemStack(stackstring)
676 if itemstack:is_empty() then
677 return false, "Cannot give an empty item"
678 elseif (not itemstack:is_known()) or (itemstack:get_name() == "unknown") then
679 return false, "Cannot give an unknown item"
680 -- Forbid giving 'ignore' due to unwanted side effects
681 elseif itemstack:get_name() == "ignore" then
682 return false, "Giving 'ignore' is not allowed"
684 local receiverref = core.get_player_by_name(receiver)
685 if receiverref == nil then
686 return false, receiver .. " is not a known player"
688 local leftover = receiverref:get_inventory():add_item("main", itemstack)
690 if leftover:is_empty() then
692 elseif leftover:get_count() == itemstack:get_count() then
693 partiality = "could not be "
695 partiality = "partially "
697 -- The actual item stack string may be different from what the "giver"
698 -- entered (e.g. big numbers are always interpreted as 2^16-1).
699 stackstring = itemstack:to_string()
700 if giver == receiver then
701 local msg = "%q %sadded to inventory."
702 return true, msg:format(stackstring, partiality)
704 core.chat_send_player(receiver, ("%q %sadded to inventory.")
705 :format(stackstring, partiality))
706 local msg = "%q %sadded to %s's inventory."
707 return true, msg:format(stackstring, partiality, receiver)
711 core.register_chatcommand("give", {
712 params = "<name> <ItemString> [<count> [<wear>]]",
713 description = "Give item to player",
715 func = function(name, param)
716 local toname, itemstring = string.match(param, "^([^ ]+) +(.+)$")
717 if not toname or not itemstring then
718 return false, "Name and ItemString required"
720 return handle_give_command("/give", name, toname, itemstring)
724 core.register_chatcommand("giveme", {
725 params = "<ItemString> [<count> [<wear>]]",
726 description = "Give item to yourself",
728 func = function(name, param)
729 local itemstring = string.match(param, "(.+)$")
730 if not itemstring then
731 return false, "ItemString required"
733 return handle_give_command("/giveme", name, name, itemstring)
737 core.register_chatcommand("spawnentity", {
738 params = "<EntityName> [<X>,<Y>,<Z>]",
739 description = "Spawn entity at given (or your) position",
740 privs = {give=true, interact=true},
741 func = function(name, param)
742 local entityname, p = string.match(param, "^([^ ]+) *(.*)$")
743 if not entityname then
744 return false, "EntityName required"
746 core.log("action", ("%s invokes /spawnentity, entityname=%q")
747 :format(name, entityname))
748 local player = core.get_player_by_name(name)
749 if player == nil then
750 core.log("error", "Unable to spawn entity, player is nil")
751 return false, "Unable to spawn entity, player is nil"
753 if not core.registered_entities[entityname] then
754 return false, "Cannot spawn an unknown entity"
759 p = core.string_to_pos(p)
761 return false, "Invalid parameters ('" .. param .. "')"
765 local obj = core.add_entity(p, entityname)
766 local msg = obj and "%q spawned." or "%q failed to spawn."
767 return true, msg:format(entityname)
771 core.register_chatcommand("pulverize", {
773 description = "Destroy item in hand",
774 func = function(name, param)
775 local player = core.get_player_by_name(name)
777 core.log("error", "Unable to pulverize, no player.")
778 return false, "Unable to pulverize, no player."
780 local wielded_item = player:get_wielded_item()
781 if wielded_item:is_empty() then
782 return false, "Unable to pulverize, no item in hand."
784 core.log("action", name .. " pulverized \"" ..
785 wielded_item:get_name() .. " " .. wielded_item:get_count() .. "\"")
786 player:set_wielded_item(nil)
787 return true, "An item was pulverized."
792 core.rollback_punch_callbacks = {}
794 core.register_on_punchnode(function(pos, node, puncher)
795 local name = puncher and puncher:get_player_name()
796 if name and core.rollback_punch_callbacks[name] then
797 core.rollback_punch_callbacks[name](pos, node, puncher)
798 core.rollback_punch_callbacks[name] = nil
802 core.register_chatcommand("rollback_check", {
803 params = "[<range>] [<seconds>] [<limit>]",
804 description = "Check who last touched a node or a node near it"
805 .. " within the time specified by <seconds>. Default: range = 0,"
806 .. " seconds = 86400 = 24h, limit = 5. Set <seconds> to inf for no time limit",
807 privs = {rollback=true},
808 func = function(name, param)
809 if not core.settings:get_bool("enable_rollback_recording") then
810 return false, "Rollback functions are disabled."
812 local range, seconds, limit =
813 param:match("(%d+) *(%d*) *(%d*)")
814 range = tonumber(range) or 0
815 seconds = tonumber(seconds) or 86400
816 limit = tonumber(limit) or 5
818 return false, "That limit is too high!"
821 core.rollback_punch_callbacks[name] = function(pos, node, puncher)
822 local name = puncher:get_player_name()
823 core.chat_send_player(name, "Checking " .. core.pos_to_string(pos) .. "...")
824 local actions = core.rollback_get_node_actions(pos, range, seconds, limit)
826 core.chat_send_player(name, "Rollback functions are disabled")
829 local num_actions = #actions
830 if num_actions == 0 then
831 core.chat_send_player(name, "Nobody has touched"
832 .. " the specified location in "
833 .. seconds .. " seconds")
836 local time = os.time()
837 for i = num_actions, 1, -1 do
838 local action = actions[i]
839 core.chat_send_player(name,
840 ("%s %s %s -> %s %d seconds ago.")
842 core.pos_to_string(action.pos),
850 return true, "Punch a node (range=" .. range .. ", seconds="
851 .. seconds .. "s, limit=" .. limit .. ")"
855 core.register_chatcommand("rollback", {
856 params = "(<name> [<seconds>]) | (:<actor> [<seconds>])",
857 description = "Revert actions of a player. Default for <seconds> is 60. Set <seconds> to inf for no time limit",
858 privs = {rollback=true},
859 func = function(name, param)
860 if not core.settings:get_bool("enable_rollback_recording") then
861 return false, "Rollback functions are disabled."
863 local target_name, seconds = string.match(param, ":([^ ]+) *(%d*)")
864 if not target_name then
866 player_name, seconds = string.match(param, "([^ ]+) *(%d*)")
867 if not player_name then
868 return false, "Invalid parameters. See /help rollback"
869 .. " and /help rollback_check."
871 target_name = "player:"..player_name
873 seconds = tonumber(seconds) or 60
874 core.chat_send_player(name, "Reverting actions of "
875 .. target_name .. " since "
876 .. seconds .. " seconds.")
877 local success, log = core.rollback_revert_actions_by(
878 target_name, seconds)
881 response = "(log is too long to show)\n"
883 for _, line in pairs(log) do
884 response = response .. line .. "\n"
887 response = response .. "Reverting actions "
888 .. (success and "succeeded." or "FAILED.")
889 return success, response
893 core.register_chatcommand("status", {
894 description = "Show server status",
895 func = function(name, param)
896 local status = core.get_server_status(name, false)
897 if status and status ~= "" then
900 return false, "This command was disabled by a mod or game"
904 core.register_chatcommand("time", {
905 params = "[<0..23>:<0..59> | <0..24000>]",
906 description = "Show or set time of day",
908 func = function(name, param)
910 local current_time = math.floor(core.get_timeofday() * 1440)
911 local minutes = current_time % 60
912 local hour = (current_time - minutes) / 60
913 return true, ("Current time is %d:%02d"):format(hour, minutes)
915 local player_privs = core.get_player_privs(name)
916 if not player_privs.settime then
917 return false, "You don't have permission to run this command " ..
918 "(missing privilege: settime)."
920 local hour, minute = param:match("^(%d+):(%d+)$")
922 local new_time = tonumber(param)
924 return false, "Invalid time."
926 -- Backward compatibility.
927 core.set_timeofday((new_time % 24000) / 24000)
928 core.log("action", name .. " sets time to " .. new_time)
929 return true, "Time of day changed."
931 hour = tonumber(hour)
932 minute = tonumber(minute)
933 if hour < 0 or hour > 23 then
934 return false, "Invalid hour (must be between 0 and 23 inclusive)."
935 elseif minute < 0 or minute > 59 then
936 return false, "Invalid minute (must be between 0 and 59 inclusive)."
938 core.set_timeofday((hour * 60 + minute) / 1440)
939 core.log("action", ("%s sets time to %d:%02d"):format(name, hour, minute))
940 return true, "Time of day changed."
944 core.register_chatcommand("days", {
945 description = "Show day count since world creation",
946 func = function(name, param)
947 return true, "Current day is " .. core.get_day_count()
951 core.register_chatcommand("shutdown", {
952 params = "[<delay_in_seconds> | -1] [reconnect] [<message>]",
953 description = "Shutdown server (-1 cancels a delayed shutdown)",
954 privs = {server=true},
955 func = function(name, param)
956 local delay, reconnect, message
957 delay, param = param:match("^%s*(%S+)(.*)")
959 reconnect, param = param:match("^%s*(%S+)(.*)")
961 message = param and param:match("^%s*(.+)") or ""
962 delay = tonumber(delay) or 0
965 core.log("action", name .. " shuts down server")
966 core.chat_send_all("*** Server shutting down (operator request).")
968 core.request_shutdown(message:trim(), core.is_yes(reconnect), delay)
973 core.register_chatcommand("ban", {
975 description = "Ban the IP of a player or show the ban list",
977 func = function(name, param)
979 local ban_list = core.get_ban_list()
980 if ban_list == "" then
981 return true, "The ban list is empty."
983 return true, "Ban list: " .. ban_list
986 if not core.get_player_by_name(param) then
987 return false, "Player is not online."
989 if not core.ban_player(param) then
990 return false, "Failed to ban player."
992 local desc = core.get_ban_description(param)
993 core.log("action", name .. " bans " .. desc .. ".")
994 return true, "Banned " .. desc .. "."
998 core.register_chatcommand("unban", {
999 params = "<name> | <IP_address>",
1000 description = "Remove IP ban belonging to a player/IP",
1002 func = function(name, param)
1003 if not core.unban_player_or_ip(param) then
1004 return false, "Failed to unban player/IP."
1006 core.log("action", name .. " unbans " .. param)
1007 return true, "Unbanned " .. param
1011 core.register_chatcommand("kick", {
1012 params = "<name> [<reason>]",
1013 description = "Kick a player",
1014 privs = {kick=true},
1015 func = function(name, param)
1016 local tokick, reason = param:match("([^ ]+) (.+)")
1017 tokick = tokick or param
1018 if not core.kick_player(tokick, reason) then
1019 return false, "Failed to kick player " .. tokick
1021 local log_reason = ""
1023 log_reason = " with reason \"" .. reason .. "\""
1025 core.log("action", name .. " kicks " .. tokick .. log_reason)
1026 return true, "Kicked " .. tokick
1030 core.register_chatcommand("clearobjects", {
1031 params = "[full | quick]",
1032 description = "Clear all objects in world",
1033 privs = {server=true},
1034 func = function(name, param)
1036 if param == "" or param == "quick" then
1037 options.mode = "quick"
1038 elseif param == "full" then
1039 options.mode = "full"
1041 return false, "Invalid usage, see /help clearobjects."
1044 core.log("action", name .. " clears all objects ("
1045 .. options.mode .. " mode).")
1046 core.chat_send_all("Clearing all objects. This may take a long time."
1047 .. " You may experience a timeout. (by "
1049 core.clear_objects(options)
1050 core.log("action", "Object clearing done.")
1051 core.chat_send_all("*** Cleared all objects.")
1056 core.register_chatcommand("msg", {
1057 params = "<name> <message>",
1058 description = "Send a direct message to a player",
1059 privs = {shout=true},
1060 func = function(name, param)
1061 local sendto, message = param:match("^(%S+)%s(.+)$")
1063 return false, "Invalid usage, see /help msg."
1065 if not core.get_player_by_name(sendto) then
1066 return false, "The player " .. sendto
1067 .. " is not online."
1069 core.log("action", "DM from " .. name .. " to " .. sendto
1071 core.chat_send_player(sendto, "DM from " .. name .. ": "
1073 return true, "Message sent."
1077 core.register_chatcommand("last-login", {
1078 params = "[<name>]",
1079 description = "Get the last login time of a player or yourself",
1080 func = function(name, param)
1084 local pauth = core.get_auth_handler().get_auth(param)
1085 if pauth and pauth.last_login and pauth.last_login ~= -1 then
1086 -- Time in UTC, ISO 8601 format
1087 return true, param.."'s last login time was " ..
1088 os.date("!%Y-%m-%dT%H:%M:%SZ", pauth.last_login)
1090 return false, param.."'s last login time is unknown"
1094 core.register_chatcommand("clearinv", {
1095 params = "[<name>]",
1096 description = "Clear the inventory of yourself or another player",
1097 func = function(name, param)
1099 if param and param ~= "" and param ~= name then
1100 if not core.check_player_privs(name, {server=true}) then
1101 return false, "You don't have permission"
1102 .. " to clear another player's inventory (missing privilege: server)"
1104 player = core.get_player_by_name(param)
1105 core.chat_send_player(param, name.." cleared your inventory.")
1107 player = core.get_player_by_name(name)
1111 player:get_inventory():set_list("main", {})
1112 player:get_inventory():set_list("craft", {})
1113 player:get_inventory():set_list("craftpreview", {})
1114 core.log("action", name.." clears "..player:get_player_name().."'s inventory")
1115 return true, "Cleared "..player:get_player_name().."'s inventory."
1117 return false, "Player must be online to clear inventory!"
1122 local function handle_kill_command(killer, victim)
1123 if core.settings:get_bool("enable_damage") == false then
1124 return false, "Players can't be killed, damage has been disabled."
1126 local victimref = core.get_player_by_name(victim)
1127 if victimref == nil then
1128 return false, string.format("Player %s is not online.", victim)
1129 elseif victimref:get_hp() <= 0 then
1130 if killer == victim then
1131 return false, "You are already dead."
1133 return false, string.format("%s is already dead.", victim)
1136 if not killer == victim then
1137 core.log("action", string.format("%s killed %s", killer, victim))
1141 return true, string.format("%s has been killed.", victim)
1144 core.register_chatcommand("kill", {
1145 params = "[<name>]",
1146 description = "Kill player or yourself",
1147 privs = {server=true},
1148 func = function(name, param)
1149 return handle_kill_command(name, param == "" and name or param)