chat.lua 40 KB

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