teamchat init
[df_teamchat.git] / init.lua
blob55e550267d6dc66eb4913427d859e1a63109d495
1 ---
2 -- coras teamchat .. indev v0.5
3 --
4 -- adds a team chat for you and a couple friends, also prevents accidental sending of coordinates
5 -- to say something in teamchat either activate teammode in the dragonfire menu or use .t message
6 --
7 -- supports the Wisp encrypted whisper mod
8 --
9 -- .t to say something in team chat (or regular chat if team mode is on)
10 -- .tadd to add a team member
11 -- .tdel to remove
12 -- .tlist to list team
14 -- .coords to send a message containing coordinates
15 -- .mcoord to send a player your current coordinates
18 --[[
19 Public methods
21 tchat.contains_coords(message) - returns true if the message contains coordinates (2d or 3d)
23 tchat.send(message) - send a message to teamchat, returns true if sent, nil if not
24 tchat.send_conditional(message, inverse?) - send a message to teamchat or regular chat, returns true if sent to teamchat, false if main chat, nil if not sent
25 tchat.send_coords(message) - send a message containing coordinates, true if sent, nil if not
27 tchat.whisper_coords(player) - DM current coords to a player
29 tchat.chat_clear() - clear chat widget
30 tchat.chat_set([]) - set chat widget
31 tchat.chat_append([] or message) - append to chat widget
33 tchat.team_add_player(player) - add player to team list
34 tchat.team_remove_player(player) - remove player from team list
35 tchat.team_clear() - clear team list
36 tchat.team_set([]) - set team list
39 Public properties
41 tchat.chat: last few chat messages
42 tchat.team: team list
43 tchat.team_online: online team list
44 tchat.players: currently online players
47 Settings
49 bool tchat_view_chat - if the team chat is shown
50 bool tchat_view_team_list - if the team list is shown
51 bool tchat_view_player_list - if the player list is shown
52 bool tchat_team_mode - if team mode is on
54 bool tchat_colorize_team - if true, team list will show all team members colored for who is online
55 bool tchat_use_wisp - if true, encrypt all messages using Wisp
57 str tchat_prefix_message - prefix for teamchat messages
58 str tchat_prefix_receive - prefix for received messages
59 str tchat_prefix_self - prefix for self sent messages
60 str tchat_prefix_send - prefix for sent messages
62 str tchat_blacklist - comma separated list of accounts that cannot send team chat messages (useful for secret alts)
64 num tchat_chat_length - chat length (messages, not lines)
65 num tchat_chat_width - chat width (columns)
66 --]]
69 ---
70 -- settings
72 local function init_settings(setting_table)
73 for k, v in pairs(setting_table) do
74 if minetest.settings:get(k) == nil then
75 if type(v) == "boolean" then
76 minetest.settings:set_bool(k, v)
77 else
78 minetest.settings:set(k, v)
79 end
80 end
81 end
82 end
84 init_settings({
85 tchat_view_chat = false,
86 tchat_view_team_list = true,
87 tchat_view_player_list = true,
88 tchat_team_mode = false,
90 tchat_colorize_team = false,
92 tchat_prefix_message = "TCHAT",
93 tchat_prefix_receive = "From",
94 tchat_prefix_self = "To Yourself",
95 tchat_prefix_send = "To",
97 tchat_use_wisp = false,
99 tchat_hide_sent = true,
100 tchat_blacklist = "",
102 tchat_chat_length = 6,
103 tchat_chat_width = 80
108 -- globals
110 tchat = {}
112 tchat.team = {}
113 tchat.team_online = {}
114 tchat.chat = {}
115 tchat.players = {}
117 -- used for logs
118 local server_info = minetest.get_server_info()
119 local server_id = server_info.address .. ':' .. server_info.port
121 local max_total_chat_length = 1024
123 local player_list_epoch = 0
125 local message_prefix = minetest.settings:get("tchat_prefix_message")
126 local message_receive = minetest.settings:get("tchat_prefix_receive")
127 local message_receive_self = minetest.settings:get("tchat_prefix_self")
128 local message_to = minetest.settings:get("tchat_prefix_send")
130 local team_mode = minetest.settings:get_bool("tchat_team_mode")
132 local use_wisp = minetest.settings:get_bool("tchat_use_wisp")
134 local hide_sent = minetest.settings:get_bool("tchat_hide_sent")
135 local blacklist = string.split(minetest.settings:get("tchat_blacklist"))
137 local chat_length = tonumber(minetest.settings:get("tchat_chat_length"))
138 local chat_width = tonumber(minetest.settings:get("tchat_chat_width"))
140 local storage = minetest.get_mod_storage()
142 if storage:get("tchat_team") == nil or storage:get("tchat_team") == "null" then
143 storage:set_string("tchat_team", "[]")
146 tchat.team = minetest.parse_json(storage:get_string("tchat_team"))
148 -- overrides contains_coords() the next time it runs
149 local message_confirmed_safe = false
151 -- coordinate matching
152 local pattern = "[-]?%d[.%d]*"
153 local space = "[,%s]+"
154 local pattern_three = pattern .. space .. pattern .. space .. pattern
155 local pattern_two = pattern .. space .. pattern
157 local chat_idx
158 local player_list_idx
159 local team_list_idx
160 local chat_str = ""
164 -- private stuff
166 local function apply(list, func, filter)
167 local out = {}
168 for k, v in ipairs(list) do
169 if filter(v) then
170 out[#out + 1] = func(v)
171 else
172 out[#out + 1] = v
175 return out
178 local function uniq(list)
179 local last
180 local out = {}
181 for k, v in ipairs(list) do
182 if last ~= v then
183 out[#out + 1] = v
185 last = v
187 return out
190 -- limit a list to the last size elements
191 local function limit_list(list, size)
192 local out = {}
193 for i = math.max(1, #list - size), #list do
194 out[#out + 1] = list[i]
196 return out
199 local function in_list(list, value)
200 for k, v in ipairs(list) do
201 if v == value then
202 return true
205 return false
209 local function get_team_str()
210 if minetest.settings:get_bool("tchat_colorize_team") then
211 return table.concat(apply(tchat.team,
212 function(value)
213 return minetest.colorize("#00FFFF", value)
214 end,
215 function(value)
216 return in_list(tchat.team_online, value)
217 end), "\n")
218 else
219 return table.concat(tchat.team_online, "\n")
224 local function display_chat()
225 return minetest.localplayer:hud_add({
226 hud_elem_type = 'text',
227 name = "Teamchat",
228 text = "Team Chat\n\n" .. chat_str,
229 number = 0xEEFFEE,
230 direction = 0,
231 position = {x=0.01, y=0.45},
232 scale = {x=0.9, y=0.9},
233 alignment = {x=1, y=1},
234 offset = {x=0, y=0}
238 local function display_player_list()
239 return minetest.localplayer:hud_add({
240 hud_elem_type = 'text',
241 name = "Online Players",
242 text = "Players\n\n" .. table.concat(tchat.players, "\n"),
243 number = 0xDDFFDD,
244 direction = 0,
245 position = {x=0.9, y=0.01},
246 alignment = {x=1, y=1},
247 offset = {x=0, y=0}
251 -- should prob have all team members with online ones colored
252 local function display_team_list()
253 return minetest.localplayer:hud_add({
254 hud_elem_type = 'text',
255 name = "Team",
256 text = "Team\n\n" .. get_team_str(),
257 number = 0x00FF00,
258 direction = 0,
259 position = {x=0.8, y=0.01},
260 alignment = {x=1, y=1},
261 offset = {x=0, y=0}
265 local function auto_display(idx, setting, func)
266 if minetest.settings:get_bool(setting) then
267 if not idx then
268 return func()
270 else
271 if idx then
272 minetest.localplayer:hud_remove(idx)
273 return nil
276 return idx
279 local function auto_update(idx, text)
280 if idx ~= nil then
281 minetest.localplayer:hud_change(idx, "text", text)
285 local function update_team_online()
286 tchat.team_online = {}
287 for k, v in ipairs(tchat.players) do
288 if in_list(tchat.team, v) then
289 tchat.team_online[#tchat.team_online + 1] = v
294 local function update_chat_str()
295 chat_str = ""
296 for k, v in ipairs(limit_list(tchat.chat, chat_length - 1)) do
297 chat_str = chat_str .. "\n" .. minetest.wrap_text(v, chat_width)
299 chat_str = table.concat(limit_list(string.split(chat_str, "\n"), chat_length - 1), "\n")
301 -- update chat (do it here so external mods can add to the chat)
302 auto_update(chat_idx, "Team Chat\n\n" .. chat_str)
305 local function team_add_self()
306 tchat.team_add_player(minetest.localplayer:get_name())
311 -- public interface
314 function tchat.contains_coords(message)
315 if (not message_confirmed_safe and (message:find(pattern_three) or message:find(pattern_two))) then
316 return true
318 return false
322 local function dm(player, message)
323 if wisp == nil or not use_wisp then
324 minetest.send_chat_message("/msg " .. player .." " .. message)
325 else
326 wisp.send(player, message, true)
330 -- send
331 function tchat.send(message, force_coords, force_commands)
332 if (tchat.contains_coords(message) and not force_coords) or in_list(blacklist, minetest.localplayer:get_name()) then
333 return
336 if message:sub(1,1) == "/" and not force_commands then
337 minetest.display_chat_message("A /command was scheduled to be sent to team chat but wasn't sent.")
338 return
341 local me = minetest.localplayer:get_name()
343 if not in_list(tchat.team, minetest.localplayer:get_name()) then
344 team_add_self()
347 update_team_online()
349 local prepend = ""
350 if use_wisp then
351 prepend = "E "
354 tchat.chat_append(prepend .. me .. ": " .. message)
356 for k, p in ipairs(tchat.team_online) do
357 if p ~= me then
358 dm(p, message_prefix .. " " .. message)
361 return true
364 function tchat.send_conditional(message, inverse, force_coords)
365 if tchat.contains_coords(message) and not force_coords then
366 return
369 team_mode = minetest.settings:get_bool("tchat_team_mode")
371 local tm = team_mode
372 if inverse then
373 tm = not team_mode
376 if tm then
377 tchat.send(message)
378 return true
379 else
380 minetest.send_chat_message(message)
381 return false
385 function tchat.send_coords(message)
386 message_confirmed_safe = true
387 local ret = tchat.send_conditional(message)
388 message_confirmed_safe = false
389 return ret
393 function tchat.whisper_coords(player)
394 if player == "" then
395 return
397 local coords = minetest.pos_to_string(vector.round(minetest.localplayer:get_pos()))
398 minetest.run_server_chatcommand("w", param .. " " .. coords)
402 -- chat
403 local function autoclear_chat()
404 if #tchat.chat > max_total_chat_length then
405 tchat = limit_list(tchat.chat, max_chat_total_length)
409 function tchat.chat_clear()
410 tchat.chat = {}
411 update_chat_str()
414 function tchat.chat_set(message_list)
415 chat = message_list
416 autoclear_chat()
417 update_chat_str()
420 function tchat.chat_append(message)
421 tchat.chat[#tchat.chat + 1] = message
422 autoclear_chat()
424 minetest.log("action", "[tchat] " .. minetest.localplayer:get_name() .. "@" .. server_id .. " " .. message)
426 update_chat_str()
428 -- popup chat if its closed
429 minetest.settings:set_bool("tchat_view_chat", true)
430 chat_idx = auto_display(chat_idx, "tchat_view_chat", display_chat)
434 local function team_save()
435 storage:set_string("tchat_team" , minetest.write_json(tchat.team))
438 -- team
439 function tchat.team_add_player(player)
440 if not in_list(tchat.team, player) then
441 tchat.team[#tchat.team + 1] = player
442 update_team_online()
443 team_save()
447 function tchat.team_remove_player(player)
448 local out = {}
449 for k, v in ipairs(tchat.team) do
450 if v ~= player then
451 out[#out + 1] = v
454 tchat.team = out
455 team_save()
458 function tchat.team_clear()
459 tchat.team = {}
460 team_save()
463 function tchat.team_set(player_list)
464 tchat.team = player_list
465 team_save()
470 -- callbacks
472 minetest.register_on_sending_chat_message(function(message)
473 if tchat.contains_coords(message) then
474 minetest.display_chat_message("Message contained coordinates, be careful.")
475 return true
478 team_mode = minetest.settings:get_bool("tchat_team_mode")
480 if not team_mode then
481 return
484 tchat.send(message)
485 return true
486 end)
489 local function message_sent(message)
490 return message == "Message sent."
493 local function clean_message(message)
494 -- dirty, strips out legitimate uses of the prefix
495 message = message:gsub(message_prefix, "")
496 message = message:gsub("^" .. message_receive, "")
497 message = message:gsub("^" .. message_receive_self, minetest.localplayer:get_name())
499 message = message:gsub(": ", ": ")
500 message = message:match("^%s*(.-)%s*$")
502 return message
505 -- greedily be the first in the receiving list (prob doesnt always work)
506 table.insert(minetest.registered_on_receiving_chat_message, 1, function(message)
507 if hide_sent and message_sent(message) then
508 return true
511 -- bit dirty, doesnt check the prefix position
512 if not message:find(message_prefix) then
513 return
516 local player = message:match(message_receive .. " (.+): " .. message_prefix)
518 local from_self = message:sub(1, message_receive_self:len()) == message_receive_self
519 local received = message:sub(1, message_receive:len()) == message_receive
520 local sent = message:sub(1, message_to:len()) == message_to
522 if sent and not from_self then
523 return true
526 if not from_self and not in_list(tchat.team_online, player) then
527 return
530 -- add to chat list
531 if from_self or received then
532 tchat.chat_append(clean_message(message))
533 return true
535 end)
537 if wisp ~= nil then
538 wisp.register_on_receive_split(function(player, message)
539 if message:find(message_prefix) then
540 tchat.chat_append("E " .. player .. ": " .. clean_message(message))
541 return true
543 end)
546 minetest.register_globalstep(function()
547 -- update data
548 if player_list_epoch < os.time() + 2 then
549 -- update players, remove duplicates
550 tchat.players = minetest.get_player_names()
551 table.sort(tchat.players)
552 tchat.players = uniq(tchat.players)
554 update_team_online()
556 -- update HUD
557 auto_update(player_list_idx, "Players\n\n" .. table.concat(tchat.players, "\n"))
558 auto_update(team_list_idx, "Team\n\n" .. get_team_str())
560 player_list_epoch = os.time()
563 -- display (if we need to)
564 if minetest.localplayer then
565 chat_idx = auto_display(chat_idx, "tchat_view_chat", display_chat)
566 player_list_idx = auto_display(player_list_idx, "tchat_view_player_list", display_player_list)
567 team_list_idx = auto_display(team_list_idx, "tchat_view_team_list", display_team_list)
569 end)
573 -- command/cheat interface
575 minetest.register_chatcommand("t", {
576 params = "<message>",
577 description = "Send a message to your team chat, or regular chat if team mode is on.",
578 func = function(message)
579 if tchat.contains_coords(message) then
580 minetest.display_chat_message("Message contained coordinates, be careful.")
581 return
583 tchat.send_conditional(message, true)
586 minetest.register_chatcommand("tcoords", {
587 params = "<message>",
588 description = "Send a message containing coordinates to teamchat.",
589 func = function(message)
590 tchat.send(message, true)
593 minetest.register_chatcommand("tlist", {
594 description = "List your team.",
595 func = function(param)
596 minetest.display_chat_message(table.concat(tchat.team, ", "))
599 minetest.register_chatcommand("tadd", {
600 params = "<player>",
601 description = "Add player to your team.",
602 func = tchat.team_add_player
604 minetest.register_chatcommand("tdel", {
605 params = "<player>",
606 description = "Remove player from your team.",
607 func = tchat.team_remove_player
609 minetest.register_chatcommand("tclear", {
610 description = "Clear team list.",
611 func = tchat.team_clear
614 minetest.register_chatcommand("tchat_clear", {
615 description = "Clear team chat widget.",
616 func = tchat.chat_clear
619 minetest.register_chatcommand("coords", {
620 params = "<message>",
621 description = "Send message containing coordinates.",
622 func = tchat.send_coords
624 minetest.register_chatcommand("mcoord", {
625 params = "<player>",
626 description = "Whisper current coordinates to player.",
627 func = tchat.whisper_coords
631 -- minetest.register_cheat("Teamchat Mode", "Chat", "tchat_team_mode") -- doesnt work rn for some reason
632 minetest.register_cheat("Show Team List", "Chat", "tchat_view_team_list")
633 minetest.register_cheat("Show Player List", "Chat", "tchat_view_player_list")
634 minetest.register_cheat("Show Teamchat", "Chat", "tchat_view_chat")