init.lua 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  1. chat_controls = chat_controls or {}
  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.player_whitelisted(pname, from)
  73. if not chat_controls.players[pname] then
  74. return
  75. end
  76. local tb = chat_controls.players[pname]
  77. local white = tb.white
  78. for i = 1, #white do
  79. if white[i] == from then
  80. return true
  81. end
  82. end
  83. end
  84. function chat_controls.check_highlighting_filters(pname, from, message)
  85. if not chat_controls.players[pname] then
  86. return
  87. end
  88. local tb = chat_controls.players[pname]
  89. local filter = tb.filter
  90. local find = string.find
  91. -- Skip leading playername.
  92. local start = find(message, ">")
  93. if not start then start = 0 end
  94. start = start + 1
  95. local dfrom = rename.gpn(from)
  96. for i = 1, #filter do
  97. if filter[i] == from or filter[i] == dfrom then
  98. return true
  99. end
  100. if find(message, filter[i], start) then
  101. return true
  102. end
  103. end
  104. end
  105. -- Load lists from storage.
  106. function chat_controls.load_lists_for_player(pname)
  107. local ms = chat_controls.modstorage
  108. local ignore = ms:get_string(pname .. ":i")
  109. local filter = ms:get_string(pname .. ":f")
  110. local white = ms:get_string(pname .. ":w")
  111. local chathide = ms:get_string(pname .. ":h")
  112. local nobeep = ms:get_string(pname .. ":b")
  113. local distance = ms:get_int(pname .. ":d")
  114. local pm = ms:get_string(pname .. ":p")
  115. local shout = ms:get_string(pname .. ":s")
  116. -- Could be uninitialized if user never selected an option.
  117. if chathide == "" then
  118. chathide = "false"
  119. end
  120. if nobeep == "" then
  121. nobeep = "false"
  122. end
  123. ignore = minetest.deserialize(ignore)
  124. filter = minetest.deserialize(filter)
  125. white = minetest.deserialize(white)
  126. pm = minetest.deserialize(pm)
  127. shout = minetest.deserialize(shout)
  128. -- Ensure player entry exists.
  129. local entry = chat_controls.players[pname]
  130. if not entry then
  131. chat_controls.players[pname] = {}
  132. entry = chat_controls.players[pname]
  133. end
  134. entry.chathide = chathide
  135. entry.distance = distance
  136. entry.nobeep = nobeep
  137. if type(ignore) == "table" then
  138. entry.ignore = ignore
  139. else
  140. entry.ignore = {}
  141. end
  142. if type(filter) == "table" then
  143. entry.filter = filter
  144. else
  145. entry.filter = {}
  146. end
  147. if type(white) == "table" then
  148. entry.white = white
  149. else
  150. entry.white = {}
  151. end
  152. if type(pm) == "table" then
  153. entry.pm = pm
  154. else
  155. entry.pm = {}
  156. end
  157. if type(shout) == "table" then
  158. entry.shout = shout
  159. else
  160. entry.shout = {}
  161. end
  162. end
  163. -- Save current lists to storage.
  164. function chat_controls.save_lists_for_player(pname)
  165. if not chat_controls.players[pname] then
  166. return
  167. end
  168. local tb = chat_controls.players[pname]
  169. local chathide = tb.chathide
  170. local distance = tb.distance
  171. local nobeep = tb.nobeep
  172. -- Could be uninitialized if user never selected an option.
  173. if chathide == "" then
  174. chathide = "false"
  175. end
  176. if nobeep == "" then
  177. nobeep = "false"
  178. end
  179. -- Clamp to prevent data corruption.
  180. if distance < 0 then
  181. distance = 0
  182. end
  183. if distance > 30000 then
  184. distance = 30000
  185. end
  186. local ignore = minetest.serialize(tb.ignore or {}) or ""
  187. local filter = minetest.serialize(tb.filter or {}) or ""
  188. local white = minetest.serialize(tb.white or {}) or ""
  189. local pm = minetest.serialize(tb.pm or {}) or ""
  190. local shout = minetest.serialize(tb.shout or {}) or ""
  191. local ms = chat_controls.modstorage
  192. ms:set_string(pname .. ":i", ignore)
  193. ms:set_string(pname .. ":f", filter)
  194. ms:set_string(pname .. ":w", white)
  195. ms:set_string(pname .. ":h", chathide)
  196. ms:set_string(pname .. ":b", nobeep)
  197. ms:set_int(pname .. ":d", distance)
  198. ms:set_string(pname .. ":p", pm)
  199. ms:set_string(pname .. ":s", shout)
  200. end
  201. function chat_controls.on_joinplayer(pname, player)
  202. if passport.player_has_key(pname, player) then
  203. chat_controls.load_lists_for_player(pname)
  204. end
  205. end
  206. function chat_controls.on_leaveplayer(pname)
  207. chat_controls.players[pname] = nil
  208. end
  209. -- Update player lists from formspec fields.
  210. function chat_controls.set_lists_from_fields(pname, fields)
  211. local ignore = string.gsub(fields.ignore, "^,", "")
  212. local filter = string.gsub(fields.filter, "^,", "")
  213. local white = string.gsub(fields.white, "^,", "")
  214. local pm = string.gsub(fields.pm, "^,", "")
  215. local shout = string.gsub(fields.shout, "^,", "")
  216. local distance = fields.dist
  217. --minetest.chat_send_player("MustTest", shout)
  218. ignore = ignore:trim()
  219. filter = filter:trim()
  220. white = white:trim()
  221. pm = pm:trim()
  222. shout = shout:trim()
  223. distance = distance:trim()
  224. distance = tonumber(distance) or 0
  225. if distance < 0 then
  226. distance = 0
  227. end
  228. if distance > 30000 then
  229. distance = 30000
  230. end
  231. ignore = string.split(ignore, ',') or {}
  232. filter = string.split(filter, ',') or {}
  233. white = string.split(white, ',') or {}
  234. pm = string.split(pm, ',') or {}
  235. shout = string.split(shout, ',') or {}
  236. local new_ignore = {}
  237. local new_filter = {}
  238. local new_white = {}
  239. local new_pm = {}
  240. local new_shout = {}
  241. -- Adjust renames in the ignore list.
  242. for i = 1, #ignore do
  243. local entry = string.trim(ignore[i])
  244. if #entry > 0 then
  245. new_ignore[#new_ignore+1] = rename.grn(entry)
  246. end
  247. end
  248. for i = 1, #white do
  249. local entry = string.trim(white[i])
  250. if #entry > 0 then
  251. new_white[#new_white+1] = rename.grn(entry)
  252. end
  253. end
  254. -- Don't use the rename algorithm on the filter list, since this often contains stuff other than player names.
  255. for i = 1, #filter do
  256. local entry = string.trim(filter[i])
  257. if #entry > 0 then
  258. new_filter[#new_filter+1] = (entry)
  259. end
  260. end
  261. for i = 1, #pm do
  262. local entry = string.trim(pm[i])
  263. if #entry > 0 then
  264. new_pm[#new_pm+1] = rename.grn(entry)
  265. end
  266. end
  267. for i = 1, #shout do
  268. local entry = string.trim(shout[i])
  269. if #entry > 0 then
  270. new_shout[#new_shout+1] = rename.grn(entry)
  271. --minetest.chat_send_player("MustTest", new_shout[#new_shout])
  272. end
  273. end
  274. chat_controls.players[pname] = chat_controls.players[pname] or {}
  275. local tb = chat_controls.players[pname]
  276. tb.ignore = new_ignore
  277. tb.filter = new_filter
  278. tb.white = new_white
  279. tb.pm = new_pm
  280. tb.shout = new_shout
  281. tb.distance = distance
  282. minetest.chat_send_player(pname, "# Server: Filters set!")
  283. end
  284. -- Get player lists in a format suitable for inclusion in a formspec.
  285. function chat_controls.get_filters_as_text(pname)
  286. local ignore = ""
  287. local filter = ""
  288. local white = ""
  289. local distance = ""
  290. local pm = ""
  291. local shout = ""
  292. if chat_controls.players[pname] then
  293. local tb = table.copy(chat_controls.players[pname])
  294. ignore = tb.ignore or {}
  295. filter = tb.filter or {}
  296. white = tb.white or {}
  297. pm = tb.pm or {}
  298. shout = tb.shout or {}
  299. for i = 1, #ignore do
  300. ignore[i] = rename.gpn(ignore[i])
  301. end
  302. -- Filter list does not need renaming.
  303. for i = 1, #white do
  304. white[i] = rename.gpn(white[i])
  305. end
  306. for i = 1, #pm do
  307. pm[i] = rename.gpn(pm[i])
  308. end
  309. for i = 1, #shout do
  310. --minetest.chat_send_player("MustTest", shout[i])
  311. shout[i] = rename.gpn(shout[i])
  312. end
  313. ignore = table.concat(ignore, ",")
  314. filter = table.concat(filter, ",")
  315. white = table.concat(white, ",")
  316. pm = table.concat(pm, ",")
  317. shout = table.concat(shout, ",")
  318. --minetest.chat_send_player("MustTest", shout)
  319. distance = tostring(tb.distance)
  320. end
  321. return ignore, filter, white, distance, pm, shout
  322. end
  323. chat_controls.info = "* * * Documentation * * *\n" ..
  324. "\n" ..
  325. "Each of the three text fields uses the same format: separate names and words with commas. Whitespace does not matter.\n" ..
  326. "\n" ..
  327. "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" ..
  328. "\n" ..
  329. "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" ..
  330. "\n" ..
  331. "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" ..
  332. "\n" ..
  333. "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" ..
  334. "\n" ..
  335. "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" ..
  336. "\n" ..
  337. "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" ..
  338. "\n" ..
  339. "* * * End Docs * * *\n"
  340. function chat_controls.compose_formspec(pname)
  341. local ignore, filter, white, distance, pm, shout =
  342. chat_controls.get_filters_as_text(pname)
  343. --minetest.chat_send_player("MustTest", ignore)
  344. local chathide = "false"
  345. local nobeep = "false"
  346. if chat_controls.players[pname] then
  347. chathide = chat_controls.players[pname].chathide
  348. nobeep = chat_controls.players[pname].nobeep
  349. end
  350. local formspec = ""
  351. formspec = formspec .. "size[12,9.5]" ..
  352. default.gui_bg ..
  353. default.gui_bg_img ..
  354. default.gui_slots ..
  355. "item_image[11,0;1,1;default:paper]" ..
  356. "label[0,0;Communication Filtering: Control who can send you messages!]" ..
  357. "field[0.3,1.2;8,1;ignore;" ..
  358. minetest.formspec_escape("Ignore list: names of players you do not want to see public chat from:") .. ";" ..
  359. minetest.formspec_escape(ignore) .. "]" ..
  360. "field[0.3,2.4;8,1;filter;" ..
  361. minetest.formspec_escape("Highlight list: words or names you want to know about, if mentioned:") .. ";" ..
  362. minetest.formspec_escape(filter) .. "]" ..
  363. "field[0.3,3.6;8,1;white;" ..
  364. minetest.formspec_escape("Whitelist: names of players you want to hear from:") .. ";" ..
  365. minetest.formspec_escape(white) .. "]" ..
  366. "field[0.3,5.5;2,1;dist;Distance;" ..
  367. minetest.formspec_escape(distance) .. "]" ..
  368. "field[0.3,6.8;8,1;pm;" ..
  369. minetest.formspec_escape("Ignore PM: names of players you don't want to PM you:") .. ";" ..
  370. minetest.formspec_escape(pm) .. "]" ..
  371. "field[0.3,8.0;8,1;shout;" ..
  372. minetest.formspec_escape("Ignore shout: names of players you don't want to hear shouts from:") .. ";" ..
  373. minetest.formspec_escape(shout) .. "]" ..
  374. "button[0,8.8;3,1;apply;Confirm Filters]" ..
  375. "button[6,8.8;2,1;close;Close]" ..
  376. "textarea[8.5,0.93;3.8,10.0;info;;" .. minetest.formspec_escape(chat_controls.info) .. "]" ..
  377. "tooltip[ignore;Separate names with commas. You may use aliases.]" ..
  378. "tooltip[filter;Separate strings with commas. You may use player-names and aliases.]" ..
  379. "tooltip[white;Separate names with commas. You may use aliases.]" ..
  380. "tooltip[dist;Min >= 0, max <= 30000.]" ..
  381. "checkbox[0,4.2;chathide;Hide chat from non-whitelisted users farther than DISTANCE meters.;" .. chathide .. "]" ..
  382. "checkbox[3,8.8;nobeep;Disable audio alerts.;" .. nobeep .. "]"
  383. return formspec
  384. end
  385. -- API function (called from passport mod, for instance).
  386. function chat_controls.show_formspec(pname)
  387. local formspec = chat_controls.compose_formspec(pname)
  388. minetest.show_formspec(pname, "chat_controls:main", formspec)
  389. end
  390. function chat_controls.on_receive_fields(player, formname, fields)
  391. local pname = player:get_player_name()
  392. if formname ~= "chat_controls:main" then
  393. return
  394. end
  395. if fields.quit then
  396. return true
  397. end
  398. if fields.apply then
  399. chat_controls.set_lists_from_fields(pname, fields)
  400. chat_controls.save_lists_for_player(pname)
  401. chat_controls.show_formspec(pname)
  402. return true
  403. end
  404. if fields.chathide then
  405. if chat_controls.players[pname] then
  406. chat_controls.players[pname].chathide = fields.chathide
  407. end
  408. -- Clicking the checkbox so far, does the same thing as clicking apply.
  409. chat_controls.set_lists_from_fields(pname, fields)
  410. chat_controls.save_lists_for_player(pname)
  411. chat_controls.show_formspec(pname)
  412. return true
  413. end
  414. if fields.nobeep then
  415. if chat_controls.players[pname] then
  416. chat_controls.players[pname].nobeep = fields.nobeep
  417. end
  418. -- Clicking the checkbox so far, does the same thing as clicking apply.
  419. chat_controls.set_lists_from_fields(pname, fields)
  420. chat_controls.save_lists_for_player(pname)
  421. chat_controls.show_formspec(pname)
  422. return true
  423. end
  424. if fields.close then
  425. -- Go back to the KoC control panel.
  426. passport.show_formspec(pname)
  427. return true
  428. end
  429. return true
  430. end
  431. if not chat_controls.run_once then
  432. chat_controls.modstorage = minetest.get_mod_storage()
  433. -- GUI input handler.
  434. minetest.register_on_player_receive_fields(function(...)
  435. return chat_controls.on_receive_fields(...)
  436. end)
  437. minetest.register_on_joinplayer(function(player)
  438. return chat_controls.on_joinplayer(player:get_player_name(), player)
  439. end)
  440. minetest.register_on_leaveplayer(function(player)
  441. return chat_controls.on_leaveplayer(player:get_player_name())
  442. end)
  443. local c = "chat_controls:core"
  444. local f = chat_controls.modpath .. "/init.lua"
  445. reload.register_file(c, f, false)
  446. chat_controls.run_once = true
  447. end