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