diff --git a/PlayerTags/Features/ChatTagTargetFeature.cs b/PlayerTags/Features/ChatTagTargetFeature.cs index 72508c7..aab575e 100644 --- a/PlayerTags/Features/ChatTagTargetFeature.cs +++ b/PlayerTags/Features/ChatTagTargetFeature.cs @@ -28,10 +28,7 @@ namespace PlayerTags.Features /// public SeString SeString { get; init; } - /// - /// The matching text payload. - /// - public TextPayload? TextPayload { get; init; } + public List DisplayTextPayloads { get; init; } = new(); /// /// The matching game object if one exists @@ -43,16 +40,18 @@ namespace PlayerTags.Features /// public PlayerPayload? PlayerPayload { get; init; } - public Payload? PreferredPayload + public Payload? PlayerNamePayload { get { - if (TextPayload != null) - { - return TextPayload; - } + Payload textPayload = null; + string textMatch = GetMatchText(); - return PlayerPayload; + textPayload = DisplayTextPayloads.FirstOrDefault(n => n is TextPayload textPayload && textPayload.Text.Contains(textMatch)); + textPayload ??= PlayerPayload; + textPayload ??= DisplayTextPayloads.FirstOrDefault(); + + return textPayload; } } @@ -72,11 +71,6 @@ namespace PlayerTags.Features return GameObject.Name.TextValue; } - if (TextPayload != null) - { - return TextPayload.Text; - } - if (PlayerPayload != null) { return PlayerPayload.PlayerName; @@ -107,8 +101,8 @@ namespace PlayerTags.Features { if (m_PluginConfiguration.GeneralOptions[ActivityContextManager.CurrentActivityContext].IsApplyTagsToAllChatMessagesEnabled || Enum.IsDefined(type)) { - AddTagsToChat(sender); - AddTagsToChat(message); + AddTagsToChat(sender, type, true); + AddTagsToChat(message, type, false); } } @@ -139,31 +133,44 @@ namespace PlayerTags.Features /// A list of matched game objects. private List GetStringMatches(SeString seString) { - List stringMatches = new List(); + List stringMatches = new(); + Stack curPlayerPayload = new(); + Stack> curRefPayloads = new(); + var defaultRawPayload = RawPayload.LinkTerminator.Data; - for (int payloadIndex = 0; payloadIndex < seString.Payloads.Count; ++payloadIndex) + foreach (var payload in seString.Payloads) { - var payload = seString.Payloads[payloadIndex]; + if (payload is PlayerPayload playerPayload) + { + curPlayerPayload.Push(playerPayload); + curRefPayloads.Push(new List()); + } + else if (payload is RawPayload rawPayload) + { + if (defaultRawPayload.SequenceEqual(rawPayload.Data)) + finishCurrentMatch(); + } + else + { + if (curRefPayloads.TryPeek(out List result)) + result.Add(payload); + } + } + + // Finally finish, if not closed by RawPayload + finishCurrentMatch(); + + void finishCurrentMatch() + { + if (curPlayerPayload.TryPop(out PlayerPayload playerPayload)) { 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) - { - textPayload = seString.Payloads[payloadIndex + 1] as TextPayload; - - // Don't handle the text payload twice - payloadIndex++; - } - var stringMatch = new StringMatch(seString) { GameObject = gameObject, PlayerPayload = playerPayload, - TextPayload = textPayload + DisplayTextPayloads = curRefPayloads.Pop() }; stringMatches.Add(stringMatch); } @@ -172,12 +179,45 @@ namespace PlayerTags.Features return stringMatches; } + private void SplitOffPartyNumberPrefix(SeString sender, XivChatType type) + { + if (type == XivChatType.Party || type == XivChatType.Alliance) + { + PlayerPayload lastPlayerPayload = null; + foreach (var payload in sender.Payloads.ToArray()) + { + if (payload is PlayerPayload playerPayload) + lastPlayerPayload = playerPayload; + else if (payload is TextPayload playerNamePayload && lastPlayerPayload != null) + { + // Get position of player name in payload + var indexOfPlayerName = playerNamePayload.Text.IndexOf(lastPlayerPayload.PlayerName); + + if (indexOfPlayerName > 0) + { + // Split off the name from the prefix number + var prefixPayload = new TextPayload(playerNamePayload.Text[..indexOfPlayerName]); + playerNamePayload.Text = playerNamePayload.Text[indexOfPlayerName..]; + + // Add prefix number before the player name payload + var playerNamePayloadIndex = sender.Payloads.IndexOf(playerNamePayload); + sender.Payloads.Insert(playerNamePayloadIndex, prefixPayload); + } + } + } + } + } + /// /// Adds all configured tags to a chat message. /// /// The message to change. - private void AddTagsToChat(SeString message) + private void AddTagsToChat(SeString message, XivChatType chatType, bool isSender) { + // Split out the party/alliance number from the PlayerPayload + if (isSender) + SplitOffPartyNumberPrefix(message, chatType); + var stringMatches = GetStringMatches(message); foreach (var stringMatch in stringMatches) { @@ -235,7 +275,7 @@ namespace PlayerTags.Features } // An additional step to apply text color to additional locations - if (stringMatch.PlayerPayload != null && stringMatch.PreferredPayload != null) + if (stringMatch.PlayerPayload != null && stringMatch.DisplayTextPayloads.Any()) { Identity identity = m_PluginData.GetIdentity(stringMatch.PlayerPayload); foreach (var customTagId in identity.CustomTagIds) @@ -252,10 +292,10 @@ namespace PlayerTags.Features } void applyTextFormatting(Tag tag) - => ApplyTextFormatting(stringMatch.GameObject, tag, new[] { message }, new[] { tag.IsTextColorAppliedToChatName }, stringMatch.PreferredPayload); + => ApplyTextFormatting(stringMatch.GameObject, tag, new[] { message }, new[] { tag.IsTextColorAppliedToChatName }, stringMatch.DisplayTextPayloads); } - ApplyStringChanges(message, stringChanges, stringMatch.PreferredPayload); + ApplyStringChanges(message, stringChanges, stringMatch.DisplayTextPayloads, stringMatch.PlayerNamePayload); } // Replace PlayerPayloads of your own character with TextPayloads diff --git a/PlayerTags/Features/LinkSelfInChatFeature.cs b/PlayerTags/Features/LinkSelfInChatFeature.cs index d1a078e..7f346cc 100644 --- a/PlayerTags/Features/LinkSelfInChatFeature.cs +++ b/PlayerTags/Features/LinkSelfInChatFeature.cs @@ -33,12 +33,12 @@ namespace PlayerTags.Features { if (m_PluginConfiguration.GeneralOptions[activityContextManager.CurrentActivityContext].IsLinkSelfInChatEnabled) { - ParsePayloads(sender); - ParsePayloads(message); + ParsePayloads(sender, type, true); + ParsePayloads(message, type, false); } } - private void ParsePayloads(SeString seString) + private void ParsePayloads(SeString seString, XivChatType chatType, bool isSender) { if (PluginServices.ClientState.LocalPlayer != null) { @@ -101,7 +101,10 @@ namespace PlayerTags.Features // 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); - var playerPayloadIndex = seString.Payloads.IndexOf(playerTextPayload); + int playerPayloadIndex = seString.Payloads.IndexOf(playerTextPayload); + + if (isSender && (chatType == XivChatType.Party || chatType == XivChatType.Alliance)) + playerPayloadIndex--; // Add the Player Link Payload seString.Payloads.Insert(playerPayloadIndex++, playerPayload); diff --git a/PlayerTags/Features/TagTargetFeature.cs b/PlayerTags/Features/TagTargetFeature.cs index 8261f53..911c697 100644 --- a/PlayerTags/Features/TagTargetFeature.cs +++ b/PlayerTags/Features/TagTargetFeature.cs @@ -240,7 +240,7 @@ namespace PlayerTags.Features /// The string to apply changes to. /// The changes to apply. /// The payload in the string that changes should be anchored to. If there is no anchor, the changes will be applied to the entire string. - protected void ApplyStringChanges(SeString seString, Dictionary> stringChanges, Payload? anchorPayload = null) + protected void ApplyStringChanges(SeString seString, Dictionary> stringChanges, List anchorPayloads = null, Payload anchorReplacePayload = null) { if (stringChanges.Count == 0) { @@ -249,7 +249,7 @@ namespace PlayerTags.Features List tagPositionsOrdered = new List(); // If there's no anchor payload, do replaces first so that befores and afters are based on the replaced data - if (anchorPayload == null) + if (anchorPayloads == null || !anchorPayloads.Any()) { tagPositionsOrdered.Add(TagPosition.Replace); } @@ -258,7 +258,7 @@ namespace PlayerTags.Features tagPositionsOrdered.Add(TagPosition.After); // If there is an anchor payload, do replaces last so that we still know which payload needs to be removed - if (anchorPayload != null) + if (anchorPayloads != null && anchorPayloads.Any()) { tagPositionsOrdered.Add(TagPosition.Replace); } @@ -270,8 +270,9 @@ namespace PlayerTags.Features AddSpacesBetweenTextPayloads(stringChanges[tagPosition], tagPosition); if (tagPosition == TagPosition.Before) { - if (anchorPayload != null) + if (anchorPayloads != null && anchorPayloads.Any()) { + var anchorPayload = anchorPayloads.First(); var anchorPayloadIndex = seString.Payloads.IndexOf(anchorPayload); seString.Payloads.InsertRange(anchorPayloadIndex, payloads); } @@ -282,8 +283,9 @@ namespace PlayerTags.Features } else if (tagPosition == TagPosition.After) { - if (anchorPayload != null) + if (anchorPayloads != null && anchorPayloads.Any()) { + var anchorPayload = anchorPayloads.Last(); var anchorPayloadIndex = seString.Payloads.IndexOf(anchorPayload); seString.Payloads.InsertRange(anchorPayloadIndex + 1, payloads); } @@ -294,11 +296,11 @@ namespace PlayerTags.Features } else if (tagPosition == TagPosition.Replace) { - if (anchorPayload != null) + if (anchorReplacePayload != null) { - var anchorPayloadIndex = seString.Payloads.IndexOf(anchorPayload); + var anchorPayloadIndex = seString.Payloads.IndexOf(anchorReplacePayload); seString.Payloads.InsertRange(anchorPayloadIndex, payloads); - seString.Payloads.Remove(anchorPayload); + seString.Payloads.Remove(anchorReplacePayload); } else { @@ -310,7 +312,7 @@ namespace PlayerTags.Features } } - protected void ApplyTextFormatting(GameObject gameObject, Tag tag, SeString[] destStrings, InheritableValue[] textColorApplied, Payload preferedPayload) + protected void ApplyTextFormatting(GameObject gameObject, Tag tag, SeString[] destStrings, InheritableValue[] textColorApplied, List preferedPayloads) { if (IsTagVisible(tag, gameObject)) { @@ -355,10 +357,10 @@ namespace PlayerTags.Features void applyTextFormattingPayloads(SeString destPayload, Payload startPayload, Payload endPayload) { - if (preferedPayload == null) + if (preferedPayloads == null || !preferedPayloads.Any()) applyTextFormattingPayloadToStartAndEnd(destPayload, startPayload, endPayload); else - applyTextFormattingPayloadsToSpecificPosition(destPayload, startPayload, endPayload, preferedPayload); + applyTextFormattingPayloadsToSpecificPosition(destPayload, startPayload, endPayload, preferedPayloads); } void applyTextFormattingPayloadToStartAndEnd(SeString destPayload, Payload startPayload, Payload endPayload) @@ -367,11 +369,13 @@ namespace PlayerTags.Features destPayload.Payloads.Add(endPayload); } - void applyTextFormattingPayloadsToSpecificPosition(SeString destPayload, Payload startPayload, Payload endPayload, Payload preferedPayload) + void applyTextFormattingPayloadsToSpecificPosition(SeString destPayload, Payload startPayload, Payload endPayload, List preferedPayload) { - int payloadIndex = destPayload.Payloads.IndexOf(preferedPayload); - destPayload.Payloads.Insert(payloadIndex + 1, endPayload); - destPayload.Payloads.Insert(payloadIndex, startPayload); + int payloadStartIndex = destPayload.Payloads.IndexOf(preferedPayloads.First()); + destPayload.Payloads.Insert(payloadStartIndex, startPayload); + + int payloadEndIndex = destPayload.Payloads.IndexOf(preferedPayloads.Last()); + destPayload.Payloads.Insert(payloadEndIndex + 1, endPayload); } } }