40 Commits

Author SHA1 Message Date
f66e81d6b6 v1.11.1 2024-07-20 09:28:07 +02:00
dc1d4581aa update submodule 2024-07-20 09:27:43 +02:00
a11f81ca48 Lösche CHANGELOG 2024-07-18 05:18:14 +00:00
2e76ece592 CHANGELOG hinzufügen 2024-07-18 05:17:34 +00:00
a49e29a4f1 update readme 2024-07-18 07:09:55 +02:00
63ede74217 update repo url in manifest 2024-07-18 07:02:24 +02:00
59d6be0ffd update submodule 2024-07-18 06:36:21 +02:00
f83cff5683 update submodule 2024-07-18 06:26:39 +02:00
b52174d3e3 update readme & gitmodule 2024-07-18 06:26:10 +02:00
1b23cb5da8 update submodule 2024-07-17 19:39:02 +02:00
19db0cdf41 version bump (again) 2024-07-17 15:18:12 +02:00
b570c1c003 update submodule 2024-07-17 15:16:47 +02:00
ffa9f8497a only handle player nameplates 2024-07-17 15:04:30 +02:00
b356138732 update DalamudPackager 2024-07-17 15:04:30 +02:00
361bd4ee21 update submodule 2024-07-17 15:04:30 +02:00
5fe7afe7cf update submodule 2024-07-17 15:04:30 +02:00
4fb5cc0b9d migrate chat, nameplate, etc. to new api 2024-07-17 15:04:30 +02:00
8f644ce913 update submodule 2024-07-17 15:04:30 +02:00
2bce938cf3 apply code formatting 2024-07-17 15:04:30 +02:00
b40f0d5b87 use IDatamudPluginInterface 2024-07-17 15:04:30 +02:00
681419e11b update 2024-07-17 15:04:30 +02:00
56f58e7cbd target .net 8 2024-03-23 12:46:22 +01:00
3ec790296e test 2023-12-12 09:06:31 +01:00
52f8723f84 update submodules 2023-10-07 11:25:33 +02:00
0f729a846d v0.5.2.1 2023-10-07 11:25:27 +02:00
a6111e45d9 v1.10.2 2023-10-07 11:20:26 +02:00
aa99228e96 update submodule 2023-10-07 11:20:11 +02:00
4db9f29747 v1.10.1 2023-10-06 21:01:19 +02:00
86448e1490 code formatting 2023-10-06 21:01:07 +02:00
27d900bee3 update submodules 2023-10-06 21:01:01 +02:00
746c3adf9d fix wrong version v1.10 2023-10-04 09:57:12 +02:00
e1f80a3dd5 v1.10 2023-10-04 09:40:41 +02:00
e41b875653 update submodules 2023-10-03 09:46:37 +02:00
d1a3d51e18 merge branche 'master' into master 2023-10-03 09:46:23 +02:00
db13d432ab update nuget packages 2023-10-03 09:44:52 +02:00
5072246f64 update submodule 2023-10-03 09:44:14 +02:00
30794201f0 migrate to GameConfig & cleanup warnings 2023-10-03 09:44:14 +02:00
cc1bdc7e08 update nuget packages 2023-10-03 09:44:14 +02:00
1f1b08cd59 update submodule 2023-10-03 09:44:14 +02:00
2dcf94eae2 some adjustments for API9 2023-10-03 09:44:14 +02:00
58 changed files with 4385 additions and 4958 deletions

2
.gitmodules vendored
View File

@@ -1,4 +1,4 @@
[submodule "Pilz.Dalamud"]
path = Pilz.Dalamud
url = https://github.com/Pilzinsel64/Pilz.Dalamud.git
url = https://git.pilzinsel64.de/pilz-framework/pilz.dalamud.git
branch = master

View File

@@ -26,6 +26,7 @@ Global
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
RESX_ShowErrorsInErrorList = False
SolutionGuid = {B17E85B1-5F60-4440-9F9A-3DDE877E8CDF}
EndGlobalSection
EndGlobal

View File

@@ -1,74 +1,41 @@
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
using Lumina.Excel.GeneratedSheets;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PlayerTags.Configuration.GameConfig
namespace PlayerTags.Configuration.GameConfig;
public class GameConfigHelper
{
public class GameConfigHelper
private static GameConfigHelper instance = null;
private unsafe static ConfigModule* configModule = null;
public static GameConfigHelper Instance
{
private static GameConfigHelper instance = null;
private unsafe static ConfigModule* configModule = null;
public static GameConfigHelper Instance
get
{
get
{
instance ??= new GameConfigHelper();
return instance;
}
}
private GameConfigHelper()
{
unsafe
{
configModule = ConfigModule.Instance();
}
}
private int? GetIntValue(ConfigOption option)
{
int? value = null;
unsafe
{
var index = configModule->GetIndex(option);
if (index.HasValue)
value = configModule->GetIntValue(index.Value);
}
return value;
}
public LogNameType? GetLogNameType()
{
LogNameType? logNameType = null;
int? value = GetIntValue(ConfigOption.LogNameType);
if (value.HasValue)
{
switch (value)
{
case 0:
logNameType = LogNameType.FullName;
break;
case 1:
logNameType = LogNameType.LastNameShorted;
break;
case 2:
logNameType = LogNameType.FirstNameShorted;
break;
case 3:
logNameType = LogNameType.Initials;
break;
}
}
return logNameType;
instance ??= new GameConfigHelper();
return instance;
}
}
private GameConfigHelper()
{
unsafe
{
configModule = ConfigModule.Instance();
}
}
private uint? GetIntValue(ConfigOption option)
{
if (PluginServices.GameConfig.UiConfig.TryGetUInt(nameof(ConfigOption.LogNameType), out var value))
return value;
return null;
}
public LogNameType? GetLogNameType()
{
uint? value = GetIntValue(ConfigOption.LogNameType);
if (value != null)
return (LogNameType)value;
return null;
}
}

View File

@@ -1,16 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PlayerTags.Configuration.GameConfig;
namespace PlayerTags.Configuration.GameConfig
public enum LogNameType : uint
{
public enum LogNameType
{
FullName,
LastNameShorted,
FirstNameShorted,
Initials
}
FullName,
LastNameShorted,
FirstNameShorted,
Initials
}

View File

