diff --git a/MareSynchronos/MareConfiguration/Configurations/MareConfig.cs b/MareSynchronos/MareConfiguration/Configurations/MareConfig.cs index 24df7f5..dd2e01e 100644 --- a/MareSynchronos/MareConfiguration/Configurations/MareConfig.cs +++ b/MareSynchronos/MareConfiguration/Configurations/MareConfig.cs @@ -18,6 +18,8 @@ public class MareConfig : IMareConfiguration public DtrEntry.Colors DtrColorsDefault { get; set; } = default; public DtrEntry.Colors DtrColorsNotConnected { get; set; } = new(Glow: 0x0428FFu); public DtrEntry.Colors DtrColorsPairsInRange { get; set; } = new(Glow: 0xFFBA47u); + public bool UseNameColors { get; set; } = false; + public DtrEntry.Colors NameColors { get; set; } = new(Foreground: 0xF5EB67u, Glow: 0x78710Fu); public bool EnableRightClickMenus { get; set; } = true; public NotificationLocation ErrorNotification { get; set; } = NotificationLocation.Both; public string ExportFolder { get; set; } = string.Empty; diff --git a/MareSynchronos/MarePlugin.cs b/MareSynchronos/MarePlugin.cs index 64a9ffb..46da741 100644 --- a/MareSynchronos/MarePlugin.cs +++ b/MareSynchronos/MarePlugin.cs @@ -152,6 +152,7 @@ public class MarePlugin : MediatorSubscriberBase, IHostedService _runtimeServiceScope.ServiceProvider.GetRequiredService(); _runtimeServiceScope.ServiceProvider.GetRequiredService(); _runtimeServiceScope.ServiceProvider.GetRequiredService(); + _runtimeServiceScope.ServiceProvider.GetRequiredService(); #if !DEBUG if (_mareConfigService.Current.LogLevel != LogLevel.Information) diff --git a/MareSynchronos/PlayerData/Pairs/Pair.cs b/MareSynchronos/PlayerData/Pairs/Pair.cs index b227a12..166a7f4 100644 --- a/MareSynchronos/PlayerData/Pairs/Pair.cs +++ b/MareSynchronos/PlayerData/Pairs/Pair.cs @@ -48,6 +48,7 @@ public class Pair public bool IsVisible => CachedPlayer?.IsVisible ?? false; public CharacterData? LastReceivedCharacterData { get; set; } public string? PlayerName => GetPlayerName(); + public uint PlayerCharacterId => GetPlayerCharacterId(); public long LastAppliedDataSize => CachedPlayer?.LastAppliedDataSize ?? -1; public UserData UserData => UserPair?.User ?? GroupPair.First().Value.User; @@ -173,6 +174,13 @@ public class Pair return _serverConfigurationManager.GetNameForUid(UserData.UID); } + public uint GetPlayerCharacterId() + { + if (CachedPlayer != null) + return CachedPlayer.PlayerCharacterId; + return uint.MaxValue; + } + public string? GetNoteOrName() { string? note = GetNote(); diff --git a/MareSynchronos/Plugin.cs b/MareSynchronos/Plugin.cs index 412b108..7408215 100644 --- a/MareSynchronos/Plugin.cs +++ b/MareSynchronos/Plugin.cs @@ -47,7 +47,8 @@ public sealed class Plugin : IDalamudPlugin public Plugin(IDalamudPluginInterface pluginInterface, ICommandManager commandManager, IDataManager gameData, IFramework framework, IObjectTable objectTable, IClientState clientState, ICondition condition, IChatGui chatGui, IGameGui gameGui, IDtrBar dtrBar, IToastGui toastGui, IPluginLog pluginLog, ITargetManager targetManager, IGameLifecycle addonLifecycle, - INotificationManager notificationManager, ITextureProvider textureProvider, IContextMenu contextMenu, IGameInteropProvider gameInteropProvider) + INotificationManager notificationManager, ITextureProvider textureProvider, IContextMenu contextMenu, IGameInteropProvider gameInteropProvider, + INamePlateGui namePlateGui) { Plugin.Self = this; _host = new HostBuilder() @@ -161,6 +162,8 @@ public sealed class Plugin : IDalamudPlugin s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService>(), gameInteropProvider, chatGui, s.GetRequiredService(), s.GetRequiredService())); + collection.AddScoped((s) => new GuiHookService(s.GetRequiredService>(), s.GetRequiredService(), + s.GetRequiredService(), namePlateGui, s.GetRequiredService())); collection.AddHostedService(p => p.GetRequiredService()); collection.AddHostedService(p => p.GetRequiredService()); diff --git a/MareSynchronos/Services/GuiHookService.cs b/MareSynchronos/Services/GuiHookService.cs new file mode 100644 index 0000000..5ec90cc --- /dev/null +++ b/MareSynchronos/Services/GuiHookService.cs @@ -0,0 +1,108 @@ +using Dalamud.Game.ClientState.Objects; +using Dalamud.Game.Gui.NamePlate; +using Dalamud.Game.Text.SeStringHandling; +using Dalamud.Game.Text.SeStringHandling.Payloads; +using Dalamud.Plugin.Services; +using MareSynchronos.MareConfiguration; +using MareSynchronos.PlayerData.Pairs; +using MareSynchronos.Services.Mediator; +using MareSynchronos.UI; +using Microsoft.Extensions.Logging; + +namespace MareSynchronos.Services; + +public class GuiHookService : DisposableMediatorSubscriberBase +{ + private readonly ILogger _logger; + private readonly MareConfigService _configService; + private readonly INamePlateGui _namePlateGui; + private readonly PairManager _pairManager; + + private bool _isModified = false; + + public GuiHookService(ILogger logger, MareMediator mediator, MareConfigService configService, + INamePlateGui namePlateGui, PairManager pairManager) + : base(logger, mediator) + { + _logger = logger; + _configService = configService; + _namePlateGui = namePlateGui; + _pairManager = pairManager; + + _namePlateGui.OnNamePlateUpdate += OnNamePlateUpdate; + _namePlateGui.RequestRedraw(); + + Mediator.Subscribe(this, (_) => _namePlateGui.RequestRedraw()); + } + + public void RequestRedraw() + { + if (!_configService.Current.UseNameColors) + { + if (!_isModified) + return; + _isModified = false; + } + + _namePlateGui.RequestRedraw(); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + _namePlateGui.OnNamePlateUpdate -= OnNamePlateUpdate; + _namePlateGui.RequestRedraw(); + } + + private void OnNamePlateUpdate(INamePlateUpdateContext context, IReadOnlyList handlers) + { + if (!_configService.Current.UseNameColors) + return; + + var visibleUsersIds = _pairManager.GetOnlineUserPairs().Where(u => u.IsVisible && u.PlayerCharacterId != uint.MaxValue).Select(u => (ulong)u.PlayerCharacterId).ToHashSet(); + var colors = _configService.Current.NameColors; + + foreach (var handler in handlers) + { + if (visibleUsersIds.Contains(handler.GameObjectId)) + { + handler.NameParts.TextWrap = ( + BuildColorStartSeString(colors), + BuildColorEndSeString(colors) + ); + _isModified = true; + } + } + } + + #region Colored SeString + private const byte _colorTypeForeground = 0x13; + private const byte _colorTypeGlow = 0x14; + + private static SeString BuildColorStartSeString(DtrEntry.Colors colors) + { + var ssb = new SeStringBuilder(); + if (colors.Foreground != default) + ssb.Add(BuildColorStartPayload(_colorTypeForeground, colors.Foreground)); + if (colors.Glow != default) + ssb.Add(BuildColorStartPayload(_colorTypeGlow, colors.Glow)); + return ssb.Build(); + } + + private static SeString BuildColorEndSeString(DtrEntry.Colors colors) + { + var ssb = new SeStringBuilder(); + if (colors.Glow != default) + ssb.Add(BuildColorEndPayload(_colorTypeGlow)); + if (colors.Foreground != default) + ssb.Add(BuildColorEndPayload(_colorTypeForeground)); + return ssb.Build(); + } + + private static RawPayload BuildColorStartPayload(byte colorType, uint color) + => new(unchecked([0x02, colorType, 0x05, 0xF6, byte.Max((byte)color, 0x01), byte.Max((byte)(color >> 8), 0x01), byte.Max((byte)(color >> 16), 0x01), 0x03])); + + private static RawPayload BuildColorEndPayload(byte colorType) + => new([0x02, colorType, 0x02, 0xEC, 0x03]); + #endregion +} diff --git a/MareSynchronos/UI/SettingsUi.cs b/MareSynchronos/UI/SettingsUi.cs index 6be7a45..1e4565f 100644 --- a/MareSynchronos/UI/SettingsUi.cs +++ b/MareSynchronos/UI/SettingsUi.cs @@ -49,6 +49,7 @@ public class SettingsUi : WindowMediatorSubscriberBase private readonly MareCharaFileManager _mareCharaFileManager; private readonly PairManager _pairManager; private readonly ChatService _chatService; + private readonly GuiHookService _guiHookService; private readonly PerformanceCollectorService _performanceCollector; private readonly ServerConfigurationManager _serverConfigurationManager; private readonly UiSharedService _uiShared; @@ -73,7 +74,7 @@ public class SettingsUi : WindowMediatorSubscriberBase public SettingsUi(ILogger logger, UiSharedService uiShared, MareConfigService configService, - MareCharaFileManager mareCharaFileManager, PairManager pairManager, ChatService chatService, + MareCharaFileManager mareCharaFileManager, PairManager pairManager, ChatService chatService, GuiHookService guiHookService, ServerConfigurationManager serverConfigurationManager, MareMediator mediator, PerformanceCollectorService performanceCollector, DalamudUtilService dalamudUtilService, @@ -87,6 +88,7 @@ public class SettingsUi : WindowMediatorSubscriberBase _mareCharaFileManager = mareCharaFileManager; _pairManager = pairManager; _chatService = chatService; + _guiHookService = guiHookService; _serverConfigurationManager = serverConfigurationManager; _performanceCollector = performanceCollector; _dalamudUtilService = dalamudUtilService; @@ -1052,6 +1054,26 @@ public class SettingsUi : WindowMediatorSubscriberBase } } + var useNameColors = _configService.Current.UseNameColors; + var nameColors = _configService.Current.NameColors; + if (ImGui.Checkbox("Color nameplates of paired players", ref useNameColors)) + { + _configService.Current.UseNameColors = useNameColors; + _configService.Save(); + _guiHookService.RequestRedraw(); + } + + using (ImRaii.Disabled(!useNameColors)) + { + using var indent = ImRaii.PushIndent(); + if (InputDtrColors("Character Name Color", ref nameColors)) + { + _configService.Current.NameColors = nameColors; + _configService.Save(); + _guiHookService.RequestRedraw(); + } + } + if (ImGui.Checkbox("Show separate Visible group", ref showVisibleSeparate)) { _configService.Current.ShowVisibleUsersSeparately = showVisibleSeparate;