chatcommands.lua 32 KB

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