@@ -2,351 +2,348 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Pilz.Dalamud.ActivityContexts;
using Pilz.Dalamud.Nameplates.Tools;
using Pilz.Dalamud.Tools.NamePlates;
using PlayerTags.Data;
using PlayerTags.Inheritables;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection.Metadata.Ecma335;
using System.Runtime.CompilerServices;
namespace PlayerTags.Configuration
namespace PlayerTags.Configuration;
[Serializable]
public class PluginConfiguration : IPluginConfiguration
{
[Serializable]
public class PluginConfiguration : IPluginConfiguration
private const int DEFAULT_CONFIG_VERSION = 1;
[JsonProperty]
public int RootVersion { get; private set; } = DEFAULT_CONFIG_VERSION;
public int Version { get; set; } = DEFAULT_CONFIG_VERSION;
public bool IsVisible = false;
public bool EnabledGlobal = true;
[JsonProperty("GeneralOptionsV2")]
public Dictionary<ActivityType, GeneralOptionsClass> GeneralOptions = new()
{
private const int DEFAULT_CONFIG_VERSION = 1;
{ ActivityType.None, new GeneralOptionsClass() },
{ ActivityType.PveDuty, new GeneralOptionsClass() },
{ ActivityType.PvpDuty, new GeneralOptionsClass() }
};
[JsonProperty]
public int RootVersion { get; private set; } = DEFAULT_CONFIG_VERSION;
public int Version { get; set; } = DEFAULT_CONFIG_VERSION;
public bool IsVisible = false;
public bool EnabledGlobal = true;
public DefaultPluginDataTemplate DefaultPluginDataTemplate = DefaultPluginDataTemplate.None;
public StatusIconPriorizerSettings StatusIconPriorizerSettings = new(true);
public bool MoveStatusIconToNameplateTextIfPossible = true;
public bool IsPlayerNameRandomlyGenerated = false;
public bool IsCustomTagsContextMenuEnabled = true;
public bool IsShowInheritedPropertiesEnabled = true;
public bool IsPlayersTabOrderedByProximity = false;
public bool IsPlayersTabSelfVisible = true;
public bool IsPlayersTabFriendsVisible = true;
public bool IsPlayersTabPartyVisible = true;
public bool IsPlayersTabAllianceVisible = true;
public bool IsPlayersTabEnemiesVisible = true;
public bool IsPlayersTabOthersVisible = false;
public bool IsGeneralOptionsAllTheSameEnabled = true;
[JsonProperty("GeneralOptionsV2")]
public Dictionary<ActivityType, GeneralOptionsClass> GeneralOptions = new()
[JsonProperty(TypeNameHandling = TypeNameHandling.None, ItemTypeNameHandling = TypeNameHandling.None)]
public Dictionary<string, InheritableData> AllTagsChanges = [];
[JsonProperty(TypeNameHandling = TypeNameHandling.None, ItemTypeNameHandling = TypeNameHandling.None)]
public Dictionary<string, InheritableData> AllRoleTagsChanges = [];
[JsonProperty(TypeNameHandling = TypeNameHandling.None, ItemTypeNameHandling = TypeNameHandling.None)]
public Dictionary<Role, Dictionary<string, InheritableData>> RoleTagsChanges = [];
[JsonProperty(TypeNameHandling = TypeNameHandling.None, ItemTypeNameHandling = TypeNameHandling.None)]
public Dictionary<DpsRole, Dictionary<string, InheritableData>> DpsRoleTagsChanges = [];
[JsonProperty(TypeNameHandling = TypeNameHandling.None, ItemTypeNameHandling = TypeNameHandling.None)]
public Dictionary<RangedDpsRole, Dictionary<string, InheritableData>> RangedDpsRoleTagsChanges = [];
[JsonProperty(TypeNameHandling = TypeNameHandling.None, ItemTypeNameHandling = TypeNameHandling.None)]
public Dictionary<LandHandRole, Dictionary<string, InheritableData>> LandHandRoleTagsChanges = [];
[JsonProperty(TypeNameHandling = TypeNameHandling.None, ItemTypeNameHandling = TypeNameHandling.None)]
public Dictionary<string, Dictionary<string, InheritableData>> JobTagsChanges = [];
[JsonProperty(TypeNameHandling = TypeNameHandling.None, ItemTypeNameHandling = TypeNameHandling.None)]
public Dictionary<string, InheritableData> AllCustomTagsChanges = [];
[JsonProperty(TypeNameHandling = TypeNameHandling.None, ItemTypeNameHandling = TypeNameHandling.None)]
public List<Dictionary<string, InheritableData>> CustomTagsChanges = [];
[JsonProperty(TypeNameHandling = TypeNameHandling.None, ItemTypeNameHandling = TypeNameHandling.None)]
public List<Identity> Identities = [];
#region Obsulate Properties
[Obsolete]
[JsonProperty("GeneralOptions")]
private Dictionary<Data.ActivityContext, GeneralOptionsClass> GeneralOptionsV1
{
set
{
{ ActivityType.None, new GeneralOptionsClass() },
{ ActivityType.PveDuty, new GeneralOptionsClass() },
{ ActivityType.PvpDuty, new GeneralOptionsClass() }
GeneralOptions.Clear();
foreach (var kvp in value)
GeneralOptions.Add((ActivityType)kvp.Key, kvp.Value);
}
}
[JsonProperty("NameplateFreeCompanyVisibility"), Obsolete]
private NameplateFreeCompanyVisibility NameplateFreeCompanyVisibilityV1
{
set
{
foreach (var key in GeneralOptions.Keys)
GeneralOptions[key].NameplateFreeCompanyVisibility = value;
}
}
[JsonProperty("NameplateTitleVisibility"), Obsolete]
public NameplateTitleVisibility NameplateTitleVisibilityV1
{
set
{
foreach (var key in GeneralOptions.Keys)
GeneralOptions[key].NameplateTitleVisibility = value;
}
}
[JsonProperty("NameplateTitlePosition"), Obsolete]
public NameplateTitlePosition NameplateTitlePositionV1
{
set
{
foreach (var key in GeneralOptions.Keys)
GeneralOptions[key].NameplateTitlePosition = value;
}
}
[JsonProperty("IsApplyTagsToAllChatMessagesEnabled"), Obsolete]
private bool IsApplyTagsToAllChatMessagesEnabledV1
{
set
{
foreach (var key in GeneralOptions.Keys)
GeneralOptions[key].IsApplyTagsToAllChatMessagesEnabled = value;
}
}
#endregion
public event System.Action? Saved;
public void Save(PluginData pluginData)
{
AllTagsChanges = pluginData.AllTags.GetChanges(pluginData.Default.AllTags.GetChanges());
AllRoleTagsChanges = pluginData.AllRoleTags.GetChanges(pluginData.Default.AllRoleTags.GetChanges());
RoleTagsChanges = [];
foreach ((var role, var roleTag) in pluginData.RoleTags)
{
Dictionary<string, InheritableData>? defaultChanges = [];
if (pluginData.Default.RoleTags.TryGetValue(role, out var defaultTag))
{
defaultChanges = defaultTag.GetChanges();
}
var changes = roleTag.GetChanges(defaultChanges);
if (changes.Any())
{
RoleTagsChanges[role] = changes;
}
}
DpsRoleTagsChanges = [];
foreach ((var dpsRole, var dpsRoleTag) in pluginData.DpsRoleTags)
{
Dictionary<string, InheritableData>? defaultChanges = [];
if (pluginData.Default.DpsRoleTags.TryGetValue(dpsRole, out var defaultTag))
{
defaultChanges = defaultTag.GetChanges();
}
var changes = dpsRoleTag.GetChanges(defaultChanges);
if (changes.Any())
{
DpsRoleTagsChanges[dpsRole] = changes;
}
}
RangedDpsRoleTagsChanges = [];
foreach ((var rangedDpsRole, var rangedDpsRoleTag) in pluginData.RangedDpsRoleTags)
{
Dictionary<string, InheritableData>? defaultChanges = [];
if (pluginData.Default.RangedDpsRoleTags.TryGetValue(rangedDpsRole, out var defaultTag))
{
defaultChanges = defaultTag.GetChanges();
}
var changes = rangedDpsRoleTag.GetChanges(defaultChanges);
if (changes.Any())
{
RangedDpsRoleTagsChanges[rangedDpsRole] = changes;
}
}
LandHandRoleTagsChanges = [];
foreach ((var landHandRole, var landHandRoleTag) in pluginData.LandHandRoleTags)
{
Dictionary<string, InheritableData>? defaultChanges = [];
if (pluginData.Default.LandHandRoleTags.TryGetValue(landHandRole, out var defaultTag))
{
defaultChanges = defaultTag.GetChanges();
}
var changes = landHandRoleTag.GetChanges(defaultChanges);
if (changes.Any())
{
LandHandRoleTagsChanges[landHandRole] = changes;
}
}
JobTagsChanges = [];
foreach ((var jobAbbreviation, var jobTag) in pluginData.JobTags)
{
Dictionary<string, InheritableData>? defaultChanges = [];
if (pluginData.Default.JobTags.TryGetValue(jobAbbreviation, out var defaultTag))
{
defaultChanges = defaultTag.GetChanges();
}
var changes = jobTag.GetChanges(defaultChanges);
if (changes.Any())
{
JobTagsChanges[jobAbbreviation] = changes;
}
}
AllCustomTagsChanges = pluginData.AllCustomTags.GetChanges(pluginData.Default.AllCustomTags.GetChanges());
CustomTagsChanges = [];
foreach (var customTag in pluginData.CustomTags)
{
CustomTagsChanges.Add(customTag.GetChanges());
}
Identities = pluginData.Identities;
SavePluginConfig();
Saved?.Invoke();
}
private void SavePluginConfig()
{
Version = DEFAULT_CONFIG_VERSION;
var configFilePath = GetConfigFilePath();
var configFileContent = JsonConvert.SerializeObject(this, Formatting.Indented, GetJsonSettings());
File.WriteAllText(configFilePath, configFileContent);
}
private static void BackupPluginConfig()
{
var configFilePath = GetConfigFilePath();
var configFilePathOld = Path.ChangeExtension(configFilePath, ".old" + Path.GetExtension(configFilePath));
File.Copy(configFilePath, configFilePathOld, true);
}
public static PluginConfiguration LoadPluginConfig()
{
var configFilePath = GetConfigFilePath();
object config = null;
if (File.Exists(configFilePath))
{
var configFileContent = File.ReadAllText(configFilePath);
config = JsonConvert.DeserializeObject<PluginConfiguration>(configFileContent, GetJsonSettings());
}
else
{
// Try loading the old settings, if possible
configFilePath = PluginServices.DalamudPluginInterface.ConfigFile.FullName;
config = PluginServices.DalamudPluginInterface.GetPluginConfig();
}
if (config is PluginConfiguration pluginConfig)
{
if (PluginConfigFix(pluginConfig))
{
BackupPluginConfig();
pluginConfig.SavePluginConfig();
}
}
return config as PluginConfiguration;
}
private static string GetConfigFilePath()
{
return Path.Combine(PluginServices.DalamudPluginInterface.ConfigDirectory.FullName, "Config.json");
}
private static JsonSerializerSettings GetJsonSettings()
{
var jsonSettings = new JsonSerializerSettings
{
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple,
TypeNameHandling = TypeNameHandling.Auto,
};
public DefaultPluginDataTemplate DefaultPluginDataTemplate = DefaultPluginDataTemplate.None;
public StatusIconPriorizerSettings StatusIconPriorizerSettings = new(true);
public bool MoveStatusIconToNameplateTextIfPossible = true;
public bool IsPlayerNameRandomlyGenerated = false;
public bool IsCustomTagsContextMenuEnabled = true;
public bool IsShowInheritedPropertiesEnabled = true;
public bool IsPlayersTabOrderedByProximity = false;
public bool IsPlayersTabSelfVisible = true;
public bool IsPlayersTabFriendsVisible = true;
public bool IsPlayersTabPartyVisible = true;
public bool IsPlayersTabAllianceVisible = true;
public bool IsPlayersTabEnemiesVisible = true;
public bool IsPlayersTabOthersVisible = false;
public bool IsGeneralOptionsAllTheSameEnabled = true;
jsonSettings.Converters.Add(new StringEnumConverter());
[JsonProperty(TypeNameHandling = TypeNameHandling.None, ItemTypeNameHandling = TypeNameHandling.None)]
public Dictionary<string, InheritableData> AllTagsChanges = new Dictionary<string, InheritableData>();
[JsonProperty(TypeNameHandling = TypeNameHandling.None, ItemTypeNameHandling = TypeNameHandling.None)]
public Dictionary<string, InheritableData> AllRoleTagsChanges = new Dictionary<string, InheritableData>();
[JsonProperty(TypeNameHandling = TypeNameHandling.None, ItemTypeNameHandling = TypeNameHandling.None)]
public Dictionary<Role, Dictionary<string, InheritableData>> RoleTagsChanges = new Dictionary<Role, Dictionary<string, InheritableData>>();
[JsonProperty(TypeNameHandling = TypeNameHandling.None, ItemTypeNameHandling = TypeNameHandling.None)]
public Dictionary<DpsRole, Dictionary<string, InheritableData>> DpsRoleTagsChanges = new Dictionary<DpsRole, Dictionary<string, InheritableData>>();
[JsonProperty(TypeNameHandling = TypeNameHandling.None, ItemTypeNameHandling = TypeNameHandling.None)]
public Dictionary<RangedDpsRole, Dictionary<string, InheritableData>> RangedDpsRoleTagsChanges = new Dictionary<RangedDpsRole, Dictionary<string, InheritableData>>();
[JsonProperty(TypeNameHandling = TypeNameHandling.None, ItemTypeNameHandling = TypeNameHandling.None)]
public Dictionary<LandHandRole, Dictionary<string, InheritableData>> LandHandRoleTagsChanges = new Dictionary<LandHandRole, Dictionary<string, InheritableData>>();
[JsonProperty(TypeNameHandling = TypeNameHandling.None, ItemTypeNameHandling = TypeNameHandling.None)]
public Dictionary<string, Dictionary<string, InheritableData>> JobTagsChanges = new Dictionary<string, Dictionary<string, InheritableData>>();
[JsonProperty(TypeNameHandling = TypeNameHandling.None, ItemTypeNameHandling = TypeNameHandling.None)]
public Dictionary<string, InheritableData> AllCustomTagsChanges = new Dictionary<string, InheritableData>();
[JsonProperty(TypeNameHandling = TypeNameHandling.None, ItemTypeNameHandling = TypeNameHandling.None)]
public List<Dictionary<string, InheritableData>> CustomTagsChanges = new List<Dictionary<string, InheritableData>>();
[JsonProperty(TypeNameHandling = TypeNameHandling.None, ItemTypeNameHandling = TypeNameHandling.None)]
public List<Identity> Identities = new List<Identity>();
#region Obsulate Properties
[Obsolete]
[JsonProperty("GeneralOptions")]
private Dictionary<Data.ActivityContext, GeneralOptionsClass> GeneralOptionsV1
{
set
{
GeneralOptions.Clear();
foreach (var kvp in value)
GeneralOptions.Add((ActivityType)kvp.Key, kvp.Value);
}
}
[JsonProperty("NameplateFreeCompanyVisibility"), Obsolete]
private NameplateFreeCompanyVisibility NameplateFreeCompanyVisibilityV1
{
set
{
foreach (var key in GeneralOptions.Keys)
GeneralOptions[key].NameplateFreeCompanyVisibility = value;
}
}
[JsonProperty("NameplateTitleVisibility"), Obsolete]
public NameplateTitleVisibility NameplateTitleVisibilityV1
{
set
{
foreach (var key in GeneralOptions.Keys)
GeneralOptions[key].NameplateTitleVisibility = value;
}
}
[JsonProperty("NameplateTitlePosition"), Obsolete]
public NameplateTitlePosition NameplateTitlePositionV1
{
set
{
foreach (var key in GeneralOptions.Keys)
GeneralOptions[key].NameplateTitlePosition = value;
}
}
[JsonProperty("IsApplyTagsToAllChatMessagesEnabled"), Obsolete]
private bool IsApplyTagsToAllChatMessagesEnabledV1
{
set
{
foreach (var key in GeneralOptions.Keys)
GeneralOptions[key].IsApplyTagsToAllChatMessagesEnabled = value;
}
}
#endregion
public event System.Action? Saved;
public void Save(PluginData pluginData)
{
AllTagsChanges = pluginData.AllTags.GetChanges(pluginData.Default.AllTags.GetChanges());
AllRoleTagsChanges = pluginData.AllRoleTags.GetChanges(pluginData.Default.AllRoleTags.GetChanges());
RoleTagsChanges = new Dictionary<Role, Dictionary<string, InheritableData>>();
foreach ((var role, var roleTag) in pluginData.RoleTags)
{
Dictionary<string, InheritableData>? defaultChanges = new Dictionary<string, InheritableData>();
if (pluginData.Default.RoleTags.TryGetValue(role, out var defaultTag))
{
defaultChanges = defaultTag.GetChanges();
}
var changes = roleTag.GetChanges(defaultChanges);
if (changes.Any())
{
RoleTagsChanges[role] = changes;
}
}
DpsRoleTagsChanges = new Dictionary<DpsRole, Dictionary<string, InheritableData>>();
foreach ((var dpsRole, var dpsRoleTag) in pluginData.DpsRoleTags)
{
Dictionary<string, InheritableData>? defaultChanges = new Dictionary<string, InheritableData>();
if (pluginData.Default.DpsRoleTags.TryGetValue(dpsRole, out var defaultTag))
{
defaultChanges = defaultTag.GetChanges();
}
var changes = dpsRoleTag.GetChanges(defaultChanges);
if (changes.Any())
{
DpsRoleTagsChanges[dpsRole] = changes;
}
}
RangedDpsRoleTagsChanges = new Dictionary<RangedDpsRole, Dictionary<string, InheritableData>>();
foreach ((var rangedDpsRole, var rangedDpsRoleTag) in pluginData.RangedDpsRoleTags)
{
Dictionary<string, InheritableData>? defaultChanges = new Dictionary<string, InheritableData>();
if (pluginData.Default.RangedDpsRoleTags.TryGetValue(rangedDpsRole, out var defaultTag))
{
defaultChanges = defaultTag.GetChanges();
}
var changes = rangedDpsRoleTag.GetChanges(defaultChanges);
if (changes.Any())
{
RangedDpsRoleTagsChanges[rangedDpsRole] = changes;
}
}
LandHandRoleTagsChanges = new Dictionary<LandHandRole, Dictionary<string, InheritableData>>();
foreach ((var landHandRole, var landHandRoleTag) in pluginData.LandHandRoleTags)
{
Dictionary<string, InheritableData>? defaultChanges = new Dictionary<string, InheritableData>();
if (pluginData.Default.LandHandRoleTags.TryGetValue(landHandRole, out var defaultTag))
{
defaultChanges = defaultTag.GetChanges();
}
var changes = landHandRoleTag.GetChanges(defaultChanges);
if (changes.Any())
{
LandHandRoleTagsChanges[landHandRole] = changes;
}
}
JobTagsChanges = new Dictionary<string, Dictionary<string, InheritableData>>();
foreach ((var jobAbbreviation, var jobTag) in pluginData.JobTags)
{
Dictionary<string, InheritableData>? defaultChanges = new Dictionary<string, InheritableData>();
if (pluginData.Default.JobTags.TryGetValue(jobAbbreviation, out var defaultTag))
{
defaultChanges = defaultTag.GetChanges();
}
var changes = jobTag.GetChanges(defaultChanges);
if (changes.Any())
{
JobTagsChanges[jobAbbreviation] = changes;
}
}
AllCustomTagsChanges = pluginData.AllCustomTags.GetChanges(pluginData.Default.AllCustomTags.GetChanges());
CustomTagsChanges = new List<Dictionary<string, InheritableData>>();
foreach (var customTag in pluginData.CustomTags)
{
CustomTagsChanges.Add(customTag.GetChanges());
}
Identities = pluginData.Identities;
SavePluginConfig();
Saved?.Invoke();
}
private void SavePluginConfig()
{
Version = DEFAULT_CONFIG_VERSION;
var configFilePath = GetConfigFilePath();
var configFileContent = JsonConvert.SerializeObject(this, Formatting.Indented, GetJsonSettings());
File.WriteAllText(configFilePath, configFileContent);
}
private static void BackupPluginConfig()
{
var configFilePath = GetConfigFilePath();
var configFilePathOld = Path.ChangeExtension(configFilePath, ".old" + Path.GetExtension(configFilePath));
File.Copy(configFilePath, configFilePathOld, true);
}
public static PluginConfiguration LoadPluginConfig()
{
var configFilePath = GetConfigFilePath();
object config = null;
if (File.Exists(configFilePath))
{
var configFileContent = File.ReadAllText(configFilePath);
config = JsonConvert.DeserializeObject<PluginConfiguration>(configFileContent, GetJsonSettings());
}
else
{
// Try loading the old settings, if possible
configFilePath = PluginServices.DalamudPluginInterface.ConfigFile.FullName;
config = PluginServices.DalamudPluginInterface.GetPluginConfig();
}
if (config is PluginConfiguration pluginConfig)
{
if (PluginConfigFix(pluginConfig))
{
BackupPluginConfig();
pluginConfig.SavePluginConfig();
}
}
return config as PluginConfiguration;
}
private static string GetConfigFilePath()
{
return Path.Combine(PluginServices.DalamudPluginInterface.ConfigDirectory.FullName, "Config.json");
}
private static JsonSerializerSettings GetJsonSettings()
{
var jsonSettings = new JsonSerializerSettings
{
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple,
TypeNameHandling = TypeNameHandling.Auto,
};
jsonSettings.Converters.Add(new StringEnumConverter());
return jsonSettings;
}
private static bool PluginConfigFix(PluginConfiguration config)
{
bool hasFixes = false;
// Patch 6.4 - Disable all Job & Role specific colors & prefix
// Not used yet, but keeping it there, just for the case,
//if (config.Version <= 1)
//{
// void fixTags(Dictionary<string, InheritableData> dic)
// {
// foreach (var change in config.AllRoleTagsChanges.ToArray())
// {
// var key = change.Key;
// if (key == nameof(Tag.IsTextVisibleInChat) ||
// key == nameof(Tag.IsTextVisibleInNameplates) ||
// key == nameof(Tag.IsRoleIconVisibleInChat) ||
// key == nameof(Tag.IsRoleIconVisibleInNameplates) ||
// key == nameof(Tag.IsTextColorAppliedToNameplateName) ||
// key == nameof(Tag.IsTextColorAppliedToChatName) ||
// key == nameof(Tag.IsJobIconVisibleInNameplates))
// {
// var data = change.Value;
// data.Behavior = InheritableBehavior.Disabled;
// }
// }
// }
// // "All Roles" tag changes
// fixTags(config.AllRoleTagsChanges);
// // Role tags changes
// foreach (var kvp in config.RoleTagsChanges)
// fixTags(kvp.Value);
// // Job tags changes
// foreach (var kvp in config.JobTagsChanges)
// fixTags(kvp.Value);
// hasFixes = true;
//}
return hasFixes;
}
return jsonSettings;
}
public class GeneralOptionsClass
private static bool PluginConfigFix(PluginConfiguration config)
{
public NameplateFreeCompanyVisibility NameplateFreeCompanyVisibility = NameplateFreeCompanyVisibility.Default;
public NameplateTitleVisibility NameplateTitleVisibility = NameplateTitleVisibility.WhenHasTags;
public NameplateTitlePosition NameplateTitlePosition = NameplateTitlePosition.AlwaysAboveName;
public DeadPlayerHandling NameplateDeadPlayerHandling = DeadPlayerHandling.Include;
public bool IsApplyTagsToAllChatMessagesEnabled = true;
bool hasFixes = false;
// Patch 6.4 - Disable all Job & Role specific colors & prefix
// Not used yet, but keeping it there, just for the case,
//if (config.Version <= 1)
//{
// void fixTags(Dictionary<string, InheritableData> dic)
// {
// foreach (var change in config.AllRoleTagsChanges.ToArray())
// {
// var key = change.Key;
// if (key == nameof(Tag.IsTextVisibleInChat) ||
// key == nameof(Tag.IsTextVisibleInNameplates) ||
// key == nameof(Tag.IsRoleIconVisibleInChat) ||
// key == nameof(Tag.IsRoleIconVisibleInNameplates) ||
// key == nameof(Tag.IsTextColorAppliedToNameplateName) ||
// key == nameof(Tag.IsTextColorAppliedToChatName) ||
// key == nameof(Tag.IsJobIconVisibleInNameplates))
// {
// var data = change.Value;
// data.Behavior = InheritableBehavior.Disabled;
// }
// }
// }
// // "All Roles" tag changes
// fixTags(config.AllRoleTagsChanges);
// // Role tags changes
// foreach (var kvp in config.RoleTagsChanges)
// fixTags(kvp.Value);
// // Job tags changes
// foreach (var kvp in config.JobTagsChanges)
// fixTags(kvp.Value);
// hasFixes = true;
//}
return hasFixes;
}
}
public class GeneralOptionsClass
{
public NameplateFreeCompanyVisibility NameplateFreeCompanyVisibility = NameplateFreeCompanyVisibility.Default;
public NameplateTitleVisibility NameplateTitleVisibility = NameplateTitleVisibility.WhenHasTags;
public NameplateTitlePosition NameplateTitlePosition = NameplateTitlePosition.AlwaysAboveName;
public DeadPlayerHandling NameplateDeadPlayerHandling = DeadPlayerHandling.Include;
public bool IsApplyTagsToAllChatMessagesEnabled = true;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +1,14 @@
using Newtonsoft.Json;
using System;
namespace PlayerTags.Data
namespace PlayerTags.Data;
[Obsolete]
[Flags]
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
public enum ActivityContext
{
[Obsolete]
[Flags]
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
public enum ActivityContext
{
None = 0x0,
PveDuty = 0x1,
PvpDuty = 0x2,
}
None = 0x0,
PveDuty = 0x1,
PvpDuty = 0x2,
}

View File

@@ -1,23 +1,22 @@
using Pilz.Dalamud.ActivityContexts;
namespace PlayerTags.Data
namespace PlayerTags.Data;
public static class ActivityContextHelper
{
public static class ActivityContextHelper
public static bool GetIsVisible(ActivityType playerContext, bool desiredPveDutyVisibility, bool desiredPvpDutyVisibility, bool desiredOthersVisibility)
{
public static bool GetIsVisible(ActivityType playerContext, bool desiredPveDutyVisibility, bool desiredPvpDutyVisibility, bool desiredOthersVisibility)
{
bool isVisible = false;
bool isVisible = false;
if (playerContext.HasFlag(ActivityType.PveDuty))
isVisible |= desiredPveDutyVisibility;
if (playerContext.HasFlag(ActivityType.PveDuty))
isVisible |= desiredPveDutyVisibility;
if (playerContext.HasFlag(ActivityType.PvpDuty))
isVisible |= desiredPvpDutyVisibility;
if (playerContext.HasFlag(ActivityType.PvpDuty))
isVisible |= desiredPvpDutyVisibility;
if (playerContext == ActivityType.None)
isVisible |= desiredOthersVisibility;
if (playerContext == ActivityType.None)
isVisible |= desiredOthersVisibility;
return isVisible;
}
return isVisible;
}
}

View File

@@ -1,15 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PlayerTags.Data;
namespace PlayerTags.Data
public enum DeadPlayerHandling
{
public enum DeadPlayerHandling
{
Ignore,
Include,
GrayOut
}
Ignore,
Include,
GrayOut
}

View File

@@ -5,487 +5,486 @@ using System;
using System.Collections.Generic;
using System.Linq;
namespace PlayerTags.Data
namespace PlayerTags.Data;
public class DefaultPluginData
{
public class DefaultPluginData
public Tag AllTags { get; private set; }
public Tag AllRoleTags { get; private set; }
public Dictionary<Role, Tag> RoleTags { get; private set; }
public Dictionary<DpsRole, Tag> DpsRoleTags { get; private set; }
public Dictionary<RangedDpsRole, Tag> RangedDpsRoleTags { get; private set; }
public Dictionary<LandHandRole, Tag> LandHandRoleTags { get; private set; }
public Dictionary<string, Tag> JobTags { get; private set; }
public Tag AllCustomTags { get; private set; }
public DefaultPluginData(DefaultPluginDataTemplate template)
{
public Tag AllTags { get; private set; }
SetupTemplate(template);
}
public Tag AllRoleTags { get; private set; }
public Dictionary<Role, Tag> RoleTags { get; private set; }
public Dictionary<DpsRole, Tag> DpsRoleTags { get; private set; }
public Dictionary<RangedDpsRole, Tag> RangedDpsRoleTags { get; private set; }
public Dictionary<LandHandRole, Tag> LandHandRoleTags { get; private set; }
public Dictionary<string, Tag> JobTags { get; private set; }
private void SetupTemplate(DefaultPluginDataTemplate template)
{
Clear();
public Tag AllCustomTags { get; private set; }
public DefaultPluginData(DefaultPluginDataTemplate template)
switch (template)
{
SetupTemplate(template);
case DefaultPluginDataTemplate.None:
SetupTemplateNone();
break;
case DefaultPluginDataTemplate.Basic:
SetupTemplateBasic();
break;
case DefaultPluginDataTemplate.Simple:
SetupTemplateSimple();
break;
case DefaultPluginDataTemplate.Full:
SetupTemplateFull();
break;
}
private void SetupTemplate(DefaultPluginDataTemplate template)
SetupJobTags();
}
private void Clear()
{
RoleTags = [];
DpsRoleTags = [];
RangedDpsRoleTags = [];
LandHandRoleTags = [];
}
private void SetupTemplateNone()
{
AllTags = new Tag()
{
Clear();
IsSelected = true,
IsExpanded = true,
};
switch(template)
{
case DefaultPluginDataTemplate.None:
SetupTemplateNone();
break;
case DefaultPluginDataTemplate.Basic:
SetupTemplateBasic();
break;
case DefaultPluginDataTemplate.Simple:
SetupTemplateSimple();
break;
case DefaultPluginDataTemplate.Full:
SetupTemplateFull();
break;
}
SetupJobTags();
}
private void Clear()
AllRoleTags = new Tag()
{
RoleTags = new Dictionary<Role, Tag>();
DpsRoleTags = new Dictionary<DpsRole, Tag>();
RangedDpsRoleTags = new Dictionary<RangedDpsRole, Tag>();
LandHandRoleTags = new Dictionary<LandHandRole, Tag>();
}
IsSelected = false,
IsExpanded = true,
};
private void SetupTemplateNone()
RoleTags[Role.LandHand] = new Tag()
{
AllTags = new Tag()
{
IsSelected = true,
IsExpanded = true,
};
IsSelected = false,
IsExpanded = false,
};
AllRoleTags = new Tag()
{
IsSelected = false,
IsExpanded = true,
};
RoleTags[Role.LandHand] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
RoleTags[Role.Tank] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
RoleTags[Role.Healer] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
RoleTags[Role.Dps] = new Tag()
{
IsSelected = false,
IsExpanded = true,
};
DpsRoleTags[DpsRole.Melee] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
DpsRoleTags[DpsRole.Ranged] = new Tag()
{
IsSelected = false,
IsExpanded = true,
};
RangedDpsRoleTags[RangedDpsRole.Magical] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
RangedDpsRoleTags[RangedDpsRole.Physical] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
LandHandRoleTags[LandHandRole.Land] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
LandHandRoleTags[LandHandRole.Hand] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
AllCustomTags = new Tag()
{
IsSelected = false,
IsExpanded = true,
};
}
private void SetupTemplateBasic()
RoleTags[Role.Tank] = new Tag()
{
AllTags = new Tag()
{
IsSelected = true,
IsExpanded = true,
IsSelected = false,
IsExpanded = false,
};
TagPositionInChat = TagPosition.Before,
InsertBehindNumberPrefixInChat = true,
TagPositionInNameplates = TagPosition.Replace,
TagTargetInNameplates = NameplateElement.Title,
TargetChatTypes = new List<XivChatType>(Enum.GetValues<XivChatType>()),
TargetChatTypesIncludeUndefined = true,
};
AllRoleTags = new Tag()
{
IsSelected = false,
IsExpanded = true,
};
RoleTags[Role.LandHand] = new Tag()
{
IsSelected = false,
IsExpanded = false
};
RoleTags[Role.Tank] = new Tag()
{
IsSelected = false,
IsExpanded = false,
Icon = BitmapFontIcon.Tank,
TextColor = 546,
};
RoleTags[Role.Healer] = new Tag()
{
IsSelected = false,
IsExpanded = false,
Icon = BitmapFontIcon.Healer,
TextColor = 43,
};
RoleTags[Role.Dps] = new Tag()
{
IsSelected = false,
IsExpanded = true,
Icon = BitmapFontIcon.DPS,
TextColor = 508,
};
DpsRoleTags[DpsRole.Melee] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
DpsRoleTags[DpsRole.Ranged] = new Tag()
{
IsSelected = false,
IsExpanded = true,
};
RangedDpsRoleTags[RangedDpsRole.Magical] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
RangedDpsRoleTags[RangedDpsRole.Physical] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
LandHandRoleTags[LandHandRole.Land] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
LandHandRoleTags[LandHandRole.Hand] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
AllCustomTags = new Tag()
{
IsSelected = false,
IsExpanded = true,
IsTextVisibleInChat = true,
IsTextVisibleInNameplates = true,
};
}
private void SetupTemplateSimple()
RoleTags[Role.Healer] = new Tag()
{
AllTags = new Tag()
{
IsSelected = true,
IsExpanded = true,
TagPositionInChat = TagPosition.Before,
InsertBehindNumberPrefixInChat = true,
TagPositionInNameplates = TagPosition.Replace,
TagTargetInNameplates = NameplateElement.Title,
IsTextItalic = true,
IsSelected = false,
IsExpanded = false,
};
IsVisibleInOverworld = true,
IsVisibleInPveDuties = true,
IsVisibleInPvpDuties = true,
IsVisibleForSelf = true,
IsVisibleForFriendPlayers = true,
IsVisibleForPartyPlayers = true,
IsVisibleForAlliancePlayers = true,
IsVisibleForEnemyPlayers = true,
IsVisibleForOtherPlayers = true,
TargetChatTypes = new List<XivChatType>(Enum.GetValues<XivChatType>()),
TargetChatTypesIncludeUndefined = true,
};
AllRoleTags = new Tag()
{
IsSelected = false,
IsExpanded = true,
IsRoleIconVisibleInChat = true,
IsTextVisibleInChat = true,
IsRoleIconVisibleInNameplates = true,
IsTextVisibleInNameplates = true,
IsTextColorAppliedToChatName = true,
};
RoleTags[Role.LandHand] = new Tag()
{
IsSelected = false,
IsExpanded = false,
Icon = BitmapFontIcon.Crafter,
TextColor = 3,
};
RoleTags[Role.Tank] = new Tag()
{
IsSelected = false,
IsExpanded = false,
Icon = BitmapFontIcon.Tank,
TextColor = 546,
};
RoleTags[Role.Healer] = new Tag()
{
IsSelected = false,
IsExpanded = false,
Icon = BitmapFontIcon.Healer,
TextColor = 43,
};
RoleTags[Role.Dps] = new Tag()
{
IsSelected = false,
IsExpanded = true,
Icon = BitmapFontIcon.DPS,
TextColor = 508,
};
DpsRoleTags[DpsRole.Melee] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
DpsRoleTags[DpsRole.Ranged] = new Tag()
{
IsSelected = false,
IsExpanded = true,
};
RangedDpsRoleTags[RangedDpsRole.Magical] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
RangedDpsRoleTags[RangedDpsRole.Physical] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
LandHandRoleTags[LandHandRole.Land] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
LandHandRoleTags[LandHandRole.Hand] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
AllCustomTags = new Tag()
{
IsSelected = false,
IsExpanded = true,
IsTextVisibleInChat = true,
IsTextVisibleInNameplates = true,
};
}
private void SetupTemplateFull()
RoleTags[Role.Dps] = new Tag()
{
AllTags = new Tag()
{
IsSelected = true,
IsExpanded = true,
TagPositionInChat = TagPosition.Before,
InsertBehindNumberPrefixInChat = true,
TagPositionInNameplates = TagPosition.Replace,
TagTargetInNameplates = NameplateElement.Title,
IsTextItalic = true,
IsSelected = false,
IsExpanded = true,
};
IsVisibleInOverworld = true,
IsVisibleInPveDuties = true,
IsVisibleInPvpDuties = true,
IsVisibleForSelf = true,
IsVisibleForFriendPlayers = true,
IsVisibleForPartyPlayers = true,
IsVisibleForAlliancePlayers = true,
IsVisibleForEnemyPlayers = true,
IsVisibleForOtherPlayers = true,
TargetChatTypes = new List<XivChatType>(Enum.GetValues<XivChatType>()),
TargetChatTypesIncludeUndefined = true,
};
AllRoleTags = new Tag()
{
IsSelected = false,
IsExpanded = true,
IsRoleIconVisibleInChat = true,
IsTextVisibleInChat = true,
IsRoleIconVisibleInNameplates = true,
IsTextVisibleInNameplates = true,
IsTextColorAppliedToNameplateName = true,
IsTextColorAppliedToChatName = true,
IsJobIconVisibleInNameplates = true,
};
RoleTags[Role.LandHand] = new Tag()
{
IsSelected = false,
IsExpanded = false,
Icon = BitmapFontIcon.Crafter,
TextColor = 3,
};
RoleTags[Role.Tank] = new Tag()
{
IsSelected = false,
IsExpanded = false,
Icon = BitmapFontIcon.Tank,
TextColor = 546,
};
RoleTags[Role.Healer] = new Tag()
{
IsSelected = false,
IsExpanded = false,
Icon = BitmapFontIcon.Healer,
TextColor = 43,
};
RoleTags[Role.Dps] = new Tag()
{
IsSelected = false,
IsExpanded = true,
Icon = BitmapFontIcon.DPS,
TextColor = 508,
};
DpsRoleTags[DpsRole.Melee] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
DpsRoleTags[DpsRole.Ranged] = new Tag()
{
IsSelected = false,
IsExpanded = true,
};
RangedDpsRoleTags[RangedDpsRole.Magical] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
RangedDpsRoleTags[RangedDpsRole.Physical] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
LandHandRoleTags[LandHandRole.Land] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
LandHandRoleTags[LandHandRole.Hand] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
AllCustomTags = new Tag()
{
IsSelected = false,
IsExpanded = true,
IsTextVisibleInChat = true,
IsTextVisibleInNameplates = true,
};
}
private void SetupJobTags()
DpsRoleTags[DpsRole.Melee] = new Tag()
{
JobTags = new Dictionary<string, Tag>();
IsSelected = false,
IsExpanded = false,
};
var classJobs = PluginServices.DataManager.GetExcelSheet<ClassJob>();
if (classJobs != null)
DpsRoleTags[DpsRole.Ranged] = new Tag()
{
IsSelected = false,
IsExpanded = true,
};
RangedDpsRoleTags[RangedDpsRole.Magical] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
RangedDpsRoleTags[RangedDpsRole.Physical] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
LandHandRoleTags[LandHandRole.Land] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
LandHandRoleTags[LandHandRole.Hand] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
AllCustomTags = new Tag()
{
IsSelected = false,
IsExpanded = true,
};
}
private void SetupTemplateBasic()
{
AllTags = new Tag()
{
IsSelected = true,
IsExpanded = true,
TagPositionInChat = TagPosition.Before,
InsertBehindNumberPrefixInChat = true,
TagPositionInNameplates = TagPosition.Replace,
TagTargetInNameplates = NameplateElement.Title,
TargetChatTypes = new List<XivChatType>(Enum.GetValues<XivChatType>()),
TargetChatTypesIncludeUndefined = true,
};
AllRoleTags = new Tag()
{
IsSelected = false,
IsExpanded = true,
};
RoleTags[Role.LandHand] = new Tag()
{
IsSelected = false,
IsExpanded = false
};
RoleTags[Role.Tank] = new Tag()
{
IsSelected = false,
IsExpanded = false,
Icon = BitmapFontIcon.Tank,
TextColor = 546,
};
RoleTags[Role.Healer] = new Tag()
{
IsSelected = false,
IsExpanded = false,
Icon = BitmapFontIcon.Healer,
TextColor = 43,
};
RoleTags[Role.Dps] = new Tag()
{
IsSelected = false,
IsExpanded = true,
Icon = BitmapFontIcon.DPS,
TextColor = 508,
};
DpsRoleTags[DpsRole.Melee] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
DpsRoleTags[DpsRole.Ranged] = new Tag()
{
IsSelected = false,
IsExpanded = true,
};
RangedDpsRoleTags[RangedDpsRole.Magical] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
RangedDpsRoleTags[RangedDpsRole.Physical] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
LandHandRoleTags[LandHandRole.Land] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
LandHandRoleTags[LandHandRole.Hand] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
AllCustomTags = new Tag()
{
IsSelected = false,
IsExpanded = true,
IsTextVisibleInChat = true,
IsTextVisibleInNameplates = true,
};
}
private void SetupTemplateSimple()
{
AllTags = new Tag()
{
IsSelected = true,
IsExpanded = true,
TagPositionInChat = TagPosition.Before,
InsertBehindNumberPrefixInChat = true,
TagPositionInNameplates = TagPosition.Replace,
TagTargetInNameplates = NameplateElement.Title,
IsTextItalic = true,
IsVisibleInOverworld = true,
IsVisibleInPveDuties = true,
IsVisibleInPvpDuties = true,
IsVisibleForSelf = true,
IsVisibleForFriendPlayers = true,
IsVisibleForPartyPlayers = true,
IsVisibleForAlliancePlayers = true,
IsVisibleForEnemyPlayers = true,
IsVisibleForOtherPlayers = true,
TargetChatTypes = new List<XivChatType>(Enum.GetValues<XivChatType>()),
TargetChatTypesIncludeUndefined = true,
};
AllRoleTags = new Tag()
{
IsSelected = false,
IsExpanded = true,
IsRoleIconVisibleInChat = true,
IsTextVisibleInChat = true,
IsRoleIconVisibleInNameplates = true,
IsTextVisibleInNameplates = true,
IsTextColorAppliedToChatName = true,
};
RoleTags[Role.LandHand] = new Tag()
{
IsSelected = false,
IsExpanded = false,
Icon = BitmapFontIcon.Crafter,
TextColor = 3,
};
RoleTags[Role.Tank] = new Tag()
{
IsSelected = false,
IsExpanded = false,
Icon = BitmapFontIcon.Tank,
TextColor = 546,
};
RoleTags[Role.Healer] = new Tag()
{
IsSelected = false,
IsExpanded = false,
Icon = BitmapFontIcon.Healer,
TextColor = 43,
};
RoleTags[Role.Dps] = new Tag()
{
IsSelected = false,
IsExpanded = true,
Icon = BitmapFontIcon.DPS,
TextColor = 508,
};
DpsRoleTags[DpsRole.Melee] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
DpsRoleTags[DpsRole.Ranged] = new Tag()
{
IsSelected = false,
IsExpanded = true,
};
RangedDpsRoleTags[RangedDpsRole.Magical] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
RangedDpsRoleTags[RangedDpsRole.Physical] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
LandHandRoleTags[LandHandRole.Land] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
LandHandRoleTags[LandHandRole.Hand] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
AllCustomTags = new Tag()
{
IsSelected = false,
IsExpanded = true,
IsTextVisibleInChat = true,
IsTextVisibleInNameplates = true,
};
}
private void SetupTemplateFull()
{
AllTags = new Tag()
{
IsSelected = true,
IsExpanded = true,
TagPositionInChat = TagPosition.Before,
InsertBehindNumberPrefixInChat = true,
TagPositionInNameplates = TagPosition.Replace,
TagTargetInNameplates = NameplateElement.Title,
IsTextItalic = true,
IsVisibleInOverworld = true,
IsVisibleInPveDuties = true,
IsVisibleInPvpDuties = true,
IsVisibleForSelf = true,
IsVisibleForFriendPlayers = true,
IsVisibleForPartyPlayers = true,
IsVisibleForAlliancePlayers = true,
IsVisibleForEnemyPlayers = true,
IsVisibleForOtherPlayers = true,
TargetChatTypes = new List<XivChatType>(Enum.GetValues<XivChatType>()),
TargetChatTypesIncludeUndefined = true,
};
AllRoleTags = new Tag()
{
IsSelected = false,
IsExpanded = true,
IsRoleIconVisibleInChat = true,
IsTextVisibleInChat = true,
IsRoleIconVisibleInNameplates = true,
IsTextVisibleInNameplates = true,
IsTextColorAppliedToNameplateName = true,
IsTextColorAppliedToChatName = true,
IsJobIconVisibleInNameplates = true,
};
RoleTags[Role.LandHand] = new Tag()
{
IsSelected = false,
IsExpanded = false,
Icon = BitmapFontIcon.Crafter,
TextColor = 3,
};
RoleTags[Role.Tank] = new Tag()
{
IsSelected = false,
IsExpanded = false,
Icon = BitmapFontIcon.Tank,
TextColor = 546,
};
RoleTags[Role.Healer] = new Tag()
{
IsSelected = false,
IsExpanded = false,
Icon = BitmapFontIcon.Healer,
TextColor = 43,
};
RoleTags[Role.Dps] = new Tag()
{
IsSelected = false,
IsExpanded = true,
Icon = BitmapFontIcon.DPS,
TextColor = 508,
};
DpsRoleTags[DpsRole.Melee] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
DpsRoleTags[DpsRole.Ranged] = new Tag()
{
IsSelected = false,
IsExpanded = true,
};
RangedDpsRoleTags[RangedDpsRole.Magical] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
RangedDpsRoleTags[RangedDpsRole.Physical] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
LandHandRoleTags[LandHandRole.Land] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
LandHandRoleTags[LandHandRole.Hand] = new Tag()
{
IsSelected = false,
IsExpanded = false,
};
AllCustomTags = new Tag()
{
IsSelected = false,
IsExpanded = true,
IsTextVisibleInChat = true,
IsTextVisibleInNameplates = true,
};
}
private void SetupJobTags()
{
JobTags = [];
var classJobs = PluginServices.DataManager.GetExcelSheet<ClassJob>();
if (classJobs != null)
{
foreach ((var role, var roleTagChanges) in RoleTags)
{
foreach ((var role, var roleTagChanges) in RoleTags)
foreach (var classJob in classJobs.Where(classJob => RoleHelper.RolesByRoleId[classJob.Role] == role && !string.IsNullOrEmpty(classJob.Abbreviation.RawString)))
{
foreach (var classJob in classJobs.Where(classJob => RoleHelper.RolesByRoleId[classJob.Role] == role && !string.IsNullOrEmpty(classJob.Abbreviation.RawString)))
if (!JobTags.ContainsKey(classJob.Abbreviation.RawString))
{
if (!JobTags.ContainsKey(classJob.Abbreviation.RawString))
JobTags[classJob.Abbreviation.RawString] = new Tag()
{
JobTags[classJob.Abbreviation.RawString] = new Tag()
{
IsSelected = false,
IsExpanded = false,
Text = classJob.Abbreviation.RawString,
};
}
IsSelected = false,
IsExpanded = false,
Text = classJob.Abbreviation.RawString,
};
}
}
}

View File

@@ -1,16 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PlayerTags.Data;
namespace PlayerTags.Data
public enum DefaultPluginDataTemplate
{
public enum DefaultPluginDataTemplate
{
None,
Basic,
Simple,
Full
}
None,
Basic,
Simple,
Full
}

View File

@@ -1,8 +1,7 @@
namespace PlayerTags.Data
namespace PlayerTags.Data;
public enum DpsRole
{
public enum DpsRole
{
Melee,
Ranged
}
Melee,
Ranged
}

View File

@@ -2,100 +2,99 @@
using System;
using System.Collections.Generic;
namespace PlayerTags.Data
namespace PlayerTags.Data;
public class Identity : IComparable<Identity>, IEquatable<Identity>
{
public class Identity : IComparable<Identity>, IEquatable<Identity>
public string Name { get; init; }
public uint? WorldId { get; set; } = null;
public List<Guid> CustomTagIds { get; init; } = [];
[JsonIgnore]
public string? WorldName => WorldHelper.GetWorldName(WorldId);
public Identity(string name)
{
public string Name { get; init; }
public uint? WorldId { get; set; } = null;
public List<Guid> CustomTagIds { get; init; } = new List<Guid>();
Name = name;
}
[JsonIgnore]
public string? WorldName => WorldHelper.GetWorldName(WorldId);
public override string ToString()
{
string str = Name;
public Identity(string name)
if (WorldId != null)
{
Name = name;
str += $"@{WorldName}";
}
public override string ToString()
return str;
}
public int CompareTo(Identity? other)
{
string? otherToString = null;
if (!(other is null))
{
string str = Name;
if (WorldId != null)
{
str += $"@{WorldName}";
}
return str;
otherToString = other.ToString();
}
public int CompareTo(Identity? other)
{
string? otherToString = null;
if (!(other is null))
{
otherToString = other.ToString();
}
return ToString().CompareTo(otherToString);
}
return ToString().CompareTo(otherToString);
public override bool Equals(object? obj)
{
return obj is Identity identity && Equals(identity);
}
public bool Equals(Identity? obj)
{
if (obj is null)
{
return false;
}
public override bool Equals(object? obj)
return this == obj;
}
public static bool operator ==(Identity? first, Identity? second)
{
if (ReferenceEquals(first, second))
{
return obj is Identity identity && Equals(identity);
return true;
}
public bool Equals(Identity? obj)
if (first is null && second is null)
{
if (obj is null)
{
return false;
}
return this == obj;
return true;
}
public static bool operator ==(Identity? first, Identity? second)
if (first is null || second is null)
{
if (ReferenceEquals(first, second))
{
return true;
}
if (first is null && second is null)
{
return true;
}
if (first is null || second is null)
{
return false;
}
bool areNamesEqual = first.Name.ToLower().Trim() == second.Name.ToLower().Trim();
// If one of the worlds are null then it's technically equal as it could be promoted to the identity that does have a world
bool areWorldsEqual = first.WorldId == null || second.WorldId == null || first.WorldId == second.WorldId;
return areNamesEqual && areWorldsEqual;
return false;
}
public static bool operator !=(Identity? first, Identity? second)
bool areNamesEqual = first.Name.ToLower().Trim() == second.Name.ToLower().Trim();
// If one of the worlds are null then it's technically equal as it could be promoted to the identity that does have a world
bool areWorldsEqual = first.WorldId == null || second.WorldId == null || first.WorldId == second.WorldId;
return areNamesEqual && areWorldsEqual;
}
public static bool operator !=(Identity? first, Identity? second)
{
return !(first == second);
}
public override int GetHashCode()
{
var hashCode = Name.GetHashCode();
if (WorldName != null)
{
return !(first == second);
hashCode *= 17 ^ WorldName.GetHashCode();
}
public override int GetHashCode()
{
var hashCode = Name.GetHashCode();
if (WorldName != null)
{
hashCode *= 17 ^ WorldName.GetHashCode();
}
return hashCode;
}
return hashCode;
}
}

View File

@@ -1,14 +1,13 @@
using System;
namespace PlayerTags.Data
{
public class InheritableCategoryAttribute : Attribute
{
public string CategoryId { get; private set; }
namespace PlayerTags.Data;
public InheritableCategoryAttribute(string categoryId)
{
CategoryId = categoryId;
}
public class InheritableCategoryAttribute : Attribute
{
public string CategoryId { get; private set; }
public InheritableCategoryAttribute(string categoryId)
{
CategoryId = categoryId;
}
}

View File

@@ -1,8 +1,7 @@
namespace PlayerTags.Data
namespace PlayerTags.Data;
public enum LandHandRole
{
public enum LandHandRole
{
Land,
Hand
}
Land,
Hand
}

View File

@@ -1,9 +1,8 @@
namespace PlayerTags.Data
namespace PlayerTags.Data;
public enum NameplateElement
{
public enum NameplateElement
{
Name,
Title,
FreeCompany
}
Name,
Title,
FreeCompany,
}

View File

@@ -1,11 +1,10 @@
using Newtonsoft.Json;
namespace PlayerTags.Data
namespace PlayerTags.Data;
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
public enum NameplateFreeCompanyVisibility
{
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
public enum NameplateFreeCompanyVisibility
{
Default,
Never
}
Default,
Never
}

View File

@@ -1,12 +1,11 @@
using Newtonsoft.Json;
namespace PlayerTags.Data
namespace PlayerTags.Data;
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
public enum NameplateTitlePosition
{
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
public enum NameplateTitlePosition
{
Default,
AlwaysAboveName,
AlwaysBelowName
}
Default,
AlwaysAboveName,
AlwaysBelowName
}

View File

@@ -1,13 +1,12 @@
using Newtonsoft.Json;
namespace PlayerTags.Data
namespace PlayerTags.Data;
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
public enum NameplateTitleVisibility
{
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
public enum NameplateTitleVisibility
{
Default,
Always,
Never,
WhenHasTags
}
Default,
Always,
Never,
WhenHasTags
}

View File

@@ -1,12 +1,11 @@
namespace PlayerTags.Data
namespace PlayerTags.Data;
public enum PlayerContext
{
public enum PlayerContext
{
None = 0x0,
Self = 0x1,
Party = 0x2,
Alliance = 0x4,
Enemy = 0x8,
Friend = 0x10
}
None = 0x0,
Self = 0x1,
Party = 0x2,
Alliance = 0x4,
Enemy = 0x8,
Friend = 0x10
}

View File

@@ -1,81 +1,59 @@
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.ClientState.Objects.SubKinds;
namespace PlayerTags.Data
namespace PlayerTags.Data;
public static class PlayerContextHelper
{
public static class PlayerContextHelper
public static PlayerContext GetPlayerContext(IPlayerCharacter playerCharacter)
{
public static PlayerContext GetPlayerContext(PlayerCharacter playerCharacter)
{
PlayerContext playerContext = PlayerContext.None;
var playerContext = PlayerContext.None;
if (PluginServices.ClientState.LocalPlayer == playerCharacter)
{
playerContext |= PlayerContext.Self;
}
if (PluginServices.ClientState.LocalPlayer == playerCharacter)
playerContext |= PlayerContext.Self;
if (playerCharacter.StatusFlags.HasFlag(StatusFlags.Friend))
{
playerContext |= PlayerContext.Friend;
}
if (playerCharacter.StatusFlags.HasFlag(StatusFlags.Friend))
playerContext |= PlayerContext.Friend;
if (playerCharacter.StatusFlags.HasFlag(StatusFlags.PartyMember))
{
playerContext |= PlayerContext.Party;
}
if (playerCharacter.StatusFlags.HasFlag(StatusFlags.PartyMember))
playerContext |= PlayerContext.Party;
if (playerCharacter.StatusFlags.HasFlag(StatusFlags.AllianceMember))
{
playerContext |= PlayerContext.Alliance;
}
if (playerCharacter.StatusFlags.HasFlag(StatusFlags.AllianceMember))
playerContext |= PlayerContext.Alliance;
if (playerCharacter.StatusFlags.HasFlag(StatusFlags.Hostile))
{
playerContext |= PlayerContext.Enemy;
}
if (playerCharacter.StatusFlags.HasFlag(StatusFlags.Hostile))
playerContext |= PlayerContext.Enemy;
return playerContext;
}
return playerContext;
}
public static bool GetIsVisible(PlayerContext playerContext, bool desiredSelfVisibility, bool desiredFriendsVisibility, bool desiredPartyVisibility, bool desiredAllianceVisibility, bool desiredEnemiesVisibility, bool desiredOthersVisibility)
{
if (playerContext.HasFlag(PlayerContext.Self))
{
return desiredSelfVisibility;
}
public static bool GetIsVisible(PlayerContext playerContext, bool desiredSelfVisibility, bool desiredFriendsVisibility, bool desiredPartyVisibility, bool desiredAllianceVisibility, bool desiredEnemiesVisibility, bool desiredOthersVisibility)
{
if (playerContext.HasFlag(PlayerContext.Self))
return desiredSelfVisibility;
bool isVisible = false;
if (playerContext.HasFlag(PlayerContext.Friend))
{
isVisible |= desiredFriendsVisibility;
}
var isVisible = false;
if (playerContext.HasFlag(PlayerContext.Party))
{
isVisible |= desiredPartyVisibility;
}
if (playerContext.HasFlag(PlayerContext.Friend))
isVisible |= desiredFriendsVisibility;
if (!playerContext.HasFlag(PlayerContext.Party) && playerContext.HasFlag(PlayerContext.Alliance))
{
isVisible |= desiredAllianceVisibility;
}
if (playerContext.HasFlag(PlayerContext.Party))
isVisible |= desiredPartyVisibility;
if (playerContext.HasFlag(PlayerContext.Enemy))
{
isVisible |= desiredEnemiesVisibility;
}
if (!playerContext.HasFlag(PlayerContext.Party) && playerContext.HasFlag(PlayerContext.Alliance))
isVisible |= desiredAllianceVisibility;
if (playerContext == PlayerContext.None)
{
isVisible |= desiredOthersVisibility;
}
if (playerContext.HasFlag(PlayerContext.Enemy))
isVisible |= desiredEnemiesVisibility;
return isVisible;
}
if (playerContext == PlayerContext.None)
isVisible |= desiredOthersVisibility;
public static bool GetIsVisible(PlayerCharacter playerCharacter, bool desiredSelfVisibility, bool desiredFriendsVisibility, bool desiredPartyVisibility, bool desiredAllianceVisibility, bool desiredEnemiesVisibility, bool desiredOthersVisibility)
{
return GetIsVisible(GetPlayerContext(playerCharacter), desiredSelfVisibility, desiredFriendsVisibility, desiredPartyVisibility, desiredAllianceVisibility, desiredEnemiesVisibility, desiredOthersVisibility);
}
return isVisible;
}
public static bool GetIsVisible(IPlayerCharacter playerCharacter, bool desiredSelfVisibility, bool desiredFriendsVisibility, bool desiredPartyVisibility, bool desiredAllianceVisibility, bool desiredEnemiesVisibility, bool desiredOthersVisibility)
{
return GetIsVisible(GetPlayerContext(playerCharacter), desiredSelfVisibility, desiredFriendsVisibility, desiredPartyVisibility, desiredAllianceVisibility, desiredEnemiesVisibility, desiredOthersVisibility);
}
}

View File

@@ -1,307 +1,305 @@
using Dalamud.ContextMenu;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Party;
using Dalamud.Game.Gui.ContextMenu;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using Dalamud.Logging;
using PlayerTags.Configuration;
using PlayerTags.PluginStrings;
using System;
using System.Collections.Generic;
using System.Linq;
namespace PlayerTags.Data
namespace PlayerTags.Data;
public class PluginData
{
public class PluginData
public DefaultPluginData Default;
public Tag AllTags;
public Tag AllRoleTags;
public Dictionary<Role, Tag> RoleTags;
public Dictionary<DpsRole, Tag> DpsRoleTags;
public Dictionary<RangedDpsRole, Tag> RangedDpsRoleTags;
public Dictionary<LandHandRole, Tag> LandHandRoleTags;
public Dictionary<string, Tag> JobTags;
public Tag AllCustomTags;
public List<Tag> CustomTags;
public List<Identity> Identities;
private PluginConfiguration pluginConfiguration;
public PluginData(PluginConfiguration pluginConfiguration)
{
public DefaultPluginData Default;
public Tag AllTags;
public Tag AllRoleTags;
public Dictionary<Role, Tag> RoleTags;
public Dictionary<DpsRole, Tag> DpsRoleTags;
public Dictionary<RangedDpsRole, Tag> RangedDpsRoleTags;
public Dictionary<LandHandRole, Tag> LandHandRoleTags;
public Dictionary<string, Tag> JobTags;
public Tag AllCustomTags;
public List<Tag> CustomTags;
public List<Identity> Identities;
this.pluginConfiguration = pluginConfiguration;
ReloadDefault();
}
private PluginConfiguration pluginConfiguration;
public void ReloadDefault()
{
Default = new DefaultPluginData(pluginConfiguration.DefaultPluginDataTemplate);
public PluginData(PluginConfiguration pluginConfiguration)
// Set the default changes and saved changes
AllTags = new Tag(new LocalizedPluginString(nameof(AllTags)), Default.AllTags);
AllTags.SetChanges(pluginConfiguration.AllTagsChanges);
AllRoleTags = new Tag(new LocalizedPluginString(nameof(AllRoleTags)), Default.AllRoleTags);
AllRoleTags.SetChanges(pluginConfiguration.AllRoleTagsChanges);
RoleTags = [];
foreach (var role in Enum.GetValues<Role>())
{
this.pluginConfiguration = pluginConfiguration;
ReloadDefault();
if (Default.RoleTags.TryGetValue(role, out var defaultTag))
{
RoleTags[role] = new Tag(new LocalizedPluginString(Localizer.GetName(role)), defaultTag);
if (pluginConfiguration.RoleTagsChanges.TryGetValue(role, out var savedChanges))
{
RoleTags[role].SetChanges(savedChanges);
}
}
}
public void ReloadDefault()
DpsRoleTags = [];
foreach (var dpsRole in Enum.GetValues<DpsRole>())
{
Default = new DefaultPluginData(pluginConfiguration.DefaultPluginDataTemplate);
// Set the default changes and saved changes
AllTags = new Tag(new LocalizedPluginString(nameof(AllTags)), Default.AllTags);
AllTags.SetChanges(pluginConfiguration.AllTagsChanges);
AllRoleTags = new Tag(new LocalizedPluginString(nameof(AllRoleTags)), Default.AllRoleTags);
AllRoleTags.SetChanges(pluginConfiguration.AllRoleTagsChanges);
RoleTags = new Dictionary<Role, Tag>();
foreach (var role in Enum.GetValues<Role>())
if (Default.DpsRoleTags.TryGetValue(dpsRole, out var defaultTag))
{
if (Default.RoleTags.TryGetValue(role, out var defaultTag))
DpsRoleTags[dpsRole] = new Tag(new LocalizedPluginString(Localizer.GetName(dpsRole)), defaultTag);
if (pluginConfiguration.DpsRoleTagsChanges.TryGetValue(dpsRole, out var savedChanges))
{
RoleTags[role] = new Tag(new LocalizedPluginString(Localizer.GetName(role)), defaultTag);
if (pluginConfiguration.RoleTagsChanges.TryGetValue(role, out var savedChanges))
{
RoleTags[role].SetChanges(savedChanges);
}
DpsRoleTags[dpsRole].SetChanges(savedChanges);
}
}
}
DpsRoleTags = new Dictionary<DpsRole, Tag>();
foreach (var dpsRole in Enum.GetValues<DpsRole>())
RangedDpsRoleTags = [];
foreach (var rangedDpsRole in Enum.GetValues<RangedDpsRole>())
{
if (Default.RangedDpsRoleTags.TryGetValue(rangedDpsRole, out var defaultTag))
{
if (Default.DpsRoleTags.TryGetValue(dpsRole, out var defaultTag))
RangedDpsRoleTags[rangedDpsRole] = new Tag(new LocalizedPluginString(Localizer.GetName(rangedDpsRole)), defaultTag);
if (pluginConfiguration.RangedDpsRoleTagsChanges.TryGetValue(rangedDpsRole, out var savedChanges))
{
DpsRoleTags[dpsRole] = new Tag(new LocalizedPluginString(Localizer.GetName(dpsRole)), defaultTag);
if (pluginConfiguration.DpsRoleTagsChanges.TryGetValue(dpsRole, out var savedChanges))
{
DpsRoleTags[dpsRole].SetChanges(savedChanges);
}
RangedDpsRoleTags[rangedDpsRole].SetChanges(savedChanges);
}
}
}
RangedDpsRoleTags = new Dictionary<RangedDpsRole, Tag>();
foreach (var rangedDpsRole in Enum.GetValues<RangedDpsRole>())
LandHandRoleTags = [];
foreach (var landHandRole in Enum.GetValues<LandHandRole>())
{
if (Default.LandHandRoleTags.TryGetValue(landHandRole, out var defaultChanges))
{
if (Default.RangedDpsRoleTags.TryGetValue(rangedDpsRole, out var defaultTag))
LandHandRoleTags[landHandRole] = new Tag(new LocalizedPluginString(Localizer.GetName(landHandRole)), defaultChanges);
if (pluginConfiguration.LandHandRoleTagsChanges.TryGetValue(landHandRole, out var savedChanges))
{
RangedDpsRoleTags[rangedDpsRole] = new Tag(new LocalizedPluginString(Localizer.GetName(rangedDpsRole)), defaultTag);
if (pluginConfiguration.RangedDpsRoleTagsChanges.TryGetValue(rangedDpsRole, out var savedChanges))
{
RangedDpsRoleTags[rangedDpsRole].SetChanges(savedChanges);
}
LandHandRoleTags[landHandRole].SetChanges(savedChanges);
}
}
}
LandHandRoleTags = new Dictionary<LandHandRole, Tag>();
foreach (var landHandRole in Enum.GetValues<LandHandRole>())
JobTags = [];
foreach ((var jobAbbreviation, var role) in RoleHelper.RolesByJobAbbreviation)
{
if (Default.JobTags.TryGetValue(jobAbbreviation, out var defaultChanges))
{
if (Default.LandHandRoleTags.TryGetValue(landHandRole, out var defaultChanges))
JobTags[jobAbbreviation] = new Tag(new LiteralPluginString(jobAbbreviation), defaultChanges);
if (pluginConfiguration.JobTagsChanges.TryGetValue(jobAbbreviation, out var savedChanges))
{
LandHandRoleTags[landHandRole] = new Tag(new LocalizedPluginString(Localizer.GetName(landHandRole)), defaultChanges);
if (pluginConfiguration.LandHandRoleTagsChanges.TryGetValue(landHandRole, out var savedChanges))
{
LandHandRoleTags[landHandRole].SetChanges(savedChanges);
}
JobTags[jobAbbreviation].SetChanges(savedChanges);
}
}
}
JobTags = new Dictionary<string, Tag>();
foreach ((var jobAbbreviation, var role) in RoleHelper.RolesByJobAbbreviation)
AllCustomTags = new Tag(new LocalizedPluginString(nameof(AllCustomTags)), Default.AllCustomTags);
AllCustomTags.SetChanges(pluginConfiguration.AllCustomTagsChanges);
CustomTags = [];
foreach (var savedChanges in pluginConfiguration.CustomTagsChanges)
{
var tag = new Tag(new LocalizedPluginString(nameof(CustomTags)));
tag.SetChanges(savedChanges);
CustomTags.Add(tag);
}
// Set up the inheritance heirarchy
AllRoleTags.Parent = AllTags;
foreach ((var role, var roleTag) in RoleTags)
{
roleTag.Parent = AllRoleTags;
if (role == Role.Dps)
{
if (Default.JobTags.TryGetValue(jobAbbreviation, out var defaultChanges))
foreach ((var dpsRole, var dpsRoleTag) in DpsRoleTags)
{
JobTags[jobAbbreviation] = new Tag(new LiteralPluginString(jobAbbreviation), defaultChanges);
if (pluginConfiguration.JobTagsChanges.TryGetValue(jobAbbreviation, out var savedChanges))
dpsRoleTag.Parent = roleTag;
if (dpsRole == DpsRole.Ranged)
{
JobTags[jobAbbreviation].SetChanges(savedChanges);
}
}
}
AllCustomTags = new Tag(new LocalizedPluginString(nameof(AllCustomTags)), Default.AllCustomTags);
AllCustomTags.SetChanges(pluginConfiguration.AllCustomTagsChanges);
CustomTags = new List<Tag>();
foreach (var savedChanges in pluginConfiguration.CustomTagsChanges)
{
var tag = new Tag(new LocalizedPluginString(nameof(CustomTags)));
tag.SetChanges(savedChanges);
CustomTags.Add(tag);
}
// Set up the inheritance heirarchy
AllRoleTags.Parent = AllTags;
foreach ((var role, var roleTag) in RoleTags)
{
roleTag.Parent = AllRoleTags;
if (role == Role.Dps)
{
foreach ((var dpsRole, var dpsRoleTag) in DpsRoleTags)
{
dpsRoleTag.Parent = roleTag;
if (dpsRole == DpsRole.Ranged)
foreach ((var rangedDpsRole, var rangedDpsRoleTag) in RangedDpsRoleTags)
{
foreach ((var rangedDpsRole, var rangedDpsRoleTag) in RangedDpsRoleTags)
{
rangedDpsRoleTag.Parent = dpsRoleTag;
}
rangedDpsRoleTag.Parent = dpsRoleTag;
}
}
}
else if (role == Role.LandHand)
}
else if (role == Role.LandHand)
{
foreach ((var landHandRole, var landHandRoleTag) in LandHandRoleTags)
{
foreach ((var landHandRole, var landHandRoleTag) in LandHandRoleTags)
{
landHandRoleTag.Parent = roleTag;
}
landHandRoleTag.Parent = roleTag;
}
}
}
foreach ((var jobAbbreviation, var jobTag) in JobTags)
foreach ((var jobAbbreviation, var jobTag) in JobTags)
{
if (RoleHelper.RolesByJobAbbreviation.TryGetValue(jobAbbreviation, out var role))
{
if (RoleHelper.RolesByJobAbbreviation.TryGetValue(jobAbbreviation, out var role))
if (RoleHelper.DpsRolesByJobAbbreviation.TryGetValue(jobAbbreviation, out var dpsRole))
{
if (RoleHelper.DpsRolesByJobAbbreviation.TryGetValue(jobAbbreviation, out var dpsRole))
if (RoleHelper.RangedDpsRolesByJobAbbreviation.TryGetValue(jobAbbreviation, out var rangedDpsRole))
{
if (RoleHelper.RangedDpsRolesByJobAbbreviation.TryGetValue(jobAbbreviation, out var rangedDpsRole))
{
jobTag.Parent = RangedDpsRoleTags[rangedDpsRole];
}
else
{
jobTag.Parent = DpsRoleTags[dpsRole];
}
}
else if (RoleHelper.LandHandRolesByJobAbbreviation.TryGetValue(jobAbbreviation, out var landHandRole))
{
jobTag.Parent = LandHandRoleTags[landHandRole];
jobTag.Parent = RangedDpsRoleTags[rangedDpsRole];
}
else
{
jobTag.Parent = RoleTags[RoleHelper.RolesByJobAbbreviation[jobAbbreviation]];
jobTag.Parent = DpsRoleTags[dpsRole];
}
}
else if (RoleHelper.LandHandRolesByJobAbbreviation.TryGetValue(jobAbbreviation, out var landHandRole))
{
jobTag.Parent = LandHandRoleTags[landHandRole];
}
else
{
jobTag.Parent = RoleTags[RoleHelper.RolesByJobAbbreviation[jobAbbreviation]];
}
}
}
AllCustomTags.Parent = AllTags;
foreach (var tag in CustomTags)
AllCustomTags.Parent = AllTags;
foreach (var tag in CustomTags)
{
tag.Parent = AllCustomTags;
}
Identities = pluginConfiguration.Identities;
// Migrate old custom tag identity assignments
bool customTagsMigrated = false;
foreach (var customTag in CustomTags)
{
if (customTag.CustomId.Value == Guid.Empty)
{
tag.Parent = AllCustomTags;
customTag.CustomId.Behavior = Inheritables.InheritableBehavior.Enabled;
customTag.CustomId.Value = Guid.NewGuid();
customTagsMigrated = true;
}
Identities = pluginConfiguration.Identities;
// Migrate old custom tag identity assignments
bool customTagsMigrated = false;
foreach (var customTag in CustomTags)
foreach (string identityToAddTo in customTag.IdentitiesToAddTo)
{
if (customTag.CustomId.Value == Guid.Empty)
Identity? identity = Identities.FirstOrDefault(identity => identity.Name.ToLower() == identityToAddTo.ToLower());
if (identity == null)
{
customTag.CustomId.Behavior = Inheritables.InheritableBehavior.Enabled;
customTag.CustomId.Value = Guid.NewGuid();
customTagsMigrated = true;
identity = new Identity(identityToAddTo);
Identities.Add(identity);
}
foreach (string identityToAddTo in customTag.IdentitiesToAddTo)
if (identity != null)
{
Identity? identity = Identities.FirstOrDefault(identity => identity.Name.ToLower() == identityToAddTo.ToLower());
if (identity == null)
{
identity = new Identity(identityToAddTo);
Identities.Add(identity);
}
if (identity != null)
{
identity.CustomTagIds.Add(customTag.CustomId.Value);
customTagsMigrated = true;
}
}
if (customTag.GameObjectNamesToApplyTo.Behavior != Inheritables.InheritableBehavior.Inherit)
{
customTag.GameObjectNamesToApplyTo.Behavior = Inheritables.InheritableBehavior.Inherit;
customTag.GameObjectNamesToApplyTo.Value = "";
identity.CustomTagIds.Add(customTag.CustomId.Value);
customTagsMigrated = true;
}
}
if (customTagsMigrated)
if (customTag.GameObjectNamesToApplyTo.Behavior != Inheritables.InheritableBehavior.Inherit)
{
pluginConfiguration.Save(this);
customTag.GameObjectNamesToApplyTo.Behavior = Inheritables.InheritableBehavior.Inherit;
customTag.GameObjectNamesToApplyTo.Value = "";
customTagsMigrated = true;
}
}
public void AddCustomTagToIdentity(Tag customTag, Identity identity)
if (customTagsMigrated)
{
if (!identity.CustomTagIds.Contains(customTag.CustomId.Value))
{
identity.CustomTagIds.Add(customTag.CustomId.Value);
}
if (!Identities.Contains(identity))
{
Identities.Add(identity);
}
}
public void RemoveCustomTagFromIdentity(Tag customTag, Identity identity)
{
identity.CustomTagIds.Remove(customTag.CustomId.Value);
if (!identity.CustomTagIds.Any())
{
Identities.Remove(identity);
}
}
public void RemoveCustomTagFromIdentities(Tag customTag)
{
foreach (var identity in Identities.ToArray())
{
RemoveCustomTagFromIdentity(customTag, identity);
}
}
public Identity GetIdentity(string name, uint? worldId)
{
foreach (var identity in Identities)
{
if (identity.Name.ToLower().Trim() == name.ToLower().Trim())
{
if (identity.WorldId == null && worldId != null)
{
identity.WorldId = worldId;
pluginConfiguration.Save(this);
}
return identity;
}
}
return new Identity(name)
{
WorldId = worldId
};
}
public Identity? GetIdentity(GameObjectContextMenuOpenArgs contextMenuOpenedArgs)
{
if (string.IsNullOrEmpty(contextMenuOpenedArgs.Text?.TextValue)
|| contextMenuOpenedArgs.ObjectWorld == 0
|| contextMenuOpenedArgs.ObjectWorld == 65535)
{
return null;
}
return GetIdentity(contextMenuOpenedArgs.Text?.TextValue ?? string.Empty, contextMenuOpenedArgs.ObjectWorld);
}
public Identity GetIdentity(PlayerCharacter playerCharacter)
{
return GetIdentity(playerCharacter.Name.TextValue, playerCharacter.HomeWorld.Id);
}
public Identity GetIdentity(PartyMember partyMember)
{
return GetIdentity(partyMember.Name.TextValue, partyMember.World.Id);
}
public Identity GetIdentity(PlayerPayload playerPayload)
{
return GetIdentity(playerPayload.PlayerName, playerPayload.World.RowId);
pluginConfiguration.Save(this);
}
}
public void AddCustomTagToIdentity(Tag customTag, Identity identity)
{
if (!identity.CustomTagIds.Contains(customTag.CustomId.Value))
{
identity.CustomTagIds.Add(customTag.CustomId.Value);
}
if (!Identities.Contains(identity))
{
Identities.Add(identity);
}
}
public void RemoveCustomTagFromIdentity(Tag customTag, Identity identity)
{
identity.CustomTagIds.Remove(customTag.CustomId.Value);
if (!identity.CustomTagIds.Any())
{
Identities.Remove(identity);
}
}
public void RemoveCustomTagFromIdentities(Tag customTag)
{
foreach (var identity in Identities.ToArray())
{
RemoveCustomTagFromIdentity(customTag, identity);
}
}
public Identity GetIdentity(string name, uint? worldId)
{
foreach (var identity in Identities)
{
if (identity.Name.ToLower().Trim() == name.ToLower().Trim())
{
if (identity.WorldId == null && worldId != null)
{
identity.WorldId = worldId;
pluginConfiguration.Save(this);
}
return identity;
}
}
return new Identity(name)
{
WorldId = worldId
};
}
public Identity? GetIdentity(MenuTargetDefault taget)
{
if (string.IsNullOrEmpty(taget.TargetName)
|| taget.TargetHomeWorld.Id == 0
|| taget.TargetHomeWorld.Id == 65535)
{
return null;
}
return GetIdentity(taget.TargetName, taget.TargetHomeWorld.Id);
}
public Identity GetIdentity(IPlayerCharacter playerCharacter)
{
return GetIdentity(playerCharacter.Name.TextValue, playerCharacter.HomeWorld.Id);
}
public Identity GetIdentity(IPartyMember partyMember)
{
return GetIdentity(partyMember.Name.TextValue, partyMember.World.Id);
}
public Identity GetIdentity(PlayerPayload playerPayload)
{
return GetIdentity(playerPayload.PlayerName, playerPayload.World.RowId);
}
}

View File

@@ -1,8 +1,7 @@
namespace PlayerTags.Data
namespace PlayerTags.Data;
public enum RangedDpsRole
{
public enum RangedDpsRole
{
Magical,
Physical,
}
Magical,
Physical,
}

View File

@@ -1,10 +1,9 @@
namespace PlayerTags.Data
namespace PlayerTags.Data;
public enum Role
{
public enum Role
{
LandHand,
Tank,
Healer,
Dps,
}
LandHand,
Tank,
Healer,
Dps,
}

View File

@@ -2,152 +2,151 @@
using System.Collections.Generic;
using System.Linq;
namespace PlayerTags.Data
namespace PlayerTags.Data;
public static class RoleHelper
{
public static class RoleHelper
public static Dictionary<byte, Role> RolesByRoleId { get; } = new Dictionary<byte, Role>()
{
public static Dictionary<byte, Role> RolesByRoleId { get; } = new Dictionary<byte, Role>()
{
{ 0, Role.LandHand },
{ 1, Role.Tank },
{ 2, Role.Dps },
{ 3, Role.Dps },
{ 4, Role.Healer },
};
{ 0, Role.LandHand },
{ 1, Role.Tank },
{ 2, Role.Dps },
{ 3, Role.Dps },
{ 4, Role.Healer },
};
public static Dictionary<byte, DpsRole> DpsRolesByRoleId { get; } = new Dictionary<byte, DpsRole>()
{
{ 2, DpsRole.Melee },
{ 3, DpsRole.Ranged },
};
public static Dictionary<byte, DpsRole> DpsRolesByRoleId { get; } = new Dictionary<byte, DpsRole>()
{
{ 2, DpsRole.Melee },
{ 3, DpsRole.Ranged },
};
public static Dictionary<byte, RangedDpsRole> RangedDpsRolesByPrimaryStat { get; } = new Dictionary<byte, RangedDpsRole>()
{
{ 4, RangedDpsRole.Magical },
{ 2, RangedDpsRole.Physical },
};
public static Dictionary<byte, RangedDpsRole> RangedDpsRolesByPrimaryStat { get; } = new Dictionary<byte, RangedDpsRole>()
{
{ 4, RangedDpsRole.Magical },
{ 2, RangedDpsRole.Physical },
};
private static Dictionary<string, Role>? s_RolesByJobAbbreviation = null;
public static Dictionary<string, Role> RolesByJobAbbreviation
private static Dictionary<string, Role>? s_RolesByJobAbbreviation = null;
public static Dictionary<string, Role> RolesByJobAbbreviation
{
get
{
get
if (s_RolesByJobAbbreviation == null)
{
if (s_RolesByJobAbbreviation == null)
{
s_RolesByJobAbbreviation = new Dictionary<string, Role>();
s_RolesByJobAbbreviation = [];
var classJobs = PluginServices.DataManager.GetExcelSheet<ClassJob>();
if (classJobs != null)
var classJobs = PluginServices.DataManager.GetExcelSheet<ClassJob>();
if (classJobs != null)
{
foreach (var classJob in classJobs.Where(classJob => !string.IsNullOrEmpty(classJob.Abbreviation.RawString)))
{
foreach (var classJob in classJobs.Where(classJob => !string.IsNullOrEmpty(classJob.Abbreviation.RawString)))
if (RolesByRoleId.TryGetValue(classJob.Role, out var role))
{
if (RolesByRoleId.TryGetValue(classJob.Role, out var role))
s_RolesByJobAbbreviation[classJob.Abbreviation] = role;
}
}
}
}
return s_RolesByJobAbbreviation;
}
}
private static Dictionary<string, DpsRole>? s_DpsRolesByJobAbbreviation = null;
public static Dictionary<string, DpsRole> DpsRolesByJobAbbreviation
{
get
{
if (s_DpsRolesByJobAbbreviation == null)
{
s_DpsRolesByJobAbbreviation = [];
var classJobs = PluginServices.DataManager.GetExcelSheet<ClassJob>();
if (classJobs != null)
{
foreach (var classJob in classJobs.Where(classJob => !string.IsNullOrEmpty(classJob.Abbreviation.RawString)))
{
if (DpsRolesByRoleId.TryGetValue(classJob.Role, out var dpsRole))
{
s_DpsRolesByJobAbbreviation[classJob.Abbreviation] = dpsRole;
}
}
}
}
return s_DpsRolesByJobAbbreviation;
}
}
private static Dictionary<string, RangedDpsRole>? s_RangedDpsRolesByJobAbbreviation = null;
public static Dictionary<string, RangedDpsRole> RangedDpsRolesByJobAbbreviation
{
get
{
if (s_RangedDpsRolesByJobAbbreviation == null)
{
s_RangedDpsRolesByJobAbbreviation = [];
var classJobs = PluginServices.DataManager.GetExcelSheet<ClassJob>();
if (classJobs != null)
{
foreach (var classJob in classJobs.Where(classJob => !string.IsNullOrEmpty(classJob.Abbreviation.RawString)))
{
if (DpsRolesByJobAbbreviation.TryGetValue(classJob.Abbreviation, out var dpsRole) && dpsRole == DpsRole.Ranged)
{
if (RangedDpsRolesByPrimaryStat.TryGetValue(classJob.PrimaryStat, out var rangedDPSRole))
{
s_RolesByJobAbbreviation[classJob.Abbreviation] = role;
s_RangedDpsRolesByJobAbbreviation[classJob.Abbreviation] = rangedDPSRole;
}
}
}
}
return s_RolesByJobAbbreviation;
}
return s_RangedDpsRolesByJobAbbreviation;
}
}
private static Dictionary<string, DpsRole>? s_DpsRolesByJobAbbreviation = null;
public static Dictionary<string, DpsRole> DpsRolesByJobAbbreviation
private static Dictionary<string, LandHandRole>? s_LandHandRolesByJobAbbreviation = null;
public static Dictionary<string, LandHandRole> LandHandRolesByJobAbbreviation
{
get
{
get
if (s_LandHandRolesByJobAbbreviation == null)
{
if (s_DpsRolesByJobAbbreviation == null)
s_LandHandRolesByJobAbbreviation = [];
var classJobs = PluginServices.DataManager.GetExcelSheet<ClassJob>();
var gatheringSubCategories = PluginServices.DataManager.GetExcelSheet<GatheringSubCategory>();
if (classJobs != null && gatheringSubCategories != null)
{
s_DpsRolesByJobAbbreviation = new Dictionary<string, DpsRole>();
var gatheringJobAbbreviations = gatheringSubCategories
.Select(gatheringSubCategory => gatheringSubCategory.ClassJob.Value)
.Where(classJob => classJob != null)
.Select(classJob => classJob!.Abbreviation).Distinct();
var classJobs = PluginServices.DataManager.GetExcelSheet<ClassJob>();
if (classJobs != null)
foreach (var classJob in classJobs.Where(classJob => !string.IsNullOrEmpty(classJob.Abbreviation.RawString)))
{
foreach (var classJob in classJobs.Where(classJob => !string.IsNullOrEmpty(classJob.Abbreviation.RawString)))
if (RolesByRoleId.TryGetValue(classJob.Role, out var role))
{
if (DpsRolesByRoleId.TryGetValue(classJob.Role, out var dpsRole))
if (role == Role.LandHand)
{
s_DpsRolesByJobAbbreviation[classJob.Abbreviation] = dpsRole;
}
}
}
}
return s_DpsRolesByJobAbbreviation;
}
}
private static Dictionary<string, RangedDpsRole>? s_RangedDpsRolesByJobAbbreviation = null;
public static Dictionary<string, RangedDpsRole> RangedDpsRolesByJobAbbreviation
{
get
{
if (s_RangedDpsRolesByJobAbbreviation == null)
{
s_RangedDpsRolesByJobAbbreviation = new Dictionary<string, RangedDpsRole>();
var classJobs = PluginServices.DataManager.GetExcelSheet<ClassJob>();
if (classJobs != null)
{
foreach (var classJob in classJobs.Where(classJob => !string.IsNullOrEmpty(classJob.Abbreviation.RawString)))
{
if (DpsRolesByJobAbbreviation.TryGetValue(classJob.Abbreviation, out var dpsRole) && dpsRole == DpsRole.Ranged)
{
if (RangedDpsRolesByPrimaryStat.TryGetValue(classJob.PrimaryStat, out var rangedDPSRole))
if (gatheringJobAbbreviations.Contains(classJob.Abbreviation))
{
s_RangedDpsRolesByJobAbbreviation[classJob.Abbreviation] = rangedDPSRole;
s_LandHandRolesByJobAbbreviation[classJob.Abbreviation] = LandHandRole.Land;
}
else
{
s_LandHandRolesByJobAbbreviation[classJob.Abbreviation] = LandHandRole.Hand;
}
}
}
}
}
return s_RangedDpsRolesByJobAbbreviation;
}
}
private static Dictionary<string, LandHandRole>? s_LandHandRolesByJobAbbreviation = null;
public static Dictionary<string, LandHandRole> LandHandRolesByJobAbbreviation
{
get
{
if (s_LandHandRolesByJobAbbreviation == null)
{
s_LandHandRolesByJobAbbreviation = new Dictionary<string, LandHandRole>();
var classJobs = PluginServices.DataManager.GetExcelSheet<ClassJob>();
var gatheringSubCategories = PluginServices.DataManager.GetExcelSheet<GatheringSubCategory>();
if (classJobs != null && gatheringSubCategories != null)
{
var gatheringJobAbbreviations = gatheringSubCategories
.Select(gatheringSubCategory => gatheringSubCategory.ClassJob.Value)
.Where(classJob => classJob != null)
.Select(classJob => classJob!.Abbreviation).Distinct();
foreach (var classJob in classJobs.Where(classJob => !string.IsNullOrEmpty(classJob.Abbreviation.RawString)))
{
if (RolesByRoleId.TryGetValue(classJob.Role, out var role))
{
if (role == Role.LandHand)
{
if (gatheringJobAbbreviations.Contains(classJob.Abbreviation))
{
s_LandHandRolesByJobAbbreviation[classJob.Abbreviation] = LandHandRole.Land;
}
else
{
s_LandHandRolesByJobAbbreviation[classJob.Abbreviation] = LandHandRole.Hand;
}
}
}
}
}
}
return s_LandHandRolesByJobAbbreviation;
}
return s_LandHandRolesByJobAbbreviation;
}
}
}

View File

@@ -1,330 +1,320 @@
using Dalamud.Game.Text;
using Dalamud.Game.Text.SeStringHandling;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Pilz.Dalamud.Icons;
using PlayerTags.Inheritables;
using PlayerTags.PluginStrings;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace PlayerTags.Data
namespace PlayerTags.Data;
public class Tag
{
public class Tag
public IPluginString Name { get; init; }
[JsonProperty("Parent")]
private Tag? m_Parent = null;
[JsonIgnore]
public Tag? Parent
{
public IPluginString Name { get; init; }
[JsonProperty("Parent")]
private Tag? m_Parent = null;
[JsonIgnore]
public Tag? Parent
get => m_Parent;
set
{
get => m_Parent;
set
if (m_Parent != value)
{
if (m_Parent != value)
if (m_Parent != null)
{
if (m_Parent != null)
if (m_Parent.Children.Contains(this))
{
if (m_Parent.Children.Contains(this))
{
m_Parent.Children.Remove(this);
}
m_Parent.Children.Remove(this);
}
}
m_Parent = value;
if (m_Parent != null)
m_Parent = value;
if (m_Parent != null)
{
m_Parent.Children.Add(this);
foreach ((var name, IInheritable inheritable) in Inheritables)
{
m_Parent.Children.Add(this);
foreach ((var name, IInheritable inheritable) in Inheritables)
{
inheritable.Parent = m_Parent.Inheritables[name];
}
inheritable.Parent = m_Parent.Inheritables[name];
}
}
}
}
}
public List<Tag> Children { get; } = new List<Tag>();
public List<Tag> Children { get; } = [];
[JsonIgnore]
public IEnumerable<Tag> Descendents
[JsonIgnore]
public IEnumerable<Tag> Descendents
{
get
{
get
IEnumerable<Tag> descendents = Children.Prepend(this);
foreach (var child in Children)
{
IEnumerable<Tag> descendents = Children.Prepend(this);
foreach (var child in Children)
{
descendents = descendents.Union(child.Descendents);
}
return descendents.Distinct();
descendents = descendents.Union(child.Descendents);
}
return descendents.Distinct();
}
}
[JsonIgnore]
private Dictionary<string, IInheritable>? m_Inheritables = null;
[JsonIgnore]
public Dictionary<string, IInheritable> Inheritables
[JsonIgnore]
private Dictionary<string, IInheritable>? m_Inheritables = null;
[JsonIgnore]
public Dictionary<string, IInheritable> Inheritables
{
get
{
get
if (m_Inheritables == null)
{
if (m_Inheritables == null)
{
m_Inheritables = new Dictionary<string, IInheritable>();
m_Inheritables = [];
var inheritableFields = GetType().GetFields().Where(field => typeof(IInheritable).IsAssignableFrom(field.FieldType));
foreach (var inheritableField in inheritableFields)
var inheritableFields = GetType().GetFields().Where(field => typeof(IInheritable).IsAssignableFrom(field.FieldType));
foreach (var inheritableField in inheritableFields)
{
IInheritable? inheritable = inheritableField.GetValue(this) as IInheritable;
if (inheritable != null)
{
IInheritable? inheritable = inheritableField.GetValue(this) as IInheritable;
if (inheritable != null)
{
Inheritables[inheritableField.Name] = inheritable;
}
Inheritables[inheritableField.Name] = inheritable;
}
}
return m_Inheritables!;
}
return m_Inheritables!;
}
}
public InheritableValue<bool> IsSelected = new InheritableValue<bool>(false)
public InheritableValue<bool> IsSelected = new(false)
{
Behavior = InheritableBehavior.Enabled
};
public InheritableValue<bool> IsExpanded = new(false)
{
Behavior = InheritableBehavior.Enabled
};
// Deprecated
public InheritableReference<string> GameObjectNamesToApplyTo = new("");
public InheritableValue<Guid> CustomId = new(Guid.Empty);
[JsonProperty, Obsolete]
private InheritableValue<bool> IsIconVisibleInChat
{
set => IsRoleIconVisibleInChat = value;
}
[JsonProperty, Obsolete]
private InheritableValue<bool> IsIconVisibleInNameplate
{
set => IsRoleIconVisibleInNameplates = value;
}
[InheritableCategory("IconCategory")]
public InheritableValue<BitmapFontIcon> Icon = new(BitmapFontIcon.Aethernet);
[InheritableCategory("IconCategory")]
public InheritableValue<bool> IsRoleIconVisibleInChat = new(false);
[InheritableCategory("IconCategory")]
public InheritableValue<bool> IsRoleIconVisibleInNameplates = new(false);
[InheritableCategory("IconCategory")]
public InheritableValue<bool> IsJobIconVisibleInNameplates = new(false);
[InheritableCategory("IconCategory")]
public InheritableValue<JobIconSetName> JobIconSet = new(JobIconSetName.Framed);
[InheritableCategory("TextCategory")]
public InheritableReference<string> Text = new("");
[InheritableCategory("TextCategory")]
public InheritableValue<ushort> TextColor = new(6);
[InheritableCategory("TextCategory")]
public InheritableValue<ushort> TextGlowColor = new(6);
[InheritableCategory("TextCategory")]
public InheritableValue<bool> IsTextItalic = new(false);
[InheritableCategory("TextCategory")]
public InheritableValue<bool> IsTextVisibleInChat = new(false);
[InheritableCategory("TextCategory")]
public InheritableValue<bool> IsTextVisibleInNameplates = new(false);
[InheritableCategory("TextCategory")]
public InheritableValue<bool> IsTextColorAppliedToChatName = new(false);
[InheritableCategory("TextCategory")]
public InheritableValue<bool> IsTextColorAppliedToNameplateName = new(false);
[InheritableCategory("TextCategory")]
public InheritableValue<bool> IsTextColorAppliedToNameplateTitle = new(false);
[InheritableCategory("TextCategory")]
public InheritableValue<bool> IsTextColorAppliedToNameplateFreeCompany = new(false);
[InheritableCategory("PositionCategory")]
public InheritableValue<TagPosition> TagPositionInChat = new(TagPosition.Before);
[InheritableCategory("PositionCategory")]
public InheritableValue<bool> InsertBehindNumberPrefixInChat = new(true);
[InheritableCategory("PositionCategory")]
public InheritableValue<TagPosition> TagPositionInNameplates = new(TagPosition.Before);
[InheritableCategory("PositionCategory")]
public InheritableValue<NameplateElement> TagTargetInNameplates = new(NameplateElement.Name);
[InheritableCategory("ActivityCategory")]
public InheritableValue<bool> IsVisibleInPveDuties = new(false);
[InheritableCategory("ActivityCategory")]
public InheritableValue<bool> IsVisibleInPvpDuties = new(false);
[InheritableCategory("ActivityCategory")]
public InheritableValue<bool> IsVisibleInOverworld = new(false);
[InheritableCategory("PlayerCategory")]
public InheritableValue<bool> IsVisibleForSelf = new(false);
[InheritableCategory("PlayerCategory")]
public InheritableValue<bool> IsVisibleForFriendPlayers = new(false);
[InheritableCategory("PlayerCategory")]
public InheritableValue<bool> IsVisibleForPartyPlayers = new(false);
[InheritableCategory("PlayerCategory")]
public InheritableValue<bool> IsVisibleForAlliancePlayers = new(false);
[InheritableCategory("PlayerCategory")]
public InheritableValue<bool> IsVisibleForEnemyPlayers = new(false);
[InheritableCategory("PlayerCategory")]
public InheritableValue<bool> IsVisibleForOtherPlayers = new(false);
[InheritableCategory("ChatFeatureCategory")]
public InheritableReference<List<XivChatType>> TargetChatTypes = new(new List<XivChatType>(Enum.GetValues<XivChatType>()));
[InheritableCategory("ChatFeatureCategory")]
public InheritableValue<bool> TargetChatTypesIncludeUndefined = new(true);
[JsonIgnore]
public string[] IdentitiesToAddTo
{
get
{
Behavior = InheritableBehavior.Enabled
};
public InheritableValue<bool> IsExpanded = new InheritableValue<bool>(false)
{
Behavior = InheritableBehavior.Enabled
};
// Deprecated
public InheritableReference<string> GameObjectNamesToApplyTo = new InheritableReference<string>("");
public InheritableValue<Guid> CustomId = new InheritableValue<Guid>(Guid.Empty);
[JsonProperty, Obsolete]
private InheritableValue<bool> IsIconVisibleInChat
{
set => IsRoleIconVisibleInChat = value;
}
[JsonProperty, Obsolete]
private InheritableValue<bool> IsIconVisibleInNameplate
{
set => IsRoleIconVisibleInNameplates = value;
}
[InheritableCategory("IconCategory")]
public InheritableValue<BitmapFontIcon> Icon = new InheritableValue<BitmapFontIcon>(BitmapFontIcon.Aethernet);
[InheritableCategory("IconCategory")]
public InheritableValue<bool> IsRoleIconVisibleInChat = new InheritableValue<bool>(false);
[InheritableCategory("IconCategory")]
public InheritableValue<bool> IsRoleIconVisibleInNameplates = new InheritableValue<bool>(false);
[InheritableCategory("IconCategory")]
public InheritableValue<bool> IsJobIconVisibleInNameplates = new InheritableValue<bool>(false);
[InheritableCategory("IconCategory")]
public InheritableValue<JobIconSetName> JobIconSet = new InheritableValue<JobIconSetName>(JobIconSetName.Framed);
[InheritableCategory("TextCategory")]
public InheritableReference<string> Text = new InheritableReference<string>("");
[InheritableCategory("TextCategory")]
public InheritableValue<ushort> TextColor = new InheritableValue<ushort>(6);
[InheritableCategory("TextCategory")]
public InheritableValue<ushort> TextGlowColor = new InheritableValue<ushort>(6);
[InheritableCategory("TextCategory")]
public InheritableValue<bool> IsTextItalic = new InheritableValue<bool>(false);
[InheritableCategory("TextCategory")]
public InheritableValue<bool> IsTextVisibleInChat = new InheritableValue<bool>(false);
[InheritableCategory("TextCategory")]
public InheritableValue<bool> IsTextVisibleInNameplates = new InheritableValue<bool>(false);
[InheritableCategory("TextCategory")]
public InheritableValue<bool> IsTextColorAppliedToChatName = new InheritableValue<bool>(false);
[InheritableCategory("TextCategory")]
public InheritableValue<bool> IsTextColorAppliedToNameplateName = new InheritableValue<bool>(false);
[InheritableCategory("TextCategory")]
public InheritableValue<bool> IsTextColorAppliedToNameplateTitle = new InheritableValue<bool>(false);
[InheritableCategory("TextCategory")]
public InheritableValue<bool> IsTextColorAppliedToNameplateFreeCompany = new InheritableValue<bool>(false);
//[InheritableCategory("NameplateCategory")]
//public InheritableValue<NameplateFreeCompanyVisibility> NameplateFreeCompanyVisibility = new InheritableValue<NameplateFreeCompanyVisibility>(Data.NameplateFreeCompanyVisibility.Default);
//[InheritableCategory("NameplateCategory")]
//public InheritableValue<NameplateTitleVisibility> NameplateTitleVisibility = new InheritableValue<NameplateTitleVisibility>(Data.NameplateTitleVisibility.Default);
//[InheritableCategory("NameplateCategory")]
//public InheritableValue<NameplateTitlePosition> NameplateTitlePosition = new InheritableValue<NameplateTitlePosition>(Data.NameplateTitlePosition.Default);
[InheritableCategory("PositionCategory")]
public InheritableValue<TagPosition> TagPositionInChat = new InheritableValue<TagPosition>(TagPosition.Before);
[InheritableCategory("PositionCategory")]
public InheritableValue<bool> InsertBehindNumberPrefixInChat = new InheritableValue<bool>(true);
[InheritableCategory("PositionCategory")]
public InheritableValue<TagPosition> TagPositionInNameplates = new InheritableValue<TagPosition>(TagPosition.Before);
[InheritableCategory("PositionCategory")]
public InheritableValue<NameplateElement> TagTargetInNameplates = new InheritableValue<NameplateElement>(NameplateElement.Name);
[InheritableCategory("ActivityCategory")]
public InheritableValue<bool> IsVisibleInPveDuties = new InheritableValue<bool>(false);
[InheritableCategory("ActivityCategory")]
public InheritableValue<bool> IsVisibleInPvpDuties = new InheritableValue<bool>(false);
[InheritableCategory("ActivityCategory")]
public InheritableValue<bool> IsVisibleInOverworld = new InheritableValue<bool>(false);
[InheritableCategory("PlayerCategory")]
public InheritableValue<bool> IsVisibleForSelf = new InheritableValue<bool>(false);
[InheritableCategory("PlayerCategory")]
public InheritableValue<bool> IsVisibleForFriendPlayers = new InheritableValue<bool>(false);
[InheritableCategory("PlayerCategory")]
public InheritableValue<bool> IsVisibleForPartyPlayers = new InheritableValue<bool>(false);
[InheritableCategory("PlayerCategory")]
public InheritableValue<bool> IsVisibleForAlliancePlayers = new InheritableValue<bool>(false);
[InheritableCategory("PlayerCategory")]
public InheritableValue<bool> IsVisibleForEnemyPlayers = new InheritableValue<bool>(false);
[InheritableCategory("PlayerCategory")]
public InheritableValue<bool> IsVisibleForOtherPlayers = new InheritableValue<bool>(false);
[InheritableCategory("ChatFeatureCategory")]
public InheritableReference<List<XivChatType>> TargetChatTypes = new(new List<XivChatType>(Enum.GetValues<XivChatType>()));
[InheritableCategory("ChatFeatureCategory")]
public InheritableValue<bool> TargetChatTypesIncludeUndefined = new(true);
[JsonIgnore]
public string[] IdentitiesToAddTo
{
get
if (GameObjectNamesToApplyTo == null || GameObjectNamesToApplyTo.InheritedValue == null)
{
if (GameObjectNamesToApplyTo == null || GameObjectNamesToApplyTo.InheritedValue == null)
{
return new string[] { };
}
return GameObjectNamesToApplyTo.InheritedValue.Split(';', ',').Where(item => !string.IsNullOrEmpty(item)).Select(item => item.Trim()).ToArray();
return new string[] { };
}
return GameObjectNamesToApplyTo.InheritedValue.Split(';', ',').Where(item => !string.IsNullOrEmpty(item)).Select(item => item.Trim()).ToArray();
}
}
private Tag? m_Defaults;
[JsonIgnore]
public bool HasDefaults
private Tag? m_Defaults;
[JsonIgnore]
public bool HasDefaults
{
get { return m_Defaults != null; }
}
public Tag()
{
Name = new LiteralPluginString("");
m_Defaults = null;
}
public Tag(IPluginString name)
{
Name = name;
m_Defaults = null;
}
public Tag(IPluginString name, Tag defaults)
{
Name = name;
m_Defaults = defaults;
SetChanges(defaults.GetChanges());
}
public Dictionary<string, InheritableData> GetChanges(Dictionary<string, InheritableData>? defaultChanges = null)
{
Dictionary<string, InheritableData> changes = [];
foreach ((var name, var inheritable) in Inheritables)
{
get { return m_Defaults != null; }
}
public Tag()
{
Name = new LiteralPluginString("");
m_Defaults = null;
}
public Tag(IPluginString name)
{
Name = name;
m_Defaults = null;
}
public Tag(IPluginString name, Tag defaults)
{
Name = name;
m_Defaults = defaults;
SetChanges(defaults.GetChanges());
}
public Dictionary<string, InheritableData> GetChanges(Dictionary<string, InheritableData>? defaultChanges = null)
{
Dictionary<string, InheritableData> changes = new Dictionary<string, InheritableData>();
foreach ((var name, var inheritable) in Inheritables)
// If there's a default for this name, only set the value if it's different from the default
if (defaultChanges != null && defaultChanges.TryGetValue(name, out var defaultInheritableData))
{
// If there's a default for this name, only set the value if it's different from the default
if (defaultChanges != null && defaultChanges.TryGetValue(name, out var defaultInheritableData))
{
var inheritableData = inheritable.GetData();
if (inheritableData.Behavior != defaultInheritableData.Behavior ||
!EqualsInheritableData(inheritableData, defaultInheritableData))
{
changes[name] = inheritable.GetData();
}
}
// If there's no default, then only set the value if it's not inherited
else if (inheritable.Behavior != InheritableBehavior.Inherit)
var inheritableData = inheritable.GetData();
if (inheritableData.Behavior != defaultInheritableData.Behavior ||
!EqualsInheritableData(inheritableData, defaultInheritableData))
{
changes[name] = inheritable.GetData();
}
}
return changes;
}
private static bool EqualsInheritableData(InheritableData data1, InheritableData data2)
{
if (data1.Value is List<XivChatType>)
return EqualsInheritableDataListXivChatType<XivChatType>(data1, data2);
else
return data1.Value.Equals(data2.Value);
}
private static bool EqualsInheritableDataListXivChatType<TEnum>(InheritableData data1, InheritableData data2)
{
var list1 = data1.Value as List<TEnum>;
var list2 = data2.Value as List<TEnum>;
if (list1 is null || list2 is null || list1.Count != list2.Count)
return false;
for (int i = 0; i < list1.Count; i++)
{
if (!list1[i].Equals(list2[i]))
return false;
}
return true;
}
private static readonly Dictionary<string, string> ObsulteInheritableStringMap = new()
{
{ "IsIconVisibleInChat", nameof(IsRoleIconVisibleInChat) },
{ "IsIconVisibleInNameplate", nameof(IsRoleIconVisibleInNameplates) },
{ "IsIconVisibleInNameplates", nameof(IsRoleIconVisibleInNameplates) }
};
private static string FixObsuleteInheritableStringName(string name)
{
if (ObsulteInheritableStringMap.ContainsKey(name))
return ObsulteInheritableStringMap[name];
else
return name;
}
public void SetChanges(IEnumerable<KeyValuePair<string, InheritableData>> changes)
{
foreach ((var name, var inheritableData) in changes)
{
var namefixed = FixObsuleteInheritableStringName(name);
Inheritables[namefixed].SetData(inheritableData);
}
}
private Dictionary<string, InheritableData> GetAllAsChanges()
{
Dictionary<string, InheritableData> changes = new Dictionary<string, InheritableData>();
foreach ((var name, var inheritable) in Inheritables)
// If there's no default, then only set the value if it's not inherited
else if (inheritable.Behavior != InheritableBehavior.Inherit)
{
changes[name] = inheritable.GetData();
}
return changes;
}
public void SetDefaults()
return changes;
}
private static bool EqualsInheritableData(InheritableData data1, InheritableData data2)
{
if (data1.Value is List<XivChatType>)
return EqualsInheritableDataListXivChatType<XivChatType>(data1, data2);
else
return data1.Value.Equals(data2.Value);
}
private static bool EqualsInheritableDataListXivChatType<TEnum>(InheritableData data1, InheritableData data2)
{
var list1 = data1.Value as List<TEnum>;
var list2 = data2.Value as List<TEnum>;
if (list1 is null || list2 is null || list1.Count != list2.Count)
return false;
for (int i = 0; i < list1.Count; i++)
{
if (m_Defaults != null)
{
// Exclude IsSelected and IsExpanded for UX purposes
SetChanges(m_Defaults.GetAllAsChanges().Where(change => change.Key != nameof(IsSelected) && change.Key != nameof(IsExpanded)));
}
if (!list1[i].Equals(list2[i]))
return false;
}
return true;
}
private static readonly Dictionary<string, string> ObsulteInheritableStringMap = new()
{
{ "IsIconVisibleInChat", nameof(IsRoleIconVisibleInChat) },
{ "IsIconVisibleInNameplate", nameof(IsRoleIconVisibleInNameplates) },
{ "IsIconVisibleInNameplates", nameof(IsRoleIconVisibleInNameplates) }
};
private static string FixObsuleteInheritableStringName(string name)
{
if (ObsulteInheritableStringMap.ContainsKey(name))
return ObsulteInheritableStringMap[name];
else
return name;
}
public void SetChanges(IEnumerable<KeyValuePair<string, InheritableData>> changes)
{
foreach ((var name, var inheritableData) in changes)
{
var namefixed = FixObsuleteInheritableStringName(name);
Inheritables[namefixed].SetData(inheritableData);
}
}
private Dictionary<string, InheritableData> GetAllAsChanges()
{
Dictionary<string, InheritableData> changes = [];
foreach ((var name, var inheritable) in Inheritables)
{
changes[name] = inheritable.GetData();
}
return changes;
}
public void SetDefaults()
{
if (m_Defaults != null)
{
// Exclude IsSelected and IsExpanded for UX purposes
SetChanges(m_Defaults.GetAllAsChanges().Where(change => change.Key != nameof(IsSelected) && change.Key != nameof(IsExpanded)));
}
}
}

View File

@@ -1,9 +1,8 @@
namespace PlayerTags.Data
namespace PlayerTags.Data;
public enum TagPosition
{
public enum TagPosition
{
Before,
After,
Replace
}
Before,
After,
Replace
}

View File

@@ -1,8 +1,7 @@
namespace PlayerTags.Data
namespace PlayerTags.Data;
public enum TagTarget
{
public enum TagTarget
{
Chat,
Nameplate
}
Chat,
Nameplate
}

View File

@@ -1,41 +1,40 @@
using Lumina.Excel.GeneratedSheets;
using System.Collections.Generic;
namespace PlayerTags.Data
{
public static class WorldHelper
{
private static Dictionary<uint, string>? s_WorldNames = null;
public static Dictionary<uint, string> WorldNames
{
get
{
if (s_WorldNames == null)
{
s_WorldNames = new Dictionary<uint, string>();
namespace PlayerTags.Data;
var worlds = PluginServices.DataManager.GetExcelSheet<World>();
if (worlds != null)
public static class WorldHelper
{
private static Dictionary<uint, string>? s_WorldNames = null;
public static Dictionary<uint, string> WorldNames
{
get
{
if (s_WorldNames == null)
{
s_WorldNames = [];
var worlds = PluginServices.DataManager.GetExcelSheet<World>();
if (worlds != null)
{
foreach (var world in worlds)
{
foreach (var world in worlds)
{
s_WorldNames[world.RowId] = world.Name;
}
s_WorldNames[world.RowId] = world.Name;
}
}
return s_WorldNames;
}
}
public static string? GetWorldName(uint? worldId)
{
if (worldId != null && WorldNames.TryGetValue(worldId.Value, out var name))
{
return name;
}
return null;
return s_WorldNames;
}
}
public static string? GetWorldName(uint? worldId)
{
if (worldId != null && WorldNames.TryGetValue(worldId.Value, out var name))
{
return name;
}
return null;
}
}

View File

@@ -1,40 +1,35 @@
using Dalamud.Game.Text.SeStringHandling;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PlayerTags
namespace PlayerTags;
internal static class Extensions
{
internal static class Extensions
/// <summary>
/// Removes a Payload from a given SeString.
/// Using <code>SeString.Payloads.Remove()</code> does not use the reference to compare for some reason. Tis is a workaround.
/// </summary>
/// <param name="seString"></param>
/// <param name="payload"></param>
public static void Remove(this SeString seString, Payload payload)
{
/// <summary>
/// Removes a Payload from a given SeString.
/// Using <code>SeString.Payloads.Remove()</code> does not use the reference to compare for some reason. Tis is a workaround.
/// </summary>
/// <param name="seString"></param>
/// <param name="payload"></param>
public static void Remove(this SeString seString, Payload payload)
{
Remove(seString.Payloads, payload);
}
Remove(seString.Payloads, payload);
}
/// <summary>
/// Removes a Payload from a given list.
/// Using <code>List.Remove()</code> does not use the reference to compare for some reason. Tis is a workaround.
/// </summary>
/// <param name="seString"></param>
/// <param name="payload"></param>
public static void Remove(this List<Payload> payloads, Payload payload)
/// <summary>
/// Removes a Payload from a given list.
/// Using <code>List.Remove()</code> does not use the reference to compare for some reason. Tis is a workaround.
/// </summary>
/// <param name="seString"></param>
/// <param name="payload"></param>
public static void Remove(this List<Payload> payloads, Payload payload)
{
for (int i = 0; i < payloads.Count; i++)
{
for (int i = 0; i < payloads.Count; i++)
if (ReferenceEquals(payloads[i], payload))
{
if (ReferenceEquals(payloads[i], payload))
{
payloads.RemoveAt(i);
break;
}
payloads.RemoveAt(i);
break;
}
}
}

View File

@@ -3,435 +3,487 @@ using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.Text;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
using Lumina.Excel.GeneratedSheets;
using Pilz.Dalamud.Tools.Strings;
using PlayerTags.Configuration;
using PlayerTags.Configuration.GameConfig;
using PlayerTags.Data;
using PlayerTags.Inheritables;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using Action = System.Action;
namespace PlayerTags.Features
namespace PlayerTags.Features;
/// <summary>
/// A feature that adds tags to chat messages.
/// </summary>
public class ChatTagTargetFeature : TagTargetFeature
{
/// <summary>
/// A feature that adds tags to chat messages.
/// A match found within a string.
/// </summary>
public class ChatTagTargetFeature : TagTargetFeature
private class StringMatch(SeString seString)
{
/// <summary>
/// A match found within a string.
/// The string that the match was found in.
/// </summary>
private class StringMatch
public SeString SeString { get; init; } = seString;
public List<Payload> DisplayTextPayloads { get; init; } = [];
/// <summary>
/// The matching game object if one exists
/// </summary>
public IGameObject? GameObject { get; init; }
/// <summary>
/// A matching player payload if one exists.
/// </summary>
public PlayerPayload? PlayerPayload { get; init; }
public RawPayload LinkTerminatorPayload { get; init; }
public Payload? PlayerNamePayload
{
/// <summary>
/// The string that the match was found in.
/// </summary>
public SeString SeString { get; init; }
public List<Payload> DisplayTextPayloads { get; init; } = new();
/// <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 RawPayload LinkTerminatorPayload { get; init; }
public Payload? PlayerNamePayload
get
{
get
{
Payload textPayload = null;
string textMatch = GetMatchTextInternal();
string textMatchShort = BuildPlayername(textMatch);
Payload textPayload = null;
string textMatch = GetMatchTextInternal();
string textMatchShort = BuildPlayername(textMatch);
textPayload = DisplayTextPayloads.FirstOrDefault(n => n is TextPayload textPayload && (textPayload.Text.Contains(textMatch) || ((!string.IsNullOrEmpty(textMatchShort)) && textPayload.Text.Contains(textMatchShort))));
textPayload ??= PlayerPayload;
textPayload ??= DisplayTextPayloads.FirstOrDefault();
textPayload = DisplayTextPayloads.FirstOrDefault(n => n is TextPayload textPayload && (textPayload.Text.Contains(textMatch) || ((!string.IsNullOrEmpty(textMatchShort)) && textPayload.Text.Contains(textMatchShort))));
textPayload ??= PlayerPayload;
textPayload ??= DisplayTextPayloads.FirstOrDefault();
return textPayload;
}
}
public bool IsLocalPlayer
{
get
{
return GetMatchTextInternal() == PluginServices.ClientState.LocalPlayer.Name.TextValue;
}
}
public StringMatch(SeString seString)
{
SeString = seString;
}
private string GetMatchTextInternal()
{
if (GameObject != null)
return GameObject.Name.TextValue;
else if (PlayerPayload != null)
return PlayerPayload.PlayerName;
else
return SeString.TextValue;
}
/// <summary>
/// Gets the matches text.
/// </summary>
/// <returns>The match text.</returns>
public string GetMatchText()
{
var playerNamePayload = PlayerNamePayload;
if (playerNamePayload is PlayerPayload pp)
return pp.PlayerName;
else if (playerNamePayload is TextPayload tp)
return tp.Text;
else
return SeString.TextValue;
return textPayload;
}
}
public ChatTagTargetFeature(PluginConfiguration pluginConfiguration, PluginData pluginData) : base(pluginConfiguration, pluginData)
public bool IsLocalPlayer
{
PluginServices.ChatGui.ChatMessage += Chat_ChatMessage;
get => GetMatchTextInternal() == PluginServices.ClientState.LocalPlayer.Name.TextValue;
}
public override void Dispose()
private string GetMatchTextInternal()
{
PluginServices.ChatGui.ChatMessage -= Chat_ChatMessage;
base.Dispose();
}
private void Chat_ChatMessage(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled)
{
if (EnableGlobal && pluginConfiguration.GeneralOptions[ActivityContextManager.CurrentActivityContext.ActivityType].IsApplyTagsToAllChatMessagesEnabled)
{
AddTagsToChat(sender, type, true);
AddTagsToChat(message, type, false);
}
}
protected override bool IsIconVisible(Tag tag)
{
if (tag.IsRoleIconVisibleInChat.InheritedValue != null)
{
return tag.IsRoleIconVisibleInChat.InheritedValue.Value;
}
return false;
}
protected override bool IsTextVisible(Tag tag)
{
if (tag.IsTextVisibleInChat.InheritedValue != null)
{
return tag.IsTextVisibleInChat.InheritedValue.Value;
}
return false;
if (GameObject != null)
return GameObject.Name.TextValue;
else if (PlayerPayload != null)
return PlayerPayload.PlayerName;
else
return SeString.TextValue;
}
/// <summary>
/// Searches the given string for game object matches.
/// Gets the matches text.
/// </summary>
/// <param name="seString">The string to search.</param>
/// <returns>A list of matched game objects.</returns>
private List<StringMatch> GetStringMatches(SeString seString)
/// <returns>The match text.</returns>
public string GetMatchText()
{
List<StringMatch> stringMatches = new();
Stack<PlayerPayload> curPlayerPayload = new();
Stack<List<Payload>> curRefPayloads = new();
var defaultRawPayload = RawPayload.LinkTerminator.Data;
var playerNamePayload = PlayerNamePayload;
if (playerNamePayload is PlayerPayload pp)
return pp.PlayerName;
else if (playerNamePayload is TextPayload tp)
return tp.Text;
else
return SeString.TextValue;
}
}
foreach (var payload in seString.Payloads)
public ChatTagTargetFeature(PluginConfiguration pluginConfiguration, PluginData pluginData) : base(pluginConfiguration, pluginData)
{
PluginServices.ChatGui.ChatMessage += ChatGui_ChatMessage;
}
public override void Dispose()
{
PluginServices.ChatGui.ChatMessage -= ChatGui_ChatMessage;
base.Dispose();
GC.SuppressFinalize(this);
}
private void ChatGui_ChatMessage(XivChatType type, int timestamp, ref SeString sender, ref SeString message, ref bool isHandled)
{
if (EnableGlobal && pluginConfiguration.GeneralOptions[ActivityContextManager.CurrentActivityContext.ActivityType].IsApplyTagsToAllChatMessagesEnabled)
{
AddTagsToChat(sender, type, true);
AddTagsToChat(message, type, false);
}
}
protected override bool IsIconVisible(Tag tag)
{
if (tag.IsRoleIconVisibleInChat.InheritedValue != null)
return tag.IsRoleIconVisibleInChat.InheritedValue.Value;
return false;
}
protected override bool IsTextVisible(Tag tag)
{
if (tag.IsTextVisibleInChat.InheritedValue != null)
{
return tag.IsTextVisibleInChat.InheritedValue.Value;
}
return false;
}
/// <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 = [];
Stack<PlayerPayload> curPlayerPayload = new();
Stack<List<Payload>> curRefPayloads = new();
var defaultRawPayload = RawPayload.LinkTerminator.Data;
foreach (var payload in seString.Payloads)
{
if (payload is PlayerPayload playerPayload)
{
curPlayerPayload.Push(playerPayload);
curRefPayloads.Push([]);
}
else if (payload is RawPayload rawPayload)
{
if (defaultRawPayload.SequenceEqual(rawPayload.Data))
finishCurrentMatch(rawPayload);
}
else
{
if (curRefPayloads.TryPeek(out List<Payload> result))
result.Add(payload);
}
}
// Finally finish, if not closed by RawPayload
finishCurrentMatch(null);
void finishCurrentMatch(RawPayload linkTerminatorPayload)
{
if (curPlayerPayload.TryPop(out PlayerPayload playerPayload))
{
var gameObject = PluginServices.ObjectTable.FirstOrDefault(gameObject => gameObject.Name.TextValue == playerPayload.PlayerName);
var stringMatch = new StringMatch(seString)
{
GameObject = gameObject,
PlayerPayload = playerPayload,
LinkTerminatorPayload = linkTerminatorPayload,
DisplayTextPayloads = curRefPayloads.Pop()
};
stringMatches.Add(stringMatch);
}
}
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)
{
curPlayerPayload.Push(playerPayload);
curRefPayloads.Push(new List<Payload>());
}
else if (payload is RawPayload rawPayload)
{
if (defaultRawPayload.SequenceEqual(rawPayload.Data))
finishCurrentMatch(rawPayload);
}
else
{
if (curRefPayloads.TryPeek(out List<Payload> result))
result.Add(payload);
}
}
// Get position of player name in payload
var indexOfPlayerName = playerNamePayload.Text.IndexOf(BuildPlayername(lastPlayerPayload.PlayerName));
// Finally finish, if not closed by RawPayload
finishCurrentMatch(null);
if (indexOfPlayerName == -1)
indexOfPlayerName = playerNamePayload.Text.IndexOf(lastPlayerPayload.PlayerName);
void finishCurrentMatch(RawPayload linkTerminatorPayload)
{
if (curPlayerPayload.TryPop(out PlayerPayload playerPayload))
{
var gameObject = PluginServices.ObjectTable.FirstOrDefault(gameObject => gameObject.Name.TextValue == playerPayload.PlayerName);
var stringMatch = new StringMatch(seString)
if (indexOfPlayerName > 0)
{
GameObject = gameObject,
PlayerPayload = playerPayload,
LinkTerminatorPayload = linkTerminatorPayload,
DisplayTextPayloads = curRefPayloads.Pop()
};
stringMatches.Add(stringMatch);
}
}
// Split off the name from the prefix number
var prefixPayload = new TextPayload(playerNamePayload.Text[..indexOfPlayerName]);
playerNamePayload.Text = playerNamePayload.Text[indexOfPlayerName..];
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(BuildPlayername(lastPlayerPayload.PlayerName));
if (indexOfPlayerName == -1)
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);
}
// Add prefix number before the player name payload
var playerNamePayloadIndex = sender.Payloads.IndexOf(playerNamePayload);
sender.Payloads.Insert(playerNamePayloadIndex, prefixPayload);
}
}
}
}
private void ParsePayloadsForOwnPlayer(SeString seString, XivChatType chatType, bool isSender)
{
if (PluginServices.ClientState.LocalPlayer != null)
{
foreach (var payload in seString.Payloads.ToArray())
{
if (payload is TextPayload textPayload)
{
List<TextPayload> playerTextPayloads = new List<TextPayload>();
var playerName = PluginServices.ClientState.LocalPlayer.Name.TextValue;
var playerNameShorted = BuildPlayername(playerName);
if (textPayload.Text == playerName || textPayload.Text == playerNameShorted)
{
playerTextPayloads.Add(textPayload);
}
else
{
var usedPlayerName = chatType == XivChatType.Party || chatType == XivChatType.Alliance ? playerNameShorted : playerName;
var textMatchIndex = textPayload.Text.IndexOf(usedPlayerName);
while (textMatchIndex >= 0)
{
var textPayloadIndex = seString.Payloads.IndexOf(payload);
// Chop text to the left and insert it as a new payload
if (textMatchIndex > 0)
{
// Add the content before the player
seString.Payloads.Insert(textPayloadIndex++, new TextPayload(textPayload.Text.Substring(0, textMatchIndex)));
// Remove from the chopped text from the original payload
textPayload.Text = textPayload.Text.Substring(textMatchIndex, textPayload.Text.Length - textMatchIndex);
}
// This is the last reference to the local player in this payload
if (textPayload.Text.Length == usedPlayerName.Length)
{
playerTextPayloads.Add(textPayload);
break;
}
// Create the new name payload and add it
var playerTextPayload = new TextPayload(usedPlayerName);
playerTextPayloads.Add(playerTextPayload);
seString.Payloads.Insert(textPayloadIndex, playerTextPayload);
// Remove from the chopped text from the original payload
textPayload.Text = textPayload.Text.Substring(usedPlayerName.Length);
textMatchIndex = textPayload.Text.IndexOf(usedPlayerName);
}
}
foreach (var playerTextPayload in playerTextPayloads)
{
// Fix displaying of abbreviated own player name as the game does this after the chat message handler
playerTextPayload.Text = BuildPlayername(playerTextPayload.Text);
var playerPayload = new PlayerPayload(playerName, PluginServices.ClientState.LocalPlayer.HomeWorld.Id);
int playerPayloadIndex = seString.Payloads.IndexOf(playerTextPayload);
var hasNumberPrefix = isSender && (chatType == XivChatType.Party || chatType == XivChatType.Alliance);
// Ensure to include the group number prefix within the player link
if (hasNumberPrefix)
playerPayloadIndex--;
// Add the Player Link Payload
seString.Payloads.Insert(playerPayloadIndex++, playerPayload);
// Same as above, but reverse
if (hasNumberPrefix)
playerPayloadIndex++;
// Add the Link Terminator to end the Player Link. This should be done behind the Text Payload (display text).
// Normally used to end PlayerPayload linking. But for the own player it has no affect. Anyway, use it, just because. Maybe it's needed in the future somewhere else.
seString.Payloads.Insert(++playerPayloadIndex, RawPayload.LinkTerminator);
// I M P O R T A N T N O T I C E:
// The PlayerPayload is now just temporary. We keep the TextPayload.
// The PayerPayload gets removed at the ChatTagTargetFeature at the end and the TextPayload will be keeped there.
}
}
}
}
}
/// <summary>
/// Adds all configured tags to a chat message.
/// </summary>
/// <param name="message">The message to change.</param>
private void AddTagsToChat(SeString message, XivChatType chatType, bool isSender)
{
// Parse Payloads for local player to be able to work with in the following code
ParsePayloadsForOwnPlayer(message, chatType, isSender);
// Split out the party/alliance number from the PlayerPayload
if (isSender)
SplitOffPartyNumberPrefix(message, chatType);
var stringMatches = GetStringMatches(message);
foreach (var stringMatch in stringMatches)
{
StringChanges stringChanges = new();
bool isTagEnabled(Tag tag)
=> tag.TagPositionInChat.InheritedValue != null && tag.TargetChatTypes.InheritedValue != null &&
(tag.TargetChatTypes.InheritedValue.Contains(chatType) || (!Enum.IsDefined(chatType) && (tag.TargetChatTypesIncludeUndefined?.InheritedValue ?? false)));
if (stringMatch.GameObject is PlayerCharacter playerCharacter)
{
// Add the job tag
if (playerCharacter.ClassJob.GameData != null && pluginData.JobTags.TryGetValue(playerCharacter.ClassJob.GameData.Abbreviation, out var jobTag))
{
if (isTagEnabled(jobTag))
{
var payloads = GetPayloads(jobTag, stringMatch.GameObject);
if (payloads.Any())
{
var insertBehindNumberPrefix = jobTag.InsertBehindNumberPrefixInChat?.Value ?? true;
addPayloadChanges(jobTag, payloads);
}
}
}
// Add randomly generated name tag payload
if (pluginConfiguration.IsPlayerNameRandomlyGenerated)
{
var playerName = stringMatch.GetMatchText();
if (playerName != null)
{
var generatedName = BuildPlayername(RandomNameGenerator.Generate(playerName));
if (generatedName != null)
{
AddPayloadChanges(StringPosition.Replace, Enumerable.Empty<Payload>().Append(new TextPayload(generatedName)), stringChanges, false);
}
}
}
}
// Add custom tags
if (stringMatch.PlayerPayload != null)
{
Identity identity = pluginData.GetIdentity(stringMatch.PlayerPayload);
foreach (var customTagId in identity.CustomTagIds)
{
var customTag = pluginData.CustomTags.FirstOrDefault(tag => tag.CustomId.Value == customTagId);
if (customTag != null)
{
if (isTagEnabled(customTag))
{
var customTagPayloads = GetPayloads(customTag, stringMatch.GameObject);
if (customTagPayloads.Any())
{
var insertBehindNumberPrefix = customTag.InsertBehindNumberPrefixInChat?.Value ?? true;
addPayloadChanges(customTag, customTagPayloads);
}
}
}
}
}
void addPayloadChanges(Tag tag, IEnumerable<Payload> payloads)
{
var insertBehindNumberPrefix = tag.InsertBehindNumberPrefixInChat?.Value ?? true;
var insertPositionInChat = tag.TagPositionInChat.InheritedValue.Value;
AddPayloadChanges((StringPosition)insertPositionInChat, payloads, stringChanges, insertBehindNumberPrefix);
}
// An additional step to apply text color to additional locations
if (stringMatch.PlayerPayload != null && stringMatch.DisplayTextPayloads.Any())
{
Identity identity = pluginData.GetIdentity(stringMatch.PlayerPayload);
if (stringMatch.GameObject is PlayerCharacter playerCharacter1)
{
if (playerCharacter1.ClassJob.GameData != null && pluginData.JobTags.TryGetValue(playerCharacter1.ClassJob.GameData.Abbreviation, out var jobTag) && isTagEnabled(jobTag))
applyTextFormatting(jobTag);
}
foreach (var customTagId in identity.CustomTagIds)
{
var customTag = pluginData.CustomTags.FirstOrDefault(tag => tag.CustomId.Value == customTagId);
if (customTag != null && isTagEnabled(customTag))
applyTextFormatting(customTag);
}
void applyTextFormatting(Tag tag)
=> ApplyTextFormatting(stringMatch.GameObject, tag, new[] { message }, new[] { tag.IsTextColorAppliedToChatName }, stringMatch.DisplayTextPayloads);
}
// Finally apply the all the changes to the message
ApplyStringChanges(message, stringChanges, stringMatch.DisplayTextPayloads, stringMatch.PlayerNamePayload);
// Remove PlayerPayload and LinkTerminator if it's your own character (they just got added temporary)
if (stringMatch.IsLocalPlayer)
{
if (stringMatch.PlayerPayload != null)
message.Remove(stringMatch.PlayerPayload);
if (stringMatch.LinkTerminatorPayload != null)
message.Remove(stringMatch.LinkTerminatorPayload);
}
}
}
}
private void ParsePayloadsForOwnPlayer(SeString seString, XivChatType chatType, bool isSender)
{
if (PluginServices.ClientState.LocalPlayer != null)
{
foreach (var payload in seString.Payloads.ToArray())
{
if (payload is TextPayload textPayload)
{
List<TextPayload> playerTextPayloads = [];
var playerName = PluginServices.ClientState.LocalPlayer.Name.TextValue;
var playerNameShorted = BuildPlayername(playerName);
if (textPayload.Text == playerName || textPayload.Text == playerNameShorted)
{
playerTextPayloads.Add(textPayload);
}
else
{
var usedPlayerName = chatType == XivChatType.Party || chatType == XivChatType.Alliance ? playerNameShorted : playerName;
var textMatchIndex = textPayload.Text.IndexOf(usedPlayerName);
while (textMatchIndex >= 0)
{
var textPayloadIndex = seString.Payloads.IndexOf(payload);
// Chop text to the left and insert it as a new payload
if (textMatchIndex > 0)
{
// Add the content before the player
seString.Payloads.Insert(textPayloadIndex++, new TextPayload(textPayload.Text.Substring(0, textMatchIndex)));
// Remove from the chopped text from the original payload
textPayload.Text = textPayload.Text.Substring(textMatchIndex, textPayload.Text.Length - textMatchIndex);
}
// This is the last reference to the local player in this payload
if (textPayload.Text.Length == usedPlayerName.Length)
{
playerTextPayloads.Add(textPayload);
break;
}
// Create the new name payload and add it
var playerTextPayload = new TextPayload(usedPlayerName);
playerTextPayloads.Add(playerTextPayload);
seString.Payloads.Insert(textPayloadIndex, playerTextPayload);
// Remove from the chopped text from the original payload
textPayload.Text = textPayload.Text.Substring(usedPlayerName.Length);
textMatchIndex = textPayload.Text.IndexOf(usedPlayerName);
}
}
foreach (var playerTextPayload in playerTextPayloads)
{
// Fix displaying of abbreviated own player name as the game does this after the chat message handler
playerTextPayload.Text = BuildPlayername(playerTextPayload.Text);
var playerPayload = new PlayerPayload(playerName, PluginServices.ClientState.LocalPlayer.HomeWorld.Id);
int playerPayloadIndex = seString.Payloads.IndexOf(playerTextPayload);
var hasNumberPrefix = isSender && (chatType == XivChatType.Party || chatType == XivChatType.Alliance);
// Ensure to include the group number prefix within the player link
if (hasNumberPrefix)
playerPayloadIndex--;
// Add the Player Link Payload
seString.Payloads.Insert(playerPayloadIndex++, playerPayload);
// Same as above, but reverse
if (hasNumberPrefix)
playerPayloadIndex++;
// Add the Link Terminator to end the Player Link. This should be done behind the Text Payload (display text).
// Normally used to end PlayerPayload linking. But for the own player it has no affect. Anyway, use it, just because. Maybe it's needed in the future somewhere else.
seString.Payloads.Insert(++playerPayloadIndex, RawPayload.LinkTerminator);
// I M P O R T A N T N O T I C E:
// The PlayerPayload is now just temporary. We keep the TextPayload.
// The PayerPayload gets removed at the ChatTagTargetFeature at the end and the TextPayload will be keeped there.
}
}
}
}
}
/// <summary>
/// Adds all configured tags to a chat message.
/// </summary>
/// <param name="message">The message to change.</param>
private void AddTagsToChat(SeString message, XivChatType chatType, bool isSender)
{
// Parse Payloads for local player to be able to work with in the following code
ParsePayloadsForOwnPlayer(message, chatType, isSender);
// Split out the party/alliance number from the PlayerPayload
if (isSender)
SplitOffPartyNumberPrefix(message, chatType);
var stringMatches = GetStringMatches(message);
foreach (var stringMatch in stringMatches)
{
StringChanges stringChanges = new();
bool isTagEnabled(Tag tag)
=> tag.TagPositionInChat.InheritedValue != null && tag.TargetChatTypes.InheritedValue != null &&
(tag.TargetChatTypes.InheritedValue.Contains(chatType) || (!Enum.IsDefined(chatType) && (tag.TargetChatTypesIncludeUndefined?.InheritedValue ?? false)));
if (stringMatch.GameObject is IPlayerCharacter playerCharacter)
{
// Add the job tag
if (playerCharacter.ClassJob.GameData != null && pluginData.JobTags.TryGetValue(playerCharacter.ClassJob.GameData.Abbreviation, out var jobTag))
{
if (isTagEnabled(jobTag))
{
var payloads = GetPayloads(jobTag, playerCharacter);
if (payloads.Any())
{
var insertBehindNumberPrefix = jobTag.InsertBehindNumberPrefixInChat?.Value ?? true;
addPayloadChanges(jobTag, payloads);
}
}
}
// Add randomly generated name tag payload
if (pluginConfiguration.IsPlayerNameRandomlyGenerated)
{
var playerName = stringMatch.GetMatchText();
if (playerName != null)
{
var generatedName = BuildPlayername(RandomNameGenerator.Generate(playerName));
if (generatedName != null)
{
AddPayloadChanges(StringPosition.Replace, Enumerable.Empty<Payload>().Append(new TextPayload(generatedName)), stringChanges, false);
}
}
}
}
// Add custom tags
if (stringMatch.PlayerPayload != null)
{
Identity identity = pluginData.GetIdentity(stringMatch.PlayerPayload);
foreach (var customTagId in identity.CustomTagIds)
{
var customTag = pluginData.CustomTags.FirstOrDefault(tag => tag.CustomId.Value == customTagId);
if (customTag != null)
{
if (isTagEnabled(customTag))
{
var customTagPayloads = GetPayloads(customTag, stringMatch.GameObject);
if (customTagPayloads.Any())
{
var insertBehindNumberPrefix = customTag.InsertBehindNumberPrefixInChat?.Value ?? true;
addPayloadChanges(customTag, customTagPayloads);
}
}
}
}
}
void addPayloadChanges(Tag tag, IEnumerable<Payload> payloads)
{
var insertBehindNumberPrefix = tag.InsertBehindNumberPrefixInChat?.Value ?? true;
var insertPositionInChat = tag.TagPositionInChat.InheritedValue.Value;
AddPayloadChanges((StringPosition)insertPositionInChat, payloads, stringChanges, insertBehindNumberPrefix);
}
// An additional step to apply text color to additional locations
if (stringMatch.PlayerPayload != null && stringMatch.DisplayTextPayloads.Any())
{
Identity identity = pluginData.GetIdentity(stringMatch.PlayerPayload);
if (stringMatch.GameObject is IPlayerCharacter playerCharacter1)
{
if (playerCharacter1.ClassJob.GameData != null && pluginData.JobTags.TryGetValue(playerCharacter1.ClassJob.GameData.Abbreviation, out var jobTag) && isTagEnabled(jobTag))
applyTextFormatting(jobTag);
}
foreach (var customTagId in identity.CustomTagIds)
{
var customTag = pluginData.CustomTags.FirstOrDefault(tag => tag.CustomId.Value == customTagId);
if (customTag != null && isTagEnabled(customTag))
applyTextFormatting(customTag);
}
void applyTextFormatting(Tag tag)
=> ApplyTextFormatting(stringMatch.GameObject, tag, new[] { message }, new[] { tag.IsTextColorAppliedToChatName }, stringMatch.DisplayTextPayloads);
}
// Finally apply the all the changes to the message
ApplyStringChanges(message, stringChanges, stringMatch.DisplayTextPayloads, stringMatch.PlayerNamePayload);
// Remove PlayerPayload and LinkTerminator if it's your own character (they just got added temporary)
if (stringMatch.IsLocalPlayer)
{
if (stringMatch.PlayerPayload != null)
message.Remove(stringMatch.PlayerPayload);
if (stringMatch.LinkTerminatorPayload != null)
message.Remove(stringMatch.LinkTerminatorPayload);
}
}
}
private void ApplyTextFormatting(IGameObject? gameObject, Tag tag, SeString[] destStrings, InheritableValue<bool>[] textColorApplied, List<Payload> preferedPayloads, ushort? overwriteTextColor = null)
{
if (IsTagVisible(tag, gameObject))
{
for (int i = 0; i < destStrings.Length; i++)
{
var destString = destStrings[i];
var isTextColorApplied = textColorApplied[i];
applyTextColor(destString, isTextColorApplied, tag.TextColor);
}
}
void applyTextColor(SeString destPayload, InheritableValue<bool> enableFlag, InheritableValue<ushort> colorValue)
{
var colorToUse = overwriteTextColor ?? colorValue?.InheritedValue;
if (shouldApplyFormattingPayloads(destPayload)
&& enableFlag.InheritedValue != null
&& enableFlag.InheritedValue.Value
&& colorToUse != null)
applyTextFormattingPayloads(destPayload, new UIForegroundPayload(colorToUse.Value), new UIForegroundPayload(0));
}
bool shouldApplyFormattingPayloads(SeString destPayload)
=> destPayload.Payloads.Any(payload => payload is TextPayload || payload is PlayerPayload);
void applyTextFormattingPayloads(SeString destPayload, Payload startPayload, Payload endPayload)
{
if (preferedPayloads == null || !preferedPayloads.Any())
applyTextFormattingPayloadToStartAndEnd(destPayload, startPayload, endPayload);
else
applyTextFormattingPayloadsToSpecificPosition(destPayload, startPayload, endPayload, preferedPayloads);
}
void applyTextFormattingPayloadToStartAndEnd(SeString destPayload, Payload startPayload, Payload endPayload)
{
destPayload.Payloads.Insert(0, startPayload);
destPayload.Payloads.Add(endPayload);
}
void applyTextFormattingPayloadsToSpecificPosition(SeString destPayload, Payload startPayload, Payload endPayload, List<Payload> preferedPayload)
{
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);
}
}
/// <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, StringChanges stringChanges, List<Payload> anchorPayloads = null, Payload anchorReplacePayload = null)
{
var props = new StringChangesProps
{
Destination = seString,
AnchorPayload = anchorReplacePayload,
AnchorPayloads = anchorPayloads,
StringChanges = stringChanges
};
StringUpdateFactory.ApplyStringChanges(props);
}
}

View File

@@ -1,6 +1,4 @@
using Dalamud.ContextMenu;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Logging;
using Dalamud.Game.Gui.ContextMenu;
using PlayerTags.Configuration;
using PlayerTags.Data;
using PlayerTags.Resources;
@@ -8,88 +6,81 @@ using System;
using System.Collections.Generic;
using System.Linq;
namespace PlayerTags.Features
namespace PlayerTags.Features;
/// <summary>
/// A feature that adds options for the management of custom tags to context menus.
/// </summary>
public class CustomTagsContextMenuFeature : FeatureBase, IDisposable
{
/// <summary>
/// A feature that adds options for the management of custom tags to context menus.
/// </summary>
public class CustomTagsContextMenuFeature : FeatureBase, IDisposable
private readonly string[] supportedAddonNames =
[
null,
"_PartyList",
"ChatLog",
"ContactList",
"ContentMemberList",
"CrossWorldLinkshell",
"FreeCompany",
"FriendList",
"LookingForGroup",
"LinkShell",
"PartyMemberList",
"SocialList",
];
public CustomTagsContextMenuFeature(PluginConfiguration pluginConfiguration, PluginData pluginData) : base(pluginConfiguration, pluginData)
{
private string?[] SupportedAddonNames = new string?[]
{
null,
"_PartyList",
"ChatLog",
"ContactList",
"ContentMemberList",
"CrossWorldLinkshell",
"FreeCompany",
"FriendList",
"LookingForGroup",
"LinkShell",
"PartyMemberList",
"SocialList",
};
PluginServices.ContextMenu.OnMenuOpened += ContextMenu_OnMenuOpened; ;
}
private DalamudContextMenu? m_ContextMenu;
public void Dispose()
{
PluginServices.ContextMenu.OnMenuOpened -= ContextMenu_OnMenuOpened;
}
public CustomTagsContextMenuFeature(PluginConfiguration pluginConfiguration, PluginData pluginData) : base(pluginConfiguration, pluginData)
{
m_ContextMenu = new DalamudContextMenu();
m_ContextMenu.OnOpenGameObjectContextMenu += ContextMenuHooks_ContextMenuOpened;
}
private void ContextMenu_OnMenuOpened(IMenuOpenedArgs args)
{
if (!EnableGlobal || !pluginConfiguration.IsCustomTagsContextMenuEnabled
|| args.MenuType != ContextMenuType.Default
|| args.Target is not MenuTargetDefault menuTarget
|| !supportedAddonNames.Contains(args.AddonName))
return;
public void Dispose()
Identity? identity = pluginData.GetIdentity(menuTarget);
if (identity != null)
{
if (m_ContextMenu != null)
var allTags = new Dictionary<Tag, bool>();
foreach (var customTag in pluginData.CustomTags)
{
m_ContextMenu.OnOpenGameObjectContextMenu -= ContextMenuHooks_ContextMenuOpened;
((IDisposable)m_ContextMenu).Dispose();
m_ContextMenu = null;
}
}
private void ContextMenuHooks_ContextMenuOpened(GameObjectContextMenuOpenArgs contextMenuOpenedArgs)
{
if (!EnableGlobal || !pluginConfiguration.IsCustomTagsContextMenuEnabled
|| !SupportedAddonNames.Contains(contextMenuOpenedArgs.ParentAddonName))
{
return;
var isAdded = identity.CustomTagIds.Contains(customTag.CustomId.Value);
allTags.Add(customTag, isAdded);
}
Identity? identity = pluginData.GetIdentity(contextMenuOpenedArgs);
if (identity != null)
var sortedTags = allTags.OrderBy(n => n.Value);
foreach (var tag in sortedTags)
{
var allTags = new Dictionary<Tag, bool>();
foreach (var customTag in pluginData.CustomTags)
{
var isAdded = identity.CustomTagIds.Contains(customTag.CustomId.Value);
allTags.Add(customTag, isAdded);
}
var sortedTags = allTags.OrderBy(n => n.Value);
foreach (var tag in sortedTags)
{
string menuItemText;
if (tag.Value)
menuItemText = Strings.Loc_Static_ContextMenu_RemoveTag;
else
menuItemText = Strings.Loc_Static_ContextMenu_AddTag;
menuItemText = string.Format(menuItemText, tag.Key.Text.Value);
string menuItemText;
if (tag.Value)
menuItemText = Strings.Loc_Static_ContextMenu_RemoveTag;
else
menuItemText = Strings.Loc_Static_ContextMenu_AddTag;
menuItemText = string.Format(menuItemText, tag.Key.Text.Value);
contextMenuOpenedArgs.AddCustomItem(
new GameObjectContextMenuItem(menuItemText, openedEventArgs =>
{
if (tag.Value)
pluginData.RemoveCustomTagFromIdentity(tag.Key, identity);
else
pluginData.AddCustomTagToIdentity(tag.Key, identity);
pluginConfiguration.Save(pluginData);
})
{
IsSubMenu = false
});
}
args.AddMenuItem(new()
{
IsSubmenu = false,
IsEnabled = true,
Name = menuItemText,
OnClicked = openedEventArgs =>
{
if (tag.Value)
pluginData.RemoveCustomTagFromIdentity(tag.Key, identity);
else
pluginData.AddCustomTagToIdentity(tag.Key, identity);
pluginConfiguration.Save(pluginData);
},
});
}
}
}

View File

@@ -1,24 +1,18 @@
using PlayerTags.Configuration;
using PlayerTags.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PlayerTags.Features
namespace PlayerTags.Features;
public class FeatureBase
{
public class FeatureBase
protected readonly PluginConfiguration pluginConfiguration;
protected readonly PluginData pluginData;
public virtual bool EnableGlobal => pluginConfiguration.EnabledGlobal;
protected FeatureBase(PluginConfiguration pluginConfiguration, PluginData pluginData)
{
protected readonly PluginConfiguration pluginConfiguration;
protected readonly PluginData pluginData;
public virtual bool EnableGlobal => pluginConfiguration.EnabledGlobal;
protected FeatureBase(PluginConfiguration pluginConfiguration, PluginData pluginData)
{
this.pluginConfiguration = pluginConfiguration;
this.pluginData = pluginData;
}
this.pluginConfiguration = pluginConfiguration;
this.pluginData = pluginData;
}
}

View File

@@ -1,283 +1,222 @@
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.System.Memory;
using FFXIVClientStructs.FFXIV.Component.GUI;
using Lumina.Excel.GeneratedSheets;
using Pilz.Dalamud.Icons;
using Pilz.Dalamud.Nameplates.Tools;
using Pilz.Dalamud.NamePlate;
using Pilz.Dalamud.Tools.NamePlates;
using Pilz.Dalamud.Tools.Strings;
using PlayerTags.Configuration;
using PlayerTags.Data;
using PlayerTags.GameInterface.Nameplates;
using PlayerTags.Inheritables;
using System;
using System.Collections.Generic;
using System.Linq;
namespace PlayerTags.Features
namespace PlayerTags.Features;
/// <summary>
/// A feature that adds tags to nameplates.
/// </summary>
public class NameplateTagTargetFeature : TagTargetFeature
{
/// <summary>
/// A feature that adds tags to nameplates.
/// </summary>
public class NameplateTagTargetFeature : TagTargetFeature
private readonly StatusIconPriorizer statusiconPriorizer;
private readonly JobIconSets jobIconSets = new();
public NameplateTagTargetFeature(PluginConfiguration pluginConfiguration, PluginData pluginData) : base(pluginConfiguration, pluginData)
{
private readonly StatusIconPriorizer statusiconPriorizer;
private readonly JobIconSets jobIconSets = new();
private Nameplate? m_Nameplate;
statusiconPriorizer = new(pluginConfiguration.StatusIconPriorizerSettings);
PluginServices.NamePlateGui.OnNamePlateUpdate += NamePlateGui_OnNamePlateUpdate;
}
public NameplateTagTargetFeature(PluginConfiguration pluginConfiguration, PluginData pluginData) : base(pluginConfiguration, pluginData)
public override void Dispose()
{
PluginServices.NamePlateGui.OnNamePlateUpdate -= NamePlateGui_OnNamePlateUpdate;
base.Dispose();
}
protected override bool IsIconVisible(Tag tag)
{
if (tag.IsRoleIconVisibleInNameplates.InheritedValue != null)
return tag.IsRoleIconVisibleInNameplates.InheritedValue.Value;
return false;
}
protected override bool IsTextVisible(Tag tag)
{
if (tag.IsTextVisibleInNameplates.InheritedValue != null)
return tag.IsTextVisibleInNameplates.InheritedValue.Value;
return false;
}
private void NamePlateGui_OnNamePlateUpdate(INamePlateUpdateContext context, IReadOnlyList<INamePlateUpdateHandler> handlers)
{
if (!EnableGlobal)
return;
foreach (var handler in handlers)
{
statusiconPriorizer = new(pluginConfiguration.StatusIconPriorizerSettings);
// Only handle player nameplates
if (handler.NamePlateKind != NamePlateKind.PlayerCharacter || handler.PlayerCharacter == null)
continue;
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_Nameplate == null)
{
m_Nameplate = new Nameplate();
if (!m_Nameplate.IsValid)
{
m_Nameplate = null;
}
if (m_Nameplate != null)
{
m_Nameplate.PlayerNameplateUpdated += Nameplate_PlayerNameplateUpdated;
}
}
}
private void Unhook()
{
if (m_Nameplate != null)
{
m_Nameplate.PlayerNameplateUpdated -= Nameplate_PlayerNameplateUpdated;
m_Nameplate.Dispose();
m_Nameplate = null;
}
}
private void ClientState_Login(object? sender, EventArgs e)
{
Hook();
}
private void ClientState_Logout(object? sender, EventArgs e)
{
Unhook();
}
protected override bool IsIconVisible(Tag tag)
{
if (tag.IsRoleIconVisibleInNameplates.InheritedValue != null)
{
return tag.IsRoleIconVisibleInNameplates.InheritedValue.Value;
}
return false;
}
protected override bool IsTextVisible(Tag tag)
{
if (tag.IsTextVisibleInNameplates.InheritedValue != null)
{
return tag.IsTextVisibleInNameplates.InheritedValue.Value;
}
return false;
}
private unsafe void Nameplate_PlayerNameplateUpdated(PlayerNameplateUpdatedArgs args)
{
if (!EnableGlobal) return;
var beforeTitleBytes = args.Title.Encode();
var iconID = args.IconId;
var beforeTitleBytes = handler.InfoView.Title.Encode();
var generalOptions = pluginConfiguration.GeneralOptions[ActivityContextManager.CurrentActivityContext.ActivityType];
AddTagsToNameplate(args.PlayerCharacter, args.Name, args.Title, args.FreeCompany, ref iconID, generalOptions);
args.IconId = iconID;
AddTagsToNameplate(handler, generalOptions);
if (generalOptions.NameplateTitlePosition == NameplateTitlePosition.AlwaysAboveName)
args.IsTitleAboveName = true;
handler.IsPrefixTitle = true;
else if (generalOptions.NameplateTitlePosition == NameplateTitlePosition.AlwaysBelowName)
args.IsTitleAboveName = false;
handler.IsPrefixTitle = false;
if (generalOptions.NameplateTitleVisibility == NameplateTitleVisibility.Always)
args.IsTitleVisible = true;
handler.DisplayTitle = true;
else if (generalOptions.NameplateTitleVisibility == NameplateTitleVisibility.Never)
args.IsTitleVisible = false;
handler.DisplayTitle = false;
else if (generalOptions.NameplateTitleVisibility == NameplateTitleVisibility.WhenHasTags)
{
bool hasTitleChanged = !beforeTitleBytes.SequenceEqual(args.Title.Encode());
args.IsTitleVisible = hasTitleChanged;
}
handler.DisplayTitle = !beforeTitleBytes.SequenceEqual(handler.InfoView.Title.Encode());
if (generalOptions.NameplateFreeCompanyVisibility == NameplateFreeCompanyVisibility.Never)
args.FreeCompany.Payloads.Clear();
handler.RemoveFreeCompanyTag();
}
}
/// <summary>
/// Adds the given payload changes to the specified locations.
/// </summary>
/// <param name="nameplateElement">The nameplate element of the changes.</param>
/// <param name="tagPosition">The position of the changes.</param>
/// <param name="payloadChanges">The payload changes to add.</param>
/// <param name="nameplateChanges">The dictionary to add changes to.</param>
private void AddPayloadChanges(NameplateElement nameplateElement, TagPosition tagPosition, IEnumerable<Payload> payloadChanges, NameplateChanges nameplateChanges, bool forceUsingSingleAnchorPayload)
/// <summary>
/// Adds the given payload changes to the specified locations.
/// </summary>
/// <param name="nameplateElement">The nameplate element of the changes.</param>
/// <param name="tagPosition">The position of the changes.</param>
/// <param name="payloadChanges">The payload changes to add.</param>
/// <param name="nameplateChanges">The dictionary to add changes to.</param>
private void AddPayloadChanges(NameplateElement nameplateElement, TagPosition tagPosition, IEnumerable<Payload> payloadChanges, NameplateChanges nameplateChanges, bool forceUsingSingleAnchorPayload)
{
if (payloadChanges.Any())
{
if (payloadChanges.Any())
var changes = nameplateChanges.GetChange((NameplateElements)nameplateElement);
AddPayloadChanges((StringPosition)tagPosition, payloadChanges, changes.Changes, forceUsingSingleAnchorPayload);
}
}
/// <summary>
/// Adds tags to the nameplate of a game object.
/// </summary>
/// <param name="playerCharacter">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>
private void AddTagsToNameplate(INamePlateUpdateHandler handler, GeneralOptionsClass generalOptions)
{
int? newStatusIcon = null;
var nameplateChanges = new NameplateChanges(handler);
if (handler.PlayerCharacter != null && (!handler.PlayerCharacter.IsDead || generalOptions.NameplateDeadPlayerHandling != DeadPlayerHandling.Ignore))
{
var classJob = handler.PlayerCharacter.ClassJob;
var classJobGameData = classJob?.GameData;
// Add the job tags
if (classJobGameData != null && pluginData.JobTags.TryGetValue(classJobGameData.Abbreviation, out var jobTag))
{
var changes = nameplateChanges.GetChanges((NameplateElements)nameplateElement);
AddPayloadChanges((StringPosition)tagPosition, payloadChanges, changes, forceUsingSingleAnchorPayload);
if (jobTag.TagTargetInNameplates.InheritedValue != null && jobTag.TagPositionInNameplates.InheritedValue != null)
checkTag(jobTag);
}
}
private NameplateChanges GenerateEmptyNameplateChanges(SeString name, SeString title, SeString freeCompany)
{
NameplateChanges nameplateChanges = new();
nameplateChanges.GetProps(NameplateElements.Name).Destination = name;
nameplateChanges.GetProps(NameplateElements.Title).Destination = title;
nameplateChanges.GetProps(NameplateElements.FreeCompany).Destination = freeCompany;
return nameplateChanges;
}
/// <summary>
/// Adds 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>
private void AddTagsToNameplate(GameObject gameObject, SeString name, SeString title, SeString freeCompany, ref int statusIcon, GeneralOptionsClass generalOptions)
{
var playerCharacter = gameObject as PlayerCharacter;
int? newStatusIcon = null;
NameplateChanges nameplateChanges = GenerateEmptyNameplateChanges(name, title, freeCompany);
if (playerCharacter != null && (!playerCharacter.IsDead || generalOptions.NameplateDeadPlayerHandling != DeadPlayerHandling.Ignore))
// Add the randomly generated name tag payload
if (pluginConfiguration.IsPlayerNameRandomlyGenerated)
{
var classJob = playerCharacter.ClassJob;
var classJobGameData = classJob?.GameData;
// Add the job tags
if (classJobGameData != null && pluginData.JobTags.TryGetValue(classJobGameData.Abbreviation, out var jobTag))
var characterName = handler.PlayerCharacter.Name.TextValue;
if (characterName != null)
{
if (jobTag.TagTargetInNameplates.InheritedValue != null && jobTag.TagPositionInNameplates.InheritedValue != null)
checkTag(jobTag);
}
// Add the randomly generated name tag payload
if (pluginConfiguration.IsPlayerNameRandomlyGenerated)
{
var characterName = playerCharacter.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, false);
}
}
// Add custom tags
Identity identity = pluginData.GetIdentity(playerCharacter);
foreach (var customTagId in identity.CustomTagIds)
{
var customTag = pluginData.CustomTags.FirstOrDefault(tag => tag.CustomId.Value == customTagId);
if (customTag != null)
checkTag(customTag);
}
void checkTag(Tag tag)
{
if (tag.TagTargetInNameplates.InheritedValue != null && tag.TagPositionInNameplates.InheritedValue != null)
{
var payloads = GetPayloads(tag, gameObject);
if (payloads.Any())
AddPayloadChanges(tag.TagTargetInNameplates.InheritedValue.Value, tag.TagPositionInNameplates.InheritedValue.Value, payloads, nameplateChanges, false);
}
if (IsTagVisible(tag, gameObject) && newStatusIcon == null && classJob != null && (tag.IsJobIconVisibleInNameplates?.InheritedValue ?? false))
newStatusIcon = jobIconSets.GetJobIcon(tag.JobIconSet?.InheritedValue ?? JobIconSetName.Framed, classJob.Id);
var generatedName = RandomNameGenerator.Generate(characterName);
if (generatedName != null)
AddPayloadChanges(NameplateElement.Name, TagPosition.Replace, Enumerable.Empty<Payload>().Append(new TextPayload(generatedName)), nameplateChanges, false);
}
}
// Apply new status icon
if (newStatusIcon != null)
// Add custom tags
Identity identity = pluginData.GetIdentity(handler.PlayerCharacter);
foreach (var customTagId in identity.CustomTagIds)
{
var change = nameplateChanges.GetChange(NameplateElements.Name, StringPosition.Before);
NameplateUpdateFactory.ApplyStatusIconWithPrio(ref statusIcon, (int)newStatusIcon, change, ActivityContextManager.CurrentActivityContext, statusiconPriorizer, pluginConfiguration.MoveStatusIconToNameplateTextIfPossible);
var customTag = pluginData.CustomTags.FirstOrDefault(tag => tag.CustomId.Value == customTagId);
if (customTag != null)
checkTag(customTag);
}
// Gray out the nameplate
if (playerCharacter != null && playerCharacter.IsDead && generalOptions.NameplateDeadPlayerHandling == DeadPlayerHandling.GrayOut)
GrayOutNameplate(gameObject, nameplateChanges);
// Build the final strings out of the payloads
ApplyNameplateChanges(nameplateChanges);
if (playerCharacter != null && (!playerCharacter.IsDead || generalOptions.NameplateDeadPlayerHandling == DeadPlayerHandling.Include))
void checkTag(Tag tag)
{
// An additional step to apply text color to additional locations
Identity identity = pluginData.GetIdentity(playerCharacter);
foreach (var customTagId in identity.CustomTagIds)
if (tag.TagTargetInNameplates.InheritedValue != null && tag.TagPositionInNameplates.InheritedValue != null)
{
var customTag = pluginData.CustomTags.FirstOrDefault(tag => tag.CustomId.Value == customTagId);
if (customTag != null)
applyTextFormatting(customTag);
}
if (playerCharacter.ClassJob.GameData != null && pluginData.JobTags.TryGetValue(playerCharacter.ClassJob.GameData.Abbreviation, out var jobTag))
applyTextFormatting(jobTag);
void applyTextFormatting(Tag tag)
{
var destStrings = new[] { name, title, freeCompany };
var isTextColorApplied = new[] { tag.IsTextColorAppliedToNameplateName, tag.IsTextColorAppliedToNameplateTitle, tag.IsTextColorAppliedToNameplateFreeCompany };
ApplyTextFormatting(gameObject, tag, new[] { name, title, freeCompany }, isTextColorApplied, null);
var payloads = GetPayloads(tag, handler.PlayerCharacter);
if (payloads.Length != 0)
AddPayloadChanges(tag.TagTargetInNameplates.InheritedValue.Value, tag.TagPositionInNameplates.InheritedValue.Value, payloads, nameplateChanges, false);
}
if (IsTagVisible(tag, handler.PlayerCharacter) && newStatusIcon == null && classJob != null && (tag.IsJobIconVisibleInNameplates?.InheritedValue ?? false))
newStatusIcon = jobIconSets.GetJobIcon(tag.JobIconSet?.InheritedValue ?? JobIconSetName.Framed, classJob.Id);
}
}
private void GrayOutNameplate(GameObject gameObject, NameplateChanges nameplateChanges)
// Apply new status icon
if (newStatusIcon != null)
{
if (gameObject is PlayerCharacter playerCharacter)
{
foreach (NameplateElements element in Enum.GetValues<NameplateElements>())
{
nameplateChanges.GetChange(element, StringPosition.Before).Payloads.Add(new UIForegroundPayload(3));
nameplateChanges.GetChange(element, StringPosition.After).Payloads.Add(new UIForegroundPayload(0));
}
}
NameplateUpdateFactory.ApplyStatusIconWithPrio(handler, (int)newStatusIcon, ActivityContextManager.CurrentActivityContext, statusiconPriorizer, pluginConfiguration.MoveStatusIconToNameplateTextIfPossible);
}
protected void ApplyNameplateChanges(NameplateChanges nameplateChanges)
// Build the final strings out of the payloads
NameplateUpdateFactory.ApplyNameplateChanges(new NameplateChangesProps
{
var props = new NameplateChangesProps
Changes = nameplateChanges
});
// Gray out the nameplate
if (handler.PlayerCharacter != null && handler.PlayerCharacter.IsDead && generalOptions.NameplateDeadPlayerHandling == DeadPlayerHandling.GrayOut)
GrayOutNameplate(handler.PlayerCharacter, nameplateChanges);
// Apply text color
if (handler.PlayerCharacter != null && (!handler.PlayerCharacter.IsDead || generalOptions.NameplateDeadPlayerHandling == DeadPlayerHandling.Include))
{
Identity identity = pluginData.GetIdentity(handler.PlayerCharacter);
foreach (var customTagId in identity.CustomTagIds)
{
Changes = nameplateChanges
};
NameplateUpdateFactory.ApplyNameplateChanges(props);
var customTag = pluginData.CustomTags.FirstOrDefault(tag => tag.CustomId.Value == customTagId);
if (customTag != null)
applyTextFormatting(customTag);
}
if (handler.PlayerCharacter.ClassJob.GameData != null && pluginData.JobTags.TryGetValue(handler.PlayerCharacter.ClassJob.GameData.Abbreviation, out var jobTag))
applyTextFormatting(jobTag);
void applyTextFormatting(Tag tag)
{
var dic = new Dictionary<NameplateElementChange, InheritableValue<bool>>
{
{ nameplateChanges.GetChange(NameplateElements.Name), tag.IsTextColorAppliedToNameplateName },
{ nameplateChanges.GetChange(NameplateElements.Title), tag.IsTextColorAppliedToNameplateTitle },
{ nameplateChanges.GetChange(NameplateElements.FreeCompany), tag.IsTextColorAppliedToNameplateFreeCompany },
};
ApplyTextFormatting(handler.PlayerCharacter, tag, dic, null);
}
}
}
private void GrayOutNameplate(IPlayerCharacter playerCharacter, NameplateChanges nameplateChanges)
{
foreach (var element in Enum.GetValues<NameplateElements>())
nameplateChanges.GetChange(element).ApplyFormatting(new SeString().Append(new UIForegroundPayload(3)), new SeString().Append(new UIForegroundPayload(0)));
}
protected void ApplyTextFormatting(IPlayerCharacter? gameObject, Tag tag, IEnumerable<KeyValuePair<NameplateElementChange, InheritableValue<bool>>> changes, ushort? overwriteTextColor = null)
{
if (IsTagVisible(tag, gameObject))
{
foreach (var kvp in changes)
{
var change = kvp.Key;
var enableFlag = kvp.Value;
if (enableFlag.InheritedValue != null && enableFlag.InheritedValue.Value && (overwriteTextColor ?? tag.TextColor?.InheritedValue) is ushort colorToUse)
change.ApplyFormatting(new SeString().Append(new UIForegroundPayload(colorToUse)), new SeString().Append(new UIForegroundPayload(0)));
}
}
}
}

