Fix ChatService dispose + Add chat configuration
This commit is contained in:
		| @@ -56,4 +56,8 @@ public class MareConfig : IMareConfiguration | |||||||
|     public NotificationLocation WarningNotification { get; set; } = NotificationLocation.Both; |     public NotificationLocation WarningNotification { get; set; } = NotificationLocation.Both; | ||||||
|  |  | ||||||
|     public bool DisableSyncshellChat { get; set; } = false; |     public bool DisableSyncshellChat { get; set; } = false; | ||||||
|  |     public int ChatColor { get; set; } = 0; // 0 means "use plugin default" | ||||||
|  |     public int ChatLogKind { get; set; } = 1; // XivChatType.Debug | ||||||
|  |     public bool ExtraChatAPI { get; set; } = false; | ||||||
|  |     public bool ExtraChatTags { get; set; } = false; | ||||||
| } | } | ||||||
| @@ -3,5 +3,8 @@ namespace MareSynchronos.MareConfiguration.Models; | |||||||
| [Serializable] | [Serializable] | ||||||
| public class ShellConfig | public class ShellConfig | ||||||
| { | { | ||||||
|  |     public bool Enabled { get; set; } = true; | ||||||
|     public int ShellNumber { get; set; } |     public int ShellNumber { get; set; } | ||||||
|  |     public int Color { get; set; } = 0; // 0 means "default to the global setting" | ||||||
|  |     public int LogKind { get; set; } = 0; // 0 means "default to the global setting" | ||||||
| } | } | ||||||
| @@ -5,9 +5,11 @@ using Dalamud.Plugin.Services; | |||||||
| using MareSynchronos.API.Data; | using MareSynchronos.API.Data; | ||||||
| using MareSynchronos.Interop; | using MareSynchronos.Interop; | ||||||
| using MareSynchronos.MareConfiguration; | using MareSynchronos.MareConfiguration; | ||||||
|  | using MareSynchronos.MareConfiguration.Models; | ||||||
| using MareSynchronos.PlayerData.Pairs; | using MareSynchronos.PlayerData.Pairs; | ||||||
| using MareSynchronos.Services.Mediator; | using MareSynchronos.Services.Mediator; | ||||||
| using MareSynchronos.Services.ServerConfiguration; | using MareSynchronos.Services.ServerConfiguration; | ||||||
|  | using MareSynchronos.Utils; | ||||||
| using MareSynchronos.WebAPI; | using MareSynchronos.WebAPI; | ||||||
| using Microsoft.Extensions.Logging; | using Microsoft.Extensions.Logging; | ||||||
|  |  | ||||||
| @@ -15,6 +17,9 @@ namespace MareSynchronos.Services; | |||||||
|  |  | ||||||
| public class ChatService : DisposableMediatorSubscriberBase | public class ChatService : DisposableMediatorSubscriberBase | ||||||
| { | { | ||||||
|  |     public const int DefaultColor = 710; | ||||||
|  |     public const int CommandMaxNumber = 50; | ||||||
|  |  | ||||||
|     private readonly ILogger<ChatService> _logger; |     private readonly ILogger<ChatService> _logger; | ||||||
|     private readonly IChatGui _chatGui; |     private readonly IChatGui _chatGui; | ||||||
|     private readonly DalamudUtilService _dalamudUtil; |     private readonly DalamudUtilService _dalamudUtil; | ||||||
| @@ -45,6 +50,7 @@ public class ChatService : DisposableMediatorSubscriberBase | |||||||
|  |  | ||||||
|     protected override void Dispose(bool disposing) |     protected override void Dispose(bool disposing) | ||||||
|     { |     { | ||||||
|  |         base.Dispose(disposing); | ||||||
|         if (_gameChatHooks.IsValueCreated) |         if (_gameChatHooks.IsValueCreated) | ||||||
|             _gameChatHooks.Value!.Dispose(); |             _gameChatHooks.Value!.Dispose(); | ||||||
|     } |     } | ||||||
| @@ -61,28 +67,81 @@ public class ChatService : DisposableMediatorSubscriberBase | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private ushort ResolveShellColor(int shellColor) | ||||||
|  |     { | ||||||
|  |         if (shellColor != 0) | ||||||
|  |             return (ushort)shellColor; | ||||||
|  |         var globalColor = _mareConfig.Current.ChatColor; | ||||||
|  |         if (globalColor != 0) | ||||||
|  |             return (ushort)globalColor; | ||||||
|  |         return (ushort)DefaultColor; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private XivChatType ResolveShellLogKind(int shellLogKind) | ||||||
|  |     { | ||||||
|  |         if (shellLogKind != 0) | ||||||
|  |             return (XivChatType)shellLogKind; | ||||||
|  |         return (XivChatType)_mareConfig.Current.ChatLogKind; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     private void HandleGroupChat(GroupChatMsgMessage message) |     private void HandleGroupChat(GroupChatMsgMessage message) | ||||||
|     { |     { | ||||||
|         if (_mareConfig.Current.DisableSyncshellChat) |         if (_mareConfig.Current.DisableSyncshellChat) | ||||||
|             return; |             return; | ||||||
|  |  | ||||||
|         var chatMsg = message.ChatMsg; |         var chatMsg = message.ChatMsg; | ||||||
|         var shellNumber = _serverConfigurationManager.GetShellNumberForGid(message.GroupInfo.GID); |         var shellConfig = _serverConfigurationManager.GetShellConfigForGid(message.GroupInfo.GID); | ||||||
|  |         var shellNumber = shellConfig.ShellNumber; | ||||||
|  |  | ||||||
|  |         if (!shellConfig.Enabled) | ||||||
|  |             return; | ||||||
|  |  | ||||||
|  |         ushort color = ResolveShellColor(shellConfig.Color); | ||||||
|  |         var extraChatTags = _mareConfig.Current.ExtraChatTags; | ||||||
|  |         var logKind = ResolveShellLogKind(shellConfig.LogKind); | ||||||
|  |  | ||||||
|         var msg = new SeStringBuilder(); |         var msg = new SeStringBuilder(); | ||||||
|         // TODO: Configure colors and appearance |         if (extraChatTags) | ||||||
|         msg.AddUiForeground(710); |         { | ||||||
|  |             msg.Add(ChatUtils.CreateExtraChatTagPayload(message.GroupInfo.GID)); | ||||||
|  |             msg.Add(RawPayload.LinkTerminator); | ||||||
|  |         } | ||||||
|  |         if (color != 0) | ||||||
|  |             msg.AddUiForeground((ushort)color); | ||||||
|         msg.AddText($"[SS{shellNumber}]<"); |         msg.AddText($"[SS{shellNumber}]<"); | ||||||
|         // TODO: Don't link to the local player because it lets you do invalid things |         // TODO: Don't link to the local player because it lets you do invalid things | ||||||
|         msg.Add(new PlayerPayload(chatMsg.SenderName, (uint)chatMsg.SenderHomeWorldId)); |         msg.Add(new PlayerPayload(chatMsg.SenderName, chatMsg.SenderHomeWorldId)); | ||||||
|         msg.AddText("> "); |         msg.AddText("> "); | ||||||
|         msg.Append(SeString.Parse(message.ChatMsg.PayloadContent)); |         msg.Append(SeString.Parse(message.ChatMsg.PayloadContent)); | ||||||
|         msg.AddUiForegroundOff(); |         if (color != 0) | ||||||
|  |             msg.AddUiForegroundOff(); | ||||||
|  |  | ||||||
|         _chatGui.Print(new XivChatEntry{ |         _chatGui.Print(new XivChatEntry{ | ||||||
|             Message = msg.Build(), |             Message = msg.Build(), | ||||||
|             Name = chatMsg.SenderName, |             Name = chatMsg.SenderName, | ||||||
|             Type = XivChatType.StandardEmote |             Type = logKind | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Print an example message to the configured global chat channel | ||||||
|  |     public void PrintChannelExample(string message, string gid = "") | ||||||
|  |     { | ||||||
|  |         int chatType = _mareConfig.Current.ChatLogKind; | ||||||
|  |  | ||||||
|  |         foreach (var group in _pairManager.Groups) | ||||||
|  |         { | ||||||
|  |             if (group.Key.GID == gid) | ||||||
|  |             { | ||||||
|  |                 int shellChatType = _serverConfigurationManager.GetShellConfigForGid(gid).LogKind; | ||||||
|  |                 if (shellChatType != 0) | ||||||
|  |                     chatType = shellChatType; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         _chatGui.Print(new XivChatEntry{ | ||||||
|  |             Message = message, | ||||||
|  |             Name = "", | ||||||
|  |             Type = (XivChatType)chatType | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -94,7 +153,8 @@ public class ChatService : DisposableMediatorSubscriberBase | |||||||
|  |  | ||||||
|         foreach (var group in _pairManager.Groups) |         foreach (var group in _pairManager.Groups) | ||||||
|         { |         { | ||||||
|             if (_serverConfigurationManager.GetShellNumberForGid(group.Key.GID) == shellNumber) |             var shellConfig = _serverConfigurationManager.GetShellConfigForGid(group.Key.GID); | ||||||
|  |             if (shellConfig.Enabled && shellConfig.ShellNumber == shellNumber) | ||||||
|             { |             { | ||||||
|                 if (_gameChatHooks.IsValueCreated && _gameChatHooks.Value.ChatChannelOverride != null) |                 if (_gameChatHooks.IsValueCreated && _gameChatHooks.Value.ChatChannelOverride != null) | ||||||
|                 { |                 { | ||||||
| @@ -113,7 +173,8 @@ public class ChatService : DisposableMediatorSubscriberBase | |||||||
|  |  | ||||||
|         foreach (var group in _pairManager.Groups) |         foreach (var group in _pairManager.Groups) | ||||||
|         { |         { | ||||||
|             if (_serverConfigurationManager.GetShellNumberForGid(group.Key.GID) == shellNumber) |             var shellConfig = _serverConfigurationManager.GetShellConfigForGid(group.Key.GID); | ||||||
|  |             if (shellConfig.Enabled && shellConfig.ShellNumber == shellNumber) | ||||||
|             { |             { | ||||||
|                 var name = _serverConfigurationManager.GetNoteForGid(group.Key.GID) ?? group.Key.AliasOrGID; |                 var name = _serverConfigurationManager.GetNoteForGid(group.Key.GID) ?? group.Key.AliasOrGID; | ||||||
|                 // BUG: This doesn't always update the chat window e.g. when renaming a group |                 // BUG: This doesn't always update the chat window e.g. when renaming a group | ||||||
| @@ -136,7 +197,8 @@ public class ChatService : DisposableMediatorSubscriberBase | |||||||
|  |  | ||||||
|         foreach (var group in _pairManager.Groups) |         foreach (var group in _pairManager.Groups) | ||||||
|         { |         { | ||||||
|             if (_serverConfigurationManager.GetShellNumberForGid(group.Key.GID) == shellNumber) |             var shellConfig = _serverConfigurationManager.GetShellConfigForGid(group.Key.GID); | ||||||
|  |             if (shellConfig.Enabled && shellConfig.ShellNumber == shellNumber) | ||||||
|             { |             { | ||||||
|                 Task.Run(async () => { |                 Task.Run(async () => { | ||||||
|                     // TODO: Cache the name and home world instead of fetching it every time |                     // TODO: Cache the name and home world instead of fetching it every time | ||||||
|   | |||||||
| @@ -19,7 +19,6 @@ public sealed class CommandManagerService : IDisposable | |||||||
|     private const string _commandName2 = "/loporrit"; |     private const string _commandName2 = "/loporrit"; | ||||||
|  |  | ||||||
|     private const string _ssCommandPrefix = "/ss"; |     private const string _ssCommandPrefix = "/ss"; | ||||||
|     private const int _ssCommandMaxNumber = 50; |  | ||||||
|  |  | ||||||
|     private readonly ApiController _apiController; |     private readonly ApiController _apiController; | ||||||
|     private readonly ICommandManager _commandManager; |     private readonly ICommandManager _commandManager; | ||||||
| @@ -52,7 +51,7 @@ public sealed class CommandManagerService : IDisposable | |||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         // Lazy registration of all possible /ss# commands which tbf is what the game does for linkshells anyway |         // Lazy registration of all possible /ss# commands which tbf is what the game does for linkshells anyway | ||||||
|         for (int i = 1; i <= _ssCommandMaxNumber; ++i) |         for (int i = 1; i <= ChatService.CommandMaxNumber; ++i) | ||||||
|         { |         { | ||||||
|             _commandManager.AddHandler($"{_ssCommandPrefix}{i}", new CommandInfo(OnChatCommand) |             _commandManager.AddHandler($"{_ssCommandPrefix}{i}", new CommandInfo(OnChatCommand) | ||||||
|             { |             { | ||||||
| @@ -66,7 +65,7 @@ public sealed class CommandManagerService : IDisposable | |||||||
|         _commandManager.RemoveHandler(_commandName); |         _commandManager.RemoveHandler(_commandName); | ||||||
|         _commandManager.RemoveHandler(_commandName2); |         _commandManager.RemoveHandler(_commandName2); | ||||||
|  |  | ||||||
|         for (int i = 1; i <= _ssCommandMaxNumber; ++i) |         for (int i = 1; i <= ChatService.CommandMaxNumber; ++i) | ||||||
|             _commandManager.RemoveHandler($"{_ssCommandPrefix}{i}"); |             _commandManager.RemoveHandler($"{_ssCommandPrefix}{i}"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -70,6 +70,12 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber | |||||||
|                 .Where(w => w.Name.ByteLength > 0 && w.DataCenter.RowId != 0 && (w.IsPublic || char.IsUpper((char)w.Name.Data.Span[0]))) |                 .Where(w => w.Name.ByteLength > 0 && w.DataCenter.RowId != 0 && (w.IsPublic || char.IsUpper((char)w.Name.Data.Span[0]))) | ||||||
|                 .ToDictionary(w => (ushort)w.RowId, w => w.Name.ToString()); |                 .ToDictionary(w => (ushort)w.RowId, w => w.Name.ToString()); | ||||||
|         }); |         }); | ||||||
|  |         UiColors = new(() => | ||||||
|  |         { | ||||||
|  |             return gameData.GetExcelSheet<Lumina.Excel.Sheets.UIColor>(Dalamud.Game.ClientLanguage.English)! | ||||||
|  |                 .Where(x => x.RowId != 0 && !(x.RowId >= 500 && (x.UIForeground & 0xFFFFFF00) == 0)) | ||||||
|  |                 .ToDictionary(x => (int)x.RowId); | ||||||
|  |         }); | ||||||
|         mediator.Subscribe<TargetPairMessage>(this, async (msg) => |         mediator.Subscribe<TargetPairMessage>(this, async (msg) => | ||||||
|         { |         { | ||||||
|             if (clientState.IsPvP) return; |             if (clientState.IsPvP) return; | ||||||
| @@ -99,6 +105,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber | |||||||
|     public bool IsInCombat { get; private set; } = false; |     public bool IsInCombat { get; private set; } = false; | ||||||
|  |  | ||||||
|     public Lazy<Dictionary<ushort, string>> WorldData { get; private set; } |     public Lazy<Dictionary<ushort, string>> WorldData { get; private set; } | ||||||
|  |     public Lazy<Dictionary<int, Lumina.Excel.Sheets.UIColor>> UiColors { get; private set; } | ||||||
|  |  | ||||||
|     public MareMediator Mediator { get; } |     public MareMediator Mediator { get; } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| using MareSynchronos.MareConfiguration; | using MareSynchronos.MareConfiguration; | ||||||
| using MareSynchronos.MareConfiguration.Models; | using MareSynchronos.MareConfiguration.Models; | ||||||
| using MareSynchronos.Services.Mediator; | using MareSynchronos.Services.Mediator; | ||||||
|  | using MareSynchronos.Utils; | ||||||
| using MareSynchronos.WebAPI; | using MareSynchronos.WebAPI; | ||||||
| using Microsoft.Extensions.Logging; | using Microsoft.Extensions.Logging; | ||||||
| using System.Diagnostics; | using System.Diagnostics; | ||||||
| @@ -246,16 +247,27 @@ public class ServerConfigurationManager | |||||||
|         return CurrentServerTagStorage().ServerAvailablePairTags; |         return CurrentServerTagStorage().ServerAvailablePairTags; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     internal int GetShellNumberForGid(string gid) |     internal ShellConfig GetShellConfigForGid(string gid) | ||||||
|     { |     { | ||||||
|         if (CurrentSyncshellStorage().GidShellConfig.TryGetValue(gid, out var config)) |         if (CurrentSyncshellStorage().GidShellConfig.TryGetValue(gid, out var config)) | ||||||
|         { |             return config; | ||||||
|             return config.ShellNumber; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         int newNumber = CurrentSyncshellStorage().GidShellConfig.Count > 0 ? CurrentSyncshellStorage().GidShellConfig.Select(x => x.Value.ShellNumber).Max() + 1 : 1; |         // Pick the next higher syncshell number that is available | ||||||
|         SetShellNumberForGid(gid, newNumber, false); |         int newShellNumber = CurrentSyncshellStorage().GidShellConfig.Count > 0 ? CurrentSyncshellStorage().GidShellConfig.Select(x => x.Value.ShellNumber).Max() + 1 : 1; | ||||||
|         return newNumber; |  | ||||||
|  |         var shellConfig = new ShellConfig{ | ||||||
|  |             ShellNumber = newShellNumber | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         // Save config to avoid auto-generated numbers shuffling around | ||||||
|  |         SaveShellConfigForGid(gid, shellConfig); | ||||||
|  |  | ||||||
|  |         return CurrentSyncshellStorage().GidShellConfig[gid]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     internal int GetShellNumberForGid(string gid) | ||||||
|  |     { | ||||||
|  |         return GetShellConfigForGid(gid).ShellNumber; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     internal Dictionary<string, List<string>> GetUidServerPairedUserTags() |     internal Dictionary<string, List<string>> GetUidServerPairedUserTags() | ||||||
| @@ -359,23 +371,14 @@ public class ServerConfigurationManager | |||||||
|         _notesConfig.Save(); |         _notesConfig.Save(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     internal void SetShellNumberForGid(string gid, int number, bool save = true) |     internal void SaveShellConfigForGid(string gid, ShellConfig config) | ||||||
|     { |     { | ||||||
|         if (string.IsNullOrEmpty(gid)) return; |         if (string.IsNullOrEmpty(gid)) return; | ||||||
|  |  | ||||||
|         if (CurrentSyncshellStorage().GidShellConfig.TryGetValue(gid, out var config)) |         // This is somewhat pointless because ShellConfig is a ref type we returned to the caller anyway... | ||||||
|         { |         CurrentSyncshellStorage().GidShellConfig[gid] = config; | ||||||
|             config.ShellNumber = number; |  | ||||||
|         } |  | ||||||
|         else |  | ||||||
|         { |  | ||||||
|             CurrentSyncshellStorage().GidShellConfig.Add(gid, new(){ |  | ||||||
|                 ShellNumber = number |  | ||||||
|             }); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if (save) |         _syncshellConfig.Save(); | ||||||
|             _syncshellConfig.Save(); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private ServerNotesStorage CurrentNotesStorage() |     private ServerNotesStorage CurrentNotesStorage() | ||||||
|   | |||||||
| @@ -239,7 +239,8 @@ internal sealed class GroupPanel | |||||||
|  |  | ||||||
|         if (!string.Equals(_editGroupEntry, groupDto.GID, StringComparison.Ordinal)) |         if (!string.Equals(_editGroupEntry, groupDto.GID, StringComparison.Ordinal)) | ||||||
|         { |         { | ||||||
|             if (!_mareConfig.Current.DisableSyncshellChat) |             var shellConfig = _serverConfigurationManager.GetShellConfigForGid(groupDto.GID); | ||||||
|  |             if (!_mareConfig.Current.DisableSyncshellChat && shellConfig.Enabled) | ||||||
|             { |             { | ||||||
|                 ImGui.TextUnformatted($"[{shellNumber}]"); |                 ImGui.TextUnformatted($"[{shellNumber}]"); | ||||||
|                 UiSharedService.AttachToolTip("Chat command prefix: /ss" + shellNumber); |                 UiSharedService.AttachToolTip("Chat command prefix: /ss" + shellNumber); | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| using Dalamud.Interface; | using Dalamud.Game.Text; | ||||||
|  | using Dalamud.Interface; | ||||||
| using Dalamud.Interface.Colors; | using Dalamud.Interface.Colors; | ||||||
| using Dalamud.Interface.Utility; | using Dalamud.Interface.Utility; | ||||||
| using Dalamud.Interface.Utility.Raii; | using Dalamud.Interface.Utility.Raii; | ||||||
| @@ -38,11 +39,13 @@ public class SettingsUi : WindowMediatorSubscriberBase | |||||||
|     private readonly MareConfigService _configService; |     private readonly MareConfigService _configService; | ||||||
|     private readonly ConcurrentDictionary<GameObjectHandler, Dictionary<string, FileDownloadStatus>> _currentDownloads = new(); |     private readonly ConcurrentDictionary<GameObjectHandler, Dictionary<string, FileDownloadStatus>> _currentDownloads = new(); | ||||||
|     private readonly FileCompactor _fileCompactor; |     private readonly FileCompactor _fileCompactor; | ||||||
|  |     private readonly DalamudUtilService _dalamudUtilService; | ||||||
|     private readonly FileUploadManager _fileTransferManager; |     private readonly FileUploadManager _fileTransferManager; | ||||||
|     private readonly FileTransferOrchestrator _fileTransferOrchestrator; |     private readonly FileTransferOrchestrator _fileTransferOrchestrator; | ||||||
|     private readonly FileCacheManager _fileCacheManager; |     private readonly FileCacheManager _fileCacheManager; | ||||||
|     private readonly MareCharaFileManager _mareCharaFileManager; |     private readonly MareCharaFileManager _mareCharaFileManager; | ||||||
|     private readonly PairManager _pairManager; |     private readonly PairManager _pairManager; | ||||||
|  |     private readonly ChatService _chatService; | ||||||
|     private readonly PerformanceCollectorService _performanceCollector; |     private readonly PerformanceCollectorService _performanceCollector; | ||||||
|     private readonly ServerConfigurationManager _serverConfigurationManager; |     private readonly ServerConfigurationManager _serverConfigurationManager; | ||||||
|     private readonly UiSharedService _uiShared; |     private readonly UiSharedService _uiShared; | ||||||
| @@ -67,9 +70,10 @@ public class SettingsUi : WindowMediatorSubscriberBase | |||||||
|  |  | ||||||
|     public SettingsUi(ILogger<SettingsUi> logger, |     public SettingsUi(ILogger<SettingsUi> logger, | ||||||
|         UiSharedService uiShared, MareConfigService configService, |         UiSharedService uiShared, MareConfigService configService, | ||||||
|         MareCharaFileManager mareCharaFileManager, PairManager pairManager, |         MareCharaFileManager mareCharaFileManager, PairManager pairManager, ChatService chatService, | ||||||
|         ServerConfigurationManager serverConfigurationManager, |         ServerConfigurationManager serverConfigurationManager, | ||||||
|         MareMediator mediator, PerformanceCollectorService performanceCollector, |         MareMediator mediator, PerformanceCollectorService performanceCollector, | ||||||
|  |         DalamudUtilService dalamudUtilService, | ||||||
|         FileUploadManager fileTransferManager, |         FileUploadManager fileTransferManager, | ||||||
|         FileTransferOrchestrator fileTransferOrchestrator, |         FileTransferOrchestrator fileTransferOrchestrator, | ||||||
|         FileCacheManager fileCacheManager, |         FileCacheManager fileCacheManager, | ||||||
| @@ -78,8 +82,10 @@ public class SettingsUi : WindowMediatorSubscriberBase | |||||||
|         _configService = configService; |         _configService = configService; | ||||||
|         _mareCharaFileManager = mareCharaFileManager; |         _mareCharaFileManager = mareCharaFileManager; | ||||||
|         _pairManager = pairManager; |         _pairManager = pairManager; | ||||||
|  |         _chatService = chatService; | ||||||
|         _serverConfigurationManager = serverConfigurationManager; |         _serverConfigurationManager = serverConfigurationManager; | ||||||
|         _performanceCollector = performanceCollector; |         _performanceCollector = performanceCollector; | ||||||
|  |         _dalamudUtilService = dalamudUtilService; | ||||||
|         _fileTransferManager = fileTransferManager; |         _fileTransferManager = fileTransferManager; | ||||||
|         _fileTransferOrchestrator = fileTransferOrchestrator; |         _fileTransferOrchestrator = fileTransferOrchestrator; | ||||||
|         _fileCacheManager = fileCacheManager; |         _fileCacheManager = fileCacheManager; | ||||||
| @@ -369,6 +375,233 @@ public class SettingsUi : WindowMediatorSubscriberBase | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private static readonly List<(XivChatType, string)> _syncshellChatTypes = [ | ||||||
|  |         (0, "(use global setting)"), | ||||||
|  |         (XivChatType.Debug, "Debug"), | ||||||
|  |         (XivChatType.Echo, "Echo"), | ||||||
|  |         (XivChatType.StandardEmote, "Standard Emote"), | ||||||
|  |         (XivChatType.CustomEmote, "Custom Emote"), | ||||||
|  |         (XivChatType.SystemMessage, "System Message"), | ||||||
|  |         (XivChatType.SystemError, "System Error"), | ||||||
|  |         (XivChatType.GatheringSystemMessage, "Gathering Message"), | ||||||
|  |         (XivChatType.ErrorMessage, "Error message"), | ||||||
|  |     ]; | ||||||
|  |  | ||||||
|  |     private void DrawChatConfig() | ||||||
|  |     { | ||||||
|  |         _lastTab = "Chat"; | ||||||
|  |  | ||||||
|  |         UiSharedService.FontText("Chat Settings", _uiShared.UidFont); | ||||||
|  |  | ||||||
|  |         var disableSyncshellChat = _configService.Current.DisableSyncshellChat; | ||||||
|  |  | ||||||
|  |         if (ImGui.Checkbox("Disable chat globally", ref disableSyncshellChat)) | ||||||
|  |         { | ||||||
|  |             _configService.Current.DisableSyncshellChat = disableSyncshellChat; | ||||||
|  |             _configService.Save(); | ||||||
|  |         } | ||||||
|  |         UiSharedService.DrawHelpText("Global setting to disable chat for all syncshells."); | ||||||
|  |  | ||||||
|  |         using var pushDisableGlobal = ImRaii.Disabled(disableSyncshellChat); | ||||||
|  |  | ||||||
|  |         var uiColors = _dalamudUtilService.UiColors.Value; | ||||||
|  |         int globalChatColor = _configService.Current.ChatColor; | ||||||
|  |  | ||||||
|  |         if (globalChatColor != 0 && !uiColors.ContainsKey(globalChatColor)) | ||||||
|  |         { | ||||||
|  |             globalChatColor = 0; | ||||||
|  |             _configService.Current.ChatColor = 0; | ||||||
|  |             _configService.Save(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); | ||||||
|  |         _uiShared.DrawColorCombo("Chat text color", Enumerable.Concat([0], uiColors.Keys), | ||||||
|  |         i => i switch | ||||||
|  |         { | ||||||
|  |             0 => (uiColors[ChatService.DefaultColor].UIForeground, "Plugin Default"), | ||||||
|  |             _ => (uiColors[i].UIForeground, $"[{i}] Sample Text") | ||||||
|  |         }, | ||||||
|  |         i => { | ||||||
|  |             _configService.Current.ChatColor = i; | ||||||
|  |             _configService.Save(); | ||||||
|  |         }, globalChatColor); | ||||||
|  |  | ||||||
|  |         int globalChatType = _configService.Current.ChatLogKind; | ||||||
|  |         int globalChatTypeIdx = _syncshellChatTypes.FindIndex(x => globalChatType == (int)x.Item1); | ||||||
|  |  | ||||||
|  |         if (globalChatTypeIdx == -1) | ||||||
|  |             globalChatTypeIdx = 0; | ||||||
|  |  | ||||||
|  |         ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); | ||||||
|  |         _uiShared.DrawCombo("Chat channel", Enumerable.Range(1, _syncshellChatTypes.Count - 1), i => $"{_syncshellChatTypes[i].Item2}", | ||||||
|  |         i => { | ||||||
|  |             if (_configService.Current.ChatLogKind == (int)_syncshellChatTypes[i].Item1) | ||||||
|  |                 return; | ||||||
|  |             _configService.Current.ChatLogKind = (int)_syncshellChatTypes[i].Item1; | ||||||
|  |             _chatService.PrintChannelExample($"Selected channel: {_syncshellChatTypes[i].Item2}"); | ||||||
|  |             _configService.Save(); | ||||||
|  |         }, globalChatTypeIdx); | ||||||
|  |         UiSharedService.DrawHelpText("FFXIV chat channel to output chat messages on."); | ||||||
|  |  | ||||||
|  |         ImGui.SetWindowFontScale(0.6f); | ||||||
|  |         UiSharedService.FontText("\"Chat 2\" Plugin Integration", _uiShared.UidFont); | ||||||
|  |         ImGui.SetWindowFontScale(1.0f); | ||||||
|  |  | ||||||
|  |         // TODO: ExtraChat API impersonation | ||||||
|  |         /* | ||||||
|  |         var extraChatAPI = _configService.Current.ExtraChatAPI; | ||||||
|  |         if (ImGui.Checkbox("ExtraChat replacement mode", ref extraChatAPI)) | ||||||
|  |         { | ||||||
|  |             _configService.Current.ExtraChatAPI = extraChatAPI; | ||||||
|  |             if (extraChatAPI) | ||||||
|  |                 _configService.Current.ExtraChatTags = true; | ||||||
|  |             _configService.Save(); | ||||||
|  |         } | ||||||
|  |         ImGui.EndDisabled(); | ||||||
|  |         UiSharedService.DrawHelpText("Enable ExtraChat APIs for full Chat 2 plugin integration.\n\nDo not enable this if ExtraChat is also installed and running."); | ||||||
|  |         */ | ||||||
|  |  | ||||||
|  |         var extraChatTags = _configService.Current.ExtraChatTags; | ||||||
|  |         if (ImGui.Checkbox("Tag messages as ExtraChat", ref extraChatTags)) | ||||||
|  |         { | ||||||
|  |             _configService.Current.ExtraChatTags = extraChatTags; | ||||||
|  |             if (!extraChatTags) | ||||||
|  |                 _configService.Current.ExtraChatAPI = false; | ||||||
|  |             _configService.Save(); | ||||||
|  |         } | ||||||
|  |         UiSharedService.DrawHelpText("If enabled, messages will be filtered under the category \"ExtraChat channels: All\".\n\nThis works even if ExtraChat is also installed and enabled."); | ||||||
|  |  | ||||||
|  |         ImGui.Separator(); | ||||||
|  |  | ||||||
|  |         UiSharedService.FontText("Syncshell Settings", _uiShared.UidFont); | ||||||
|  |  | ||||||
|  |         if (!ApiController.ServerAlive) | ||||||
|  |         { | ||||||
|  |             ImGui.Text("Connect to the server to configure individual syncshell settings."); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         foreach (var group in _pairManager.Groups.OrderBy(k => k.Key.GID)) | ||||||
|  |         { | ||||||
|  |             var gid = group.Key.GID; | ||||||
|  |             using var pushId = ImRaii.PushId(gid); | ||||||
|  |  | ||||||
|  |             var shellConfig = _serverConfigurationManager.GetShellConfigForGid(gid); | ||||||
|  |             var shellNumber = shellConfig.ShellNumber; | ||||||
|  |             var shellEnabled = shellConfig.Enabled; | ||||||
|  |             var shellName = _serverConfigurationManager.GetNoteForGid(gid) ?? group.Key.AliasOrGID; | ||||||
|  |  | ||||||
|  |             if (shellEnabled) | ||||||
|  |                 shellName = $"[{shellNumber}] {shellName}"; | ||||||
|  |  | ||||||
|  |             ImGui.SetWindowFontScale(0.6f); | ||||||
|  |             UiSharedService.FontText(shellName, _uiShared.UidFont); | ||||||
|  |             ImGui.SetWindowFontScale(1.0f); | ||||||
|  |  | ||||||
|  |             using var pushIndent = ImRaii.PushIndent(); | ||||||
|  |  | ||||||
|  |             if (ImGui.Checkbox($"Enable chat for this syncshell##{gid}", ref shellEnabled)) | ||||||
|  |             { | ||||||
|  |                 // If there is an active group with the same syncshell number, pick a new one | ||||||
|  |                 int nextNumber = 1; | ||||||
|  |                 bool conflict = false; | ||||||
|  |                 foreach (var otherGroup in _pairManager.Groups) | ||||||
|  |                 { | ||||||
|  |                     if (gid == otherGroup.Key.GID) continue; | ||||||
|  |                     var otherShellConfig = _serverConfigurationManager.GetShellConfigForGid(otherGroup.Key.GID); | ||||||
|  |                     if (otherShellConfig.Enabled && otherShellConfig.ShellNumber == shellNumber) | ||||||
|  |                         conflict = true; | ||||||
|  |                     nextNumber = System.Math.Max(nextNumber, otherShellConfig.ShellNumber) + 1; | ||||||
|  |                 } | ||||||
|  |                 if (conflict) | ||||||
|  |                     shellConfig.ShellNumber = nextNumber; | ||||||
|  |                 shellConfig.Enabled = shellEnabled; | ||||||
|  |                 _serverConfigurationManager.SaveShellConfigForGid(gid, shellConfig); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             using var pushDisabled = ImRaii.Disabled(!shellEnabled); | ||||||
|  |  | ||||||
|  |             ImGui.SetNextItemWidth(50 * ImGuiHelpers.GlobalScale); | ||||||
|  |  | ||||||
|  |             var setSyncshellNumberFn = (int i) => { | ||||||
|  |                 // Find an active group with the same syncshell number as selected, and swap it | ||||||
|  |                 // This logic can leave duplicate IDs present in the config but its not critical | ||||||
|  |                 foreach (var otherGroup in _pairManager.Groups) | ||||||
|  |                 { | ||||||
|  |                     if (gid == otherGroup.Key.GID) continue; | ||||||
|  |                     var otherShellConfig = _serverConfigurationManager.GetShellConfigForGid(otherGroup.Key.GID); | ||||||
|  |                     if (otherShellConfig.Enabled && otherShellConfig.ShellNumber == i) | ||||||
|  |                     { | ||||||
|  |                         otherShellConfig.ShellNumber = shellNumber; | ||||||
|  |                         _serverConfigurationManager.SaveShellConfigForGid(otherGroup.Key.GID, otherShellConfig); | ||||||
|  |                         break; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 shellConfig.ShellNumber = i; | ||||||
|  |                 _serverConfigurationManager.SaveShellConfigForGid(gid, shellConfig); | ||||||
|  |             }; | ||||||
|  |  | ||||||
|  |             // _uiShared.DrawCombo() remembers the selected option -- we don't want that, because the value can change | ||||||
|  |             if (ImGui.BeginCombo("Syncshell number##{gid}", $"{shellNumber}")) | ||||||
|  |             { | ||||||
|  |                 // Same hard-coded number in CommandManagerService | ||||||
|  |                 for (int i = 1; i <= ChatService.CommandMaxNumber; ++i) | ||||||
|  |                 { | ||||||
|  |                     if (ImGui.Selectable($"{i}", i == shellNumber)) | ||||||
|  |                     { | ||||||
|  |                         // Find an active group with the same syncshell number as selected, and swap it | ||||||
|  |                         // This logic can leave duplicate IDs present in the config but its not critical | ||||||
|  |                         foreach (var otherGroup in _pairManager.Groups) | ||||||
|  |                         { | ||||||
|  |                             if (gid == otherGroup.Key.GID) continue; | ||||||
|  |                             var otherShellConfig = _serverConfigurationManager.GetShellConfigForGid(otherGroup.Key.GID); | ||||||
|  |                             if (otherShellConfig.Enabled && otherShellConfig.ShellNumber == i) | ||||||
|  |                             { | ||||||
|  |                                 otherShellConfig.ShellNumber = shellNumber; | ||||||
|  |                                 _serverConfigurationManager.SaveShellConfigForGid(otherGroup.Key.GID, otherShellConfig); | ||||||
|  |                                 break; | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                         shellConfig.ShellNumber = i; | ||||||
|  |                         _serverConfigurationManager.SaveShellConfigForGid(gid, shellConfig); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 ImGui.EndCombo(); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (shellConfig.Color != 0 && !uiColors.ContainsKey(shellConfig.Color)) | ||||||
|  |             { | ||||||
|  |                 shellConfig.Color = 0; | ||||||
|  |                 _serverConfigurationManager.SaveShellConfigForGid(gid, shellConfig); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); | ||||||
|  |             _uiShared.DrawColorCombo($"Chat text color##{gid}", Enumerable.Concat([0], uiColors.Keys), | ||||||
|  |             i => i switch | ||||||
|  |             { | ||||||
|  |                 0 => (uiColors[globalChatColor > 0 ? globalChatColor : ChatService.DefaultColor].UIForeground, "(use global setting)"), | ||||||
|  |                 _ => (uiColors[i].UIForeground, $"[{i}] Sample Text") | ||||||
|  |             }, | ||||||
|  |             i => { | ||||||
|  |                 shellConfig.Color = i; | ||||||
|  |                 _serverConfigurationManager.SaveShellConfigForGid(gid, shellConfig); | ||||||
|  |             }, shellConfig.Color); | ||||||
|  |  | ||||||
|  |             int shellChatTypeIdx = _syncshellChatTypes.FindIndex(x => shellConfig.LogKind == (int)x.Item1); | ||||||
|  |  | ||||||
|  |             if (shellChatTypeIdx == -1) | ||||||
|  |                 shellChatTypeIdx = 0; | ||||||
|  |  | ||||||
|  |             ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); | ||||||
|  |             _uiShared.DrawCombo($"Chat channel##{gid}", Enumerable.Range(0, _syncshellChatTypes.Count), i => $"{_syncshellChatTypes[i].Item2}", | ||||||
|  |             i => { | ||||||
|  |                 shellConfig.LogKind = (int)_syncshellChatTypes[i].Item1; | ||||||
|  |                 _serverConfigurationManager.SaveShellConfigForGid(gid, shellConfig); | ||||||
|  |             }, shellChatTypeIdx); | ||||||
|  |             UiSharedService.DrawHelpText("Override the FFXIV chat channel used for this syncshell."); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     private void DrawDebug() |     private void DrawDebug() | ||||||
|     { |     { | ||||||
|         _lastTab = "Debug"; |         _lastTab = "Debug"; | ||||||
| @@ -651,18 +884,6 @@ public class SettingsUi : WindowMediatorSubscriberBase | |||||||
|         } |         } | ||||||
|         UiSharedService.DrawHelpText("This will open a popup that allows you to set the notes for a user after successfully adding them to your individual pairs."); |         UiSharedService.DrawHelpText("This will open a popup that allows you to set the notes for a user after successfully adding them to your individual pairs."); | ||||||
|  |  | ||||||
|         ImGui.Separator(); |  | ||||||
|         UiSharedService.FontText("Chat", _uiShared.UidFont); |  | ||||||
|  |  | ||||||
|         var disableSyncshellChat = _configService.Current.DisableSyncshellChat; |  | ||||||
|  |  | ||||||
|         if (ImGui.Checkbox("Disable Syncshell Chat", ref disableSyncshellChat)) |  | ||||||
|         { |  | ||||||
|             _configService.Current.DisableSyncshellChat = disableSyncshellChat; |  | ||||||
|             _configService.Save(); |  | ||||||
|         } |  | ||||||
|         UiSharedService.DrawHelpText("Disable sending/receiving all syncshell chat messages."); |  | ||||||
|  |  | ||||||
|         ImGui.Separator(); |         ImGui.Separator(); | ||||||
|         UiSharedService.FontText("UI", _uiShared.UidFont); |         UiSharedService.FontText("UI", _uiShared.UidFont); | ||||||
|         var showCharacterNames = _configService.Current.ShowCharacterNames; |         var showCharacterNames = _configService.Current.ShowCharacterNames; | ||||||
| @@ -1230,6 +1451,12 @@ public class SettingsUi : WindowMediatorSubscriberBase | |||||||
|                 ImGui.EndDisabled(); // _registrationInProgress |                 ImGui.EndDisabled(); // _registrationInProgress | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  |             if (ImGui.BeginTabItem("Chat")) | ||||||
|  |             { | ||||||
|  |                 DrawChatConfig(); | ||||||
|  |                 ImGui.EndTabItem(); | ||||||
|  |             } | ||||||
|  |  | ||||||
|             if (ImGui.BeginTabItem("Debug")) |             if (ImGui.BeginTabItem("Debug")) | ||||||
|             { |             { | ||||||
|                 DrawDebug(); |                 DrawDebug(); | ||||||
|   | |||||||
| @@ -688,6 +688,51 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase | |||||||
|         return (T)_selectedComboItems[comboName]; |         return (T)_selectedComboItems[comboName]; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public T? DrawColorCombo<T>(string comboName, IEnumerable<T> comboItems, Func<T, (uint Color, string Name)> toEntry, | ||||||
|  |         Action<T>? onSelected = null, T? initialSelectedItem = default) | ||||||
|  |     { | ||||||
|  |         if (!comboItems.Any()) return default; | ||||||
|  |  | ||||||
|  |         if (!_selectedComboItems.TryGetValue(comboName, out var selectedItem) && selectedItem == null) | ||||||
|  |         { | ||||||
|  |             if (!EqualityComparer<T>.Default.Equals(initialSelectedItem, default)) | ||||||
|  |             { | ||||||
|  |                 selectedItem = initialSelectedItem; | ||||||
|  |                 _selectedComboItems[comboName] = selectedItem!; | ||||||
|  |                 if (!EqualityComparer<T>.Default.Equals(initialSelectedItem, default)) | ||||||
|  |                     onSelected?.Invoke(initialSelectedItem); | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 selectedItem = comboItems.First(); | ||||||
|  |                 _selectedComboItems[comboName] = selectedItem!; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         var entry = toEntry((T)selectedItem!); | ||||||
|  |         ImGui.PushStyleColor(ImGuiCol.Text, ColorHelpers.RgbaUintToVector4(ColorHelpers.SwapEndianness(entry.Color))); | ||||||
|  |         if (ImGui.BeginCombo(comboName, entry.Name)) | ||||||
|  |         { | ||||||
|  |             foreach (var item in comboItems) | ||||||
|  |             { | ||||||
|  |                 entry = toEntry(item); | ||||||
|  |                 ImGui.PushStyleColor(ImGuiCol.Text, ColorHelpers.RgbaUintToVector4(ColorHelpers.SwapEndianness(entry.Color))); | ||||||
|  |                 bool isSelected = EqualityComparer<T>.Default.Equals(item, (T)selectedItem); | ||||||
|  |                 if (ImGui.Selectable(entry.Name, isSelected)) | ||||||
|  |                 { | ||||||
|  |                     _selectedComboItems[comboName] = item!; | ||||||
|  |                     onSelected?.Invoke(item!); | ||||||
|  |                 } | ||||||
|  |                 ImGui.PopStyleColor(); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             ImGui.EndCombo(); | ||||||
|  |         } | ||||||
|  |         ImGui.PopStyleColor(); | ||||||
|  |  | ||||||
|  |         return (T)_selectedComboItems[comboName]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     public void DrawFileScanState() |     public void DrawFileScanState() | ||||||
|     { |     { | ||||||
|         ImGui.TextUnformatted("File Scanner Status"); |         ImGui.TextUnformatted("File Scanner Status"); | ||||||
|   | |||||||
							
								
								
									
										34
									
								
								MareSynchronos/Utils/ChatUtils.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								MareSynchronos/Utils/ChatUtils.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | using Dalamud.Game.Text.SeStringHandling.Payloads; | ||||||
|  | using System.Security.Cryptography; | ||||||
|  | using System.Text; | ||||||
|  |  | ||||||
|  | namespace MareSynchronos.Utils; | ||||||
|  |  | ||||||
|  | public static class ChatUtils | ||||||
|  | { | ||||||
|  | 	// Based on https://git.anna.lgbt/anna/ExtraChat/src/branch/main/client/ExtraChat/Util/PayloadUtil.cs | ||||||
|  | 	// This must store a Guid (16 bytes), as Chat 2 converts the data back to one | ||||||
|  |  | ||||||
|  | 	public static RawPayload CreateExtraChatTagPayload(Guid guid) | ||||||
|  | 	{ | ||||||
|  | 		var header = (byte[])[ | ||||||
|  | 			0x02,   // Payload.START_BYTE | ||||||
|  | 			0x27,   // SeStringChunkType.Interactable | ||||||
|  | 			2 + 16, // remaining length: ExtraChat sends 19 here but I think its an error | ||||||
|  | 			0x20    // Custom ExtraChat InfoType | ||||||
|  | 		]; | ||||||
|  |  | ||||||
|  | 		var footer = (byte)0x03; // Payload.END_BYTE | ||||||
|  |  | ||||||
|  | 		return new RawPayload([..header, ..guid.ToByteArray(), footer]); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// We have a unique identifier in the form of a GID, which can be consistently mapped to the same GUID | ||||||
|  | 	public static RawPayload CreateExtraChatTagPayload(string gid) | ||||||
|  | 	{ | ||||||
|  | 		var gidBytes = UTF8Encoding.UTF8.GetBytes(gid); | ||||||
|  | 		var hashedBytes = MD5.HashData(gidBytes); | ||||||
|  | 		var guid = new Guid(hashedBytes); | ||||||
|  | 		return CreateExtraChatTagPayload(guid); | ||||||
|  | 	} | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user
	 Loporrit
					Loporrit