From f6fcd57270f899bcfc8163a66eca746fe65a28e9 Mon Sep 17 00:00:00 2001 From: r00telement <47005506+r00telement@users.noreply.github.com> Date: Wed, 22 Dec 2021 16:04:01 +0000 Subject: [PATCH] Added experimental LinkSelfInChat feature --- .../Configuration/PluginConfiguration.cs | 1 + .../Configuration/PluginConfigurationUI.cs | 8 +- PlayerTags/Features/ChatTagTargetFeature.cs | 95 +++++++++------ PlayerTags/Features/LinkSelfInChatFeature.cs | 115 ++++++++++++++++++ PlayerTags/Plugin.cs | 5 +- PlayerTags/Resources/Strings.Designer.cs | 36 ++++++ PlayerTags/Resources/Strings.resx | 12 ++ 7 files changed, 231 insertions(+), 41 deletions(-) create mode 100644 PlayerTags/Features/LinkSelfInChatFeature.cs diff --git a/PlayerTags/Configuration/PluginConfiguration.cs b/PlayerTags/Configuration/PluginConfiguration.cs index 6863757..282baac 100644 --- a/PlayerTags/Configuration/PluginConfiguration.cs +++ b/PlayerTags/Configuration/PluginConfiguration.cs @@ -27,6 +27,7 @@ namespace PlayerTags.Configuration public bool IsPlayersTabAllianceVisible = true; public bool IsPlayersTabEnemiesVisible = true; public bool IsPlayersTabOthersVisible = false; + public bool IsLinkSelfInChatEnabled = false; [JsonProperty(TypeNameHandling = TypeNameHandling.None, ItemTypeNameHandling = TypeNameHandling.None)] public Dictionary AllTagsChanges = new Dictionary(); diff --git a/PlayerTags/Configuration/PluginConfigurationUI.cs b/PlayerTags/Configuration/PluginConfigurationUI.cs index 5bbf498..fcc32d3 100644 --- a/PlayerTags/Configuration/PluginConfigurationUI.cs +++ b/PlayerTags/Configuration/PluginConfigurationUI.cs @@ -68,8 +68,9 @@ namespace PlayerTags.Configuration ImGui.Spacing(); ImGui.Spacing(); - DrawHeading(Strings.Loc_Static_Development); + DrawHeading(Strings.Loc_Static_Experimental); DrawCheckbox(nameof(m_PluginConfiguration.IsPlayerNameRandomlyGenerated), true, ref m_PluginConfiguration.IsPlayerNameRandomlyGenerated, () => m_PluginConfiguration.Save(m_PluginData)); + DrawCheckbox(nameof(m_PluginConfiguration.IsLinkSelfInChatEnabled), true, ref m_PluginConfiguration.IsLinkSelfInChatEnabled, () => m_PluginConfiguration.Save(m_PluginData)); ImGui.EndTabItem(); } @@ -279,13 +280,13 @@ namespace PlayerTags.Configuration string itemName = tag.Name.Value; if (m_PluginData.CustomTags.Contains(tag)) { - if (tag.Text.InheritedValue != null) + if (!string.IsNullOrWhiteSpace(tag.Text.InheritedValue)) { itemName = tag.Text.InheritedValue; } else { - itemName = ""; + itemName = Strings.Loc_Static_NoText; } } @@ -566,7 +567,6 @@ namespace PlayerTags.Configuration ImGui.SetTooltip(Strings.Loc_Static_AddPropertyOverride_Description); } - // Render all the property overrides, and optionally allow the inherited properties to be rendered IEnumerable> inheritables = tag.Inheritables; if (!m_PluginConfiguration.IsShowInheritedPropertiesEnabled) diff --git a/PlayerTags/Features/ChatTagTargetFeature.cs b/PlayerTags/Features/ChatTagTargetFeature.cs index a357c19..f328837 100644 --- a/PlayerTags/Features/ChatTagTargetFeature.cs +++ b/PlayerTags/Features/ChatTagTargetFeature.cs @@ -26,7 +26,7 @@ namespace PlayerTags.Features /// /// The matching text payload. /// - public TextPayload TextPayload { get; init; } + public TextPayload? TextPayload { get; init; } /// /// The matching game object if one exists @@ -38,10 +38,22 @@ namespace PlayerTags.Features /// public PlayerPayload? PlayerPayload { get; init; } - public StringMatch(SeString seString, TextPayload textPayload) + public Payload? PreferredPayload + { + get + { + if (TextPayload != null) + { + return TextPayload; + } + + return PlayerPayload; + } + } + + public StringMatch(SeString seString) { SeString = seString; - TextPayload = textPayload; } /// @@ -55,7 +67,17 @@ namespace PlayerTags.Features return GameObject.Name.TextValue; } - return TextPayload.Text; + if (TextPayload != null) + { + return TextPayload.Text; + } + + if (PlayerPayload != null) + { + return PlayerPayload.PlayerName; + } + + return SeString.TextValue; } } @@ -118,23 +140,24 @@ namespace PlayerTags.Features { var gameObject = PluginServices.ObjectTable.FirstOrDefault(gameObject => gameObject.Name.TextValue == playerPayload.PlayerName); + TextPayload? textPayload = null; + // The next payload MUST be a text payload - if (payloadIndex + 1 < seString.Payloads.Count && seString.Payloads[payloadIndex + 1] is TextPayload textPayload) + if (payloadIndex + 1 < seString.Payloads.Count) { - var stringMatch = new StringMatch(seString, textPayload) - { - GameObject = gameObject, - PlayerPayload = playerPayload - }; - stringMatches.Add(stringMatch); + textPayload = seString.Payloads[payloadIndex + 1] as TextPayload; // Don't handle the text payload twice payloadIndex++; } - else + + var stringMatch = new StringMatch(seString) { - PluginLog.Error("Expected payload after player payload to be a text payload but it wasn't"); - } + GameObject = gameObject, + PlayerPayload = playerPayload, + TextPayload = textPayload + }; + stringMatches.Add(stringMatch); } } @@ -202,26 +225,7 @@ namespace PlayerTags.Features } // An additional step to apply text color to additional locations - if (stringMatch.GameObject is PlayerCharacter playerCharacter1) - { - if (m_PluginData.JobTags.TryGetValue(playerCharacter1.ClassJob.GameData.Abbreviation, out var jobTag)) - { - if (IsTagVisible(jobTag, stringMatch.GameObject)) - { - if (jobTag.TextColor.InheritedValue != null) - { - if (jobTag.IsTextColorAppliedToChatName.InheritedValue != null && jobTag.IsTextColorAppliedToChatName.InheritedValue.Value) - { - int payloadIndex = message.Payloads.IndexOf(stringMatch.TextPayload); - message.Payloads.Insert(payloadIndex + 1, new UIForegroundPayload(0)); - message.Payloads.Insert(payloadIndex, (new UIForegroundPayload(jobTag.TextColor.InheritedValue.Value))); - } - } - } - } - } - - if (stringMatch.PlayerPayload != null) + if (stringMatch.PlayerPayload != null && stringMatch.PreferredPayload != null) { foreach (var customTag in m_PluginData.CustomTags) { @@ -233,7 +237,7 @@ namespace PlayerTags.Features { if (customTag.IsTextColorAppliedToChatName.InheritedValue != null && customTag.IsTextColorAppliedToChatName.InheritedValue.Value) { - int payloadIndex = message.Payloads.IndexOf(stringMatch.TextPayload); + int payloadIndex = message.Payloads.IndexOf(stringMatch.PreferredPayload); message.Payloads.Insert(payloadIndex + 1, new UIForegroundPayload(0)); message.Payloads.Insert(payloadIndex, (new UIForegroundPayload(customTag.TextColor.InheritedValue.Value))); } @@ -241,9 +245,28 @@ namespace PlayerTags.Features } } } + + if (stringMatch.GameObject is PlayerCharacter playerCharacter1) + { + if (m_PluginData.JobTags.TryGetValue(playerCharacter1.ClassJob.GameData.Abbreviation, out var jobTag)) + { + if (IsTagVisible(jobTag, stringMatch.GameObject)) + { + if (jobTag.TextColor.InheritedValue != null) + { + if (jobTag.IsTextColorAppliedToChatName.InheritedValue != null && jobTag.IsTextColorAppliedToChatName.InheritedValue.Value) + { + int payloadIndex = message.Payloads.IndexOf(stringMatch.PreferredPayload); + message.Payloads.Insert(payloadIndex + 1, new UIForegroundPayload(0)); + message.Payloads.Insert(payloadIndex, (new UIForegroundPayload(jobTag.TextColor.InheritedValue.Value))); + } + } + } + } + } } - ApplyStringChanges(message, stringChanges, stringMatch.TextPayload); + ApplyStringChanges(message, stringChanges, stringMatch.PreferredPayload); } } } diff --git a/PlayerTags/Features/LinkSelfInChatFeature.cs b/PlayerTags/Features/LinkSelfInChatFeature.cs new file mode 100644 index 0000000..d82e3bb --- /dev/null +++ b/PlayerTags/Features/LinkSelfInChatFeature.cs @@ -0,0 +1,115 @@ +using Dalamud.Game.ClientState.Objects.SubKinds; +using Dalamud.Game.ClientState.Objects.Types; +using Dalamud.Game.Text; +using Dalamud.Game.Text.SeStringHandling; +using Dalamud.Game.Text.SeStringHandling.Payloads; +using Dalamud.Logging; +using PlayerTags.Configuration; +using PlayerTags.Data; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace PlayerTags.Features +{ + public class LinkSelfInChatFeature : IDisposable + { + private PluginConfiguration m_PluginConfiguration; + private PluginData m_PluginData; + + public LinkSelfInChatFeature(PluginConfiguration pluginConfiguration, PluginData pluginData) + { + m_PluginConfiguration = pluginConfiguration; + m_PluginData = pluginData; + + PluginServices.ChatGui.ChatMessage += Chat_ChatMessage; + } + + public void Dispose() + { + PluginServices.ChatGui.ChatMessage -= Chat_ChatMessage; + } + + private void Chat_ChatMessage(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled) + { + if (m_PluginConfiguration.IsLinkSelfInChatEnabled) + { + ParsePayloads(sender); + ParsePayloads(message); + } + } + + private void ParsePayloads(SeString seString) + { + if (PluginServices.ClientState.LocalPlayer != null) + { + foreach (var payload in seString.Payloads.ToArray()) + { + if (payload is not TextPayload textPayload) + { + continue; + } + + List playerTextPayloads = new List(); + + var playerName = PluginServices.ClientState.LocalPlayer.Name.TextValue; + + if (textPayload.Text == playerName) + { + playerTextPayloads.Add(textPayload); + textPayload.Text = textPayload.Text; + } + else + { + var textMatchIndex = textPayload.Text.IndexOf(playerName); + while (textMatchIndex >= 0) + { + var textPayloadIndex = seString.Payloads.IndexOf(payload); + + // Chop text to the left and insert it as a new payload + if (textMatchIndex > 0) + { + seString.Payloads.Insert(textPayloadIndex, new TextPayload(textPayload.Text.Substring(0, textMatchIndex))); + + // Remove from the chopped text from the original payload + textPayload.Text = textPayload.Text.Substring(textMatchIndex, textPayload.Text.Length - textMatchIndex); + } + + // This is the last reference to the local player in this payload + if (textPayload.Text.Length == playerName.Length) + { + playerTextPayloads.Add(textPayload); + break; + } + + // Create the new name payload and add it + var playerTextPayload = new TextPayload(playerName); + playerTextPayloads.Add(playerTextPayload); + seString.Payloads.Insert(textPayloadIndex, playerTextPayload); + + // Remove from the chopped text from the original payload + textPayload.Text = textPayload.Text.Substring(0, playerName.Length); + + textMatchIndex = textPayload.Text.IndexOf(playerName); + } + } + + foreach (var playerTextPayload in playerTextPayloads) + { + // This does some dodgy shit for an unknown reason. + // Typically when you receive a player payload followed by a text payload, it displays the text + // and links it with the player payload. When trying to make one of these manually, it displays the player payload separately, + // effectively doubling up the player name. + //var playerPayload = new PlayerPayload(playerName, PluginServices.ClientState.LocalPlayer.HomeWorld.Id); + //seString.Payloads.Insert(seString.Payloads.IndexOf(playerTextPayload), playerPayload); + + // For now, don't follow up with a text payload. Only use a player payload. + var playerPayload = new PlayerPayload(playerName, PluginServices.ClientState.LocalPlayer.HomeWorld.Id); + seString.Payloads.Insert(seString.Payloads.IndexOf(playerTextPayload), playerPayload); + seString.Payloads.Remove(playerTextPayload); + } + } + } + } + } +} diff --git a/PlayerTags/Plugin.cs b/PlayerTags/Plugin.cs index 8368839..8b59d2c 100644 --- a/PlayerTags/Plugin.cs +++ b/PlayerTags/Plugin.cs @@ -18,6 +18,7 @@ namespace PlayerTags private PluginData m_PluginData; private PluginConfigurationUI m_PluginConfigurationUI; + private LinkSelfInChatFeature m_LinkSelfInChatFeature; private CustomTagsContextMenuFeature m_CustomTagsContextMenuFeature; private NameplatesTagTargetFeature m_NameplatesTagTargetFeature; private ChatTagTargetFeature m_ChatTagTargetFeature; @@ -38,6 +39,7 @@ namespace PlayerTags m_PluginConfiguration.IsVisible = true; m_PluginConfiguration.Save(m_PluginData); }) { HelpMessage = "Shows the config" }); + m_LinkSelfInChatFeature = new LinkSelfInChatFeature(m_PluginConfiguration, m_PluginData); m_CustomTagsContextMenuFeature = new CustomTagsContextMenuFeature(m_XivCommon, m_PluginConfiguration, m_PluginData); m_NameplatesTagTargetFeature = new NameplatesTagTargetFeature(m_PluginConfiguration, m_PluginData); m_ChatTagTargetFeature = new ChatTagTargetFeature(m_PluginConfiguration, m_PluginData); @@ -47,7 +49,8 @@ namespace PlayerTags { m_ChatTagTargetFeature.Dispose(); m_NameplatesTagTargetFeature.Dispose(); - m_CustomTagsContextMenuFeature.Dispose(); + m_CustomTagsContextMenuFeature.Dispose(); + m_LinkSelfInChatFeature.Dispose(); PluginServices.CommandManager.RemoveHandler(c_CommandName); PluginServices.DalamudPluginInterface.UiBuilder.OpenConfigUi -= UiBuilder_OpenConfigUi; PluginServices.DalamudPluginInterface.UiBuilder.Draw -= UiBuilder_Draw; diff --git a/PlayerTags/Resources/Strings.Designer.cs b/PlayerTags/Resources/Strings.Designer.cs index 74c5e37..4091608 100644 --- a/PlayerTags/Resources/Strings.Designer.cs +++ b/PlayerTags/Resources/Strings.Designer.cs @@ -240,6 +240,24 @@ namespace PlayerTags.Resources { } } + /// + /// Looks up a localized string similar to Detect references to self in chat. + /// + public static string Loc_IsLinkSelfInChatEnabled { + get { + return ResourceManager.GetString("Loc_IsLinkSelfInChatEnabled", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Attempts to detect references to self in chat. This allows tags to appear in chat for yourself.. + /// + public static string Loc_IsLinkSelfInChatEnabled_Description { + get { + return ResourceManager.GetString("Loc_IsLinkSelfInChatEnabled_Description", resourceCulture); + } + } + /// /// Looks up a localized string similar to Randomly generate player names. /// @@ -1041,6 +1059,15 @@ namespace PlayerTags.Resources { } } + /// + /// Looks up a localized string similar to Experimental. + /// + public static string Loc_Static_Experimental { + get { + return ResourceManager.GetString("Loc_Static_Experimental", resourceCulture); + } + } + /// /// Looks up a localized string similar to Add '{0}' to {1}.. /// @@ -1086,6 +1113,15 @@ namespace PlayerTags.Resources { } } + /// + /// Looks up a localized string similar to <No text>. + /// + public static string Loc_Static_NoText { + get { + return ResourceManager.GetString("Loc_Static_NoText", resourceCulture); + } + } + /// /// Looks up a localized string similar to Player. /// diff --git a/PlayerTags/Resources/Strings.resx b/PlayerTags/Resources/Strings.resx index 02510cc..507b88e 100644 --- a/PlayerTags/Resources/Strings.resx +++ b/PlayerTags/Resources/Strings.resx @@ -252,15 +252,27 @@ Development + + Experimental + Randomly generate player names Replace every player's name with a randomly generated one. + + Detect references to self in chat + + + Attempts to detect references to self in chat. This allows tags to appear in chat for yourself. + Tags + + <No text> + All