View File

@@ -2,294 +2,171 @@
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using Lumina.Excel.GeneratedSheets;
using Pilz.Dalamud.ActivityContexts;
using Pilz.Dalamud.Tools.Strings;
using PlayerTags.Configuration;
using PlayerTags.Configuration.GameConfig;
using PlayerTags.Data;
using PlayerTags.Inheritables;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using GameObject = Dalamud.Game.ClientState.Objects.Types.GameObject;
namespace PlayerTags.Features
namespace PlayerTags.Features;
/// <summary>
/// The base of a feature that adds tags to UI elements.
/// </summary>
public abstract class TagTargetFeature(PluginConfiguration pluginConfiguration, PluginData pluginData) : FeatureBase(pluginConfiguration, pluginData), IDisposable
{
/// <summary>
/// The base of a feature that adds tags to UI elements.
/// </summary>
public abstract class TagTargetFeature : FeatureBase, IDisposable
public ActivityContextManager ActivityContextManager { get; init; } = new();
public virtual void Dispose()
{
public ActivityContextManager ActivityContextManager { get; init; }
ActivityContextManager.Dispose();
GC.SuppressFinalize(this);
}
protected TagTargetFeature(PluginConfiguration pluginConfiguration, PluginData pluginData) : base(pluginConfiguration, pluginData)
protected abstract bool IsIconVisible(Tag tag);
protected abstract bool IsTextVisible(Tag tag);
protected bool IsTagVisible(Tag tag, IGameObject? gameObject)
{
bool isVisibleForActivity = ActivityContextHelper.GetIsVisible(ActivityContextManager.CurrentActivityContext.ActivityType,
tag.IsVisibleInPveDuties.InheritedValue ?? false,
tag.IsVisibleInPvpDuties.InheritedValue ?? false,
tag.IsVisibleInOverworld.InheritedValue ?? false);
if (!isVisibleForActivity)
return false;
if (gameObject is IPlayerCharacter playerCharacter)
{
ActivityContextManager = new();
}
bool isVisibleForPlayer = PlayerContextHelper.GetIsVisible(playerCharacter,
tag.IsVisibleForSelf.InheritedValue ?? false,
tag.IsVisibleForFriendPlayers.InheritedValue ?? false,
tag.IsVisibleForPartyPlayers.InheritedValue ?? false,
tag.IsVisibleForAlliancePlayers.InheritedValue ?? false,
tag.IsVisibleForEnemyPlayers.InheritedValue ?? false,
tag.IsVisibleForOtherPlayers.InheritedValue ?? false);
public virtual void Dispose()
{
ActivityContextManager.Dispose();
}
protected abstract bool IsIconVisible(Tag tag);
protected abstract bool IsTextVisible(Tag tag);
protected bool IsTagVisible(Tag tag, GameObject? gameObject)
{
bool isVisibleForActivity = ActivityContextHelper.GetIsVisible(ActivityContextManager.CurrentActivityContext.ActivityType,
tag.IsVisibleInPveDuties.InheritedValue ?? false,
tag.IsVisibleInPvpDuties.InheritedValue ?? false,
tag.IsVisibleInOverworld.InheritedValue ?? false);
if (!isVisibleForActivity)
{
if (!isVisibleForPlayer)
return false;
}
if (gameObject is PlayerCharacter playerCharacter)
{
bool isVisibleForPlayer = PlayerContextHelper.GetIsVisible(playerCharacter,
tag.IsVisibleForSelf.InheritedValue ?? false,
tag.IsVisibleForFriendPlayers.InheritedValue ?? false,
tag.IsVisibleForPartyPlayers.InheritedValue ?? false,
tag.IsVisibleForAlliancePlayers.InheritedValue ?? false,
tag.IsVisibleForEnemyPlayers.InheritedValue ?? false,
tag.IsVisibleForOtherPlayers.InheritedValue ?? false);
if (!isVisibleForPlayer)
{
return false;
}
}
return true;
}
/// <summary>
/// Gets the payloads for the given tag and game object depending on visibility conditions.
/// </summary>
/// <param name="gameObject">The game object to get payloads for.</param>
/// <param name="tag">The tag config to get payloads for.</param>
/// <returns>A list of payloads for the given tag.</returns>
protected Payload[] GetPayloads(Tag tag, GameObject? gameObject)
{
if (!IsTagVisible(tag, gameObject))
{
return Array.Empty<Payload>();
}
return true;
}
return CreatePayloads(tag);
/// <summary>
/// Gets the payloads for the given tag and game object depending on visibility conditions.
/// </summary>
/// <param name="gameObject">The game object to get payloads for.</param>
/// <param name="tag">The tag config to get payloads for.</param>
/// <returns>A list of payloads for the given tag.</returns>
protected Payload[] GetPayloads(Tag tag, IGameObject? playerCharacter)
{
if (!IsTagVisible(tag, playerCharacter))
return [];
return CreatePayloads(tag);
}
/// <summary>
/// Creates payloads for the given tag.
/// </summary>
/// <param name="tag">The tag to create payloads for.</param>
/// <returns>The payloads for the given tag.</returns>
private Payload[] CreatePayloads(Tag tag)
{
List<Payload> newPayloads = [];
BitmapFontIcon? icon = null;
string? text = null;
if (IsIconVisible(tag))
icon = tag.Icon.InheritedValue;
if (icon != null && icon.Value != BitmapFontIcon.None)
newPayloads.Add(new IconPayload(icon.Value));
if (IsTextVisible(tag))
text = tag.Text.InheritedValue;
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));
}
/// <summary>
/// Creates payloads for the given tag.
/// </summary>
/// <param name="tag">The tag to create payloads for.</param>
/// <returns>The payloads for the given tag.</returns>
private Payload[] CreatePayloads(Tag tag)
return [.. newPayloads];
}
protected static string BuildPlayername(string name)
{
var logNameType = GameConfigHelper.Instance.GetLogNameType();
var result = string.Empty;
if (logNameType != null && !string.IsNullOrEmpty(name))
{
List<Payload> newPayloads = new List<Payload>();
var nameSplitted = name.Split(' ');
BitmapFontIcon? icon = null;
if (IsIconVisible(tag))
if (nameSplitted.Length > 1)
{
icon = tag.Icon.InheritedValue;
}
var firstName = nameSplitted[0];
var lastName = nameSplitted[1];
if (icon != null && icon.Value != BitmapFontIcon.None)
{
newPayloads.Add(new IconPayload(icon.Value));
}
string? text = null;
if (IsTextVisible(tag))
{
text = tag.Text.InheritedValue;
}
if (!string.IsNullOrWhiteSpace(text))
{
if (tag.IsTextItalic.InheritedValue != null && tag.IsTextItalic.InheritedValue.Value)
switch (logNameType)
{
newPayloads.Add(new EmphasisItalicPayload(true));
case LogNameType.FullName:
result = $"{firstName} {lastName}";
break;
case LogNameType.LastNameShorted:
result = $"{firstName} {lastName[..1]}.";
break;
case LogNameType.FirstNameShorted:
result = $"{firstName[..1]}. {lastName}";
break;
case LogNameType.Initials:
result = $"{firstName[..1]}. {lastName[..1]}.";
break;
}
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();
}
protected static string BuildPlayername(string name)
{
var logNameType = GameConfigHelper.Instance.GetLogNameType();
var result = string.Empty;
if (logNameType != null && !string.IsNullOrEmpty(name))
{
var nameSplitted = name.Split(' ');
if (nameSplitted.Length > 1)
{
var firstName = nameSplitted[0];
var lastName = nameSplitted[1];
switch (logNameType)
{
case LogNameType.FullName:
result = $"{firstName} {lastName}";
break;
case LogNameType.LastNameShorted:
result = $"{firstName} {lastName[..1]}.";
break;
case LogNameType.FirstNameShorted:
result = $"{firstName[..1]}. {lastName}";
break;
case LogNameType.Initials:
result = $"{firstName[..1]}. {lastName[..1]}.";
break;
}
}
}
if (string.IsNullOrEmpty(result))
result = name;
return result;
}
/// <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(StringPosition tagPosition, IEnumerable<Payload> payloads, StringChanges stringChanges, bool forceUsingSingleAnchorPayload)
{
if (payloads != null && payloads.Any() && stringChanges != null)
{
var changes = stringChanges.GetChange(tagPosition);
changes.Payloads.AddRange(payloads);
changes.ForceUsingSingleAnchorPayload = forceUsingSingleAnchorPayload;
}
}
/// <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, StringChanges stringChanges, List<Payload> anchorPayloads = null, Payload anchorReplacePayload = null)
if (string.IsNullOrEmpty(result))
result = name;
return result;
}
/// <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(StringPosition tagPosition, IEnumerable<Payload> payloads, StringChanges stringChanges, bool forceUsingSingleAnchorPayload)
{
if (payloads != null && payloads.Any() && stringChanges != null)
{
var props = new StringChangesProps
{
Destination = seString,
AnchorPayload = anchorReplacePayload
};
props.AnchorPayloads = anchorPayloads;
props.StringChanges = stringChanges;
StringUpdateFactory.ApplyStringChanges(props);
}
protected void ApplyTextFormatting(GameObject gameObject, Tag tag, SeString[] destStrings, InheritableValue<bool>[] textColorApplied, List<Payload> preferedPayloads, ushort? overwriteTextColor = null)
{
if (IsTagVisible(tag, gameObject))
{
for (int i = 0; i < destStrings.Length; i++)
{
var destString = destStrings[i];
var isTextColorApplied = textColorApplied[i];
applyTextColor(destString, isTextColorApplied, tag.TextColor);
//applyTextGlowColor(destString, isTextColorApplied, tag.TextGlowColor);
//applyTextItalicColor(destString, tag.IsTextItalic); // Disabled, because that is needed only for a few parts somewhere else.
}
}
void applyTextColor(SeString destPayload, InheritableValue<bool> enableFlag, InheritableValue<ushort> colorValue)
{
var colorToUse = overwriteTextColor ?? colorValue?.InheritedValue;
if (shouldApplyFormattingPayloads(destPayload)
&& enableFlag.InheritedValue != null
&& enableFlag.InheritedValue.Value
&& colorToUse != null)
applyTextFormattingPayloads(destPayload, new UIForegroundPayload(colorToUse.Value), new UIForegroundPayload(0));
}
//void applyTextGlowColor(SeString destPayload, InheritableValue<bool> enableFlag, InheritableValue<ushort> colorValue)
//{
// if (shouldApplyFormattingPayloads(destPayload)
// && enableFlag.InheritedValue != null
// && enableFlag.InheritedValue.Value
// && colorValue.InheritedValue != null)
// applyTextFormattingPayloads(destPayload, new UIGlowPayload(colorValue.InheritedValue.Value), new UIGlowPayload(0));
//}
//void applyTextItalicColor(SeString destPayload, InheritableValue<bool> italicValue)
//{
// if (shouldApplyFormattingPayloads(destPayload)
// && italicValue.InheritedValue != null
// && italicValue.InheritedValue.Value)
// applyTextFormattingPayloads(destPayload, new EmphasisItalicPayload(true), new EmphasisItalicPayload(false));
//}
bool shouldApplyFormattingPayloads(SeString destPayload)
=> destPayload.Payloads.Any(payload => payload is TextPayload || payload is PlayerPayload);
void applyTextFormattingPayloads(SeString destPayload, Payload startPayload, Payload endPayload)
{
if (preferedPayloads == null || !preferedPayloads.Any())
applyTextFormattingPayloadToStartAndEnd(destPayload, startPayload, endPayload);
else
applyTextFormattingPayloadsToSpecificPosition(destPayload, startPayload, endPayload, preferedPayloads);
}
void applyTextFormattingPayloadToStartAndEnd(SeString destPayload, Payload startPayload, Payload endPayload)
{
destPayload.Payloads.Insert(0, startPayload);
destPayload.Payloads.Add(endPayload);
}
void applyTextFormattingPayloadsToSpecificPosition(SeString destPayload, Payload startPayload, Payload endPayload, List<Payload> preferedPayload)
{
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);
}
var changes = stringChanges.GetChange(tagPosition);
changes.Payloads.AddRange(payloads);
changes.ForceUsingSingleAnchorPayload = forceUsingSingleAnchorPayload;
}
}
}

