-- chat_tells - Core logic, properties, and data for the Chat system -- (component of Chat System) -- Template attachment: chat_system -- Created by: Patrick Ferland -- Created on: 05/27/2008 -- Revision History -- 05/27/2008 P.Ferland - New script -- 05/30/2008 P.Ferland - Initial release -- 06/02/2008 P.Ferland - Cleanup and documentation -- 07/08/2008 P.Ferland - Implementation of crossworld chat -- 07/10/2008 P.Ferland - Cleanup on XW chat including talk_connect() and talk_error() -- 07/17/2008 P.Ferland - Added string cleanup in gsay and gtell -- 07/18/2008 P.Ferland - Made /gunsub send the user to spatial if unsubscribing current channel -- 07/22/2008 P.Ferland - Commented out sync scripts -- 07/23/2008 P.Ferland - Silenced entry/exit XW messages and fixed connect to reconnect users to channels -- 07/29/2008 P.Ferland - Replaced "-" system prefix with coloration -- 07/30/2008 P.Ferland - Hacked around timing problem with registering talk target -- 08/04/2008 P.Ferland - Added initialization to script attachment -- 08/11/2008 P.Ferland - Fixed bug in formatting of chat channel for /tell and /reply -- 08/18/2008 P.Ferland - Coerced username into user object for sender if XW user is in this world -- 09/02/2008 P.Ferland - Changed hardcoded buffer sizes to MAX_SEND_BUFFER_SIZE -- 09/02/2008 P.Ferland - Bug fixes in talk registration -- 09/08/2008 P.Ferland - Another bug fix in registration (person -> user) -- 09/12/2008 P.Ferland - Resolved talk registration on module import -- 10/08/2008 P.Ferland - OX friendliness pass -- 10/28/2008 P.Ferland - Removed obsolete script attachment code and default "Builders" channel for admins -- 11/06/2008 P.Ferland - XW tells now come from chat system instead of recipient if sender is not in-world, to allow /ignore -- Constants PLAYER_TELL_SCRIPT = '11302:21' TALK_ERROR_MESSAGES = { [600] = "The user could not be found.", [601] = "An error occurred with your chat request.", [602] = "Your chat request was not valid.", } -- Script properties Define Properties() script_description = "Chat Private and Crossworld" script_long_description = "Set up private messaging and chat between worlds" chat_system_tell_enabled = 1 ExposeProperty('chat_system_tell_enabled', 'Allow /tell private messaging?', 'checkbox') PersistProperty('chat_system_tell_enabled') chat_system_tell_sound_enabled = 1 ExposeProperty('chat_system_tell_sound_enabled', 'Play sound when receiving a tell?', 'checkbox') PersistProperty('chat_system_tell_sound_enabled') chat_system_tell_sound = '11302:3' ExposeProperty('chat_system_tell_sound', 'Sound to play when receiving a tell', 'soundId') PersistProperty('chat_system_tell_sound') chat_system_xworld_enabled = 1 ExposeProperty('chat_system_xworld_enabled', 'Enable crossworld chat?', 'checkbox') PersistProperty('chat_system_xworld_enabled') IncludeScript('11302:12') -- chat_utility end -- Commands -- no commands -- Triggers -- Script attaches (object instantiation or attached in builder) Trigger attach() -- Register scripts for loading chat_system_register_player_script(self, PLAYER_TELL_SCRIPT) SendTo(self, 'chat_system_register_command', 0, 'tell', 'Send a private message to a user (/tell \038lt;user\038gt; \038lt;message\038gt;)', 1, 0) SendTo(self, 'chat_system_register_alias', 0, 't', 'tell') SendTo(self, 'chat_system_register_command', 0, 'reply', 'Reply to the user who last sent you a /tell (/reply \038lt;message\038gt;)', 0, 0) SendTo(self, 'chat_system_register_alias', 0, 'r', 'reply') SendTo(self, 'chat_system_register_command', 0, 'afk', 'Mark yourself AFK (away from keyboard) with an optional message, or return from AFK status', 0, 0) SendTo(self, 'chat_system_register_command', 0, 'gsub', 'Subscribe to a cross-world channel (/gsub \038lt;channel\038gt;)', 1, 0) SendTo(self, 'chat_system_register_command', 0, 'gunsub', 'Unsubscribe from a cross-world channel (/gunsub \038lt;channel\038gt;)', 1, 0) SendTo(self, 'chat_system_register_command', 0, 'glist', 'List current cross-world subscriptions', 0, 0) SendTo(self, 'chat_system_register_command', 0, 'gsay', 'Talk to a cross-world channel (/gsay \038lt;channel\038gt; \038lt;message\038gt;)', 1, 0) SendTo(self, 'chat_system_register_command', 0, 'gtell', 'Send a private cross-world message (/gtell \038lt;user\038gt; message)', 1, 0) --SendTo(self, 'chat_system_delayed_talk_register', 1000) chat_system_initialize_talk(self) end Trigger chat_system_delayed_talk_register() chat_system_initialize_talk(self) end -- Script detaches Trigger detach() -- Unregister scripts chat_system_unregister_player_script(self, PLAYER_TELL_SCRIPT) SendTo(self, 'chat_system_unregister_command', 0, 'tell') SendTo(self, 'chat_system_unregister_command', 0, 'reply') SendTo(self, 'chat_system_unregister_command', 0, 'gsub') SendTo(self, 'chat_system_unregister_command', 0, 'gunsub') SendTo(self, 'chat_system_unregister_command', 0, 'glist') SendTo(self, 'chat_system_unregister_command', 0, 'gsay') end -- Chat command /tell -- Sends message to username Trigger chat_msg_tell(sender, message, username) local user = nil local target_user = nil if (username == nil) then -- No user specified, abort chat_system_set_callback(sender, CHAT_STATUS_CODES.SYNTAX_ERROR, 'No username provided') return end local start = string.find(message, ' ') if (start == nil) then start = string.len(username) + 1 end local chat_message = message while ( (string.sub(message, start, start) == ' ') and (start < string.len(message) ) ) do start = start + 1 end if (start <= string.len(message) ) then chat_message = string.sub(message, start, string.len(message)) else chat_message = '' end if ( (chat_message == '') or (chat_message == nil) ) then -- No message specified, abort chat_system_set_callback(sender, CHAT_STATUS_CODES.SYNTAX_ERROR, 'No message provided') return end if (self.chat_system_tell_enabled == 1) then local lower_name = string.lower(username) local w = GetWorld() for _, user in ipairs(w.users) do if ( string.lower(user.name) == lower_name ) then target_user = user break end end if (target_user ~= nil) then local formatted_message = sender.name .. ', private: ' .. chat_message local channel = chat_system_channel_name('user', target_user.name) SendTo(self, 'chat_system_send_message', 0, target_user, formatted_message, channel, sender, 'tell') SendTo(target_user, 'chat_system_tell', 0, sender) if (self.chat_system_tell_sound_enabled == 1) then PlaySoundTo(target_user, self.chat_system_tell_sound, 1, 0) end local echo_message = 'Sent to ' .. username .. ': ' .. chat_message local echo_channel = chat_system_channel_name('user', sender.name) SendTo(self, 'chat_system_send_message', 0, sender, echo_message, echo_channel, sender, 'tell') else local message = chat_system_format_system_message(username .. ' could not be located for a tell.') local echo_channel = chat_system_channel_name('user', sender.name) SendTo(self, 'chat_system_send_message', 0, sender, message, echo_channel, sender, 'tell') end else local channel = chat_system_channel_name('user', sender.name) local message = chat_system_format_system_message('Private messaging is disabled.') SendTo(self, 'chat_system_send_message', 0, sender, message, channel, sender, 'tell') end chat_system_set_callback(sender, CHAT_STATUS_CODES.OK, 'OK') end -- Chat command /reply -- Replies via a /tell to the last user a tell was received from Trigger chat_msg_reply(sender, message) local username = sender.chat_system_tell_reply local user = nil local target_user = nil if ( (username == nil) or (username == '') ) then -- No user specified, abort chat_system_set_callback(sender, CHAT_STATUS_CODES.SYNTAX_ERROR, 'You have no tells to reply to') return end local chat_message = message if ( (chat_message == '') or (chat_message == nil) ) then -- No message specified, abort chat_system_set_callback(sender, CHAT_STATUS_CODES.SYNTAX_ERROR, 'No message provided') return end if (self.chat_system_tell_enabled == 1) then local lower_name = string.lower(username) local w = GetWorld() for _, user in ipairs(w.users) do if ( string.lower(user.name) == lower_name ) then target_user = user break end end if (target_user ~= nil) then local formatted_message = sender.name .. ', private: ' .. chat_message local channel = chat_system_channel_name('user', target_user.name) SendTo(self, 'chat_system_send_message', 0, target_user, formatted_message, channel, sender, 'tell') SendTo(target_user, 'chat_system_tell', 0, sender) if (self.chat_system_tell_sound_enabled == 1) then PlaySoundTo(target_user, self.chat_system_tell_sound, 1, 0) end local echo_message = 'Replied to ' .. username .. ': ' .. chat_message local echo_channel = chat_system_channel_name('user', sender.name) SendTo(self, 'chat_system_send_message', 0, sender, echo_message, echo_channel, sender, 'tell') else local message = chat_system_format_system_message(username .. ' could not be located for a reply.') local echo_channel = chat_system_channel_name('user', sender.name) SendTo(self, 'chat_system_send_message', 0, sender, message, echo_channel, sender, 'tell') end else local channel = chat_system_channel_name('user', sender.name) local message = chat_system_format_system_message('Private messaging is disabled.') SendTo(self, 'chat_system_send_message', 0, sender, message, channel, sender, 'tell') end chat_system_set_callback(sender, CHAT_STATUS_CODES.OK, 'OK') end -- Chat command /afk [message] -- Set a message (user-supplied, or default if none supplied) to auto-respond to private messages with -- (or toggle off AFK status) Trigger chat_msg_afk(sender, message) local afk_message = message local empty_afk = 0 if ( (message == '') or (message == nil) ) then empty_afk = 1 afk_message = 'User is AFK (away from keyboard).' end local set_afk = 0 if (empty_afk == 1) then -- Prepare to toggle! if (sender.chat_system_tell_afk == '') then sender.chat_system_tell_afk = afk_message set_afk = 1 else sender.chat_system_tell_afk = '' set_afk = 0 end else -- Set AFK sender.chat_system_tell_afk = afk_message set_afk = 1 end local afk_response = '' if (set_afk == 1) then afk_response = chat_system_format_system_message('You are now AFK: ' .. afk_message) else afk_response = chat_system_format_system_message('You are no longer AFK.') end SendTo(self, 'chat_system_send_message', 0, sender, afk_response, channel, sender, 'afk') chat_system_set_callback(sender, CHAT_STATUS_CODES.OK, 'OK') end -- Chat command /gsub -- Subscribes to a cross-world chat channel Trigger chat_msg_gsub(sender, message, gchannel) if (gchannel ~= nil) then local gchannel_fixed = string.upper(string.sub(gchannel, 1, 1)) if (string.len(gchannel) > 1) then gchannel_fixed = gchannel_fixed .. string.lower(string.sub(gchannel, 2, string.len(gchannel))) end if (sender.chat_system_xworld_subscriptions[gchannel_fixed] ~= nil) then chat_system_set_callback(sender, CHAT_STATUS_CODES.INVALID_ARGUMENT_VALUE, 'Already in XW channel ' .. gchannel_fixed) return end local gchannel_name = chat_system_channel_name('xworld', gchannel_fixed) SendTo(self, 'chat_system_subscribe_channel', 0, sender, gchannel_name) TalkJoin(sender, gchannel_fixed) --TalkMessageChannel(sender, gchannel_fixed, 'Joined') local user_channel = chat_system_channel_name('user', sender.name) chat_system_send_to_subscribers(self, sender, user_channel, chat_system_format_system_message('You have joined XW channel "' .. gchannel_fixed .. '"'), 'gsub') sender.chat_system_xworld_subscriptions[gchannel_fixed] = 1 SaveToDb(sender) end chat_system_set_callback(sender, CHAT_STATUS_CODES.OK, 'OK') end -- Chat command /gunsub -- Unsubscribes from a cross-world chat channel Trigger chat_msg_gunsub(sender, message, gchannel) if (self.chat_system_xworld_enabled ~= 1) then return end if (gchannel ~= nil) then local gchannel_fixed = string.upper(string.sub(gchannel, 1, 1)) if (string.len(gchannel) > 1) then gchannel_fixed = gchannel_fixed .. string.lower(string.sub(gchannel, 2, string.len(gchannel))) end local user_channel = chat_system_channel_name('user', sender.name) if (sender.chat_system_xworld_subscriptions[gchannel_fixed] == nil) then -- Not in channel! TODO: Tell user. chat_system_set_callback(sender, CHAT_STATUS_CODES.INVALID_ARGUMENT_VALUE, 'Not in XW channel ' .. gchannel_fixed) return end local gchannel_name = chat_system_channel_name('xworld', gchannel_fixed) --TalkMessageChannel(sender, gchannel_fixed, 'Left') TalkLeave(sender, gchannel_fixed) sender.chat_system_xworld_subscriptions[gchannel_fixed] = nil SendTo(self, 'chat_system_unsubscribe_channel', 0, sender, gchannel_name) SaveToDb(sender) chat_system_send_to_subscribers(self, sender, user_channel, chat_system_format_system_message('You have left XW channel "' .. gchannel_fixed .. '"'), 'gunsub') if (gchannel_name == sender.chat_system_current_channel) then if (sender.chat_system_channel_aliases['Spatial'] ~= nil) then sender.chat_system_current_channel = sender.chat_system_channel_aliases['Spatial'] end end end chat_system_set_callback(sender, CHAT_STATUS_CODES.OK, 'OK') end -- Chat command /glist -- Lists current cross-world channel subscriptio Trigger chat_msg_glist(sender) if (self.chat_system_xworld_enabled ~= 1) then return end local message = chat_system_format_system_message('- Current XW channels:
') local pre = '' local channel = chat_system_channel_name('user', sender.name) local xw_channel = '' for xw_channel, _ in pairs(sender.chat_system_xworld_subscriptions) do message = message .. chat_system_format_system_message(pre .. xw_channel) pre = ', ' end if (string.len(message) > 0) then chat_system_send_to_subscribers(self, sender, channel, message, 'glist') end chat_system_set_callback(sender, CHAT_STATUS_CODES.OK, 'OK') end -- Chat command /gsay -- Say to cross-world channel (if subscribed) Trigger chat_msg_gsay(sender, message, gchannel) local clean_message = '' if (message ~= nil) then if (string.len(message) > self.chat_system_buffer_size) then clean_message = string.sub(message, 1, self.chat_system_buffer_size) else clean_message = message end end clean_message = chat_system_strip_html(clean_message) if (self.chat_system_xworld_enabled ~= 1) then return end if (gchannel ~= nil) then local gchannel_fixed = string.upper(string.sub(gchannel, 1, 1)) if (string.len(gchannel) > 1) then gchannel_fixed = gchannel_fixed .. string.lower(string.sub(gchannel, 2, string.len(gchannel))) end if (sender.chat_system_xworld_subscriptions[gchannel_fixed] == nil) then chat_system_set_callback(sender, CHAT_STATUS_CODES.INVALID_ARGUMENT_VALUE, 'Not in XW channel ' .. gchannel_fixed) return end local start = string.find(clean_message, ' ') if (start == nil) then chat_system_set_callback(sender, CHAT_STATUS_CODES.INVALID_ARGUMENT_VALUE, 'No message provided.') return end local chat_message = clean_message while ( (string.sub(clean_message, start, start) == ' ') and (start < string.len(clean_message) ) ) do start = start + 1 end if (start <= string.len(clean_message) ) then chat_message = string.sub(clean_message, start, string.len(clean_message)) else chat_message = '' end if ( (chat_message == '') or (chat_message == nil) ) then -- No message specified, abort chat_system_set_callback(sender, CHAT_STATUS_CODES.SYNTAX_ERROR, 'No message provided') return end local gchannel_name = chat_system_channel_name('xworld', gchannel_fixed) TalkMessageChannel(sender, gchannel_fixed, chat_message) end chat_system_set_callback(sender, CHAT_STATUS_CODES.OK, 'OK') end -- Chat command /gtell -- Send private to crossworld user Trigger chat_msg_gtell(sender, message, guser) local clean_message = '' if (message ~= nil) then if (string.len(message) > self.chat_system_buffer_size) then clean_message = string.sub(message, 1, self.chat_system_buffer_size) else clean_message = message end end clean_message = chat_system_strip_html(clean_message) if (self.chat_system_xworld_enabled ~= 1) then return end if (guser ~= nil) then local start = string.find(clean_message, ' ') if (start == nil) then chat_system_set_callback(sender, CHAT_STATUS_CODES.SYNTAX_ERROR, 'No message provided') return end local chat_message = clean_message while ( (string.sub(clean_message, start, start) == ' ') and (start < string.len(clean_message) ) ) do start = start + 1 end if (start <= string.len(clean_message) ) then chat_message = string.sub(clean_message, start, string.len(clean_message)) else chat_message = '' end if ( (chat_message == '') or (chat_message == nil) ) then -- No message specified, abort chat_system_set_callback(sender, CHAT_STATUS_CODES.SYNTAX_ERROR, 'No message provided') return end if (string.find(guser, '@metaplace.com/') == nil) then local bad_username = 'Bad XW username ' .. guser .. '; Fully-qualified XW username is username@metaplace.com/world' chat_system_set_callback(sender, CHAT_STATUS_CODES.INVALID_ARGUMENT_VALUE, bad_username) return end -- Assume an error at the outset. -- If the user is not online, the TalkMessageUser will fail. chat_system_set_callback(sender, CHAT_STATUS_CODES.INVALID_ARGUMENT_VALUE, 'Could not find XW user ' .. guser) TalkMessageUser(sender, guser, chat_message) local sender_channel = chat_system_channel_name('user', sender.name) chat_system_send_to_subscribers(self, sender, sender_channel, 'Private message sent to ' .. guser .. ': ' .. chat_message, 'gtell') end chat_system_set_callback(sender, CHAT_STATUS_CODES.OK, 'OK') end -- System trigger: Incoming crossworld user message Trigger talk_msg_user(from, person, body) if (self.chat_system_xworld_enabled ~= 1) then return end local from_name = string.split(from, '@')[1] local sender = person local sender_name = string.lower(from_name) local user = person local w = GetWorld() for _, user in ipairs(w.users) do if (string.lower(user.name) == sender_name) then sender = user break end end if (sender == person) then sender = self end local channel = chat_system_channel_name('user', person.name) local processed_message = '(XW, private) ' .. from .. ': ' .. body chat_system_send_to_subscribers(self, sender, channel, processed_message, 'gtell') end -- System trigger: Incoming crossworld channel message Trigger talk_msg_channel(from, channel, body) if (self.chat_system_xworld_enabled ~= 1) then return end local from_name = string.split(from, '@')[1] local sender = self local sender_name = string.lower(from_name) local user = person local w = GetWorld() for _, user in ipairs(w.users) do if (string.lower(user.name) == sender_name) then sender = user break end end local gchannel_name = chat_system_channel_name('xworld', channel) local processed_message = '(XW ' .. channel .. ') ' .. from_name .. ': ' .. body chat_system_send_to_subscribers(self, sender, gchannel_name, processed_message, 'gsay') end -- System trigger: World has connected to talk server (in case of talk server restart) Trigger talk_connect() if (self.chat_system_xworld_enabled ~= 1) then return end chat_system_initialize_talk(self) end -- System trigger: World has connected to talk server (in case of talk server restart) Trigger talk_disconnect() if (self.chat_system_xworld_enabled ~= 1) then return end local world_channel = chat_system_channel_name('world') chat_system_send_to_subscribers(self, self, world_channel, chat_system_format_system_message('XW Error: Lost connection to cross-world talk server'), 'help') end -- System trigger: Error with talk command Trigger talk_error(from, code, message) if (self.chat_system_xworld_enabled ~= 1) then return end local user_channel = chat_system_channel_name('user', from) chat_system_send_to_subscribers(self, self, user_channel, chat_system_format_system_message('XW Error: ' .. TALK_ERROR_MESSAGES[code]), 'help') end -- Local functions function chat_system_initialize_talk(self) TalkRegisterTarget(self) local w = GetWorld() local user = nil local xw_channel = '' for _, user in pairs(w.users) do TalkLoginUser(user) for xw_channel, _ in pairs(user.chat_system_xworld_subscriptions) do local chat_xw_channel = chat_system_channel_name('xworld', xw_channel) SendTo(self, 'chat_system_subscribe_channel', 0, user, chat_xw_channel) TalkJoin(user, xw_channel) local user_channel = chat_system_channel_name('user', user.name) chat_system_send_to_subscribers(self, user, user_channel, chat_system_format_system_message('You have joined XW channel "' .. xw_channel .. '"'), 'gsub') end end end