init.lua 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. --------------------------------------------------------------------------------
  2. -- Core Chat System for Must Test Survival
  3. -- Author: GoldFireUn
  4. -- License: MIT
  5. --------------------------------------------------------------------------------
  6. chat_core = chat_core or {}
  7. chat_core.modpath = minetest.get_modpath("chat_core")
  8. chat_core.players = chat_core.players or {}
  9. function chat_core.on_joinplayer(player)
  10. local pname = player:get_player_name()
  11. chat_core.players[pname] = {
  12. last_pm_from = "",
  13. }
  14. end
  15. function chat_core.on_leaveplayer(player, timeout)
  16. local pname = player:get_player_name()
  17. chat_core.players[pname] = nil
  18. end
  19. if minetest.get_modpath("reload") then
  20. local c = "chat_core:core"
  21. local f = chat_core.modpath .. "/init.lua"
  22. if not reload.file_registered(c) then
  23. reload.register_file(c, f, false)
  24. end
  25. end
  26. local color_green = core.get_color_escape_sequence("#00ff00")
  27. local color_dark_green = core.get_color_escape_sequence("#00d000")
  28. local color_dark_cyan = core.get_color_escape_sequence("#88ffff")
  29. local color_nametag = core.get_color_escape_sequence("#ffd870")
  30. local color_white = core.get_color_escape_sequence("#ffffff")
  31. --local color_cyan = core.get_color_escape_sequence("#00e0ff")
  32. chat_core.nametag_color = color_nametag -- make public to other mods
  33. -- Used in PMs.
  34. local color_magenta = core.get_color_escape_sequence("#ff50ff")
  35. local color_dark_magenta = core.get_color_escape_sequence("#c800c8")
  36. chat_core.rewrite_message = function(chat2)
  37. -- Prevent players from including zero-bytes or control characters in their chat.
  38. local sub = string.gsub
  39. local chat = chat2
  40. chat = sub(chat, "[%z%c]", "") -- Zero byte & control bytes.
  41. chat = sub(chat, " +", " ") -- Excess spaces.
  42. --chat = sub(chat, "[qQ]", "k")
  43. return chat
  44. end
  45. -- Send regular chat from a player to all other players.
  46. -- This is called by this mod after validation checks pass.
  47. chat_core.send_all = function(from, prename, actname, postname, message, alwaysecho)
  48. -- `alwaysecho` is true in the case of a /me command.
  49. -- The client never echoes this command by itself.
  50. local player = minetest.get_player_by_name(from)
  51. if not player then
  52. return
  53. end
  54. local ppos = player:get_pos()
  55. local allplayers = minetest.get_connected_players()
  56. local mlower = string.lower(message)
  57. for k, v in ipairs(allplayers) do
  58. local pname = v:get_player_name()
  59. local plower = string.lower(rename.gpn(pname))
  60. local plowero = string.lower(pname)
  61. local tpos = v:get_pos()
  62. if pname ~= from then
  63. local chosen_color = ""
  64. local should_send = true
  65. local should_beep = false
  66. local ignored = false
  67. -- Execute chat filters. Order is relevant!
  68. -- Hide chat from players who are too far away (if feature is enabled).
  69. if chat_controls.player_too_far(pname, from) then
  70. should_send = false
  71. end
  72. -- Whitelisted player chat can be see even if far away.
  73. if chat_controls.player_whitelisted(pname, from) then
  74. should_send = true
  75. end
  76. -- Ignore list takes precedence over whitelist.
  77. if chat_controls.player_ignored(pname, from) then
  78. should_send = false
  79. ignored = true
  80. end
  81. -- Chat from nearby players is highlighted.
  82. -- Even ignored players may talk if they are close enough.
  83. if vector.distance(ppos, tpos) < 64 then
  84. -- Highlight chat from nearby player only if originating player is not invisible.
  85. if not gdac_invis.is_invisible(from) then
  86. chosen_color = color_dark_cyan
  87. end
  88. should_send = true
  89. end
  90. -- Finally, check highlighting filters. But only if not ignored!
  91. if not ignored then
  92. if chat_controls.check_highlighting_filters(pname, from, message) then
  93. chosen_color = color_dark_green
  94. should_send = true
  95. end
  96. end
  97. if should_send then
  98. -- Colorize message for players if their name or alt is used.
  99. -- This overrides any previous coloring.
  100. if string.find(mlower, plower) or string.find(mlower, plowero) then
  101. chosen_color = color_green
  102. should_beep = true
  103. end
  104. -- If /me, use correct color.
  105. if alwaysecho then
  106. chosen_color = chat_colorize.COLOR_ORANGE
  107. end
  108. -- Finally send the message.
  109. if should_beep then
  110. chat_core.alert_player_sound(pname)
  111. end
  112. minetest.chat_send_player(pname, prename .. color_nametag .. actname .. color_white .. postname .. chosen_color .. message)
  113. end
  114. else -- Message being echoed back to player that sent it.
  115. if alwaysecho then
  116. -- It should be a /me command.
  117. minetest.chat_send_player(pname, prename .. color_nametag .. actname .. color_white .. postname .. chat_colorize.COLOR_ORANGE .. message)
  118. else
  119. -- Send chat to self if echo enabled.
  120. chat_echo.echo_chat(pname, prename .. color_nametag .. actname .. color_white .. postname .. message)
  121. end
  122. end
  123. end
  124. end
  125. -- Check player's language, and kick them if they are not protected by the PoC/KoC.
  126. -- Or, mute them and send a message to other players that they were muted.
  127. -- This function can be called from other mods.
  128. function chat_core.check_language(name, message)
  129. -- Players with anticurse bypass priv cannot be kicked by this mod.
  130. local nokick = minetest.check_player_privs(name, {anticurse_bypass=true})
  131. if nokick then
  132. if anticurse.check(name, message, "foul") then
  133. anticurse.log(name, message)
  134. elseif anticurse.check(name, message, "curse") then
  135. anticurse.log(name, message)
  136. end
  137. return false -- Has bypass.
  138. end
  139. if anticurse.check(name, message, "foul") then
  140. anticurse.log(name, message)
  141. -- Players who have registered (and therefore have probably played
  142. -- on the server more than a few days) are warned but not kicked.
  143. if passport.player_registered(name) then
  144. local ext = anticurse.get_kick_message("foul")
  145. minetest.chat_send_all("# Server: Talk from someone hidden in case of uninteresting language.")
  146. minetest.chat_send_player(name, "# Server: " .. ext)
  147. else
  148. anticurse.kick(name, "foul")
  149. end
  150. return true -- Blocked.
  151. elseif anticurse.check(name, message, "curse") then
  152. anticurse.log(name, message)
  153. -- Players who have registered (and therefore have probably played
  154. -- on the server more than a few days) are warned but not kicked.
  155. if passport.player_registered(name) then
  156. local ext = anticurse.get_kick_message("curse")
  157. minetest.chat_send_all("# Server: Talk from someone hidden in case of uninteresting language.")
  158. minetest.chat_send_player(name, "# Server: " .. ext)
  159. else
  160. anticurse.kick(name, "foul")
  161. end
  162. return true -- Blocked.
  163. end
  164. return false -- Nothing found.
  165. end
  166. local generate_coord_string = function(name)
  167. local coord_string = ""
  168. if command_tokens.mark.player_marked(name) then
  169. local entity = minetest.get_player_by_name(name)
  170. local pos = entity:get_pos()
  171. local pstr = rc.pos_to_string(vector.round(pos))
  172. pstr = string.gsub(pstr, "[%(%)]", "")
  173. -- remember to include leading space!
  174. coord_string = " [" .. rc.realm_description_at_pos(pos) .. ": " .. pstr .. "]"
  175. minetest.chat_send_player(name, "# Server: You are marked (" .. pstr .. ")!")
  176. end
  177. return coord_string
  178. end
  179. chat_core.generate_coord_string = generate_coord_string
  180. chat_core.on_chat_message = function(name, message)
  181. -- Trim input.
  182. message = string.trim(message)
  183. if message:sub(1, 1) == "/" then
  184. minetest.chat_send_player(name, "# Server: Invalid command. See '/help all' for a list of valid commands.")
  185. easyvend.sound_error(name)
  186. -- It's a special command, and not one that was registered.
  187. -- This is actually never called?
  188. return
  189. end
  190. if not minetest.check_player_privs(name, {shout=true}) then
  191. minetest.chat_send_player(name, "# Server: You do not have 'shout' priv.")
  192. -- Player doesn't have shout priv.
  193. return
  194. end
  195. local player_muted = false
  196. if command_tokens.mute.player_muted(name) then
  197. minetest.chat_send_player(name, "# Server: You are currently gagged.")
  198. -- Player is muted.
  199. return
  200. end
  201. -- Shouts can be executed by prepending a '!' to your chat.
  202. if string.find(message, "^!") then
  203. -- Handled by the shout mod.
  204. shout.shout(name, string.sub(message, 2))
  205. return
  206. end
  207. -- Check for accidents.
  208. local pm_pos = string.find(message, "msg")
  209. if not pm_pos then
  210. pm_pos = string.find(message, "MSG")
  211. end
  212. if not pm_pos then
  213. pm_pos = string.find(message, "pm")
  214. end
  215. if not pm_pos then
  216. pm_pos = string.find(message, "PM")
  217. end
  218. if pm_pos and pm_pos <= 3 then -- Space for 2 symbols.
  219. minetest.chat_send_player(name,
  220. "# Server: Did you mean to send a PM? The command format is \"/msg <player> <message>\" (without quotes). Chat not sent.")
  221. return
  222. end
  223. if chat_core.check_language(name, message) then return end
  224. local coord_string = generate_coord_string(name)
  225. player_labels.on_chat_message(name, message)
  226. chat_core.send_all(name, "<", rename.gpn(name), coord_string .. "> ", message)
  227. chat_logging.log_public_chat(name, message, coord_string)
  228. afk_removal.reset_timeout(name)
  229. end
  230. chat_core.handle_command_me = function(name, param)
  231. if not minetest.check_player_privs(name, {shout=true}) then
  232. minetest.chat_send_player(name, "# Server: You do not have 'shout' priv.")
  233. return -- Player doesn't have shout priv.
  234. end
  235. if command_tokens.mute.player_muted(name) then
  236. minetest.chat_send_player(name, "# Server: You can't do that while gagged, sorry.")
  237. return -- Player is muted.
  238. end
  239. param = string.trim(param)
  240. if #param < 1 then
  241. minetest.chat_send_player(name, "# Server: No action specified.")
  242. return
  243. end
  244. if chat_core.check_language(name, param) then return end
  245. local coord_string = generate_coord_string(name)
  246. player_labels.on_chat_message(name, param)
  247. chat_core.send_all(name, "* <", rename.gpn(name), coord_string .. "> ", param, true)
  248. chat_logging.log_public_action(name, param, coord_string)
  249. afk_removal.reset_timeout(name)
  250. end
  251. chat_core.handle_command_msg = function(name, param)
  252. if not minetest.check_player_privs(name, {shout=true}) then
  253. minetest.chat_send_player(name, "# Server: You do not have 'shout' priv.")
  254. easyvend.sound_error(name)
  255. return -- Player doesn't have shout priv.
  256. end
  257. if command_tokens.mute.player_muted(name) then
  258. minetest.chat_send_player(name, "# Server: You are gagged at the moment.")
  259. easyvend.sound_error(name)
  260. return -- Player is muted.
  261. end
  262. local coord_string = generate_coord_string(name)
  263. -- Split command arguments.
  264. local p = string.find(param, " ")
  265. local to, newmsg
  266. if p then
  267. newmsg = string.trim(param:sub(p+1))
  268. to = string.trim(param:sub(1, p-1))
  269. end
  270. if type(to)=="string" and type(newmsg)=="string" and string.len(newmsg) > 0 and string.len(to) > 0 then
  271. to = rename.grn(to)
  272. if gdac_invis.is_invisible(to) and to ~= name then -- If target is invisible, and player sending is not same as target ...
  273. if chat_core.players[name] and (chat_core.players[name].last_pm_from or "") ~= to then -- Do not permit, if player did not receive a PM from this target.
  274. if to == "MustTest" then
  275. minetest.chat_send_player(name, "# Server: The server admin is not available at this time! If it's important, send mail instead.")
  276. else
  277. minetest.chat_send_player(name, "# Server: <" .. rename.gpn(to) .. "> is not available at this time! If it's important, send mail instead.")
  278. end
  279. return
  280. end
  281. end
  282. if minetest.get_player_by_name(to) then
  283. -- Bad words in PMs.
  284. if chat_core.check_language(name, newmsg) then
  285. return
  286. end
  287. -- Cannot PM player if being ignored.
  288. if chat_controls.player_ignored_pm(to, name) and to ~= name then
  289. minetest.chat_send_player(name, "# Server: <" .. rename.gpn(to) .. "> is not available for private messaging!")
  290. easyvend.sound_error(name)
  291. return
  292. end
  293. minetest.after(0, function()
  294. chat_core.alert_player_sound(to)
  295. minetest.chat_send_player(to, color_magenta .. "# PM: FROM <" .. rename.gpn(name) .. coord_string .. ">: " .. newmsg)
  296. -- Record name of last player to send this player a PM.
  297. if chat_core.players[to] then
  298. chat_core.players[to].last_pm_from = name
  299. end
  300. end)
  301. minetest.chat_send_player(name, color_dark_magenta .. "# PM: TO <" .. rename.gpn(to) .. coord_string .. ">: " .. newmsg)
  302. chat_logging.log_private_message(name, to, newmsg)
  303. afk_removal.reset_timeout(name)
  304. else minetest.chat_send_player(name, "# Server: <" .. rename.gpn(to) .. "> is not online.") end
  305. else minetest.chat_send_player(name, "# Server: Usage: '/msg <playername> <message>'.") end
  306. end
  307. function chat_core.handle_command_r(name, param)
  308. local to = chat_core.players[name].last_pm_from or ""
  309. if to == "" then
  310. minetest.chat_send_player(name, "# Server: No one has sent you a PM yet that can be replied to. Use /msg instead to specify the player.")
  311. return
  312. end
  313. return chat_core.handle_command_msg(name, to .. " " .. param) -- Prepend target name, and call normal /msg function.
  314. end
  315. function chat_core.alert_player_sound(to)
  316. if chat_controls.beep_enabled(to) then
  317. if afk_removal.is_afk(to) then
  318. minetest.sound_play("chat_alert", {to_player = to, gain = 1})
  319. else
  320. if afk_removal.seconds_since_action(to) > 60*2 then
  321. minetest.sound_play("chat_alert", {to_player = to, gain = 1})
  322. else
  323. minetest.sound_play("chat_alert", {to_player = to, gain = 0.4})
  324. end
  325. end
  326. local pref = minetest.get_player_by_name(to)
  327. if pref then
  328. local pos = pref:get_pos()
  329. ambiance.sound_play("chat_alert", pos, 0.5, 20, to)
  330. end
  331. end
  332. end
  333. if not chat_core.registered then
  334. minetest.register_chatcommand("me", {
  335. params = "<action>",
  336. description = "Send an 'action' message beginning with your name.",
  337. privs = {shout=true},
  338. func = function(name, param)
  339. chat_core.handle_command_me(name, chat_core.rewrite_message(param))
  340. return true
  341. end,
  342. })
  343. minetest.register_chatcommand("msg", {
  344. params = "<player> <message>",
  345. description = "Send a private message to another player.",
  346. privs = {shout=true},
  347. func = function(name, param)
  348. chat_core.handle_command_msg(name, chat_core.rewrite_message(param))
  349. return true
  350. end,
  351. })
  352. minetest.register_chatcommand("r", {
  353. params = "<message>",
  354. description = "Reply via PM to the last player to send you a PM.",
  355. privs = {shout=true},
  356. func = function(name, param)
  357. chat_core.handle_command_r(name, chat_core.rewrite_message(param))
  358. return true
  359. end,
  360. })
  361. -- This should be the only handler registered. Only one handler can be registered.
  362. minetest.register_on_chat_message(function(name, message)
  363. chat_core.on_chat_message(name, chat_core.rewrite_message(message))
  364. return true -- Don't send message automatically, we already did this.
  365. end)
  366. minetest.register_on_joinplayer(function(...) return chat_core.on_joinplayer(...) end)
  367. minetest.register_on_leaveplayer(function(...) return chat_core.on_leaveplayer(...) end)
  368. chat_core.registered = true
  369. end