View File

@@ -1,145 +0,0 @@
using Dalamud.Game.Text.SeStringHandling;
using FFXIVClientStructs.FFXIV.Client.System.Memory;
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace PlayerTags.GameInterface
{
public static class GameInterfaceHelper
{
public static SeString ReadSeString(IntPtr ptr)
{
if (ptr == IntPtr.Zero)
{
return new SeString();
}
if (TryReadStringBytes(ptr, out var bytes) && bytes != null)
{
return SeString.Parse(bytes);
}
return new SeString();
}
public static bool TryReadSeString(IntPtr ptr, out SeString? seString)
{
seString = null;
if (ptr == IntPtr.Zero)
{
return false;
}
if (TryReadStringBytes(ptr, out var bytes) && bytes != null)
{
seString = SeString.Parse(bytes);
return true;
}
return false;
}
public static string? ReadString(IntPtr ptr)
{
if (ptr == IntPtr.Zero)
{
return null;
}
if (TryReadStringBytes(ptr, out var bytes) && bytes != null)
{
return Encoding.UTF8.GetString(bytes);
}
return null;
}
public static bool TryReadString(IntPtr ptr, out string? str)
{
str = null;
if (ptr == IntPtr.Zero)
{
return false;
}
if (TryReadStringBytes(ptr, out var bytes) && bytes != null)
{
str = Encoding.UTF8.GetString(bytes);
return true;
}
return false;
}
public static bool TryReadStringBytes(IntPtr ptr, out byte[]? bytes)
{
bytes = null;
if (ptr == IntPtr.Zero)
{
return false;
}
var size = 0;
while (Marshal.ReadByte(ptr, size) != 0)
{
size++;
}
bytes = new byte[size];
Marshal.Copy(ptr, bytes, 0, size);
return true;
}
public static IntPtr PluginAllocate(byte[] bytes)
{
IntPtr pointer = Marshal.AllocHGlobal(bytes.Length + 1);
Marshal.Copy(bytes, 0, pointer, bytes.Length);
Marshal.WriteByte(pointer, bytes.Length, 0);
return pointer;
}
public static IntPtr PluginAllocate(SeString seString)
{
return PluginAllocate(seString.Encode());
}
public static void PluginFree(ref IntPtr ptr)
{
Marshal.FreeHGlobal(ptr);
ptr = IntPtr.Zero;
}
public static byte[] NullTerminate(this byte[] bytes)
{
if (bytes.Length == 0 || bytes[bytes.Length - 1] != 0)
{
var newBytes = new byte[bytes.Length + 1];
Array.Copy(bytes, newBytes, bytes.Length);
newBytes[^1] = 0;
return newBytes;
}
return bytes;
}
public static unsafe IntPtr GameUIAllocate(ulong size)
{
return (IntPtr)IMemorySpace.GetUISpace()->Malloc(size, 0);
}
public static unsafe void GameFree(ref IntPtr ptr, ulong size)
{
if (ptr == IntPtr.Zero)
{
return;
}
IMemorySpace.Free((void*)ptr, size);
ptr = IntPtr.Zero;
}
}
}

