diff --git a/PlayerTags/Configuration/PluginConfiguration.cs b/PlayerTags/Configuration/PluginConfiguration.cs index b5ff541..38587d6 100644 --- a/PlayerTags/Configuration/PluginConfiguration.cs +++ b/PlayerTags/Configuration/PluginConfiguration.cs @@ -56,6 +56,9 @@ namespace PlayerTags.Configuration [JsonProperty(TypeNameHandling = TypeNameHandling.None, ItemTypeNameHandling = TypeNameHandling.None)] public List> CustomTagsChanges = new List>(); + [JsonProperty(TypeNameHandling = TypeNameHandling.None, ItemTypeNameHandling = TypeNameHandling.None)] + public List Identities = new List(); + public event System.Action? Saved; public void Save(PluginData pluginData) @@ -151,6 +154,8 @@ namespace PlayerTags.Configuration CustomTagsChanges.Add(customTag.GetChanges()); } + Identities = pluginData.Identities; + PluginServices.DalamudPluginInterface.SavePluginConfig(this); Saved?.Invoke(); } diff --git a/PlayerTags/Configuration/PluginConfigurationUI.cs b/PlayerTags/Configuration/PluginConfigurationUI.cs index 28604ff..5f2a5af 100644 --- a/PlayerTags/Configuration/PluginConfigurationUI.cs +++ b/PlayerTags/Configuration/PluginConfigurationUI.cs @@ -135,7 +135,7 @@ namespace PlayerTags.Configuration .Where(gameObject => gameObject is PlayerCharacter) .Select(gameObject => gameObject as PlayerCharacter) .ToDictionary( - playerCharacter => Identity.From(playerCharacter!), + playerCharacter => m_PluginData.GetIdentity(playerCharacter!), playerCharacter => new PlayerInfo() { PlayerContext = PlayerContextHelper.GetPlayerContext(playerCharacter!), @@ -145,7 +145,7 @@ namespace PlayerTags.Configuration // Include party members that aren't in the game object list foreach (var partyMember in PluginServices.PartyList) { - var partyMemberIdentity = Identity.From(partyMember); + var partyMemberIdentity = m_PluginData.GetIdentity(partyMember); if (!playerNameContexts.ContainsKey(partyMemberIdentity)) { @@ -203,7 +203,7 @@ namespace PlayerTags.Configuration ImGui.TableHeadersRow(); int rowIndex = 0; - foreach (var identity in m_PluginData.CustomTags.SelectMany(customTag => customTag.IdentitiesToAddTo).Distinct().OrderBy(name => name)) + foreach (var identity in m_PluginData.Identities.ToArray()) { DrawQuickAddRow(identity, rowIndex); ++rowIndex; @@ -211,7 +211,7 @@ namespace PlayerTags.Configuration if (PluginServices.ObjectTable.Length == 0 && PluginServices.ClientState.LocalPlayer != null) { - DrawQuickAddRow(Identity.From(PluginServices.ClientState.LocalPlayer), 0); + DrawQuickAddRow(m_PluginData.GetIdentity(PluginServices.ClientState.LocalPlayer), 0); } ImGui.EndTable(); @@ -262,20 +262,20 @@ namespace PlayerTags.Configuration ImGui.TableNextColumn(); - bool isTagAssigned = customTag.CanAddToIdentity(identity); + bool isTagAssigned = identity.CustomTagIds.Contains(customTag.CustomId.Value); DrawSimpleCheckbox(string.Format(Strings.Loc_Static_Format_AddTagToPlayer, customTag.Text.InheritedValue, identity.Name), ref isTagAssigned, () => { if (isTagAssigned) { - customTag.AddIdentityToAddTo(identity); + m_PluginData.AddCustomTagToIdentity(customTag, identity); + m_PluginConfiguration.Save(m_PluginData); } else { - customTag.RemoveIdentityToAddTo(identity); + m_PluginData.RemoveCustomTagFromIdentity(customTag, identity); + m_PluginConfiguration.Save(m_PluginData); } - - m_PluginConfiguration.Save(m_PluginData); }); ImGui.PopID(); @@ -378,7 +378,7 @@ namespace PlayerTags.Configuration { IsExpanded = true, Text = Strings.Loc_Static_NewTag, - GameObjectNamesToApplyTo = "" + CustomId = Guid.NewGuid() }; m_PluginData.CustomTags.Add(newTag); @@ -413,6 +413,7 @@ namespace PlayerTags.Configuration ImGui.SetCursorPosX(ImGui.GetCursorPos().X + ImGui.GetContentRegionAvail().X - 23); if (ImGui.Button(FontAwesomeIcon.TrashAlt.ToIconString())) { + m_PluginData.RemoveCustomTagFromIdentities(tag); m_PluginData.AllCustomTags.Children.Remove(tag); m_PluginData.CustomTags.Remove(tag); m_PluginConfiguration.Save(m_PluginData); diff --git a/PlayerTags/Data/Identity.cs b/PlayerTags/Data/Identity.cs index 6d153bd..a14c9ce 100644 --- a/PlayerTags/Data/Identity.cs +++ b/PlayerTags/Data/Identity.cs @@ -1,23 +1,20 @@ -using Dalamud.Game.ClientState.Objects.SubKinds; -using Dalamud.Game.ClientState.Party; -using Dalamud.Game.Text.SeStringHandling.Payloads; -using Lumina.Excel.GeneratedSheets; +using Lumina.Excel.GeneratedSheets; +using Newtonsoft.Json; using System; +using System.Collections.Generic; using System.Linq; -using System.Text.RegularExpressions; namespace PlayerTags.Data { - // FirstName LastName - // FirstName LastName@World - // FirstName LastName:Id - // FirstName LastName@World:Id - public struct Identity : IComparable, IEquatable + [Serializable] + public class Identity : IComparable { public string Name; public uint? WorldId; - public string? Id; + public List CustomTagIds = new List(); + + [JsonIgnore] public string? World { get @@ -40,63 +37,10 @@ namespace PlayerTags.Data } } - private static Regex s_WorldRegex = new Regex(@"@([a-zA-Z0-9]+)"); - private static Regex s_IdRegex = new Regex(@"@([a-zA-Z0-9]+)"); - public Identity(string name) { Name = name; WorldId = null; - Id = null; - } - - public static Identity From(string str) - { - var identity = new Identity(); - - while (s_WorldRegex.Match(str) is Match match && match.Success) - { - if (uint.TryParse(match.Groups.Values.Last().Value, out var value)) - { - identity.WorldId = value; - } - - str = str.Replace(match.Value, ""); - } - - while (s_IdRegex.Match(str) is Match match && match.Success) - { - identity.Id = match.Groups.Values.Last().Value; - str = str.Replace(match.Value, ""); - } - - identity.Name = str; - - return identity; - } - - public static Identity From(PlayerCharacter playerCharacter) - { - return new Identity(playerCharacter.Name.TextValue) - { - WorldId = playerCharacter.HomeWorld.GameData.RowId - }; - } - - public static Identity From(PartyMember partyMember) - { - return new Identity(partyMember.Name.TextValue) - { - WorldId = partyMember.World.GameData.RowId - }; - } - - public static Identity From(PlayerPayload playerPayload) - { - return new Identity(playerPayload.PlayerName) - { - WorldId = playerPayload.World.RowId - }; } public override string ToString() @@ -108,76 +52,18 @@ namespace PlayerTags.Data str += $"@{World}"; } - if (Id != null) - { - str += $":{Id}"; - } - return str; } - public string ToDataString() + public int CompareTo(Identity? other) { - string str = Name; - - if (WorldId != null) + string? otherName = null; + if (other != null) { - str += $"@{WorldId}"; + otherName = other.Name; } - if (Id != null) - { - str += $":{Id}"; - } - - return str; - } - - public override bool Equals(object? obj) - { - return obj is Identity identity && Equals(identity); - } - - public bool Equals(Identity obj) - { - return this == obj; - } - - public static bool operator ==(Identity first, Identity second) - { - if (first.Id != null || second.Id != null) - { - return first.Id == second.Id; - } - - bool areNamesEqual = first.Name.ToLower().Trim() == second.Name.ToLower().Trim(); - - // If one of the worlds are null then it's technically equal as it could be promoted to the identity that does have a world - bool areWorldsEqual = first.WorldId == null || second.WorldId == null || first.WorldId == second.WorldId; - - return areNamesEqual && areWorldsEqual; - } - - public static bool operator !=(Identity first, Identity second) - { - return !(first == second); - } - - public override int GetHashCode() - { - var hashCode = Name.GetHashCode(); - - if (Id != null) - { - hashCode *= 17 ^ Id.GetHashCode(); - } - - return hashCode; - } - - public int CompareTo(Identity other) - { - return Name.CompareTo(other.Name); + return Name.CompareTo(otherName); } } } diff --git a/PlayerTags/Data/PluginData.cs b/PlayerTags/Data/PluginData.cs index 5e2717c..f3fb1d1 100644 --- a/PlayerTags/Data/PluginData.cs +++ b/PlayerTags/Data/PluginData.cs @@ -1,7 +1,12 @@ -using PlayerTags.Configuration; +using Dalamud.Game.ClientState.Objects.SubKinds; +using Dalamud.Game.ClientState.Party; +using Dalamud.Game.Text.SeStringHandling.Payloads; +using PlayerTags.Configuration; using PlayerTags.PluginStrings; using System; using System.Collections.Generic; +using System.Linq; +using XivCommon.Functions.ContextMenu; namespace PlayerTags.Data { @@ -17,9 +22,14 @@ namespace PlayerTags.Data public Dictionary JobTags; public Tag AllCustomTags; public List CustomTags; + public List Identities; + + private PluginConfiguration m_PluginConfiguration; public PluginData(PluginConfiguration pluginConfiguration) { + m_PluginConfiguration = pluginConfiguration; + Default = new DefaultPluginData(); // Set the default changes and saved changes @@ -166,6 +176,132 @@ namespace PlayerTags.Data { tag.Parent = AllCustomTags; } + + Identities = pluginConfiguration.Identities; + + // Migrate old custom tag identity assignments + bool customTagsMigrated = false; + foreach (var customTag in CustomTags) + { + if (customTag.CustomId.Value == Guid.Empty) + { + customTag.CustomId.Behavior = Inheritables.InheritableBehavior.Enabled; + customTag.CustomId.Value = Guid.NewGuid(); + customTagsMigrated = true; + } + + foreach (string identityToAddTo in customTag.IdentitiesToAddTo) + { + Identity? identity = Identities.FirstOrDefault(identity => identity.Name.ToLower() == identityToAddTo.ToLower()); + if (identity == null) + { + identity = new Identity(identityToAddTo); + Identities.Add(identity); + } + + if (identity != null) + { + identity.CustomTagIds.Add(customTag.CustomId.Value); + customTagsMigrated = true; + } + } + + if (customTag.GameObjectNamesToApplyTo.Behavior != Inheritables.InheritableBehavior.Inherit) + { + customTag.GameObjectNamesToApplyTo.Behavior = Inheritables.InheritableBehavior.Inherit; + customTag.GameObjectNamesToApplyTo.Value = ""; + customTagsMigrated = true; + } + } + + if (customTagsMigrated) + { + pluginConfiguration.Save(this); + } + } + + public void AddCustomTagToIdentity(Tag customTag, Identity identity) + { + if (!identity.CustomTagIds.Contains(customTag.CustomId.Value)) + { + identity.CustomTagIds.Add(customTag.CustomId.Value); + } + + if (!Identities.Contains(identity)) + { + Identities.Add(identity); + } + } + + public void RemoveCustomTagFromIdentity(Tag customTag, Identity identity) + { + identity.CustomTagIds.Remove(customTag.CustomId.Value); + + if (!identity.CustomTagIds.Any()) + { + Identities.Remove(identity); + } + } + + public void RemoveCustomTagFromIdentities(Tag customTag) + { + foreach (var identity in Identities) + { + RemoveCustomTagFromIdentity(customTag, identity); + } + } + + public Identity GetIdentity(string name, uint? worldId) + { + foreach (var identity in Identities) + { + if (identity.Name.ToLower().Trim() == name.ToLower().Trim()) + { + if (identity.WorldId == null && worldId != null) + { + identity.WorldId = worldId; + m_PluginConfiguration.Save(this); + + return identity; + } + else + { + return identity; + } + } + } + + return new Identity(name) + { + WorldId = worldId + }; + } + + public Identity? GetIdentity(ContextMenuOpenArgs contextMenuOpenArgs) + { + if (contextMenuOpenArgs.Text == null + || contextMenuOpenArgs.ObjectWorld == 0 + || contextMenuOpenArgs.ObjectWorld == 65535) + { + return null; + } + + return GetIdentity(contextMenuOpenArgs.Text!.TextValue, contextMenuOpenArgs.ObjectWorld); + } + + public Identity GetIdentity(PlayerCharacter playerCharacter) + { + return GetIdentity(playerCharacter.Name.TextValue, playerCharacter.HomeWorld.GameData.RowId); + } + + public Identity GetIdentity(PartyMember partyMember) + { + return GetIdentity(partyMember.Name.TextValue, partyMember.World.GameData.RowId); + } + + public Identity GetIdentity(PlayerPayload playerPayload) + { + return GetIdentity(playerPayload.PlayerName, playerPayload.World.RowId); } } } diff --git a/PlayerTags/Data/Tag.cs b/PlayerTags/Data/Tag.cs index a4589ab..adc7836 100644 --- a/PlayerTags/Data/Tag.cs +++ b/PlayerTags/Data/Tag.cs @@ -91,8 +91,12 @@ namespace PlayerTags.Data Behavior = InheritableBehavior.Enabled }; + // Deprecated + [InheritableCategory("General")] public InheritableReference GameObjectNamesToApplyTo = new InheritableReference(""); + public InheritableValue CustomId = new InheritableValue(Guid.Empty); + [InheritableCategory("IconCategory")] public InheritableValue Icon = new InheritableValue(BitmapFontIcon.Aethernet); [InheritableCategory("IconCategory")] @@ -148,7 +152,7 @@ namespace PlayerTags.Data [InheritableCategory("PlayerCategory")] public InheritableValue IsVisibleForOtherPlayers = new InheritableValue(false); - private string[] IdentityDatasToAddTo + public string[] IdentitiesToAddTo { get { @@ -161,14 +165,6 @@ namespace PlayerTags.Data } } - public Identity[] IdentitiesToAddTo - { - get - { - return IdentityDatasToAddTo.Select(identityData => Identity.From(identityData)).ToArray(); - } - } - private Tag? m_Defaults; public bool HasDefaults { @@ -194,31 +190,6 @@ namespace PlayerTags.Data SetChanges(defaults.GetChanges()); } - public bool CanAddToIdentity(Identity identity) - { - return IdentitiesToAddTo.Contains(identity); - } - - public void AddIdentityToAddTo(Identity identity) - { - if (CanAddToIdentity(identity)) - { - return; - } - - GameObjectNamesToApplyTo.Value = string.Join(", ", IdentitiesToAddTo.Append(identity).Select(id => id.ToDataString())); - } - - public void RemoveIdentityToAddTo(Identity identity) - { - if (!CanAddToIdentity(identity)) - { - return; - } - - GameObjectNamesToApplyTo.Value = string.Join(", ", IdentitiesToAddTo.Where(identityToAddTo => identityToAddTo != identity).Select(id => id.ToDataString())); - } - public Dictionary GetChanges(Dictionary? defaultChanges = null) { Dictionary changes = new Dictionary(); diff --git a/PlayerTags/Features/ChatTagTargetFeature.cs b/PlayerTags/Features/ChatTagTargetFeature.cs index e8862c4..19944d1 100644 --- a/PlayerTags/Features/ChatTagTargetFeature.cs +++ b/PlayerTags/Features/ChatTagTargetFeature.cs @@ -205,12 +205,14 @@ namespace PlayerTags.Features } } + // Add custom tags if (stringMatch.PlayerPayload != null) { - // Add all other tags - foreach (var customTag in m_PluginData.CustomTags) + Identity identity = m_PluginData.GetIdentity(stringMatch.PlayerPayload); + foreach (var customTagId in identity.CustomTagIds) { - if (customTag.CanAddToIdentity(Identity.From(stringMatch.PlayerPayload))) + var customTag = m_PluginData.CustomTags.FirstOrDefault(tag => tag.CustomId.Value == customTagId); + if (customTag != null) { if (customTag.TagPositionInChat.InheritedValue != null) { @@ -227,9 +229,11 @@ namespace PlayerTags.Features // An additional step to apply text color to additional locations if (stringMatch.PlayerPayload != null && stringMatch.PreferredPayload != null) { - foreach (var customTag in m_PluginData.CustomTags) + Identity identity = m_PluginData.GetIdentity(stringMatch.PlayerPayload); + foreach (var customTagId in identity.CustomTagIds) { - if (customTag.CanAddToIdentity(Identity.From(stringMatch.PlayerPayload))) + var customTag = m_PluginData.CustomTags.FirstOrDefault(tag => tag.CustomId.Value == customTagId); + if (customTag != null) { if (IsTagVisible(customTag, stringMatch.GameObject)) { diff --git a/PlayerTags/Features/CustomTagsContextMenuFeature.cs b/PlayerTags/Features/CustomTagsContextMenuFeature.cs index ba33dbd..af39eaf 100644 --- a/PlayerTags/Features/CustomTagsContextMenuFeature.cs +++ b/PlayerTags/Features/CustomTagsContextMenuFeature.cs @@ -52,42 +52,40 @@ namespace PlayerTags.Features return; } - var identity = new Identity() + Identity? identity = m_PluginData.GetIdentity(args); + if (identity != null) { - Name = args.Text!.TextValue, - WorldId = args.ObjectWorld - }; - - var notAddedTags = m_PluginData.CustomTags.Where(tag => !tag.CanAddToIdentity(identity)); - if (notAddedTags.Any()) - { - args.Items.Add(new NormalContextSubMenuItem(Strings.Loc_Static_ContextMenu_AddTag, (itemArgs => + var notAddedTags = m_PluginData.CustomTags.Where(customTag => !identity.CustomTagIds.Contains(customTag.CustomId.Value)); + if (notAddedTags.Any()) { - foreach (var notAddedTag in notAddedTags) + args.Items.Add(new NormalContextSubMenuItem(Strings.Loc_Static_ContextMenu_AddTag, (itemArgs => { - itemArgs.Items.Add(new NormalContextMenuItem(notAddedTag.Text.Value, (args => + foreach (var notAddedTag in notAddedTags) { - notAddedTag.AddIdentityToAddTo(identity); - m_PluginConfiguration.Save(m_PluginData); - }))); - } - }))); - } + itemArgs.Items.Add(new NormalContextMenuItem(notAddedTag.Text.Value, (args => + { + m_PluginData.AddCustomTagToIdentity(notAddedTag, identity); + m_PluginConfiguration.Save(m_PluginData); + }))); + } + }))); + } - var addedTags = m_PluginData.CustomTags.Where(tag => tag.CanAddToIdentity(identity)); - if (addedTags.Any()) - { - args.Items.Add(new NormalContextSubMenuItem(Strings.Loc_Static_ContextMenu_RemoveTag, (itemArgs => + var addedTags = m_PluginData.CustomTags.Where(customTag => identity.CustomTagIds.Contains(customTag.CustomId.Value)); + if (addedTags.Any()) { - foreach (var addedTag in addedTags) + args.Items.Add(new NormalContextSubMenuItem(Strings.Loc_Static_ContextMenu_RemoveTag, (itemArgs => { - itemArgs.Items.Add(new NormalContextMenuItem(addedTag.Text.Value, (args => + foreach (var addedTag in addedTags) { - addedTag.RemoveIdentityToAddTo(identity); - m_PluginConfiguration.Save(m_PluginData); - }))); - } - }))); + itemArgs.Items.Add(new NormalContextMenuItem(addedTag.Text.Value, (args => + { + m_PluginData.RemoveCustomTagFromIdentity(addedTag, identity); + m_PluginConfiguration.Save(m_PluginData); + }))); + } + }))); + } } } diff --git a/PlayerTags/Features/NameplatesTagTargetFeature.cs b/PlayerTags/Features/NameplatesTagTargetFeature.cs index 66ef5dd..ea2b827 100644 --- a/PlayerTags/Features/NameplatesTagTargetFeature.cs +++ b/PlayerTags/Features/NameplatesTagTargetFeature.cs @@ -204,10 +204,12 @@ namespace PlayerTags.Features } } - // Add all other tags - foreach (var customTag in m_PluginData.CustomTags) + // Add custom tags + Identity identity = m_PluginData.GetIdentity(playerCharacter); + foreach (var customTagId in identity.CustomTagIds) { - if (customTag.CanAddToIdentity(Identity.From(playerCharacter))) + var customTag = m_PluginData.CustomTags.FirstOrDefault(tag => tag.CustomId.Value == customTagId); + if (customTag != null) { if (customTag.TagTargetInNameplates.InheritedValue != null && customTag.TagPositionInNameplates.InheritedValue != null) { @@ -250,9 +252,11 @@ namespace PlayerTags.Features if (gameObject is PlayerCharacter playerCharacter1) { // An additional step to apply text color to additional locations - foreach (var customTag in m_PluginData.CustomTags) + Identity identity = m_PluginData.GetIdentity(playerCharacter1); + foreach (var customTagId in identity.CustomTagIds) { - if (customTag.CanAddToIdentity(Identity.From(playerCharacter1))) + var customTag = m_PluginData.CustomTags.FirstOrDefault(tag => tag.CustomId.Value == customTagId); + if (customTag != null) { if (IsTagVisible(customTag, gameObject)) { diff --git a/PlayerTags/Inheritables/InheritableValue.cs b/PlayerTags/Inheritables/InheritableValue.cs index b62e4b9..e49e304 100644 --- a/PlayerTags/Inheritables/InheritableValue.cs +++ b/PlayerTags/Inheritables/InheritableValue.cs @@ -67,6 +67,10 @@ namespace PlayerTags.Inheritables // This should never happen PluginLog.Error($"Expected value of type {Value.GetType()} but received null"); } + else if (typeof(T) == typeof(Guid) && inheritableData.Value is string strValue) + { + Value = (T)(object)Guid.Parse(strValue); + } else { Value = (T)Convert.ChangeType(inheritableData.Value, typeof(T));