Improve documentation around banning (#9088)
[oweals/minetest.git] / builtin / game / chat.lua
1 -- Minetest: builtin/game/chat.lua
2
3 --
4 -- Chat message formatter
5 --
6
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"
11         local i
12
13         str, i = str:gsub("@name", name, 1)
14         if i == 0 then
15                 error(error_str:format("@name"), 2)
16         end
17
18         str, i = str:gsub("@message", message, 1)
19         if i == 0 then
20                 error(error_str:format("@message"), 2)
21         end
22
23         str = str:gsub("@timestamp", os.date("%H:%M:%S", os.time()), 1)
24
25         return str
26 end
27
28 --
29 -- Chat command handler
30 --
31
32 core.chatcommands = core.registered_chatcommands -- BACKWARDS COMPATIBILITY
33
34 core.register_on_chat_message(function(name, message)
35         if message:sub(1,1) ~= "/" then
36                 return
37         end
38
39         local cmd, param = string.match(message, "^/([^ ]+) *(.*)")
40         if not cmd then
41                 core.chat_send_player(name, "-!- Empty command")
42                 return true
43         end
44
45         param = param or ""
46
47         local cmd_def = core.registered_chatcommands[cmd]
48         if not cmd_def then
49                 core.chat_send_player(name, "-!- Invalid command: " .. cmd)
50                 return true
51         end
52         local has_privs, missing_privs = core.check_player_privs(name, cmd_def.privs)
53         if has_privs then
54                 core.set_last_run_mod(cmd_def.mod_origin)
55                 local _, result = cmd_def.func(name, param)
56                 if result then
57                         core.chat_send_player(name, result)
58                 end
59         else
60                 core.chat_send_player(name, "You don't have permission"
61                                 .. " to run this command (missing privileges: "
62                                 .. table.concat(missing_privs, ", ") .. ")")
63         end
64         return true  -- Handled chat message
65 end)
66
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()
71 end
72
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)
76         local p1, p2
77         local args = str:split(" ")
78
79         if args[1] == "here" then
80                 p1, p2 = core.get_player_radius_area(player_name, tonumber(args[2]))
81                 if p1 == nil then
82                         return false, "Unable to get player " .. player_name .. " position"
83                 end
84         else
85                 p1, p2 = core.string_to_area(str)
86                 if p1 == nil then
87                         return false, "Incorrect area format. Expected: (x1,y1,z1) (x2,y2,z2)"
88                 end
89         end
90
91         return p1, p2
92 end
93
94 --
95 -- Chat commands
96 --
97 core.register_chatcommand("me", {
98         params = "<action>",
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)
104         end,
105 })
106
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")
111                 if admin then
112                         return true, "The administrator of this server is " .. admin .. "."
113                 else
114                         return false, "There's no administrator named in the config file."
115                 end
116         end,
117 })
118
119 core.register_chatcommand("privs", {
120         params = "[<name>]",
121         description = "Show privileges of yourself or another player",
122         func = function(caller, param)
123                 param = param:trim()
124                 local name = (param ~= "" and param or caller)
125                 if not core.player_exists(name) then
126                         return false, "Player " .. name .. " does not exist."
127                 end
128                 return true, "Privileges of " .. name .. ": "
129                         .. core.privs_to_string(
130                                 core.get_player_privs(name), ' ')
131         end,
132 })
133
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)
139                 param = param:trim()
140                 if param == "" then
141                         return false, "Invalid parameters (see /help haspriv)"
142                 end
143                 if not core.registered_privileges[param] then
144                         return false, "Unknown privilege!"
145                 end
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)
152                         end
153                 end
154                 return true, "Players online with the \"" .. param .. "\" privilege: " ..
155                         table.concat(players_with_priv, ", ")
156         end
157 })
158
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."
163         end
164
165         if not core.get_auth_handler().get_auth(grantname) then
166                 return false, "Player " .. grantname .. " does not exist."
167         end
168         local grantprivs = core.string_to_privs(grantprivstr)
169         if grantprivstr == "all" then
170                 grantprivs = core.registered_privileges
171         end
172         local privs = core.get_player_privs(grantname)
173         local privs_unknown = ""
174         local basic_privs =
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."
179                 end
180                 if not core.registered_privileges[priv] then
181                         privs_unknown = privs_unknown .. "Unknown privilege: " .. priv .. "\n"
182                 end
183                 privs[priv] = true
184         end
185         if privs_unknown ~= "" then
186                 return false, privs_unknown
187         end
188         for priv, _ in pairs(grantprivs) do
189                 -- call the on_grant callbacks
190                 core.run_priv_callbacks(grantname, priv, caller, "grant")
191         end
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, ' '))
198         end
199         return true, "Privileges of " .. grantname .. ": "
200                 .. core.privs_to_string(
201                         core.get_player_privs(grantname), ' ')
202 end
203
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)"
211                 end
212                 return handle_grant_command(name, grantname, grantprivstr)
213         end,
214 })
215
216 core.register_chatcommand("grantme", {
217         params = "<privilege> | all",
218         description = "Grant privileges to yourself",
219         func = function(name, param)
220                 if param == "" then
221                         return false, "Invalid parameters (see /help grantme)"
222                 end
223                 return handle_grant_command(name, name, param)
224         end,
225 })
226
227 core.register_chatcommand("revoke", {
228         params = "<name> (<privilege> | all)",
229         description = "Remove privileges from player",
230         privs = {},
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."
235                 end
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."
241                 end
242                 local revoke_privs = core.string_to_privs(revoke_priv_str)
243                 local privs = core.get_player_privs(revoke_name)
244                 local basic_privs =
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."
250                         end
251                 end
252                 if revoke_priv_str == "all" then
253                         revoke_privs = privs
254                         privs = {}
255                 else
256                         for priv, _ in pairs(revoke_privs) do
257                                 privs[priv] = nil
258                         end
259                 end
260
261                 for priv, _ in pairs(revoke_privs) do
262                         -- call the on_revoke callbacks
263                         core.run_priv_callbacks(revoke_name, priv, name, "revoke")
264                 end
265
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, ' '))
274                 end
275                 return true, "Privileges of " .. revoke_name .. ": "
276                         .. core.privs_to_string(
277                                 core.get_player_privs(revoke_name), ' ')
278         end,
279 })
280
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, "^([^ ]+) +(.+)$")
287                 if not toname then
288                         toname = param:match("^([^ ]+) *$")
289                         raw_password = nil
290                 end
291
292                 if not toname then
293                         return false, "Name field required"
294                 end
295
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"
301                 else
302                         core.set_player_password(toname,
303                                         core.get_password_hash(toname,
304                                                         raw_password))
305                         act_str_past = "set"
306                         act_str_pres = "sets"
307                 end
308
309                 if toname ~= name then
310                         core.chat_send_player(toname, "Your password was "
311                                         .. act_str_past .. " by " .. name)
312                 end
313
314                 core.log("action", name .. " " .. act_str_pres ..
315                                 " password of " .. toname .. ".")
316
317                 return true, "Password of player \"" .. toname .. "\" " .. act_str_past
318         end,
319 })
320
321 core.register_chatcommand("clearpassword", {
322         params = "<name>",
323         description = "Set empty password for a player",
324         privs = {password=true},
325         func = function(name, param)
326                 local toname = param
327                 if toname == "" then
328                         return false, "Name field required"
329                 end
330                 core.set_player_password(toname, '')
331
332                 core.log("action", name .. " clears password of " .. toname .. ".")
333
334                 return true, "Password of player \"" .. toname .. "\" cleared"
335         end,
336 })
337
338 core.register_chatcommand("auth_reload", {
339         params = "",
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.")
345         end,
346 })
347
348 core.register_chatcommand("remove_player", {
349         params = "<name>",
350         description = "Remove a player's data",
351         privs = {server=true},
352         func = function(name, param)
353                 local toname = param
354                 if toname == "" then
355                         return false, "Name field required"
356                 end
357
358                 local rc = core.remove_player(toname)
359
360                 if rc == 0 then
361                         core.log("action", name .. " removed player data of " .. toname .. ".")
362                         return true, "Player \"" .. toname .. "\" removed."
363                 elseif rc == 1 then
364                         return true, "No such player \"" .. toname .. "\" to remove."
365                 elseif rc == 2 then
366                         return true, "Player \"" .. toname .. "\" is connected, cannot remove."
367                 end
368
369                 return false, "Unhandled remove_player return code " .. rc .. ""
370         end,
371 })
372
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)
380                         local tries = {
381                                 {x=1,y=0,z=0},
382                                 {x=-1,y=0,z=0},
383                                 {x=0,y=0,z=1},
384                                 {x=0,y=0,z=-1},
385                         }
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)
389                                 if n and n.name then
390                                         local def = core.registered_nodes[n.name]
391                                         if def and not def.walkable then
392                                                 return p, true
393                                         end
394                                 end
395                         end
396                         return pos, false
397                 end
398
399                 local p = {}
400                 p.x, p.y, p.z = string.match(param, "^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
401                 p.x = tonumber(p.x)
402                 p.y = tonumber(p.y)
403                 p.z = tonumber(p.z)
404                 if p.x and p.y and p.z then
405
406                         local lm = 31000
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!"
409                         end
410                         local teleportee = core.get_player_by_name(name)
411                         if teleportee then
412                                 teleportee:set_pos(p)
413                                 return true, "Teleporting to "..core.pos_to_string(p)
414                         end
415                 end
416
417                 local target_name = param:match("^([^ ]+)$")
418                 local teleportee = core.get_player_by_name(name)
419
420                 p = nil
421                 if target_name then
422                         local target = core.get_player_by_name(target_name)
423                         if target then
424                                 p = target:get_pos()
425                         end
426                 end
427
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)
433                 end
434
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)"
437                 end
438
439                 teleportee = nil
440                 p = {}
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)
447                 end
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)
452                 end
453
454                 teleportee = nil
455                 p = nil
456                 teleportee_name, target_name = string.match(param, "^([^ ]+) +([^ ]+)$")
457                 if teleportee_name then
458                         teleportee = core.get_player_by_name(teleportee_name)
459                 end
460                 if target_name then
461                         local target = core.get_player_by_name(target_name)
462                         if target then
463                                 p = target:get_pos()
464                         end
465                 end
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)
472                 end
473
474                 return false, 'Invalid parameters ("' .. param
475                                 .. '") or player not found (see /help teleport)'
476         end,
477 })
478
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
488                 end
489
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."
494                         end
495                         core.settings:set(setname, setvalue)
496                         return true, setname .. " = " .. setvalue
497                 end
498
499                 setname = string.match(param, "([^ ]+)")
500                 if setname then
501                         setvalue = core.settings:get(setname)
502                         if not setvalue then
503                                 setvalue = "<not set>"
504                         end
505                         return true, setname .. " = " .. setvalue
506                 end
507
508                 return false, "Invalid parameters (see /help set)."
509         end,
510 })
511
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
516         end
517         ctx.current_blocks = ctx.current_blocks + 1
518
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))
523         end
524 end
525
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))
532
533                 core.after(2, emergeblocks_progress_update, ctx)
534         end
535 end
536
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)
544                 if p1 == false then
545                         return false, p2
546                 end
547
548                 local context = {
549                         current_blocks = 0,
550                         total_blocks   = 0,
551                         start_time     = os.clock(),
552                         requestor_name = name
553                 }
554
555                 core.emerge_area(p1, p2, emergeblocks_callback, context)
556                 core.after(2, emergeblocks_progress_update, context)
557
558                 return true, "Started emerge of area ranging from " ..
559                         core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
560         end,
561 })
562
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)
570                 if p1 == false then
571                         return false, p2
572                 end
573
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)
577                 else
578                         return false, "Failed to clear one or more blocks in area"
579                 end
580         end,
581 })
582
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)
590                 if p1 == false then
591                         return false, p2
592                 end
593
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)
597                 else
598                         return false, "Failed to load one or more blocks in area"
599                 end
600         end,
601 })
602
603 core.register_chatcommand("mods", {
604         params = "",
605         description = "List mods installed on the server",
606         privs = {},
607         func = function(name, param)
608                 return true, table.concat(core.get_modnames(), ", ")
609         end,
610 })
611
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"
623         end
624         local receiverref = core.get_player_by_name(receiver)
625         if receiverref == nil then
626                 return false, receiver .. " is not a known player"
627         end
628         local leftover = receiverref:get_inventory():add_item("main", itemstack)
629         local partiality
630         if leftover:is_empty() then
631                 partiality = ""
632         elseif leftover:get_count() == itemstack:get_count() then
633                 partiality = "could not be "
634         else
635                 partiality = "partially "
636         end
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)
643         else
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)
648         end
649 end
650
651 core.register_chatcommand("give", {
652         params = "<name> <ItemString> [<count> [<wear>]]",
653         description = "Give item to player",
654         privs = {give=true},
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"
659                 end
660                 return handle_give_command("/give", name, toname, itemstring)
661         end,
662 })
663
664 core.register_chatcommand("giveme", {
665         params = "<ItemString> [<count> [<wear>]]",
666         description = "Give item to yourself",
667         privs = {give=true},
668         func = function(name, param)
669                 local itemstring = string.match(param, "(.+)$")
670                 if not itemstring then
671                         return false, "ItemString required"
672                 end
673                 return handle_give_command("/giveme", name, name, itemstring)
674         end,
675 })
676
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"
685                 end
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"
692                 end
693                 if not core.registered_entities[entityname] then
694                         return false, "Cannot spawn an unknown entity"
695                 end
696                 if p == "" then
697                         p = player:get_pos()
698                 else
699                         p = core.string_to_pos(p)
700                         if p == nil then
701                                 return false, "Invalid parameters ('" .. param .. "')"
702                         end
703                 end
704                 p.y = p.y + 1
705                 core.add_entity(p, entityname)
706                 return true, ("%q spawned."):format(entityname)
707         end,
708 })
709
710 core.register_chatcommand("pulverize", {
711         params = "",
712         description = "Destroy item in hand",
713         func = function(name, param)
714                 local player = core.get_player_by_name(name)
715                 if not player then
716                         core.log("error", "Unable to pulverize, no player.")
717                         return false, "Unable to pulverize, no player."
718                 end
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."
722                 end
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."
727         end,
728 })
729
730 -- Key = player name
731 core.rollback_punch_callbacks = {}
732
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
738         end
739 end)
740
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."
750                 end
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
756                 if limit > 100 then
757                         return false, "That limit is too high!"
758                 end
759
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)
764                         if not actions then
765                                 core.chat_send_player(name, "Rollback functions are disabled")
766                                 return
767                         end
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")
773                                 return
774                         end
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.")
780                                                 :format(
781                                                         core.pos_to_string(action.pos),
782                                                         action.actor,
783                                                         action.oldnode.name,
784                                                         action.newnode.name,
785                                                         time - action.time))
786                         end
787                 end
788
789                 return true, "Punch a node (range=" .. range .. ", seconds="
790                                 .. seconds .. "s, limit=" .. limit .. ")"
791         end,
792 })
793
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."
801                 end
802                 local target_name, seconds = string.match(param, ":([^ ]+) *(%d*)")
803                 if not target_name then
804                         local player_name
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."
809                         end
810                         target_name = "player:"..player_name
811                 end
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)
818                 local response = ""
819                 if #log > 100 then
820                         response = "(log is too long to show)\n"
821                 else
822                         for _, line in pairs(log) do
823                                 response = response .. line .. "\n"
824                         end
825                 end
826                 response = response .. "Reverting actions "
827                                 .. (success and "succeeded." or "FAILED.")
828                 return success, response
829         end,
830 })
831
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
837                         return true, status
838                 end
839                 return false, "This command was disabled by a mod or game"
840         end,
841 })
842
843 core.register_chatcommand("time", {
844         params = "[<0..23>:<0..59> | <0..24000>]",
845         description = "Show or set time of day",
846         privs = {},
847         func = function(name, param)
848                 if param == "" then
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)
853                 end
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)."
858                 end
859                 local hour, minute = param:match("^(%d+):(%d+)$")
860                 if not hour then
861                         local new_time = tonumber(param)
862                         if not new_time then
863                                 return false, "Invalid time."
864                         end
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."
869                 end
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)."
876                 end
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."
880         end,
881 })
882
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()
887         end
888 })
889
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+)(.*)")
897                 if param then
898                         reconnect, param = param:match("^%s*(%S+)(.*)")
899                 end
900                 message = param and param:match("^%s*(.+)") or ""
901                 delay = tonumber(delay) or 0
902
903                 if delay == 0 then
904                         core.log("action", name .. " shuts down server")
905                         core.chat_send_all("*** Server shutting down (operator request).")
906                 end
907                 core.request_shutdown(message:trim(), core.is_yes(reconnect), delay)
908         end,
909 })
910
911 core.register_chatcommand("ban", {
912         params = "[<name>]",
913         description = "Ban the IP of a player or show the ban list",
914         privs = {ban=true},
915         func = function(name, param)
916                 if param == "" then
917                         local ban_list = core.get_ban_list()
918                         if ban_list == "" then
919                                 return true, "The ban list is empty."
920                         else
921                                 return true, "Ban list: " .. ban_list
922                         end
923                 end
924                 if not core.get_player_by_name(param) then
925                         return false, "Player is not online."
926                 end
927                 if not core.ban_player(param) then
928                         return false, "Failed to ban player."
929                 end
930                 local desc = core.get_ban_description(param)
931                 core.log("action", name .. " bans " .. desc .. ".")
932                 return true, "Banned " .. desc .. "."
933         end,
934 })
935
936 core.register_chatcommand("unban", {
937         params = "<name> | <IP_address>",
938         description = "Remove IP ban belonging to a player/IP",
939         privs = {ban=true},
940         func = function(name, param)
941                 if not core.unban_player_or_ip(param) then
942                         return false, "Failed to unban player/IP."
943                 end
944                 core.log("action", name .. " unbans " .. param)
945                 return true, "Unbanned " .. param
946         end,
947 })
948
949 core.register_chatcommand("kick", {
950         params = "<name> [<reason>]",
951         description = "Kick a player",
952         privs = {kick=true},
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
958                 end
959                 local log_reason = ""
960                 if reason then
961                         log_reason = " with reason \"" .. reason .. "\""
962                 end
963                 core.log("action", name .. " kicks " .. tokick .. log_reason)
964                 return true, "Kicked " .. tokick
965         end,
966 })
967
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)
973                 local options = {}
974                 if param == "" or param == "quick" then
975                         options.mode = "quick"
976                 elseif param == "full" then
977                         options.mode = "full"
978                 else
979                         return false, "Invalid usage, see /help clearobjects."
980                 end
981
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 "
986                                 .. name .. ")")
987                 core.clear_objects(options)
988                 core.log("action", "Object clearing done.")
989                 core.chat_send_all("*** Cleared all objects.")
990         end,
991 })
992
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(.+)$")
999                 if not sendto then
1000                         return false, "Invalid usage, see /help msg."
1001                 end
1002                 if not core.get_player_by_name(sendto) then
1003                         return false, "The player " .. sendto
1004                                         .. " is not online."
1005                 end
1006                 core.log("action", "DM from " .. name .. " to " .. sendto
1007                                 .. ": " .. message)
1008                 core.chat_send_player(sendto, "DM from " .. name .. ": "
1009                                 .. message)
1010                 return true, "Message sent."
1011         end,
1012 })
1013
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)
1018                 if param == "" then
1019                         param = name
1020                 end
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)
1026                 end
1027                 return false, "Last login time is unknown"
1028         end,
1029 })
1030
1031 core.register_chatcommand("clearinv", {
1032         params = "[<name>]",
1033         description = "Clear the inventory of yourself or another player",
1034         func = function(name, param)
1035                 local player
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)"
1040                         end
1041                         player = core.get_player_by_name(param)
1042                         core.chat_send_player(param, name.." cleared your inventory.")
1043                 else
1044                         player = core.get_player_by_name(name)
1045                 end
1046
1047                 if player then
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."
1053                 else
1054                         return false, "Player must be online to clear inventory!"
1055                 end
1056         end,
1057 })
1058
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."
1062         end
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."
1069                 else
1070                         return false, string.format("%s is already dead.", victim)
1071                 end
1072         end
1073         if not killer == victim then
1074                 core.log("action", string.format("%s killed %s", killer, victim))
1075         end
1076         -- Kill victim
1077         victimref:set_hp(0)
1078         return true, string.format("%s has been killed.", victim)
1079 end
1080
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)
1087         end,
1088 })