View File

@@ -1,65 +0,0 @@
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Hooking;
using Dalamud.Logging;
using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.UI;
using Pilz.Dalamud.Nameplates;
using System;
using System.Diagnostics.Tracing;
using System.Linq;
using System.Runtime.InteropServices;
namespace PlayerTags.GameInterface.Nameplates
{
/// <summary>
/// Provides an interface to modify nameplates.
/// </summary>
public class Nameplate : IDisposable
{
public NameplateManager NameplateManager { get; init; }
/// <summary>
/// Occurs when a player nameplate is updated by the game.
/// </summary>
public event PlayerNameplateUpdatedDelegate? PlayerNameplateUpdated;
/// <summary>
/// Whether the required hooks are in place and this instance is valid.
/// </summary>
public bool IsValid
{
get => NameplateManager != null && NameplateManager.IsValid;
}
public Nameplate()
{
NameplateManager = new();
NameplateManager.Hooks.AddonNamePlate_SetPlayerNameManaged += Hooks_AddonNamePlate_SetPlayerNameManaged;
}
public void Dispose()
{
NameplateManager.Hooks.AddonNamePlate_SetPlayerNameManaged -= Hooks_AddonNamePlate_SetPlayerNameManaged;
NameplateManager.Dispose();
}
private void Hooks_AddonNamePlate_SetPlayerNameManaged(Pilz.Dalamud.Nameplates.EventArgs.AddonNamePlate_SetPlayerNameManagedEventArgs eventArgs)
{
try
{
PlayerCharacter? playerCharacter = NameplateManager.GetNameplateGameObject<PlayerCharacter>(eventArgs.SafeNameplateObject);
if (playerCharacter != null)
{
var playerNameplateUpdatedArgs = new PlayerNameplateUpdatedArgs(playerCharacter, eventArgs);
PlayerNameplateUpdated?.Invoke(playerNameplateUpdatedArgs);
}
}
catch (Exception ex)
{
PluginLog.Error(ex, $"SetPlayerNameplateDetour");
}
}
}
}

