1 -- Minetest: builtin/chatcommands.lua
4 -- Chat command handler
8 function core.register_chatcommand(cmd, def)
10 def.params = def.params or ""
11 def.description = def.description or ""
12 def.privs = def.privs or {}
13 core.chatcommands[cmd] = def
16 core.register_on_chat_message(function(name, message)
17 local cmd, param = string.match(message, "^/([^ ]+) *(.*)")
21 local cmd_def = core.chatcommands[cmd]
25 local has_privs, missing_privs = core.check_player_privs(name, cmd_def.privs)
27 local success, message = cmd_def.func(name, param)
29 core.chat_send_player(name, message)
32 core.chat_send_player(name, "You don't have permission"
33 .. " to run this command (missing privileges: "
34 .. table.concat(missing_privs, ", ") .. ")")
36 return true -- Handled chat message
42 core.register_chatcommand("me", {
44 description = "chat action (eg. /me orders a pizza)",
46 func = function(name, param)
47 core.chat_send_all("* " .. name .. " " .. param)
51 core.register_chatcommand("help", {
53 params = "[all/privs/<cmd>]",
54 description = "Get help for commands or list privileges",
55 func = function(name, param)
56 local function format_help_line(cmd, def)
58 if def.params and def.params ~= "" then
59 msg = msg .. " " .. def.params
61 if def.description and def.description ~= "" then
62 msg = msg .. ": " .. def.description
69 for cmd, def in pairs(core.chatcommands) do
70 if core.check_player_privs(name, def.privs) then
71 table.insert(cmds, cmd)
75 return true, "Available commands: " .. table.concat(cmds, " ") .. "\n"
76 .. "Use '/help <cmd>' to get more information,"
77 .. " or '/help all' to list everything."
78 elseif param == "all" then
80 for cmd, def in pairs(core.chatcommands) do
81 if core.check_player_privs(name, def.privs) then
82 table.insert(cmds, format_help_line(cmd, def))
86 return true, "Available commands:\n"..table.concat(cmds, "\n")
87 elseif param == "privs" then
89 for priv, def in pairs(core.registered_privileges) do
90 table.insert(privs, priv .. ": " .. def.description)
93 return true, "Available privileges:\n"..table.concat(privs, "\n")
96 local def = core.chatcommands[cmd]
98 return false, "Command not available: "..cmd
100 return true, format_help_line(cmd, def)
106 core.register_chatcommand("privs", {
108 description = "print out privileges of player",
109 func = function(name, param)
110 param = (param ~= "" and param or name)
111 return true, "Privileges of " .. param .. ": "
112 .. core.privs_to_string(
113 core.get_player_privs(param), ' ')
116 core.register_chatcommand("grant", {
117 params = "<name> <privilege>|all",
118 description = "Give privilege to player",
119 func = function(name, param)
120 if not core.check_player_privs(name, {privs=true}) and
121 not core.check_player_privs(name, {basic_privs=true}) then
122 return false, "Your privileges are insufficient."
124 local grantname, grantprivstr = string.match(param, "([^ ]+) (.+)")
125 if not grantname or not grantprivstr then
126 return false, "Invalid parameters (see /help grant)"
127 elseif not core.auth_table[grantname] then
128 return false, "Player " .. grantname .. " does not exist."
130 local grantprivs = core.string_to_privs(grantprivstr)
131 if grantprivstr == "all" then
132 grantprivs = core.registered_privileges
134 local privs = core.get_player_privs(grantname)
135 local privs_unknown = ""
136 for priv, _ in pairs(grantprivs) do
137 if priv ~= "interact" and priv ~= "shout" and
138 not core.check_player_privs(name, {privs=true}) then
139 return false, "Your privileges are insufficient."
141 if not core.registered_privileges[priv] then
142 privs_unknown = privs_unknown .. "Unknown privilege: " .. priv .. "\n"
146 if privs_unknown ~= "" then
147 return false, privs_unknown
149 core.set_player_privs(grantname, privs)
150 core.log("action", name..' granted ('..core.privs_to_string(grantprivs, ', ')..') privileges to '..grantname)
151 if grantname ~= name then
152 core.chat_send_player(grantname, name
153 .. " granted you privileges: "
154 .. core.privs_to_string(grantprivs, ' '))
156 return true, "Privileges of " .. grantname .. ": "
157 .. core.privs_to_string(
158 core.get_player_privs(grantname), ' ')
161 core.register_chatcommand("revoke", {
162 params = "<name> <privilege>|all",
163 description = "Remove privilege from player",
165 func = function(name, param)
166 if not core.check_player_privs(name, {privs=true}) and
167 not core.check_player_privs(name, {basic_privs=true}) then
168 return false, "Your privileges are insufficient."
170 local revoke_name, revoke_priv_str = string.match(param, "([^ ]+) (.+)")
171 if not revoke_name or not revoke_priv_str then
172 return false, "Invalid parameters (see /help revoke)"
173 elseif not core.auth_table[revoke_name] then
174 return false, "Player " .. revoke_name .. " does not exist."
176 local revoke_privs = core.string_to_privs(revoke_priv_str)
177 local privs = core.get_player_privs(revoke_name)
178 for priv, _ in pairs(revoke_privs) do
179 if priv ~= "interact" and priv ~= "shout" and priv ~= "interact_extra" and
180 not core.check_player_privs(name, {privs=true}) then
181 return false, "Your privileges are insufficient."
184 if revoke_priv_str == "all" then
187 for priv, _ in pairs(revoke_privs) do
191 core.set_player_privs(revoke_name, privs)
192 core.log("action", name..' revoked ('
193 ..core.privs_to_string(revoke_privs, ', ')
194 ..') privileges from '..revoke_name)
195 if revoke_name ~= name then
196 core.chat_send_player(revoke_name, name
197 .. " revoked privileges from you: "
198 .. core.privs_to_string(revoke_privs, ' '))
200 return true, "Privileges of " .. revoke_name .. ": "
201 .. core.privs_to_string(
202 core.get_player_privs(revoke_name), ' ')
206 core.register_chatcommand("setpassword", {
207 params = "<name> <password>",
208 description = "set given password",
209 privs = {password=true},
210 func = function(name, param)
211 local toname, raw_password = string.match(param, "^([^ ]+) +(.+)$")
213 toname = param:match("^([^ ]+) *$")
217 return false, "Name field required"
220 if not raw_password then
221 core.set_player_password(toname, "")
224 core.set_player_password(toname,
225 core.get_password_hash(toname,
229 if toname ~= name then
230 core.chat_send_player(toname, "Your password was "
231 .. actstr .. " by " .. name)
233 return true, "Password of player \"" .. toname .. "\" " .. actstr
237 core.register_chatcommand("clearpassword", {
239 description = "set empty password",
240 privs = {password=true},
241 func = function(name, param)
244 return false, "Name field required"
246 core.set_player_password(toname, '')
247 return true, "Password of player \"" .. toname .. "\" cleared"
251 core.register_chatcommand("auth_reload", {
253 description = "reload authentication data",
254 privs = {server=true},
255 func = function(name, param)
256 local done = core.auth_reload()
257 return done, (done and "Done." or "Failed.")
261 core.register_chatcommand("teleport", {
262 params = "<X>,<Y>,<Z> | <to_name> | <name> <X>,<Y>,<Z> | <name> <to_name>",
263 description = "teleport to given position",
264 privs = {teleport=true},
265 func = function(name, param)
266 -- Returns (pos, true) if found, otherwise (pos, false)
267 local function find_free_position_near(pos)
274 for _, d in ipairs(tries) do
275 local p = {x = pos.x+d.x, y = pos.y+d.y, z = pos.z+d.z}
276 local n = core.get_node_or_nil(p)
278 local def = core.registered_nodes[n.name]
279 if def and not def.walkable then
287 local teleportee = nil
289 p.x, p.y, p.z = string.match(param, "^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
293 teleportee = core.get_player_by_name(name)
294 if teleportee and p.x and p.y and p.z then
296 return true, "Teleporting to "..core.pos_to_string(p)
299 local teleportee = nil
301 local target_name = nil
302 target_name = param:match("^([^ ]+)$")
303 teleportee = core.get_player_by_name(name)
305 local target = core.get_player_by_name(target_name)
310 if teleportee and p then
311 p = find_free_position_near(p)
313 return true, "Teleporting to " .. target_name
314 .. " at "..core.pos_to_string(p)
317 if core.check_player_privs(name, {bring=true}) then
318 local teleportee = nil
320 local teleportee_name = nil
321 teleportee_name, p.x, p.y, p.z = param:match(
322 "^([^ ]+) +([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
323 p.x, p.y, p.z = tonumber(p.x), tonumber(p.y), tonumber(p.z)
324 if teleportee_name then
325 teleportee = core.get_player_by_name(teleportee_name)
327 if teleportee and p.x and p.y and p.z then
329 return true, "Teleporting " .. teleportee_name
330 .. " to " .. core.pos_to_string(p)
333 local teleportee = nil
335 local teleportee_name = nil
336 local target_name = nil
337 teleportee_name, target_name = string.match(param, "^([^ ]+) +([^ ]+)$")
338 if teleportee_name then
339 teleportee = core.get_player_by_name(teleportee_name)
342 local target = core.get_player_by_name(target_name)
347 if teleportee and p then
348 p = find_free_position_near(p)
350 return true, "Teleporting " .. teleportee_name
351 .. " to " .. target_name
352 .. " at " .. core.pos_to_string(p)
356 return false, 'Invalid parameters ("' .. param
357 .. '") or player not found (see /help teleport)'
361 core.register_chatcommand("set", {
362 params = "[-n] <name> <value> | <name>",
363 description = "set or read server configuration setting",
364 privs = {server=true},
365 func = function(name, param)
366 local arg, setname, setvalue = string.match(param, "(-[n]) ([^ ]+) (.+)")
367 if arg and arg == "-n" and setname and setvalue then
368 core.setting_set(setname, setvalue)
369 return true, setname .. " = " .. setvalue
371 local setname, setvalue = string.match(param, "([^ ]+) (.+)")
372 if setname and setvalue then
373 if not core.setting_get(setname) then
374 return false, "Failed. Use '/set -n <name> <value>' to create a new setting."
376 core.setting_set(setname, setvalue)
377 return true, setname .. " = " .. setvalue
379 local setname = string.match(param, "([^ ]+)")
381 local setvalue = core.setting_get(setname)
383 setvalue = "<not set>"
385 return true, setname .. " = " .. setvalue
387 return false, "Invalid parameters (see /help set)."
391 core.register_chatcommand("mods", {
393 description = "List mods installed on the server",
395 func = function(name, param)
396 return true, table.concat(core.get_modnames(), ", ")
400 local function handle_give_command(cmd, giver, receiver, stackstring)
401 core.log("action", giver .. " invoked " .. cmd
402 .. ', stackstring="' .. stackstring .. '"')
403 local itemstack = ItemStack(stackstring)
404 if itemstack:is_empty() then
405 return false, "Cannot give an empty item"
406 elseif not itemstack:is_known() then
407 return false, "Cannot give an unknown item"
409 local receiverref = core.get_player_by_name(receiver)
410 if receiverref == nil then
411 return false, receiver .. " is not a known player"
413 local leftover = receiverref:get_inventory():add_item("main", itemstack)
414 if leftover:is_empty() then
416 elseif leftover:get_count() == itemstack:get_count() then
417 partiality = "could not be "
419 partiality = "partially "
421 -- The actual item stack string may be different from what the "giver"
422 -- entered (e.g. big numbers are always interpreted as 2^16-1).
423 stackstring = itemstack:to_string()
424 if giver == receiver then
425 return true, ("%q %sadded to inventory.")
426 :format(stackstring, partiality)
428 core.chat_send_player(receiver, ("%q %sadded to inventory.")
429 :format(stackstring, partiality))
430 return true, ("%q %sadded to %s's inventory.")
431 :format(stackstring, partiality, receiver)
435 core.register_chatcommand("give", {
436 params = "<name> <ItemString>",
437 description = "give item to player",
439 func = function(name, param)
440 local toname, itemstring = string.match(param, "^([^ ]+) +(.+)$")
441 if not toname or not itemstring then
442 return false, "Name and ItemString required"
444 return handle_give_command("/give", name, toname, itemstring)
448 core.register_chatcommand("giveme", {
449 params = "<ItemString>",
450 description = "give item to yourself",
452 func = function(name, param)
453 local itemstring = string.match(param, "(.+)$")
454 if not itemstring then
455 return false, "ItemString required"
457 return handle_give_command("/giveme", name, name, itemstring)
461 core.register_chatcommand("spawnentity", {
462 params = "<EntityName>",
463 description = "Spawn entity at your position",
464 privs = {give=true, interact=true},
465 func = function(name, param)
466 local entityname = string.match(param, "(.+)$")
467 if not entityname then
468 return false, "EntityName required"
470 core.log("action", ("/spawnentity invoked, entityname=%q")
472 local player = core.get_player_by_name(name)
473 if player == nil then
474 core.log("error", "Unable to spawn entity, player is nil")
475 return false, "Unable to spawn entity, player is nil"
477 local p = player:getpos()
479 core.add_entity(p, entityname)
480 return true, ("%q spawned."):format(entityname)
484 core.register_chatcommand("pulverize", {
486 description = "Destroy item in hand",
487 func = function(name, param)
488 local player = core.get_player_by_name(name)
490 core.log("error", "Unable to pulverize, no player.")
491 return false, "Unable to pulverize, no player."
493 if player:get_wielded_item():is_empty() then
494 return false, "Unable to pulverize, no item in hand."
496 player:set_wielded_item(nil)
497 return true, "An item was pulverized."
502 core.rollback_punch_callbacks = {}
504 core.register_on_punchnode(function(pos, node, puncher)
505 local name = puncher:get_player_name()
506 if core.rollback_punch_callbacks[name] then
507 core.rollback_punch_callbacks[name](pos, node, puncher)
508 core.rollback_punch_callbacks[name] = nil
512 core.register_chatcommand("rollback_check", {
513 params = "[<range>] [<seconds>] [limit]",
514 description = "Check who has last touched a node or near it,"
515 .. " max. <seconds> ago (default range=0,"
516 .. " seconds=86400=24h, limit=5)",
517 privs = {rollback=true},
518 func = function(name, param)
519 local range, seconds, limit =
520 param:match("(%d+) *(%d*) *(%d*)")
521 range = tonumber(range) or 0
522 seconds = tonumber(seconds) or 86400
523 limit = tonumber(limit) or 5
525 return false, "That limit is too high!"
528 core.rollback_punch_callbacks[name] = function(pos, node, puncher)
529 local name = puncher:get_player_name()
530 core.chat_send_player(name, "Checking " .. core.pos_to_string(pos) .. "...")
531 local actions = core.rollback_get_node_actions(pos, range, seconds, limit)
532 local num_actions = #actions
533 if num_actions == 0 then
534 core.chat_send_player(name, "Nobody has touched"
535 .. " the specified location in "
536 .. seconds .. " seconds")
539 local time = os.time()
540 for i = num_actions, 1, -1 do
541 local action = actions[i]
542 core.chat_send_player(name,
543 ("%s %s %s -> %s %d seconds ago.")
545 core.pos_to_string(action.pos),
553 return true, "Punch a node (range=" .. range .. ", seconds="
554 .. seconds .. "s, limit=" .. limit .. ")"
558 core.register_chatcommand("rollback", {
559 params = "<player name> [<seconds>] | :<actor> [<seconds>]",
560 description = "revert actions of a player; default for <seconds> is 60",
561 privs = {rollback=true},
562 func = function(name, param)
563 local target_name, seconds = string.match(param, ":([^ ]+) *(%d*)")
564 if not target_name then
565 local player_name = nil
566 player_name, seconds = string.match(param, "([^ ]+) *(%d*)")
567 if not player_name then
568 return false, "Invalid parameters. See /help rollback"
569 .. " and /help rollback_check."
571 target_name = "player:"..player_name
573 seconds = tonumber(seconds) or 60
574 core.chat_send_player(name, "Reverting actions of "
575 .. target_name .. " since "
576 .. seconds .. " seconds.")
577 local success, log = core.rollback_revert_actions_by(
578 target_name, seconds)
581 response = "(log is too long to show)\n"
583 for _, line in pairs(log) do
584 response = response .. line .. "\n"
587 response = response .. "Reverting actions "
588 .. (success and "succeeded." or "FAILED.")
589 return success, response
593 core.register_chatcommand("status", {
594 description = "Print server status",
595 func = function(name, param)
596 return true, core.get_server_status()
600 core.register_chatcommand("time", {
601 params = "<0...24000>",
602 description = "set time of day",
603 privs = {settime=true},
604 func = function(name, param)
606 return false, "Missing time."
608 local newtime = tonumber(param)
609 if newtime == nil then
610 return false, "Invalid time."
612 core.set_timeofday((newtime % 24000) / 24000)
613 core.log("action", name .. " sets time " .. newtime)
614 return true, "Time of day changed."
618 core.register_chatcommand("shutdown", {
619 description = "shutdown server",
620 privs = {server=true},
621 func = function(name, param)
622 core.log("action", name .. " shuts down server")
623 core.request_shutdown()
624 core.chat_send_all("*** Server shutting down (operator request).")
628 core.register_chatcommand("ban", {
630 description = "Ban IP of player",
632 func = function(name, param)
634 return true, "Ban list: " .. core.get_ban_list()
636 if not core.get_player_by_name(param) then
637 return false, "No such player."
639 if not core.ban_player(param) then
640 return false, "Failed to ban player."
642 local desc = core.get_ban_description(param)
643 core.log("action", name .. " bans " .. desc .. ".")
644 return true, "Banned " .. desc .. "."
648 core.register_chatcommand("unban", {
649 params = "<name/ip>",
650 description = "remove IP ban",
652 func = function(name, param)
653 if not core.unban_player_or_ip(param) then
654 return false, "Failed to unban player/IP."
656 core.log("action", name .. " unbans " .. param)
657 return true, "Unbanned " .. param
661 core.register_chatcommand("kick", {
662 params = "<name> [reason]",
663 description = "kick a player",
665 func = function(name, param)
666 local tokick, reason = param:match("([^ ]+) (.+)")
667 tokick = tokick or param
668 if not core.kick_player(tokick, reason) then
669 return false, "Failed to kick player " .. tokick
671 core.log("action", name .. " kicked " .. tokick)
672 return true, "Kicked " .. tokick
676 core.register_chatcommand("clearobjects", {
677 description = "clear all objects in world",
678 privs = {server=true},
679 func = function(name, param)
680 core.log("action", name .. " clears all objects.")
681 core.chat_send_all("Clearing all objects. This may take long."
682 .. " You may experience a timeout. (by "
685 core.log("action", "Object clearing done.")
686 core.chat_send_all("*** Cleared all objects.")
690 core.register_chatcommand("msg", {
691 params = "<name> <message>",
692 description = "Send a private message",
693 privs = {shout=true},
694 func = function(name, param)
695 local sendto, message = param:match("^(%S+)%s(.+)$")
697 return false, "Invalid usage, see /help msg."
699 if not core.get_player_by_name(sendto) then
700 return false, "The player " .. sendto
703 core.log("action", "PM from " .. name .. " to " .. sendto
705 core.chat_send_player(sendto, "PM from " .. name .. ": "
707 return true, "Message sent."