From e6a783c517befd89dc9aae75b84cfc13c753b1a7 Mon Sep 17 00:00:00 2001 From: Bohicatv Date: Tue, 3 Mar 2026 11:06:45 -0800 Subject: [PATCH 01/15] feat: Adds discord integration --- Projects/UOContent/Engines/Chat/Channel.cs | 12 ++--- Projects/UOContent/Engines/Chat/Chat.cs | 1 + .../Engines/Chat/ChatActionHandler.cs | 2 - .../Engines/Chat/ChatActionHandlers.cs | 6 ++- Projects/UOContent/Engines/Chat/ChatUser.cs | 11 +---- Projects/UOContent/Engines/Chat/Discord.cs | 47 +++++++++++++++++++ 6 files changed, 57 insertions(+), 22 deletions(-) create mode 100644 Projects/UOContent/Engines/Chat/Discord.cs diff --git a/Projects/UOContent/Engines/Chat/Channel.cs b/Projects/UOContent/Engines/Chat/Channel.cs index 09b8f6cb3..939921522 100644 --- a/Projects/UOContent/Engines/Chat/Channel.cs +++ b/Projects/UOContent/Engines/Chat/Channel.cs @@ -63,19 +63,12 @@ namespace Server.Engines.Chat } public bool AlwaysAvailable { get; set; } - public static List Channels { get; } = new(); - public bool Contains(ChatUser user) => m_Users.Contains(user); - public bool IsBanned(ChatUser user) => m_Banned.Contains(user); - public bool CanTalk(ChatUser user) => !m_VoiceRestricted || m_Voices.Contains(user) || m_Moderators.Contains(user); - public bool IsModerator(ChatUser user) => m_Moderators.Contains(user); - public bool IsVoiced(ChatUser user) => m_Voices.Contains(user); - public bool ValidatePassword(string password) => m_Password?.InsensitiveEquals(password) != false; public bool ValidateModerator(ChatUser user) @@ -159,7 +152,7 @@ namespace Server.Engines.Chat SendCommand(ChatCommand.RemoveUserFromChannel, user, user.Username); ChatSystem.SendCommandTo(user.Mobile, ChatCommand.LeaveChannel); - if (m_Users.Count == 0 && !AlwaysAvailable) + if (!AlwaysAvailable && m_Users.Count == 0) { RemoveChannel(this); } @@ -478,6 +471,9 @@ namespace Server.Engines.Chat return null; } + // currently, all chat is combined to a single webhook + // all static channels will be captured + // also captures player created channels public static void Initialize() { AddStaticChannel("Newbie Help"); diff --git a/Projects/UOContent/Engines/Chat/Chat.cs b/Projects/UOContent/Engines/Chat/Chat.cs index 22452231b..cf11f4e51 100644 --- a/Projects/UOContent/Engines/Chat/Chat.cs +++ b/Projects/UOContent/Engines/Chat/Chat.cs @@ -7,6 +7,7 @@ namespace Server.Engines.Chat public static void Configure() { Enabled = ServerConfiguration.GetOrUpdateSetting("chat.enabled", false); + Discord.Configure(); } public static void SendCommandTo(Mobile to, ChatCommand type, string param1 = null, string param2 = null) diff --git a/Projects/UOContent/Engines/Chat/ChatActionHandler.cs b/Projects/UOContent/Engines/Chat/ChatActionHandler.cs index 897fbaae6..aeee563eb 100644 --- a/Projects/UOContent/Engines/Chat/ChatActionHandler.cs +++ b/Projects/UOContent/Engines/Chat/ChatActionHandler.cs @@ -12,9 +12,7 @@ namespace Server.Engines.Chat } public bool RequireModerator { get; } - public bool RequireConference { get; } - public OnChatAction Callback { get; } } } diff --git a/Projects/UOContent/Engines/Chat/ChatActionHandlers.cs b/Projects/UOContent/Engines/Chat/ChatActionHandlers.cs index 309f59af0..e0bcb9ddc 100644 --- a/Projects/UOContent/Engines/Chat/ChatActionHandlers.cs +++ b/Projects/UOContent/Engines/Chat/ChatActionHandlers.cs @@ -9,9 +9,7 @@ namespace Server.Engines.Chat m_Handlers = new ChatActionHandler[0x100]; Register(0x41, true, true, ChangeChannelPassword); - Register(0x58, false, false, LeaveChat); - Register(0x61, false, true, ChannelMessage); Register(0x62, false, false, JoinChannel); Register(0x63, false, false, JoinNewChannel); @@ -63,6 +61,8 @@ namespace Server.Engines.Chat if (channel.CanTalk(from)) { channel.SendIgnorableMessage(57, from, from.GetColorCharacter() + from.Username, param); // %1: %2 + + _ = Discord.SendChannelMessageAsync(channel.Name, from.Username, param); } else { @@ -75,6 +75,8 @@ namespace Server.Engines.Chat if (channel.CanTalk(from)) { channel.SendIgnorableMessage(58, from, from.GetColorCharacter() + from.Username, param); // %1 %2 + + _ = Discord.SendChannelMessageAsync(channel.Name, from.Username, param); } else { diff --git a/Projects/UOContent/Engines/Chat/ChatUser.cs b/Projects/UOContent/Engines/Chat/ChatUser.cs index 79ae3d91f..9d1fbe7b7 100644 --- a/Projects/UOContent/Engines/Chat/ChatUser.cs +++ b/Projects/UOContent/Engines/Chat/ChatUser.cs @@ -20,25 +20,16 @@ namespace Server.Engines.Chat } public Mobile Mobile { get; } - public List Ignored { get; } - public List Ignoring { get; } - public string Username { get; } - public Channel CurrentChannel { get; set; } - public bool IsOnline => Mobile.NetState != null; - public bool Anonymous { get; set; } - public bool IgnorePrivateMessage { get; set; } - public bool IsModerator => CurrentChannel?.IsModerator(this) == true; - public char GetColorCharacter() => - IsModerator ? ModeratorColorCharacter : + public char GetColorCharacter() => IsModerator ? ModeratorColorCharacter : CurrentChannel?.IsVoiced(this) == true ? VoicedColorCharacter : NormalColorCharacter; public bool CheckOnline() diff --git a/Projects/UOContent/Engines/Chat/Discord.cs b/Projects/UOContent/Engines/Chat/Discord.cs new file mode 100644 index 000000000..d01b736ff --- /dev/null +++ b/Projects/UOContent/Engines/Chat/Discord.cs @@ -0,0 +1,47 @@ +using System; +using System.Net.Http; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; + +namespace Server.Engines.Chat +{ + public static class Discord + { + private static readonly HttpClient _httpClient = new(); + private static string _webhookUrl; + + public static void Configure() + { + _webhookUrl = ServerConfiguration.GetOrUpdateSetting("chatiscord.webhookUrl", "DISCORD_CHANNEL_WEBHOOK_HERE"); + } + + public static bool IsEnabled => !string.IsNullOrEmpty(_webhookUrl); + + public static async Task SendChannelMessageAsync(string channelName, string username, string message) + { + if (!IsEnabled) + { + return; + } + + try + { + var payload = new + { + username = $"UO Chat", + content = $"**[{channelName}]** {username}: {message}", + }; + + var json = JsonSerializer.Serialize(payload); + var content = new StringContent(json, Encoding.UTF8, "application/json"); + + await _httpClient.PostAsync(_webhookUrl, content); + } + catch (Exception ex) + { + Console.WriteLine($"Discord webhook error: {ex.Message}"); + } + } + } +} \ No newline at end of file From 5fde60df95795a0109049857a53321d195185782 Mon Sep 17 00:00:00 2001 From: Bohicatv Date: Tue, 3 Mar 2026 11:07:59 -0800 Subject: [PATCH 02/15] fixes memory leak --- Projects/UOContent/Engines/Chat/Channel.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Projects/UOContent/Engines/Chat/Channel.cs b/Projects/UOContent/Engines/Chat/Channel.cs index 939921522..011b506de 100644 --- a/Projects/UOContent/Engines/Chat/Channel.cs +++ b/Projects/UOContent/Engines/Chat/Channel.cs @@ -361,8 +361,9 @@ namespace Server.Engines.Chat { user.SendMessage(number, from.Mobile, param1, param2); } - else if (!Contains(user)) + else { + RemoveUser(user); --i; } } @@ -388,8 +389,9 @@ namespace Server.Engines.Chat { ChatSystem.SendCommandTo(user.Mobile, command, param1, param2); } - else if (!Contains(user)) + else { + RemoveUser(user); --i; } } From 38249c08cda310cb6dffb3dab600c03972cd80ea Mon Sep 17 00:00:00 2001 From: Bohicatv Date: Tue, 3 Mar 2026 11:10:49 -0800 Subject: [PATCH 03/15] cleanup --- Projects/UOContent/Engines/Chat/Discord.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Projects/UOContent/Engines/Chat/Discord.cs b/Projects/UOContent/Engines/Chat/Discord.cs index d01b736ff..07e31d4b8 100644 --- a/Projects/UOContent/Engines/Chat/Discord.cs +++ b/Projects/UOContent/Engines/Chat/Discord.cs @@ -13,7 +13,7 @@ namespace Server.Engines.Chat public static void Configure() { - _webhookUrl = ServerConfiguration.GetOrUpdateSetting("chatiscord.webhookUrl", "DISCORD_CHANNEL_WEBHOOK_HERE"); + _webhookUrl = ServerConfiguration.GetOrUpdateSetting("chatdiscord.webhookUrl", "DISCORD_CHANNEL_WEBHOOK_HERE"); } public static bool IsEnabled => !string.IsNullOrEmpty(_webhookUrl); From 36ec1a22168f8909d70ee338210ef7373c4e252f Mon Sep 17 00:00:00 2001 From: Bohica <53943479+Bohicatv@users.noreply.github.com> Date: Tue, 3 Mar 2026 11:12:05 -0800 Subject: [PATCH 04/15] cleanup --- Projects/UOContent/Engines/Chat/Discord.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Projects/UOContent/Engines/Chat/Discord.cs b/Projects/UOContent/Engines/Chat/Discord.cs index d01b736ff..b8fe50d84 100644 --- a/Projects/UOContent/Engines/Chat/Discord.cs +++ b/Projects/UOContent/Engines/Chat/Discord.cs @@ -13,7 +13,7 @@ namespace Server.Engines.Chat public static void Configure() { - _webhookUrl = ServerConfiguration.GetOrUpdateSetting("chatiscord.webhookUrl", "DISCORD_CHANNEL_WEBHOOK_HERE"); + _webhookUrl = ServerConfiguration.GetOrUpdateSetting("chatdiscord.webhookUrl", "DISCORD_CHANNEL_WEBHOOK_HERE"); } public static bool IsEnabled => !string.IsNullOrEmpty(_webhookUrl); @@ -44,4 +44,4 @@ namespace Server.Engines.Chat } } } -} \ No newline at end of file +} From 0ed063d81fdfe45e8ee80bd84405f37508060704 Mon Sep 17 00:00:00 2001 From: Bohicatv Date: Thu, 5 Mar 2026 02:21:55 -0800 Subject: [PATCH 05/15] simplify --- Projects/UOContent/Engines/Chat/Channel.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Projects/UOContent/Engines/Chat/Channel.cs b/Projects/UOContent/Engines/Chat/Channel.cs index 011b506de..45bccf501 100644 --- a/Projects/UOContent/Engines/Chat/Channel.cs +++ b/Projects/UOContent/Engines/Chat/Channel.cs @@ -348,7 +348,7 @@ namespace Server.Engines.Chat public void SendIgnorableMessage(int number, ChatUser from, string param1, string param2) { - for (var i = 0; i < m_Users.Count; ++i) + for (var i = m_Users.Count - 1; i >= 0; --i) { var user = m_Users[i]; @@ -364,7 +364,6 @@ namespace Server.Engines.Chat else { RemoveUser(user); - --i; } } } @@ -376,7 +375,7 @@ namespace Server.Engines.Chat public void SendCommand(ChatCommand command, ChatUser initiator, string param1 = null, string param2 = null) { - for (var i = 0; i < m_Users.Count; ++i) + for (var i = m_Users.Count - 1; i >= 0; --i) { var user = m_Users[i]; @@ -392,7 +391,6 @@ namespace Server.Engines.Chat else { RemoveUser(user); - --i; } } } From b750a0c66f9f2dcf09a2060dbbba57ab7e10d136 Mon Sep 17 00:00:00 2001 From: Bohicatv Date: Thu, 5 Mar 2026 02:22:54 -0800 Subject: [PATCH 06/15] cleanup --- Projects/UOContent/Engines/Chat/Discord.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Projects/UOContent/Engines/Chat/Discord.cs b/Projects/UOContent/Engines/Chat/Discord.cs index b8fe50d84..95e1be9e9 100644 --- a/Projects/UOContent/Engines/Chat/Discord.cs +++ b/Projects/UOContent/Engines/Chat/Discord.cs @@ -13,7 +13,7 @@ namespace Server.Engines.Chat public static void Configure() { - _webhookUrl = ServerConfiguration.GetOrUpdateSetting("chatdiscord.webhookUrl", "DISCORD_CHANNEL_WEBHOOK_HERE"); + _webhookUrl = ServerConfiguration.GetOrUpdateSetting("chatdiscord.webhookUrl", "DISCORD_CHANNEL_WEBHOOK"); } public static bool IsEnabled => !string.IsNullOrEmpty(_webhookUrl); @@ -40,7 +40,7 @@ namespace Server.Engines.Chat } catch (Exception ex) { - Console.WriteLine($"Discord webhook error: {ex.Message}"); + Console.WriteLine($"Discord webhook error for channel [{channelName}] {username}: {ex.Message}"); } } } From bd410c500d3695363b9cb24a1c9f4fe2e1fa7000 Mon Sep 17 00:00:00 2001 From: Bohicatv Date: Thu, 5 Mar 2026 14:27:29 -0800 Subject: [PATCH 07/15] adds ILogger --- Projects/UOContent/Engines/Chat/Discord.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Projects/UOContent/Engines/Chat/Discord.cs b/Projects/UOContent/Engines/Chat/Discord.cs index 95e1be9e9..d86990f42 100644 --- a/Projects/UOContent/Engines/Chat/Discord.cs +++ b/Projects/UOContent/Engines/Chat/Discord.cs @@ -3,17 +3,20 @@ using System.Net.Http; using System.Text; using System.Text.Json; using System.Threading.Tasks; +using Server.Logging; namespace Server.Engines.Chat { public static class Discord { private static readonly HttpClient _httpClient = new(); + private static readonly ILogger _logger = LogFactory.GetLogger(typeof(Discord)); private static string _webhookUrl; public static void Configure() { _webhookUrl = ServerConfiguration.GetOrUpdateSetting("chatdiscord.webhookUrl", "DISCORD_CHANNEL_WEBHOOK"); + _logger.Information("Discord integration configured. Enabled: {Enabled}", IsEnabled); } public static bool IsEnabled => !string.IsNullOrEmpty(_webhookUrl); @@ -37,10 +40,12 @@ namespace Server.Engines.Chat var content = new StringContent(json, Encoding.UTF8, "application/json"); await _httpClient.PostAsync(_webhookUrl, content); + + _logger.Debug("Discord message sent for channel {ChannelName} user {Username}", channelName, username); } catch (Exception ex) { - Console.WriteLine($"Discord webhook error for channel [{channelName}] {username}: {ex.Message}"); + _logger.Error(ex, "Discord webhook failed for channel {ChannelName} user {Username}", channelName, username); } } } From 8a54a8e6b165a5e99caf57b82660031394bc1f86 Mon Sep 17 00:00:00 2001 From: Bohicatv Date: Sun, 15 Mar 2026 00:40:03 -0700 Subject: [PATCH 08/15] fix: Prevents static chat channels from disappearing when logging back in --- Projects/UOContent/Engines/Chat/ChatUser.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Projects/UOContent/Engines/Chat/ChatUser.cs b/Projects/UOContent/Engines/Chat/ChatUser.cs index 9d1fbe7b7..d27b7d8a1 100644 --- a/Projects/UOContent/Engines/Chat/ChatUser.cs +++ b/Projects/UOContent/Engines/Chat/ChatUser.cs @@ -92,6 +92,7 @@ namespace Server.Engines.Chat if (user != null) { + Channel.SendChannelsTo(user); return user; } From 35ffb0e5cb2f46f2b1c71556c313e1aba244ae19 Mon Sep 17 00:00:00 2001 From: Bohicatv Date: Tue, 17 Mar 2026 19:10:10 -0700 Subject: [PATCH 09/15] fix prefix --- Projects/UOContent/Engines/Chat/Channel.cs | 118 +++++++++--------- .../Engines/Chat/ChatActionHandlers.cs | 12 +- Projects/UOContent/Engines/Chat/ChatUser.cs | 24 ++-- 3 files changed, 77 insertions(+), 77 deletions(-) diff --git a/Projects/UOContent/Engines/Chat/Channel.cs b/Projects/UOContent/Engines/Chat/Channel.cs index 45bccf501..b69d2eadc 100644 --- a/Projects/UOContent/Engines/Chat/Channel.cs +++ b/Projects/UOContent/Engines/Chat/Channel.cs @@ -4,50 +4,50 @@ namespace Server.Engines.Chat { public class Channel { - private readonly List m_Banned; - private readonly List m_Moderators; - private readonly List m_Users; - private readonly List m_Voices; - private string m_Name; - private string m_Password; - private bool m_VoiceRestricted; + private readonly List _Banned; + private readonly List _Moderators; + private readonly List _Users; + private readonly List _Voices; + private string _Name; + private string _Password; + private bool _VoiceRestricted; public Channel(string name) { - m_Name = name; + _Name = name; - m_Users = new List(); - m_Banned = new List(); - m_Moderators = new List(); - m_Voices = new List(); + _Users = new List(); + _Banned = new List(); + _Moderators = new List(); + _Voices = new List(); } - public Channel(string name, string password) : this(name) => m_Password = password; + public Channel(string name, string password) : this(name) => _Password = password; public string Name { - get => m_Name; + get => _Name; set { - SendCommand(ChatCommand.RemoveChannel, m_Name); - m_Name = value; - SendCommand(ChatCommand.AddChannel, m_Name); - SendCommand(ChatCommand.JoinedChannel, m_Name); + SendCommand(ChatCommand.RemoveChannel, _Name); + _Name = value; + SendCommand(ChatCommand.AddChannel, _Name); + SendCommand(ChatCommand.JoinedChannel, _Name); } } public string Password { - get => m_Password; - set => m_Password = (value?.Trim()).DefaultIfNullOrEmpty(null); + get => _Password; + set => _Password = (value?.Trim()).DefaultIfNullOrEmpty(null); } public bool VoiceRestricted { - get => m_VoiceRestricted; + get => _VoiceRestricted; set { - m_VoiceRestricted = value; + _VoiceRestricted = value; if (value) { @@ -64,12 +64,12 @@ namespace Server.Engines.Chat public bool AlwaysAvailable { get; set; } public static List Channels { get; } = new(); - public bool Contains(ChatUser user) => m_Users.Contains(user); - public bool IsBanned(ChatUser user) => m_Banned.Contains(user); - public bool CanTalk(ChatUser user) => !m_VoiceRestricted || m_Voices.Contains(user) || m_Moderators.Contains(user); - public bool IsModerator(ChatUser user) => m_Moderators.Contains(user); - public bool IsVoiced(ChatUser user) => m_Voices.Contains(user); - public bool ValidatePassword(string password) => m_Password?.InsensitiveEquals(password) != false; + public bool Contains(ChatUser user) => _Users.Contains(user); + public bool IsBanned(ChatUser user) => _Banned.Contains(user); + public bool CanTalk(ChatUser user) => !_VoiceRestricted || _Voices.Contains(user) || _Moderators.Contains(user); + public bool IsModerator(ChatUser user) => _Moderators.Contains(user); + public bool IsVoiced(ChatUser user) => _Voices.Contains(user); + public bool ValidatePassword(string password) => _Password?.InsensitiveEquals(password) != false; public bool ValidateModerator(ChatUser user) { @@ -97,7 +97,7 @@ namespace Server.Engines.Chat { if (Contains(user)) { - user.SendMessage(46, m_Name); // You are already in the conference '%1'. + user.SendMessage(46, _Name); // You are already in the conference '%1'. return true; } @@ -115,14 +115,14 @@ namespace Server.Engines.Chat user.CurrentChannel?.RemoveUser(user); // Remove them from their current channel first - ChatSystem.SendCommandTo(user.Mobile, ChatCommand.JoinedChannel, m_Name); + ChatSystem.SendCommandTo(user.Mobile, ChatCommand.JoinedChannel, _Name); SendCommand(ChatCommand.AddUserToChannel, user.GetColorCharacter() + user.Username); - m_Users.Add(user); + _Users.Add(user); user.CurrentChannel = this; - if (user.Mobile.AccessLevel >= AccessLevel.GameMaster || !AlwaysAvailable && m_Users.Count == 1) + if (user.Mobile.AccessLevel >= AccessLevel.GameMaster || !AlwaysAvailable && _Users.Count == 1) { AddModerator(user); } @@ -136,23 +136,23 @@ namespace Server.Engines.Chat { if (Contains(user)) { - m_Users.Remove(user); + _Users.Remove(user); user.CurrentChannel = null; - if (m_Moderators.Contains(user)) + if (_Moderators.Contains(user)) { - m_Moderators.Remove(user); + _Moderators.Remove(user); } - if (m_Voices.Contains(user)) + if (_Voices.Contains(user)) { - m_Voices.Remove(user); + _Voices.Remove(user); } SendCommand(ChatCommand.RemoveUserFromChannel, user, user.Username); ChatSystem.SendCommandTo(user.Mobile, ChatCommand.LeaveChannel); - if (!AlwaysAvailable && m_Users.Count == 0) + if (!AlwaysAvailable && _Users.Count == 0) { RemoveChannel(this); } @@ -166,9 +166,9 @@ namespace Server.Engines.Chat return; } - if (!m_Banned.Contains(user)) + if (!_Banned.Contains(user)) { - m_Banned.Add(user); + _Banned.Add(user); } Kick(user, moderator, true); @@ -176,9 +176,9 @@ namespace Server.Engines.Chat public void RemoveBan(ChatUser user) { - if (m_Banned.Contains(user)) + if (_Banned.Contains(user)) { - m_Banned.Remove(user); + _Banned.Remove(user); } } @@ -235,7 +235,7 @@ namespace Server.Engines.Chat if (!IsBanned(user) && !IsModerator(user) && !IsVoiced(user)) { - m_Voices.Add(user); + _Voices.Add(user); if (moderator != null) { @@ -257,7 +257,7 @@ namespace Server.Engines.Chat if (!IsModerator(user) && IsVoiced(user)) { - m_Voices.Remove(user); + _Voices.Remove(user); if (moderator != null) { @@ -284,10 +284,10 @@ namespace Server.Engines.Chat if (IsVoiced(user)) { - m_Voices.Remove(user); + _Voices.Remove(user); } - m_Moderators.Add(user); + _Moderators.Add(user); if (moderator != null) { @@ -307,7 +307,7 @@ namespace Server.Engines.Chat if (IsModerator(user)) { - m_Moderators.Remove(user); + _Moderators.Remove(user); if (moderator != null) { @@ -326,9 +326,9 @@ namespace Server.Engines.Chat public void SendMessage(int number, ChatUser initiator, string param1 = null, string param2 = null) { - for (var i = 0; i < m_Users.Count; ++i) + for (var i = 0; i < _Users.Count; ++i) { - var user = m_Users[i]; + var user = _Users[i]; if (user == initiator) { @@ -348,9 +348,9 @@ namespace Server.Engines.Chat public void SendIgnorableMessage(int number, ChatUser from, string param1, string param2) { - for (var i = m_Users.Count - 1; i >= 0; --i) + for (var i = _Users.Count - 1; i >= 0; --i) { - var user = m_Users[i]; + var user = _Users[i]; if (user.IsIgnored(from)) { @@ -375,9 +375,9 @@ namespace Server.Engines.Chat public void SendCommand(ChatCommand command, ChatUser initiator, string param1 = null, string param2 = null) { - for (var i = m_Users.Count - 1; i >= 0; --i) + for (var i = _Users.Count - 1; i >= 0; --i) { - var user = m_Users[i]; + var user = _Users[i]; if (user == initiator) { @@ -397,9 +397,9 @@ namespace Server.Engines.Chat public void SendUsersTo(ChatUser to) { - for (var i = 0; i < m_Users.Count; ++i) + for (var i = 0; i < _Users.Count; ++i) { - var user = m_Users[i]; + var user = _Users[i]; ChatSystem.SendCommandTo(to.Mobile, ChatCommand.AddUserToChannel, user.GetColorCharacter() + user.Username); } @@ -445,12 +445,12 @@ namespace Server.Engines.Chat return; } - if (Channels.Contains(channel) && channel.m_Users.Count == 0) + if (Channels.Contains(channel) && channel._Users.Count == 0) { ChatUser.GlobalSendCommand(ChatCommand.RemoveChannel, channel.Name); - channel.m_Moderators.Clear(); - channel.m_Voices.Clear(); + channel._Moderators.Clear(); + channel._Voices.Clear(); Channels.Remove(channel); } @@ -462,7 +462,7 @@ namespace Server.Engines.Chat { var channel = Channels[i]; - if (channel.m_Name == name) + if (channel._Name == name) { return channel; } diff --git a/Projects/UOContent/Engines/Chat/ChatActionHandlers.cs b/Projects/UOContent/Engines/Chat/ChatActionHandlers.cs index e0bcb9ddc..33bd20e09 100644 --- a/Projects/UOContent/Engines/Chat/ChatActionHandlers.cs +++ b/Projects/UOContent/Engines/Chat/ChatActionHandlers.cs @@ -2,11 +2,11 @@ namespace Server.Engines.Chat { public static class ChatActionHandlers { - private static readonly ChatActionHandler[] m_Handlers; + private static readonly ChatActionHandler[] _Handlers; static ChatActionHandlers() { - m_Handlers = new ChatActionHandler[0x100]; + _Handlers = new ChatActionHandler[0x100]; Register(0x41, true, true, ChangeChannelPassword); Register(0x58, false, false, LeaveChat); @@ -40,17 +40,17 @@ namespace Server.Engines.Chat public static void Register(int actionID, bool requireModerator, bool requireConference, OnChatAction callback) { - if (actionID >= 0 && actionID < m_Handlers.Length) + if (actionID >= 0 && actionID < _Handlers.Length) { - m_Handlers[actionID] = new ChatActionHandler(requireModerator, requireConference, callback); + _Handlers[actionID] = new ChatActionHandler(requireModerator, requireConference, callback); } } public static ChatActionHandler GetHandler(int actionID) { - if (actionID >= 0 && actionID < m_Handlers.Length) + if (actionID >= 0 && actionID < _Handlers.Length) { - return m_Handlers[actionID]; + return _Handlers[actionID]; } return null; diff --git a/Projects/UOContent/Engines/Chat/ChatUser.cs b/Projects/UOContent/Engines/Chat/ChatUser.cs index d27b7d8a1..cb52b5772 100644 --- a/Projects/UOContent/Engines/Chat/ChatUser.cs +++ b/Projects/UOContent/Engines/Chat/ChatUser.cs @@ -8,8 +8,8 @@ namespace Server.Engines.Chat public const char ModeratorColorCharacter = '1'; public const char VoicedColorCharacter = '2'; - private static readonly List m_Users = new(); - private static readonly Dictionary m_Table = new(); + private static readonly List _Users = new(); + private static readonly Dictionary _Table = new(); public ChatUser(Mobile m, string username) { @@ -98,8 +98,8 @@ namespace Server.Engines.Chat user = new ChatUser(from, username); - m_Users.Add(user); - m_Table[from] = user; + _Users.Add(user); + _Table[from] = user; Channel.SendChannelsTo(user); @@ -115,7 +115,7 @@ namespace Server.Engines.Chat } } - // ChatSystem.SendCommandTo( user.m_Mobile, ChatCommand.AddUserToChannel, user.GetColorCharacter() + user.Username ); + // ChatSystem.SendCommandTo( user._Mobile, ChatCommand.AddUserToChannel, user.GetColorCharacter() + user.Username ); return user; } @@ -132,27 +132,27 @@ namespace Server.Engines.Chat user.Ignoring[i].RemoveIgnored(user); } - if (m_Users.Remove(user)) + if (_Users.Remove(user)) { ChatSystem.SendCommandTo(user.Mobile, ChatCommand.CloseChatWindow); user.CurrentChannel?.RemoveUser(user); - m_Table.Remove(user.Mobile); + _Table.Remove(user.Mobile); } } public static ChatUser GetChatUser(Mobile from) { - m_Table.TryGetValue(from, out var c); + _Table.TryGetValue(from, out var c); return c; } public static ChatUser GetChatUser(string username) { - for (var i = 0; i < m_Users.Count; ++i) + for (var i = 0; i < _Users.Count; ++i) { - var user = m_Users[i]; + var user = _Users[i]; if (user.Username == username) { @@ -172,9 +172,9 @@ namespace Server.Engines.Chat ChatCommand command, ChatUser initiator = null, string param1 = null, string param2 = null ) { - for (var i = 0; i < m_Users.Count; ++i) + for (var i = 0; i < _Users.Count; ++i) { - var user = m_Users[i]; + var user = _Users[i]; if (user == initiator) { From c618a141a41fcbf8f363d85b83f25e34386ab3a9 Mon Sep 17 00:00:00 2001 From: Bohicatv Date: Tue, 17 Mar 2026 19:12:17 -0700 Subject: [PATCH 10/15] adds logger --- Projects/UOContent/Engines/Chat/ChatPackets.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Projects/UOContent/Engines/Chat/ChatPackets.cs b/Projects/UOContent/Engines/Chat/ChatPackets.cs index b0fce023f..f330382b3 100644 --- a/Projects/UOContent/Engines/Chat/ChatPackets.cs +++ b/Projects/UOContent/Engines/Chat/ChatPackets.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2026 - ModernUO Development Team * + * Copyright 2019-2023 - ModernUO Development Team * * Email: hi@modernuo.com * * File: ChatPackets.cs * * * @@ -15,12 +15,15 @@ using System; using System.Buffers; +using Server.Logging; using Server.Network; namespace Server.Engines.Chat { public static class ChatPackets { + private static readonly ILogger logger = LogFactory.GetLogger(typeof(ChatPackets)); + public static unsafe void Configure() { IncomingPackets.Register(0xB5, 0x40, true, &OpenChatWindowRequest); @@ -97,7 +100,7 @@ namespace Server.Engines.Chat } catch (Exception e) { - Console.WriteLine(e); + logger.Error(e, "Exception in chat action handler"); } } From b3dd5a8eeee5d8b7dc8216d08ded0ca8ed70986f Mon Sep 17 00:00:00 2001 From: Bohicatv Date: Tue, 17 Mar 2026 19:16:58 -0700 Subject: [PATCH 11/15] adds pooledreflist for chat users --- Projects/UOContent/Engines/Chat/ChatUser.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/Projects/UOContent/Engines/Chat/ChatUser.cs b/Projects/UOContent/Engines/Chat/ChatUser.cs index cb52b5772..bd9b91950 100644 --- a/Projects/UOContent/Engines/Chat/ChatUser.cs +++ b/Projects/UOContent/Engines/Chat/ChatUser.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Server.Collections; namespace Server.Engines.Chat { @@ -127,9 +128,16 @@ namespace Server.Engines.Chat return; } + using var ignoringUsers = new PooledRefList(); + for (var i = 0; i < user.Ignoring.Count; ++i) { - user.Ignoring[i].RemoveIgnored(user); + ignoringUsers.Add(user.Ignoring[i]); + } + + for (var i = 0; i < ignoringUsers.Count; i++) + { + ignoringUsers[i].RemoveIgnored(user); } if (_Users.Remove(user)) @@ -172,6 +180,8 @@ namespace Server.Engines.Chat ChatCommand command, ChatUser initiator = null, string param1 = null, string param2 = null ) { + using var usersToProcess = new PooledRefList(); + for (var i = 0; i < _Users.Count; ++i) { var user = _Users[i]; @@ -183,9 +193,14 @@ namespace Server.Engines.Chat if (user.CheckOnline()) { - ChatSystem.SendCommandTo(user.Mobile, command, param1, param2); + usersToProcess.Add(user); } } + + for (var i = 0; i < usersToProcess.Count; i++) + { + ChatSystem.SendCommandTo(usersToProcess[i].Mobile, command, param1, param2); + } } } } From 94c1b01f3d7ffdc9b3ffe233026026a7380cafa0 Mon Sep 17 00:00:00 2001 From: Bohicatv Date: Tue, 17 Mar 2026 19:20:17 -0700 Subject: [PATCH 12/15] simplify --- Projects/UOContent/Engines/Chat/Channel.cs | 28 +++++++++++----------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Projects/UOContent/Engines/Chat/Channel.cs b/Projects/UOContent/Engines/Chat/Channel.cs index b69d2eadc..d91f1fbbe 100644 --- a/Projects/UOContent/Engines/Chat/Channel.cs +++ b/Projects/UOContent/Engines/Chat/Channel.cs @@ -326,23 +326,23 @@ namespace Server.Engines.Chat public void SendMessage(int number, ChatUser initiator, string param1 = null, string param2 = null) { - for (var i = 0; i < _Users.Count; ++i) + for (var i = _Users.Count - 1; i >= 0; --i) { - var user = _Users[i]; + var user = _Users[i]; - if (user == initiator) - { - continue; - } + if (user == initiator) + { + continue; + } - if (user.CheckOnline()) - { - user.SendMessage(number, param1, param2); - } - else if (!Contains(user)) - { - --i; - } + if (user.CheckOnline()) + { + user.SendMessage(number, param1, param2); + } + else + { + RemoveUser(user); + } } } From c0a36e5d72900804fe9c15eceb03c633d75274d0 Mon Sep 17 00:00:00 2001 From: Bohicatv Date: Tue, 17 Mar 2026 19:23:52 -0700 Subject: [PATCH 13/15] fixes race condition --- Projects/UOContent/Engines/Chat/Discord.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Projects/UOContent/Engines/Chat/Discord.cs b/Projects/UOContent/Engines/Chat/Discord.cs index d86990f42..39fb34056 100644 --- a/Projects/UOContent/Engines/Chat/Discord.cs +++ b/Projects/UOContent/Engines/Chat/Discord.cs @@ -23,6 +23,12 @@ namespace Server.Engines.Chat public static async Task SendChannelMessageAsync(string channelName, string username, string message) { + var webhookUrl = _webhookUrl; + if (string.IsNullOrEmpty(webhookUrl)) + { + return; + } + if (!IsEnabled) { return; From 17192bd7fbe18b7af7c133d9648963faffb98978 Mon Sep 17 00:00:00 2001 From: Bohicatv Date: Tue, 17 Mar 2026 19:32:39 -0700 Subject: [PATCH 14/15] cleanup --- Projects/UOContent/Engines/Chat/Discord.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Projects/UOContent/Engines/Chat/Discord.cs b/Projects/UOContent/Engines/Chat/Discord.cs index 39fb34056..f4784b735 100644 --- a/Projects/UOContent/Engines/Chat/Discord.cs +++ b/Projects/UOContent/Engines/Chat/Discord.cs @@ -29,11 +29,6 @@ namespace Server.Engines.Chat return; } - if (!IsEnabled) - { - return; - } - try { var payload = new From 57a800a05fb30a1b0f99ccd01937684b68987ea7 Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Sun, 14 Jun 2026 09:52:37 -0700 Subject: [PATCH 15/15] Apply suggestion from @kamronbatman --- Projects/UOContent/Engines/Chat/ChatPackets.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Projects/UOContent/Engines/Chat/ChatPackets.cs b/Projects/UOContent/Engines/Chat/ChatPackets.cs index f330382b3..4ab0cf455 100644 --- a/Projects/UOContent/Engines/Chat/ChatPackets.cs +++ b/Projects/UOContent/Engines/Chat/ChatPackets.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2023 - ModernUO Development Team * + * Copyright 2019-2026 - ModernUO Development Team * * Email: hi@modernuo.com * * File: ChatPackets.cs * * *