View File

@@ -1,52 +0,0 @@
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.Text.SeStringHandling;
using Pilz.Dalamud.Nameplates.EventArgs;
namespace PlayerTags.GameInterface.Nameplates
{
public class PlayerNameplateUpdatedArgs
{
private readonly AddonNamePlate_SetPlayerNameManagedEventArgs eventArgs;
public PlayerCharacter PlayerCharacter { get; }
public SeString Name
{
get => eventArgs.Name;
}
public SeString Title
{
get => eventArgs.Title;
}
public SeString FreeCompany
{
get => eventArgs.FreeCompany;
}
public bool IsTitleVisible
{
get => eventArgs.IsTitleVisible;
set => eventArgs.IsTitleVisible = value;
}
public bool IsTitleAboveName
{
get => eventArgs.IsTitleAboveName;
set => eventArgs.IsTitleAboveName = value;
}
public int IconId
{
get => eventArgs.IconID;
set => eventArgs.IconID = value;
}
public PlayerNameplateUpdatedArgs(PlayerCharacter playerCharacter, AddonNamePlate_SetPlayerNameManagedEventArgs eventArgs)
{
PlayerCharacter = playerCharacter;
this.eventArgs = eventArgs;
}
}
}

View File

@@ -1,4 +0,0 @@
namespace PlayerTags.GameInterface.Nameplates
{
public delegate void PlayerNameplateUpdatedDelegate(PlayerNameplateUpdatedArgs args);
}

