-- chat_system - 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 -- 06/03/2008 P.Ferland - Corrected attach/detach logic to allow chat system creation (singleton) and deletion -- 06/10/2008 P.Ferland - Corrected player attach/detach bugs, added /fontsize, and reformatted help -- 06/10/2008 P.Ferland - Added max buffer size -- 06/12/2008 P.Ferland - Added enter/exit messages for spatial chat -- 06/13/2008 P.Ferland - Removed spurious debugs -- 06/17/2008 P.Ferland - Added spatial exit announce for unsubscribe_all, fixed bug that wasn't unsubscribing users -- 06/23/2008 P.Ferland - Tested trigger reference count model for pre/message/finalize loop -- 06/25/2008 P.Ferland - Removed trigger reference count -- 07/09/2008 P.Ferland - Corrected sender logic to only test players for Superuser status -- 07/15/2008 P.Ferland - Added /who command -- 07/16/2008 P.Ferland - Added channel aliases -- 07/17/2008 P.Ferland - Made /channelname alias switching configurable -- 07/22/2008 P.Ferland - Commented out object attach/detach statements -- 07/22/2008 P.Ferland - Commented out sync scripts -- 07/29/2008 P.Ferland - Replaced - prefixing with system coloration -- 07/30/2008 P.Ferland - Added /say command to speak to current spatial channel -- 08/01/2008 P.Ferland - Defaulted spatial to whole place -- 08/15/2008 P.Ferland - Added "autochat" command -- unusable by players, does not filter incoming chat -- This is for things like a chatbot relaying an RSS feed without the HTML getting stripped. -- 08/29/2008 P.Ferland - Removed spurious debugs -- 09/02/2008 P.Ferland - Changed hardcoded buffer sizes to MAX_SEND_BUFFER_SIZE -- 09/03/2008 P.Ferland - Extended send check for invalid endpoints -- 10/08/2008 P.Ferland - OX friendliness pass -- 10/09/2008 P.Ferland - Removed pause -- 10/28/2008 P.Ferland - Cleaned up old script attachment code -- 10/30/2008 P.Ferland - Made quick-button display configurable -- 11/06/2008 P.Ferland - Made autochat message send as autochat command, not as chat -- Constants WORLD_CHAT_SCRIPT = '11302:9' PLAYER_CHAT_SCRIPT = '11302:10' CHAT_ENTITY = '11302:11' CURRENT_VERSION = { VERSION = '2', VALUES = { ['chat_system_toolbar_enabled'] = 0, ['chat_system_system_color'] = '#AB9A89', } } -- Script Properties Define Properties() script_description = "Chat" script_long_description = "Set up appearance and behaviors of your chat system" -- List of channels and subscribers chat_system_subscriptions = {} -- chat_system_display_quickbuttons = 1 ExposeProperty('chat_system_display_quickbuttons', 'Display mood/emote/action menus and buttons?', 'checkbox') PersistProperty('chat_system_display_quickbuttons') -- System message color chat_system_system_color = '#AB9A89' ExposeProperty('chat_system_system_color', 'Color (web-style) for system-generated messages') PersistProperty('chat_system_system_color') -- Radius for spatial chat chat_system_radius = 0 ExposeProperty('chat_system_radius', 'How far away can you hear other users? (0 for everywhere)', 'int') SetPropRange('chat_system_radius', 0, 1000) PersistProperty('chat_system_radius') -- Toggle for player-defined chat font size chat_system_player_size = 1 ExposeProperty('chat_system_player_size', 'Allow user to set font size?', 'checkbox') PersistProperty('chat_system_player_size') -- Default chat font size chat_system_default_size = 12 ExposeProperty('chat_system_default_size', 'Default chat system font size', 'int') SetPropRange('chat_system_default_size', 8, 18) PersistProperty('chat_system_default_size') -- Entry/exit messages chat_system_announce_ee_spatial = 0 ExposeProperty('chat_system_announce_ee_spatial', 'Announce when users enter/exit spatial?', 'checkbox') PersistProperty('chat_system_announce_ee_spatial') -- Flow control chat_system_buffer_size = 300 ExposeProperty('chat_system_buffer_size', 'Maximum chat message length?', 'int') SetPropRange('chat_system_buffer_size', 100, 1000) PersistProperty('chat_system_buffer_size') -- /who command chat_system_userlist_who = 1 ExposeProperty('chat_system_userlist_who', 'Allow users to use /who command?', 'checkbox') PersistProperty('chat_system_userlist_who') chat_system_userlist_who_afk = 1 ExposeProperty('chat_system_userlist_who_afk', 'Show AFK status in /who command?', 'checkbox') PersistProperty('chat_system_userlist_who_afk') chat_system_userlist_single_max = 20 ExposeProperty('chat_system_userlist_single_max', 'Max number of users to display in /who as single-line?', 'int') SetPropRange('chat_system_userlist_single_max', 0, 100) PersistProperty('chat_system_userlist_single_max') -- /channel shortcuts chat_system_slash_channel_shortcuts = 1 ExposeProperty('chat_system_slash_channel_shortcuts', 'Allow /channelname to quickly switch channels?', 'checkbox') PersistProperty('chat_system_slash_channel_shortcuts') -- Debug setting to display chat channels chat_system_channel_debug = 0 ExposeProperty('chat_system_channel_debug', 'Debug chat channels?', 'checkbox') PersistProperty('chat_system_channel_debug') -- List of scripts to attach/detach on player objects chat_system_plugin_player_scripts = { attached = {}, detached = {} } PersistProperty('chat_system_plugin_player_scripts') -- Chat slash commands and command aliases chat_system_commands = {} chat_system_command_aliases = {} -- List of channels that users may switch their default chat broadcast to chat_system_selectable_channels = {} chat_system_current_version = '1' PersistProperty('chat_system_current_version') IncludeScript('11302:12') -- chat_utility end -- Triggers -- Attach and detach all requisite scripts for users in world -- Generally called when new components are added Trigger chat_system_sync_all_users_scripts() local w = GetWorld() local user = nil if (w.users ~= nil) then for _, user in ipairs(w.users) do chat_system_attach_core_player_scripts(user) SendTo(self, 'chat_system_attach_all_attachable_scripts', 0, user) SendTo(self, 'chat_system_detach_all_detachable_scripts', 0, user) end end end -- Attach all requisite scripts for an entity (user) Trigger chat_system_attach_all_attachable_scripts(entity) chat_system_debug('DEPRECATED: Remove call to chat_system_attach_all_attachable_scripts') end -- Detach all requisite scripts for an entity (user) Trigger chat_system_detach_all_detachable_scripts(entity) chat_system_debug('DEPRECATED: Remove call to chat_system_attach_all_detachable_scripts') end -- Detach all scripts attached to an entity Trigger chat_system_detach_all_scripts(entity) chat_system_debug('DEPRECATED: Remove call to chat_system_attach_all_detachable_scripts (?)') end -- system trigger: fired when script is attached (on instantiation or in builder) -- Chat system enters any place; register as world's chat system Trigger attach() chat_system_log('Adding new chat system ' .. self.id) local w = GetWorld() if (w.chat_system ~= nil) then if (w.chat_system.id ~= nil) then if (GetObjectById(w.chat_system.id).id ~= self.id) then chat_system_log(self.id .. ' attempted to register; world already had chat_system ' .. w.chat_system.id) DestroyObject(self) return end end end w.chat_system = self chat_system_version_check(self) SendTo(self, 'chat_system_initialize', 0) -- Register default channels table.clear(self.chat_system_selectable_channels) self.chat_system_selectable_channels['Spatial'] = '{world}#{place}/_spatial_' self.chat_system_selectable_channels['Place'] = '{world}#{place}' end Trigger chat_system_version_check() chat_system_version_check(self) end -- Chat system is destroyed; unregister as world's chat system Trigger destroyed(place_id) chat_system_log('Chat system ' .. self.id .. ' destroyed.') end -- Initialize user-facing UI components and core command registrations Trigger chat_system_initialize() -- Load default commands SendTo(self, 'chat_system_register_command', 0, 'say', 'Speak to the default channel', 0, 0) SendTo(self, 'chat_system_register_command', 0, 'help', 'Display a list of available commands', 0, 0) SendTo(self, 'chat_system_register_command', 0, 'ignore', 'Ignore user (/ignore \038lt;username\038gt;) - does not work on admins', 1, 0) SendTo(self, 'chat_system_register_command', 0, 'ignorelist', 'List ignored users', 0, 0) SendTo(self, 'chat_system_register_command', 0, 'unignore', 'Unignore user (/unignore \038lt;username\038gt;)', 1, 0) SendTo(self, 'chat_system_register_command', 0, 'mute', '* Mute user (user may not issue ANY chat commands) - does not work on admins', 1, 1) SendTo(self, 'chat_system_register_command', 0, 'unmute', '* Unmute user (user may issue chat commands) - does not work on admins', 1, 1) SendTo(self, 'chat_system_register_command', 0, 'sysglobal', '* Send system message to world channel', 0, 1) SendTo(self, 'chat_system_register_command', 0, 'syslocal', '* Send system message to local channel', 0, 1) SendTo(self, 'chat_system_register_command', 0, 'roll', 'Roll a random number from 1 to 100 (/roll [number] for 1 to [number])', 0, 0) SendTo(self, 'chat_system_register_command', 0, 'fontsize', 'Set font size (between 8 and 18) (/fontsize \038lt;size\038gt;)', 1, 0) SendTo(self, 'chat_system_register_command', 0, 'who', 'See who is online (default is current place, or /who all to see the whole world)', 0, 0) SendTo(self, 'chat_system_register_command', 0, 'emote', 'Perform an emote.', 0, 0) SendTo(self, 'chat_system_register_alias', 0, 'me', 'emote') SendTo(self, 'chat_system_register_alias', 0, 'em', 'emote') SendTo(self, 'chat_system_register_command', 0, 'think', 'Perform a "thought" emote', 0, 0) local w = GetWorld() local user = nil if (w.users ~= nil) then for _, user in ipairs(w.users) do SendTo(user, 'chat_system_initialize', 250) end end end -- Optional trigger a command can call to enhance chat functionality -- This causes the command to be displayed in /help and to use the argument parser / validation Trigger chat_system_register_command(command, help_text, num_args, superuser) self.chat_system_commands[command] = { command = command, help_text = help_text, num_args = num_args, superuser = superuser } end -- Removes a command from /help and parsing Trigger chat_system_unregister_command(command) self.chat_system_commands[command] = nil end -- Registeres a command alias (such as /em for /emote) Trigger chat_system_register_alias(alias, command) self.chat_system_command_aliases[alias] = command end -- Parses a command, building a list of mandatory arguments or failing if missing -- If no command is found, it sends unprocessed data anyway to handle unregistered commands Trigger chat_system_parse_command(sender, command, message) -- Silently ignore muted users if (sender.chat_system_mute == 1) then return end local clean_message = '' if (message ~= nil) then if (string.len(message) > self.chat_system_buffer_size) then clean_message = chat_system_strip_html(string.sub(message, 1, self.chat_system_buffer_size)) else clean_message = chat_system_strip_html(message) end end local i = 0 local command_data = nil local command_ok = 0 local arguments = {} local clean_arguments = {} command_data = self.chat_system_commands[command] if (command_data == nil) then -- check for aliases command_data = self.chat_system_commands[self.chat_system_command_aliases[command]] if (command_data ~= nil) then -- found an alias, so route to the correct command command = self.chat_system_command_aliases[command] end end local super_user = 0 if (sender.type == 'player') then if (IsSuperuser(sender) == 1 ) then super_user = 1 end end if (command_data ~= nil) then if ( super_user == 1 or (command_data.superuser == 0) ) then if (command_data.num_args > 0) then local plural = '' if (command_data.num_args ~= 1) then plural = 's' end if ( (clean_message ~= nil) and (clean_message ~= '') ) then -- NOTE: Yes, the following statement concatenates an empty string to message. -- This forces message to be a string, even if it consists solely of a number arguments = string.split('' .. clean_message, ' ') if (#arguments >= command_data.num_args) then command_ok = 1 local i = 0 for i = 1, command_data.num_args do clean_arguments[i] = arguments[i] end else -- Syntax error: Abort call chat_system_set_callback(sender, CHAT_STATUS_CODES.SYNTAX_ERROR, 'Command /' .. command .. ' requires at least ' .. command_data.num_args .. ' parameter' .. plural) return end else -- Syntax error: Abort call chat_system_set_callback(sender, CHAT_STATUS_CODES.SYNTAX_ERROR, 'Command /' .. command .. ' requires at least ' .. command_data.num_args .. ' parameter' .. plural) return end else command_ok = 1 end end end if (command_ok == 1) then SendTo(self, 'chat_msg_' .. command, 0, sender, clean_message, unpack(clean_arguments)) elseif (command_data == nil) then -- attempt to run as unregistered command (without argument parsing) SendTo(self, 'chat_msg_' .. command, 0, sender, clean_message) end end -- Core chat commands -- /say : Speak to current default channel -- Changes chat_system_current_channel to spatial and chats Trigger chat_msg_say(sender, message) local say_channel = sender.chat_system_current_channel if (sender.chat_system_channel_aliases ~= nil) then say_channel = sender.chat_system_channel_aliases['Spatial'] end if (say_channel == nil) then say_channel = sender.chat_system_current_channel else sender.chat_system_current_channel = say_channel end 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 -- Silently ignore muted users -- This has to be implemented here because it's possible to bypass parse_command if (sender.chat_system_mute == 1) then return end local processed_message = sender.name .. ': ' .. chat_system_strip_html(clean_message) local channel = sender.chat_system_current_channel if (string.find(sender.chat_system_current_channel, '/_spatial_') ~= nil) then chat_system_send_to_subscribers_radial(self, sender, channel, processed_message, 'chat') else chat_system_send_to_subscribers(self, sender, channel, processed_message, 'chat') end chat_system_set_callback(sender, CHAT_STATUS_CODES.OK, 'OK') end -- /who: See online users -- Default is in the same place; optionally specify "/who all" to see all users online Trigger chat_msg_who(sender, message) local w = GetWorld() local channel = chat_system_channel_name('user', sender.name) if (self.chat_system_userlist_who ~= 1) then chat_system_set_callback(sender, CHAT_STATUS_CODES.DENIED, 'User listing is disabled') return end local mode = 'place' if ( message == 'all' ) then mode = 'world' end local user_list = {} local who = '- Users Online' local pre = '' local users_table = {} if (mode == 'place') then who = who .. ' (this place)
' local user = nil -- NOTE: Flaky behavior from GetUsersInPlace(); poll world and compare instead 20080715 PF for _, user in ipairs(w.users) do if (user.place == sender.place) then local user_afk = '' if (self.chat_system_userlist_who_afk == 1) then if ( (user.chat_system_tell_afk ~= '') and (user.chat_system_tell_afk ~= nil) ) then user_afk = ' (' .. user.chat_system_tell_afk .. ')' end end table.insert(users_table, user.name .. user_afk) end end else -- (mode == 'world') who = who .. ' (all)
' local user = nil for _, user in ipairs(w.users) do local user_afk = '' if (self.chat_system_userlist_who_afk == 1) then if ( (user.chat_system_tell_afk ~= '') and (user.chat_system_tell_afk ~= nil) ) then user_afk = ' (' .. user.chat_system_tell_afk .. ')' end end local user_place = " \038lt;here\038gt;" if (user.place ~= sender.place) then user_place = " \038lt;world\038gt;" end table.insert(users_table, user.name .. user_place .. user_afk) end end table.sort(users_table) local user_text = '' for _, user_text in ipairs(users_table) do who = who .. pre .. user_text if (#users_table < self.chat_system_userlist_single_max) then pre = '
' else pre = ', ' end end who = who .. '
- ' .. #users_table .. ' users found' chat_system_send_to_subscribers(self, sender, channel, chat_system_format_system_message(who), 'who') chat_system_set_callback(sender, CHAT_STATUS_CODES.OK, 'OK') end -- /chat (typically not called in this fashion; this is the "default" command for a user) -- Standard chat. Sends message to sender's current broadcast channel Trigger chat_msg_chat(sender, message) 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 -- Silently ignore muted users -- This has to be implemented here because it's possible to bypass parse_command if (sender.chat_system_mute == 1) then return end local processed_message = sender.name .. ': ' .. chat_system_strip_html(clean_message) local channel = sender.chat_system_current_channel if (string.find(sender.chat_system_current_channel, '/_spatial_') ~= nil) then chat_system_send_to_subscribers_radial(self, sender, channel, processed_message, 'chat') else chat_system_send_to_subscribers(self, sender, channel, processed_message, 'chat') end chat_system_set_callback(sender, CHAT_STATUS_CODES.OK, 'OK') end -- /autochat (unusable by player objects) -- Chat with no input processing, for use by things like bots. Sends message to sender's current broadcast channel Trigger chat_msg_autochat(sender, message) if (sender.type == 'player') then return end 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 -- Silently ignore muted users -- This has to be implemented here because it's possible to bypass parse_command if (sender.chat_system_mute == 1) then return end local processed_message = sender.name .. ': ' .. clean_message local channel = sender.chat_system_current_channel if (string.find(sender.chat_system_current_channel, '/_spatial_') ~= nil) then chat_system_send_to_subscribers_radial(self, sender, channel, processed_message, 'autochat') else chat_system_send_to_subscribers(self, sender, channel, processed_message, 'autochat') end chat_system_set_callback(sender, CHAT_STATUS_CODES.OK, 'OK') end -- /emote: Perform emote -- Sends emote to sender's current broadcast channel. (/me emotes about something. == User emotes about something.) Trigger chat_msg_emote(sender, message) local processed_message = sender.name .. ' ' .. chat_system_strip_html(message) local channel = sender.chat_system_current_channel chat_system_send_to_subscribers(self, sender, channel, processed_message, 'emote') chat_system_set_callback(sender, CHAT_STATUS_CODES.OK, 'OK') end -- /fontsize : Set font size (range: 8 - 18) -- Sets user's font size. All messages are formatted to this size going forward Trigger chat_msg_fontsize(sender, message, size) local channel = chat_system_channel_name('user', sender.name) local font_message = '' if (self.chat_system_player_size == 1) then local font_size = tonumber(size) if (font_size == nil) then font_size = 10 end if (font_size < 8) then font_size = 8 end if (font_size > 18) then font_size = 18 end sender.chat_system_font_size = font_size SaveToDb(sender) font_message = chat_system_format_system_message('Font size is now ' .. font_size) else font_message = chat_system_format_system_message('Changing font size is not allowed.') end chat_system_send_to_subscribers(self, sender, channel, font_message, 'fontsize') chat_system_set_callback(sender, CHAT_STATUS_CODES.OK, 'OK') end -- /think: Perform "Think" emote -- Sends "think" emote to sender's current broadcast channel. (/think this is nifty! == User thinks this is nifty!) Trigger chat_msg_think(sender, message) local processed_message = sender.name .. ' thinks ' .. chat_system_strip_html(message) local channel = sender.chat_system_current_channel chat_system_send_to_subscribers(self, sender, channel, processed_message, 'think') chat_system_set_callback(sender, CHAT_STATUS_CODES.OK, 'OK') end -- /help: Sends list of valid registered commands to a user. -- This includes only sending superuser commands to superusers! Trigger chat_msg_help(sender) local message = chat_system_format_system_message('- Available commands:
') local pre = '' local channel = chat_system_channel_name('user', sender.name) local command_name = '' local command_data = '' local super_user = 0 if (sender.type == 'player') then if (IsSuperuser(sender) == 1) then super_user = 1 end end local valid_command_list = {} for command_name, command_data in pairs(self.chat_system_commands) do if ( (super_user == 1) or (command_data.superuser == 0) ) then table.insert(valid_command_list, command_name) end end table.sort(valid_command_list) for _, command_name in ipairs(valid_command_list) do command_data = self.chat_system_commands[command_name] message = message .. pre .. chat_system_format_system_message('/' .. command_name .. ' -- ' .. command_data.help_text) pre = '
' end chat_system_send_to_subscribers(self, sender, channel, message, 'help') chat_system_set_callback(sender, CHAT_STATUS_CODES.OK, 'OK') end -- /ignore: Allows a user to ignore a given user (doesn't work on superusers) Trigger chat_msg_ignore(sender, message, username) SendTo(sender, 'chat_system_ignore', 0, username) chat_system_set_callback(sender, CHAT_STATUS_CODES.OK, 'OK') end -- /ignorelist: List a user's ignored users Trigger chat_msg_ignorelist(sender) SendTo(sender, 'chat_system_ignorelist', 0, username) chat_system_set_callback(sender, CHAT_STATUS_CODES.OK, 'OK') end -- /unignore: Unignore a given user Trigger chat_msg_unignore(sender, message, username) SendTo(sender, 'chat_system_unignore', 0, username) chat_system_set_callback(sender, CHAT_STATUS_CODES.OK, 'OK') end -- /mute -- SU command: Mute a user (renders user unable to issue ANY commands) -- Doesn't work on superusers Trigger chat_msg_mute(sender, message, username) local user = nil local target_user = nil 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 message = chat_system_format_system_message(username .. ' has been muted and may not issue chat commands. This will persist until you /unmute them!') SendTo(target_user, 'chat_system_mute', 0) SendTo(self, 'chat_system_send_message', 0, sender, message, channel, sender, 'mute') else local message = chat_system_format_system_message(username .. ' is not online and may not be muted.') SendTo(self, 'chat_system_send_message', 0, sender, message, channel, sender, 'mute') end chat_system_set_callback(sender, CHAT_STATUS_CODES.OK, 'OK') end -- /unmute -- SU command: Unmute a user (renders user able to issue commands) Trigger chat_msg_unmute(sender, message, username) local user = nil local target_user = nil 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 message = chat_system_format_system_message(username .. ' has been unmuted and may issue chat commands.') SendTo(target_user, 'chat_system_unmute', 0) SendTo(self, 'chat_system_send_message', 0, sender, message, channel, sender, 'unmute') else local message = chat_system_format_system_message(username .. ' is not online and may not be unmuted.') SendTo(self, 'chat_system_send_message', 0, sender, message, channel, sender, 'unmute') end chat_system_set_callback(sender, CHAT_STATUS_CODES.OK, 'OK') end -- /syslocal -- SU command: Broadcast a system message to the Place Trigger chat_msg_syslocal(sender, message) local super_user = 0 if (sender.type == 'player') then if (IsSuperuser(sender) == 1) then super_user = 1 end end if (super_user ~= 1) then return end local processed_message = '*SYSTEM (local)* ' .. sender.name .. ': ' .. chat_system_strip_html(message) if (sender.place.name == nil) then return -- if we can't determine the place, abort end local channel = chat_system_channel_name('place', sender.place.name) chat_system_send_to_subscribers(self, sender, channel, processed_message, 'syslocal') chat_system_set_callback(sender, CHAT_STATUS_CODES.OK, 'OK') end -- /sysglobal -- SU command: Broadcast a system message to the World Trigger chat_msg_sysglobal(sender, message) local super_user = 0 if (sender.type == 'player') then if (IsSuperuser(sender) == 1) then super_user = 1 end end if (super_user ~= 1) then return end local processed_message = '*SYSTEM* ' .. sender.name .. ': ' .. chat_system_strip_html(message) local channel = chat_system_channel_name('world') chat_system_send_to_subscribers(self, sender, channel, processed_message, 'sysglobal') chat_system_set_callback(sender, CHAT_STATUS_CODES.OK, 'OK') end -- /roll: Roll a random number. -- Default is 1 to 100; optionally specify a number between 1 and 1,000,000 Trigger chat_msg_roll(sender, message) local maximum = 100 local arguments = {} if ( (message ~= nil) and (message ~= '') ) then local num = tonumber(message) if (num ~= nil) then num = math.floor(num) if (num < 1) then maximum = 1 elseif (num > 1000000) then maximum = 1000000 else maximum = num end end end local roll = math.random(maximum) local message = '(ROLL) ' .. sender.name .. ' rolls ' .. roll .. ' (of ' .. maximum .. ')' local channel = sender.chat_system_current_channel chat_system_send_to_subscribers(self, sender, channel, message, 'roll') chat_system_set_callback(sender, CHAT_STATUS_CODES.OK, 'OK') end -- Subscribe an entity (implementing chat_entity script, but not necessarily a user) to a channel -- The entity will receive messages on that channel -- (Radial chat messages may not reach the entity if it is out of range or non-physical) Trigger chat_system_subscribe_channel(subscriber, channel) local data = subscriber.chat_system_callback_data chat_system_set_callback(subscriber, CHAT_STATUS_CODES.OK, 'OK') if (self.chat_system_subscriptions[channel] == nil) then self.chat_system_subscriptions[channel] = { subscriber } if (data ~= nil) then chat_system_set_callback(subscriber, CHAT_STATUS_CODES.OK, 'New channel') end else local found = 0 local endpoint = nil for _, endpoint in ipairs(self.chat_system_subscriptions[channel]) do if endpoint == subscriber then found = 1 break end end if (found == 0) then table.insert(self.chat_system_subscriptions[channel], subscriber) end if (self.chat_system_announce_ee_spatial == 1) then if (string.find(channel, '_spatial_') ~= nil) then chat_system_send_to_subscribers(self, subscriber, channel, chat_system_format_system_message(subscriber.name .. ' has entered.'), 'spatial_announce') end end end SendTo(subscriber, 'chat_system_subscribed', 0, channel) end -- Unsubscribe an entity from a channel -- The entity will no longer receive messages on that channel Trigger chat_system_unsubscribe_channel(subscriber, channel) local data = subscriber.chat_system_callback_data chat_system_set_callback(subscriber, CHAT_STATUS_CODES.CHANNEL_NOT_FOUND, 'Channel ' .. channel .. ' not found') if (self.chat_system_subscriptions[channel] ~= nil) then local i = 0 local endpoint = nil for i, endpoint in ipairs(self.chat_system_subscriptions[channel]) do if endpoint == subscriber then if (self.chat_system_announce_ee_spatial == 1) then if (string.find(channel, '_spatial_') ~= nil) then chat_system_send_to_subscribers(self, subscriber, channel, chat_system_format_system_message(subscriber.name .. ' has left.'), 'spatial_announce') end end table.remove(self.chat_system_subscriptions[channel], i) break end end end SendTo(subscriber, 'chat_system_unsubscribed', 0, channel) end -- Unsubscribe an entity from all channels -- (Typically used upon user left or object destroyed) Trigger chat_system_unsubscribe_all(subscriber) local name = '' local channel = {} local i = 0 local j = 0 for name, channel in pairs(self.chat_system_subscriptions) do local endpoint = nil local newchannel = {} for _, endpoint in ipairs(channel) do if (endpoint ~= subscriber) then table.insert(newchannel, endpoint) else if (self.chat_system_announce_ee_spatial == 1) then if (string.find(name, '_spatial_') ~= nil) then if (subscriber.name ~= nil) then chat_system_send_to_subscribers(self, subscriber, name, chat_system_format_system_message(subscriber.name .. ' has left.'), 'spatial_announce') end end end end end self.chat_system_subscriptions[name] = newchannel end end -- Send a finished, prepared message to a single entity -- This sends the message_pre, message, and message_finalize triggers -- This is used to route all "output" -- channel and command are for reference only; command has been processed at this point! Trigger chat_system_send_message(endpoint, message, channel, sender, command) if ( (endpoint == nil) or (endpoint.id == nil) or (type(endpoint) ~= 'userdata') ) then -- Invalid endpoint detected SendTo(self, 'chat_system_unsubscribe_all', 0, endpoint) return end if (self.chat_system_channel_debug == 1) then message = '' .. channel .. ' ' .. message end SendTo(endpoint, 'chat_system_message_pre', 0, message, channel, sender, command) SendTo(endpoint, 'chat_system_message', 0, message, channel, sender, command) SendTo(endpoint, 'chat_system_message_finalize', 0, message, channel, sender, command) end -- Local functions function chat_system_version_check(self) if (self.chat_system_current_version ~= CURRENT_VERSION.VERSION) then local property = '' local value = '' for property, value in pairs(CURRENT_VERSION.VALUES) do if (self[property] ~= nil) then self[property] = value end end self.chat_system_current_version = CURRENT_VERSION.VERSION end end