From d7fa3a5097c16ba3fc5c38bdbefb32d2e702abb3 Mon Sep 17 00:00:00 2001 From: Wessel Kuipers Date: Sat, 13 Jul 2024 00:42:59 +0200 Subject: [PATCH 1/9] update netstone --- NetStone | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetStone b/NetStone index eae6cee..4d21f40 160000 --- a/NetStone +++ b/NetStone @@ -1 +1 @@ -Subproject commit eae6cee904e15b8d3baf76b1335a63e97c686e41 +Subproject commit 4d21f40497f701d6fe1afb433bbcb565f2b3544a From cdac2ff7bc50484a13d90f53ac4f251ee467a397 Mon Sep 17 00:00:00 2001 From: Wessel Kuipers Date: Sat, 13 Jul 2024 02:38:02 +0200 Subject: [PATCH 2/9] Update nameplate logic to use service provided by Dalamud --- FCNameColor/API/FCNameColorProvider.cs | 5 +- FCNameColor/Config/ConfigurationMigrator.cs | 4 +- FCNameColor/Config/ConfigurationV1.cs | 4 +- FCNameColor/FCNameColor.csproj | 9 +- FCNameColor/FCNameColor.json | 2 +- FCNameColor/Plugin.cs | 187 ++++++++++---------- FCNameColor/packages.lock.json | 14 +- 7 files changed, 110 insertions(+), 115 deletions(-) diff --git a/FCNameColor/API/FCNameColorProvider.cs b/FCNameColor/API/FCNameColorProvider.cs index 0c2b811..cbb65fa 100644 --- a/FCNameColor/API/FCNameColorProvider.cs +++ b/FCNameColor/API/FCNameColorProvider.cs @@ -3,6 +3,7 @@ using Dalamud.Logging; using Dalamud.Plugin; using Dalamud.Plugin.Ipc; +using Dalamud.Plugin.Services; namespace FCNameColor { @@ -24,7 +25,7 @@ public class FCNameColorProvider private readonly ICallGateProvider providerAddPlayerToIgnoredPlayers; private readonly ICallGateProvider providerRemovePlayerFromIgnoredPlayers; - public FCNameColorProvider(DalamudPluginInterface pluginInterface, IFCNameColorAPI api) + public FCNameColorProvider(IDalamudPluginInterface pluginInterface, IFCNameColorAPI api, IPluginLog pluginLog) { try { @@ -57,7 +58,7 @@ public FCNameColorProvider(DalamudPluginInterface pluginInterface, IFCNameColorA } catch (Exception ex) { - PluginLog.LogError($"Error registering IPC provider:\n{ex}"); + pluginLog.Error($"Error registering IPC provider:\n{ex}"); } } diff --git a/FCNameColor/Config/ConfigurationMigrator.cs b/FCNameColor/Config/ConfigurationMigrator.cs index f56094e..ca312e8 100644 --- a/FCNameColor/Config/ConfigurationMigrator.cs +++ b/FCNameColor/Config/ConfigurationMigrator.cs @@ -13,11 +13,11 @@ namespace FCNameColor.Config { public class ConfigurationMigrator { - private DalamudPluginInterface pi; + private IDalamudPluginInterface pi; private IPluginLog pluginLog; private IChatGui chat; - public ConfigurationV1 GetConfig(DalamudPluginInterface pi, IPluginLog pluginLog, IChatGui chat) + public ConfigurationV1 GetConfig(IDalamudPluginInterface pi, IPluginLog pluginLog, IChatGui chat) { this.pi = pi; this.pluginLog = pluginLog; diff --git a/FCNameColor/Config/ConfigurationV1.cs b/FCNameColor/Config/ConfigurationV1.cs index b95f742..4b8d908 100644 --- a/FCNameColor/Config/ConfigurationV1.cs +++ b/FCNameColor/Config/ConfigurationV1.cs @@ -86,7 +86,7 @@ public class ConfigurationV1 : IPluginConfiguration public Dictionary FCs { get; set; } = new(); #endregion - [NonSerialized] private DalamudPluginInterface pluginInterface; + [NonSerialized] private IDalamudPluginInterface pluginInterface; [NonSerialized] public static KeyValuePair[] DefaultGroups = { @@ -100,7 +100,7 @@ public class ConfigurationV1 : IPluginConfiguration }) }; - public void Initialize(DalamudPluginInterface pluginInterface) + public void Initialize(IDalamudPluginInterface pluginInterface) { this.pluginInterface = pluginInterface; } diff --git a/FCNameColor/FCNameColor.csproj b/FCNameColor/FCNameColor.csproj index 5ccec34..d53f529 100644 --- a/FCNameColor/FCNameColor.csproj +++ b/FCNameColor/FCNameColor.csproj @@ -1,13 +1,13 @@  {13C812E9-0D42-4B95-8646-40EEBF30636F} - net7.0-windows + net8.0-windows7.0 9.0 FCNameColor FCNameColor Copyright © 2023 - 4.0.0.3 - 4.0.0.3 + 5.0.0.0 + 5.0.0.0 true bin\$(Configuration)\ false @@ -30,8 +30,7 @@ $(DALAMUD_HOME)/ - - + $(DalamudLibPath)FFXIVClientStructs.dll false diff --git a/FCNameColor/FCNameColor.json b/FCNameColor/FCNameColor.json index 38c495b..89f5ca2 100644 --- a/FCNameColor/FCNameColor.json +++ b/FCNameColor/FCNameColor.json @@ -5,7 +5,7 @@ "CategoryTags": ["ui", "social"], "Description": "Color your FC’s tag or the entire nameplate if they are in your FC or other specified FCs.\nWorks using Lodestone data, so it can continue working inside duties as well as on different servers!", "InternalName": "FCNameColor", - "AssemblyVersion": "4.0.0.3", + "AssemblyVersion": "5.0.0.0", "RepoUrl": "https://github.com/WesselKuipers/FCNameColor", "IconUrl": "https://github.com/WesselKuipers/FCNameColor/raw/main/images/icon.png", "Tags": ["nameplate", "FC", "name", "colors"], diff --git a/FCNameColor/Plugin.cs b/FCNameColor/Plugin.cs index 21cb707..a7e4f5c 100644 --- a/FCNameColor/Plugin.cs +++ b/FCNameColor/Plugin.cs @@ -13,13 +13,10 @@ using NetStone.Model.Parseables.FreeCompany.Members; using NetStone.Search.Character; using Dalamud.Plugin.Services; -using Pilz.Dalamud; -using Pilz.Dalamud.Nameplates; -using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Game.ClientState.Objects.Enums; -using Pilz.Dalamud.Nameplates.Tools; -using Pilz.Dalamud.Tools.Strings; using FCNameColor.Config; +using Dalamud.Game.Gui.NamePlate; +using Dalamud.Game.Text.SeStringHandling; namespace FCNameColor { @@ -29,7 +26,7 @@ public class Plugin : IDalamudPlugin private const string CommandName = "/fcnc"; private readonly ConfigurationV1 config; - [PluginService] public static DalamudPluginInterface Pi { get; private set; } + [PluginService] public static IDalamudPluginInterface Pi { get; private set; } [PluginService] public static ISigScanner SigScanner { get; private set; } [PluginService] public static IClientState ClientState { get; private set; } [PluginService] public static IChatGui Chat { get; private set; } @@ -39,6 +36,7 @@ public class Plugin : IDalamudPlugin [PluginService] public static IFramework Framework { get; private set; } [PluginService] public static IGameInteropProvider GameInteropProvider { get; private set; } [PluginService] public static IPluginLog PluginLog { get; private set; } + [PluginService] public static INamePlateGui NamePlateGui { get; private set; } private Dictionary WorldNames; private LodestoneClient lodestoneClient; @@ -63,7 +61,6 @@ public class Plugin : IDalamudPlugin public string PlayerKey; public bool SearchingFC; public string SearchingFCError = ""; - public NameplateManager NameplateManager { get; init; } public Plugin(IDataManager dataManager) { @@ -109,9 +106,7 @@ public Plugin(IDataManager dataManager) HelpMessage = "Opens the FCNameColor Config." }); - PluginServices.Initialize(Pi); - NameplateManager = new(); - NameplateManager.Hooks.AddonNamePlate_SetPlayerNameManaged += Hooks_AddonNamePlate_SetPlayerNameManaged; + NamePlateGui.OnNamePlateUpdate += this.NamePlateGui_OnNamePlateUpdate; timer.Elapsed += delegate { @@ -127,7 +122,7 @@ public Plugin(IDataManager dataManager) Pi.UiBuilder.Draw += DrawUI; Pi.UiBuilder.OpenConfigUi += DrawConfigUI; - fcNameColorProvider = new FCNameColorProvider(Pi, new FCNameColorAPI(config, PluginLog)); + fcNameColorProvider = new FCNameColorProvider(Pi, new FCNameColorAPI(config, PluginLog), PluginLog); } private void OnCommand(string command, string args) @@ -480,120 +475,127 @@ async void ScheduleFCUpdates() } } - private void ApplyNameplateColor(NameplateChanges nameplateChanges, NameplateElements type, string uiColor) + private (SeString, SeString) CreateTextWrap(UInt16 uiColor) { - var color = Convert.ToUInt16(uiColor); - var before = nameplateChanges.GetChange(type, StringPosition.Before); - before.Payloads.Add(new UIForegroundPayload(color)); - before.Payloads.Add(new UIGlowPayload(config.Glow ? color : (ushort)0)); - - var after = nameplateChanges.GetChange(type, StringPosition.After); - after.Payloads.Add(UIGlowPayload.UIGlowOff); - after.Payloads.Add(UIForegroundPayload.UIForegroundOff); - } + var left = new SeStringBuilder(); + var right = new SeStringBuilder(); + left.AddUiForeground(uiColor); + right.AddUiForegroundOff(); + + if (config.Glow) + { + PluginLog.Info(uiColor.ToString()); + left.AddUiGlow(uiColor); + right.AddUiGlowOff(); + } + + return (left.BuiltString, right.BuiltString); + } - private void Hooks_AddonNamePlate_SetPlayerNameManaged(Pilz.Dalamud.Nameplates.EventArgs.AddonNamePlate_SetPlayerNameManagedEventArgs eventArgs) + private void NamePlateGui_OnNamePlateUpdate(INamePlateUpdateContext context, IReadOnlyList handlers) { if (!config.Enabled || !NotInFC && (!FC.HasValue || FC?.Members == null || FC?.Members.Length == 0) || ClientState.IsPvPExcludingDen) { return; } - try + foreach (var handler in handlers) { - var playerCharacter = NameplateManager.GetNameplateGameObject(eventArgs.SafeNameplateObject); - if (playerCharacter == null) { return; } - if (playerCharacter.ObjectKind != ObjectKind.Player) { return; } - - var objectID = playerCharacter.ObjectId; - var name = playerCharacter.Name.TextValue; + if (handler.NamePlateKind != NamePlateKind.PlayerCharacter) { continue; }; - if (skipCache.Contains(objectID)) { return; } - if (config.IgnoredPlayers.ContainsKey(name)) + try { - return; - } + var playerCharacter = handler.PlayerCharacter; + if (playerCharacter == null) { continue; } - var isLocalPlayer = ClientState?.LocalPlayer?.ObjectId == objectID; - var isInDuty = Condition[ConditionFlag.BoundByDuty56]; + var entityId = playerCharacter.EntityId; + var name = playerCharacter.Name.TextValue; - if (isInDuty && isLocalPlayer) { return; } - if (!isInDuty && config.OnlyDuties) { return; } - if (!isInDuty && isLocalPlayer && !config.IncludeSelf) { return; } - // Skip any player who is dead, colouring the name of dead characters makes them harder to recognize. - if (playerCharacter.CurrentHp == 0) { return; } + if (skipCache.Contains(entityId)) { continue; } + if (config.IgnoredPlayers.ContainsKey(name)) { continue; } + var isLocalPlayer = ClientState?.LocalPlayer?.EntityId == entityId; + var isInDuty = Condition[ConditionFlag.BoundByDuty56]; - var isInParty = playerCharacter.StatusFlags.HasFlag(StatusFlags.PartyMember); - var isInAlliance = playerCharacter.StatusFlags.HasFlag(StatusFlags.AllianceMember); - var isFriend = playerCharacter.StatusFlags.HasFlag(StatusFlags.Friend); + if (isInDuty && isLocalPlayer) { continue; } + if (!isInDuty && config.OnlyDuties) { continue; } + if (!isInDuty && isLocalPlayer && !config.IncludeSelf) { continue; } + // Skip any player who is dead, colouring the name of dead characters makes them harder to recognize. + if (playerCharacter.CurrentHp == 0) { continue; } - if (config.IgnoreFriends && isFriend) { return; } - var world = playerCharacter.HomeWorld.GameData.Name; - var group = NotInFC ? config.Groups.First().Value : config.Groups.GetValueOrDefault(config.FCGroups[PlayerKey][FC.Value.ID], ConfigurationV1.DefaultGroups[0].Value); - var color = group.Color; - var uiColor = group.UiColor; + var isInParty = playerCharacter.StatusFlags.HasFlag(StatusFlags.PartyMember); + var isInAlliance = playerCharacter.StatusFlags.HasFlag(StatusFlags.AllianceMember); + var isFriend = playerCharacter.StatusFlags.HasFlag(StatusFlags.Friend); - if (NotInFC || (FC.HasValue && !FC.Value.Members.Any(member => member.Name == name))) - { - var additionalFCIndex = TrackedFCs.FindIndex(f => f.World == world && f.Members.Any(m => m.Name == name)); - if (additionalFCIndex < 0) - { - // This player isn’t an FC member or in one of the tracked FCs. - // We can skip it in future calls. - PluginLog.Debug("Adding {name} ({id}) to skip cache", name, objectID); - skipCache.Add(objectID); - return; - } + if (config.IgnoreFriends && isFriend) { continue; } - var id = TrackedFCs[additionalFCIndex].ID; - var groupName = config.FCGroups[PlayerKey].ContainsKey(id) ? config.FCGroups[PlayerKey][id] : "Default"; - if (!config.Groups.ContainsKey(groupName)) - { - config.Groups.Add(groupName, ConfigurationV1.DefaultGroups[1].Value); - } + var world = playerCharacter.HomeWorld.GameData.Name; + var group = NotInFC ? config.Groups.First().Value : config.Groups.GetValueOrDefault(config.FCGroups[PlayerKey][FC.Value.ID], ConfigurationV1.DefaultGroups[0].Value); + var color = group.Color; + var uiColor = group.UiColor; - var trackedGroup = config.Groups[groupName]; - color = trackedGroup.Color; - uiColor = trackedGroup.UiColor; - } + if (NotInFC || (FC.HasValue && !FC.Value.Members.Any(member => member.Name == name))) + { + var additionalFCIndex = TrackedFCs.FindIndex(f => f.World == world && f.Members.Any(m => m.Name == name)); + if (additionalFCIndex < 0) + { + // This player isn’t an FC member or in one of the tracked FCs. + // We can skip it in future calls. + PluginLog.Debug("Adding {name} ({id}) to skip cache", name, entityId); + skipCache.Add(entityId); + continue; + } - var nameplateChanges = new NameplateChanges(eventArgs); - var shouldReplaceName = !config.OnlyColorFCTag && !isLocalPlayer; - if (!isInDuty && !shouldReplaceName) - { - ApplyNameplateColor(nameplateChanges, NameplateElements.FreeCompany, uiColor); - } + var id = TrackedFCs[additionalFCIndex].ID; + var groupName = config.FCGroups[PlayerKey].ContainsKey(id) ? config.FCGroups[PlayerKey][id] : "Default"; + if (!config.Groups.TryGetValue(groupName, out Group value)) + { + value = ConfigurationV1.DefaultGroups[1].Value; + config.Groups.Add(groupName, value); + } - if ((isInDuty && config.IncludeDuties) || shouldReplaceName) - { - ApplyNameplateColor(nameplateChanges, NameplateElements.Name, uiColor); + var trackedGroup = value; + color = trackedGroup.Color; + uiColor = trackedGroup.UiColor; + } - if (eventArgs.IsTitleVisible && eventArgs.Title.TextValue.Length > 0) + var shouldReplaceName = !config.OnlyColorFCTag && !isLocalPlayer; + var wrapper = CreateTextWrap(UInt16.Parse(uiColor)); + if (!isInDuty && !shouldReplaceName) { - ApplyNameplateColor(nameplateChanges, NameplateElements.Title, uiColor); + handler.FreeCompanyTagParts.OuterWrap = wrapper; } - if (!isInDuty) + if ((isInDuty && config.IncludeDuties) || shouldReplaceName) { - ApplyNameplateColor(nameplateChanges, NameplateElements.FreeCompany, uiColor); + handler.NameParts.TextWrap = wrapper; + + if (handler.DisplayTitle && handler.TitleParts.Text.TextValue.Length > 0) + { + handler.TitleParts.OuterWrap = wrapper; + } + + if (!isInDuty) + { + handler.FreeCompanyTagParts.OuterWrap = wrapper; + } } - } #if DEBUG - PluginLog.Verbose("Overriding player nameplate for {name} (ObjectID {objectID})", name, objectID); + PluginLog.Verbose("Overriding player nameplate for {name} (ObjectID {objectID})", name, entityId); #endif - - NameplateUpdateFactory.ApplyNameplateChanges(new NameplateChangesProps(nameplateChanges)); - } - catch (Exception e) - { - PluginLog.Error("Something went wrong when trying to run the nameplate logic."); - PluginLog.Error("Error message: {e}", e.Message); + } + catch (Exception e) + { + PluginLog.Error("Something went wrong when trying to run the nameplate logic."); + PluginLog.Error("Error message: {e}", e.Message); + continue; + } } + } protected virtual void Dispose(bool disposing) @@ -608,8 +610,7 @@ protected virtual void Dispose(bool disposing) Commands.RemoveHandler(CommandName); Framework.Update -= OnFrameworkUpdate; ClientState.Login -= OnLogin; - NameplateManager.Hooks.AddonNamePlate_SetPlayerNameManaged -= Hooks_AddonNamePlate_SetPlayerNameManaged; - NameplateManager.Dispose(); + NamePlateGui.OnNamePlateUpdate -= NamePlateGui_OnNamePlateUpdate; } catch (Exception ex) { diff --git a/FCNameColor/packages.lock.json b/FCNameColor/packages.lock.json index 766826e..50392f1 100644 --- a/FCNameColor/packages.lock.json +++ b/FCNameColor/packages.lock.json @@ -1,18 +1,12 @@ { "version": 1, "dependencies": { - "net7.0-windows7.0": { + "net8.0-windows7.0": { "DalamudPackager": { "type": "Direct", - "requested": "[2.1.12, )", - "resolved": "2.1.12", - "contentHash": "Sc0PVxvgg4NQjcI8n10/VfUQBAS4O+Fw2pZrAqBdRMbthYGeogzu5+xmIGCGmsEZ/ukMOBuAqiNiB5qA3MRalg==" - }, - "Pilz.Dalamud": { - "type": "Direct", - "requested": "[0.5.2.1, )", - "resolved": "0.5.2.1", - "contentHash": "Sy4eMnNwRMCNgiJnsIZ55le1XRe/K9dJfIVn+yLXj7zzUCTN99obCxfEHvHu2OxIP2DiXOaiZjHBwydidv0Y6Q==" + "requested": "[2.1.13, )", + "resolved": "2.1.13", + "contentHash": "rMN1omGe8536f4xLMvx9NwfvpAc9YFFfeXJ1t4P4PE6Gu8WCIoFliR1sh07hM+bfODmesk/dvMbji7vNI+B/pQ==" }, "HtmlAgilityPack": { "type": "Transitive", From bf751f98be0487dddb51289883f3cb72a96aab25 Mon Sep 17 00:00:00 2001 From: Wessel Kuipers Date: Sat, 13 Jul 2024 06:24:31 +0200 Subject: [PATCH 3/9] Rework group handling and add RGB colours! --- FCNameColor/Plugin.cs | 20 +++-- FCNameColor/UI/PluginUI.cs | 157 ++++++++++--------------------------- 2 files changed, 51 insertions(+), 126 deletions(-) diff --git a/FCNameColor/Plugin.cs b/FCNameColor/Plugin.cs index a7e4f5c..cb0e5f1 100644 --- a/FCNameColor/Plugin.cs +++ b/FCNameColor/Plugin.cs @@ -6,7 +6,6 @@ using Dalamud.Game; using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.Command; -using Dalamud.Game.Text.SeStringHandling.Payloads; using Dalamud.IoC; using Dalamud.Plugin; using NetStone; @@ -17,6 +16,8 @@ using FCNameColor.Config; using Dalamud.Game.Gui.NamePlate; using Dalamud.Game.Text.SeStringHandling; +using Lumina.Text; +using System.Numerics; namespace FCNameColor { @@ -475,22 +476,21 @@ async void ScheduleFCUpdates() } } - private (SeString, SeString) CreateTextWrap(UInt16 uiColor) + private (Dalamud.Game.Text.SeStringHandling.SeString, Dalamud.Game.Text.SeStringHandling.SeString) CreateTextWrap(Vector4 color) { - var left = new SeStringBuilder(); - var right = new SeStringBuilder(); + var left = new Lumina.Text.SeStringBuilder(); + var right = new Dalamud.Game.Text.SeStringHandling.SeStringBuilder(); - left.AddUiForeground(uiColor); + left.PushColorRgba(color); right.AddUiForegroundOff(); if (config.Glow) { - PluginLog.Info(uiColor.ToString()); - left.AddUiGlow(uiColor); + left.PushEdgeColorRgba(color); right.AddUiGlowOff(); } - return (left.BuiltString, right.BuiltString); + return ((Dalamud.Game.Text.SeStringHandling.SeString)left.ToSeString(), right.BuiltString); } private void NamePlateGui_OnNamePlateUpdate(INamePlateUpdateContext context, IReadOnlyList handlers) @@ -534,7 +534,6 @@ private void NamePlateGui_OnNamePlateUpdate(INamePlateUpdateContext context, IRe var world = playerCharacter.HomeWorld.GameData.Name; var group = NotInFC ? config.Groups.First().Value : config.Groups.GetValueOrDefault(config.FCGroups[PlayerKey][FC.Value.ID], ConfigurationV1.DefaultGroups[0].Value); var color = group.Color; - var uiColor = group.UiColor; if (NotInFC || (FC.HasValue && !FC.Value.Members.Any(member => member.Name == name))) { @@ -558,11 +557,10 @@ private void NamePlateGui_OnNamePlateUpdate(INamePlateUpdateContext context, IRe var trackedGroup = value; color = trackedGroup.Color; - uiColor = trackedGroup.UiColor; } var shouldReplaceName = !config.OnlyColorFCTag && !isLocalPlayer; - var wrapper = CreateTextWrap(UInt16.Parse(uiColor)); + var wrapper = CreateTextWrap(color); if (!isInDuty && !shouldReplaceName) { handler.FreeCompanyTagParts.OuterWrap = wrapper; diff --git a/FCNameColor/UI/PluginUI.cs b/FCNameColor/UI/PluginUI.cs index efa4555..521d8e5 100644 --- a/FCNameColor/UI/PluginUI.cs +++ b/FCNameColor/UI/PluginUI.cs @@ -8,6 +8,7 @@ using Dalamud.Interface.Colors; using Dalamud.Interface.Components; using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility.Raii; using Dalamud.Plugin.Services; using FCNameColor.Config; using ImGuiNET; @@ -32,15 +33,13 @@ internal class PluginUI : IDisposable { private readonly Plugin plugin; private readonly ConfigurationV1 configuration; - private readonly List uiColors; private bool showIgnoreList; private bool showAdditionalFCConfig; private bool showAddAdditionalFC; private string fcUrl = ""; private FCMember currentIgnoredPlayer; private readonly IClientState clientState; - private string currentGroup; - private IPluginLog PluginLog; + private readonly IPluginLog pluginLog; private readonly Regex fcUrlPattern = new Regex(@"https:\/\/(eu|na|jp).finalfantasyxiv.com\/lodestone\/freecompany\/(\d{19})\/*"); @@ -61,33 +60,7 @@ public PluginUI(ConfigurationV1 config, IDataManager data, Plugin plugin, IClien configuration = config; this.clientState = clientState; this.plugin = plugin; - currentGroup = "Default"; - - var list = new List(data.GetExcelSheet()!.Distinct(new UIColorComparer())); - list.Sort((a, b) => - { - var colorA = ConvertUIColorToColor(a); - var colorB = ConvertUIColorToColor(b); - ImGui.ColorConvertRGBtoHSV(colorA.X, colorA.Y, colorA.Z, out var aH, out var aS, out var aV); - ImGui.ColorConvertRGBtoHSV(colorB.X, colorB.Y, colorB.Z, out var bH, out var bS, out var bV); - - var hue = aH.CompareTo(bH); - if (hue != 0) - { - return hue; - } - - var saturation = aS.CompareTo(bS); - if (saturation != 0) - { - return saturation; - } - - var value = aV.CompareTo(bV); - return value != 0 ? value : 0; - }); - uiColors = list; - PluginLog = pluginLog; + this.pluginLog = pluginLog; } public void Dispose() @@ -243,56 +216,61 @@ private void DrawMainWindow() ImGui.Separator(); var groups = configuration.Groups.Keys.Where(a => a != "Other FC" && a != "Default").Prepend("Other FC").Prepend("Default").ToArray(); - var groupIndex = Array.IndexOf(groups, currentGroup); - - ImGui.Text("Group: "); + ImGui.Text("Groups"); ImGui.SameLine(); - ImGui.SetNextItemWidth(150f * ImGuiHelpers.GlobalScale); - if (ImGui.Combo("###AdditionalFCGroup", ref groupIndex, groups, groups.Length)) - { - currentGroup = groups[groupIndex]; - } - ImGui.SameLine(); if (ImGuiComponents.IconButton(FontAwesomeIcon.Plus)) { newGroup = ""; showAddNewGroup = true; } - var deletable = !(currentGroup == "Default" || currentGroup == "Other FC"); - - ImGui.SameLine(); - - if (ImGuiComponents.IconButton(FontAwesomeIcon.Trash, new Vector4(0.8f, 0, 0, deletable ? 1f : 0.5f), - new Vector4(1f, 0, 0, deletable ? 1f : 0.5f), new Vector4(0.9f, 0, 0, deletable ? 1f : 0.5f)) && deletable) + using (var groupsPanel = ImRaii.Child("###GroupsPanel", new Vector2(ImGui.GetContentRegionAvail().X, 350.0f * ImGuiHelpers.GlobalScale))) { - if (deletable) + if (groupsPanel) { - PluginLog.Debug($"Deleting group {currentGroup}"); - configuration.Groups.Remove(currentGroup); - - foreach (var playerConfigs in configuration.FCGroups) + foreach (var (groupName, group) in configuration.Groups) { - foreach (var fcGroup in playerConfigs.Value) + var groupColor = group.Color; + if (ImGui.ColorEdit4(groupName, ref groupColor, ImGuiColorEditFlags.NoInputs | ImGuiColorEditFlags.NoAlpha)) + { + var newGroup = configuration.Groups[groupName]; + newGroup.Color = groupColor; + configuration.Groups[groupName] = newGroup; + configuration.Save(); + } + + if (groupName != "Default" && groupName != "Other FC") { - if (fcGroup.Value == currentGroup) + ImGui.SameLine(); + using var id = ImRaii.PushId(groupName); + if (ImGuiComponents.IconButton(FontAwesomeIcon.Trash, new Vector4(0.8f, 0, 0, 1f), new Vector4(1f, 0, 0, 1f), new Vector4(0.9f, 0, 0, 1f))) + { + pluginLog.Debug($"Deleting group {groupName}"); + configuration.Groups.Remove(groupName); + + foreach (var playerConfigs in configuration.FCGroups) + { + foreach (var fcGroup in playerConfigs.Value) + { + if (fcGroup.Value == groupName) + { + configuration.FCGroups[playerConfigs.Key][fcGroup.Key] = "Other FC"; + } + } + } + + configuration.Save(); + } + + if (ImGui.IsItemHovered()) { - configuration.FCGroups[playerConfigs.Key][fcGroup.Key] = "Other FC"; + ImGui.SetTooltip($"Delete group {groupName}.\nThe groups Default and Other FC cannot be removed."); } } } - - currentGroup = "Default"; - configuration.Save(); } - - } - - if (ImGui.IsItemHovered()) - { - ImGui.SetTooltip($"Delete group {currentGroup}.\nThe groups Default and Other FC cannot be removed."); } if (showAddNewGroup) @@ -330,62 +308,11 @@ private void DrawMainWindow() Color = new Vector4(0.07450981f, 0.8f, 0.6392157f, 1f) }); configuration.Save(); - currentGroup = newGroup; showAddNewGroup = false; } ImGui.End(); } - - ImGui.Text("Settings for"); - ImGui.SameLine(); - ImGui.TextColored(configuration.Groups[currentGroup].Color, currentGroup); - ImGui.ColorButton("Nameplate color. Click on a color below to select a new one.", configuration.Groups[currentGroup].Color); - ImGui.SameLine(); - ImGui.Text("Nameplate color. Click on a color below to set a new one."); - - ImGui.Columns(12, "columns", false); - foreach (var z in uiColors) - { - if (z.UIForeground is 0 or 255) - { - continue; - } - - var color = ConvertUIColorToColor(z); - var id = z.RowId.ToString(); - var oldCursor = ImGui.GetCursorPos(); - - if (ImGui.ColorButton(id, color)) - { - - var group = configuration.Groups[currentGroup]; - group.Color = color; - group.UiColor = id; - configuration.Groups[currentGroup] = group; - - - configuration.Save(); - } - - if (id == (configuration.Groups[currentGroup].UiColor)) - { - // For the selected colour, render a transparent checkmark on top - var newCursor = ImGui.GetCursorPos(); - ImGui.SetCursorPos(oldCursor); - ImGui.PushStyleColor(ImGuiCol.FrameBgActive, Vector4.Zero); - ImGui.PushStyleColor(ImGuiCol.FrameBgHovered, Vector4.Zero); - ImGui.PushStyleColor(ImGuiCol.FrameBg, Vector4.Zero); - var selected = true; - ImGui.Checkbox("", ref selected); - ImGui.PopStyleColor(3); - ImGui.SetCursorPos(newCursor); - } - - ImGui.NextColumn(); - } - - ImGui.Columns(1); } ImGui.Separator(); @@ -562,13 +489,13 @@ private void DrawMainWindow() if (ImGuiComponents.IconButton(FontAwesomeIcon.Trash, new Vector4(0.8f, 0, 0, 1f), new Vector4(1f, 0, 0, 1f), new Vector4(0.9f, 0, 0, 1f))) { - PluginLog.Debug("Deleting additional FC {fc}", fc.Name); + pluginLog.Debug("Deleting additional FC {fc}", fc.Name); configuration.FCGroups[plugin.PlayerKey].Remove(id); var shouldDeleteFC = !configuration.FCGroups.Any(character => character.Value.ContainsValue(groupName)); if (shouldDeleteFC) { configuration.FCs.Remove(fc.ID); - PluginLog.Debug("Removing FC {name} altogether, no settings found anymore.", fc.Name); + pluginLog.Debug("Removing FC {name} altogether, no settings found anymore.", fc.Name); } configuration.Save(); } From e44d14ccb426d5e1432f918293f0c3b4fc9b85a1 Mon Sep 17 00:00:00 2001 From: Wessel Kuipers Date: Sat, 13 Jul 2024 16:15:40 +0200 Subject: [PATCH 4/9] Adjust size of config window --- FCNameColor/UI/PluginUI.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/FCNameColor/UI/PluginUI.cs b/FCNameColor/UI/PluginUI.cs index 521d8e5..ed270de 100644 --- a/FCNameColor/UI/PluginUI.cs +++ b/FCNameColor/UI/PluginUI.cs @@ -79,8 +79,8 @@ private void DrawMainWindow() return; } - ImGui.SetNextWindowSize(new Vector2(380, 550), ImGuiCond.FirstUseEver); - ImGui.SetNextWindowSizeConstraints(new Vector2(380, 550), new Vector2(float.MaxValue, float.MaxValue)); + ImGui.SetNextWindowSize(new Vector2(380, 300), ImGuiCond.FirstUseEver); + ImGui.SetNextWindowSizeConstraints(new Vector2(380, 300), new Vector2(float.MaxValue, float.MaxValue)); if (ImGui.Begin("FC Name Color Config", ref visible, ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse | ImGuiWindowFlags.AlwaysAutoResize)) @@ -226,7 +226,7 @@ private void DrawMainWindow() showAddNewGroup = true; } - using (var groupsPanel = ImRaii.Child("###GroupsPanel", new Vector2(ImGui.GetContentRegionAvail().X, 350.0f * ImGuiHelpers.GlobalScale))) + using (var groupsPanel = ImRaii.Child("###GroupsPanel", new Vector2(ImGui.GetContentRegionAvail().X, 300.0f * ImGuiHelpers.GlobalScale))) { if (groupsPanel) { From a6bb73891e67461325b254cc8469b5815659982f Mon Sep 17 00:00:00 2001 From: Wessel Kuipers Date: Sat, 13 Jul 2024 20:23:29 +0200 Subject: [PATCH 5/9] Migrate to WiindowSystem --- FCNameColor/API/FCNameColorProvider.cs | 1 - FCNameColor/Plugin.cs | 34 +- FCNameColor/UI/AddAdditionalFCWindow.cs | 132 +++++ FCNameColor/UI/AddNewGroupWindow.cs | 70 +++ FCNameColor/UI/AdditionalFCsWindow.cs | 116 +++++ FCNameColor/UI/IgnoreListWindow.cs | 108 ++++ FCNameColor/UI/PluginUI.cs | 650 ++++++------------------ 7 files changed, 611 insertions(+), 500 deletions(-) create mode 100644 FCNameColor/UI/AddAdditionalFCWindow.cs create mode 100644 FCNameColor/UI/AddNewGroupWindow.cs create mode 100644 FCNameColor/UI/AdditionalFCsWindow.cs create mode 100644 FCNameColor/UI/IgnoreListWindow.cs diff --git a/FCNameColor/API/FCNameColorProvider.cs b/FCNameColor/API/FCNameColorProvider.cs index cbb65fa..eb0142d 100644 --- a/FCNameColor/API/FCNameColorProvider.cs +++ b/FCNameColor/API/FCNameColorProvider.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using Dalamud.Logging; using Dalamud.Plugin; using Dalamud.Plugin.Ipc; using Dalamud.Plugin.Services; diff --git a/FCNameColor/Plugin.cs b/FCNameColor/Plugin.cs index cb0e5f1..6045d7d 100644 --- a/FCNameColor/Plugin.cs +++ b/FCNameColor/Plugin.cs @@ -15,9 +15,9 @@ using Dalamud.Game.ClientState.Objects.Enums; using FCNameColor.Config; using Dalamud.Game.Gui.NamePlate; -using Dalamud.Game.Text.SeStringHandling; -using Lumina.Text; using System.Numerics; +using Dalamud.Interface.Windowing; +using FCNameColor.UI; namespace FCNameColor { @@ -38,10 +38,12 @@ public class Plugin : IDalamudPlugin [PluginService] public static IGameInteropProvider GameInteropProvider { get; private set; } [PluginService] public static IPluginLog PluginLog { get; private set; } [PluginService] public static INamePlateGui NamePlateGui { get; private set; } + public readonly WindowSystem WindowSystem = new("FC Name Color"); private Dictionary WorldNames; private LodestoneClient lodestoneClient; private readonly FCNameColorProvider fcNameColorProvider; + private PluginUI UI { get; } private bool loggingIn; private readonly Timer timer = new() { Interval = 1000 }; @@ -62,6 +64,7 @@ public class Plugin : IDalamudPlugin public string PlayerKey; public bool SearchingFC; public string SearchingFCError = ""; + public bool ConfigOpen => UI.IsOpen; public Plugin(IDataManager dataManager) { @@ -100,14 +103,25 @@ public Plugin(IDataManager dataManager) } } - UI = new PluginUI(config, dataManager, this, ClientState, PluginLog); + var addNewGroupWindow = new AddNewGroupWindow(config, this); + var ignoreListWindow = new IgnoreListWindow(config, this); + var addAdditionalFCWindow = new AddAdditionalFCWindow(config, this); + var additionalFCsWindow = new AdditionalFCsWindow(config, this, PluginLog, addAdditionalFCWindow); + + UI = new PluginUI(config, dataManager, this, ClientState, PluginLog, addNewGroupWindow, ignoreListWindow, additionalFCsWindow); + WindowSystem.AddWindow(UI); + WindowSystem.AddWindow(addNewGroupWindow); + WindowSystem.AddWindow(ignoreListWindow); + WindowSystem.AddWindow(addAdditionalFCWindow); + WindowSystem.AddWindow(additionalFCsWindow); + Commands.AddHandler(CommandName, new CommandInfo(OnCommand) { HelpMessage = "Opens the FCNameColor Config." }); - NamePlateGui.OnNamePlateUpdate += this.NamePlateGui_OnNamePlateUpdate; + NamePlateGui.OnNamePlateUpdate += NamePlateGui_OnNamePlateUpdate; timer.Elapsed += delegate { @@ -120,20 +134,20 @@ public Plugin(IDataManager dataManager) ClientState.Login += OnLogin; Framework.Update += OnFrameworkUpdate; - Pi.UiBuilder.Draw += DrawUI; - Pi.UiBuilder.OpenConfigUi += DrawConfigUI; + Pi.UiBuilder.Draw += WindowSystem.Draw; + Pi.UiBuilder.OpenConfigUi += ToggleConfigUI; fcNameColorProvider = new FCNameColorProvider(Pi, new FCNameColorAPI(config, PluginLog), PluginLog); } private void OnCommand(string command, string args) { - UI.Visible = !UI.Visible; + UI.Toggle(); } - private void DrawConfigUI() + private void ToggleConfigUI() { - UI.Visible = !UI.Visible; + UI.Toggle(); } private void OnLogin() @@ -601,7 +615,7 @@ protected virtual void Dispose(bool disposing) try { if (!disposing) return; - UI.Dispose(); + WindowSystem.RemoveAllWindows(); fcNameColorProvider.Dispose(); diff --git a/FCNameColor/UI/AddAdditionalFCWindow.cs b/FCNameColor/UI/AddAdditionalFCWindow.cs new file mode 100644 index 0000000..afe1c3a --- /dev/null +++ b/FCNameColor/UI/AddAdditionalFCWindow.cs @@ -0,0 +1,132 @@ +using Dalamud.Interface.Colors; +using Dalamud.Interface.Components; +using Dalamud.Interface.Utility; +using Dalamud.Interface.Windowing; +using FCNameColor.Config; +using ImGuiNET; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace FCNameColor.UI +{ + internal class AddAdditionalFCWindow : Window + { + private readonly ConfigurationV1 configuration; + private readonly Plugin plugin; + private readonly Regex fcUrlPattern = new Regex(@"https:\/\/(eu|na|jp).finalfantasyxiv.com\/lodestone\/freecompany\/(\d{19})\/*"); + + private string fcUrl; + + public AddAdditionalFCWindow(ConfigurationV1 configuration, Plugin plugin) : base("FC Name Color Config - Adding Additional FC", ImGuiWindowFlags.AlwaysAutoResize) + { + this.configuration = configuration; + this.plugin = plugin; + } + + public override void OnOpen() + { + fcUrl = ""; + base.OnOpen(); + } + + public override bool DrawConditions() => plugin.ConfigOpen; + + public override void Draw() + { + ImGui.Spacing(); + + ImGui.Text("Please enter the lodestone URL of the FC."); + ImGui.SameLine(); + if (ImGui.SmallButton("Open Lodestone")) + { + Process.Start("explorer", "https://eu.finalfantasyxiv.com/lodestone/community/search/"); + } + + ImGui.Text( + "It should look like this: https://eu.finalfantasyxiv.com/lodestone/freecompany/1234567890123456789"); + ImGui.SetNextItemWidth(500f * ImGuiHelpers.GlobalScale); + ImGui.InputTextWithHint("###FCUrl", + "https://eu.finalfantasyxiv.com/lodestone/freecompany/1234567890123456789", + ref fcUrl, 100); + + ImGui.SameLine(); + if (plugin.SearchingFC) + { + ImGuiComponents.DisabledButton("Searching FC"); + } + else + { + var isMatch = fcUrl.Length > 0 && fcUrlPattern.IsMatch(fcUrl); + + if (!isMatch) + { + ImGuiComponents.DisabledButton("Search FC"); + } + else if (isMatch && ImGui.Button("Search FC")) + { + var match = fcUrlPattern.Match(fcUrl); + var id = match.Groups[2].Value; + var shouldContinue = true; + + if (configuration.PlayerIDs.TryGetValue(plugin.PlayerKey, out var currentPlayerID)) + { + if (configuration.PlayerFCIDs.TryGetValue(currentPlayerID, out var playerFC)) + { + if (playerFC == id) + { + ImGui.OpenPopup("###SameFC"); + shouldContinue = false; + } + } + } + + if (shouldContinue) + { + if (configuration.FCGroups[plugin.PlayerKey].ContainsKey(id)) + { + ImGui.OpenPopup("###AddFCDupe"); + } + else + { + plugin.SearchFC(id, "Other FC").ContinueWith(async success => + { + var result = await success; + if (result) + { + IsOpen = false; + } + }); + } + } + } + } + + if (fcUrl.Length > 0 && !fcUrlPattern.IsMatch(fcUrl)) + { + ImGui.TextColored(ImGuiColors.DalamudRed, "Url doesn’t match the FC url format."); + } + + if (plugin.SearchingFCError.Length > 0) + { + ImGui.TextColored(ImGuiColors.DalamudRed, plugin.SearchingFCError); + } + + if (ImGui.BeginPopup("###SameFC")) + { + ImGui.Text("This is your own FC, it’s already being tracked."); + ImGui.EndPopup(); + } + + if (ImGui.BeginPopup("###AddFCDupe")) + { + ImGui.Text("You’ve already added this FC!"); + ImGui.EndPopup(); + } + } + } +} diff --git a/FCNameColor/UI/AddNewGroupWindow.cs b/FCNameColor/UI/AddNewGroupWindow.cs new file mode 100644 index 0000000..b70700d --- /dev/null +++ b/FCNameColor/UI/AddNewGroupWindow.cs @@ -0,0 +1,70 @@ +using Dalamud.Interface.Colors; +using Dalamud.Interface.Components; +using Dalamud.Interface.Windowing; +using FCNameColor.Config; +using ImGuiNET; +using System; +using System.Linq; +using System.Numerics; + +namespace FCNameColor.UI +{ + internal class AddNewGroupWindow : Window + { + private readonly ConfigurationV1 configuration; + private readonly Plugin plugin; + private string newGroup; + + public AddNewGroupWindow(ConfigurationV1 configuration, Plugin plugin) : base("FC Name Color Config - Add new group", ImGuiWindowFlags.AlwaysAutoResize) + { + this.configuration = configuration; + this.plugin = plugin; + } + + public override void OnOpen() + { + base.OnOpen(); + newGroup = ""; + } + + public override bool DrawConditions() => plugin.ConfigOpen; + + public override void Draw() + { + var groups = configuration.Groups.Keys.Where(a => a != "Other FC" && a != "Default").Prepend("Other FC").Prepend("Default").ToArray(); + var exists = groups.Contains(newGroup); + var add = ImGui.InputTextWithHint("###NewGroup", "Your group name", ref newGroup, 50, + ImGuiInputTextFlags.EnterReturnsTrue) && newGroup.Length > 1; + + ImGui.SameLine(); + + if (newGroup.Length == 0 || exists) + { + ImGuiComponents.DisabledButton("Add Group"); + } + else + { + if (ImGui.Button("Add Group")) + { + add = true; + } + } + + if (exists) + { + ImGui.TextColored(ImGuiColors.DalamudRed, "Group names must be unique."); + } + + if (add) + { + configuration.Groups.Add(newGroup, new Group + { + UiColor = "52", + Color = new Vector4(0.07450981f, 0.8f, 0.6392157f, 1f) + }); + configuration.Save(); + IsOpen = false; + } + } + } +} diff --git a/FCNameColor/UI/AdditionalFCsWindow.cs b/FCNameColor/UI/AdditionalFCsWindow.cs new file mode 100644 index 0000000..42f8e50 --- /dev/null +++ b/FCNameColor/UI/AdditionalFCsWindow.cs @@ -0,0 +1,116 @@ +using Dalamud.Interface.Components; +using Dalamud.Interface; +using Dalamud.Interface.Windowing; +using Dalamud.Plugin.Services; +using FCNameColor.Config; +using ImGuiNET; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Text; +using System.Threading.Tasks; +using Dalamud.Interface.Utility.Raii; + +namespace FCNameColor.UI +{ + internal class AdditionalFCsWindow : Window + { + private readonly ConfigurationV1 configuration; + private readonly Plugin plugin; + private readonly IPluginLog pluginLog; + private readonly AddAdditionalFCWindow addAdditionalFCWindow; + + public AdditionalFCsWindow(ConfigurationV1 configuration, Plugin plugin, IPluginLog pluginLog, AddAdditionalFCWindow addAdditionalFCWindow) : base("FC Name Color Config - Additional FCs") + { + Size = new Vector2(320, 250); + SizeCondition = ImGuiCond.FirstUseEver; + SizeConstraints = new WindowSizeConstraints() { MinimumSize = new Vector2(320, 250), MaximumSize = new Vector2(320, 1000f) }; + + this.configuration = configuration; + this.plugin = plugin; + this.pluginLog = pluginLog; + this.addAdditionalFCWindow = addAdditionalFCWindow; + } + + public override bool DrawConditions() => plugin.ConfigOpen; + + public override void OnClose() + { + addAdditionalFCWindow.IsOpen = false; + base.OnClose(); + } + + public override void Draw() + { + ImGui.Spacing(); + ImGui.TextWrapped("Track FCs that aren’t your own."); + + if (ImGui.Button("Add FC")) + { + addAdditionalFCWindow.IsOpen = true; + plugin.SearchingFCError = ""; + } + + ImGui.Separator(); + + if (configuration.FCGroups[plugin.PlayerKey].Count == 0) + { + if (plugin.FC.HasValue) + { + configuration.FCGroups[plugin.PlayerKey][plugin.FC.Value.ID] = "Default"; + } + + ImGui.Text("There are currently no additional FCs being tracked."); + } + + foreach (var fcConfigEntry in configuration.FCGroups[plugin.PlayerKey]) + { + var id = fcConfigEntry.Key; + var groupName = fcConfigEntry.Value; + + if (!configuration.FCs.ContainsKey(id)) + { + ImGui.Text($"Fetching FC {id}..."); + continue; + } + + var fc = configuration.FCs[id]; + + using var imguiId = ImRaii.PushId(id); + ImGui.Text("Settings for"); + ImGui.SameLine(); + ImGui.TextColored(configuration.Groups[groupName].Color, fc.Name); + ImGui.ColorButton("", configuration.Groups[groupName].Color); + ImGui.SameLine(); + var groups = configuration.Groups.Keys.ToArray(); + var groupIndex = Array.IndexOf(groups, groupName); + if (ImGui.Combo("###AdditionalFCGroup", ref groupIndex, groups, groups.Length)) + { + configuration.FCGroups[plugin.PlayerKey][fc.ID] = groups[groupIndex]; + configuration.Save(); + } + + ImGui.SameLine(); + if (ImGuiComponents.IconButton(FontAwesomeIcon.Trash, new Vector4(0.8f, 0, 0, 1f), + new Vector4(1f, 0, 0, 1f), new Vector4(0.9f, 0, 0, 1f))) + { + pluginLog.Debug("Deleting additional FC {fc}", fc.Name); + configuration.FCGroups[plugin.PlayerKey].Remove(id); + var shouldDeleteFC = !configuration.FCGroups.Any(character => character.Value.ContainsValue(groupName)); + if (shouldDeleteFC) + { + configuration.FCs.Remove(fc.ID); + pluginLog.Debug("Removing FC {name} altogether, no settings found anymore.", fc.Name); + } + configuration.Save(); + } + + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip($"Delete {fc.Name}."); + } + } + } + } +} diff --git a/FCNameColor/UI/IgnoreListWindow.cs b/FCNameColor/UI/IgnoreListWindow.cs new file mode 100644 index 0000000..7d0012b --- /dev/null +++ b/FCNameColor/UI/IgnoreListWindow.cs @@ -0,0 +1,108 @@ +using Dalamud.Interface.Utility.Raii; +using Dalamud.Interface.Utility; +using Dalamud.Interface; +using Dalamud.Interface.Windowing; +using FCNameColor.Config; +using ImGuiNET; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; + +namespace FCNameColor.UI +{ + internal class IgnoreListWindow : Window + { + private readonly ConfigurationV1 configuration; + private readonly Plugin plugin; + private FCMember currentIgnoredPlayer; + + public IgnoreListWindow(ConfigurationV1 configuration, Plugin plugin) : base("FC Name Color Config - Ignore List") + { + this.configuration = configuration; + this.plugin = plugin; + + Size = new Vector2(270, 200); + SizeCondition = ImGuiCond.FirstUseEver; + } + + public override bool DrawConditions() + { + return plugin.ConfigOpen && base.DrawConditions(); + } + + public override void Draw() + { + ImGui.TextWrapped("Don’t update nameplates for these players."); + ImGui.Spacing(); + ImGui.SetNextItemWidth(150f * ImGuiHelpers.GlobalScale); + var fcMembers = GetFCMembers(); + var playerNames = fcMembers.Select(member => member.Name).ToArray(); + var playerIndex = Array.IndexOf(playerNames, currentIgnoredPlayer.Name); + ImGui.SetNextItemWidth(170f * ImGuiHelpers.GlobalScale); + + if (ImGui.Combo( + "###AddPlayerToIgnoreList", + ref playerIndex, + playerNames, + playerNames.Length)) + { + currentIgnoredPlayer = fcMembers[playerIndex]; + } + + ImGui.SameLine(); + if (ImGui.SmallButton("Add Player")) + { + if (configuration.IgnoredPlayers.ContainsKey(currentIgnoredPlayer.Name)) + { + ImGui.OpenPopup("###AddPlayerToIgnoreListDupe"); + } + else + { + configuration.IgnoredPlayers.Add(currentIgnoredPlayer.Name, + currentIgnoredPlayer.ID); + configuration.Save(); + } + } + + using (var visible = ImRaii.Popup("###AddPlayerToIgnoreListDupe")) + { + if (visible) + { + ImGui.Text("You’ve already added this player!"); + } + } + + foreach (var (key, _) in configuration.IgnoredPlayers.ToList()) + { + ImGui.Spacing(); + ImGui.Text(key); + ImGui.SameLine(); + ImGui.BeginGroup(); + ImGui.PushFont(UiBuilder.IconFont); + ImGui.Text(FontAwesomeIcon.Times.ToIconString()); + ImGui.PopFont(); + ImGui.EndGroup(); + if (!ImGui.IsItemClicked(ImGuiMouseButton.Left)) continue; + configuration.IgnoredPlayers.Remove(key); + configuration.Save(); + } + } + + private List GetFCMembers() + { + var fcMembers = new List(); + var playersFCs = configuration.PlayerFCIDs; + // TODO: Should this fetch *every* tracked FC or just the player's FCs? + foreach (var playerFCID in playersFCs) + { + var exists = configuration.FCs.TryGetValue(playerFCID.Value, out var fc); + fcMembers.AddRange(fc.Members); + } + + fcMembers = fcMembers.Distinct().OrderBy(member => member.Name).ToList(); + + return fcMembers; + } + } +} diff --git a/FCNameColor/UI/PluginUI.cs b/FCNameColor/UI/PluginUI.cs index ed270de..d5b9f89 100644 --- a/FCNameColor/UI/PluginUI.cs +++ b/FCNameColor/UI/PluginUI.cs @@ -9,8 +9,10 @@ using Dalamud.Interface.Components; using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; +using Dalamud.Interface.Windowing; using Dalamud.Plugin.Services; using FCNameColor.Config; +using FCNameColor.UI; using ImGuiNET; using Lumina.Excel.GeneratedSheets; @@ -29,297 +31,230 @@ public int GetHashCode(UIColor obj) } } - internal class PluginUI : IDisposable + internal class PluginUI : Window { private readonly Plugin plugin; private readonly ConfigurationV1 configuration; - private bool showIgnoreList; - private bool showAdditionalFCConfig; - private bool showAddAdditionalFC; - private string fcUrl = ""; - private FCMember currentIgnoredPlayer; + private readonly IClientState clientState; private readonly IPluginLog pluginLog; - private readonly Regex fcUrlPattern = - new Regex(@"https:\/\/(eu|na|jp).finalfantasyxiv.com\/lodestone\/freecompany\/(\d{19})\/*"); + private readonly AdditionalFCsWindow showAdditionalFCsWindow; + private readonly IgnoreListWindow ignoreListWindow; + private readonly AddNewGroupWindow addNewGroupWindow; - // this extra bool exists for ImGui, since you can't ref a property - private bool visible; - private string newGroup; - private bool showAddNewGroup; - - public bool Visible - { - get => visible; - set => visible = value; - } - - public PluginUI(ConfigurationV1 config, IDataManager data, Plugin plugin, IClientState clientState, IPluginLog pluginLog) + public PluginUI(ConfigurationV1 config, IDataManager data, Plugin plugin, IClientState clientState, IPluginLog pluginLog, AddNewGroupWindow addNewGroupWindow, IgnoreListWindow ignoreListWindow, AdditionalFCsWindow showAdditionalFCsWindow) : base("FC Name Color Config") { configuration = config; this.clientState = clientState; this.plugin = plugin; this.pluginLog = pluginLog; + this.addNewGroupWindow = addNewGroupWindow; + this.ignoreListWindow = ignoreListWindow; + this.showAdditionalFCsWindow = showAdditionalFCsWindow; + + Flags = ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse | ImGuiWindowFlags.AlwaysAutoResize; + Size = new Vector2(380, 300); + SizeCondition = ImGuiCond.FirstUseEver; + SizeConstraints = new WindowSizeConstraints + { + MinimumSize = new Vector2(380, 300), + MaximumSize = new Vector2(float.MaxValue, float.MaxValue) + }; } - public void Dispose() - { - } - - public void Draw() - { - DrawMainWindow(); - } - - private void DrawMainWindow() + public override void Draw() { - if (!Visible) + if (plugin.FirstTime) { + ImGui.TextColored(ImGuiColors.DalamudYellow, + "Plugin is setting up for the first time, please wait a moment."); return; } - ImGui.SetNextWindowSize(new Vector2(380, 300), ImGuiCond.FirstUseEver); - ImGui.SetNextWindowSizeConstraints(new Vector2(380, 300), new Vector2(float.MaxValue, float.MaxValue)); - if (ImGui.Begin("FC Name Color Config", ref visible, - ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse | - ImGuiWindowFlags.AlwaysAutoResize)) + var playerKey = plugin.PlayerKey; + var enabled = configuration.Enabled; + if (ImGui.Checkbox("Enabled", ref enabled)) { - if (plugin.FirstTime) - { - ImGui.TextColored(ImGuiColors.DalamudYellow, - "Plugin is setting up for the first time, please wait a moment."); - ImGui.End(); - return; - } + configuration.Enabled = enabled; + configuration.Save(); + } - var playerKey = plugin.PlayerKey; - var enabled = configuration.Enabled; - if (ImGui.Checkbox("Enabled", ref enabled)) - { - configuration.Enabled = enabled; - configuration.Save(); - } + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip("Changes may take a couple of seconds to apply."); + } - if (ImGui.IsItemHovered()) - { - ImGui.SetTooltip("Changes may take a couple of seconds to apply."); - } + if (clientState.IsPvP) + { + ImGui.SameLine(); + ImGui.TextColored(ImGuiColors.DalamudOrange, "Plugin is disabled during PvP"); + } - if (clientState.IsPvP) - { - ImGui.SameLine(); - ImGui.TextColored(ImGuiColors.DalamudOrange, "Plugin is disabled during PvP"); - } + if (plugin.NotInFC) + { + ImGui.SameLine(); + ImGui.TextColored(ImGuiColors.DalamudRed, "Character not in FC"); + } + else if (plugin.Loading && !plugin.Error) + { + ImGui.SameLine(); + ImGui.Text(" Fetching FC members from Lodestone..."); + } + else if (plugin.Error) + { + ImGui.SameLine(); + ImGui.TextColored(ImGuiColors.DalamudRed, + $"Error when fetching. Retrying in {plugin.Cooldown} seconds."); + } - if (plugin.NotInFC) - { - ImGui.SameLine(); - ImGui.TextColored(ImGuiColors.DalamudRed, "Character not in FC"); - } - else if (plugin.Loading && !plugin.Error) - { - ImGui.SameLine(); - ImGui.Text(" Fetching FC members from Lodestone..."); - } - else if (plugin.Error) - { - ImGui.SameLine(); - ImGui.TextColored(ImGuiColors.DalamudRed, - $"Error when fetching. Retrying in {plugin.Cooldown} seconds."); - } + var onlyColorFCTag = configuration.OnlyColorFCTag; + if (ImGui.Checkbox("Only color the FC tag", ref onlyColorFCTag)) + { + configuration.OnlyColorFCTag = onlyColorFCTag; + configuration.Save(); + } - var onlyColorFCTag = configuration.OnlyColorFCTag; - if (ImGui.Checkbox("Only color the FC tag", ref onlyColorFCTag)) - { - configuration.OnlyColorFCTag = onlyColorFCTag; - configuration.Save(); - } + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip("This will only colour the FC tag instead of the entire name."); + } - if (ImGui.IsItemHovered()) - { - ImGui.SetTooltip("This will only colour the FC tag instead of the entire name."); - } + var includeSelf = configuration.IncludeSelf; + if (ImGui.Checkbox("Include self", ref includeSelf)) + { + configuration.IncludeSelf = includeSelf; + configuration.Save(); + } - var includeSelf = configuration.IncludeSelf; - if (ImGui.Checkbox("Include self", ref includeSelf)) - { - configuration.IncludeSelf = includeSelf; - configuration.Save(); - } + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip("This will colour your own FC tag."); + } - if (ImGui.IsItemHovered()) - { - ImGui.SetTooltip("This will colour your own FC tag."); - } + var ignoreFriends = configuration.IgnoreFriends; + ImGui.SameLine(); + if (ImGui.Checkbox("Ignore friends", ref ignoreFriends)) + { + configuration.IgnoreFriends = ignoreFriends; + configuration.Save(); + } - var ignoreFriends = configuration.IgnoreFriends; - ImGui.SameLine(); - if (ImGui.Checkbox("Ignore friends", ref ignoreFriends)) - { - configuration.IgnoreFriends = ignoreFriends; - configuration.Save(); - } + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip("Don't change the nameplates of friends."); + } - if (ImGui.IsItemHovered()) + var includeDuties = configuration.IncludeDuties; + if (ImGui.Checkbox("Include duties", ref includeDuties)) + { + if (!includeDuties) { - ImGui.SetTooltip("Don't change the nameplates of friends."); + configuration.OnlyDuties = false; } - var includeDuties = configuration.IncludeDuties; - if (ImGui.Checkbox("Include duties", ref includeDuties)) - { - if (!includeDuties) - { - configuration.OnlyDuties = false; - } + configuration.IncludeDuties = includeDuties; + configuration.Save(); + } - configuration.IncludeDuties = includeDuties; - configuration.Save(); - } + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip("Will colour the entire names of FC members when inside a duty."); + } - if (ImGui.IsItemHovered()) + ImGui.SameLine(); + var onlyDuties = configuration.OnlyDuties; + if (ImGui.Checkbox("Only duties", ref onlyDuties)) + { + if (onlyDuties) { - ImGui.SetTooltip("Will colour the entire names of FC members when inside a duty."); + configuration.IncludeDuties = true; } - ImGui.SameLine(); - var onlyDuties = configuration.OnlyDuties; - if (ImGui.Checkbox("Only duties", ref onlyDuties)) - { - if (onlyDuties) - { - configuration.IncludeDuties = true; - } - - configuration.OnlyDuties = onlyDuties; - configuration.Save(); - } + configuration.OnlyDuties = onlyDuties; + configuration.Save(); + } - if (ImGui.IsItemHovered()) - { - ImGui.SetTooltip("Disable the plugin outside of duties. This helps with conflicts with other plugins."); - } + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip("Disable the plugin outside of duties. This helps with conflicts with other plugins."); + } - var glow = configuration.Glow; - if (ImGui.Checkbox("Enable glow", ref glow)) - { - configuration.Glow = glow; - configuration.Save(); - } + var glow = configuration.Glow; + if (ImGui.Checkbox("Enable glow", ref glow)) + { + configuration.Glow = glow; + configuration.Save(); + } - if (ImGui.IsItemHovered()) - { - ImGui.SetTooltip("Makes outline of the nameplates thicker."); - } - ImGui.Separator(); + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip("Makes outline of the nameplates thicker."); + } - var groups = configuration.Groups.Keys.Where(a => a != "Other FC" && a != "Default").Prepend("Other FC").Prepend("Default").ToArray(); + ImGui.Separator(); - ImGui.Text("Groups"); - ImGui.SameLine(); + ImGui.Text("Groups"); + ImGui.SameLine(); - if (ImGuiComponents.IconButton(FontAwesomeIcon.Plus)) - { - newGroup = ""; - showAddNewGroup = true; - } + if (ImGuiComponents.IconButton(FontAwesomeIcon.Plus)) + { + addNewGroupWindow.IsOpen = true; + } - using (var groupsPanel = ImRaii.Child("###GroupsPanel", new Vector2(ImGui.GetContentRegionAvail().X, 300.0f * ImGuiHelpers.GlobalScale))) + using (var groupsPanel = ImRaii.Child("###GroupsPanel", new Vector2(ImGui.GetContentRegionAvail().X, 300.0f * ImGuiHelpers.GlobalScale))) + { + if (groupsPanel) { - if (groupsPanel) + foreach (var (groupName, group) in configuration.Groups) { - foreach (var (groupName, group) in configuration.Groups) + var groupColor = group.Color; + if (ImGui.ColorEdit4(groupName, ref groupColor, ImGuiColorEditFlags.NoInputs | ImGuiColorEditFlags.NoAlpha)) { - var groupColor = group.Color; - if (ImGui.ColorEdit4(groupName, ref groupColor, ImGuiColorEditFlags.NoInputs | ImGuiColorEditFlags.NoAlpha)) - { - var newGroup = configuration.Groups[groupName]; - newGroup.Color = groupColor; - configuration.Groups[groupName] = newGroup; - configuration.Save(); - } + var newGroup = configuration.Groups[groupName]; + newGroup.Color = groupColor; + configuration.Groups[groupName] = newGroup; + configuration.Save(); + } - if (groupName != "Default" && groupName != "Other FC") + if (groupName != "Default" && groupName != "Other FC") + { + ImGui.SameLine(); + using var id = ImRaii.PushId(groupName); + if (ImGuiComponents.IconButton(FontAwesomeIcon.Trash, new Vector4(0.8f, 0, 0, 1f), new Vector4(1f, 0, 0, 1f), new Vector4(0.9f, 0, 0, 1f))) { - ImGui.SameLine(); - using var id = ImRaii.PushId(groupName); - if (ImGuiComponents.IconButton(FontAwesomeIcon.Trash, new Vector4(0.8f, 0, 0, 1f), new Vector4(1f, 0, 0, 1f), new Vector4(0.9f, 0, 0, 1f))) - { - pluginLog.Debug($"Deleting group {groupName}"); - configuration.Groups.Remove(groupName); + pluginLog.Debug($"Deleting group {groupName}"); + configuration.Groups.Remove(groupName); - foreach (var playerConfigs in configuration.FCGroups) + foreach (var playerConfigs in configuration.FCGroups) + { + foreach (var fcGroup in playerConfigs.Value) { - foreach (var fcGroup in playerConfigs.Value) + if (fcGroup.Value == groupName) { - if (fcGroup.Value == groupName) - { - configuration.FCGroups[playerConfigs.Key][fcGroup.Key] = "Other FC"; - } + configuration.FCGroups[playerConfigs.Key][fcGroup.Key] = "Other FC"; } } - - configuration.Save(); } - if (ImGui.IsItemHovered()) - { - ImGui.SetTooltip($"Delete group {groupName}.\nThe groups Default and Other FC cannot be removed."); - } + configuration.Save(); } - } - } - } - if (showAddNewGroup) - { - ImGui.Begin("FC Name Color Config - Add new group", ref showAddNewGroup, - ImGuiWindowFlags.AlwaysAutoResize); - var exists = groups.Contains(newGroup); - var add = ImGui.InputTextWithHint("###NewGroup", "Your group name", ref newGroup, 50, - ImGuiInputTextFlags.EnterReturnsTrue) && newGroup.Length > 1; - - ImGui.SameLine(); - - if (newGroup.Length == 0 || exists) - { - ImGuiComponents.DisabledButton("Add Group"); - } - else - { - if (ImGui.Button("Add Group")) - { - add = true; + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip($"Delete group {groupName}.\nThe groups Default and Other FC cannot be removed."); + } } } - - if (exists) - { - ImGui.TextColored(ImGuiColors.DalamudRed, "Group names must be unique."); - } - - if (add) - { - configuration.Groups.Add(newGroup, new Group - { - UiColor = "52", - Color = new Vector4(0.07450981f, 0.8f, 0.6392157f, 1f) - }); - configuration.Save(); - showAddNewGroup = false; - } - - ImGui.End(); } - } + } ImGui.Separator(); ImGui.Spacing(); if (ImGui.Button("Ignore List")) { - showIgnoreList = !showIgnoreList; + ignoreListWindow.Toggle(); } if (ImGui.IsItemHovered()) @@ -328,9 +263,9 @@ private void DrawMainWindow() } ImGui.SameLine(); - if (ImGui.Button("FC management")) + if (ImGui.Button("FC tracking")) { - showAdditionalFCConfig = !showAdditionalFCConfig; + showAdditionalFCsWindow.Toggle(); } if (ImGui.IsItemHovered()) @@ -350,7 +285,7 @@ private void DrawMainWindow() configuration.PlayerFCIDs = new(); configuration.PlayerIDs = new(); configuration.Save(); - showAddAdditionalFC = false; + showAdditionalFCsWindow.IsOpen = false; plugin.SearchingFC = false; plugin.Reload(); } @@ -366,273 +301,10 @@ private void DrawMainWindow() If something goes wrong trying to fetch the data, you can try again after {(plugin.Cooldown > 0 ? plugin.Cooldown : Plugin.CooldownTime)} seconds."); } - if (showIgnoreList) - { - ImGui.SetNextWindowSize(new Vector2(270, 200), ImGuiCond.FirstUseEver); - ImGui.Begin("FC Name Color Config - Ignore List", ref showIgnoreList); - ImGui.TextWrapped("Don’t update nameplates for these players."); - ImGui.Spacing(); - ImGui.SetNextItemWidth(150f * ImGuiHelpers.GlobalScale); - var fcMembers = GetFCMembers(); - var playerNames = fcMembers.Select(member => member.Name).ToArray(); - var playerIndex = Array.IndexOf(playerNames, currentIgnoredPlayer.Name); - ImGui.SetNextItemWidth(170f * ImGuiHelpers.GlobalScale); - - if (ImGui.Combo( - "###AddPlayerToIgnoreList", - ref playerIndex, - playerNames, - playerNames.Length)) - { - currentIgnoredPlayer = fcMembers[playerIndex]; - } - - ImGui.SameLine(); - if (ImGui.SmallButton("Add Player")) - { - if (configuration.IgnoredPlayers.ContainsKey(currentIgnoredPlayer.Name)) - { - ImGui.OpenPopup("###AddPlayerToIgnoreListDupe"); - } - else - { - configuration.IgnoredPlayers.Add(currentIgnoredPlayer.Name, - currentIgnoredPlayer.ID); - configuration.Save(); - } - } - - if (ImGui.BeginPopup("###AddPlayerToIgnoreListDupe")) - { - ImGui.Text("You’ve already added this player!"); - ImGui.EndPopup(); - } - - foreach (var (key, _) in configuration.IgnoredPlayers.ToList()) - { - ImGui.Spacing(); - ImGui.Text(key); - ImGui.SameLine(); - ImGui.BeginGroup(); - ImGui.PushFont(UiBuilder.IconFont); - ImGui.Text(FontAwesomeIcon.Times.ToIconString()); - ImGui.PopFont(); - ImGui.EndGroup(); - if (!ImGui.IsItemClicked(ImGuiMouseButton.Left)) continue; - configuration.IgnoredPlayers.Remove(key); - configuration.Save(); - } - - ImGui.End(); - } - if (!configuration.FCGroups.ContainsKey(plugin.PlayerKey)) { configuration.FCGroups.Add(plugin.PlayerKey, new()); } - - if (showAdditionalFCConfig) - { - ImGui.SetNextWindowSize(new Vector2(320, 250), ImGuiCond.FirstUseEver); - ImGui.SetNextWindowSizeConstraints(new Vector2(320, 250), new Vector2(320, 1000f)); - ImGui.Begin("FC Name Color Config - Additional FCs", ref showAdditionalFCConfig); - ImGui.Spacing(); - ImGui.TextWrapped("Track FCs that aren’t your own."); - - if (ImGui.Button("Add FC")) - { - fcUrl = ""; - plugin.SearchingFCError = ""; - showAddAdditionalFC = true; - } - - ImGui.Separator(); - - if (configuration.FCGroups[plugin.PlayerKey].Count == 0) - { - if (plugin.FC.HasValue) - { - configuration.FCGroups[plugin.PlayerKey][plugin.FC.Value.ID] = "Default"; - } - - ImGui.Text("There are currently no additional FCs being tracked."); - } - - foreach (var fcConfigEntry in configuration.FCGroups[plugin.PlayerKey]) - { - var id = fcConfigEntry.Key; - var groupName = fcConfigEntry.Value; - - if (!configuration.FCs.ContainsKey(id)) - { - ImGui.Text($"Fetching FC {id}..."); - continue; - } - - var fc = configuration.FCs[id]; - - ImGui.PushID(fcConfigEntry.Key); - ImGui.Text("Settings for"); - ImGui.SameLine(); - ImGui.TextColored(configuration.Groups[groupName].Color, fc.Name); - ImGui.ColorButton("", configuration.Groups[groupName].Color); - ImGui.SameLine(); - var groups = configuration.Groups.Keys.ToArray(); - var groupIndex = Array.IndexOf(groups, groupName); - if (ImGui.Combo("###AdditionalFCGroup", ref groupIndex, groups, groups.Length)) - { - configuration.FCGroups[plugin.PlayerKey][fc.ID] = groups[groupIndex]; - configuration.Save(); - } - - ImGui.SameLine(); - if (ImGuiComponents.IconButton(FontAwesomeIcon.Trash, new Vector4(0.8f, 0, 0, 1f), - new Vector4(1f, 0, 0, 1f), new Vector4(0.9f, 0, 0, 1f))) - { - pluginLog.Debug("Deleting additional FC {fc}", fc.Name); - configuration.FCGroups[plugin.PlayerKey].Remove(id); - var shouldDeleteFC = !configuration.FCGroups.Any(character => character.Value.ContainsValue(groupName)); - if (shouldDeleteFC) - { - configuration.FCs.Remove(fc.ID); - pluginLog.Debug("Removing FC {name} altogether, no settings found anymore.", fc.Name); - } - configuration.Save(); - } - - if (ImGui.IsItemHovered()) - { - ImGui.SetTooltip($"Delete {fc.Name}."); - } - - ImGui.PopID(); - } - - ImGui.End(); - - if (showAddAdditionalFC || plugin.SearchingFC) - { - ImGui.Begin("FC Name Color Config - Adding Additional FC", ref showAddAdditionalFC, ImGuiWindowFlags.AlwaysAutoResize); - ImGui.Spacing(); - - ImGui.Text("Please enter the lodestone URL of the FC."); - ImGui.SameLine(); - if (ImGui.SmallButton("Open Lodestone")) - { - Process.Start("explorer", "https://eu.finalfantasyxiv.com/lodestone/community/search/"); - } - - ImGui.Text( - "It should look like this: https://eu.finalfantasyxiv.com/lodestone/freecompany/1234567890123456789"); - ImGui.SetNextItemWidth(500f * ImGuiHelpers.GlobalScale); - ImGui.InputTextWithHint("###FCUrl", - "https://eu.finalfantasyxiv.com/lodestone/freecompany/1234567890123456789", - ref fcUrl, 100); - - ImGui.SameLine(); - if (plugin.SearchingFC) - { - ImGuiComponents.DisabledButton("Searching FC"); - } - else - { - var isMatch = fcUrl.Length > 0 && fcUrlPattern.IsMatch(fcUrl); - - if (!isMatch) - { - ImGuiComponents.DisabledButton("Search FC"); - } - else if (isMatch && ImGui.Button("Search FC")) - { - var match = fcUrlPattern.Match(fcUrl); - var id = match.Groups[2].Value; - var shouldContinue = true; - - if (configuration.PlayerIDs.TryGetValue(plugin.PlayerKey, out var currentPlayerID)) - { - if (configuration.PlayerFCIDs.TryGetValue(currentPlayerID, out var playerFC)) - { - if (playerFC == id) - { - ImGui.OpenPopup("###SameFC"); - shouldContinue = false; - } - } - } - - if (shouldContinue) - { - if (configuration.FCGroups[plugin.PlayerKey].ContainsKey(id)) - { - ImGui.OpenPopup("###AddFCDupe"); - } - else - { - plugin.SearchFC(id, "Other FC").ContinueWith(async success => - { - var result = await success; - if (result) - { - showAddAdditionalFC = false; - } - }); - } - } - } - } - - if (fcUrl.Length > 0 && !fcUrlPattern.IsMatch(fcUrl)) - { - ImGui.TextColored(ImGuiColors.DalamudRed, "Url doesn’t match the FC url format."); - } - - if (plugin.SearchingFCError.Length > 0) - { - ImGui.TextColored(ImGuiColors.DalamudRed, plugin.SearchingFCError); - } - - if (ImGui.BeginPopup("###SameFC")) - { - ImGui.Text("This is your own FC, it’s already being tracked."); - ImGui.EndPopup(); - } - - if (ImGui.BeginPopup("###AddFCDupe")) - { - ImGui.Text("You’ve already added this FC!"); - ImGui.EndPopup(); - } - - ImGui.End(); - } - - ImGui.End(); - } - } - - private static Vector4 ConvertUIColorToColor(UIColor uiColor) - { - var temp = BitConverter.GetBytes(uiColor.UIForeground); - return new Vector4((float)temp[3] / 255, - (float)temp[2] / 255, - (float)temp[1] / 255, - (float)temp[0] / 255); - } - - private List GetFCMembers() - { - var fcMembers = new List(); - var playersFCs = configuration.PlayerFCIDs; - // TODO: Should this fetch *every* tracked FC or just the player's FCs? - foreach (var playerFCID in playersFCs) - { - var exists = configuration.FCs.TryGetValue(playerFCID.Value, out var fc); - fcMembers.AddRange(fc.Members); - } - - fcMembers = fcMembers.Distinct().OrderBy(member => member.Name).ToList(); - - return fcMembers; } } } \ No newline at end of file From 2f1727dbcae4a32817908e2ef11eda03e3d6623d Mon Sep 17 00:00:00 2001 From: Wessel Kuipers Date: Sat, 13 Jul 2024 20:26:04 +0200 Subject: [PATCH 6/9] Add tooltip on the add group button --- FCNameColor/UI/IgnoreListWindow.cs | 5 +---- FCNameColor/UI/PluginUI.cs | 5 +++++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/FCNameColor/UI/IgnoreListWindow.cs b/FCNameColor/UI/IgnoreListWindow.cs index 7d0012b..974bd71 100644 --- a/FCNameColor/UI/IgnoreListWindow.cs +++ b/FCNameColor/UI/IgnoreListWindow.cs @@ -26,10 +26,7 @@ public IgnoreListWindow(ConfigurationV1 configuration, Plugin plugin) : base("FC SizeCondition = ImGuiCond.FirstUseEver; } - public override bool DrawConditions() - { - return plugin.ConfigOpen && base.DrawConditions(); - } + public override bool DrawConditions() => plugin.ConfigOpen; public override void Draw() { diff --git a/FCNameColor/UI/PluginUI.cs b/FCNameColor/UI/PluginUI.cs index d5b9f89..1d83e16 100644 --- a/FCNameColor/UI/PluginUI.cs +++ b/FCNameColor/UI/PluginUI.cs @@ -203,6 +203,11 @@ public override void Draw() addNewGroupWindow.IsOpen = true; } + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip("Add a new group which you can assign FCs to."); + } + using (var groupsPanel = ImRaii.Child("###GroupsPanel", new Vector2(ImGui.GetContentRegionAvail().X, 300.0f * ImGuiHelpers.GlobalScale))) { if (groupsPanel) From 4b4f30e68d2a30187241473b0b1d78a3233fa464 Mon Sep 17 00:00:00 2001 From: Wessel Kuipers Date: Sat, 13 Jul 2024 21:19:33 +0200 Subject: [PATCH 7/9] Temporarily add NamePlate code from PR --- FCNameColor/FCNameColor.csproj | 6 +- FCNameColor/NamePlates/INamePlateGui.cs | 35 + FCNameColor/NamePlates/NamePlateGui.cs | 197 ++++++ FCNameColor/NamePlates/NamePlateInfoView.cs | 104 +++ FCNameColor/NamePlates/NamePlateKind.cs | 57 ++ .../NamePlates/NamePlatePartsContainer.cs | 46 ++ .../NamePlates/NamePlateQuotedParts.cs | 98 +++ .../NamePlates/NamePlateSimpleParts.cs | 44 ++ .../NamePlates/NamePlateStringField.cs | 38 ++ .../NamePlates/NamePlateUpdateContext.cs | 151 +++++ .../NamePlates/NamePlateUpdateHandler.cs | 604 ++++++++++++++++++ FCNameColor/Plugin.cs | 11 +- 12 files changed, 1386 insertions(+), 5 deletions(-) create mode 100644 FCNameColor/NamePlates/INamePlateGui.cs create mode 100644 FCNameColor/NamePlates/NamePlateGui.cs create mode 100644 FCNameColor/NamePlates/NamePlateInfoView.cs create mode 100644 FCNameColor/NamePlates/NamePlateKind.cs create mode 100644 FCNameColor/NamePlates/NamePlatePartsContainer.cs create mode 100644 FCNameColor/NamePlates/NamePlateQuotedParts.cs create mode 100644 FCNameColor/NamePlates/NamePlateSimpleParts.cs create mode 100644 FCNameColor/NamePlates/NamePlateStringField.cs create mode 100644 FCNameColor/NamePlates/NamePlateUpdateContext.cs create mode 100644 FCNameColor/NamePlates/NamePlateUpdateHandler.cs diff --git a/FCNameColor/FCNameColor.csproj b/FCNameColor/FCNameColor.csproj index d53f529..af94cde 100644 --- a/FCNameColor/FCNameColor.csproj +++ b/FCNameColor/FCNameColor.csproj @@ -2,7 +2,7 @@ {13C812E9-0D42-4B95-8646-40EEBF30636F} net8.0-windows7.0 - 9.0 + 12.0 FCNameColor FCNameColor Copyright © 2023 @@ -17,11 +17,11 @@ full - 10.0 + 12.0 pdbonly - 10.0 + 12.0 $(appdata)\XIVLauncher\addon\Hooks\dev\ diff --git a/FCNameColor/NamePlates/INamePlateGui.cs b/FCNameColor/NamePlates/INamePlateGui.cs new file mode 100644 index 0000000..f017896 --- /dev/null +++ b/FCNameColor/NamePlates/INamePlateGui.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; + +namespace FCNameColor.Nameplates; + +/// +/// Class used to modify the data used when rendering nameplates. +/// +public interface INamePlateGui +{ + /// + /// The delegate used for receiving nameplate update events. + /// + /// An object containing information about the pending data update. + /// A list of handlers used for updating nameplate data. + public delegate void OnPlateUpdateDelegate( + INamePlateUpdateContext context, IReadOnlyList handlers); + + /// + /// An event which fires when nameplate data is updated and at least one nameplate has important updates. The + /// subscriber is provided with a list of handlers for nameplates with important updates. + /// + event OnPlateUpdateDelegate? OnNamePlateUpdate; + + /// + /// An event which fires when nameplate data is updated. The subscriber is provided with a list of handlers for all + /// nameplates. This event is likely to fire every frame even when no nameplates are actually updated, so in most + /// cases is preferred. + /// + event OnPlateUpdateDelegate? OnDataUpdate; + + /// + /// Requests that all nameplates should be redrawn on the following frame. + /// + void RequestRedraw(); +} diff --git a/FCNameColor/NamePlates/NamePlateGui.cs b/FCNameColor/NamePlates/NamePlateGui.cs new file mode 100644 index 0000000..65e8d20 --- /dev/null +++ b/FCNameColor/NamePlates/NamePlateGui.cs @@ -0,0 +1,197 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using Dalamud.Game.Addon.Lifecycle; +using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; +using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Client.UI; + +namespace FCNameColor.Nameplates; + +/// +/// Class used to modify the data used when rendering nameplates. +/// +internal sealed class NamePlateGui : INamePlateGui +{ + /// + /// The index for the number array used by the NamePlate addon. + /// + public const int NumberArrayIndex = 5; + + /// + /// The index for the string array used by the NamePlate addon. + /// + public const int StringArrayIndex = 4; + + /// + /// The index for of the FullUpdate entry in the NamePlate number array. + /// + internal const int NumberArrayFullUpdateIndex = 4; + + /// + /// An empty null-terminated string pointer allocated in unmanaged memory, used to tag removed fields. + /// + internal static readonly nint EmptyStringPointer = CreateEmptyStringPointer(); + + private NamePlateUpdateContext? context; + + private NamePlateUpdateHandler[] updateHandlers = []; + + public NamePlateGui() + { + Plugin.AddonLifecycleHandler.RegisterListener(AddonEvent.PreRequestedUpdate, "NamePlate", OnPreRequestedUpdate); + } + + public void Dispose() + { + Plugin.AddonLifecycleHandler.UnregisterListener(AddonEvent.PreRequestedUpdate, "NamePlate", OnPreRequestedUpdate); + } + + /// + public event INamePlateGui.OnPlateUpdateDelegate? OnNamePlateUpdate; + + /// + public event INamePlateGui.OnPlateUpdateDelegate? OnDataUpdate; + + /// + public unsafe void RequestRedraw() + { + var addon = Plugin.GameGuiHandler.GetAddonByName("NamePlate"); + if (addon != 0) + { + var raptureAtkModule = RaptureAtkModule.Instance(); + if (raptureAtkModule == null) + { + return; + } + + ((AddonNamePlate*)addon)->DoFullUpdate = 1; + var namePlateNumberArrayData = raptureAtkModule->AtkArrayDataHolder.NumberArrays[NumberArrayIndex]; + namePlateNumberArrayData->SetValue(NumberArrayFullUpdateIndex, 1); + } + } + + /// + /// Strips the surrounding quotes from a free company tag. If the quotes are not present in the expected location, + /// no modifications will be made. + /// + /// A quoted free company tag. + /// A span containing the free company tag without its surrounding quote characters. + internal static ReadOnlySpan StripFreeCompanyTagQuotes(ReadOnlySpan text) + { + if (text.Length > 4 && text.StartsWith(" «"u8) && text.EndsWith("»"u8)) + { + return text[3..^2]; + } + + return text; + } + + /// + /// Strips the surrounding quotes from a title. If the quotes are not present in the expected location, no + /// modifications will be made. + /// + /// A quoted title. + /// A span containing the title without its surrounding quote characters. + internal static ReadOnlySpan StripTitleQuotes(ReadOnlySpan text) + { + if (text.Length > 5 && text.StartsWith("《"u8) && text.EndsWith("》"u8)) + { + return text[3..^3]; + } + + return text; + } + + private static nint CreateEmptyStringPointer() + { + var pointer = Marshal.AllocHGlobal(1); + Marshal.WriteByte(pointer, 0, 0); + return pointer; + } + + private void CreateHandlers(NamePlateUpdateContext createdContext) + { + var handlers = new List(); + for (var i = 0; i < AddonNamePlate.NumNamePlateObjects; i++) + { + handlers.Add(new NamePlateUpdateHandler(createdContext, i)); + } + + this.updateHandlers = handlers.ToArray(); + } + + private void OnPreRequestedUpdate(AddonEvent type, AddonArgs args) + { + if (this.OnDataUpdate == null && this.OnNamePlateUpdate == null) + { + return; + } + + var reqArgs = (AddonRequestedUpdateArgs)args; + if (this.context == null) + { + this.context = new NamePlateUpdateContext(Plugin.Objects, reqArgs); + this.CreateHandlers(this.context); + } + else + { + this.context.ResetState(reqArgs); + } + + var activeNamePlateCount = this.context.ActiveNamePlateCount; + if (activeNamePlateCount == 0) + return; + + var activeHandlers = this.updateHandlers[..activeNamePlateCount]; + + if (this.context.IsFullUpdate) + { + foreach (var handler in activeHandlers) + { + handler.ResetState(); + } + + this.OnDataUpdate?.Invoke(this.context, activeHandlers); + this.OnNamePlateUpdate?.Invoke(this.context, activeHandlers); + if (this.context.HasParts) + this.ApplyBuilders(activeHandlers); + } + else + { + var udpatedHandlers = new List(activeNamePlateCount); + foreach (var handler in activeHandlers) + { + handler.ResetState(); + if (handler.IsUpdating) + udpatedHandlers.Add(handler); + } + + if (this.OnDataUpdate is not null) + { + this.OnDataUpdate?.Invoke(this.context, activeHandlers); + this.OnNamePlateUpdate?.Invoke(this.context, udpatedHandlers); + if (this.context.HasParts) + this.ApplyBuilders(activeHandlers); + } + else if (udpatedHandlers.Count != 0) + { + var changedHandlersSpan = udpatedHandlers.ToArray().AsSpan(); + this.OnNamePlateUpdate?.Invoke(this.context, udpatedHandlers); + if (this.context.HasParts) + this.ApplyBuilders(changedHandlersSpan); + } + } + } + + private void ApplyBuilders(Span handlers) + { + foreach (var handler in handlers) + { + if (handler.PartsContainer is { } container) + { + container.ApplyBuilders(handler); + } + } + } +} \ No newline at end of file diff --git a/FCNameColor/NamePlates/NamePlateInfoView.cs b/FCNameColor/NamePlates/NamePlateInfoView.cs new file mode 100644 index 0000000..3a92335 --- /dev/null +++ b/FCNameColor/NamePlates/NamePlateInfoView.cs @@ -0,0 +1,104 @@ +using Dalamud.Game.Text.SeStringHandling; +using FFXIVClientStructs.FFXIV.Client.UI; + +namespace FCNameColor.Nameplates; + +/// +/// Provides a read-only view of the nameplate info object data for a nameplate. Modifications to +/// fields do not affect this data. +/// +public interface INamePlateInfoView +{ + /// + /// Gets the displayed name for this nameplate according to the nameplate info object. + /// + SeString Name { get; } + + /// + /// Gets the displayed free company tag for this nameplate according to the nameplate info object. For this field, + /// the quote characters which appear on either side of the title are NOT included. + /// + SeString FreeCompanyTag { get; } + + /// + /// Gets the displayed free company tag for this nameplate according to the nameplate info object. For this field, + /// the quote characters which appear on either side of the title ARE included. + /// + SeString QuotedFreeCompanyTag { get; } + + /// + /// Gets the displayed title for this nameplate according to the nameplate info object. For this field, the quote + /// characters which appear on either side of the title are NOT included. + /// + SeString Title { get; } + + /// + /// Gets the displayed title for this nameplate according to the nameplate info object. For this field, the quote + /// characters which appear on either side of the title ARE included. + /// + SeString QuotedTitle { get; } + + /// + /// Gets the displayed level text for this nameplate according to the nameplate info object. + /// + SeString LevelText { get; } + + /// + /// Gets the flags for this nameplate according to the nameplate info object. + /// + int Flags { get; } + + /// + /// Gets a value indicating whether this nameplate is considered 'dirty' or not according to the nameplate + /// info object. + /// + bool IsDirty { get; } + + /// + /// Gets a value indicating whether the title for this nameplate is a prefix title or not according to the nameplate + /// info object. This value is derived from the field. + /// + bool IsPrefixTitle { get; } +} + +/// +/// Provides a read-only view of the nameplate info object data for a nameplate. Modifications to +/// fields do not affect this data. +/// +internal unsafe class NamePlateInfoView(RaptureAtkModule.NamePlateInfo* info) : INamePlateInfoView +{ + private SeString? name; + private SeString? freeCompanyTag; + private SeString? quotedFreeCompanyTag; + private SeString? title; + private SeString? quotedTitle; + private SeString? levelText; + + /// + public SeString Name => this.name ??= SeString.Parse(info->Name); + + /// + public SeString FreeCompanyTag => this.freeCompanyTag ??= + SeString.Parse(NamePlateGui.StripFreeCompanyTagQuotes(info->FcName)); + + /// + public SeString QuotedFreeCompanyTag => this.quotedFreeCompanyTag ??= SeString.Parse(info->FcName); + + /// + public SeString Title => this.title ??= SeString.Parse(info->Title); + + /// + public SeString QuotedTitle => this.quotedTitle ??= SeString.Parse(info->DisplayTitle); + + /// + public SeString LevelText => this.levelText ??= SeString.Parse(info->LevelText); + + /// + public int Flags => info->Flags; + + /// + public bool IsDirty => info->IsDirty; + + /// + public bool IsPrefixTitle => ((info->Flags >> (8 * 3)) & 0xFF) == 1; +} diff --git a/FCNameColor/NamePlates/NamePlateKind.cs b/FCNameColor/NamePlates/NamePlateKind.cs new file mode 100644 index 0000000..6d54907 --- /dev/null +++ b/FCNameColor/NamePlates/NamePlateKind.cs @@ -0,0 +1,57 @@ +namespace FCNameColor.Nameplates; + +/// +/// An enum describing what kind of game object this nameplate represents. +/// +public enum NamePlateKind : byte +{ + /// + /// A player character. + /// + PlayerCharacter = 0, + + /// + /// An event NPC or companion. + /// + EventNpcCompanion = 1, + + /// + /// A retainer. + /// + Retainer = 2, + + /// + /// An enemy battle NPC. + /// + BattleNpcEnemy = 3, + + /// + /// A friendly battle NPC. + /// + BattleNpcFriendly = 4, + + /// + /// An event object. + /// + EventObject = 5, + + /// + /// Treasure. + /// + Treasure = 6, + + /// + /// A gathering point. + /// + GatheringPoint = 7, + + /// + /// A battle NPC with subkind 6. + /// + BattleNpcSubkind6 = 8, + + /// + /// Something else. + /// + Other = 9, +} diff --git a/FCNameColor/NamePlates/NamePlatePartsContainer.cs b/FCNameColor/NamePlates/NamePlatePartsContainer.cs new file mode 100644 index 0000000..0e05744 --- /dev/null +++ b/FCNameColor/NamePlates/NamePlatePartsContainer.cs @@ -0,0 +1,46 @@ +namespace FCNameColor.Nameplates; + +/// +/// A container for parts. +/// +internal class NamePlatePartsContainer +{ + private NamePlateSimpleParts? nameParts; + private NamePlateQuotedParts? titleParts; + private NamePlateQuotedParts? freeCompanyTagParts; + + /// + /// Initializes a new instance of the class. + /// + /// The currently executing update context. + public NamePlatePartsContainer(NamePlateUpdateContext context) + { + context.HasParts = true; + } + + /// + /// Gets a parts object for constructing a nameplate name. + /// + internal NamePlateSimpleParts Name => this.nameParts ??= new NamePlateSimpleParts(NamePlateStringField.Name); + + /// + /// Gets a parts object for constructing a nameplate title. + /// + internal NamePlateQuotedParts Title => this.titleParts ??= new NamePlateQuotedParts(NamePlateStringField.Title, false); + + /// + /// Gets a parts object for constructing a nameplate free company tag. + /// + internal NamePlateQuotedParts FreeCompanyTag => this.freeCompanyTagParts ??= new NamePlateQuotedParts(NamePlateStringField.FreeCompanyTag, true); + + /// + /// Applies all container parts. + /// + /// The handler to apply the builders to. + internal void ApplyBuilders(NamePlateUpdateHandler handler) + { + this.nameParts?.Apply(handler); + this.freeCompanyTagParts?.Apply(handler); + this.titleParts?.Apply(handler); + } +} diff --git a/FCNameColor/NamePlates/NamePlateQuotedParts.cs b/FCNameColor/NamePlates/NamePlateQuotedParts.cs new file mode 100644 index 0000000..6813149 --- /dev/null +++ b/FCNameColor/NamePlates/NamePlateQuotedParts.cs @@ -0,0 +1,98 @@ +using Dalamud.Game.Text.SeStringHandling; + +namespace FCNameColor.Nameplates; + +/// +/// A part builder for constructing and setting quoted nameplate fields (i.e. free company tag and title). +/// +/// The field type which should be set. +/// Whether the field is a free company tag. +public class NamePlateQuotedParts(NamePlateStringField field, bool isFreeCompany) +{ + /// + /// Gets or sets the opening and closing SeStrings which will wrap the entire contents, which can be used to apply + /// colors or styling to the entire field. + /// + public (SeString, SeString)? OuterWrap { get; set; } + + /// + /// Gets or sets the opening quote string which appears before the text and opening text-wrap. + /// + public SeString? LeftQuote { get; set; } + + /// + /// Gets or sets the closing quote string which appears after the text and closing text-wrap. + /// + public SeString? RightQuote { get; set; } + + /// + /// Gets or sets the opening and closing SeStrings which will wrap the text, which can be used to apply colors or + /// styling to the field's text. + /// + public (SeString, SeString)? TextWrap { get; set; } + + /// + /// Gets or sets this field's text. + /// + public SeString? Text { get; set; } + + /// + /// Applies the changes from this builder to the actual field. + /// + /// The handler to perform the changes on. + internal unsafe void Apply(NamePlateUpdateHandler handler) + { + if ((nint)handler.GetFieldAsPointer(field) == NamePlateGui.EmptyStringPointer) + return; + + var sb = new SeStringBuilder(); + if (this.OuterWrap is { Item1: var outerLeft }) + { + sb.Append(outerLeft); + } + + if (this.LeftQuote is not null) + { + sb.Append(this.LeftQuote); + } + else + { + sb.Append(isFreeCompany ? " «" : "《"); + } + + if (this.TextWrap is { Item1: var left, Item2: var right }) + { + sb.Append(left); + sb.Append(this.Text ?? this.GetStrippedField(handler)); + sb.Append(right); + } + else + { + sb.Append(this.Text ?? this.GetStrippedField(handler)); + } + + if (this.RightQuote is not null) + { + sb.Append(this.RightQuote); + } + else + { + sb.Append(isFreeCompany ? "»" : "》"); + } + + if (this.OuterWrap is { Item2: var outerRight }) + { + sb.Append(outerRight); + } + + handler.SetField(field, sb.Build()); + } + + private SeString GetStrippedField(NamePlateUpdateHandler handler) + { + return SeString.Parse( + isFreeCompany + ? NamePlateGui.StripFreeCompanyTagQuotes(handler.GetFieldAsSpan(field)) + : NamePlateGui.StripTitleQuotes(handler.GetFieldAsSpan(field))); + } +} diff --git a/FCNameColor/NamePlates/NamePlateSimpleParts.cs b/FCNameColor/NamePlates/NamePlateSimpleParts.cs new file mode 100644 index 0000000..55d7c44 --- /dev/null +++ b/FCNameColor/NamePlates/NamePlateSimpleParts.cs @@ -0,0 +1,44 @@ +using Dalamud.Game.Text.SeStringHandling; + +namespace FCNameColor.Nameplates; + +/// +/// A part builder for constructing and setting a simple (unquoted) nameplate field. +/// +/// The field type which should be set. +public class NamePlateSimpleParts(NamePlateStringField field) +{ + /// + /// Gets or sets the opening and closing SeStrings which will wrap the text, which can be used to apply colors or + /// styling to the field's text. + /// + public (SeString, SeString)? TextWrap { get; set; } + + /// + /// Gets or sets this field's text. + /// + public SeString? Text { get; set; } + + /// + /// Applies the changes from this builder to the actual field. + /// + /// The handler to perform the changes on. + internal unsafe void Apply(NamePlateUpdateHandler handler) + { + if ((nint)handler.GetFieldAsPointer(field) == NamePlateGui.EmptyStringPointer) + return; + + if (this.TextWrap is { Item1: var left, Item2: var right }) + { + var sb = new SeStringBuilder(); + sb.Append(left); + sb.Append(this.Text ?? handler.GetFieldAsSeString(field)); + sb.Append(right); + handler.SetField(field, sb.Build()); + } + else if (this.Text is not null) + { + handler.SetField(field, this.Text); + } + } +} diff --git a/FCNameColor/NamePlates/NamePlateStringField.cs b/FCNameColor/NamePlates/NamePlateStringField.cs new file mode 100644 index 0000000..405c51a --- /dev/null +++ b/FCNameColor/NamePlates/NamePlateStringField.cs @@ -0,0 +1,38 @@ +namespace FCNameColor.Nameplates; + +/// +/// An enum describing the string fields available in nameplate data. The and various flags +/// determine which fields will actually be rendered. +/// +public enum NamePlateStringField +{ + /// + /// The object's name. + /// + Name = 0, + + /// + /// The object's title. + /// + Title = 50, + + /// + /// The object's free company tag. + /// + FreeCompanyTag = 100, + + /// + /// The object's status prefix. + /// + StatusPrefix = 150, + + /// + /// The object's target suffix. + /// + TargetSuffix = 200, + + /// + /// The object's level prefix. + /// + LevelPrefix = 250, +} diff --git a/FCNameColor/NamePlates/NamePlateUpdateContext.cs b/FCNameColor/NamePlates/NamePlateUpdateContext.cs new file mode 100644 index 0000000..d7a9971 --- /dev/null +++ b/FCNameColor/NamePlates/NamePlateUpdateContext.cs @@ -0,0 +1,151 @@ +using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; +using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Component.GUI; + +namespace FCNameColor.Nameplates; + +/// +/// Contains information related to the pending nameplate data update. This is only valid for a single frame and should +/// not be kept across frames. +/// +public interface INamePlateUpdateContext +{ + /// + /// Gets the number of active nameplates. The actual number visible may be lower than this in cases where some + /// nameplates are hidden by default (based on in-game "Display Name Settings" and so on). + /// + int ActiveNamePlateCount { get; } + + /// + /// Gets a value indicating whether the game is currently performing a full update of all active nameplates. + /// + bool IsFullUpdate { get; } + + /// + /// Gets the address of the NamePlate addon. + /// + nint AddonAddress { get; } + + /// + /// Gets the address of the NamePlate addon's number array data container. + /// + nint NumberArrayDataAddress { get; } + + /// + /// Gets the address of the NamePlate addon's string array data container. + /// + nint StringArrayDataAddress { get; } + + /// + /// Gets the address of the first entry in the NamePlate addon's int array. + /// + nint NumberArrayDataEntryAddress { get; } +} + +/// +/// Contains information related to the pending nameplate data update. This is only valid for a single frame and should +/// not be kept across frames. +/// +internal unsafe class NamePlateUpdateContext : INamePlateUpdateContext +{ + /// + /// Initializes a new instance of the class. + /// + /// An object table. + /// The addon lifecycle arguments for the update request. + internal NamePlateUpdateContext(IObjectTable objectTable, AddonRequestedUpdateArgs args) + { + this.ObjectTable = objectTable; + this.RaptureAtkModule = FFXIVClientStructs.FFXIV.Client.UI.RaptureAtkModule.Instance(); + this.Ui3DModule = UIModule.Instance()->GetUI3DModule(); + this.ResetState(args); + } + + /// + /// Gets the number of active nameplates. The actual number visible may be lower than this in cases where some + /// nameplates are hidden by default (based on in-game "Display Name Settings" and so on). + /// + public int ActiveNamePlateCount { get; private set; } + + /// + /// Gets a value indicating whether the game is currently performing a full update of all active nameplates. + /// + public bool IsFullUpdate { get; private set; } + + /// + /// Gets the address of the NamePlate addon. + /// + public nint AddonAddress => (nint)this.Addon; + + /// + /// Gets the address of the NamePlate addon's number array data container. + /// + public nint NumberArrayDataAddress => (nint)this.NumberData; + + /// + /// Gets the address of the NamePlate addon's string array data container. + /// + public nint StringArrayDataAddress => (nint)this.StringData; + + /// + /// Gets the address of the first entry in the NamePlate addon's int array. + /// + public nint NumberArrayDataEntryAddress => (nint)this.NumberStruct; + + /// + /// Gets the RaptureAtkModule. + /// + internal RaptureAtkModule* RaptureAtkModule { get; } + + /// + /// Gets the Ui3DModule. + /// + internal UI3DModule* Ui3DModule { get; } + + /// + /// Gets the ObjectTable. + /// + internal IObjectTable ObjectTable { get; } + + /// + /// Gets a pointer to the NamePlate addon. + /// + internal AddonNamePlate* Addon { get; private set; } + + /// + /// Gets a pointer to the NamePlate addon's number array data container. + /// + internal NumberArrayData* NumberData { get; private set; } + + /// + /// Gets a pointer to the NamePlate addon's string array data container. + /// + internal StringArrayData* StringData { get; private set; } + + /// + /// Gets a pointer to the NamePlate addon's number array entries as a struct. + /// + internal AddonNamePlate.NamePlateIntArrayData* NumberStruct { get; private set; } + + /// + /// Gets or sets a value indicating whether any handler in the current context has instantiated a part builder. + /// + internal bool HasParts { get; set; } + + /// + /// Resets the state of the context based on the provided addon lifecycle arguments. + /// + /// The addon lifecycle arguments for the update request. + internal void ResetState(AddonRequestedUpdateArgs args) + { + this.Addon = (AddonNamePlate*)args.Addon; + this.NumberData = ((NumberArrayData**)args.NumberArrayData)![NamePlateGui.NumberArrayIndex]; + this.NumberStruct = (AddonNamePlate.NamePlateIntArrayData*)this.NumberData->IntArray; + this.StringData = ((StringArrayData**)args.StringArrayData)![NamePlateGui.StringArrayIndex]; + this.HasParts = false; + + this.ActiveNamePlateCount = this.NumberStruct->ActiveNamePlateCount; + this.IsFullUpdate = this.Addon->DoFullUpdate != 0; + } +} diff --git a/FCNameColor/NamePlates/NamePlateUpdateHandler.cs b/FCNameColor/NamePlates/NamePlateUpdateHandler.cs new file mode 100644 index 0000000..3613f40 --- /dev/null +++ b/FCNameColor/NamePlates/NamePlateUpdateHandler.cs @@ -0,0 +1,604 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; +using Dalamud.Game.ClientState.Objects.SubKinds; +using Dalamud.Game.ClientState.Objects.Types; +using Dalamud.Game.Text.SeStringHandling; +using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.Interop; + +namespace FCNameColor.Nameplates; + +/// +/// A class representing a single nameplate. Provides mechanisms to look up the game object associated with the +/// nameplate and allows for modification of various backing fields in number and string array data, which in turn +/// affect aspects of the nameplate's appearance when drawn. Instances of this class are only valid for a single frame +/// and should not be kept across frames. +/// +public interface INamePlateUpdateHandler +{ + /// + /// Gets the GameObjectId of the game object associated with this nameplate. + /// + ulong GameObjectId { get; } + + /// + /// Gets the associated with this nameplate, if possible. Performs an object table scan + /// and caches the result if successful. + /// + IGameObject? GameObject { get; } + + /// + /// Gets a read-only view of the nameplate info object data for a nameplate. Modifications to + /// fields do not affect fields in the returned view. + /// + INamePlateInfoView InfoView { get; } + + /// + /// Gets the index for this nameplate data in the backing number and string array data. This is not the same as the + /// rendered or object index, which can be retrieved from . + /// + int ArrayIndex { get; } + + /// + /// Gets the associated with this nameplate, if possible. Returns null if the nameplate + /// has an associated , but that object cannot be assigned to . + /// + IBattleChara? BattleChara { get; } + + /// + /// Gets the associated with this nameplate, if possible. Returns null if the + /// nameplate has an associated , but that object cannot be assigned to + /// . + /// + IPlayerCharacter? PlayerCharacter { get; } + + /// + /// Gets the address of the nameplate info struct. + /// + nint NamePlateInfoAddress { get; } + + /// + /// Gets the address of the first entry associated with this nameplate in the NamePlate addon's int array. + /// + nint NamePlateObjectAddress { get; } + + /// + /// Gets a value indicating what kind of nameplate this is, based on the kind of object it is associated with. + /// + NamePlateKind NamePlateKind { get; } + + /// + /// Gets the update flags for this nameplate. + /// + int UpdateFlags { get; } + + /// + /// Gets or sets the overall text color for this nameplate. If this value is changed, the appropriate update flag + /// will be set so that the game will reflect this change immediately. + /// + uint TextColor { get; set; } + + /// + /// Gets or sets the overall text edge color for this nameplate. If this value is changed, the appropriate update + /// flag will be set so that the game will reflect this change immediately. + /// + uint EdgeColor { get; set; } + + /// + /// Gets or sets the icon ID for the nameplate's marker icon, which is the large icon used to indicate quest + /// availability and so on. This value is read from and reset by the game every frame, not just when a nameplate + /// changes. + /// + int MarkerIconId { get; set; } + + /// + /// Gets or sets the icon ID for the nameplate's name icon, which is the small icon shown to the left of the name. + /// + int NameIconId { get; set; } + + /// + /// Gets the nameplate index, which is the index used for rendering and looking up entries in the object array. For + /// number and string array data, is used. + /// + int NamePlateIndex { get; } + + /// + /// Gets the draw flags for this nameplate. + /// + int DrawFlags { get; } + + /// + /// Gets or sets the visibility flags for this nameplate. + /// + int VisibilityFlags { get; set; } + + /// + /// Gets a value indicating whether this nameplate is undergoing a major update or not. This is usually true when a + /// nameplate has just appeared or something meaningful about the entity has changed (e.g. its job or status). This + /// flag is reset by the game during the update process (during requested update and before draw). + /// + bool IsUpdating { get; } + + /// + /// Gets or sets a value indicating whether the title (when visible) will be displayed above the object's name (a + /// prefix title) instead of below the object's name (a suffix title). + /// + bool IsPrefixTitle { get; set; } + + /// + /// Gets or sets a value indicating whether the title should be displayed at all. + /// + bool DisplayTitle { get; set; } + + /// + /// Gets or sets the name for this nameplate. + /// + SeString Name { get; set; } + + /// + /// Gets a builder which can be used to help cooperatively build a new name for this nameplate even when other + /// plugins modifying the name are present. Specifically, this builder allows setting text and text-wrapping + /// payloads (e.g. for setting text color) separately. + /// + NamePlateSimpleParts NameParts { get; } + + /// + /// Gets or sets the title for this nameplate. + /// + SeString Title { get; set; } + + /// + /// Gets a builder which can be used to help cooperatively build a new title for this nameplate even when other + /// plugins modifying the title are present. Specifically, this builder allows setting text, text-wrapping + /// payloads (e.g. for setting text color), and opening and closing quote sequences separately. + /// + NamePlateQuotedParts TitleParts { get; } + + /// + /// Gets or sets the free company tag for this nameplate. + /// + SeString FreeCompanyTag { get; set; } + + /// + /// Gets a builder which can be used to help cooperatively build a new FC tag for this nameplate even when other + /// plugins modifying the FC tag are present. Specifically, this builder allows setting text, text-wrapping + /// payloads (e.g. for setting text color), and opening and closing quote sequences separately. + /// + NamePlateQuotedParts FreeCompanyTagParts { get; } + + /// + /// Gets or sets the status prefix for this nameplate. This prefix is used by the game to add BitmapFontIcon-based + /// online status icons to player nameplates. + /// + SeString StatusPrefix { get; set; } + + /// + /// Gets or sets the target suffix for this nameplate. This suffix is used by the game to add the squared-letter + /// target tags to the end of combat target nameplates. + /// + SeString TargetSuffix { get; set; } + + /// + /// Gets or sets the level prefix for this nameplate. This "Lv60" style prefix is added to enemy and friendly battle + /// NPC nameplates to indicate the NPC level. + /// + SeString LevelPrefix { get; set; } + + /// + /// Removes the contents of the name field for this nameplate. This differs from simply setting the field + /// to an empty string because it writes a special value to memory, and other setters (except SetField variants) + /// will refuse to overwrite this value. Therefore, fields removed this way are more likely to stay removed. + /// + void RemoveName(); + + /// + /// Removes the contents of the title field for this nameplate. This differs from simply setting the field + /// to an empty string because it writes a special value to memory, and other setters (except SetField variants) + /// will refuse to overwrite this value. Therefore, fields removed this way are more likely to stay removed. + /// + void RemoveTitle(); + + /// + /// Removes the contents of the FC tag field for this nameplate. This differs from simply setting the field + /// to an empty string because it writes a special value to memory, and other setters (except SetField variants) + /// will refuse to overwrite this value. Therefore, fields removed this way are more likely to stay removed. + /// + void RemoveFreeCompanyTag(); + + /// + /// Removes the contents of the status prefix field for this nameplate. This differs from simply setting the field + /// to an empty string because it writes a special value to memory, and other setters (except SetField variants) + /// will refuse to overwrite this value. Therefore, fields removed this way are more likely to stay removed. + /// + void RemoveStatusPrefix(); + + /// + /// Removes the contents of the target suffix field for this nameplate. This differs from simply setting the field + /// to an empty string because it writes a special value to memory, and other setters (except SetField variants) + /// will refuse to overwrite this value. Therefore, fields removed this way are more likely to stay removed. + /// + void RemoveTargetSuffix(); + + /// + /// Removes the contents of the level prefix field for this nameplate. This differs from simply setting the field + /// to an empty string because it writes a special value to memory, and other setters (except SetField variants) + /// will refuse to overwrite this value. Therefore, fields removed this way are more likely to stay removed. + /// + void RemoveLevelPrefix(); + + /// + /// Gets a pointer to the string array value in the provided field. + /// + /// The field to read from. + /// A pointer to a sequence of non-null bytes. + unsafe byte* GetFieldAsPointer(NamePlateStringField field); + + /// + /// Gets a byte span containing the string array value in the provided field. + /// + /// The field to read from. + /// A ReadOnlySpan containing a sequence of non-null bytes. + ReadOnlySpan GetFieldAsSpan(NamePlateStringField field); + + /// + /// Gets a UTF8 string copy of the string array value in the provided field. + /// + /// The field to read from. + /// A copy of the string array value as a string. + string GetFieldAsString(NamePlateStringField field); + + /// + /// Gets a parsed SeString copy of the string array value in the provided field. + /// + /// The field to read from. + /// A copy of the string array value as a parsed SeString. + SeString GetFieldAsSeString(NamePlateStringField field); + + /// + /// Sets the string array value for the provided field. + /// + /// The field to write to. + /// The string to write. + void SetField(NamePlateStringField field, string value); + + /// + /// Sets the string array value for the provided field. + /// + /// The field to write to. + /// The SeString to write. + void SetField(NamePlateStringField field, SeString value); + + /// + /// Sets the string array value for the provided field. + /// + /// The field to write to. + /// The ReadOnlySpan of bytes to write. + void SetField(NamePlateStringField field, ReadOnlySpan value); + + /// + /// Sets the string array value for the provided field. + /// + /// The field to write to. + /// The pointer to a null-terminated sequence of bytes to write. + unsafe void SetField(NamePlateStringField field, byte* value); + + /// + /// Sets the string array value for the provided field to a fixed pointer to an empty string in unmanaged memory. + /// Other methods may notice this fixed pointer and refuse to overwrite it, preserving the emptiness of the field. + /// + /// The field to write to. + void RemoveField(NamePlateStringField field); +} + +/// +/// A class representing a single nameplate. Provides mechanisms to look up the game object associated with the +/// nameplate and allows for modification of various backing fields in number and string array data, which in turn +/// affect aspects of the nameplate's appearance when drawn. Instances of this class are only valid for a single frame +/// and should not be kept across frames. +/// +internal unsafe class NamePlateUpdateHandler : INamePlateUpdateHandler +{ + private readonly NamePlateUpdateContext context; + + private ulong? gameObjectId; + private IGameObject? gameObject; + private NamePlateInfoView? infoView; + private NamePlatePartsContainer? partsContainer; + + /// + /// Initializes a new instance of the class. + /// + /// The current update context. + /// The index for this nameplate data in the backing number and string array data. This is + /// not the same as the rendered index, which can be retrieved from . + internal NamePlateUpdateHandler(NamePlateUpdateContext context, int arrayIndex) + { + this.context = context; + this.ArrayIndex = arrayIndex; + } + + /// + public int ArrayIndex { get; } + + /// + public ulong GameObjectId => this.gameObjectId ??= this.NamePlateInfo->ObjectId; + + /// + public IGameObject? GameObject => this.gameObject ??= this.context.ObjectTable.CreateObjectReference( + (nint)this.context.Ui3DModule->NamePlateObjectInfoPointers[ + this.ArrayIndex].Value->GameObject); + + /// + public IBattleChara? BattleChara => this.GameObject as IBattleChara; + + /// + public IPlayerCharacter? PlayerCharacter => this.GameObject as IPlayerCharacter; + + /// + public INamePlateInfoView InfoView => this.infoView ??= new NamePlateInfoView(this.NamePlateInfo); + + /// + public nint NamePlateInfoAddress => (nint)this.NamePlateInfo; + + /// + public nint NamePlateObjectAddress => (nint)this.NamePlateObject; + + /// + public NamePlateKind NamePlateKind => (NamePlateKind)this.ObjectData->NamePlateKind; + + /// + public int UpdateFlags + { + get => this.ObjectData->UpdateFlags; + private set => this.ObjectData->UpdateFlags = value; + } + + /// + public uint TextColor + { + get => this.ObjectData->NameTextColor; + set + { + if (value != this.TextColor) this.UpdateFlags |= 2; + this.ObjectData->NameTextColor = value; + } + } + + /// + public uint EdgeColor + { + get => this.ObjectData->NameEdgeColor; + set + { + if (value != this.EdgeColor) this.UpdateFlags |= 2; + this.ObjectData->NameEdgeColor = value; + } + } + + /// + public int MarkerIconId + { + get => this.ObjectData->MarkerIconId; + set => this.ObjectData->MarkerIconId = value; + } + + /// + public int NameIconId + { + get => this.ObjectData->NameIconId; + set => this.ObjectData->NameIconId = value; + } + + /// + public int NamePlateIndex => this.ObjectData->NamePlateObjectIndex; + + /// + public int DrawFlags + { + get => this.ObjectData->DrawFlags; + private set => this.ObjectData->DrawFlags = value; + } + + /// + public int VisibilityFlags + { + get => ObjectData->VisibilityFlags; + set => ObjectData->VisibilityFlags = value; + } + + /// + public bool IsUpdating => (this.UpdateFlags & 1) != 0; + + /// + public bool IsPrefixTitle + { + get => (this.DrawFlags & 1) != 0; + set => this.DrawFlags = value ? this.DrawFlags | 1 : this.DrawFlags & ~1; + } + + /// + public bool DisplayTitle + { + get => (this.DrawFlags & 0x80) == 0; + set => this.DrawFlags = value ? this.DrawFlags & ~0x80 : this.DrawFlags | 0x80; + } + + /// + public SeString Name + { + get => this.GetFieldAsSeString(NamePlateStringField.Name); + set => this.WeakSetField(NamePlateStringField.Name, value); + } + + /// + public NamePlateSimpleParts NameParts => this.PartsContainer.Name; + + /// + public SeString Title + { + get => this.GetFieldAsSeString(NamePlateStringField.Title); + set => this.WeakSetField(NamePlateStringField.Title, value); + } + + /// + public NamePlateQuotedParts TitleParts => this.PartsContainer.Title; + + /// + public SeString FreeCompanyTag + { + get => this.GetFieldAsSeString(NamePlateStringField.FreeCompanyTag); + set => this.WeakSetField(NamePlateStringField.FreeCompanyTag, value); + } + + /// + public NamePlateQuotedParts FreeCompanyTagParts => this.PartsContainer.FreeCompanyTag; + + /// + public SeString StatusPrefix + { + get => this.GetFieldAsSeString(NamePlateStringField.StatusPrefix); + set => this.WeakSetField(NamePlateStringField.StatusPrefix, value); + } + + /// + public SeString TargetSuffix + { + get => this.GetFieldAsSeString(NamePlateStringField.TargetSuffix); + set => this.WeakSetField(NamePlateStringField.TargetSuffix, value); + } + + /// + public SeString LevelPrefix + { + get => this.GetFieldAsSeString(NamePlateStringField.LevelPrefix); + set => this.WeakSetField(NamePlateStringField.LevelPrefix, value); + } + + /// + /// Gets or (lazily) creates a part builder container for this nameplate. + /// + internal NamePlatePartsContainer PartsContainer => + this.partsContainer ??= new NamePlatePartsContainer(this.context); + + private RaptureAtkModule.NamePlateInfo* NamePlateInfo => + this.context.RaptureAtkModule->NamePlateInfoEntries.GetPointer(this.NamePlateIndex); + + private AddonNamePlate.NamePlateObject* NamePlateObject => + &this.context.Addon->NamePlateObjectArray[this.NamePlateIndex]; + + private AddonNamePlate.NamePlateIntArrayData.NamePlateObjectIntArrayData* ObjectData => + this.context.NumberStruct->ObjectData.GetPointer(this.ArrayIndex); + + /// + public void RemoveName() => this.RemoveField(NamePlateStringField.Name); + + /// + public void RemoveTitle() => this.RemoveField(NamePlateStringField.Title); + + /// + public void RemoveFreeCompanyTag() => this.RemoveField(NamePlateStringField.FreeCompanyTag); + + /// + public void RemoveStatusPrefix() => this.RemoveField(NamePlateStringField.StatusPrefix); + + /// + public void RemoveTargetSuffix() => this.RemoveField(NamePlateStringField.TargetSuffix); + + /// + public void RemoveLevelPrefix() => this.RemoveField(NamePlateStringField.LevelPrefix); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte* GetFieldAsPointer(NamePlateStringField field) + { + return this.context.StringData->StringArray[this.ArrayIndex + (int)field]; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySpan GetFieldAsSpan(NamePlateStringField field) + { + return MemoryMarshal.CreateReadOnlySpanFromNullTerminated(this.GetFieldAsPointer(field)); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string GetFieldAsString(NamePlateStringField field) + { + return Encoding.UTF8.GetString(this.GetFieldAsSpan(field)); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public SeString GetFieldAsSeString(NamePlateStringField field) + { + return SeString.Parse(this.GetFieldAsSpan(field)); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetField(NamePlateStringField field, string value) + { + this.context.StringData->SetValue(this.ArrayIndex + (int)field, value, true, true, true); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetField(NamePlateStringField field, SeString value) + { + this.context.StringData->SetValue(this.ArrayIndex + (int)field, value.Encode(), true, true, true); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetField(NamePlateStringField field, ReadOnlySpan value) + { + this.context.StringData->SetValue(this.ArrayIndex + (int)field, value, true, true, true); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetField(NamePlateStringField field, byte* value) + { + this.context.StringData->SetValue(this.ArrayIndex + (int)field, value, true, true, true); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RemoveField(NamePlateStringField field) + { + this.context.StringData->SetValue( + this.ArrayIndex + (int)field, + (byte*)NamePlateGui.EmptyStringPointer, + true, + false, + true); + } + + /// + /// Resets the state of this handler for re-use in a new update. + /// + internal void ResetState() + { + this.gameObjectId = null; + this.gameObject = null; + this.infoView = null; + this.partsContainer = null; + } + + /// + /// Sets the string array value for the provided field, unless it was already set to the special empty string + /// pointer used by the Remove methods. + /// + /// The field to write to. + /// The SeString to write. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WeakSetField(NamePlateStringField field, SeString value) + { + if ((nint)this.GetFieldAsPointer(field) == NamePlateGui.EmptyStringPointer) + return; + this.context.StringData->SetValue(this.ArrayIndex + (int)field, value.Encode(), true, true, true); + } +} diff --git a/FCNameColor/Plugin.cs b/FCNameColor/Plugin.cs index 6045d7d..22197bd 100644 --- a/FCNameColor/Plugin.cs +++ b/FCNameColor/Plugin.cs @@ -14,10 +14,10 @@ using Dalamud.Plugin.Services; using Dalamud.Game.ClientState.Objects.Enums; using FCNameColor.Config; -using Dalamud.Game.Gui.NamePlate; using System.Numerics; using Dalamud.Interface.Windowing; using FCNameColor.UI; +using FCNameColor.Nameplates; namespace FCNameColor { @@ -27,6 +27,11 @@ public class Plugin : IDalamudPlugin private const string CommandName = "/fcnc"; private readonly ConfigurationV1 config; + // TODO: Temporary until this is merged: https://github.com/goatcorp/Dalamud/pull/1915 + [PluginService] public static IAddonLifecycle AddonLifecycleHandler { get; set; } = null!; + [PluginService] public static IGameGui GameGuiHandler { get; set; } = null!; + // + [PluginService] public static IDalamudPluginInterface Pi { get; private set; } [PluginService] public static ISigScanner SigScanner { get; private set; } [PluginService] public static IClientState ClientState { get; private set; } @@ -37,7 +42,7 @@ public class Plugin : IDalamudPlugin [PluginService] public static IFramework Framework { get; private set; } [PluginService] public static IGameInteropProvider GameInteropProvider { get; private set; } [PluginService] public static IPluginLog PluginLog { get; private set; } - [PluginService] public static INamePlateGui NamePlateGui { get; private set; } + //[PluginService] public static INamePlateGui NamePlateGui { get; private set; } public readonly WindowSystem WindowSystem = new("FC Name Color"); private Dictionary WorldNames; @@ -66,6 +71,8 @@ public class Plugin : IDalamudPlugin public string SearchingFCError = ""; public bool ConfigOpen => UI.IsOpen; + NamePlateGui NamePlateGui = new NamePlateGui(); + public Plugin(IDataManager dataManager) { config = new ConfigurationMigrator().GetConfig(Pi, PluginLog, Chat); From feb20fcea69a73ca48b237b9282277dc50630d74 Mon Sep 17 00:00:00 2001 From: Wessel Kuipers Date: Sat, 13 Jul 2024 21:25:32 +0200 Subject: [PATCH 8/9] Fix issue with replacing titles --- FCNameColor/Plugin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FCNameColor/Plugin.cs b/FCNameColor/Plugin.cs index 22197bd..2879e8d 100644 --- a/FCNameColor/Plugin.cs +++ b/FCNameColor/Plugin.cs @@ -591,7 +591,7 @@ private void NamePlateGui_OnNamePlateUpdate(INamePlateUpdateContext context, IRe { handler.NameParts.TextWrap = wrapper; - if (handler.DisplayTitle && handler.TitleParts.Text.TextValue.Length > 0) + if (handler.DisplayTitle) { handler.TitleParts.OuterWrap = wrapper; } From eeef06ac477575b78034d6b73e639bc597d89d47 Mon Sep 17 00:00:00 2001 From: Wessel Kuipers Date: Sat, 13 Jul 2024 23:16:13 +0200 Subject: [PATCH 9/9] Cleanup --- FCNameColor/NamePlates/NamePlateGui.cs | 1 - FCNameColor/Plugin.cs | 1 + FCNameColor/UI/AddAdditionalFCWindow.cs | 5 ----- FCNameColor/UI/AdditionalFCsWindow.cs | 3 --- FCNameColor/UI/PluginUI.cs | 6 +----- 5 files changed, 2 insertions(+), 14 deletions(-) diff --git a/FCNameColor/NamePlates/NamePlateGui.cs b/FCNameColor/NamePlates/NamePlateGui.cs index 65e8d20..afca2a8 100644 --- a/FCNameColor/NamePlates/NamePlateGui.cs +++ b/FCNameColor/NamePlates/NamePlateGui.cs @@ -3,7 +3,6 @@ using System.Runtime.InteropServices; using Dalamud.Game.Addon.Lifecycle; using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; -using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.UI; namespace FCNameColor.Nameplates; diff --git a/FCNameColor/Plugin.cs b/FCNameColor/Plugin.cs index 2879e8d..fa5bd80 100644 --- a/FCNameColor/Plugin.cs +++ b/FCNameColor/Plugin.cs @@ -18,6 +18,7 @@ using Dalamud.Interface.Windowing; using FCNameColor.UI; using FCNameColor.Nameplates; +using FFXIVClientStructs.FFXIV.Component.GUI; namespace FCNameColor { diff --git a/FCNameColor/UI/AddAdditionalFCWindow.cs b/FCNameColor/UI/AddAdditionalFCWindow.cs index afe1c3a..9924f35 100644 --- a/FCNameColor/UI/AddAdditionalFCWindow.cs +++ b/FCNameColor/UI/AddAdditionalFCWindow.cs @@ -4,13 +4,8 @@ using Dalamud.Interface.Windowing; using FCNameColor.Config; using ImGuiNET; -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; -using System.Text; using System.Text.RegularExpressions; -using System.Threading.Tasks; namespace FCNameColor.UI { diff --git a/FCNameColor/UI/AdditionalFCsWindow.cs b/FCNameColor/UI/AdditionalFCsWindow.cs index 42f8e50..5096b90 100644 --- a/FCNameColor/UI/AdditionalFCsWindow.cs +++ b/FCNameColor/UI/AdditionalFCsWindow.cs @@ -5,11 +5,8 @@ using FCNameColor.Config; using ImGuiNET; using System; -using System.Collections.Generic; using System.Linq; using System.Numerics; -using System.Text; -using System.Threading.Tasks; using Dalamud.Interface.Utility.Raii; namespace FCNameColor.UI diff --git a/FCNameColor/UI/PluginUI.cs b/FCNameColor/UI/PluginUI.cs index 1d83e16..10c135b 100644 --- a/FCNameColor/UI/PluginUI.cs +++ b/FCNameColor/UI/PluginUI.cs @@ -1,9 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; +using System.Collections.Generic; using System.Numerics; -using System.Text.RegularExpressions; using Dalamud.Interface; using Dalamud.Interface.Colors; using Dalamud.Interface.Components;