View File

@@ -1,13 +1,12 @@
namespace PlayerTags.Inheritables
namespace PlayerTags.Inheritables;
public interface IInheritable
{
public interface IInheritable
{
public IInheritable? Parent { get; set; }
public IInheritable? Parent { get; set; }
public InheritableBehavior Behavior { get; set; }
public InheritableBehavior Behavior { get; set; }
public abstract void SetData(InheritableData inheritableData);
public abstract void SetData(InheritableData inheritableData);
public abstract InheritableData GetData();
}
public abstract InheritableData GetData();
}

View File

@@ -1,12 +1,8 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace PlayerTags.Inheritables;
namespace PlayerTags.Inheritables
public enum InheritableBehavior
{
public enum InheritableBehavior
{
Inherit,
Enabled,
Disabled
}
Inherit,
Enabled,
Disabled
}

View File

@@ -2,16 +2,15 @@
using Newtonsoft.Json.Converters;
using System;
namespace PlayerTags.Inheritables
{
[Serializable]
public struct InheritableData
{
[JsonConverter(typeof(StringEnumConverter))]
[JsonProperty("Behavior")]
public InheritableBehavior Behavior;
namespace PlayerTags.Inheritables;
[JsonProperty("Value")]
public object Value;
}
[Serializable]
public struct InheritableData
{
[JsonConverter(typeof(StringEnumConverter))]
[JsonProperty("Behavior")]
public InheritableBehavior Behavior;
[JsonProperty("Value")]
public object Value;
}

View File

@@ -1,66 +1,63 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System.Collections.Generic;
namespace PlayerTags.Inheritables
namespace PlayerTags.Inheritables;
public class InheritableReference<T> : IInheritable
where T : class
{
public class InheritableReference<T> : IInheritable
where T : class
public IInheritable? Parent { get; set; }
public InheritableBehavior Behavior { get; set; }
[JsonProperty]
public T Value;
[JsonIgnore]
public T? InheritedValue
{
public IInheritable? Parent { get; set; }
public InheritableBehavior Behavior { get; set; }
[JsonProperty]
public T Value;
[JsonIgnore]
public T? InheritedValue
get
{
get
IInheritable? current = this;
while (current != null)
{
IInheritable? current = this;
while (current != null)
if (current.Behavior == InheritableBehavior.Enabled && current is InheritableReference<T> currentOfSameType)
{
if (current.Behavior == InheritableBehavior.Enabled && current is InheritableReference<T> currentOfSameType)
{
return currentOfSameType.Value;
}
else if (current.Behavior == InheritableBehavior.Disabled)
{
return default;
}
current = current.Parent;
return currentOfSameType.Value;
}
else if (current.Behavior == InheritableBehavior.Disabled)
{
return default;
}
return default;
current = current.Parent;
}
}
public static implicit operator InheritableReference<T>(T value) => new InheritableReference<T>(value)
{
Behavior = InheritableBehavior.Enabled
};
public InheritableReference(T value)
{
Value = value;
}
public void SetData(InheritableData inheritableData)
{
Behavior = inheritableData.Behavior;
Value = (T)inheritableData.Value;
}
public InheritableData GetData()
{
return new InheritableData
{
Behavior = Behavior,
Value = Value
};
return default;
}
}
public static implicit operator InheritableReference<T>(T value) => new(value)
{
Behavior = InheritableBehavior.Enabled
};
public InheritableReference(T value)
{
Value = value;
}
public void SetData(InheritableData inheritableData)
{
Behavior = inheritableData.Behavior;
Value = (T)inheritableData.Value;
}
public InheritableData GetData()
{
return new InheritableData
{
Behavior = Behavior,
Value = Value
};
}
}

View File

@@ -1,97 +1,95 @@
using Dalamud.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json;
using System;
namespace PlayerTags.Inheritables
namespace PlayerTags.Inheritables;
public class InheritableValue<T> : IInheritable
where T : struct
{
public class InheritableValue<T> : IInheritable
where T : struct
public IInheritable? Parent { get; set; }
public InheritableBehavior Behavior { get; set; }
[JsonProperty]
public T Value;
[JsonIgnore]
public T? InheritedValue
{
public IInheritable? Parent { get; set; }
public InheritableBehavior Behavior { get; set; }
[JsonProperty]
public T Value;
[JsonIgnore]
public T? InheritedValue
get
{
get
IInheritable? current = this;
while (current != null)
{
IInheritable? current = this;
while (current != null)
if (current.Behavior == InheritableBehavior.Enabled && current is InheritableValue<T> currentOfSameType)
{
if (current.Behavior == InheritableBehavior.Enabled && current is InheritableValue<T> currentOfSameType)
{
return currentOfSameType.Value;
}
else if (current.Behavior == InheritableBehavior.Disabled)
{
return default;
}
current = current.Parent;
return currentOfSameType.Value;
}
else if (current.Behavior == InheritableBehavior.Disabled)
{
return default;
}
return default;
current = current.Parent;
}
return default;
}
}
public static implicit operator InheritableValue<T>(T value) => new InheritableValue<T>(value)
public static implicit operator InheritableValue<T>(T value) => new(value)
{
Behavior = InheritableBehavior.Enabled
};
public InheritableValue(T value)
{
Value = value;
}
public void SetData(InheritableData inheritableData)
{
Behavior = inheritableData.Behavior;
try
{
Behavior = InheritableBehavior.Enabled
};
public InheritableValue(T value)
{
Value = value;
}
public void SetData(InheritableData inheritableData)
{
Behavior = inheritableData.Behavior;
try
if (typeof(T).IsEnum && inheritableData.Value != null)
{
if (typeof(T).IsEnum && inheritableData.Value != null)
if (inheritableData.Value is string stringValue)
{
if (inheritableData.Value is string stringValue)
{
Value = (T)Enum.Parse(typeof(T), stringValue);
}
else
{
Value = (T)Enum.ToObject(typeof(T), inheritableData.Value);
}
}
else if (inheritableData.Value == null)
{
// This should never happen
PluginLog.Error($"Expected value of type {Value.GetType()} but received null");
}
else if (typeof(T) == typeof(Guid) && inheritableData.Value is string strValue)
{
Value = (T)(object)Guid.Parse(strValue);
Value = (T)Enum.Parse(typeof(T), stringValue);
}
else
{
Value = (T)Convert.ChangeType(inheritableData.Value, typeof(T));
Value = (T)Enum.ToObject(typeof(T), inheritableData.Value);
}
}
catch (Exception ex)
else if (inheritableData.Value == null)
{
PluginLog.Error(ex, $"Failed to convert {inheritableData.Value.GetType()} value '{inheritableData.Value}' to {Value.GetType()}");
// This should never happen
PluginServices.PluginLog.Error($"Expected value of type {Value.GetType()} but received null");
}
else if (typeof(T) == typeof(Guid) && inheritableData.Value is string strValue)
{
Value = (T)(object)Guid.Parse(strValue);
}
else
{
Value = (T)Convert.ChangeType(inheritableData.Value, typeof(T));
}
}
public InheritableData GetData()
catch (Exception ex)
{
return new InheritableData
{
Behavior = Behavior,
Value = Value
};
PluginServices.PluginLog.Error(ex, $"Failed to convert {inheritableData.Value.GetType()} value '{inheritableData.Value}' to {Value.GetType()}");
}
}
public InheritableData GetData()
{
return new InheritableData
{
Behavior = Behavior,
Value = Value
};
}
}

View File

@@ -1,62 +1,58 @@
using Dalamud.Logging;
using Dalamud.Plugin;
using PlayerTags.Resources;
using PlayerTags.Resources;
using System;
using System.ComponentModel;
using System.Globalization;
namespace PlayerTags
namespace PlayerTags;
public static class Localizer
{
public static class Localizer
public static void SetLanguage(string langCode)
{
public static void SetLanguage(string langCode)
SetLanguage(new CultureInfo(langCode));
}
public static void SetLanguage(CultureInfo cultureInfo)
{
Strings.Culture = cultureInfo;
}
public static string GetName<TEnum>(TEnum value)
{
return $"{typeof(TEnum).Name}_{value}";
}
public static string GetString<TEnum>(bool isDescription)
where TEnum : Enum
{
return GetString(typeof(TEnum).Name, isDescription);
}
public static string GetString<TEnum>(TEnum value, bool isDescription)
where TEnum : Enum
{
return GetString(GetName(value), isDescription);
}
public static string GetString(string localizedStringName, bool isDescription)
{
string localizedStringId = $"Loc_{localizedStringName}";
if (isDescription)
{
SetLanguage(new CultureInfo(langCode));
localizedStringId += "_Description";
}
public static void SetLanguage(CultureInfo cultureInfo)
{
Strings.Culture = cultureInfo;
}
return GetString(localizedStringId);
}
public static string GetName<TEnum>(TEnum value)
{
return $"{typeof(TEnum).Name}_{value}";
}
public static string GetString(string localizedStringId)
{
string? value = Strings.ResourceManager.GetString(localizedStringId, Strings.Culture);
public static string GetString<TEnum>(bool isDescription)
where TEnum : Enum
{
return GetString(typeof(TEnum).Name, isDescription);
}
if (value != null)
return value;
public static string GetString<TEnum>(TEnum value, bool isDescription)
where TEnum : Enum
{
return GetString(GetName(value), isDescription);
}
public static string GetString(string localizedStringName, bool isDescription)
{
string localizedStringId = $"Loc_{localizedStringName}";
if (isDescription)
{
localizedStringId += "_Description";
}
return GetString(localizedStringId);
}
public static string GetString(string localizedStringId)
{
string? value = Strings.ResourceManager.GetString(localizedStringId, Strings.Culture);
if (value != null)
return value;
PluginLog.Error($"Failed to get localized string for id {localizedStringId}");
return localizedStringId;
}
PluginServices.PluginLog.Error($"Failed to get localized string for id {localizedStringId}");
return localizedStringId;
}
}

View File

@@ -1,33 +1,27 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
namespace PlayerTags
namespace PlayerTags;
internal class MyPaths
{
internal class MyPaths
private static string? _PluginDirectoryPath = null;
public static string PluginDirectoryPath
{
private static string? _PluginDirectoryPath = null;
public static string PluginDirectoryPath
get
{
get
if (_PluginDirectoryPath is null)
{
if (_PluginDirectoryPath is null)
{
var path = Path.GetDirectoryName(PluginServices.DalamudPluginInterface.AssemblyLocation.FullName);
if (path is null)
_PluginDirectoryPath = string.Empty;
else
_PluginDirectoryPath = path;
}
return _PluginDirectoryPath;
var path = Path.GetDirectoryName(PluginServices.DalamudPluginInterface.AssemblyLocation.FullName);
if (path is null)
_PluginDirectoryPath = string.Empty;
else
_PluginDirectoryPath = path;
}
return _PluginDirectoryPath;
}
public static string ResourcePath
=> Path.Combine(PluginDirectoryPath, "Resources");
}
public static string ResourcePath
=> Path.Combine(PluginDirectoryPath, "Resources");
}

View File

@@ -1,11 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Authors>r00telement;Pilzinsel64</Authors>
<Version>1.9.5.0</Version>
<Version>1.11.1.0</Version>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>net7.0-windows</TargetFramework>
<TargetFramework>net8.0-windows</TargetFramework>
<Platforms>x64</Platforms>
<Nullable>annotations</Nullable>
<LangVersion>latest</LangVersion>
@@ -24,8 +24,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Dalamud.ContextMenu" Version="1.2.3" />
<PackageReference Include="DalamudPackager" Version="2.1.11" />
<PackageReference Include="DalamudPackager" Version="2.1.13" />
<ProjectReference Include="..\Pilz.Dalamud\Pilz.Dalamud\Pilz.Dalamud.csproj" />
<Reference Include="FFXIVClientStructs">
<HintPath>$(DalamudLibPath)FFXIVClientStructs.dll</HintPath>
@@ -89,4 +88,4 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
</Project>

View File

@@ -5,5 +5,5 @@
"Punchline": "Lightweight job visibility in nameplates and chat.",
"Tags": [ "Jobs", "UI" ],
"CategoryTags": [ "jobs", "UI" ],
"RepoUrl": "https://github.com/Pilzinsel64/PlayerTags"
"RepoUrl": "https://git.pilzinsel64.de/pilzinsel64/playertags"
}

