Make the server status message customizable (#7357)
[oweals/minetest.git] / builtin / game / chatcommands.lua
1 -- Minetest: builtin/game/chatcommands.lua
2
3 --
4 -- Chat command handler
5 --
6
7 core.chatcommands = core.registered_chatcommands -- BACKWARDS COMPATIBILITY
8
9 core.register_on_chat_message(function(name, message)
10         if message:sub(1,1) ~= "/" then
11                 return
12         end
13
14         local cmd, param = string.match(message, "^/([^ ]+) *(.*)")
15         if not cmd then
16                 core.chat_send_player(name, "-!- Empty command")
17                 return true
18         end
19
20         param = param or ""
21
22         local cmd_def = core.registered_chatcommands[cmd]
23         if not cmd_def then
24                 core.chat_send_player(name, "-!- Invalid command: " .. cmd)
25                 return true
26         end
27         local has_privs, missing_privs = core.check_player_privs(name, cmd_def.privs)
28         if has_privs then
29                 core.set_last_run_mod(cmd_def.mod_origin)
30                 local success, message = cmd_def.func(name, param)
31                 if message then
32                         core.chat_send_player(name, message)
33                 end
34         else
35                 core.chat_send_player(name, "You don't have permission"
36                                 .. " to run this command (missing privileges: "
37                                 .. table.concat(missing_privs, ", ") .. ")")
38         end
39         return true  -- Handled chat message
40 end)
41
42 if core.settings:get_bool("profiler.load") then
43         -- Run after register_chatcommand and its register_on_chat_message
44         -- Before any chatcommands that should be profiled
45         profiler.init_chatcommand()
46 end
47
48 -- Parses a "range" string in the format of "here (number)" or
49 -- "(x1, y1, z1) (x2, y2, z2)", returning two position vectors
50 local function parse_range_str(player_name, str)
51         local p1, p2
52         local args = str:split(" ")
53
54         if args[1] == "here" then
55                 p1, p2 = core.get_player_radius_area(player_name, tonumber(args[2]))
56                 if p1 == nil then
57                         return false, "Unable to get player " .. player_name .. " position"
58                 end
59         else
60                 p1, p2 = core.string_to_area(str)
61                 if p1 == nil then
62                         return false, "Incorrect area format. Expected: (x1,y1,z1) (x2,y2,z2)"
63                 end
64         end
65
66         return p1, p2
67 end
68
69 --
70 -- Chat commands
71 --
72 core.register_chatcommand("me", {
73         params = "<action>",
74         description = "Show chat action (e.g., '/me orders a pizza' displays"
75                         .. " '<player name> orders a pizza')",
76         privs = {shout=true},
77         func = function(name, param)
78                 core.chat_send_all("* " .. name .. " " .. param)
79         end,
80 })
81
82 core.register_chatcommand("admin", {
83         description = "Show the name of the server owner",
84         func = function(name)
85                 local admin = core.settings:get("name")
86                 if admin then
87                         return true, "The administrator of this server is "..admin.."."
88                 else
89                         return false, "There's no administrator named in the config file."
90                 end
91         end,
92 })
93
94 core.register_chatcommand("privs", {
95         params = "[<name>]",
96         description = "Show privileges of yourself or another player",
97         func = function(caller, param)
98                 param = param:trim()
99                 local name = (param ~= "" and param or caller)
100                 return true, "Privileges of " .. name .. ": "
101                         .. core.privs_to_string(
102                                 core.get_player_privs(name), ' ')
103         end,
104 })
105
106 core.register_chatcommand("hasprivs", {
107         params = "<privilege>",
108         description = "Return list of all online players with privilege.",
109         privs = {basic_privs = true},
110         func = function(caller, param)
111                 param = param:trim()
112                 if param == "" then
113                         return false, "Invalid parameters (see /help hasprivs)"
114                 end
115                 if not core.registered_privileges[param] then
116                         return false, "Unknown privilege!"
117                 end
118                 local privs = core.string_to_privs(param)
119                 local players_with_privs = {}
120                 for _, player in pairs(core.get_connected_players()) do
121                         local player_name = player:get_player_name()
122                         if core.check_player_privs(player_name, privs) then
123                                 table.insert(players_with_privs, player_name)
124                         end
125                 end     
126                 return true, "Players online with the \"" .. param .. "\" priv: " ..
127                         table.concat(players_with_privs, ", ")
128         end     
129 })
130
131 local function handle_grant_command(caller, grantname, grantprivstr)
132         local caller_privs = core.get_player_privs(caller)
133         if not (caller_privs.privs or caller_privs.basic_privs) then
134                 return false, "Your privileges are insufficient."
135         end
136
137         if not core.get_auth_handler().get_auth(grantname) then
138                 return false, "Player " .. grantname .. " does not exist."
139         end
140         local grantprivs = core.string_to_privs(grantprivstr)
141         if grantprivstr == "all" then
142                 grantprivs = core.registered_privileges
143         end
144         local privs = core.get_player_privs(grantname)
145         local privs_unknown = ""
146         local basic_privs =
147                 core.string_to_privs(core.settings:get("basic_privs") or "interact,shout")
148         for priv, _ in pairs(grantprivs) do
149                 if not basic_privs[priv] and not caller_privs.privs then
150                         return false, "Your privileges are insufficient."
151                 end
152                 if not core.registered_privileges[priv] then
153                         privs_unknown = privs_unknown .. "Unknown privilege: " .. priv .. "\n"
154                 end
155                 privs[priv] = true
156         end
157         if privs_unknown ~= "" then
158                 return false, privs_unknown
159         end
160         for priv, _ in pairs(grantprivs) do
161                 core.run_priv_callbacks(grantname, priv, caller, "grant")
162         end
163         core.set_player_privs(grantname, privs)
164         core.log("action", caller..' granted ('..core.privs_to_string(grantprivs, ', ')..') privileges to '..grantname)
165         if grantname ~= caller then
166                 core.chat_send_player(grantname, caller
167                                 .. " granted you privileges: "
168                                 .. core.privs_to_string(grantprivs, ' '))
169         end
170         return true, "Privileges of " .. grantname .. ": "
171                 .. core.privs_to_string(
172                         core.get_player_privs(grantname), ' ')
173 end
174
175 core.register_chatcommand("grant", {
176         params = "<name> (<privilege> | all)",
177         description = "Give privileges to player",
178         func = function(name, param)
179                 local grantname, grantprivstr = string.match(param, "([^ ]+) (.+)")
180                 if not grantname or not grantprivstr then
181                         return false, "Invalid parameters (see /help grant)"
182                 end
183                 return handle_grant_command(name, grantname, grantprivstr)
184         end,
185 })
186
187 core.register_chatcommand("grantme", {
188         params = "<privilege> | all",
189         description = "Grant privileges to yourself",
190         func = function(name, param)
191                 if param == "" then
192                         return false, "Invalid parameters (see /help grantme)"
193                 end
194                 return handle_grant_command(name, name, param)
195         end,
196 })
197
198 core.register_chatcommand("revoke", {
199         params = "<name> (<privilege> | all)",
200         description = "Remove privileges from player",
201         privs = {},
202         func = function(name, param)
203                 if not core.check_player_privs(name, {privs=true}) and
204                                 not core.check_player_privs(name, {basic_privs=true}) then
205                         return false, "Your privileges are insufficient."
206                 end
207                 local revoke_name, revoke_priv_str = string.match(param, "([^ ]+) (.+)")
208                 if not revoke_name or not revoke_priv_str then
209                         return false, "Invalid parameters (see /help revoke)"
210                 elseif not core.get_auth_handler().get_auth(revoke_name) then
211                         return false, "Player " .. revoke_name .. " does not exist."
212                 end
213                 local revoke_privs = core.string_to_privs(revoke_priv_str)
214                 local privs = core.get_player_privs(revoke_name)
215                 local basic_privs =
216                         core.string_to_privs(core.settings:get("basic_privs") or "interact,shout")
217                 for priv, _ in pairs(revoke_privs) do
218                         if not basic_privs[priv] and
219                                         not core.check_player_privs(name, {privs=true}) then
220                                 return false, "Your privileges are insufficient."
221                         end
222                 end
223                 if revoke_priv_str == "all" then
224                         revoke_privs = privs
225                         privs = {}
226                 else
227                         for priv, _ in pairs(revoke_privs) do
228                                 privs[priv] = nil
229                         end
230                 end
231
232                 for priv, _ in pairs(revoke_privs) do
233                         core.run_priv_callbacks(revoke_name, priv, name, "revoke")
234                 end
235
236                 core.set_player_privs(revoke_name, privs)
237                 core.log("action", name..' revoked ('
238                                 ..core.privs_to_string(revoke_privs, ', ')
239                                 ..') privileges from '..revoke_name)
240                 if revoke_name ~= name then
241                         core.chat_send_player(revoke_name, name
242                                         .. " revoked privileges from you: "
243                                         .. core.privs_to_string(revoke_privs, ' '))
244                 end
245                 return true, "Privileges of " .. revoke_name .. ": "
246                         .. core.privs_to_string(
247                                 core.get_player_privs(revoke_name), ' ')
248         end,
249 })
250
251 core.register_chatcommand("setpassword", {
252         params = "<name> <password>",
253         description = "Set player's password",
254         privs = {password=true},
255         func = function(name, param)
256                 local toname, raw_password = string.match(param, "^([^ ]+) +(.+)$")
257                 if not toname then
258                         toname = param:match("^([^ ]+) *$")
259                         raw_password = nil
260                 end
261                 if not toname then
262                         return false, "Name field required"
263                 end
264                 local act_str_past = "?"
265                 local act_str_pres = "?"
266                 if not raw_password then
267                         core.set_player_password(toname, "")
268                         act_str_past = "cleared"
269                         act_str_pres = "clears"
270                 else
271                         core.set_player_password(toname,
272                                         core.get_password_hash(toname,
273                                                         raw_password))
274                         act_str_past = "set"
275                         act_str_pres = "sets"
276                 end
277                 if toname ~= name then
278                         core.chat_send_player(toname, "Your password was "
279                                         .. act_str_past .. " by " .. name)
280                 end
281
282                 core.log("action", name .. " " .. act_str_pres
283                 .. " password of " .. toname .. ".")
284
285                 return true, "Password of player \"" .. toname .. "\" " .. act_str_past
286         end,
287 })
288
289 core.register_chatcommand("clearpassword", {
290         params = "<name>",
291         description = "Set empty password for a player",
292         privs = {password=true},
293         func = function(name, param)
294                 local toname = param
295                 if toname == "" then
296                         return false, "Name field required"
297                 end
298                 core.set_player_password(toname, '')
299
300                 core.log("action", name .. " clears password of " .. toname .. ".")
301
302                 return true, "Password of player \"" .. toname .. "\" cleared"
303         end,
304 })
305
306 core.register_chatcommand("auth_reload", {
307         params = "",
308         description = "Reload authentication data",
309         privs = {server=true},
310         func = function(name, param)
311                 local done = core.auth_reload()
312                 return done, (done and "Done." or "Failed.")
313         end,
314 })
315
316 core.register_chatcommand("remove_player", {
317         params = "<name>",
318         description = "Remove a player's data",
319         privs = {server=true},
320         func = function(name, param)
321                 local toname = param
322                 if toname == "" then
323                         return false, "Name field required"
324                 end
325
326                 local rc = core.remove_player(toname)
327
328                 if rc == 0 then
329                         core.log("action", name .. " removed player data of " .. toname .. ".")
330                         return true, "Player \"" .. toname .. "\" removed."
331                 elseif rc == 1 then
332                         return true, "No such player \"" .. toname .. "\" to remove."
333                 elseif rc == 2 then
334                         return true, "Player \"" .. toname .. "\" is connected, cannot remove."
335                 end
336
337                 return false, "Unhandled remove_player return code " .. rc .. ""
338         end,
339 })
340
341 core.register_chatcommand("teleport", {
342         params = "<X>,<Y>,<Z> | <to_name> | (<name> <X>,<Y>,<Z>) | (<name> <to_name>)",
343         description = "Teleport to position or player",
344         privs = {teleport=true},
345         func = function(name, param)
346                 -- Returns (pos, true) if found, otherwise (pos, false)
347                 local function find_free_position_near(pos)
348                         local tries = {
349                                 {x=1,y=0,z=0},
350                                 {x=-1,y=0,z=0},
351                                 {x=0,y=0,z=1},
352                                 {x=0,y=0,z=-1},
353                         }
354                         for _, d in ipairs(tries) do
355                                 local p = {x = pos.x+d.x, y = pos.y+d.y, z = pos.z+d.z}
356                                 local n = core.get_node_or_nil(p)
357                                 if n and n.name then
358                                         local def = core.registered_nodes[n.name]
359                                         if def and not def.walkable then
360                                                 return p, true
361                                         end
362                                 end
363                         end
364                         return pos, false
365                 end
366
367                 local teleportee = nil
368                 local p = {}
369                 p.x, p.y, p.z = string.match(param, "^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
370                 p.x = tonumber(p.x)
371                 p.y = tonumber(p.y)
372                 p.z = tonumber(p.z)
373                 if p.x and p.y and p.z then
374                         local lm = 31000
375                         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
376                                 return false, "Cannot teleport out of map bounds!"
377                         end
378                         teleportee = core.get_player_by_name(name)
379                         if teleportee then
380                                 teleportee:setpos(p)
381                                 return true, "Teleporting to "..core.pos_to_string(p)
382                         end
383                 end
384
385                 local teleportee = nil
386                 local p = nil
387                 local target_name = nil
388                 target_name = param:match("^([^ ]+)$")
389                 teleportee = core.get_player_by_name(name)
390                 if target_name then
391                         local target = core.get_player_by_name(target_name)
392                         if target then
393                                 p = target:getpos()
394                         end
395                 end
396                 if teleportee and p then
397                         p = find_free_position_near(p)
398                         teleportee:setpos(p)
399                         return true, "Teleporting to " .. target_name
400                                         .. " at "..core.pos_to_string(p)
401                 end
402
403                 if not core.check_player_privs(name, {bring=true}) then
404                         return false, "You don't have permission to teleport other players (missing bring privilege)"
405                 end
406
407                 local teleportee = nil
408                 local p = {}
409                 local teleportee_name = nil
410                 teleportee_name, p.x, p.y, p.z = param:match(
411                                 "^([^ ]+) +([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
412                 p.x, p.y, p.z = tonumber(p.x), tonumber(p.y), tonumber(p.z)
413                 if teleportee_name then
414                         teleportee = core.get_player_by_name(teleportee_name)
415                 end
416                 if teleportee and p.x and p.y and p.z then
417                         teleportee:setpos(p)
418                         return true, "Teleporting " .. teleportee_name
419                                         .. " to " .. core.pos_to_string(p)
420                 end
421
422                 local teleportee = nil
423                 local p = nil
424                 local teleportee_name = nil
425                 local target_name = nil
426                 teleportee_name, target_name = string.match(param, "^([^ ]+) +([^ ]+)$")
427                 if teleportee_name then
428                         teleportee = core.get_player_by_name(teleportee_name)
429                 end
430                 if target_name then
431                         local target = core.get_player_by_name(target_name)
432                         if target then
433                                 p = target:getpos()
434                         end
435                 end
436                 if teleportee and p then
437                         p = find_free_position_near(p)
438                         teleportee:setpos(p)
439                         return true, "Teleporting " .. teleportee_name
440                                         .. " to " .. target_name
441                                         .. " at " .. core.pos_to_string(p)
442                 end
443
444                 return false, 'Invalid parameters ("' .. param
445                                 .. '") or player not found (see /help teleport)'
446         end,
447 })
448
449 core.register_chatcommand("set", {
450         params = "([-n] <name> <value>) | <name>",
451         description = "Set or read server configuration setting",
452         privs = {server=true},
453         func = function(name, param)
454                 local arg, setname, setvalue = string.match(param, "(-[n]) ([^ ]+) (.+)")
455                 if arg and arg == "-n" and setname and setvalue then
456                         core.settings:set(setname, setvalue)
457                         return true, setname .. " = " .. setvalue
458                 end
459                 local setname, setvalue = string.match(param, "([^ ]+) (.+)")
460                 if setname and setvalue then
461                         if not core.settings:get(setname) then
462                                 return false, "Failed. Use '/set -n <name> <value>' to create a new setting."
463                         end
464                         core.settings:set(setname, setvalue)
465                         return true, setname .. " = " .. setvalue
466                 end
467                 local setname = string.match(param, "([^ ]+)")
468                 if setname then
469                         local setvalue = core.settings:get(setname)
470                         if not setvalue then
471                                 setvalue = "<not set>"
472                         end
473                         return true, setname .. " = " .. setvalue
474                 end
475                 return false, "Invalid parameters (see /help set)."
476         end,
477 })
478
479 local function emergeblocks_callback(pos, action, num_calls_remaining, ctx)
480         if ctx.total_blocks == 0 then
481                 ctx.total_blocks   = num_calls_remaining + 1
482                 ctx.current_blocks = 0
483         end
484         ctx.current_blocks = ctx.current_blocks + 1
485
486         if ctx.current_blocks == ctx.total_blocks then
487                 core.chat_send_player(ctx.requestor_name,
488                         string.format("Finished emerging %d blocks in %.2fms.",
489                         ctx.total_blocks, (os.clock() - ctx.start_time) * 1000))
490         end
491 end
492
493 local function emergeblocks_progress_update(ctx)
494         if ctx.current_blocks ~= ctx.total_blocks then
495                 core.chat_send_player(ctx.requestor_name,
496                         string.format("emergeblocks update: %d/%d blocks emerged (%.1f%%)",
497                         ctx.current_blocks, ctx.total_blocks,
498                         (ctx.current_blocks / ctx.total_blocks) * 100))
499
500                 core.after(2, emergeblocks_progress_update, ctx)
501         end
502 end
503
504 core.register_chatcommand("emergeblocks", {
505         params = "(here [<radius>]) | (<pos1> <pos2>)",
506         description = "Load (or, if nonexistent, generate) map blocks "
507                 .. "contained in area pos1 to pos2 (<pos1> and <pos2> must be in parentheses)",
508         privs = {server=true},
509         func = function(name, param)
510                 local p1, p2 = parse_range_str(name, param)
511                 if p1 == false then
512                         return false, p2
513                 end
514
515                 local context = {
516                         current_blocks = 0,
517                         total_blocks   = 0,
518                         start_time     = os.clock(),
519                         requestor_name = name
520                 }
521
522                 core.emerge_area(p1, p2, emergeblocks_callback, context)
523                 core.after(2, emergeblocks_progress_update, context)
524
525                 return true, "Started emerge of area ranging from " ..
526                         core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
527         end,
528 })
529
530 core.register_chatcommand("deleteblocks", {
531         params = "(here [<radius>]) | (<pos1> <pos2>)",
532         description = "Delete map blocks contained in area pos1 to pos2 "
533                 .. "(<pos1> and <pos2> must be in parentheses)",
534         privs = {server=true},
535         func = function(name, param)
536                 local p1, p2 = parse_range_str(name, param)
537                 if p1 == false then
538                         return false, p2
539                 end
540
541                 if core.delete_area(p1, p2) then
542                         return true, "Successfully cleared area ranging from " ..
543                                 core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
544                 else
545                         return false, "Failed to clear one or more blocks in area"
546                 end
547         end,
548 })
549
550 core.register_chatcommand("fixlight", {
551         params = "(here [<radius>]) | (<pos1> <pos2>)",
552         description = "Resets lighting in the area between pos1 and pos2 "
553                 .. "(<pos1> and <pos2> must be in parentheses)",
554         privs = {server = true},
555         func = function(name, param)
556                 local p1, p2 = parse_range_str(name, param)
557                 if p1 == false then
558                         return false, p2
559                 end
560
561                 if core.fix_light(p1, p2) then
562                         return true, "Successfully reset light in the area ranging from " ..
563                                 core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
564                 else
565                         return false, "Failed to load one or more blocks in area"
566                 end
567         end,
568 })
569
570 core.register_chatcommand("mods", {
571         params = "",
572         description = "List mods installed on the server",
573         privs = {},
574         func = function(name, param)
575                 return true, table.concat(core.get_modnames(), ", ")
576         end,
577 })
578
579 local function handle_give_command(cmd, giver, receiver, stackstring)
580         core.log("action", giver .. " invoked " .. cmd
581                         .. ', stackstring="' .. stackstring .. '"')
582         local itemstack = ItemStack(stackstring)
583         if itemstack:is_empty() then
584                 return false, "Cannot give an empty item"
585         elseif (not itemstack:is_known()) or (itemstack:get_name() == "unknown") then
586                 return false, "Cannot give an unknown item"
587         -- Forbid giving 'ignore' due to unwanted side effects
588         elseif itemstack:get_name() == "ignore" then
589                 return false, "Giving 'ignore' is not allowed"
590         end
591         local receiverref = core.get_player_by_name(receiver)
592         if receiverref == nil then
593                 return false, receiver .. " is not a known player"
594         end
595         local leftover = receiverref:get_inventory():add_item("main", itemstack)
596         local partiality
597         if leftover:is_empty() then
598                 partiality = ""
599         elseif leftover:get_count() == itemstack:get_count() then
600                 partiality = "could not be "
601         else
602                 partiality = "partially "
603         end
604         -- The actual item stack string may be different from what the "giver"
605         -- entered (e.g. big numbers are always interpreted as 2^16-1).
606         stackstring = itemstack:to_string()
607         if giver == receiver then
608                 local msg = "%q %sadded to inventory."
609                 return true, msg:format(stackstring, partiality)
610         else
611                 core.chat_send_player(receiver, ("%q %sadded to inventory.")
612                                 :format(stackstring, partiality))
613                 local msg = "%q %sadded to %s's inventory."
614                 return true, msg:format(stackstring, partiality, receiver)
615         end
616 end
617
618 core.register_chatcommand("give", {
619         params = "<name> <ItemString> [<count> [<wear>]]",
620         description = "Give item to player",
621         privs = {give=true},
622         func = function(name, param)
623                 local toname, itemstring = string.match(param, "^([^ ]+) +(.+)$")
624                 if not toname or not itemstring then
625                         return false, "Name and ItemString required"
626                 end
627                 return handle_give_command("/give", name, toname, itemstring)
628         end,
629 })
630
631 core.register_chatcommand("giveme", {
632         params = "<ItemString> [<count> [<wear>]]",
633         description = "Give item to yourself",
634         privs = {give=true},
635         func = function(name, param)
636                 local itemstring = string.match(param, "(.+)$")
637                 if not itemstring then
638                         return false, "ItemString required"
639                 end
640                 return handle_give_command("/giveme", name, name, itemstring)
641         end,
642 })
643
644 core.register_chatcommand("spawnentity", {
645         params = "<EntityName> [<X>,<Y>,<Z>]",
646         description = "Spawn entity at given (or your) position",
647         privs = {give=true, interact=true},
648         func = function(name, param)
649                 local entityname, p = string.match(param, "^([^ ]+) *(.*)$")
650                 if not entityname then
651                         return false, "EntityName required"
652                 end
653                 core.log("action", ("%s invokes /spawnentity, entityname=%q")
654                                 :format(name, entityname))
655                 local player = core.get_player_by_name(name)
656                 if player == nil then
657                         core.log("error", "Unable to spawn entity, player is nil")
658                         return false, "Unable to spawn entity, player is nil"
659                 end
660                 if not core.registered_entities[entityname] then
661                         return false, "Cannot spawn an unknown entity"
662                 end
663                 if p == "" then
664                         p = player:getpos()
665                 else
666                         p = core.string_to_pos(p)
667                         if p == nil then
668                                 return false, "Invalid parameters ('" .. param .. "')"
669                         end
670                 end
671                 p.y = p.y + 1
672                 core.add_entity(p, entityname)
673                 return true, ("%q spawned."):format(entityname)
674         end,
675 })
676
677 core.register_chatcommand("pulverize", {
678         params = "",
679         description = "Destroy item in hand",
680         func = function(name, param)
681                 local player = core.get_player_by_name(name)
682                 if not player then
683                         core.log("error", "Unable to pulverize, no player.")
684                         return false, "Unable to pulverize, no player."
685                 end
686                 local wielded_item = player:get_wielded_item()
687                 if wielded_item:is_empty() then
688                         return false, "Unable to pulverize, no item in hand."
689                 end
690                 core.log("action", name .. " pulverized \"" ..
691                         wielded_item:get_name() .. " " .. wielded_item:get_count() .. "\"")
692                 player:set_wielded_item(nil)                    
693                 return true, "An item was pulverized."
694         end,
695 })
696
697 -- Key = player name
698 core.rollback_punch_callbacks = {}
699
700 core.register_on_punchnode(function(pos, node, puncher)
701         local name = puncher and puncher:get_player_name()
702         if name and core.rollback_punch_callbacks[name] then
703                 core.rollback_punch_callbacks[name](pos, node, puncher)
704                 core.rollback_punch_callbacks[name] = nil
705         end
706 end)
707
708 core.register_chatcommand("rollback_check", {
709         params = "[<range>] [<seconds>] [<limit>]",
710         description = "Check who last touched a node or a node near it"
711                         .. " within the time specified by <seconds>. Default: range = 0,"
712                         .. " seconds = 86400 = 24h, limit = 5",
713         privs = {rollback=true},
714         func = function(name, param)
715                 if not core.settings:get_bool("enable_rollback_recording") then
716                         return false, "Rollback functions are disabled."
717                 end
718                 local range, seconds, limit =
719                         param:match("(%d+) *(%d*) *(%d*)")
720                 range = tonumber(range) or 0
721                 seconds = tonumber(seconds) or 86400
722                 limit = tonumber(limit) or 5
723                 if limit > 100 then
724                         return false, "That limit is too high!"
725                 end
726
727                 core.rollback_punch_callbacks[name] = function(pos, node, puncher)
728                         local name = puncher:get_player_name()
729                         core.chat_send_player(name, "Checking " .. core.pos_to_string(pos) .. "...")
730                         local actions = core.rollback_get_node_actions(pos, range, seconds, limit)
731                         if not actions then
732                                 core.chat_send_player(name, "Rollback functions are disabled")
733                                 return
734                         end
735                         local num_actions = #actions
736                         if num_actions == 0 then
737                                 core.chat_send_player(name, "Nobody has touched"
738                                                 .. " the specified location in "
739                                                 .. seconds .. " seconds")
740                                 return
741                         end
742                         local time = os.time()
743                         for i = num_actions, 1, -1 do
744                                 local action = actions[i]
745                                 core.chat_send_player(name,
746                                         ("%s %s %s -> %s %d seconds ago.")
747                                                 :format(
748                                                         core.pos_to_string(action.pos),
749                                                         action.actor,
750                                                         action.oldnode.name,
751                                                         action.newnode.name,
752                                                         time - action.time))
753                         end
754                 end
755
756                 return true, "Punch a node (range=" .. range .. ", seconds="
757                                 .. seconds .. "s, limit=" .. limit .. ")"
758         end,
759 })
760
761 core.register_chatcommand("rollback", {
762         params = "(<name> [<seconds>]) | (:<actor> [<seconds>])",
763         description = "Revert actions of a player. Default for <seconds> is 60",
764         privs = {rollback=true},
765         func = function(name, param)
766                 if not core.settings:get_bool("enable_rollback_recording") then
767                         return false, "Rollback functions are disabled."
768                 end
769                 local target_name, seconds = string.match(param, ":([^ ]+) *(%d*)")
770                 if not target_name then
771                         local player_name = nil
772                         player_name, seconds = string.match(param, "([^ ]+) *(%d*)")
773                         if not player_name then
774                                 return false, "Invalid parameters. See /help rollback"
775                                                 .. " and /help rollback_check."
776                         end
777                         target_name = "player:"..player_name
778                 end
779                 seconds = tonumber(seconds) or 60
780                 core.chat_send_player(name, "Reverting actions of "
781                                 .. target_name .. " since "
782                                 .. seconds .. " seconds.")
783                 local success, log = core.rollback_revert_actions_by(
784                                 target_name, seconds)
785                 local response = ""
786                 if #log > 100 then
787                         response = "(log is too long to show)\n"
788                 else
789                         for _, line in pairs(log) do
790                                 response = response .. line .. "\n"
791                         end
792                 end
793                 response = response .. "Reverting actions "
794                                 .. (success and "succeeded." or "FAILED.")
795                 return success, response
796         end,
797 })
798
799 core.register_chatcommand("status", {
800         description = "Show server status",
801         func = function(name, param)
802                 local status = core.get_server_status(name, false)
803                 if status and status ~= "" then
804                         return true, status
805                 end
806                 return false, "This command was disabled by a mod or game"
807         end,
808 })
809
810 core.register_chatcommand("time", {
811         params = "[<0..23>:<0..59> | <0..24000>]",
812         description = "Show or set time of day",
813         privs = {},
814         func = function(name, param)
815                 if param == "" then
816                         local current_time = math.floor(core.get_timeofday() * 1440)
817                         local minutes = current_time % 60
818                         local hour = (current_time - minutes) / 60
819                         return true, ("Current time is %d:%02d"):format(hour, minutes)
820                 end
821                 local player_privs = core.get_player_privs(name)
822                 if not player_privs.settime then
823                         return false, "You don't have permission to run this command " ..
824                                 "(missing privilege: settime)."
825                 end
826                 local hour, minute = param:match("^(%d+):(%d+)$")
827                 if not hour then
828                         local new_time = tonumber(param)
829                         if not new_time then
830                                 return false, "Invalid time."
831                         end
832                         -- Backward compatibility.
833                         core.set_timeofday((new_time % 24000) / 24000)
834                         core.log("action", name .. " sets time to " .. new_time)
835                         return true, "Time of day changed."
836                 end
837                 hour = tonumber(hour)
838                 minute = tonumber(minute)
839                 if hour < 0 or hour > 23 then
840                         return false, "Invalid hour (must be between 0 and 23 inclusive)."
841                 elseif minute < 0 or minute > 59 then
842                         return false, "Invalid minute (must be between 0 and 59 inclusive)."
843                 end
844                 core.set_timeofday((hour * 60 + minute) / 1440)
845                 core.log("action", ("%s sets time to %d:%02d"):format(name, hour, minute))
846                 return true, "Time of day changed."
847         end,
848 })
849
850 core.register_chatcommand("days", {
851         description = "Show day count since world creation",
852         func = function(name, param)
853                 return true, "Current day is " .. core.get_day_count()
854         end
855 })
856
857 core.register_chatcommand("shutdown", {
858         params = "[<delay_in_seconds> | -1] [reconnect] [<message>]",
859         description = "Shutdown server (-1 cancels a delayed shutdown)",
860         privs = {server=true},
861         func = function(name, param)
862                 local delay, reconnect, message
863                 delay, param = param:match("^%s*(%S+)(.*)")
864                 if param then
865                         reconnect, param = param:match("^%s*(%S+)(.*)")
866                 end
867                 message = param and param:match("^%s*(.+)") or ""
868                 delay = tonumber(delay) or 0
869
870                 if delay == 0 then
871                         core.log("action", name .. " shuts down server")
872                         core.chat_send_all("*** Server shutting down (operator request).")
873                 end
874                 core.request_shutdown(message:trim(), core.is_yes(reconnect), delay)
875         end,
876 })
877
878 core.register_chatcommand("ban", {
879         params = "[<name> | <IP_address>]",
880         description = "Ban player or show ban list",
881         privs = {ban=true},
882         func = function(name, param)
883                 if param == "" then
884                         local ban_list = core.get_ban_list()
885                         if ban_list == "" then
886                                 return true, "The ban list is empty."
887                         else
888                                 return true, "Ban list: " .. ban_list
889                         end
890                 end
891                 if not core.get_player_by_name(param) then
892                         return false, "No such player."
893                 end
894                 if not core.ban_player(param) then
895                         return false, "Failed to ban player."
896                 end
897                 local desc = core.get_ban_description(param)
898                 core.log("action", name .. " bans " .. desc .. ".")
899                 return true, "Banned " .. desc .. "."
900         end,
901 })
902
903 core.register_chatcommand("unban", {
904         params = "<name> | <IP_address>",
905         description = "Remove player ban",
906         privs = {ban=true},
907         func = function(name, param)
908                 if not core.unban_player_or_ip(param) then
909                         return false, "Failed to unban player/IP."
910                 end
911                 core.log("action", name .. " unbans " .. param)
912                 return true, "Unbanned " .. param
913         end,
914 })
915
916 core.register_chatcommand("kick", {
917         params = "<name> [<reason>]",
918         description = "Kick a player",
919         privs = {kick=true},
920         func = function(name, param)
921                 local tokick, reason = param:match("([^ ]+) (.+)")
922                 tokick = tokick or param
923                 if not core.kick_player(tokick, reason) then
924                         return false, "Failed to kick player " .. tokick
925                 end
926                 local log_reason = ""
927                 if reason then
928                         log_reason = " with reason \"" .. reason .. "\""
929                 end
930                 core.log("action", name .. " kicks " .. tokick .. log_reason)
931                 return true, "Kicked " .. tokick
932         end,
933 })
934
935 core.register_chatcommand("clearobjects", {
936         params = "[full | quick]",
937         description = "Clear all objects in world",
938         privs = {server=true},
939         func = function(name, param)
940                 local options = {}
941                 if param == "" or param == "quick" then
942                         options.mode = "quick"
943                 elseif param == "full" then
944                         options.mode = "full"
945                 else
946                         return false, "Invalid usage, see /help clearobjects."
947                 end
948
949                 core.log("action", name .. " clears all objects ("
950                                 .. options.mode .. " mode).")
951                 core.chat_send_all("Clearing all objects.  This may take long."
952                                 .. "  You may experience a timeout.  (by "
953                                 .. name .. ")")
954                 core.clear_objects(options)
955                 core.log("action", "Object clearing done.")
956                 core.chat_send_all("*** Cleared all objects.")
957         end,
958 })
959
960 core.register_chatcommand("msg", {
961         params = "<name> <message>",
962         description = "Send a private message",
963         privs = {shout=true},
964         func = function(name, param)
965                 local sendto, message = param:match("^(%S+)%s(.+)$")
966                 if not sendto then
967                         return false, "Invalid usage, see /help msg."
968                 end
969                 if not core.get_player_by_name(sendto) then
970                         return false, "The player " .. sendto
971                                         .. " is not online."
972                 end
973                 core.log("action", "PM from " .. name .. " to " .. sendto
974                                 .. ": " .. message)
975                 core.chat_send_player(sendto, "PM from " .. name .. ": "
976                                 .. message)
977                 return true, "Message sent."
978         end,
979 })
980
981 core.register_chatcommand("last-login", {
982         params = "[<name>]",
983         description = "Get the last login time of a player or yourself",
984         func = function(name, param)
985                 if param == "" then
986                         param = name
987                 end
988                 local pauth = core.get_auth_handler().get_auth(param)
989                 if pauth and pauth.last_login then
990                         -- Time in UTC, ISO 8601 format
991                         return true, "Last login time was " ..
992                                 os.date("!%Y-%m-%dT%H:%M:%SZ", pauth.last_login)
993                 end
994                 return false, "Last login time is unknown"
995         end,
996 })
997
998 core.register_chatcommand("clearinv", {
999         params = "[<name>]",
1000         description = "Clear the inventory of yourself or another player",
1001         func = function(name, param)
1002                 local player
1003                 if param and param ~= "" and param ~= name then
1004                         if not core.check_player_privs(name, {server=true}) then
1005                                 return false, "You don't have permission"
1006                                                 .. " to clear another player's inventory (missing privilege: server)"
1007                         end
1008                         player = core.get_player_by_name(param)
1009                         core.chat_send_player(param, name.." cleared your inventory.")
1010                 else
1011                         player = core.get_player_by_name(name)
1012                 end
1013
1014                 if player then
1015                         player:get_inventory():set_list("main", {})
1016                         player:get_inventory():set_list("craft", {})
1017                         player:get_inventory():set_list("craftpreview", {})
1018                         core.log("action", name.." clears "..player:get_player_name().."'s inventory")
1019                         return true, "Cleared "..player:get_player_name().."'s inventory."
1020                 else
1021                         return false, "Player must be online to clear inventory!"
1022                 end
1023         end,
1024 })
1025
1026 local function handle_kill_command(killer, victim)
1027         if core.settings:get_bool("enable_damage") == false then
1028                 return false, "Players can't be killed, damage has been disabled."
1029         end
1030         local victimref = core.get_player_by_name(victim)
1031         if victimref == nil then
1032                 return false, string.format("Player %s is not online.", victim)
1033         elseif victimref:get_hp() <= 0 then
1034                 if killer == victim then
1035                         return false, "You are already dead."
1036                 else
1037                         return false, string.format("%s is already dead.", victim)
1038                 end
1039         end
1040         if not killer == victim then
1041                 core.log("action", string.format("%s killed %s", killer, victim))
1042         end
1043         -- Kill victim
1044         victimref:set_hp(0)
1045         return true, string.format("%s has been killed.", victim)
1046 end
1047
1048 core.register_chatcommand("kill", {
1049         params = "[<name>]",
1050         description = "Kill player or yourself",
1051         privs = {server=true},
1052         func = function(name, param)
1053                 return handle_kill_command(name, param == "" and name or param)
1054         end,
1055 })