init.lua 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596
  1. if not minetest.global_exists("chat_controls") then chat_controls = {} end
  2. chat_controls.modpath = minetest.get_modpath("chat_controls")
  3. chat_controls.players = chat_controls.players or {}
  4. -- Localize vector.distance() for performance.
  5. local vector_distance = vector.distance
  6. function chat_controls.player_ignored(pname, from)
  7. if not chat_controls.players[pname] then
  8. return
  9. end
  10. local tb = chat_controls.players[pname]
  11. local ignore = tb.ignore or {}
  12. for i = 1, #ignore do
  13. if ignore[i] == from then
  14. return true
  15. end
  16. end
  17. end
  18. function chat_controls.beep_enabled(pname)
  19. local tb = chat_controls.players[pname]
  20. if not tb then
  21. return
  22. end
  23. if tb.nobeep == "true" then
  24. return false
  25. end
  26. return true
  27. end
  28. function chat_controls.player_ignored_pm(pname, from)
  29. if not chat_controls.players[pname] then
  30. return
  31. end
  32. local tb = chat_controls.players[pname]
  33. local pm = tb.pm or {}
  34. for i = 1, #pm do
  35. if pm[i] == from then
  36. return true
  37. end
  38. end
  39. end
  40. function chat_controls.player_ignored_shout(pname, from)
  41. if not chat_controls.players[pname] then
  42. return
  43. end
  44. local tb = chat_controls.players[pname]
  45. local shout = tb.shout or {}
  46. for i = 1, #shout do
  47. if shout[i] == from then
  48. return true
  49. end
  50. end
  51. end
  52. function chat_controls.player_too_far(pname, from)
  53. if not chat_controls.players[pname] then
  54. return
  55. end
  56. local tb = chat_controls.players[pname]
  57. if tb.chathide ~= "true" then
  58. return
  59. end
  60. local dist = tb.distance
  61. local p1 = minetest.get_player_by_name(pname)
  62. local p2 = minetest.get_player_by_name(from)
  63. if not p1 or not p2 then
  64. return
  65. end
  66. local d1 = p1:get_pos()
  67. local d2 = p2:get_pos()
  68. if vector_distance(d1, d2) > dist then
  69. return true
  70. end
  71. end
  72. function chat_controls.non_citizen_ignored(pname, from)
  73. if not chat_controls.players[pname] then
  74. return
  75. end
  76. local tb = chat_controls.players[pname]
  77. if tb.nopochide ~= "true" then
  78. return
  79. end
  80. if not passport.player_registered(from) then
  81. return true
  82. end
  83. end
  84. function chat_controls.player_whitelisted(pname, from)
  85. if not chat_controls.players[pname] then
  86. return
  87. end
  88. local tb = chat_controls.players[pname]
  89. local white = tb.white
  90. for i = 1, #white do
  91. if white[i] == from then
  92. return true
  93. end
  94. end
  95. end
  96. function chat_controls.check_highlighting_filters(pname, from, message)
  97. if not chat_controls.players[pname] then
  98. return
  99. end
  100. local tb = chat_controls.players[pname]
  101. local filter = tb.filter
  102. local find = string.find
  103. -- Skip leading playername.
  104. local start = find(message, ">")
  105. if not start then start = 0 end
  106. start = start + 1
  107. local dfrom = rename.gpn(from)
  108. for i = 1, #filter do
  109. if filter[i] == from or filter[i] == dfrom then
  110. return true
  111. end
  112. if find(message, filter[i], start) then
  113. return true
  114. end
  115. end
  116. end
  117. -- Load lists from storage.
  118. function chat_controls.load_lists_for_player(pname)
  119. local ms = chat_controls.modstorage
  120. local ignore = ms:get_string(pname .. ":i")
  121. local filter = ms:get_string(pname .. ":f")
  122. local white = ms:get_string(pname .. ":w")
  123. local chathide = ms:get_string(pname .. ":h")
  124. local nopochide = ms:get_string(pname .. ":n")
  125. local nobeep = ms:get_string(pname .. ":b")
  126. local distance = ms:get_int(pname .. ":d")
  127. local pm = ms:get_string(pname .. ":p")
  128. local shout = ms:get_string(pname .. ":s")
  129. -- Could be uninitialized if user never selected an option.
  130. if chathide == "" then
  131. chathide = "false"
  132. end
  133. if nobeep == "" then
  134. nobeep = "false"
  135. end
  136. if nopochide == "" then
  137. nopochide = "false"
  138. end
  139. ignore = minetest.deserialize(ignore)
  140. filter = minetest.deserialize(filter)
  141. white = minetest.deserialize(white)
  142. pm = minetest.deserialize(pm)
  143. shout = minetest.deserialize(shout)
  144. -- Ensure player entry exists.
  145. local entry = chat_controls.players[pname]
  146. if not entry then
  147. chat_controls.players[pname] = {}
  148. entry = chat_controls.players[pname]
  149. end
  150. entry.chathide = chathide
  151. entry.nopochide = nopochide
  152. entry.distance = distance
  153. entry.nobeep = nobeep
  154. if type(ignore) == "table" then
  155. entry.ignore = ignore
  156. else
  157. entry.ignore = {}
  158. end
  159. if type(filter) == "table" then
  160. entry.filter = filter
  161. else
  162. entry.filter = {}
  163. end
  164. if type(white) == "table" then
  165. entry.white = white
  166. else
  167. entry.white = {}
  168. end
  169. if type(pm) == "table" then
  170. entry.pm = pm
  171. else
  172. entry.pm = {}
  173. end
  174. if type(shout) == "table" then
  175. entry.shout = shout
  176. else
  177. entry.shout = {}
  178. end
  179. end
  180. -- Save current lists to storage.
  181. function chat_controls.save_lists_for_player(pname)
  182. if not chat_controls.players[pname] then
  183. return
  184. end
  185. local tb = chat_controls.players[pname]
  186. local chathide = tb.chathide
  187. local nopochide = tb.nopochide
  188. local distance = tb.distance
  189. local nobeep = tb.nobeep
  190. -- Could be uninitialized if user never selected an option.
  191. if chathide == "" then
  192. chathide = "false"
  193. end
  194. if nobeep == "" then
  195. nobeep = "false"
  196. end
  197. if nopochide == "" then
  198. nopochide = "false"
  199. end
  200. -- Clamp to prevent data corruption.
  201. if distance < 0 then
  202. distance = 0
  203. end
  204. if distance > 30000 then
  205. distance = 30000
  206. end
  207. local ignore = minetest.serialize(tb.ignore or {}) or ""
  208. local filter = minetest.serialize(tb.filter or {}) or ""
  209. local white = minetest.serialize(tb.white or {}) or ""
  210. local pm = minetest.serialize(tb.pm or {}) or ""
  211. local shout = minetest.serialize(tb.shout or {}) or ""
  212. local ms = chat_controls.modstorage
  213. ms:set_string(pname .. ":i", ignore)
  214. ms:set_string(pname .. ":f", filter)
  215. ms:set_string(pname .. ":w", white)
  216. ms:set_string(pname .. ":h", chathide)
  217. ms:set_string(pname .. ":n", nopochide)
  218. ms:set_string(pname .. ":b", nobeep)
  219. ms:set_int(pname .. ":d", distance)
  220. ms:set_string(pname .. ":p", pm)
  221. ms:set_string(pname .. ":s", shout)
  222. end
  223. function chat_controls.on_joinplayer(pname, player)
  224. if passport.player_has_key(pname, player) then
  225. chat_controls.load_lists_for_player(pname)
  226. end
  227. end
  228. function chat_controls.on_leaveplayer(pname)
  229. chat_controls.players[pname] = nil
  230. end
  231. -- Update player lists from formspec fields.
  232. function chat_controls.set_lists_from_fields(pname, fields)
  233. local ignore = string.gsub(fields.ignore, "^,", "")
  234. local filter = string.gsub(fields.filter, "^,", "")
  235. local white = string.gsub(fields.white, "^,", "")
  236. local pm = string.gsub(fields.pm, "^,", "")
  237. local shout = string.gsub(fields.shout, "^,", "")
  238. local distance = fields.dist
  239. --minetest.chat_send_player("MustTest", shout)
  240. ignore = ignore:trim()
  241. filter = filter:trim()
  242. white = white:trim()
  243. pm = pm:trim()
  244. shout = shout:trim()
  245. distance = distance:trim()
  246. distance = tonumber(distance) or 0
  247. if distance < 0 then
  248. distance = 0
  249. end
  250. if distance > 30000 then
  251. distance = 30000
  252. end
  253. ignore = string.split(ignore, ',') or {}
  254. filter = string.split(filter, ',') or {}
  255. white = string.split(white, ',') or {}
  256. pm = string.split(pm, ',') or {}
  257. shout = string.split(shout, ',') or {}
  258. local new_ignore = {}
  259. local new_filter = {}
  260. local new_white = {}
  261. local new_pm = {}
  262. local new_shout = {}
  263. -- Adjust renames in the ignore list.
  264. for i = 1, #ignore do
  265. local entry = string.trim(ignore[i])
  266. if #entry > 0 then
  267. new_ignore[#new_ignore+1] = rename.grn(entry)
  268. end
  269. end
  270. for i = 1, #white do
  271. local entry = string.trim(white[i])
  272. if #entry > 0 then
  273. new_white[#new_white+1] = rename.grn(entry)
  274. end
  275. end
  276. -- Don't use the rename algorithm on the filter list, since this often contains stuff other than player names.
  277. for i = 1, #filter do
  278. local entry = string.trim(filter[i])
  279. if #entry > 0 then
  280. new_filter[#new_filter+1] = (entry)
  281. end
  282. end
  283. for i = 1, #pm do
  284. local entry = string.trim(pm[i])
  285. if #entry > 0 then
  286. new_pm[#new_pm+1] = rename.grn(entry)
  287. end
  288. end
  289. for i = 1, #shout do
  290. local entry = string.trim(shout[i])
  291. if #entry > 0 then
  292. new_shout[#new_shout+1] = rename.grn(entry)
  293. --minetest.chat_send_player("MustTest", new_shout[#new_shout])
  294. end
  295. end
  296. chat_controls.players[pname] = chat_controls.players[pname] or {}
  297. local tb = chat_controls.players[pname]
  298. tb.ignore = new_ignore
  299. tb.filter = new_filter
  300. tb.white = new_white
  301. tb.pm = new_pm
  302. tb.shout = new_shout
  303. tb.distance = distance
  304. minetest.chat_send_player(pname, "# Server: Filters set!")
  305. end
  306. -- Get player lists in a format suitable for inclusion in a formspec.
  307. function chat_controls.get_filters_as_text(pname)
  308. local ignore = ""
  309. local filter = ""
  310. local white = ""
  311. local distance = ""
  312. local pm = ""
  313. local shout = ""
  314. if chat_controls.players[pname] then
  315. local tb = table.copy(chat_controls.players[pname])
  316. ignore = tb.ignore or {}
  317. filter = tb.filter or {}
  318. white = tb.white or {}
  319. pm = tb.pm or {}
  320. shout = tb.shout or {}
  321. for i = 1, #ignore do
  322. ignore[i] = rename.gpn(ignore[i])
  323. end
  324. -- Filter list does not need renaming.
  325. for i = 1, #white do
  326. white[i] = rename.gpn(white[i])
  327. end
  328. for i = 1, #pm do
  329. pm[i] = rename.gpn(pm[i])
  330. end
  331. for i = 1, #shout do
  332. --minetest.chat_send_player("MustTest", shout[i])
  333. shout[i] = rename.gpn(shout[i])
  334. end
  335. ignore = table.concat(ignore, ",")
  336. filter = table.concat(filter, ",")
  337. white = table.concat(white, ",")
  338. pm = table.concat(pm, ",")
  339. shout = table.concat(shout, ",")
  340. --minetest.chat_send_player("MustTest", shout)
  341. distance = tostring(tb.distance)
  342. end
  343. return ignore, filter, white, distance, pm, shout
  344. end
  345. chat_controls.info = "* * * Documentation * * *\n" ..
  346. "\n" ..
  347. "Each of the three text fields uses the same format: separate names and words with commas. Whitespace does not matter.\n" ..
  348. "\n" ..
  349. "The ignore list is for players you don't want to hear from. If a player is listed here, you will not see their public chat or receive PMs from them. You might choose to ignore a player if they annoy you extremely, ruin your enjoyment of the server, or are just plain full of drama. There are two important points: an ignored player will still be able to get their messages through to you if they are standing very close -- the range is 64 meters. If you wish to prevent even this, you'll need to move away from them. An ignored player may also continue to send you mail using their Key of Citizenship.\n" ..
  350. "\n" ..
  351. "Also keep in mind that if an ignored player attempts to send you a PM, the server will inform them to the effect that you are not available for comment.\n" ..
  352. "\n" ..
  353. "The highlight list is for names or words that you want to see highlighed when players use them in chat. Note that you always see highlighted chat if your playername is used, so you don't need to include your name in this list. However, you will not see chat from an ignored player even if they include your name or a word you have put in this list.\n" ..
  354. "\n" ..
  355. "The whitelist entries are only needed if you choose to hide chat from players farther than a certain distance. In such a case, you will still see chat from whitelisted players no matter how far away they are. However, if that player is also ignored, the ignore list will take precedence.\n" ..
  356. "\n" ..
  357. "The <distance> field sets how many meters away you can see a player's chat from. You might choose to enable this filtering option if spawn is overrun by a horde of loud and annoying noobs. Alternatively, you could use this filter as a way to ignore all new players by default, unless they are close enough to you, while still receiving chat from everyone that you already know.\n" ..
  358. "\n" ..
  359. "Once you change your filter settings, you need to press 'Confirm Filters' in order to apply the settings. The settings will remain even after the server restarts.\n" ..
  360. "\n" ..
  361. "* * * End Docs * * *\n"
  362. function chat_controls.compose_formspec(pname)
  363. local ignore, filter, white, distance, pm, shout =
  364. chat_controls.get_filters_as_text(pname)
  365. --minetest.chat_send_player("MustTest", ignore)
  366. local chathide = "false"
  367. local nobeep = "false"
  368. local nopochide = "false"
  369. if chat_controls.players[pname] then
  370. chathide = chat_controls.players[pname].chathide or "false"
  371. nobeep = chat_controls.players[pname].nobeep or "false"
  372. nopochide = chat_controls.players[pname].nopochide or "false"
  373. end
  374. local formspec = ""
  375. formspec = formspec .. "size[12,9.5]" ..
  376. default.gui_bg ..
  377. default.gui_bg_img ..
  378. default.gui_slots ..
  379. "item_image[11,0;1,1;default:paper]" ..
  380. "label[0,0;Communication Filtering: Control who can send you messages!]" ..
  381. "field[0.3,1.2;8,1;ignore;" ..
  382. minetest.formspec_escape("Ignore list: names of players you do not want to see public chat from:") .. ";" ..
  383. minetest.formspec_escape(ignore) .. "]" ..
  384. "field[0.3,2.4;8,1;filter;" ..
  385. minetest.formspec_escape("Highlight list: words or names you want to know about, if mentioned:") .. ";" ..
  386. minetest.formspec_escape(filter) .. "]" ..
  387. "field[0.3,3.6;8,1;white;" ..
  388. minetest.formspec_escape("Whitelist: names of players you want to hear from:") .. ";" ..
  389. minetest.formspec_escape(white) .. "]" ..
  390. "field[0.3,5.5;2,1;dist;Distance;" ..
  391. minetest.formspec_escape(distance) .. "]" ..
  392. "field[0.3,6.8;8,1;pm;" ..
  393. minetest.formspec_escape("Ignore PM: names of players you don't want to PM you:") .. ";" ..
  394. minetest.formspec_escape(pm) .. "]" ..
  395. "field[0.3,8.0;8,1;shout;" ..
  396. minetest.formspec_escape("Ignore shout: names of players you don't want to hear shouts from:") .. ";" ..
  397. minetest.formspec_escape(shout) .. "]" ..
  398. "button[0,8.8;3,1;apply;Confirm Filters]" ..
  399. "button[6,8.8;2,1;close;Close]" ..
  400. "textarea[8.5,0.93;3.8,10.0;info;;" .. minetest.formspec_escape(chat_controls.info) .. "]" ..
  401. "tooltip[ignore;Separate names with commas. You may use aliases.]" ..
  402. "tooltip[filter;Separate strings with commas. You may use player-names and aliases.]" ..
  403. "tooltip[white;Separate names with commas. You may use aliases.]" ..
  404. "tooltip[dist;Min >= 0, max <= 30000.]" ..
  405. "checkbox[0,4.2;chathide;Hide chat from non-whitelisted users farther than DISTANCE meters.;" .. chathide .. "]" ..
  406. "checkbox[2,5.0;nopochide;Mute non-citizens (those not in whitelist).;" .. nopochide .. "]" ..
  407. "checkbox[3,8.8;nobeep;Disable audio alerts.;" .. nobeep .. "]"
  408. return formspec
  409. end
  410. -- API function (called from passport mod, for instance).
  411. function chat_controls.show_formspec(pname)
  412. local formspec = chat_controls.compose_formspec(pname)
  413. minetest.show_formspec(pname, "chat_controls:main", formspec)
  414. end
  415. function chat_controls.on_receive_fields(player, formname, fields)
  416. local pname = player:get_player_name()
  417. if formname ~= "chat_controls:main" then
  418. return
  419. end
  420. if fields.quit then
  421. return true
  422. end
  423. if fields.apply then
  424. chat_controls.set_lists_from_fields(pname, fields)
  425. chat_controls.save_lists_for_player(pname)
  426. chat_controls.show_formspec(pname)
  427. return true
  428. end
  429. if fields.chathide then
  430. if chat_controls.players[pname] then
  431. chat_controls.players[pname].chathide = fields.chathide
  432. end
  433. -- Clicking the checkbox so far, does the same thing as clicking apply.
  434. chat_controls.set_lists_from_fields(pname, fields)
  435. chat_controls.save_lists_for_player(pname)
  436. chat_controls.show_formspec(pname)
  437. return true
  438. end
  439. if fields.nopochide then
  440. if chat_controls.players[pname] then
  441. chat_controls.players[pname].nopochide = fields.nopochide
  442. end
  443. -- Clicking the checkbox so far, does the same thing as clicking apply.
  444. chat_controls.set_lists_from_fields(pname, fields)
  445. chat_controls.save_lists_for_player(pname)
  446. chat_controls.show_formspec(pname)
  447. return true
  448. end
  449. if fields.nobeep then
  450. if chat_controls.players[pname] then
  451. chat_controls.players[pname].nobeep = fields.nobeep
  452. end
  453. -- Clicking the checkbox so far, does the same thing as clicking apply.
  454. chat_controls.set_lists_from_fields(pname, fields)
  455. chat_controls.save_lists_for_player(pname)
  456. chat_controls.show_formspec(pname)
  457. return true
  458. end
  459. if fields.close then
  460. -- Go back to the KoC control panel.
  461. passport.show_formspec(pname)
  462. return true
  463. end
  464. return true
  465. end
  466. if not chat_controls.run_once then
  467. chat_controls.modstorage = minetest.get_mod_storage()
  468. -- GUI input handler.
  469. minetest.register_on_player_receive_fields(function(...)
  470. return chat_controls.on_receive_fields(...)
  471. end)
  472. minetest.register_on_joinplayer(function(player)
  473. return chat_controls.on_joinplayer(player:get_player_name(), player)
  474. end)
  475. minetest.register_on_leaveplayer(function(player)
  476. return chat_controls.on_leaveplayer(player:get_player_name())
  477. end)
  478. local c = "chat_controls:core"
  479. local f = chat_controls.modpath .. "/init.lua"
  480. reload.register_file(c, f, false)
  481. chat_controls.run_once = true
  482. end