View File

@@ -1,123 +1,113 @@
using Dalamud.Game.Command;
using Dalamud.Logging;
using Dalamud.Plugin;
using Dalamud.Plugin.Internal;
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
using PlayerTags.Configuration;
using PlayerTags.Data;
using PlayerTags.Features;
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography;
namespace PlayerTags
namespace PlayerTags;
public sealed class Plugin : IDalamudPlugin
{
public sealed class Plugin : IDalamudPlugin
private const string c_CommandName = "/playertags";
private const string c_SubCommandName_EnableGlobal = "enableglobal";
private const string c_CommandArg_On = "on";
private const string c_CommandArg_Off = "off";
private const string c_CommandArg_toggle = "toggle";
private readonly PluginConfiguration pluginConfiguration = null;
private readonly PluginData pluginData = null;
private readonly PluginConfigurationUI pluginConfigurationUI = null;
private readonly CustomTagsContextMenuFeature customTagsContextMenuFeature;
private readonly NameplateTagTargetFeature nameplatesTagTargetFeature;
private readonly ChatTagTargetFeature chatTagTargetFeature;
public Plugin(IDalamudPluginInterface pluginInterface)
{
public string Name => "Player Tags";
private const string c_CommandName = "/playertags";
private const string c_SubCommandName_EnableGlobal = "enableglobal";
private const string c_CommandArg_On = "on";
private const string c_CommandArg_Off = "off";
private const string c_CommandArg_toggle = "toggle";
PluginServices.Initialize(pluginInterface);
Pilz.Dalamud.PluginServices.Initialize(pluginInterface);
private PluginConfiguration m_PluginConfiguration;
private PluginData m_PluginData;
private PluginConfigurationUI m_PluginConfigurationUI;
pluginConfiguration = PluginConfiguration.LoadPluginConfig() ?? new();
pluginData = new PluginData(pluginConfiguration);
pluginConfigurationUI = new PluginConfigurationUI(pluginConfiguration, pluginData);
private CustomTagsContextMenuFeature m_CustomTagsContextMenuFeature;
private NameplateTagTargetFeature m_NameplatesTagTargetFeature;
private ChatTagTargetFeature m_ChatTagTargetFeature;
Localizer.SetLanguage(PluginServices.DalamudPluginInterface.UiLanguage);
PluginServices.DalamudPluginInterface.LanguageChanged += DalamudPluginInterface_LanguageChanged;
public Plugin(DalamudPluginInterface pluginInterface)
PluginServices.DalamudPluginInterface.UiBuilder.Draw += UiBuilder_Draw;
PluginServices.DalamudPluginInterface.UiBuilder.OpenConfigUi += UiBuilder_OpenConfigUi;
PluginServices.CommandManager.AddHandler(c_CommandName, new CommandInfo(CommandManager_Handler)
{
PluginServices.Initialize(pluginInterface);
Pilz.Dalamud.PluginServices.Initialize(pluginInterface);
HelpMessage = Resources.Strings.Loc_Command_playertags_v2
});
customTagsContextMenuFeature = new CustomTagsContextMenuFeature(pluginConfiguration, pluginData);
nameplatesTagTargetFeature = new NameplateTagTargetFeature(pluginConfiguration, pluginData);
chatTagTargetFeature = new ChatTagTargetFeature(pluginConfiguration, pluginData);
}
m_PluginConfiguration = PluginConfiguration.LoadPluginConfig() ?? new PluginConfiguration();
m_PluginData = new PluginData(m_PluginConfiguration);
m_PluginConfigurationUI = new PluginConfigurationUI(m_PluginConfiguration, m_PluginData);
public void Dispose()
{
chatTagTargetFeature.Dispose();
nameplatesTagTargetFeature.Dispose();
customTagsContextMenuFeature.Dispose();
PluginServices.DalamudPluginInterface.LanguageChanged -= DalamudPluginInterface_LanguageChanged;
PluginServices.CommandManager.RemoveHandler(c_CommandName);
PluginServices.DalamudPluginInterface.UiBuilder.OpenConfigUi -= UiBuilder_OpenConfigUi;
PluginServices.DalamudPluginInterface.UiBuilder.Draw -= UiBuilder_Draw;
}
Localizer.SetLanguage(PluginServices.DalamudPluginInterface.UiLanguage);
PluginServices.DalamudPluginInterface.LanguageChanged += DalamudPluginInterface_LanguageChanged;
private void DalamudPluginInterface_LanguageChanged(string langCode)
{
Localizer.SetLanguage(langCode);
}
PluginServices.DalamudPluginInterface.UiBuilder.Draw += UiBuilder_Draw;
PluginServices.DalamudPluginInterface.UiBuilder.OpenConfigUi += UiBuilder_OpenConfigUi;
PluginServices.CommandManager.AddHandler(c_CommandName, new CommandInfo(CommandManager_Handler)
{
HelpMessage = Resources.Strings.Loc_Command_playertags_v2
});
m_CustomTagsContextMenuFeature = new CustomTagsContextMenuFeature(m_PluginConfiguration, m_PluginData);
m_NameplatesTagTargetFeature = new NameplateTagTargetFeature(m_PluginConfiguration, m_PluginData);
m_ChatTagTargetFeature = new ChatTagTargetFeature(m_PluginConfiguration, m_PluginData);
}
public void Dispose()
private void CommandManager_Handler(string command, string arguments)
{
switch (command)
{
m_ChatTagTargetFeature.Dispose();
m_NameplatesTagTargetFeature.Dispose();
m_CustomTagsContextMenuFeature.Dispose();
PluginServices.DalamudPluginInterface.LanguageChanged -= DalamudPluginInterface_LanguageChanged;
PluginServices.CommandManager.RemoveHandler(c_CommandName);
PluginServices.DalamudPluginInterface.UiBuilder.OpenConfigUi -= UiBuilder_OpenConfigUi;
PluginServices.DalamudPluginInterface.UiBuilder.Draw -= UiBuilder_Draw;
}
private void DalamudPluginInterface_LanguageChanged(string langCode)
{
Localizer.SetLanguage(langCode);
}
private void CommandManager_Handler(string command, string arguments)
{
switch (command)
{
case c_CommandName:
if (string.IsNullOrWhiteSpace(arguments))
UiBuilder_OpenConfigUi();
else
case c_CommandName:
if (string.IsNullOrWhiteSpace(arguments))
UiBuilder_OpenConfigUi();
else
{
var lowerArgs = arguments.ToLower().Split(' ');
if (lowerArgs.Length >= 1)
{
var lowerArgs = arguments.ToLower().Split(' ');
if (lowerArgs.Length >= 1)
switch (lowerArgs[0])
{
switch (lowerArgs[0])
{
case c_SubCommandName_EnableGlobal:
if (lowerArgs.Length >= 2)
case c_SubCommandName_EnableGlobal:
if (lowerArgs.Length >= 2)
{
switch (lowerArgs[0])
{
switch (lowerArgs[0])
{
case c_CommandArg_On:
m_PluginConfiguration.EnabledGlobal = true;
break;
case c_CommandArg_Off:
m_PluginConfiguration.EnabledGlobal = false;
break;
case c_CommandArg_toggle:
m_PluginConfiguration.EnabledGlobal = !m_PluginConfiguration.EnabledGlobal;
break;
}
case c_CommandArg_On:
pluginConfiguration.EnabledGlobal = true;
break;
case c_CommandArg_Off:
pluginConfiguration.EnabledGlobal = false;
break;
case c_CommandArg_toggle:
pluginConfiguration.EnabledGlobal = !pluginConfiguration.EnabledGlobal;
break;
}
break;
}
}
break;
}
}
break;
}
}
private void UiBuilder_Draw()
{
if (m_PluginConfiguration.IsVisible)
m_PluginConfigurationUI.Draw();
}
private void UiBuilder_OpenConfigUi()
{
m_PluginConfiguration.IsVisible = true;
m_PluginConfiguration.Save(m_PluginData);
}
break;
}
}
private void UiBuilder_Draw()
{
if (pluginConfiguration.IsVisible)
pluginConfigurationUI.Draw();
}
private void UiBuilder_OpenConfigUi()
{
pluginConfiguration.IsVisible = true;
pluginConfiguration.Save(pluginData);
}
}

View File

@@ -1,30 +1,29 @@
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.IoC;
using Dalamud.Plugin;
using Dalamud.Plugin.Services;
using Pilz.Dalamud.NamePlate;
namespace PlayerTags
namespace PlayerTags;
public class PluginServices
{
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!;
[PluginService] public static IDalamudPluginInterface DalamudPluginInterface { get; set; }
[PluginService] public static IPluginLog PluginLog { get; set; }
[PluginService] public static IGameConfig GameConfig { get; set; }
[PluginService] public static IChatGui ChatGui { get; set; }
[PluginService] public static IClientState ClientState { get; set; }
[PluginService] public static ICommandManager CommandManager { get; set; }
[PluginService] public static IDataManager DataManager { get; set; }
[PluginService] public static IFramework Framework { get; set; }
[PluginService] public static IGameGui GameGui { get; set; }
[PluginService] public static IObjectTable ObjectTable { get; set; }
[PluginService] public static IPartyList PartyList { get; set; }
[PluginService] public static IGameInteropProvider GameInteropProvider { get; set; }
[PluginService] public static IContextMenu ContextMenu { get; set; }
[PluginService] public static INamePlateGui NamePlateGui => INamePlateGui.Instance;
public static void Initialize(DalamudPluginInterface pluginInterface)
{
pluginInterface.Create<PluginServices>();
}
public static void Initialize(IDalamudPluginInterface pluginInterface)
{
pluginInterface.Create<PluginServices>();
}
}

View File

@@ -1,7 +1,6 @@
namespace PlayerTags.PluginStrings
namespace PlayerTags.PluginStrings;
public interface IPluginString
{
public interface IPluginString
{
public string Value { get; }
}
public string Value { get; }
}

View File

@@ -1,18 +1,17 @@
namespace PlayerTags.PluginStrings
namespace PlayerTags.PluginStrings;
public class LiteralPluginString : IPluginString
{
public class LiteralPluginString : IPluginString
private string m_Value;
public string Value => m_Value;
public LiteralPluginString(string value)
{
private string m_Value;
public string Value => m_Value;
m_Value = value;
}
public LiteralPluginString(string value)
{
m_Value = value;
}
public override string ToString()
{
return Value;
}
public override string ToString()
{
return Value;
}
}

View File

@@ -1,18 +1,17 @@
namespace PlayerTags.PluginStrings
namespace PlayerTags.PluginStrings;
public class LocalizedPluginString : IPluginString
{
public class LocalizedPluginString : IPluginString
public string Key { get; init; }
public string Value => Localizer.GetString(Key, false);
public LocalizedPluginString(string key)
{
public string Key { get; init; }
public string Value => Localizer.GetString(Key, false);
Key = key;
}
public LocalizedPluginString(string key)
{
Key = key;
}
public override string ToString()
{
return Value;
}
public override string ToString()
{
return Value;
}
}

View File

@@ -1,116 +1,113 @@
using Dalamud.Logging;
using System;
using System;
using System.Globalization;
using System.IO;
using System.Reflection;
namespace PlayerTags
namespace PlayerTags;
/// <summary>
/// Generates names based on existing lists of words.
/// </summary>
public static class RandomNameGenerator
{
/// <summary>
/// Generates names based on existing lists of words.
/// </summary>
public static class RandomNameGenerator
private static string[]? s_Adjectives;
private static string[] Adjectives
{
private static string[]? s_Adjectives;
private static string[] Adjectives
get
{
get
if (s_Adjectives == null)
{
if (s_Adjectives == null)
try
{
try
{
s_Adjectives = File.ReadAllLines(Path.Combine(MyPaths.ResourcePath, Resources.Paths.AdjectivesTxt));
}
catch (Exception ex)
{
PluginLog.Error(ex, $"RandomNameGenerator failed to read adjectives");
}
s_Adjectives = File.ReadAllLines(Path.Combine(MyPaths.ResourcePath, Resources.Paths.AdjectivesTxt));
}
if (s_Adjectives != null)
catch (Exception ex)
{
return s_Adjectives;
PluginServices.PluginLog.Error(ex, $"RandomNameGenerator failed to read adjectives");
}
return new string[] { };
}
if (s_Adjectives != null)
{
return s_Adjectives;
}
return new string[] { };
}
}
private static string[]? s_Nouns;
private static string[] Nouns
{
get
{
if (s_Nouns == null)
{
try
{
s_Nouns = File.ReadAllLines(Path.Combine(MyPaths.ResourcePath, Resources.Paths.NounsTxt));
}
catch (Exception ex)
{
PluginServices.PluginLog.Error(ex, $"RandomNameGenerator failed to read nouns");
}
}
if (s_Nouns != null)
{
return s_Nouns;
}
return new string[] { };
}
}
/// <summary>
/// Generates a name for the given string.
/// </summary>
/// <param name="str">The string to generate a name for.</param>
/// <returns>A generated name.</returns>
public static string? Generate(string str)
{
if (Adjectives == null || Nouns == null)
{
return null;
}
private static string[]? s_Nouns;
private static string[] Nouns
int hash = GetDeterministicHashCode(str);
// Use the seed as the hash so the same player always gets the same name
Random random = new(hash);
var adjective = Adjectives[random.Next(0, Adjectives.Length)];
var noun = Nouns[random.Next(0, Nouns.Length)];
var generatedName = $"{adjective} {noun}";
return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(generatedName);
}
/// <summary>
/// Gets a deterministic hash code for the given string.
/// </summary>
/// <param name="str">The string to hash.</param>
/// <returns>A deterministic hash code.</returns>
private static int GetDeterministicHashCode(string str)
{
unchecked
{
get
int hash1 = (5381 << 16) + 5381;
int hash2 = hash1;
for (int index = 0; index < str.Length; index += 2)
{
if (s_Nouns == null)
hash1 = ((hash1 << 5) + hash1) ^ str[index];
if (index == str.Length - 1)
{
try
{
s_Nouns = File.ReadAllLines(Path.Combine(MyPaths.ResourcePath, Resources.Paths.NounsTxt));
}
catch (Exception ex)
{
PluginLog.Error(ex, $"RandomNameGenerator failed to read nouns");
}
break;
}
if (s_Nouns != null)
{
return s_Nouns;
}
return new string[] { };
}
}
/// <summary>
/// Generates a name for the given string.
/// </summary>
/// <param name="str">The string to generate a name for.</param>
/// <returns>A generated name.</returns>
public static string? Generate(string str)
{
if (Adjectives == null || Nouns == null)
{
return null;
hash2 = ((hash2 << 5) + hash2) ^ str[index + 1];
}
int hash = GetDeterministicHashCode(str);
// 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}";
return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(generatedName);
}
/// <summary>
/// Gets a deterministic hash code for the given string.
/// </summary>
/// <param name="str">The string to hash.</param>
/// <returns>A deterministic hash code.</returns>
private static int GetDeterministicHashCode(string str)
{
unchecked
{
int hash1 = (5381 << 16) + 5381;
int hash2 = hash1;
for (int index = 0; index < str.Length; index += 2)
{
hash1 = ((hash1 << 5) + hash1) ^ str[index];
if (index == str.Length - 1)
{
break;
}
hash2 = ((hash2 << 5) + hash2) ^ str[index + 1];
}
return hash1 + (hash2 * 1566083941);
}
return hash1 + (hash2 * 1566083941);
}
}
}

View File

@@ -5,105 +5,101 @@ using System.Collections.Generic;
using System.Linq;
using System.Numerics;
namespace PlayerTags
namespace PlayerTags;
public static class UIColorHelper
{
public static class UIColorHelper
private class UIColorComparer : IEqualityComparer<UIColor>
{
private class UIColorComparer : IEqualityComparer<UIColor>
public bool Equals(UIColor? left, UIColor? right)
{
public bool Equals(UIColor? left, UIColor? right)
if (left != null && right != null)
{
if (left != null && right != null)
{
return left.UIForeground == right.UIForeground;
}
return false;
return left.UIForeground == right.UIForeground;
}
public int GetHashCode(UIColor obj)
{
return obj.UIForeground.GetHashCode();
}
return false;
}
private static UIColor[] s_UIColors = null!;
public static IEnumerable<UIColor> UIColors
public int GetHashCode(UIColor obj)
{
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));
filteredUIColors.Sort((left, right) =>
{
var leftColor = ToColor(left);
var rightColor = ToColor(right);
ImGui.ColorConvertRGBtoHSV(leftColor.X, leftColor.Y, leftColor.Z, out float leftHue, out float leftSaturation, out float leftValue);
ImGui.ColorConvertRGBtoHSV(rightColor.X, rightColor.Y, rightColor.Z, out float rightHue, out float rightSaturation, out float rightValue);
var hueDifference = leftHue.CompareTo(rightHue);
if (hueDifference != 0)
{
return hueDifference;
}
var valueDifference = leftValue.CompareTo(rightValue);
if (valueDifference != 0)
{
return valueDifference;
}
var saturationDifference = leftSaturation.CompareTo(rightSaturation);
if (saturationDifference != 0)
{
return saturationDifference;
}
return 0;
});
return filteredUIColors.ToArray();
}
return new UIColor[] { };
return obj.UIForeground.GetHashCode();
}
}
private static UIColor[] s_UIColors = null!;
public static IEnumerable<UIColor> UIColors
{
get
{
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));
filteredUIColors.Sort((left, right) =>
{
var leftColor = ToColor(left);
var rightColor = ToColor(right);
ImGui.ColorConvertRGBtoHSV(leftColor.X, leftColor.Y, leftColor.Z, out float leftHue, out float leftSaturation, out float leftValue);
ImGui.ColorConvertRGBtoHSV(rightColor.X, rightColor.Y, rightColor.Z, out float rightHue, out float rightSaturation, out float rightValue);
var hueDifference = leftHue.CompareTo(rightHue);
if (hueDifference != 0)
{
return hueDifference;
}
var valueDifference = leftValue.CompareTo(rightValue);
if (valueDifference != 0)
{
return valueDifference;
}
var saturationDifference = leftSaturation.CompareTo(rightSaturation);
if (saturationDifference != 0)
{
return saturationDifference;
}
return 0;
});
return filteredUIColors.ToArray();
}
return new UIColor[] { };
}
}

View File

@@ -1,18 +1,12 @@
{
"version": 1,
"dependencies": {
"net7.0-windows7.0": {
"Dalamud.ContextMenu": {
"type": "Direct",
"requested": "[1.2.3, )",
"resolved": "1.2.3",
"contentHash": "ydemplF7DNcA/LLeongDVzWUD/JV0Fw3EwA2+P0jYq3Le2ZYSt4U8qyJq4FyoChqt0lFG8BxYCAzfeWp4Jmnqw=="
},
"net8.0-windows7.0": {
"DalamudPackager": {
"type": "Direct",
"requested": "[2.1.11, )",
"resolved": "2.1.11",
"contentHash": "9qlAWoRRTiL/geAvuwR/g6Bcbrd/bJJgVnB/RurBiyKs6srsP0bvpoo8IK+Eg8EA6jWeM6/YJWs66w4FIAzqPw=="
"requested": "[2.1.13, )",
"resolved": "2.1.13",
"contentHash": "rMN1omGe8536f4xLMvx9NwfvpAc9YFFfeXJ1t4P4PE6Gu8WCIoFliR1sh07hM+bfODmesk/dvMbji7vNI+B/pQ=="
},
"pilz.dalamud": {
"type": "Project"

View File

@@ -2,12 +2,23 @@
Lightweight job visibility in nameplates and chat. Create custom tags and add them to players with the context menu.
# Contribution
## Install
### Via official plugin repo
This plugin is available via the official plugin repository of Dalamud. Just search for "Player Tags" and install.
### Via third party repo
For the case you want instant updates, feel free to add my third party repo for this plugin:\
`https://git.pilzinsel64.de/pilzinsel64/playertags/-/snippets/5/raw/main/pluginmaster.json`
## Contribution
You want to help and contribute to this project? \
Feel free to open a pull request with your enhancements or bug fixes!
# Translation
## Translation
You want to help and translate this plugin to your/other language(s)? \
Start translation [here on Crowdin](https://crowdin.com/project/playertags)!
@@ -24,6 +35,6 @@ If you want to contribute to a language that isn't listed, just tell me with an
| Chinese | Andypsl8, yqdyqd
| ... | You?
# Credits
## Credits
This plugin was originally developed by [r00telement](https://github.com/r00telement) but forked and continued by [Pilzinsel64](https://github.com/Pilzinsel64).