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