]> git.lizzy.rs Git - dragonfireclient.git/blob - builtin/game/chat.lua
Add on_authplayer callback and 'last_login' to on_joinplayer (#9574)
[dragonfireclient.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                                 if teleportee:get_attach() then
447                                         return false, "Can't teleport, you're attached to an object!"
448                                 end
449                                 teleportee:set_pos(p)
450                                 return true, "Teleporting to "..core.pos_to_string(p)
451                         end
452                 end
453
454                 local target_name = param:match("^([^ ]+)$")
455                 local teleportee = core.get_player_by_name(name)
456
457                 p = nil
458                 if target_name then
459                         local target = core.get_player_by_name(target_name)
460                         if target then
461                                 p = target:get_pos()
462                         end
463                 end
464
465                 if teleportee and p then
466                         if teleportee:get_attach() then
467                                 return false, "Can't teleport, you're attached to an object!"
468                         end
469                         p = find_free_position_near(p)
470                         teleportee:set_pos(p)
471                         return true, "Teleporting to " .. target_name
472                                         .. " at "..core.pos_to_string(p)
473                 end
474
475                 if not core.check_player_privs(name, {bring=true}) then
476                         return false, "You don't have permission to teleport other players (missing bring privilege)"
477                 end
478
479                 teleportee = nil
480                 p = {}
481                 local teleportee_name
482                 teleportee_name, p.x, p.y, p.z = param:match(
483                                 "^([^ ]+) +([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
484                 p.x, p.y, p.z = tonumber(p.x), tonumber(p.y), tonumber(p.z)
485                 if teleportee_name then
486                         teleportee = core.get_player_by_name(teleportee_name)
487                 end
488                 if teleportee and p.x and p.y and p.z then
489                         if teleportee:get_attach() then
490                                 return false, "Can't teleport, player is attached to an object!"
491                         end
492                         teleportee:set_pos(p)
493                         return true, "Teleporting " .. teleportee_name
494                                         .. " to " .. core.pos_to_string(p)
495                 end
496
497                 teleportee = nil
498                 p = nil
499                 teleportee_name, target_name = string.match(param, "^([^ ]+) +([^ ]+)$")
500                 if teleportee_name then
501                         teleportee = core.get_player_by_name(teleportee_name)
502                 end
503                 if target_name then
504                         local target = core.get_player_by_name(target_name)
505                         if target then
506                                 p = target:get_pos()
507                         end
508                 end
509                 if teleportee and p then
510                         if teleportee:get_attach() then
511                                 return false, "Can't teleport, player is attached to an object!"
512                         end
513                         p = find_free_position_near(p)
514                         teleportee:set_pos(p)
515                         return true, "Teleporting " .. teleportee_name
516                                         .. " to " .. target_name
517                                         .. " at " .. core.pos_to_string(p)
518                 end
519
520                 return false, 'Invalid parameters ("' .. param
521                                 .. '") or player not found (see /help teleport)'
522         end,
523 })
524
525 core.register_chatcommand("set", {
526         params = "([-n] <name> <value>) | <name>",
527         description = "Set or read server configuration setting",
528         privs = {server=true},
529         func = function(name, param)
530                 local arg, setname, setvalue = string.match(param, "(-[n]) ([^ ]+) (.+)")
531                 if arg and arg == "-n" and setname and setvalue then
532                         core.settings:set(setname, setvalue)
533                         return true, setname .. " = " .. setvalue
534                 end
535
536                 setname, setvalue = string.match(param, "([^ ]+) (.+)")
537                 if setname and setvalue then
538                         if not core.settings:get(setname) then
539                                 return false, "Failed. Use '/set -n <name> <value>' to create a new setting."
540                         end
541                         core.settings:set(setname, setvalue)
542                         return true, setname .. " = " .. setvalue
543                 end
544
545                 setname = string.match(param, "([^ ]+)")
546                 if setname then
547                         setvalue = core.settings:get(setname)
548                         if not setvalue then
549                                 setvalue = "<not set>"
550                         end
551                         return true, setname .. " = " .. setvalue
552                 end
553
554                 return false, "Invalid parameters (see /help set)."
555         end,
556 })
557
558 local function emergeblocks_callback(pos, action, num_calls_remaining, ctx)
559         if ctx.total_blocks == 0 then
560                 ctx.total_blocks   = num_calls_remaining + 1
561                 ctx.current_blocks = 0
562         end
563         ctx.current_blocks = ctx.current_blocks + 1
564
565         if ctx.current_blocks == ctx.total_blocks then
566                 core.chat_send_player(ctx.requestor_name,
567                         string.format("Finished emerging %d blocks in %.2fms.",
568                         ctx.total_blocks, (os.clock() - ctx.start_time) * 1000))
569         end
570 end
571
572 local function emergeblocks_progress_update(ctx)
573         if ctx.current_blocks ~= ctx.total_blocks then
574                 core.chat_send_player(ctx.requestor_name,
575                         string.format("emergeblocks update: %d/%d blocks emerged (%.1f%%)",
576                         ctx.current_blocks, ctx.total_blocks,
577                         (ctx.current_blocks / ctx.total_blocks) * 100))
578
579                 core.after(2, emergeblocks_progress_update, ctx)
580         end
581 end
582
583 core.register_chatcommand("emergeblocks", {
584         params = "(here [<radius>]) | (<pos1> <pos2>)",
585         description = "Load (or, if nonexistent, generate) map blocks "
586                 .. "contained in area pos1 to pos2 (<pos1> and <pos2> must be in parentheses)",
587         privs = {server=true},
588         func = function(name, param)
589                 local p1, p2 = parse_range_str(name, param)
590                 if p1 == false then
591                         return false, p2
592                 end
593
594                 local context = {
595                         current_blocks = 0,
596                         total_blocks   = 0,
597                         start_time     = os.clock(),
598                         requestor_name = name
599                 }
600
601                 core.emerge_area(p1, p2, emergeblocks_callback, context)
602                 core.after(2, emergeblocks_progress_update, context)
603
604                 return true, "Started emerge of area ranging from " ..
605                         core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
606         end,
607 })
608
609 core.register_chatcommand("deleteblocks", {
610         params = "(here [<radius>]) | (<pos1> <pos2>)",
611         description = "Delete map blocks contained in area pos1 to pos2 "
612                 .. "(<pos1> and <pos2> must be in parentheses)",
613         privs = {server=true},
614         func = function(name, param)
615                 local p1, p2 = parse_range_str(name, param)
616                 if p1 == false then
617                         return false, p2
618                 end
619
620                 if core.delete_area(p1, p2) then
621                         return true, "Successfully cleared area ranging from " ..
622                                 core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
623                 else
624                         return false, "Failed to clear one or more blocks in area"
625                 end
626         end,
627 })
628
629 core.register_chatcommand("fixlight", {
630         params = "(here [<radius>]) | (<pos1> <pos2>)",
631         description = "Resets lighting in the area between pos1 and pos2 "
632                 .. "(<pos1> and <pos2> must be in parentheses)",
633         privs = {server = true},
634         func = function(name, param)
635                 local p1, p2 = parse_range_str(name, param)
636                 if p1 == false then
637                         return false, p2
638                 end
639
640                 if core.fix_light(p1, p2) then
641                         return true, "Successfully reset light in the area ranging from " ..
642                                 core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
643                 else
644                         return false, "Failed to load one or more blocks in area"
645                 end
646         end,
647 })
648
649 core.register_chatcommand("mods", {
650         params = "",
651         description = "List mods installed on the server",
652         privs = {},
653         func = function(name, param)
654                 return true, table.concat(core.get_modnames(), ", ")
655         end,
656 })
657
658 local function handle_give_command(cmd, giver, receiver, stackstring)
659         core.log("action", giver .. " invoked " .. cmd
660                         .. ', stackstring="' .. stackstring .. '"')
661         local itemstack = ItemStack(stackstring)
662         if itemstack:is_empty() then
663                 return false, "Cannot give an empty item"
664         elseif (not itemstack:is_known()) or (itemstack:get_name() == "unknown") then
665                 return false, "Cannot give an unknown item"
666         -- Forbid giving 'ignore' due to unwanted side effects
667         elseif itemstack:get_name() == "ignore" then
668                 return false, "Giving 'ignore' is not allowed"
669         end
670         local receiverref = core.get_player_by_name(receiver)
671         if receiverref == nil then
672                 return false, receiver .. " is not a known player"
673         end
674         local leftover = receiverref:get_inventory():add_item("main", itemstack)
675         local partiality
676         if leftover:is_empty() then
677                 partiality = ""
678         elseif leftover:get_count() == itemstack:get_count() then
679                 partiality = "could not be "
680         else
681                 partiality = "partially "
682         end
683         -- The actual item stack string may be different from what the "giver"
684         -- entered (e.g. big numbers are always interpreted as 2^16-1).
685         stackstring = itemstack:to_string()
686         if giver == receiver then
687                 local msg = "%q %sadded to inventory."
688                 return true, msg:format(stackstring, partiality)
689         else
690                 core.chat_send_player(receiver, ("%q %sadded to inventory.")
691                                 :format(stackstring, partiality))
692                 local msg = "%q %sadded to %s's inventory."
693                 return true, msg:format(stackstring, partiality, receiver)
694         end
695 end
696
697 core.register_chatcommand("give", {
698         params = "<name> <ItemString> [<count> [<wear>]]",
699         description = "Give item to player",
700         privs = {give=true},
701         func = function(name, param)
702                 local toname, itemstring = string.match(param, "^([^ ]+) +(.+)$")
703                 if not toname or not itemstring then
704                         return false, "Name and ItemString required"
705                 end
706                 return handle_give_command("/give", name, toname, itemstring)
707         end,
708 })
709
710 core.register_chatcommand("giveme", {
711         params = "<ItemString> [<count> [<wear>]]",
712         description = "Give item to yourself",
713         privs = {give=true},
714         func = function(name, param)
715                 local itemstring = string.match(param, "(.+)$")
716                 if not itemstring then
717                         return false, "ItemString required"
718                 end
719                 return handle_give_command("/giveme", name, name, itemstring)
720         end,
721 })
722
723 core.register_chatcommand("spawnentity", {
724         params = "<EntityName> [<X>,<Y>,<Z>]",
725         description = "Spawn entity at given (or your) position",
726         privs = {give=true, interact=true},
727         func = function(name, param)
728                 local entityname, p = string.match(param, "^([^ ]+) *(.*)$")
729                 if not entityname then
730                         return false, "EntityName required"
731                 end
732                 core.log("action", ("%s invokes /spawnentity, entityname=%q")
733                                 :format(name, entityname))
734                 local player = core.get_player_by_name(name)
735                 if player == nil then
736                         core.log("error", "Unable to spawn entity, player is nil")
737                         return false, "Unable to spawn entity, player is nil"
738                 end
739                 if not core.registered_entities[entityname] then
740                         return false, "Cannot spawn an unknown entity"
741                 end
742                 if p == "" then
743                         p = player:get_pos()
744                 else
745                         p = core.string_to_pos(p)
746                         if p == nil then
747                                 return false, "Invalid parameters ('" .. param .. "')"
748                         end
749                 end
750                 p.y = p.y + 1
751                 local obj = core.add_entity(p, entityname)
752                 local msg = obj and "%q spawned." or "%q failed to spawn."
753                 return true, msg:format(entityname)
754         end,
755 })
756
757 core.register_chatcommand("pulverize", {
758         params = "",
759         description = "Destroy item in hand",
760         func = function(name, param)
761                 local player = core.get_player_by_name(name)
762                 if not player then
763                         core.log("error", "Unable to pulverize, no player.")
764                         return false, "Unable to pulverize, no player."
765                 end
766                 local wielded_item = player:get_wielded_item()
767                 if wielded_item:is_empty() then
768                         return false, "Unable to pulverize, no item in hand."
769                 end
770                 core.log("action", name .. " pulverized \"" ..
771                         wielded_item:get_name() .. " " .. wielded_item:get_count() .. "\"")
772                 player:set_wielded_item(nil)
773                 return true, "An item was pulverized."
774         end,
775 })
776
777 -- Key = player name
778 core.rollback_punch_callbacks = {}
779
780 core.register_on_punchnode(function(pos, node, puncher)
781         local name = puncher and puncher:get_player_name()
782         if name and core.rollback_punch_callbacks[name] then
783                 core.rollback_punch_callbacks[name](pos, node, puncher)
784                 core.rollback_punch_callbacks[name] = nil
785         end
786 end)
787
788 core.register_chatcommand("rollback_check", {
789         params = "[<range>] [<seconds>] [<limit>]",
790         description = "Check who last touched a node or a node near it"
791                         .. " within the time specified by <seconds>. Default: range = 0,"
792                         .. " seconds = 86400 = 24h, limit = 5. Set <seconds> to inf for no time limit",
793         privs = {rollback=true},
794         func = function(name, param)
795                 if not core.settings:get_bool("enable_rollback_recording") then
796                         return false, "Rollback functions are disabled."
797                 end
798                 local range, seconds, limit =
799                         param:match("(%d+) *(%d*) *(%d*)")
800                 range = tonumber(range) or 0
801                 seconds = tonumber(seconds) or 86400
802                 limit = tonumber(limit) or 5
803                 if limit > 100 then
804                         return false, "That limit is too high!"
805                 end
806
807                 core.rollback_punch_callbacks[name] = function(pos, node, puncher)
808                         local name = puncher:get_player_name()
809                         core.chat_send_player(name, "Checking " .. core.pos_to_string(pos) .. "...")
810                         local actions = core.rollback_get_node_actions(pos, range, seconds, limit)
811                         if not actions then
812                                 core.chat_send_player(name, "Rollback functions are disabled")
813                                 return
814                         end
815                         local num_actions = #actions
816                         if num_actions == 0 then
817                                 core.chat_send_player(name, "Nobody has touched"
818                                                 .. " the specified location in "
819                                                 .. seconds .. " seconds")
820                                 return
821                         end
822                         local time = os.time()
823                         for i = num_actions, 1, -1 do
824                                 local action = actions[i]
825                                 core.chat_send_player(name,
826                                         ("%s %s %s -> %s %d seconds ago.")
827                                                 :format(
828                                                         core.pos_to_string(action.pos),
829                                                         action.actor,
830                                                         action.oldnode.name,
831                                                         action.newnode.name,
832                                                         time - action.time))
833                         end
834                 end
835
836                 return true, "Punch a node (range=" .. range .. ", seconds="
837                                 .. seconds .. "s, limit=" .. limit .. ")"
838         end,
839 })
840
841 core.register_chatcommand("rollback", {
842         params = "(<name> [<seconds>]) | (:<actor> [<seconds>])",
843         description = "Revert actions of a player. Default for <seconds> is 60. Set <seconds> to inf for no time limit",
844         privs = {rollback=true},
845         func = function(name, param)
846                 if not core.settings:get_bool("enable_rollback_recording") then
847                         return false, "Rollback functions are disabled."
848                 end
849                 local target_name, seconds = string.match(param, ":([^ ]+) *(%d*)")
850                 if not target_name then
851                         local player_name
852                         player_name, seconds = string.match(param, "([^ ]+) *(%d*)")
853                         if not player_name then
854                                 return false, "Invalid parameters. See /help rollback"
855                                                 .. " and /help rollback_check."
856                         end
857                         target_name = "player:"..player_name
858                 end
859                 seconds = tonumber(seconds) or 60
860                 core.chat_send_player(name, "Reverting actions of "
861                                 .. target_name .. " since "
862                                 .. seconds .. " seconds.")
863                 local success, log = core.rollback_revert_actions_by(
864                                 target_name, seconds)
865                 local response = ""
866                 if #log > 100 then
867                         response = "(log is too long to show)\n"
868                 else
869                         for _, line in pairs(log) do
870                                 response = response .. line .. "\n"
871                         end
872                 end
873                 response = response .. "Reverting actions "
874                                 .. (success and "succeeded." or "FAILED.")
875                 return success, response
876         end,
877 })
878
879 core.register_chatcommand("status", {
880         description = "Show server status",
881         func = function(name, param)
882                 local status = core.get_server_status(name, false)
883                 if status and status ~= "" then
884                         return true, status
885                 end
886                 return false, "This command was disabled by a mod or game"
887         end,
888 })
889
890 core.register_chatcommand("time", {
891         params = "[<0..23>:<0..59> | <0..24000>]",
892         description = "Show or set time of day",
893         privs = {},
894         func = function(name, param)
895                 if param == "" then
896                         local current_time = math.floor(core.get_timeofday() * 1440)
897                         local minutes = current_time % 60
898                         local hour = (current_time - minutes) / 60
899                         return true, ("Current time is %d:%02d"):format(hour, minutes)
900                 end
901                 local player_privs = core.get_player_privs(name)
902                 if not player_privs.settime then
903                         return false, "You don't have permission to run this command " ..
904                                 "(missing privilege: settime)."
905                 end
906                 local hour, minute = param:match("^(%d+):(%d+)$")
907                 if not hour then
908                         local new_time = tonumber(param)
909                         if not new_time then
910                                 return false, "Invalid time."
911                         end
912                         -- Backward compatibility.
913                         core.set_timeofday((new_time % 24000) / 24000)
914                         core.log("action", name .. " sets time to " .. new_time)
915                         return true, "Time of day changed."
916                 end
917                 hour = tonumber(hour)
918                 minute = tonumber(minute)
919                 if hour < 0 or hour > 23 then
920                         return false, "Invalid hour (must be between 0 and 23 inclusive)."
921                 elseif minute < 0 or minute > 59 then
922                         return false, "Invalid minute (must be between 0 and 59 inclusive)."
923                 end
924                 core.set_timeofday((hour * 60 + minute) / 1440)
925                 core.log("action", ("%s sets time to %d:%02d"):format(name, hour, minute))
926                 return true, "Time of day changed."
927         end,
928 })
929
930 core.register_chatcommand("days", {
931         description = "Show day count since world creation",
932         func = function(name, param)
933                 return true, "Current day is " .. core.get_day_count()
934         end
935 })
936
937 core.register_chatcommand("shutdown", {
938         params = "[<delay_in_seconds> | -1] [reconnect] [<message>]",
939         description = "Shutdown server (-1 cancels a delayed shutdown)",
940         privs = {server=true},
941         func = function(name, param)
942                 local delay, reconnect, message
943                 delay, param = param:match("^%s*(%S+)(.*)")
944                 if param then
945                         reconnect, param = param:match("^%s*(%S+)(.*)")
946                 end
947                 message = param and param:match("^%s*(.+)") or ""
948                 delay = tonumber(delay) or 0
949
950                 if delay == 0 then
951                         core.log("action", name .. " shuts down server")
952                         core.chat_send_all("*** Server shutting down (operator request).")
953                 end
954                 core.request_shutdown(message:trim(), core.is_yes(reconnect), delay)
955                 return true
956         end,
957 })
958
959 core.register_chatcommand("ban", {
960         params = "[<name>]",
961         description = "Ban the IP of a player or show the ban list",
962         privs = {ban=true},
963         func = function(name, param)
964                 if param == "" then
965                         local ban_list = core.get_ban_list()
966                         if ban_list == "" then
967                                 return true, "The ban list is empty."
968                         else
969                                 return true, "Ban list: " .. ban_list
970                         end
971                 end
972                 if not core.get_player_by_name(param) then
973                         return false, "Player is not online."
974                 end
975                 if not core.ban_player(param) then
976                         return false, "Failed to ban player."
977                 end
978                 local desc = core.get_ban_description(param)
979                 core.log("action", name .. " bans " .. desc .. ".")
980                 return true, "Banned " .. desc .. "."
981         end,
982 })
983
984 core.register_chatcommand("unban", {
985         params = "<name> | <IP_address>",
986         description = "Remove IP ban belonging to a player/IP",
987         privs = {ban=true},
988         func = function(name, param)
989                 if not core.unban_player_or_ip(param) then
990                         return false, "Failed to unban player/IP."
991                 end
992                 core.log("action", name .. " unbans " .. param)
993                 return true, "Unbanned " .. param
994         end,
995 })
996
997 core.register_chatcommand("kick", {
998         params = "<name> [<reason>]",
999         description = "Kick a player",
1000         privs = {kick=true},
1001         func = function(name, param)
1002                 local tokick, reason = param:match("([^ ]+) (.+)")
1003                 tokick = tokick or param
1004                 if not core.kick_player(tokick, reason) then
1005                         return false, "Failed to kick player " .. tokick
1006                 end
1007                 local log_reason = ""
1008                 if reason then
1009                         log_reason = " with reason \"" .. reason .. "\""
1010                 end
1011                 core.log("action", name .. " kicks " .. tokick .. log_reason)
1012                 return true, "Kicked " .. tokick
1013         end,
1014 })
1015
1016 core.register_chatcommand("clearobjects", {
1017         params = "[full | quick]",
1018         description = "Clear all objects in world",
1019         privs = {server=true},
1020         func = function(name, param)
1021                 local options = {}
1022                 if param == "" or param == "quick" then
1023                         options.mode = "quick"
1024                 elseif param == "full" then
1025                         options.mode = "full"
1026                 else
1027                         return false, "Invalid usage, see /help clearobjects."
1028                 end
1029
1030                 core.log("action", name .. " clears all objects ("
1031                                 .. options.mode .. " mode).")
1032                 core.chat_send_all("Clearing all objects. This may take a long time."
1033                                 .. " You may experience a timeout. (by "
1034                                 .. name .. ")")
1035                 core.clear_objects(options)
1036                 core.log("action", "Object clearing done.")
1037                 core.chat_send_all("*** Cleared all objects.")
1038                 return true
1039         end,
1040 })
1041
1042 core.register_chatcommand("msg", {
1043         params = "<name> <message>",
1044         description = "Send a direct message to a player",
1045         privs = {shout=true},
1046         func = function(name, param)
1047                 local sendto, message = param:match("^(%S+)%s(.+)$")
1048                 if not sendto then
1049                         return false, "Invalid usage, see /help msg."
1050                 end
1051                 if not core.get_player_by_name(sendto) then
1052                         return false, "The player " .. sendto
1053                                         .. " is not online."
1054                 end
1055                 core.log("action", "DM from " .. name .. " to " .. sendto
1056                                 .. ": " .. message)
1057                 core.chat_send_player(sendto, "DM from " .. name .. ": "
1058                                 .. message)
1059                 return true, "Message sent."
1060         end,
1061 })
1062
1063 core.register_chatcommand("last-login", {
1064         params = "[<name>]",
1065         description = "Get the last login time of a player or yourself",
1066         func = function(name, param)
1067                 if param == "" then
1068                         param = name
1069                 end
1070                 local pauth = core.get_auth_handler().get_auth(param)
1071                 if pauth and pauth.last_login and pauth.last_login ~= -1 then
1072                         -- Time in UTC, ISO 8601 format
1073                         return true, "Last login time was " ..
1074                                 os.date("!%Y-%m-%dT%H:%M:%SZ", pauth.last_login)
1075                 end
1076                 return false, "Last login time is unknown"
1077         end,
1078 })
1079
1080 core.register_chatcommand("clearinv", {
1081         params = "[<name>]",
1082         description = "Clear the inventory of yourself or another player",
1083         func = function(name, param)
1084                 local player
1085                 if param and param ~= "" and param ~= name then
1086                         if not core.check_player_privs(name, {server=true}) then
1087                                 return false, "You don't have permission"
1088                                                 .. " to clear another player's inventory (missing privilege: server)"
1089                         end
1090                         player = core.get_player_by_name(param)
1091                         core.chat_send_player(param, name.." cleared your inventory.")
1092                 else
1093                         player = core.get_player_by_name(name)
1094                 end
1095
1096                 if player then
1097                         player:get_inventory():set_list("main", {})
1098                         player:get_inventory():set_list("craft", {})
1099                         player:get_inventory():set_list("craftpreview", {})
1100                         core.log("action", name.." clears "..player:get_player_name().."'s inventory")
1101                         return true, "Cleared "..player:get_player_name().."'s inventory."
1102                 else
1103                         return false, "Player must be online to clear inventory!"
1104                 end
1105         end,
1106 })
1107
1108 local function handle_kill_command(killer, victim)
1109         if core.settings:get_bool("enable_damage") == false then
1110                 return false, "Players can't be killed, damage has been disabled."
1111         end
1112         local victimref = core.get_player_by_name(victim)
1113         if victimref == nil then
1114                 return false, string.format("Player %s is not online.", victim)
1115         elseif victimref:get_hp() <= 0 then
1116                 if killer == victim then
1117                         return false, "You are already dead."
1118                 else
1119                         return false, string.format("%s is already dead.", victim)
1120                 end
1121         end
1122         if not killer == victim then
1123                 core.log("action", string.format("%s killed %s", killer, victim))
1124         end
1125         -- Kill victim
1126         victimref:set_hp(0)
1127         return true, string.format("%s has been killed.", victim)
1128 end
1129
1130 core.register_chatcommand("kill", {
1131         params = "[<name>]",
1132         description = "Kill player or yourself",
1133         privs = {server=true},
1134         func = function(name, param)
1135                 return handle_kill_command(name, param == "" and name or param)
1136         end,
1137 })