First pass of reorganizing code
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
using Dalamud.Configuration;
|
||||
using Dalamud.Plugin;
|
||||
using Newtonsoft.Json;
|
||||
using PlayerTags.Data;
|
||||
using PlayerTags.Inheritables;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace PlayerTags
|
||||
namespace PlayerTags.Configuration
|
||||
{
|
||||
[Serializable]
|
||||
public class PluginConfiguration : IPluginConfiguration
|
||||
@@ -16,7 +18,7 @@ namespace PlayerTags
|
||||
public NameplateTitleVisibility NameplateTitleVisibility = NameplateTitleVisibility.WhenHasTags;
|
||||
public NameplateTitlePosition NameplateTitlePosition = NameplateTitlePosition.AlwaysAboveName;
|
||||
public bool IsPlayerNameRandomlyGenerated = false;
|
||||
public bool IsCustomTagContextMenuEnabled = true;
|
||||
public bool IsCustomTagsContextMenuEnabled = true;
|
||||
public bool IsShowInheritedPropertiesEnabled = true;
|
||||
|
||||
[JsonProperty(TypeNameHandling = TypeNameHandling.None, ItemTypeNameHandling = TypeNameHandling.None)]
|
||||
@@ -1,35 +1,29 @@
|
||||
using Dalamud.Game.ClientState;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
using Dalamud.Game.ClientState.Party;
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
using Dalamud.Interface;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using ImGuiNET;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using PlayerTags.Data;
|
||||
using PlayerTags.Inheritables;
|
||||
using PlayerTags.PluginStrings;
|
||||
using PlayerTags.Resources;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
namespace PlayerTags
|
||||
namespace PlayerTags.Configuration
|
||||
{
|
||||
public class PluginConfigurationUI
|
||||
{
|
||||
private PluginConfiguration m_PluginConfiguration;
|
||||
private PluginData m_PluginData;
|
||||
private ClientState m_ClientState;
|
||||
private PartyList m_PartyList;
|
||||
private ObjectTable m_ObjectTable;
|
||||
|
||||
private InheritableValue<ushort>? m_ColorPickerPopupDataContext;
|
||||
|
||||
public PluginConfigurationUI(PluginConfiguration config, PluginData pluginData, ClientState clientState, PartyList partyList, ObjectTable objectTable)
|
||||
public PluginConfigurationUI(PluginConfiguration config, PluginData pluginData)
|
||||
{
|
||||
m_PluginConfiguration = config;
|
||||
m_PluginData = pluginData;
|
||||
m_ClientState = clientState;
|
||||
m_PartyList = partyList;
|
||||
m_ObjectTable = objectTable;
|
||||
}
|
||||
|
||||
public void Draw()
|
||||
@@ -54,7 +48,7 @@ namespace PlayerTags
|
||||
ImGui.Spacing();
|
||||
ImGui.Spacing();
|
||||
ImGui.TreePush();
|
||||
DrawCheckbox(nameof(m_PluginConfiguration.IsCustomTagContextMenuEnabled), true, ref m_PluginConfiguration.IsCustomTagContextMenuEnabled, () => m_PluginConfiguration.Save(m_PluginData));
|
||||
DrawCheckbox(nameof(m_PluginConfiguration.IsCustomTagsContextMenuEnabled), true, ref m_PluginConfiguration.IsCustomTagsContextMenuEnabled, () => m_PluginConfiguration.Save(m_PluginData));
|
||||
ImGui.TreePop();
|
||||
|
||||
|
||||
@@ -124,15 +118,15 @@ namespace PlayerTags
|
||||
ImGui.TableHeadersRow();
|
||||
|
||||
int rowIndex = 0;
|
||||
foreach (var partyMember in m_PartyList.OrderBy(obj => obj.Name.TextValue).ToArray())
|
||||
foreach (var partyMember in PluginServices.PartyList.OrderBy(obj => obj.Name.TextValue).ToArray())
|
||||
{
|
||||
DrawPlayerAssignmentRow(partyMember.Name.TextValue, rowIndex);
|
||||
++rowIndex;
|
||||
}
|
||||
|
||||
if (m_PartyList.Length == 0 && m_ClientState.LocalPlayer != null)
|
||||
if (PluginServices.PartyList.Length == 0 && PluginServices.ClientState.LocalPlayer != null)
|
||||
{
|
||||
DrawPlayerAssignmentRow(m_ClientState.LocalPlayer.Name.TextValue, 0);
|
||||
DrawPlayerAssignmentRow(PluginServices.ClientState.LocalPlayer.Name.TextValue, 0);
|
||||
}
|
||||
|
||||
ImGui.EndTable();
|
||||
@@ -161,15 +155,15 @@ namespace PlayerTags
|
||||
ImGui.TableHeadersRow();
|
||||
|
||||
int rowIndex = 0;
|
||||
foreach (var gameObject in m_ObjectTable.Where(obj => obj is PlayerCharacter).OrderBy(obj => obj.Name.TextValue))
|
||||
foreach (var gameObject in PluginServices.ObjectTable.Where(obj => obj is PlayerCharacter).OrderBy(obj => obj.Name.TextValue))
|
||||
{
|
||||
DrawPlayerAssignmentRow(gameObject.Name.TextValue, rowIndex);
|
||||
++rowIndex;
|
||||
}
|
||||
|
||||
if (m_ObjectTable.Length == 0 && m_ClientState.LocalPlayer != null)
|
||||
if (PluginServices.ObjectTable.Length == 0 && PluginServices.ClientState.LocalPlayer != null)
|
||||
{
|
||||
DrawPlayerAssignmentRow(m_ClientState.LocalPlayer.Name.TextValue, 0);
|
||||
DrawPlayerAssignmentRow(PluginServices.ClientState.LocalPlayer.Name.TextValue, 0);
|
||||
}
|
||||
|
||||
ImGui.EndTable();
|
||||
@@ -204,9 +198,9 @@ namespace PlayerTags
|
||||
++rowIndex;
|
||||
}
|
||||
|
||||
if (m_ObjectTable.Length == 0 && m_ClientState.LocalPlayer != null)
|
||||
if (PluginServices.ObjectTable.Length == 0 && PluginServices.ClientState.LocalPlayer != null)
|
||||
{
|
||||
DrawPlayerAssignmentRow(m_ClientState.LocalPlayer.Name.TextValue, 0);
|
||||
DrawPlayerAssignmentRow(PluginServices.ClientState.LocalPlayer.Name.TextValue, 0);
|
||||
}
|
||||
|
||||
ImGui.EndTable();
|
||||
@@ -1,32 +1,37 @@
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using PlayerTags.Inheritables;
|
||||
using PlayerTags.PluginStrings;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace PlayerTags
|
||||
namespace PlayerTags.Data
|
||||
{
|
||||
public class DefaultPluginData
|
||||
{
|
||||
public Dictionary<byte, Role> RolesById { get; } = new Dictionary<byte, Role>()
|
||||
public Dictionary<byte, Role> RolesById { get; }
|
||||
public Dictionary<string, Role> RolesByJobAbbreviation { get; }
|
||||
|
||||
public Dictionary<string, InheritableData> AllTagsChanges { get; }
|
||||
public Dictionary<string, InheritableData> AllRoleTagsChanges { get; }
|
||||
public Dictionary<Role, Dictionary<string, InheritableData>> RoleTagsChanges { get; }
|
||||
public Dictionary<string, Dictionary<string, InheritableData>> JobTagsChanges { get; }
|
||||
public Dictionary<string, InheritableData> AllCustomTagsChanges { get; }
|
||||
|
||||
public DefaultPluginData()
|
||||
{
|
||||
{ 0, Role.LandHand },
|
||||
{ 1, Role.Tank },
|
||||
{ 2, Role.DPS },
|
||||
{ 3, Role.DPS },
|
||||
{ 4, Role.Healer },
|
||||
};
|
||||
RolesById = new Dictionary<byte, Role>()
|
||||
{
|
||||
{ 0, Role.LandHand },
|
||||
{ 1, Role.Tank },
|
||||
{ 2, Role.DPS },
|
||||
{ 3, Role.DPS },
|
||||
{ 4, Role.Healer },
|
||||
};
|
||||
|
||||
public Dictionary<string, Role> RolesByJobAbbreviation { get; } = new Dictionary<string, Role>();
|
||||
RolesByJobAbbreviation = new Dictionary<string, Role>();
|
||||
|
||||
public Dictionary<string, InheritableData> AllTagsChanges = new Dictionary<string, InheritableData>();
|
||||
public Dictionary<string, InheritableData> AllRoleTagsChanges = new Dictionary<string, InheritableData>();
|
||||
public Dictionary<Role, Dictionary<string, InheritableData>> RoleTagsChanges = new Dictionary<Role, Dictionary<string, InheritableData>>();
|
||||
public Dictionary<string, Dictionary<string, InheritableData>> JobTagsChanges = new Dictionary<string, Dictionary<string, InheritableData>>();
|
||||
public Dictionary<string, InheritableData> AllCustomTagsChanges = new Dictionary<string, InheritableData>();
|
||||
|
||||
public void Initialize(DataManager dataManager)
|
||||
{
|
||||
AllTagsChanges = new Tag(new LiteralPluginString(""))
|
||||
{
|
||||
IsSelected = true,
|
||||
@@ -45,6 +50,7 @@ namespace PlayerTags
|
||||
IsTextVisibleInNameplates = true,
|
||||
}.GetChanges();
|
||||
|
||||
RoleTagsChanges = new Dictionary<Role, Dictionary<string, InheritableData>>();
|
||||
RoleTagsChanges[Role.LandHand] = new Tag(new LiteralPluginString(""))
|
||||
{
|
||||
IsSelected = false,
|
||||
@@ -77,9 +83,10 @@ namespace PlayerTags
|
||||
TextColor = 508,
|
||||
}.GetChanges();
|
||||
|
||||
JobTagsChanges = new Dictionary<string, Dictionary<string, InheritableData>>();
|
||||
foreach ((var role, var roleTagChanges) in RoleTagsChanges)
|
||||
{
|
||||
var classJobs = dataManager.GetExcelSheet<ClassJob>();
|
||||
var classJobs = PluginServices.DataManager.GetExcelSheet<ClassJob>();
|
||||
if (classJobs == null)
|
||||
{
|
||||
break;
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace PlayerTags
|
||||
namespace PlayerTags.Data
|
||||
{
|
||||
public enum NameplateElement
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace PlayerTags
|
||||
namespace PlayerTags.Data
|
||||
{
|
||||
public enum NameplateFreeCompanyVisibility
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace PlayerTags
|
||||
namespace PlayerTags.Data
|
||||
{
|
||||
public enum NameplateTitlePosition
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace PlayerTags
|
||||
namespace PlayerTags.Data
|
||||
{
|
||||
public enum NameplateTitleVisibility
|
||||
{
|
||||
@@ -1,8 +1,9 @@
|
||||
using Dalamud.Data;
|
||||
using PlayerTags.Configuration;
|
||||
using PlayerTags.PluginStrings;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace PlayerTags
|
||||
namespace PlayerTags.Data
|
||||
{
|
||||
public class PluginData
|
||||
{
|
||||
@@ -14,28 +15,20 @@ namespace PlayerTags
|
||||
public Tag AllCustomTags;
|
||||
public List<Tag> CustomTags;
|
||||
|
||||
public PluginData()
|
||||
public PluginData(PluginConfiguration pluginConfiguration)
|
||||
{
|
||||
Default = new DefaultPluginData();
|
||||
AllTags = new Tag(new LocalizedPluginString(nameof(AllTags)));
|
||||
AllRoleTags = new Tag(new LocalizedPluginString(nameof(AllRoleTags)));
|
||||
RoleTags = new Dictionary<Role, Tag>();
|
||||
JobTags = new Dictionary<string, Tag>();
|
||||
AllCustomTags = new Tag(new LocalizedPluginString(nameof(AllCustomTags)));
|
||||
CustomTags = new List<Tag>();
|
||||
}
|
||||
|
||||
public void Initialize(DataManager dataManager, PluginConfiguration pluginConfiguration)
|
||||
{
|
||||
Default.Initialize(dataManager);
|
||||
|
||||
// Set the default changes and saved changes
|
||||
AllTags = new Tag(new LocalizedPluginString(nameof(AllTags)));
|
||||
AllTags.SetChanges(Default.AllTagsChanges);
|
||||
AllTags.SetChanges(pluginConfiguration.AllTagsChanges);
|
||||
|
||||
AllRoleTags = new Tag(new LocalizedPluginString(nameof(AllRoleTags)));
|
||||
AllRoleTags.SetChanges(Default.AllRoleTagsChanges);
|
||||
AllRoleTags.SetChanges(pluginConfiguration.AllRoleTagsChanges);
|
||||
|
||||
RoleTags = new Dictionary<Role, Tag>();
|
||||
foreach (var role in Enum.GetValues<Role>())
|
||||
{
|
||||
RoleTags[role] = new Tag(new LocalizedPluginString(Localizer.GetName(role)));
|
||||
@@ -67,6 +60,9 @@ namespace PlayerTags
|
||||
}
|
||||
}
|
||||
|
||||
AllCustomTags = new Tag(new LocalizedPluginString(nameof(AllCustomTags)));
|
||||
CustomTags = new List<Tag>();
|
||||
|
||||
AllCustomTags.SetChanges(Default.AllCustomTagsChanges);
|
||||
AllCustomTags.SetChanges(pluginConfiguration.AllCustomTagsChanges);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace PlayerTags
|
||||
namespace PlayerTags.Data
|
||||
{
|
||||
public enum Role
|
||||
{
|
||||
@@ -1,9 +1,11 @@
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using PlayerTags.Inheritables;
|
||||
using PlayerTags.PluginStrings;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace PlayerTags
|
||||
namespace PlayerTags.Data
|
||||
{
|
||||
public class Tag
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace PlayerTags
|
||||
namespace PlayerTags.Data
|
||||
{
|
||||
public enum TagPosition
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace PlayerTags
|
||||
namespace PlayerTags.Data
|
||||
{
|
||||
public enum TagTarget
|
||||
{
|
||||
230
PlayerTags/Features/ChatTagTargetFeature.cs
Normal file
230
PlayerTags/Features/ChatTagTargetFeature.cs
Normal file
@@ -0,0 +1,230 @@
|
||||
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.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace PlayerTags.Features
|
||||
{
|
||||
public class ChatTagTargetFeature : TagTargetFeature
|
||||
{
|
||||
private PluginConfiguration m_PluginConfiguration;
|
||||
private PluginData m_PluginData;
|
||||
|
||||
public ChatTagTargetFeature(PluginConfiguration pluginConfiguration, PluginData pluginData)
|
||||
: base(pluginConfiguration)
|
||||
{
|
||||
m_PluginConfiguration = pluginConfiguration;
|
||||
m_PluginData = pluginData;
|
||||
|
||||
PluginServices.ChatGui.ChatMessage += Chat_ChatMessage;
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
PluginServices.ChatGui.ChatMessage -= Chat_ChatMessage;
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
private void Chat_ChatMessage(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled)
|
||||
{
|
||||
AddTagsToChat(sender, out _);
|
||||
AddTagsToChat(message, out _);
|
||||
}
|
||||
|
||||
protected override BitmapFontIcon? GetIcon(Tag tag)
|
||||
{
|
||||
if (tag.IsIconVisibleInChat.InheritedValue != null && tag.IsIconVisibleInChat.InheritedValue.Value)
|
||||
{
|
||||
return tag.Icon.InheritedValue;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override string? GetText(Tag tag)
|
||||
{
|
||||
if (tag.IsTextVisibleInChat.InheritedValue != null && tag.IsTextVisibleInChat.InheritedValue.Value)
|
||||
{
|
||||
return tag.Text.InheritedValue;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A match found within a string.
|
||||
/// </summary>
|
||||
private class StringMatch
|
||||
{
|
||||
/// <summary>
|
||||
/// The string that the match was found in.
|
||||
/// </summary>
|
||||
public SeString SeString { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The matching text payload.
|
||||
/// </summary>
|
||||
public TextPayload TextPayload { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The matching game object if one exists
|
||||
/// </summary>
|
||||
public GameObject? GameObject { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// A matching player payload if one exists.
|
||||
/// </summary>
|
||||
public PlayerPayload? PlayerPayload { get; init; }
|
||||
|
||||
public StringMatch(SeString seString, TextPayload textPayload)
|
||||
{
|
||||
SeString = seString;
|
||||
TextPayload = textPayload;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the matches text.
|
||||
/// </summary>
|
||||
/// <returns>The match text.</returns>
|
||||
public string GetMatchText()
|
||||
{
|
||||
if (GameObject != null)
|
||||
{
|
||||
return GameObject.Name.TextValue;
|
||||
}
|
||||
|
||||
return TextPayload.Text;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches the given string for game object matches.
|
||||
/// </summary>
|
||||
/// <param name="seString">The string to search.</param>
|
||||
/// <returns>A list of matched game objects.</returns>
|
||||
private List<StringMatch> GetStringMatches(SeString seString)
|
||||
{
|
||||
List<StringMatch> stringMatches = new List<StringMatch>();
|
||||
|
||||
for (int payloadIndex = 0; payloadIndex < seString.Payloads.Count; ++payloadIndex)
|
||||
{
|
||||
var payload = seString.Payloads[payloadIndex];
|
||||
if (payload is PlayerPayload playerPayload)
|
||||
{
|
||||
var gameObject = PluginServices.ObjectTable.FirstOrDefault(gameObject => gameObject.Name.TextValue == playerPayload.PlayerName);
|
||||
|
||||
// The next payload MUST be a text payload
|
||||
if (payloadIndex + 1 < seString.Payloads.Count && seString.Payloads[payloadIndex + 1] is TextPayload textPayload)
|
||||
{
|
||||
var stringMatch = new StringMatch(seString, textPayload)
|
||||
{
|
||||
GameObject = gameObject,
|
||||
PlayerPayload = playerPayload
|
||||
};
|
||||
stringMatches.Add(stringMatch);
|
||||
|
||||
// Don't handle the text payload twice
|
||||
payloadIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
PluginLog.Error("Expected payload after player payload to be a text payload but it wasn't");
|
||||
}
|
||||
}
|
||||
|
||||
/// TODO: Not sure if this is desirable. Enabling this allows tags to appear next to the name of the local player by text in chat because the local player doesn't have a player payload.
|
||||
/// But because it's just a simple string comparison, it won't work in all circumstances. E.g. in party chat the player name is wrapped in (). To be comprehensive we need to search substring.
|
||||
/// This means we would need to think about breaking down existing payloads to split them out.
|
||||
/// If we decide to do that, we could even for example find unlinked player names in chat and add player payloads for them.
|
||||
// If it's just a text payload then either a character NEEDS to exist for it, or it needs to be identified as a character by custom tag configs
|
||||
//else if (payload is TextPayload textPayload)
|
||||
//{
|
||||
// var gameObject = ObjectTable.FirstOrDefault(gameObject => gameObject.Name.TextValue == textPayload.Text);
|
||||
// var isIncludedInCustomTagConfig = m_Config.CustomTags.Any(customTagConfig => customTagConfig.IncludesGameObjectName(textPayload.Text));
|
||||
|
||||
// if (gameObject != null || isIncludedInCustomTagConfig)
|
||||
// {
|
||||
// var stringMatch = new StringMatch(seString, textPayload)
|
||||
// {
|
||||
// GameObject = gameObject
|
||||
// };
|
||||
// stringMatches.Add(stringMatch);
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
return stringMatches;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds all configured tags to chat.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to change.</param>
|
||||
/// <param name="isMessageChanged">Whether the message was changed.</param>
|
||||
private void AddTagsToChat(SeString message, out bool isMessageChanged)
|
||||
{
|
||||
isMessageChanged = false;
|
||||
|
||||
var stringMatches = GetStringMatches(message);
|
||||
foreach (var stringMatch in stringMatches)
|
||||
{
|
||||
Dictionary<TagPosition, List<Payload>> stringChanges = new Dictionary<TagPosition, List<Payload>>();
|
||||
|
||||
// The role tag payloads
|
||||
if (stringMatch.GameObject is Character character)
|
||||
{
|
||||
// Add the job tag
|
||||
if (m_PluginData.JobTags.TryGetValue(character.ClassJob.GameData.Abbreviation, out var jobTag))
|
||||
{
|
||||
if (jobTag.TagPositionInChat.InheritedValue != null)
|
||||
{
|
||||
var payloads = GetPayloads(jobTag);
|
||||
if (payloads.Any())
|
||||
{
|
||||
AddPayloadChanges(jobTag.TagPositionInChat.InheritedValue.Value, payloads, stringChanges);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add randomly generated name tag payload
|
||||
if (m_PluginConfiguration.IsPlayerNameRandomlyGenerated)
|
||||
{
|
||||
var playerName = stringMatch.GetMatchText();
|
||||
if (playerName != null)
|
||||
{
|
||||
var generatedName = RandomNameGenerator.Generate(playerName);
|
||||
if (generatedName != null)
|
||||
{
|
||||
AddPayloadChanges(TagPosition.Replace, Enumerable.Empty<Payload>().Append(new TextPayload(generatedName)), stringChanges);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the custom tag payloads
|
||||
foreach (var customTag in m_PluginData.CustomTags)
|
||||
{
|
||||
if (customTag.TagPositionInChat.InheritedValue != null)
|
||||
{
|
||||
if (customTag.IncludesGameObjectNameToApplyTo(stringMatch.GetMatchText()))
|
||||
{
|
||||
var customTagPayloads = GetPayloads(customTag);
|
||||
if (customTagPayloads.Any())
|
||||
{
|
||||
AddPayloadChanges(customTag.TagPositionInChat.InheritedValue.Value, customTagPayloads, stringChanges);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ApplyStringChanges(message, stringChanges, stringMatch.TextPayload);
|
||||
isMessageChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
97
PlayerTags/Features/CustomTagsContextMenuFeature.cs
Normal file
97
PlayerTags/Features/CustomTagsContextMenuFeature.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
using PlayerTags.Configuration;
|
||||
using PlayerTags.Data;
|
||||
using PlayerTags.Resources;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using XivCommon;
|
||||
using XivCommon.Functions.ContextMenu;
|
||||
|
||||
namespace PlayerTags.Features
|
||||
{
|
||||
public class CustomTagsContextMenuFeature : IDisposable
|
||||
{
|
||||
private string?[] CustomTagsSupportedAddonNames = new string?[]
|
||||
{
|
||||
null,
|
||||
"_PartyList",
|
||||
"ChatLog",
|
||||
"ContactList",
|
||||
"ContentMemberList",
|
||||
"CrossWorldLinkshell",
|
||||
"FreeCompany",
|
||||
"FriendList",
|
||||
"LookingForGroup",
|
||||
"LinkShell",
|
||||
"PartyMemberList",
|
||||
"SocialList",
|
||||
};
|
||||
|
||||
private XivCommonBase m_XivCommon;
|
||||
private PluginConfiguration m_PluginConfiguration;
|
||||
private PluginData m_PluginData;
|
||||
|
||||
public CustomTagsContextMenuFeature(XivCommonBase xivCommon, PluginConfiguration pluginConfiguration, PluginData pluginData)
|
||||
{
|
||||
m_XivCommon = xivCommon;
|
||||
m_PluginConfiguration = pluginConfiguration;
|
||||
m_PluginData = pluginData;
|
||||
|
||||
m_XivCommon.Functions.ContextMenu.OpenContextMenu += ContextMenu_OpenContextMenu;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
m_XivCommon.Functions.ContextMenu.OpenContextMenu -= ContextMenu_OpenContextMenu;
|
||||
}
|
||||
|
||||
private void ContextMenu_OpenContextMenu(ContextMenuOpenArgs args)
|
||||
{
|
||||
if (!m_PluginConfiguration.IsCustomTagsContextMenuEnabled || !DoesContextMenuSupportCustomTags(args))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string gameObjectName = args.Text!.TextValue;
|
||||
|
||||
var notAddedTags = m_PluginData.CustomTags.Where(tag => !tag.IncludesGameObjectNameToApplyTo(gameObjectName));
|
||||
if (notAddedTags.Any())
|
||||
{
|
||||
args.Items.Add(new NormalContextSubMenuItem(Strings.Loc_Static_ContextMenu_AddTag, (itemArgs =>
|
||||
{
|
||||
foreach (var notAddedTag in notAddedTags)
|
||||
{
|
||||
itemArgs.Items.Add(new NormalContextMenuItem(notAddedTag.Text.Value, (args =>
|
||||
{
|
||||
notAddedTag.AddGameObjectNameToApplyTo(gameObjectName);
|
||||
})));
|
||||
}
|
||||
})));
|
||||
}
|
||||
|
||||
var addedTags = m_PluginData.CustomTags.Where(tag => tag.IncludesGameObjectNameToApplyTo(gameObjectName));
|
||||
if (addedTags.Any())
|
||||
{
|
||||
args.Items.Add(new NormalContextSubMenuItem(Strings.Loc_Static_ContextMenu_RemoveTag, (itemArgs =>
|
||||
{
|
||||
foreach (var addedTag in addedTags)
|
||||
{
|
||||
itemArgs.Items.Add(new NormalContextMenuItem(addedTag.Text.Value, (args =>
|
||||
{
|
||||
addedTag.RemoveGameObjectNameToApplyTo(gameObjectName);
|
||||
})));
|
||||
}
|
||||
})));
|
||||
}
|
||||
}
|
||||
|
||||
private bool DoesContextMenuSupportCustomTags(BaseContextMenuArgs args)
|
||||
{
|
||||
if (args.Text == null || args.ObjectWorld == 0 || args.ObjectWorld == 65535)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return CustomTagsSupportedAddonNames.Contains(args.ParentAddonName);
|
||||
}
|
||||
}
|
||||
}
|
||||
252
PlayerTags/Features/NameplatesTagTargetFeature.cs
Normal file
252
PlayerTags/Features/NameplatesTagTargetFeature.cs
Normal file
@@ -0,0 +1,252 @@
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
using PlayerTags.Configuration;
|
||||
using PlayerTags.Data;
|
||||
using PlayerTags.Hooks;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace PlayerTags.Features
|
||||
{
|
||||
public class NameplatesTagTargetFeature : TagTargetFeature
|
||||
{
|
||||
private PluginConfiguration m_PluginConfiguration;
|
||||
private PluginData m_PluginData;
|
||||
|
||||
private NameplateHooks? m_NameplateHooks;
|
||||
|
||||
public NameplatesTagTargetFeature(PluginConfiguration pluginConfiguration, PluginData pluginData)
|
||||
: base(pluginConfiguration)
|
||||
{
|
||||
m_PluginConfiguration = pluginConfiguration;
|
||||
m_PluginData = pluginData;
|
||||
|
||||
PluginServices.ClientState.Login += ClientState_Login;
|
||||
PluginServices.ClientState.Logout += ClientState_Logout;
|
||||
Hook();
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
Unhook();
|
||||
PluginServices.ClientState.Logout -= ClientState_Logout;
|
||||
PluginServices.ClientState.Login -= ClientState_Login;
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
private void Hook()
|
||||
{
|
||||
if (m_NameplateHooks == null)
|
||||
{
|
||||
m_NameplateHooks = new NameplateHooks(SetPlayerNameplate);
|
||||
}
|
||||
}
|
||||
|
||||
private void Unhook()
|
||||
{
|
||||
if (m_NameplateHooks != null)
|
||||
{
|
||||
m_NameplateHooks.Dispose();
|
||||
m_NameplateHooks = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void ClientState_Login(object? sender, EventArgs e)
|
||||
{
|
||||
Hook();
|
||||
}
|
||||
|
||||
private void ClientState_Logout(object? sender, EventArgs e)
|
||||
{
|
||||
Unhook();
|
||||
}
|
||||
|
||||
protected override BitmapFontIcon? GetIcon(Tag tag)
|
||||
{
|
||||
if (tag.IsIconVisibleInNameplates.InheritedValue != null && tag.IsIconVisibleInNameplates.InheritedValue.Value)
|
||||
{
|
||||
return tag.Icon.InheritedValue;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override string? GetText(Tag tag)
|
||||
{
|
||||
if (tag.IsTextVisibleInNameplates.InheritedValue != null && tag.IsTextVisibleInNameplates.InheritedValue.Value)
|
||||
{
|
||||
return tag.Text.InheritedValue;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the strings on a nameplate.
|
||||
/// </summary>
|
||||
/// <param name="playerCharacter">The player character context.</param>
|
||||
/// <param name="name">The name text.</param>
|
||||
/// <param name="title">The title text.</param>
|
||||
/// <param name="freeCompany">The free company text.</param>
|
||||
/// <param name="isTitleVisible">Whether the title is visible.</param>
|
||||
/// <param name="isTitleAboveName">Whether the title is above the name or below it.</param>
|
||||
/// <param name="iconId">The icon id.</param>
|
||||
/// <param name="isNameChanged">Whether the name was changed.</param>
|
||||
/// <param name="isTitleChanged">Whether the title was changed.</param>
|
||||
/// <param name="isFreeCompanyChanged">Whether the free company was changed.</param>
|
||||
private void SetPlayerNameplate(PlayerCharacter playerCharacter, SeString name, SeString title, SeString freeCompany, ref bool isTitleVisible, ref bool isTitleAboveName, ref int iconId, out bool isNameChanged, out bool isTitleChanged, out bool isFreeCompanyChanged)
|
||||
{
|
||||
AddTagsToNameplate(playerCharacter, name, title, freeCompany, out isNameChanged, out isTitleChanged, out isFreeCompanyChanged);
|
||||
|
||||
if (m_PluginConfiguration.NameplateTitlePosition == NameplateTitlePosition.AlwaysAboveName)
|
||||
{
|
||||
isTitleAboveName = true;
|
||||
}
|
||||
else if (m_PluginConfiguration.NameplateTitlePosition == NameplateTitlePosition.AlwaysBelowName)
|
||||
{
|
||||
isTitleAboveName = false;
|
||||
}
|
||||
|
||||
if (m_PluginConfiguration.NameplateTitleVisibility == NameplateTitleVisibility.Default)
|
||||
{
|
||||
}
|
||||
else if (m_PluginConfiguration.NameplateTitleVisibility == NameplateTitleVisibility.Always)
|
||||
{
|
||||
isTitleVisible = true;
|
||||
}
|
||||
else if (m_PluginConfiguration.NameplateTitleVisibility == NameplateTitleVisibility.Never)
|
||||
{
|
||||
isTitleVisible = false;
|
||||
}
|
||||
else if (m_PluginConfiguration.NameplateTitleVisibility == NameplateTitleVisibility.WhenHasTags)
|
||||
{
|
||||
isTitleVisible = isTitleChanged;
|
||||
}
|
||||
|
||||
if (m_PluginConfiguration.NameplateFreeCompanyVisibility == NameplateFreeCompanyVisibility.Default)
|
||||
{
|
||||
}
|
||||
else if (m_PluginConfiguration.NameplateFreeCompanyVisibility == NameplateFreeCompanyVisibility.Never)
|
||||
{
|
||||
freeCompany.Payloads.Clear();
|
||||
isFreeCompanyChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the given payload changes to the dictionary.
|
||||
/// </summary>
|
||||
/// <param name="nameplateElement">The nameplate element to add changes to.</param>
|
||||
/// <param name="tagPosition">The position to add changes to.</param>
|
||||
/// <param name="payloads">The payloads to add.</param>
|
||||
/// <param name="nameplateChanges">The dictionary to add the changes to.</param>
|
||||
private void AddPayloadChanges(NameplateElement nameplateElement, TagPosition tagPosition, IEnumerable<Payload> payloads, Dictionary<NameplateElement, Dictionary<TagPosition, List<Payload>>> nameplateChanges)
|
||||
{
|
||||
if (!payloads.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!nameplateChanges.Keys.Contains(nameplateElement))
|
||||
{
|
||||
nameplateChanges[nameplateElement] = new Dictionary<TagPosition, List<Payload>>();
|
||||
}
|
||||
|
||||
AddPayloadChanges(tagPosition, payloads, nameplateChanges[nameplateElement]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds all configured tags to the nameplate of a game object.
|
||||
/// </summary>
|
||||
/// <param name="gameObject">The game object context.</param>
|
||||
/// <param name="name">The name text to change.</param>
|
||||
/// <param name="title">The title text to change.</param>
|
||||
/// <param name="freeCompany">The free company text to change.</param>
|
||||
/// <param name="isNameChanged">Whether the name was changed.</param>
|
||||
/// <param name="isTitleChanged">Whether the title was changed.</param>
|
||||
/// <param name="isFreeCompanyChanged">Whether the free company was changed.</param>
|
||||
private void AddTagsToNameplate(GameObject gameObject, SeString name, SeString title, SeString freeCompany, out bool isNameChanged, out bool isTitleChanged, out bool isFreeCompanyChanged)
|
||||
{
|
||||
isNameChanged = false;
|
||||
isTitleChanged = false;
|
||||
isFreeCompanyChanged = false;
|
||||
|
||||
Dictionary<NameplateElement, Dictionary<TagPosition, List<Payload>>> nameplateChanges = new Dictionary<NameplateElement, Dictionary<TagPosition, List<Payload>>>();
|
||||
|
||||
if (gameObject is Character character)
|
||||
{
|
||||
// Add the job tag
|
||||
if (m_PluginData.JobTags.TryGetValue(character.ClassJob.GameData.Abbreviation, out var jobTag))
|
||||
{
|
||||
if (jobTag.TagTargetInNameplates.InheritedValue != null && jobTag.TagPositionInNameplates.InheritedValue != null)
|
||||
{
|
||||
var payloads = GetPayloads(jobTag);
|
||||
if (payloads.Any())
|
||||
{
|
||||
AddPayloadChanges(jobTag.TagTargetInNameplates.InheritedValue.Value, jobTag.TagPositionInNameplates.InheritedValue.Value, payloads, nameplateChanges);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the randomly generated name tag payload
|
||||
if (m_PluginConfiguration.IsPlayerNameRandomlyGenerated)
|
||||
{
|
||||
var characterName = character.Name.TextValue;
|
||||
if (characterName != null)
|
||||
{
|
||||
var generatedName = RandomNameGenerator.Generate(characterName);
|
||||
if (generatedName != null)
|
||||
{
|
||||
AddPayloadChanges(NameplateElement.Name, TagPosition.Replace, Enumerable.Empty<Payload>().Append(new TextPayload(generatedName)), nameplateChanges);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the custom tag payloads
|
||||
foreach (var customTag in m_PluginData.CustomTags)
|
||||
{
|
||||
if (customTag.TagTargetInNameplates.InheritedValue != null && customTag.TagPositionInNameplates.InheritedValue != null)
|
||||
{
|
||||
if (customTag.IncludesGameObjectNameToApplyTo(gameObject.Name.TextValue))
|
||||
{
|
||||
var payloads = GetPayloads(customTag);
|
||||
if (payloads.Any())
|
||||
{
|
||||
AddPayloadChanges(customTag.TagTargetInNameplates.InheritedValue.Value, customTag.TagPositionInNameplates.InheritedValue.Value, payloads, nameplateChanges);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build the final strings out of the payloads
|
||||
foreach ((var nameplateElement, var stringChanges) in nameplateChanges)
|
||||
{
|
||||
SeString? seString = null;
|
||||
if (nameplateElement == NameplateElement.Name)
|
||||
{
|
||||
seString = name;
|
||||
isNameChanged = true;
|
||||
}
|
||||
else if (nameplateElement == NameplateElement.Title)
|
||||
{
|
||||
seString = title;
|
||||
isTitleChanged = true;
|
||||
}
|
||||
else if (nameplateElement == NameplateElement.FreeCompany)
|
||||
{
|
||||
seString = freeCompany;
|
||||
isFreeCompanyChanged = true;
|
||||
}
|
||||
|
||||
if (seString != null)
|
||||
{
|
||||
ApplyStringChanges(seString, stringChanges);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
272
PlayerTags/Features/TagTargetFeature.cs
Normal file
272
PlayerTags/Features/TagTargetFeature.cs
Normal file
@@ -0,0 +1,272 @@
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
using PlayerTags.Configuration;
|
||||
using PlayerTags.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace PlayerTags.Features
|
||||
{
|
||||
public abstract class TagTargetFeature : IDisposable
|
||||
{
|
||||
private PluginConfiguration m_PluginConfiguration;
|
||||
|
||||
private Dictionary<Tag, Payload[]> m_TagPayloads;
|
||||
private TextPayload m_SpaceTextPayload;
|
||||
|
||||
public TagTargetFeature(PluginConfiguration pluginConfiguration)
|
||||
{
|
||||
m_PluginConfiguration = pluginConfiguration;
|
||||
|
||||
m_TagPayloads = new Dictionary<Tag, Payload[]>();
|
||||
m_SpaceTextPayload = new TextPayload($" ");
|
||||
|
||||
m_PluginConfiguration.Saved += PluginConfiguration_Saved;
|
||||
}
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
m_PluginConfiguration.Saved -= PluginConfiguration_Saved;
|
||||
}
|
||||
|
||||
protected abstract BitmapFontIcon? GetIcon(Tag tag);
|
||||
|
||||
protected abstract string? GetText(Tag tag);
|
||||
|
||||
private void PluginConfiguration_Saved()
|
||||
{
|
||||
// Invalidate the cached payloads so they get remade
|
||||
m_TagPayloads.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the payloads for the given tag. If the payloads don't yet exist then they will be created.
|
||||
/// </summary>
|
||||
/// <param name="tag">The tag config to get payloads for.</param>
|
||||
/// <returns>A list of payloads for the given tag.</returns>
|
||||
protected IEnumerable<Payload> GetPayloads(Tag tag)
|
||||
{
|
||||
if (m_TagPayloads.TryGetValue(tag, out var payloads))
|
||||
{
|
||||
return payloads;
|
||||
}
|
||||
|
||||
m_TagPayloads[tag] = CreatePayloads(tag);
|
||||
return m_TagPayloads[tag];
|
||||
}
|
||||
|
||||
private Payload[] CreatePayloads(Tag tag)
|
||||
{
|
||||
List<Payload> newPayloads = new List<Payload>();
|
||||
|
||||
BitmapFontIcon? icon = GetIcon(tag);
|
||||
if (icon != null && icon.Value != BitmapFontIcon.None)
|
||||
{
|
||||
newPayloads.Add(new IconPayload(icon.Value));
|
||||
}
|
||||
|
||||
string? text = GetText(tag);
|
||||
if (!string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
if (tag.IsTextItalic.InheritedValue != null && tag.IsTextItalic.InheritedValue.Value)
|
||||
{
|
||||
newPayloads.Add(new EmphasisItalicPayload(true));
|
||||
}
|
||||
|
||||
if (tag.TextGlowColor.InheritedValue != null)
|
||||
{
|
||||
newPayloads.Add(new UIGlowPayload(tag.TextGlowColor.InheritedValue.Value));
|
||||
}
|
||||
|
||||
if (tag.TextColor.InheritedValue != null)
|
||||
{
|
||||
newPayloads.Add(new UIForegroundPayload(tag.TextColor.InheritedValue.Value));
|
||||
}
|
||||
|
||||
newPayloads.Add(new TextPayload(text));
|
||||
|
||||
if (tag.TextColor.InheritedValue != null)
|
||||
{
|
||||
newPayloads.Add(new UIForegroundPayload(0));
|
||||
}
|
||||
|
||||
if (tag.TextGlowColor.InheritedValue != null)
|
||||
{
|
||||
newPayloads.Add(new UIGlowPayload(0));
|
||||
}
|
||||
|
||||
if (tag.IsTextItalic.InheritedValue != null && tag.IsTextItalic.InheritedValue.Value)
|
||||
{
|
||||
newPayloads.Add(new EmphasisItalicPayload(false));
|
||||
}
|
||||
}
|
||||
|
||||
return newPayloads.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an additional space text payload in between any existing text payloads. If there is an icon payload between two text payloads then the space is skipped.
|
||||
/// Also adds an extra space to the beginning or end depending on the tag position and whether the most significant payload in either direction is a text payload.
|
||||
/// In spirit, this is to ensure there is always a space between 2 text payloads, including between these payloads and the target payload.
|
||||
/// </summary>
|
||||
/// <param name="payloads">The payloads to add spaces between.</param>
|
||||
private void AddSpacesBetweenTextPayloads(List<Payload> payloads, TagPosition tagPosition)
|
||||
{
|
||||
if (payloads == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!payloads.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<int> indicesToInsertSpacesAt = new List<int>();
|
||||
int lastTextPayloadIndex = -1;
|
||||
foreach (var payload in payloads.Reverse<Payload>())
|
||||
{
|
||||
if (payload is IconPayload iconPayload)
|
||||
{
|
||||
lastTextPayloadIndex = -1;
|
||||
}
|
||||
else if (payload is TextPayload textPayload)
|
||||
{
|
||||
if (lastTextPayloadIndex != -1)
|
||||
{
|
||||
indicesToInsertSpacesAt.Add(payloads.IndexOf(textPayload) + 1);
|
||||
}
|
||||
|
||||
lastTextPayloadIndex = payloads.IndexOf(textPayload);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var indexToInsertSpaceAt in indicesToInsertSpacesAt)
|
||||
{
|
||||
payloads.Insert(indexToInsertSpaceAt, m_SpaceTextPayload);
|
||||
}
|
||||
|
||||
// Decide whether to add a space to the end
|
||||
if (tagPosition == TagPosition.Before)
|
||||
{
|
||||
var significantPayloads = payloads.Where(payload => payload is TextPayload || payload is IconPayload);
|
||||
if (significantPayloads.Last() is TextPayload)
|
||||
{
|
||||
payloads.Add(m_SpaceTextPayload);
|
||||
}
|
||||
}
|
||||
// Decide whether to add a space to the beginning
|
||||
else if (tagPosition == TagPosition.After)
|
||||
{
|
||||
var significantPayloads = payloads.Where(payload => payload is TextPayload || payload is IconPayload);
|
||||
if (significantPayloads.First() is TextPayload)
|
||||
{
|
||||
payloads.Insert(0, m_SpaceTextPayload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the given payload changes to the dictionary.
|
||||
/// </summary>
|
||||
/// <param name="tagPosition">The position to add changes to.</param>
|
||||
/// <param name="payloads">The payloads to add.</param>
|
||||
/// <param name="stringChanges">The dictionary to add the changes to.</param>
|
||||
protected void AddPayloadChanges(TagPosition tagPosition, IEnumerable<Payload> payloads, Dictionary<TagPosition, List<Payload>> stringChanges)
|
||||
{
|
||||
if (payloads == null || !payloads.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (stringChanges == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!stringChanges.Keys.Contains(tagPosition))
|
||||
{
|
||||
stringChanges[tagPosition] = new List<Payload>();
|
||||
}
|
||||
|
||||
stringChanges[tagPosition].AddRange(payloads);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies changes to the given string.
|
||||
/// </summary>
|
||||
/// <param name="seString">The string to apply changes to.</param>
|
||||
/// <param name="stringChanges">The changes to apply.</param>
|
||||
/// <param name="anchorPayload">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.</param>
|
||||
protected void ApplyStringChanges(SeString seString, Dictionary<TagPosition, List<Payload>> stringChanges, Payload? anchorPayload = null)
|
||||
{
|
||||
if (stringChanges.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<TagPosition> tagPositionsOrdered = new List<TagPosition>();
|
||||
// If there's no anchor payload, do replaces first so that befores and afters are based on the replaced data
|
||||
if (anchorPayload == null)
|
||||
{
|
||||
tagPositionsOrdered.Add(TagPosition.Replace);
|
||||
}
|
||||
|
||||
tagPositionsOrdered.Add(TagPosition.Before);
|
||||
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)
|
||||
{
|
||||
tagPositionsOrdered.Add(TagPosition.Replace);
|
||||
}
|
||||
|
||||
foreach (var tagPosition in tagPositionsOrdered)
|
||||
{
|
||||
if (stringChanges.TryGetValue(tagPosition, out var payloads) && payloads.Any())
|
||||
{
|
||||
AddSpacesBetweenTextPayloads(stringChanges[tagPosition], tagPosition);
|
||||
if (tagPosition == TagPosition.Before)
|
||||
{
|
||||
if (anchorPayload != null)
|
||||
{
|
||||
var anchorPayloadIndex = seString.Payloads.IndexOf(anchorPayload);
|
||||
seString.Payloads.InsertRange(anchorPayloadIndex, payloads);
|
||||
}
|
||||
else
|
||||
{
|
||||
seString.Payloads.InsertRange(0, payloads);
|
||||
}
|
||||
}
|
||||
else if (tagPosition == TagPosition.After)
|
||||
{
|
||||
if (anchorPayload != null)
|
||||
{
|
||||
var anchorPayloadIndex = seString.Payloads.IndexOf(anchorPayload);
|
||||
seString.Payloads.InsertRange(anchorPayloadIndex + 1, payloads);
|
||||
}
|
||||
else
|
||||
{
|
||||
seString.Payloads.AddRange(payloads);
|
||||
}
|
||||
}
|
||||
else if (tagPosition == TagPosition.Replace)
|
||||
{
|
||||
if (anchorPayload != null)
|
||||
{
|
||||
var anchorPayloadIndex = seString.Payloads.IndexOf(anchorPayload);
|
||||
seString.Payloads.InsertRange(anchorPayloadIndex, payloads);
|
||||
seString.Payloads.Remove(anchorPayload);
|
||||
}
|
||||
else
|
||||
{
|
||||
seString.Payloads.Clear();
|
||||
seString.Payloads.AddRange(payloads);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,52 +1,42 @@
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Game.Gui;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Logging;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace PlayerTags
|
||||
namespace PlayerTags.Hooks
|
||||
{
|
||||
public class PluginHooks : IDisposable
|
||||
public class NameplateHooks : IDisposable
|
||||
{
|
||||
private class PluginAddressResolver : BaseAddressResolver
|
||||
{
|
||||
private const string SetNameplateSignature = "48 89 5C 24 ?? 48 89 6C 24 ?? 56 57 41 54 41 56 41 57 48 83 EC 40 44 0F B6 E2";
|
||||
internal IntPtr SetNameplatePtr;
|
||||
private const string SetPlayerNameplateSignature = "48 89 5C 24 ?? 48 89 6C 24 ?? 56 57 41 54 41 56 41 57 48 83 EC 40 44 0F B6 E2";
|
||||
public IntPtr SetPlayerNameplatePtr { get; private set; }
|
||||
|
||||
protected override void Setup64Bit(SigScanner scanner)
|
||||
{
|
||||
SetNameplatePtr = scanner.ScanText(SetNameplateSignature);
|
||||
SetPlayerNameplatePtr = scanner.ScanText(SetPlayerNameplateSignature);
|
||||
}
|
||||
}
|
||||
|
||||
private delegate IntPtr SetPlayerNameplateDelegate_Unmanaged(IntPtr playerNameplateObjectPtr, bool isTitleAboveName, bool isTitleVisible, IntPtr titlePtr, IntPtr namePtr, IntPtr freeCompanyPtr, int iconId);
|
||||
|
||||
private Framework m_Framework;
|
||||
private ObjectTable m_ObjectTable;
|
||||
private GameGui m_GameGui;
|
||||
private SetPlayerNameplateDelegate m_SetPlayerNameplate;
|
||||
|
||||
private PluginAddressResolver m_PluginAddressResolver;
|
||||
private Hook<SetPlayerNameplateDelegate_Unmanaged> m_SetPlayerNameplateHook;
|
||||
|
||||
public PluginHooks(Framework framework, ObjectTable objectTable, GameGui gameGui, SetPlayerNameplateDelegate setPlayerNameplate)
|
||||
public NameplateHooks(SetPlayerNameplateDelegate setPlayerNameplate)
|
||||
{
|
||||
m_Framework = framework;
|
||||
m_ObjectTable = objectTable;
|
||||
m_GameGui = gameGui;
|
||||
m_SetPlayerNameplate = setPlayerNameplate;
|
||||
|
||||
m_PluginAddressResolver = new PluginAddressResolver();
|
||||
m_PluginAddressResolver.Setup();
|
||||
|
||||
m_SetPlayerNameplateHook = new Hook<SetPlayerNameplateDelegate_Unmanaged>(m_PluginAddressResolver.SetNameplatePtr, new SetPlayerNameplateDelegate_Unmanaged(SetPlayerNameplateDetour));
|
||||
m_SetPlayerNameplateHook = new Hook<SetPlayerNameplateDelegate_Unmanaged>(m_PluginAddressResolver.SetPlayerNameplatePtr, new SetPlayerNameplateDelegate_Unmanaged(SetPlayerNameplateDetour));
|
||||
m_SetPlayerNameplateHook.Enable();
|
||||
}
|
||||
|
||||
@@ -164,7 +154,7 @@ namespace PlayerTags
|
||||
where T : GameObject
|
||||
{
|
||||
// Get the nameplate object array
|
||||
var nameplateAddonPtr = m_GameGui.GetAddonByName("NamePlate", 1);
|
||||
var nameplateAddonPtr = PluginServices.GameGui.GetAddonByName("NamePlate", 1);
|
||||
var nameplateObjectArrayPtrPtr = nameplateAddonPtr + Marshal.OffsetOf(typeof(AddonNamePlate), nameof(AddonNamePlate.NamePlateObjectArray)).ToInt32();
|
||||
var nameplateObjectArrayPtr = Marshal.ReadIntPtr(nameplateObjectArrayPtrPtr);
|
||||
if (nameplateObjectArrayPtr == IntPtr.Zero)
|
||||
@@ -195,7 +185,7 @@ namespace PlayerTags
|
||||
|
||||
// Return the object for its object id
|
||||
var objectId = namePlateInfo.ObjectID.ObjectID;
|
||||
return m_ObjectTable.SearchById(objectId) as T;
|
||||
return PluginServices.ObjectTable.SearchById(objectId) as T;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
|
||||
namespace PlayerTags
|
||||
namespace PlayerTags.Hooks
|
||||
{
|
||||
public delegate void SetPlayerNameplateDelegate(PlayerCharacter playerCharacter, SeString name, SeString title, SeString freeCompany, ref bool isTitleVisible, ref bool isTitleAboveName, ref int iconId, out bool isNameChanged, out bool isTitleChanged, out bool isFreeCompanyChanged);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace PlayerTags
|
||||
namespace PlayerTags.Inheritables
|
||||
{
|
||||
public interface IInheritable
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace PlayerTags
|
||||
namespace PlayerTags.Inheritables
|
||||
{
|
||||
public enum InheritableBehavior
|
||||
{
|
||||
@@ -2,7 +2,7 @@
|
||||
using Newtonsoft.Json.Converters;
|
||||
using System;
|
||||
|
||||
namespace PlayerTags
|
||||
namespace PlayerTags.Inheritables
|
||||
{
|
||||
[Serializable]
|
||||
public struct InheritableData
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace PlayerTags
|
||||
namespace PlayerTags.Inheritables
|
||||
{
|
||||
public class InheritableReference<T> : IInheritable
|
||||
where T : class
|
||||
@@ -1,7 +1,7 @@
|
||||
using Dalamud.Logging;
|
||||
using System;
|
||||
|
||||
namespace PlayerTags
|
||||
namespace PlayerTags.Inheritables
|
||||
{
|
||||
public class InheritableValue<T> : IInheritable
|
||||
where T : struct
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"Author": "r00telement",
|
||||
"Name": "Player Tags",
|
||||
"Description": "See player jobs and roles in nameplates and chat. Create custom tags and add them to players by right clicking them.",
|
||||
"Description": "Lightweight job visibility in nameplates and chat. Create custom tags and add them to players with the context menu.",
|
||||
"Tags": [ "Jobs", "UI" ],
|
||||
"CategoryTags": [ "jobs", "UI" ],
|
||||
"RepoUrl": "https://github.com/r00telement/PlayerTags",
|
||||
|
||||
@@ -1,24 +1,9 @@
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Game.ClientState;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Game.ClientState.Party;
|
||||
using Dalamud.Game.Command;
|
||||
using Dalamud.Game.Gui;
|
||||
using Dalamud.Game.Text;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Game.Command;
|
||||
using Dalamud.Plugin;
|
||||
using PlayerTags.Resources;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using PlayerTags.Configuration;
|
||||
using PlayerTags.Data;
|
||||
using PlayerTags.Features;
|
||||
using XivCommon;
|
||||
using XivCommon.Functions.ContextMenu;
|
||||
|
||||
namespace PlayerTags
|
||||
{
|
||||
@@ -27,182 +12,56 @@ namespace PlayerTags
|
||||
public string Name => "Player Tags";
|
||||
private const string c_CommandName = "/playertags";
|
||||
|
||||
[PluginService]
|
||||
private static DalamudPluginInterface PluginInterface { get; set; } = null!;
|
||||
|
||||
[PluginService]
|
||||
private static Framework Framework { get; set; } = null!;
|
||||
|
||||
[PluginService]
|
||||
private static ChatGui ChatGui { get; set; } = null!;
|
||||
|
||||
[PluginService]
|
||||
private static GameGui GameGui { get; set; } = null!;
|
||||
|
||||
[PluginService]
|
||||
private static ObjectTable ObjectTable { get; set; } = null!;
|
||||
|
||||
[PluginService]
|
||||
private static DataManager DataManager { get; set; } = null!;
|
||||
|
||||
[PluginService]
|
||||
private static CommandManager CommandManager { get; set; } = null!;
|
||||
|
||||
[PluginService]
|
||||
private static ClientState ClientState { get; set; } = null!;
|
||||
|
||||
[PluginService]
|
||||
private static PartyList PartyList { get; set; } = null!;
|
||||
private XivCommonBase m_XivCommon;
|
||||
|
||||
private PluginConfiguration m_PluginConfiguration;
|
||||
private PluginData m_PluginData;
|
||||
private PluginConfigurationUI m_PluginConfigurationUI;
|
||||
private RandomNameGenerator m_RandomNameGenerator = new RandomNameGenerator();
|
||||
private PluginHooks? m_PluginHooks = null;
|
||||
private Dictionary<Tag, Dictionary<TagTarget, Payload[]>> m_TagTargetPayloads = new Dictionary<Tag, Dictionary<TagTarget, Payload[]>>();
|
||||
private TextPayload m_SpaceTextPayload = new TextPayload($" ");
|
||||
private PluginData m_PluginData = new PluginData();
|
||||
private XivCommonBase XivCommon;
|
||||
|
||||
public Plugin()
|
||||
private CustomTagsContextMenuFeature m_CustomTagsContextMenuFeature;
|
||||
private NameplatesTagTargetFeature m_NameplatesTagTargetFeature;
|
||||
private ChatTagTargetFeature m_ChatTagTargetFeature;
|
||||
|
||||
public Plugin(DalamudPluginInterface pluginInterface)
|
||||
{
|
||||
UIColorHelper.Initialize(DataManager);
|
||||
m_PluginConfiguration = PluginInterface.GetPluginConfig() as PluginConfiguration ?? new PluginConfiguration();
|
||||
m_PluginConfiguration.Initialize(PluginInterface);
|
||||
m_PluginData.Initialize(DataManager, m_PluginConfiguration);
|
||||
m_PluginConfigurationUI = new PluginConfigurationUI(m_PluginConfiguration, m_PluginData, ClientState, PartyList, ObjectTable);
|
||||
PluginServices.Initialize(pluginInterface);
|
||||
|
||||
ClientState.Login += ClientState_Login;
|
||||
ClientState.Logout += ClientState_Logout;
|
||||
ChatGui.ChatMessage += Chat_ChatMessage;
|
||||
PluginInterface.UiBuilder.Draw += UiBuilder_Draw;
|
||||
PluginInterface.UiBuilder.OpenConfigUi += UiBuilder_OpenConfigUi;
|
||||
m_PluginConfiguration.Saved += PluginConfiguration_Saved;
|
||||
CommandManager.AddHandler(c_CommandName, new CommandInfo((string command, string arguments) =>
|
||||
m_PluginConfiguration = PluginServices.DalamudPluginInterface.GetPluginConfig() as PluginConfiguration ?? new PluginConfiguration();
|
||||
m_PluginData = new PluginData(m_PluginConfiguration);
|
||||
m_PluginConfigurationUI = new PluginConfigurationUI(m_PluginConfiguration, m_PluginData);
|
||||
|
||||
m_XivCommon = new XivCommonBase(XivCommon.Hooks.ContextMenu);
|
||||
PluginServices.ClientState.TerritoryChanged += ClientState_TerritoryChanged;
|
||||
PluginServices.DalamudPluginInterface.UiBuilder.Draw += UiBuilder_Draw;
|
||||
PluginServices.DalamudPluginInterface.UiBuilder.OpenConfigUi += UiBuilder_OpenConfigUi;
|
||||
PluginServices.CommandManager.AddHandler(c_CommandName, new CommandInfo((string command, string arguments) =>
|
||||
{
|
||||
m_PluginConfiguration.IsVisible = true;
|
||||
m_PluginConfiguration.Save(m_PluginData);
|
||||
})
|
||||
{
|
||||
HelpMessage = "Shows the config"
|
||||
});
|
||||
Hook();
|
||||
XivCommon = new XivCommonBase(Hooks.ContextMenu);
|
||||
XivCommon.Functions.ContextMenu.OpenContextMenu += ContextMenu_OpenContextMenu;
|
||||
}) { HelpMessage = "Shows the config" });
|
||||
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);
|
||||
}
|
||||
|
||||
//private ExcelSheet<ContentFinderCondition> _contentFinderConditionsSheet;
|
||||
private void ClientState_TerritoryChanged(object? sender, ushort e)
|
||||
{
|
||||
//_contentFinderConditionsSheet = DataManager.GameData.GetExcelSheet<ContentFinderCondition>();
|
||||
//var content = _contentFinderConditionsSheet.FirstOrDefault(t => t.TerritoryType.Row == PluginServices.ClientState.TerritoryType);
|
||||
//content.ContentMemberType.Row
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
XivCommon.Functions.ContextMenu.OpenContextMenu -= ContextMenu_OpenContextMenu;
|
||||
XivCommon.Dispose();
|
||||
Unhook();
|
||||
CommandManager.RemoveHandler(c_CommandName);
|
||||
m_PluginConfiguration.Saved -= PluginConfiguration_Saved;
|
||||
PluginInterface.UiBuilder.OpenConfigUi -= UiBuilder_OpenConfigUi;
|
||||
PluginInterface.UiBuilder.Draw -= UiBuilder_Draw;
|
||||
ChatGui.ChatMessage -= Chat_ChatMessage;
|
||||
ClientState.Logout -= ClientState_Logout;
|
||||
ClientState.Login -= ClientState_Login;
|
||||
}
|
||||
|
||||
private void Hook()
|
||||
{
|
||||
if (m_PluginHooks == null)
|
||||
{
|
||||
m_PluginHooks = new PluginHooks(Framework, ObjectTable, GameGui, SetPlayerNameplate);
|
||||
}
|
||||
}
|
||||
|
||||
private void Unhook()
|
||||
{
|
||||
if (m_PluginHooks != null)
|
||||
{
|
||||
m_PluginHooks.Dispose();
|
||||
m_PluginHooks = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void ClientState_Login(object? sender, EventArgs e)
|
||||
{
|
||||
Hook();
|
||||
}
|
||||
|
||||
private void ClientState_Logout(object? sender, EventArgs e)
|
||||
{
|
||||
Unhook();
|
||||
}
|
||||
|
||||
private void PluginConfiguration_Saved()
|
||||
{
|
||||
// Invalidate the cached payloads so they get remade
|
||||
m_TagTargetPayloads.Clear();
|
||||
}
|
||||
|
||||
private void ContextMenu_OpenContextMenu(ContextMenuOpenArgs args)
|
||||
{
|
||||
if (!m_PluginConfiguration.IsCustomTagContextMenuEnabled || !CanContextMenuSupportTagOptions(args))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string gameObjectName = args.Text!.TextValue;
|
||||
|
||||
var notAddedTags = m_PluginData.CustomTags.Where(tag => !tag.IncludesGameObjectNameToApplyTo(gameObjectName));
|
||||
if (notAddedTags.Any())
|
||||
{
|
||||
args.Items.Add(new NormalContextSubMenuItem(Strings.Loc_Static_ContextMenu_AddTag, (itemArgs =>
|
||||
{
|
||||
foreach (var notAddedTag in notAddedTags)
|
||||
{
|
||||
itemArgs.Items.Add(new NormalContextMenuItem(notAddedTag.Text.Value, (args =>
|
||||
{
|
||||
notAddedTag.AddGameObjectNameToApplyTo(gameObjectName);
|
||||
})));
|
||||
}
|
||||
})));
|
||||
}
|
||||
|
||||
var addedTags = m_PluginData.CustomTags.Where(tag => tag.IncludesGameObjectNameToApplyTo(gameObjectName));
|
||||
if (addedTags.Any())
|
||||
{
|
||||
args.Items.Add(new NormalContextSubMenuItem(Strings.Loc_Static_ContextMenu_RemoveTag, (itemArgs =>
|
||||
{
|
||||
foreach (var addedTag in addedTags)
|
||||
{
|
||||
itemArgs.Items.Add(new NormalContextMenuItem(addedTag.Text.Value, (args =>
|
||||
{
|
||||
addedTag.RemoveGameObjectNameToApplyTo(gameObjectName);
|
||||
})));
|
||||
}
|
||||
})));
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanContextMenuSupportTagOptions(BaseContextMenuArgs args)
|
||||
{
|
||||
if (args.Text == null || args.ObjectWorld == 0 || args.ObjectWorld == 65535)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (args.ParentAddonName)
|
||||
{
|
||||
case null:
|
||||
case "_PartyList":
|
||||
case "ChatLog":
|
||||
case "ContactList":
|
||||
case "ContentMemberList":
|
||||
case "CrossWorldLinkshell":
|
||||
case "FreeCompany":
|
||||
case "FriendList":
|
||||
case "LookingForGroup":
|
||||
case "LinkShell":
|
||||
case "PartyMemberList":
|
||||
case "SocialList":
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
m_ChatTagTargetFeature.Dispose();
|
||||
m_NameplatesTagTargetFeature.Dispose();
|
||||
m_CustomTagsContextMenuFeature.Dispose();
|
||||
PluginServices.CommandManager.RemoveHandler(c_CommandName);
|
||||
PluginServices.DalamudPluginInterface.UiBuilder.OpenConfigUi -= UiBuilder_OpenConfigUi;
|
||||
PluginServices.DalamudPluginInterface.UiBuilder.Draw -= UiBuilder_Draw;
|
||||
PluginServices.ClientState.TerritoryChanged -= ClientState_TerritoryChanged;
|
||||
m_XivCommon.Dispose();
|
||||
}
|
||||
|
||||
private void UiBuilder_Draw()
|
||||
@@ -218,606 +77,5 @@ namespace PlayerTags
|
||||
m_PluginConfiguration.IsVisible = true;
|
||||
m_PluginConfiguration.Save(m_PluginData);
|
||||
}
|
||||
|
||||
private void Chat_ChatMessage(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled)
|
||||
{
|
||||
AddTagsToChat(sender, out _);
|
||||
AddTagsToChat(message, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the strings on a nameplate.
|
||||
/// </summary>
|
||||
/// <param name="gameObject">The game object context.</param>
|
||||
/// <param name="name">The name text.</param>
|
||||
/// <param name="title">The title text.</param>
|
||||
/// <param name="freeCompany">The free company text.</param>
|
||||
/// <param name="isTitleVisible">Whether the title is visible.</param>
|
||||
/// <param name="isTitleAboveName">Whether the title is above the name or below it.</param>
|
||||
/// <param name="iconId">The icon id.</param>
|
||||
/// <param name="isNameChanged">Whether the name was changed.</param>
|
||||
/// <param name="isTitleChanged">Whether the title was changed.</param>
|
||||
/// <param name="isFreeCompanyChanged">Whether the free company was changed.</param>
|
||||
private void SetPlayerNameplate(PlayerCharacter playerCharacter, SeString name, SeString title, SeString freeCompany, ref bool isTitleVisible, ref bool isTitleAboveName, ref int iconId, out bool isNameChanged, out bool isTitleChanged, out bool isFreeCompanyChanged)
|
||||
{
|
||||
AddTagsToNameplate(playerCharacter, name, title, freeCompany, out isNameChanged, out isTitleChanged, out isFreeCompanyChanged);
|
||||
|
||||
if (m_PluginConfiguration.NameplateTitlePosition == NameplateTitlePosition.AlwaysAboveName)
|
||||
{
|
||||
isTitleAboveName = true;
|
||||
}
|
||||
else if (m_PluginConfiguration.NameplateTitlePosition == NameplateTitlePosition.AlwaysBelowName)
|
||||
{
|
||||
isTitleAboveName = false;
|
||||
}
|
||||
|
||||
if (m_PluginConfiguration.NameplateTitleVisibility == NameplateTitleVisibility.Default)
|
||||
{
|
||||
}
|
||||
else if (m_PluginConfiguration.NameplateTitleVisibility == NameplateTitleVisibility.Always)
|
||||
{
|
||||
isTitleVisible = true;
|
||||
}
|
||||
else if (m_PluginConfiguration.NameplateTitleVisibility == NameplateTitleVisibility.Never)
|
||||
{
|
||||
isTitleVisible = false;
|
||||
}
|
||||
else if (m_PluginConfiguration.NameplateTitleVisibility == NameplateTitleVisibility.WhenHasTags)
|
||||
{
|
||||
isTitleVisible = isTitleChanged;
|
||||
}
|
||||
|
||||
if (m_PluginConfiguration.NameplateFreeCompanyVisibility == NameplateFreeCompanyVisibility.Default)
|
||||
{
|
||||
}
|
||||
else if (m_PluginConfiguration.NameplateFreeCompanyVisibility == NameplateFreeCompanyVisibility.Never)
|
||||
{
|
||||
freeCompany.Payloads.Clear();
|
||||
isFreeCompanyChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
private Payload[] CreateTagPayloads(TagTarget tagTarget, Tag tag)
|
||||
{
|
||||
List<Payload> newPayloads = new List<Payload>();
|
||||
|
||||
BitmapFontIcon? icon = null;
|
||||
if (tagTarget == TagTarget.Chat && tag.IsIconVisibleInChat.InheritedValue != null && tag.IsIconVisibleInChat.InheritedValue.Value)
|
||||
{
|
||||
icon = tag.Icon.InheritedValue;
|
||||
}
|
||||
else if (tagTarget == TagTarget.Nameplate && tag.IsIconVisibleInNameplates.InheritedValue != null && tag.IsIconVisibleInNameplates.InheritedValue.Value)
|
||||
{
|
||||
icon = tag.Icon.InheritedValue;
|
||||
}
|
||||
|
||||
string? text = null;
|
||||
if (tagTarget == TagTarget.Chat && tag.IsTextVisibleInChat.InheritedValue != null && tag.IsTextVisibleInChat.InheritedValue.Value)
|
||||
{
|
||||
text = tag.Text.InheritedValue;
|
||||
}
|
||||
else if (tagTarget == TagTarget.Nameplate && tag.IsTextVisibleInNameplates.InheritedValue != null && tag.IsTextVisibleInNameplates.InheritedValue.Value)
|
||||
{
|
||||
text = tag.Text.InheritedValue;
|
||||
}
|
||||
|
||||
if (!m_TagTargetPayloads.ContainsKey(tag))
|
||||
{
|
||||
m_TagTargetPayloads[tag] = new Dictionary<TagTarget, Payload[]>();
|
||||
}
|
||||
|
||||
if (icon != null && icon.Value != BitmapFontIcon.None)
|
||||
{
|
||||
newPayloads.Add(new IconPayload(icon.Value));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
if (tag.IsTextItalic.InheritedValue != null && tag.IsTextItalic.InheritedValue.Value)
|
||||
{
|
||||
newPayloads.Add(new EmphasisItalicPayload(true));
|
||||
}
|
||||
|
||||
if (tag.TextGlowColor.InheritedValue != null)
|
||||
{
|
||||
newPayloads.Add(new UIGlowPayload(tag.TextGlowColor.InheritedValue.Value));
|
||||
}
|
||||
|
||||
if (tag.TextColor.InheritedValue != null)
|
||||
{
|
||||
newPayloads.Add(new UIForegroundPayload(tag.TextColor.InheritedValue.Value));
|
||||
}
|
||||
|
||||
newPayloads.Add(new TextPayload(text));
|
||||
|
||||
if (tag.TextColor.InheritedValue != null)
|
||||
{
|
||||
newPayloads.Add(new UIForegroundPayload(0));
|
||||
}
|
||||
|
||||
if (tag.TextGlowColor.InheritedValue != null)
|
||||
{
|
||||
newPayloads.Add(new UIGlowPayload(0));
|
||||
}
|
||||
|
||||
if (tag.IsTextItalic.InheritedValue != null && tag.IsTextItalic.InheritedValue.Value)
|
||||
{
|
||||
newPayloads.Add(new EmphasisItalicPayload(false));
|
||||
}
|
||||
}
|
||||
|
||||
return newPayloads.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the payloads for the given custom tag. If the payloads don't yet exist then they are created.
|
||||
/// </summary>
|
||||
/// <param name="customTagConfig">The custom tag config to get payloads for.</param>
|
||||
/// <returns>A list of payloads for the given custom tag.</returns>
|
||||
private IEnumerable<Payload> GetTagPayloads(TagTarget tagTarget, Tag tag)
|
||||
{
|
||||
if (m_TagTargetPayloads.TryGetValue(tag, out var tagTargetPayloads))
|
||||
{
|
||||
if (tagTargetPayloads.TryGetValue(tagTarget, out var payloads))
|
||||
{
|
||||
return payloads;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_TagTargetPayloads[tag] = new Dictionary<TagTarget, Payload[]>();
|
||||
}
|
||||
|
||||
m_TagTargetPayloads[tag][tagTarget] = CreateTagPayloads(tagTarget, tag);
|
||||
return m_TagTargetPayloads[tag][tagTarget];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an additional space text payload in between any existing text payloads. If there is an icon payload between two text payloads then the space is skipped.
|
||||
/// Also adds an extra space to the beginning or end depending on the tag position and whether the most significant payload in either direction is a text payload.
|
||||
/// In spirit, this is to ensure there is always a space between 2 text payloads, including between these payloads and the target payload.
|
||||
/// </summary>
|
||||
/// <param name="payloads">The payloads to add spaces between.</param>
|
||||
private void AddSpacesBetweenTextPayloads(List<Payload> payloads, TagPosition tagPosition)
|
||||
{
|
||||
if (payloads == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!payloads.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<int> indicesToInsertSpacesAt = new List<int>();
|
||||
int lastTextPayloadIndex = -1;
|
||||
foreach (var payload in payloads.Reverse<Payload>())
|
||||
{
|
||||
if (payload is IconPayload iconPayload)
|
||||
{
|
||||
lastTextPayloadIndex = -1;
|
||||
}
|
||||
else if (payload is TextPayload textPayload)
|
||||
{
|
||||
if (lastTextPayloadIndex != -1)
|
||||
{
|
||||
indicesToInsertSpacesAt.Add(payloads.IndexOf(textPayload) + 1);
|
||||
}
|
||||
|
||||
lastTextPayloadIndex = payloads.IndexOf(textPayload);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var indexToInsertSpaceAt in indicesToInsertSpacesAt)
|
||||
{
|
||||
payloads.Insert(indexToInsertSpaceAt, m_SpaceTextPayload);
|
||||
}
|
||||
|
||||
// Decide whether to add a space to the end
|
||||
if (tagPosition == TagPosition.Before)
|
||||
{
|
||||
var significantPayloads = payloads.Where(payload => payload is TextPayload || payload is IconPayload);
|
||||
if (significantPayloads.Last() is TextPayload)
|
||||
{
|
||||
payloads.Add(m_SpaceTextPayload);
|
||||
}
|
||||
}
|
||||
// Decide whether to add a space to the beginning
|
||||
else if (tagPosition == TagPosition.After)
|
||||
{
|
||||
var significantPayloads = payloads.Where(payload => payload is TextPayload || payload is IconPayload);
|
||||
if (significantPayloads.First() is TextPayload)
|
||||
{
|
||||
payloads.Insert(0, m_SpaceTextPayload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A match found within a string.
|
||||
/// </summary>
|
||||
private class StringMatch
|
||||
{
|
||||
/// <summary>
|
||||
/// The string that the match was found in.
|
||||
/// </summary>
|
||||
public SeString SeString { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The matching text payload.
|
||||
/// </summary>
|
||||
public TextPayload TextPayload { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The matching game object if one exists
|
||||
/// </summary>
|
||||
public GameObject? GameObject { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// A matching player payload if one exists.
|
||||
/// </summary>
|
||||
public PlayerPayload? PlayerPayload { get; init; }
|
||||
|
||||
public StringMatch(SeString seString, TextPayload textPayload)
|
||||
{
|
||||
SeString = seString;
|
||||
TextPayload = textPayload;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the matches text.
|
||||
/// </summary>
|
||||
/// <returns>The match text.</returns>
|
||||
public string GetMatchText()
|
||||
{
|
||||
if (GameObject != null)
|
||||
{
|
||||
return GameObject.Name.TextValue;
|
||||
}
|
||||
|
||||
return TextPayload.Text;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches the given string for game object matches.
|
||||
/// </summary>
|
||||
/// <param name="seString">The string to search.</param>
|
||||
/// <returns>A list of matched game objects.</returns>
|
||||
private List<StringMatch> GetStringMatches(SeString seString)
|
||||
{
|
||||
List<StringMatch> stringMatches = new List<StringMatch>();
|
||||
|
||||
for (int payloadIndex = 0; payloadIndex < seString.Payloads.Count; ++payloadIndex)
|
||||
{
|
||||
var payload = seString.Payloads[payloadIndex];
|
||||
if (payload is PlayerPayload playerPayload)
|
||||
{
|
||||
var gameObject = ObjectTable.FirstOrDefault(gameObject => gameObject.Name.TextValue == playerPayload.PlayerName);
|
||||
|
||||
// The next payload MUST be a text payload
|
||||
if (payloadIndex + 1 < seString.Payloads.Count && seString.Payloads[payloadIndex + 1] is TextPayload textPayload)
|
||||
{
|
||||
var stringMatch = new StringMatch(seString, textPayload)
|
||||
{
|
||||
GameObject = gameObject,
|
||||
PlayerPayload = playerPayload
|
||||
};
|
||||
stringMatches.Add(stringMatch);
|
||||
|
||||
// Don't handle the text payload twice
|
||||
payloadIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
PluginLog.Error("Expected payload after player payload to be a text payload but it wasn't");
|
||||
}
|
||||
}
|
||||
|
||||
/// TODO: Not sure if this is desirable. Enabling this allows tags to appear next to the name of the local player by text in chat because the local player doesn't have a player payload.
|
||||
/// But because it's just a simple string comparison, it won't work in all circumstances. E.g. in party chat the player name is wrapped in (). To be comprehensive we need to search substring.
|
||||
/// This means we would need to think about breaking down existing payloads to split them out.
|
||||
/// If we decide to do that, we could even for example find unlinked player names in chat and add player payloads for them.
|
||||
// If it's just a text payload then either a character NEEDS to exist for it, or it needs to be identified as a character by custom tag configs
|
||||
//else if (payload is TextPayload textPayload)
|
||||
//{
|
||||
// var gameObject = ObjectTable.FirstOrDefault(gameObject => gameObject.Name.TextValue == textPayload.Text);
|
||||
// var isIncludedInCustomTagConfig = m_Config.CustomTags.Any(customTagConfig => customTagConfig.IncludesGameObjectName(textPayload.Text));
|
||||
|
||||
// if (gameObject != null || isIncludedInCustomTagConfig)
|
||||
// {
|
||||
// var stringMatch = new StringMatch(seString, textPayload)
|
||||
// {
|
||||
// GameObject = gameObject
|
||||
// };
|
||||
// stringMatches.Add(stringMatch);
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
return stringMatches;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the given payload changes to the dictionary.
|
||||
/// </summary>
|
||||
/// <param name="tagPosition">The position to add changes to.</param>
|
||||
/// <param name="payloads">The payloads to add.</param>
|
||||
/// <param name="stringChanges">The dictionary to add the changes to.</param>
|
||||
private void AddPayloadChanges(TagPosition tagPosition, IEnumerable<Payload> payloads, Dictionary<TagPosition, List<Payload>> stringChanges)
|
||||
{
|
||||
if (payloads == null || !payloads.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (stringChanges == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!stringChanges.Keys.Contains(tagPosition))
|
||||
{
|
||||
stringChanges[tagPosition] = new List<Payload>();
|
||||
}
|
||||
|
||||
stringChanges[tagPosition].AddRange(payloads);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the given payload changes to the dictionary.
|
||||
/// </summary>
|
||||
/// <param name="nameplateElement">The nameplate element to add changes to.</param>
|
||||
/// <param name="tagPosition">The position to add changes to.</param>
|
||||
/// <param name="payloads">The payloads to add.</param>
|
||||
/// <param name="nameplateChanges">The dictionary to add the changes to.</param>
|
||||
private void AddPayloadChanges(NameplateElement nameplateElement, TagPosition tagPosition, IEnumerable<Payload> payloads, Dictionary<NameplateElement, Dictionary<TagPosition, List<Payload>>> nameplateChanges)
|
||||
{
|
||||
if (!payloads.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!nameplateChanges.Keys.Contains(nameplateElement))
|
||||
{
|
||||
nameplateChanges[nameplateElement] = new Dictionary<TagPosition, List<Payload>>();
|
||||
}
|
||||
|
||||
AddPayloadChanges(tagPosition, payloads, nameplateChanges[nameplateElement]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies changes to the given string.
|
||||
/// </summary>
|
||||
/// <param name="seString">The string to apply changes to.</param>
|
||||
/// <param name="stringChanges">The changes to apply.</param>
|
||||
/// <param name="anchorPayload">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.</param>
|
||||
private void ApplyStringChanges(SeString seString, Dictionary<TagPosition, List<Payload>> stringChanges, Payload? anchorPayload = null)
|
||||
{
|
||||
if (stringChanges.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<TagPosition> tagPositionsOrdered = new List<TagPosition>();
|
||||
// If there's no anchor payload, do replaces first so that befores and afters are based on the replaced data
|
||||
if (anchorPayload == null)
|
||||
{
|
||||
tagPositionsOrdered.Add(TagPosition.Replace);
|
||||
}
|
||||
|
||||
tagPositionsOrdered.Add(TagPosition.Before);
|
||||
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)
|
||||
{
|
||||
tagPositionsOrdered.Add(TagPosition.Replace);
|
||||
}
|
||||
|
||||
foreach (var tagPosition in tagPositionsOrdered)
|
||||
{
|
||||
if (stringChanges.TryGetValue(tagPosition, out var payloads) && payloads.Any())
|
||||
{
|
||||
AddSpacesBetweenTextPayloads(stringChanges[tagPosition], tagPosition);
|
||||
if (tagPosition == TagPosition.Before)
|
||||
{
|
||||
if (anchorPayload != null)
|
||||
{
|
||||
var anchorPayloadIndex = seString.Payloads.IndexOf(anchorPayload);
|
||||
seString.Payloads.InsertRange(anchorPayloadIndex, payloads);
|
||||
}
|
||||
else
|
||||
{
|
||||
seString.Payloads.InsertRange(0, payloads);
|
||||
}
|
||||
}
|
||||
else if (tagPosition == TagPosition.After)
|
||||
{
|
||||
if (anchorPayload != null)
|
||||
{
|
||||
var anchorPayloadIndex = seString.Payloads.IndexOf(anchorPayload);
|
||||
seString.Payloads.InsertRange(anchorPayloadIndex + 1, payloads);
|
||||
}
|
||||
else
|
||||
{
|
||||
seString.Payloads.AddRange(payloads);
|
||||
}
|
||||
}
|
||||
else if (tagPosition == TagPosition.Replace)
|
||||
{
|
||||
if (anchorPayload != null)
|
||||
{
|
||||
var anchorPayloadIndex = seString.Payloads.IndexOf(anchorPayload);
|
||||
seString.Payloads.InsertRange(anchorPayloadIndex, payloads);
|
||||
seString.Payloads.Remove(anchorPayload);
|
||||
}
|
||||
else
|
||||
{
|
||||
seString.Payloads.Clear();
|
||||
seString.Payloads.AddRange(payloads);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds all configured tags to the nameplate of a game object.
|
||||
/// </summary>
|
||||
/// <param name="gameObject">The game object context.</param>
|
||||
/// <param name="name">The name text to change.</param>
|
||||
/// <param name="title">The title text to change.</param>
|
||||
/// <param name="freeCompany">The free company text to change.</param>
|
||||
/// <param name="isNameChanged">Whether the name was changed.</param>
|
||||
/// <param name="isTitleChanged">Whether the title was changed.</param>
|
||||
/// <param name="isFreeCompanyChanged">Whether the free company was changed.</param>
|
||||
private void AddTagsToNameplate(GameObject gameObject, SeString name, SeString title, SeString freeCompany, out bool isNameChanged, out bool isTitleChanged, out bool isFreeCompanyChanged)
|
||||
{
|
||||
isNameChanged = false;
|
||||
isTitleChanged = false;
|
||||
isFreeCompanyChanged = false;
|
||||
|
||||
Dictionary<NameplateElement, Dictionary<TagPosition, List<Payload>>> nameplateChanges = new Dictionary<NameplateElement, Dictionary<TagPosition, List<Payload>>>();
|
||||
|
||||
if (gameObject is Character character)
|
||||
{
|
||||
// Add the job tag
|
||||
if (m_PluginData.JobTags.TryGetValue(character.ClassJob.GameData.Abbreviation, out var jobTag))
|
||||
{
|
||||
if (jobTag.TagTargetInNameplates.InheritedValue != null && jobTag.TagPositionInNameplates.InheritedValue != null)
|
||||
{
|
||||
var payloads = GetTagPayloads(TagTarget.Nameplate, jobTag);
|
||||
if (payloads.Any())
|
||||
{
|
||||
AddPayloadChanges(jobTag.TagTargetInNameplates.InheritedValue.Value, jobTag.TagPositionInNameplates.InheritedValue.Value, payloads, nameplateChanges);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the randomly generated name tag payload
|
||||
if (m_PluginConfiguration.IsPlayerNameRandomlyGenerated && m_RandomNameGenerator != null)
|
||||
{
|
||||
var characterName = character.Name.TextValue;
|
||||
if (characterName != null)
|
||||
{
|
||||
var generatedName = m_RandomNameGenerator.GetGeneratedName(characterName);
|
||||
if (generatedName != null)
|
||||
{
|
||||
AddPayloadChanges(NameplateElement.Name, TagPosition.Replace, Enumerable.Empty<Payload>().Append(new TextPayload(generatedName)), nameplateChanges);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the custom tag payloads
|
||||
foreach (var customTag in m_PluginData.CustomTags)
|
||||
{
|
||||
if (customTag.TagTargetInNameplates.InheritedValue != null && customTag.TagPositionInNameplates.InheritedValue != null)
|
||||
{
|
||||
if (customTag.IncludesGameObjectNameToApplyTo(gameObject.Name.TextValue))
|
||||
{
|
||||
var payloads = GetTagPayloads(TagTarget.Nameplate, customTag);
|
||||
if (payloads.Any())
|
||||
{
|
||||
AddPayloadChanges(customTag.TagTargetInNameplates.InheritedValue.Value, customTag.TagPositionInNameplates.InheritedValue.Value, payloads, nameplateChanges);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build the final strings out of the payloads
|
||||
foreach ((var nameplateElement, var stringChanges) in nameplateChanges)
|
||||
{
|
||||
SeString? seString = null;
|
||||
if (nameplateElement == NameplateElement.Name)
|
||||
{
|
||||
seString = name;
|
||||
isNameChanged = true;
|
||||
}
|
||||
else if (nameplateElement == NameplateElement.Title)
|
||||
{
|
||||
seString = title;
|
||||
isTitleChanged = true;
|
||||
}
|
||||
else if (nameplateElement == NameplateElement.FreeCompany)
|
||||
{
|
||||
seString = freeCompany;
|
||||
isFreeCompanyChanged = true;
|
||||
}
|
||||
|
||||
if (seString != null)
|
||||
{
|
||||
ApplyStringChanges(seString, stringChanges);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds all configured tags to chat.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to change.</param>
|
||||
/// <param name="isMessageChanged">Whether the message was changed.</param>
|
||||
private void AddTagsToChat(SeString message, out bool isMessageChanged)
|
||||
{
|
||||
isMessageChanged = false;
|
||||
|
||||
var stringMatches = GetStringMatches(message);
|
||||
foreach (var stringMatch in stringMatches)
|
||||
{
|
||||
Dictionary<TagPosition, List<Payload>> stringChanges = new Dictionary<TagPosition, List<Payload>>();
|
||||
|
||||
// The role tag payloads
|
||||
if (stringMatch.GameObject is Character character)
|
||||
{
|
||||
// Add the job tag
|
||||
if (m_PluginData.JobTags.TryGetValue(character.ClassJob.GameData.Abbreviation, out var jobTag))
|
||||
{
|
||||
if (jobTag.TagPositionInChat.InheritedValue != null)
|
||||
{
|
||||
var payloads = GetTagPayloads(TagTarget.Chat, jobTag);
|
||||
if (payloads.Any())
|
||||
{
|
||||
AddPayloadChanges(jobTag.TagPositionInChat.InheritedValue.Value, payloads, stringChanges);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add randomly generated name tag payload
|
||||
if (m_PluginConfiguration.IsPlayerNameRandomlyGenerated && m_RandomNameGenerator != null)
|
||||
{
|
||||
var playerName = stringMatch.GetMatchText();
|
||||
if (playerName != null)
|
||||
{
|
||||
var generatedName = m_RandomNameGenerator.GetGeneratedName(playerName);
|
||||
if (generatedName != null)
|
||||
{
|
||||
AddPayloadChanges(TagPosition.Replace, Enumerable.Empty<Payload>().Append(new TextPayload(generatedName)), stringChanges);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the custom tag payloads
|
||||
foreach (var customTag in m_PluginData.CustomTags)
|
||||
{
|
||||
if (customTag.TagPositionInChat.InheritedValue != null)
|
||||
{
|
||||
if (customTag.IncludesGameObjectNameToApplyTo(stringMatch.GetMatchText()))
|
||||
{
|
||||
var customTagPayloads = GetTagPayloads(TagTarget.Chat, customTag);
|
||||
if (customTagPayloads.Any())
|
||||
{
|
||||
AddPayloadChanges(customTag.TagPositionInChat.InheritedValue.Value, customTagPayloads, stringChanges);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ApplyStringChanges(message, stringChanges, stringMatch.TextPayload);
|
||||
isMessageChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
30
PlayerTags/PluginServices.cs
Normal file
30
PlayerTags/PluginServices.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Game.ClientState;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.Game.ClientState.Party;
|
||||
using Dalamud.Game.Command;
|
||||
using Dalamud.Game.Gui;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.Plugin;
|
||||
|
||||
namespace PlayerTags
|
||||
{
|
||||
public class PluginServices
|
||||
{
|
||||
[PluginService] public static ChatGui ChatGui { get; set; } = null!;
|
||||
[PluginService] public static ClientState ClientState { get; set; } = null!;
|
||||
[PluginService] public static CommandManager CommandManager { get; set; } = null!;
|
||||
[PluginService] public static DalamudPluginInterface DalamudPluginInterface { get; set; } = null!;
|
||||
[PluginService] public static DataManager DataManager { get; set; } = null!;
|
||||
[PluginService] public static Framework Framework { get; set; } = null!;
|
||||
[PluginService] public static GameGui GameGui { get; set; } = null!;
|
||||
[PluginService] public static ObjectTable ObjectTable { get; set; } = null!;
|
||||
[PluginService] public static PartyList PartyList { get; set; } = null!;
|
||||
|
||||
public static void Initialize(DalamudPluginInterface pluginInterface)
|
||||
{
|
||||
pluginInterface.Create<PluginServices>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace PlayerTags
|
||||
namespace PlayerTags.PluginStrings
|
||||
{
|
||||
public interface IPluginString
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace PlayerTags
|
||||
namespace PlayerTags.PluginStrings
|
||||
{
|
||||
public class LiteralPluginString : IPluginString
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace PlayerTags
|
||||
namespace PlayerTags.PluginStrings
|
||||
{
|
||||
public class LocalizedPluginString : IPluginString
|
||||
{
|
||||
@@ -10,31 +10,64 @@ namespace PlayerTags
|
||||
/// <summary>
|
||||
/// Generates names based on an existing list of words.
|
||||
/// </summary>
|
||||
public class RandomNameGenerator
|
||||
public static class RandomNameGenerator
|
||||
{
|
||||
private const string c_AdjectivesPath = "Resources/Words/Adjectives.txt";
|
||||
private string[]? m_Adjectives;
|
||||
|
||||
private const string c_NounsPath = "Resources/Words/Nouns.txt";
|
||||
private string[]? m_Nouns;
|
||||
|
||||
private Dictionary<int, string> m_GeneratedNames = new Dictionary<int, string>();
|
||||
|
||||
private string? PluginDirectory
|
||||
private static string? PluginDirectory
|
||||
{
|
||||
get { return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); }
|
||||
}
|
||||
|
||||
public RandomNameGenerator()
|
||||
private const string c_AdjectivesPath = "Resources/Words/Adjectives.txt";
|
||||
private static string[]? s_Adjectives;
|
||||
private static string[] Adjectives
|
||||
{
|
||||
try
|
||||
get
|
||||
{
|
||||
m_Adjectives = File.ReadAllLines($"{PluginDirectory}/{c_AdjectivesPath}");
|
||||
m_Nouns = File.ReadAllLines($"{PluginDirectory}/{c_NounsPath}");
|
||||
if (s_Adjectives == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
s_Adjectives = File.ReadAllLines($"{PluginDirectory}/{c_AdjectivesPath}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginLog.Error(ex, $"RandomNameGenerator failed to read adjectives");
|
||||
}
|
||||
}
|
||||
|
||||
if (s_Adjectives != null)
|
||||
{
|
||||
return s_Adjectives;
|
||||
}
|
||||
|
||||
return new string[] { };
|
||||
}
|
||||
catch (Exception ex)
|
||||
}
|
||||
|
||||
private const string c_NounsPath = "Resources/Words/Nouns.txt";
|
||||
private static string[]? s_Nouns;
|
||||
private static string[] Nouns
|
||||
{
|
||||
get
|
||||
{
|
||||
PluginLog.Error(ex, $"RandomNameGenerator failed to create");
|
||||
if (s_Nouns == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
s_Nouns = File.ReadAllLines($"{PluginDirectory}/{c_NounsPath}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginLog.Error(ex, $"RandomNameGenerator failed to read nouns");
|
||||
}
|
||||
}
|
||||
|
||||
if (s_Nouns != null)
|
||||
{
|
||||
return s_Nouns;
|
||||
}
|
||||
|
||||
return new string[] { };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,27 +76,22 @@ namespace PlayerTags
|
||||
/// </summary>
|
||||
/// <param name="str">The string to generate a name for.</param>
|
||||
/// <returns>A generated name.</returns>
|
||||
public string? GetGeneratedName(string str)
|
||||
public static string? Generate(string str)
|
||||
{
|
||||
if (m_Adjectives == null || m_Nouns == null)
|
||||
if (Adjectives == null || Nouns == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
int hash = GetDeterministicHashCode(str);
|
||||
|
||||
if (!m_GeneratedNames.ContainsKey(hash))
|
||||
{
|
||||
// Use the seed as the hash so that player always gets the same name
|
||||
Random random = new Random(hash);
|
||||
var adjective = m_Adjectives[random.Next(0, m_Adjectives.Length)];
|
||||
var noun = m_Nouns[random.Next(0, m_Nouns.Length)];
|
||||
var generatedName = $"{adjective} {noun}";
|
||||
// Use the seed as the hash so the same player always gets the same name
|
||||
Random random = new Random(hash);
|
||||
var adjective = Adjectives[random.Next(0, Adjectives.Length)];
|
||||
var noun = Nouns[random.Next(0, Nouns.Length)];
|
||||
var generatedName = $"{adjective} {noun}";
|
||||
|
||||
m_GeneratedNames[hash] = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(generatedName);
|
||||
}
|
||||
|
||||
return m_GeneratedNames[hash];
|
||||
return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(generatedName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
8
PlayerTags/Resources/Strings.Designer.cs
generated
8
PlayerTags/Resources/Strings.Designer.cs
generated
@@ -135,18 +135,18 @@ namespace PlayerTags.Resources {
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Context menu integration.
|
||||
/// </summary>
|
||||
public static string Loc_IsCustomTagContextMenuEnabled {
|
||||
public static string Loc_IsCustomTagsContextMenuEnabled {
|
||||
get {
|
||||
return ResourceManager.GetString("Loc_IsCustomTagContextMenuEnabled", resourceCulture);
|
||||
return ResourceManager.GetString("Loc_IsCustomTagsContextMenuEnabled", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Options will be available in context menus for adding and removing custom tags from players..
|
||||
/// </summary>
|
||||
public static string Loc_IsCustomTagContextMenuEnabled_Description {
|
||||
public static string Loc_IsCustomTagsContextMenuEnabled_Description {
|
||||
get {
|
||||
return ResourceManager.GetString("Loc_IsCustomTagContextMenuEnabled_Description", resourceCulture);
|
||||
return ResourceManager.GetString("Loc_IsCustomTagsContextMenuEnabled_Description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -159,10 +159,10 @@
|
||||
<data name="Loc_IsExpanded" xml:space="preserve">
|
||||
<value>Expanded</value>
|
||||
</data>
|
||||
<data name="Loc_IsCustomTagContextMenuEnabled" xml:space="preserve">
|
||||
<data name="Loc_IsCustomTagsContextMenuEnabled" xml:space="preserve">
|
||||
<value>Context menu integration</value>
|
||||
</data>
|
||||
<data name="Loc_IsCustomTagContextMenuEnabled_Description" xml:space="preserve">
|
||||
<data name="Loc_IsCustomTagsContextMenuEnabled_Description" xml:space="preserve">
|
||||
<value>Options will be available in context menus for adding and removing custom tags from players.</value>
|
||||
</data>
|
||||
<data name="Loc_IsShowInheritedPropertiesEnabled" xml:space="preserve">
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Dalamud.Data;
|
||||
using ImGuiNET;
|
||||
using ImGuiNET;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -28,13 +27,47 @@ namespace PlayerTags
|
||||
}
|
||||
}
|
||||
|
||||
private static UIColor[] s_UIColors = new UIColor[] { };
|
||||
private static UIColor[] s_UIColors = null!;
|
||||
|
||||
public static IEnumerable<UIColor> UIColors { get { return s_UIColors; } }
|
||||
|
||||
public static void Initialize(DataManager dataManager)
|
||||
public static IEnumerable<UIColor> UIColors
|
||||
{
|
||||
var uiColors = dataManager.GetExcelSheet<UIColor>();
|
||||
get
|
||||
{
|
||||
if (s_UIColors == null)
|
||||
{
|
||||
s_UIColors = CreateUIColors();
|
||||
}
|
||||
|
||||
return s_UIColors;
|
||||
}
|
||||
}
|
||||
|
||||
public static Vector4 ToColor(UIColor uiColor)
|
||||
{
|
||||
var uiColorBytes = BitConverter.GetBytes(uiColor.UIForeground);
|
||||
return
|
||||
new Vector4((float)uiColorBytes[3] / 255,
|
||||
(float)uiColorBytes[2] / 255,
|
||||
(float)uiColorBytes[1] / 255,
|
||||
(float)uiColorBytes[0] / 255);
|
||||
}
|
||||
|
||||
public static Vector4 ToColor(ushort colorId)
|
||||
{
|
||||
foreach (var uiColor in UIColors)
|
||||
{
|
||||
if ((ushort)uiColor.RowId == colorId)
|
||||
{
|
||||
return ToColor(uiColor);
|
||||
}
|
||||
}
|
||||
|
||||
return new Vector4();
|
||||
}
|
||||
|
||||
private static UIColor[] CreateUIColors()
|
||||
{
|
||||
var uiColors = PluginServices.DataManager.GetExcelSheet<UIColor>();
|
||||
if (uiColors != null)
|
||||
{
|
||||
var filteredUIColors = new List<UIColor>(uiColors.Distinct(new UIColorComparer()).Where(uiColor => uiColor.UIForeground != 0 && uiColor.UIForeground != 255));
|
||||
@@ -67,31 +100,10 @@ namespace PlayerTags
|
||||
return 0;
|
||||
});
|
||||
|
||||
s_UIColors = filteredUIColors.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public static Vector4 ToColor(UIColor uiColor)
|
||||
{
|
||||
var uiColorBytes = BitConverter.GetBytes(uiColor.UIForeground);
|
||||
return
|
||||
new Vector4((float)uiColorBytes[3] / 255,
|
||||
(float)uiColorBytes[2] / 255,
|
||||
(float)uiColorBytes[1] / 255,
|
||||
(float)uiColorBytes[0] / 255);
|
||||
}
|
||||
|
||||
public static Vector4 ToColor(ushort colorId)
|
||||
{
|
||||
foreach (var uiColor in UIColors)
|
||||
{
|
||||
if ((ushort)uiColor.RowId == colorId)
|
||||
{
|
||||
return ToColor(uiColor);
|
||||
}
|
||||
return filteredUIColors.ToArray();
|
||||
}
|
||||
|
||||
return new Vector4();
|
||||
return new UIColor[] { };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user