diff --git a/Pilz.Dalamud/ActivityContexts/ActivityContext.cs b/Pilz.Dalamud/ActivityContexts/ActivityContext.cs new file mode 100644 index 0000000..ae9fd79 --- /dev/null +++ b/Pilz.Dalamud/ActivityContexts/ActivityContext.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Pilz.Dalamud.ActivityContexts +{ + public class ActivityContext + { + public ActivityType ActivityType { get; init; } + public ZoneType ZoneType { get; init; } + + public ActivityContext(ActivityType activityType, ZoneType zoneType) + { + ActivityType = activityType; + ZoneType = zoneType; + } + + public bool IsInDuty + { + get => ZoneType != ZoneType.Overworld; + } + } +} diff --git a/Pilz.Dalamud/ActivityContexts/ActivityContextManager.cs b/Pilz.Dalamud/ActivityContexts/ActivityContextManager.cs new file mode 100644 index 0000000..bf218d6 --- /dev/null +++ b/Pilz.Dalamud/ActivityContexts/ActivityContextManager.cs @@ -0,0 +1,85 @@ +using Dalamud.Logging; +using Lumina.Excel; +using Lumina.Excel.GeneratedSheets; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Pilz.Dalamud.ActivityContexts +{ + public class ActivityContextManager : IDisposable + { + public delegate void ActivityContextChangedEventHandler(ActivityContextManager sender, ActivityContext activityContext); + public event ActivityContextChangedEventHandler ActivityContextChanged; + + private readonly ExcelSheet contentFinderConditionsSheet; + + public ActivityContext CurrentActivityContext { get; protected set; } + + public ActivityContextManager() + { + // Get condition sheet + contentFinderConditionsSheet = PluginServices.DataManager.GameData.GetExcelSheet(); + + // Checks current territory type (if enabled/installed during a dutiy e.g.) + CheckCurrentTerritory(); + + // Enable event for automatic checks + PluginServices.ClientState.TerritoryChanged += ClientState_TerritoryChanged; + } + + public void Dispose() + { + PluginServices.ClientState.TerritoryChanged -= ClientState_TerritoryChanged; + } + + private void ClientState_TerritoryChanged(object? sender, ushort e) + { + CheckCurrentTerritory(); + } + + private void CheckCurrentTerritory() + { + var content = contentFinderConditionsSheet.FirstOrDefault(c => c.TerritoryType.Row == PluginServices.ClientState.TerritoryType); + ActivityType newActivityContext; + ZoneType newZoneType; + + if (content == null) + { + // No content found, so we must be on the overworld + newActivityContext = ActivityType.None; + newZoneType = ZoneType.Overworld; + } + else + { + // Check for ActivityContext + if (content.PvP) + newActivityContext = ActivityType.PvpDuty; + else + newActivityContext = ActivityType.PveDuty; + + // Find correct member type + var memberType = content.ContentMemberType.Row; + if (content.RowId == 16 || content.RowId == 15) + memberType = 2; // Praetorium and Castrum Meridianum + else if (content.RowId == 735 || content.RowId == 778) + memberType = 127; // Bozja + + // Check for ZoneType + newZoneType = memberType switch + { + 2 => ZoneType.Dungeon, + 3 => ZoneType.Raid, + 4 => ZoneType.AllianceRaid, + 127 => ZoneType.Foray, + _ => ZoneType.Dungeon, + }; + } + + CurrentActivityContext = new(newActivityContext, newZoneType); + ActivityContextChanged?.Invoke(this, CurrentActivityContext); + } + } +} diff --git a/Pilz.Dalamud/ActivityContexts/ActivityType.cs b/Pilz.Dalamud/ActivityContexts/ActivityType.cs new file mode 100644 index 0000000..d82d5ab --- /dev/null +++ b/Pilz.Dalamud/ActivityContexts/ActivityType.cs @@ -0,0 +1,18 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Pilz.Dalamud.ActivityContexts +{ + [JsonConverter(typeof(StringEnumConverter))] + public enum ActivityType + { + None = 0x0, + PveDuty = 0x1, + PvpDuty = 0x2 + } +} diff --git a/Pilz.Dalamud/ActivityContexts/ZoneType.cs b/Pilz.Dalamud/ActivityContexts/ZoneType.cs new file mode 100644 index 0000000..6800511 --- /dev/null +++ b/Pilz.Dalamud/ActivityContexts/ZoneType.cs @@ -0,0 +1,20 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Pilz.Dalamud.ActivityContexts +{ + [JsonConverter(typeof(StringEnumConverter))] + public enum ZoneType + { + Overworld, + Dungeon, + Raid, + AllianceRaid, + Foray + } +} diff --git a/Pilz.Dalamud/Extensions.cs b/Pilz.Dalamud/Extensions.cs new file mode 100644 index 0000000..08008a4 --- /dev/null +++ b/Pilz.Dalamud/Extensions.cs @@ -0,0 +1,41 @@ +using Dalamud.Game.Text.SeStringHandling; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Pilz.Dalamud +{ + public static class Extensions + { + /// + /// Removes a Payload from a given SeString. + /// Using SeString.Payloads.Remove() does not use the reference to compare for some reason. Tis is a workaround. + /// + /// + /// + public static void Remove(this SeString seString, Payload payload) + { + Remove(seString.Payloads, payload); + } + + /// + /// Removes a Payload from a given list. + /// Using List.Remove() does not use the reference to compare for some reason. Tis is a workaround. + /// + /// + /// + public static void Remove(this List payloads, Payload payload) + { + for (int i = 0; i < payloads.Count; i++) + { + if (ReferenceEquals(payloads[i], payload)) + { + payloads.RemoveAt(i); + break; + } + } + } + } +} diff --git a/Pilz.Dalamud/GameInterfaceHelper.cs b/Pilz.Dalamud/GameInterfaceHelper.cs new file mode 100644 index 0000000..f786f13 --- /dev/null +++ b/Pilz.Dalamud/GameInterfaceHelper.cs @@ -0,0 +1,153 @@ +using Dalamud.Game.Text.SeStringHandling; +using FFXIVClientStructs.FFXIV.Client.System.Memory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Pilz.Dalamud +{ + 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(IntPtr ptr) + { + Marshal.FreeHGlobal(ptr); + } + + public static void PluginFree(ref IntPtr ptr) + { + PluginFree(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; + } + } +} diff --git a/Pilz.Dalamud/Icons/JobIconSet.cs b/Pilz.Dalamud/Icons/JobIconSet.cs new file mode 100644 index 0000000..db7acc0 --- /dev/null +++ b/Pilz.Dalamud/Icons/JobIconSet.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace Pilz.Dalamud.Icons +{ + public class JobIconSet + { + private readonly int[] icons; + + public float IconScale { get; init; } + + public JobIconSet(int[] icons, float iconScale) + { + this.icons = icons; + IconScale = iconScale; + } + + public int GetIcon(uint jobID) + { + return icons[jobID - 1]; + } + } +} diff --git a/Pilz.Dalamud/Icons/JobIconSetName.cs b/Pilz.Dalamud/Icons/JobIconSetName.cs new file mode 100644 index 0000000..7d19a2c --- /dev/null +++ b/Pilz.Dalamud/Icons/JobIconSetName.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Pilz.Dalamud.Icons +{ + public enum JobIconSetName + { + Gold, + Framed, + Glowing, + Blue, + Red, + Purple, + Black, + Yellow, + Orange, + Green, + Grey, + Role + } +} diff --git a/Pilz.Dalamud/Icons/JobIconSets.cs b/Pilz.Dalamud/Icons/JobIconSets.cs new file mode 100644 index 0000000..376b7f5 --- /dev/null +++ b/Pilz.Dalamud/Icons/JobIconSets.cs @@ -0,0 +1,128 @@ +using Lumina.Excel.GeneratedSheets; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Pilz.Dalamud.Icons +{ + public class JobIconSets + { + private readonly Dictionary iconSets = new(); + + public JobIconSets() + { + Add(JobIconSetName.Gold, new[] + { + 62001, 62002, 62003, 62004, 62005, 62006, 62007, 62008, 62009, 62010, + 62011, 62012, 62013, 62014, 62015, 62016, 62017, 62018, 62019, 62020, + 62021, 62022, 62023, 62024, 62025, 62026, 62027, 62028, 62029, 62030, + 62031, 62032, 62033, 62034, 62035, 62036, 62037, 62038, 62039, 62040 + }, 1); + + Add(JobIconSetName.Framed, new[] + { + 62101, 62102, 62103, 62104, 62105, 62106, 62107, 62108, 62109, 62110, + 62111, 62112, 62113, 62114, 62115, 62116, 62117, 62118, 62119, 62120, + 62121, 62122, 62123, 62124, 62125, 62126, 62127, 62128, 62129, 62130, + 62131, 62132, 62133, 62134, 62135, 62136, 62137, 62138, 62139, 62140 + }); + + Add(JobIconSetName.Glowing, new[] + { + 62301, 62302, 62303, 62304, 62305, 62306, 62307, 62310, 62311, 62312, + 62313, 62314, 62315, 62316, 62317, 62318, 62319, 62320, 62401, 62402, + 62403, 62404, 62405, 62406, 62407, 62308, 62408, 62409, 62309, 62410, + 62411, 62412, 62413, 62414, 62415, 62416, 62417, 62418, 62419, 62420 + }); + + Add(JobIconSetName.Grey, new[] + { + 91022, 91023, 91024, 91025, 91026, 91028, 91029, 91031, 91032, 91033, + 91034, 91035, 91036, 91037, 91038, 91039, 91040, 91041, 91079, 91080, + 91081, 91082, 91083, 91084, 91085, 91030, 91086, 91087, 91121, 91122, + 91125, 91123, 91124, 91127, 91128, 91129, 91130, 91131, 91132, 91133 + }, 2); + + Add(JobIconSetName.Black, new[] + { + 91522, 91523, 91524, 91525, 91526, 91528, 91529, 91531, 91532, 91533, + 91534, 91535, 91536, 91537, 91538, 91539, 91540, 91541, 91579, 91580, + 91581, 91582, 91583, 91584, 91585, 91530, 91586, 91587, 91621, 91622, + 91625, 91623, 91624, 91627, 91628, 91629, 91630, 91631, 91632, 91633 + }, 2); + + Add(JobIconSetName.Yellow, new[] + { + 92022, 92023, 92024, 92025, 92026, 92028, 92029, 92031, 92032, 92033, + 92034, 92035, 92036, 92037, 92038, 92039, 92040, 92041, 92079, 92080, + 92081, 92082, 92083, 92084, 92085, 92030, 92086, 92087, 92121, 92122, + 92125, 92123, 92124, 92127, 92128, 92129, 92130, 92131, 92132, 92133 + }, 2); + + Add(JobIconSetName.Orange, new[] + { + 92522, 92523, 92524, 92525, 92526, 92528, 92529, 92531, 92532, 92533, + 92534, 92535, 92536, 92537, 92538, 92539, 92540, 92541, 92579, 92580, + 92581, 92582, 92583, 92584, 92585, 92530, 92586, 92587, 92621, 92622, + 92625, 92623, 92624, 92627, 92628, 92629, 92630, 92631, 92632, 92633 + }, 2); + + Add(JobIconSetName.Red, new[] + { + 93022, 93023, 93024, 93025, 93026, 93028, 93029, 93031, 93032, 93033, + 93034, 93035, 93036, 93037, 93038, 93039, 93040, 93041, 93079, 93080, + 93081, 93082, 93083, 93084, 93085, 93030, 93086, 93087, 93121, 93122, + 93125, 93123, 93124, 93127, 93128, 93129, 93130, 93131, 93132, 93133 + }, 2); + + Add(JobIconSetName.Purple, new[] + { + 93522, 93523, 93524, 93525, 93526, 93528, 93529, 93531, 93532, 93533, + 93534, 93535, 93536, 93537, 93538, 93539, 93540, 93541, 93579, 93580, + 93581, 93582, 93583, 93584, 93585, 93530, 93586, 93587, 93621, 93622, + 93625, 93623, 93624, 93627, 93628, 93629, 93630, 93631, 93632, 93633 + }, 2); + + Add(JobIconSetName.Blue, new[] + { + 94022, 94023, 94024, 94025, 94026, 94028, 94029, 94031, 94032, 94033, + 94034, 94035, 94036, 94037, 94038, 94039, 94040, 94041, 94079, 94080, + 94081, 94082, 94083, 94084, 94085, 94030, 94086, 94087, 94121, 94122, + 94125, 94123, 94124, 94127, 94128, 94129, 94130, 94131, 94132, 94133 + }, 2); + + Add(JobIconSetName.Green, new[] + { + 94522, 94523, 94524, 94525, 94526, 94528, 94529, 94531, 94532, 94533, + 94534, 94535, 94536, 94537, 94538, 94539, 94540, 94541, 94579, 94580, + 94581, 94582, 94583, 94584, 94585, 94530, 94586, 94587, 94621, 94622, + 94625, 94623, 94624, 94627, 94628, 94629, 94630, 94631, 94632, 94633 + }, 2); + + Add(JobIconSetName.Role, new[] + { + 62581, 62584, 62581, 62584, 62586, 62582, 62502, 62502, 62503, 62504, + 62505, 62506, 62507, 62508, 62509, 62510, 62511, 62512, 62581, 62584, + 62581, 62584, 62586, 62582, 62587, 62587, 62587, 62582, 62584, 62584, + 62586, 62581, 62582, 62584, 62587, 62587, 62581, 62586, 62584, 62582 + }); + } + + private void Add(JobIconSetName id, int[] icons, float scale = 1f) + { + iconSets[id] = new JobIconSet(icons, scale); + } + + public int GetJobIcon(JobIconSetName set, uint jobId) + { + return iconSets[set].GetIcon(jobId); + } + + public float GetJobIconSale(JobIconSetName set) + { + return iconSets[set].IconScale; + } + } +} diff --git a/Pilz.Dalamud/Nameplates/EventArgs/AddonNamePlate_SetPlayerNameEventArgs.cs b/Pilz.Dalamud/Nameplates/EventArgs/AddonNamePlate_SetPlayerNameEventArgs.cs new file mode 100644 index 0000000..3a67e2b --- /dev/null +++ b/Pilz.Dalamud/Nameplates/EventArgs/AddonNamePlate_SetPlayerNameEventArgs.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Pilz.Dalamud.Nameplates.EventArgs +{ + public class AddonNamePlate_SetPlayerNameEventArgs : HookWithResultBaseEventArgs + { + public IntPtr PlayerNameplateObjectPtr { get; set; } + public IntPtr TitlePtr { get; set; } + public IntPtr NamePtr { get; set; } + public IntPtr FreeCompanyPtr { get; set; } + public bool IsTitleAboveName { get; set; } + public bool IsTitleVisible { get; set; } + public int IconID { get; set; } + } +} diff --git a/Pilz.Dalamud/Nameplates/EventArgs/AddonNamePlate_SetPlayerNameManagedEventArgs.cs b/Pilz.Dalamud/Nameplates/EventArgs/AddonNamePlate_SetPlayerNameManagedEventArgs.cs new file mode 100644 index 0000000..494bdd1 --- /dev/null +++ b/Pilz.Dalamud/Nameplates/EventArgs/AddonNamePlate_SetPlayerNameManagedEventArgs.cs @@ -0,0 +1,42 @@ +using Dalamud.Game.Text.SeStringHandling; +using Pilz.Dalamud.Nameplates.Model; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Pilz.Dalamud.Nameplates.EventArgs +{ + public class AddonNamePlate_SetPlayerNameManagedEventArgs : HookWithResultManagedBaseEventArgs + { + public new AddonNamePlate_SetPlayerNameEventArgs OriginalEventArgs + { + get => base.OriginalEventArgs as AddonNamePlate_SetPlayerNameEventArgs; + set => base.OriginalEventArgs = value; + } + + public SafeNameplateObject SafeNameplateObject { get; set; } + public SeString Title { get; set; } + public SeString Name { get; set; } + public SeString FreeCompany { get; set; } + + public bool IsTitleAboveName + { + get => OriginalEventArgs.IsTitleAboveName; + set => OriginalEventArgs.IsTitleAboveName = value; + } + + public bool IsTitleVisible + { + get => OriginalEventArgs.IsTitleVisible; + set => OriginalEventArgs.IsTitleVisible = value; + } + + public int IconID + { + get => OriginalEventArgs.IconID; + set => OriginalEventArgs.IconID = value; + } + } +} diff --git a/Pilz.Dalamud/Nameplates/EventArgs/HookBaseEventArgs.cs b/Pilz.Dalamud/Nameplates/EventArgs/HookBaseEventArgs.cs new file mode 100644 index 0000000..2df854d --- /dev/null +++ b/Pilz.Dalamud/Nameplates/EventArgs/HookBaseEventArgs.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Pilz.Dalamud.Nameplates.EventArgs +{ + public abstract class HookBaseEventArgs + { + internal event Action CallOriginal; + + public void Original() + { + CallOriginal?.Invoke(); + } + } +} diff --git a/Pilz.Dalamud/Nameplates/EventArgs/HookManagedBaseEventArgs.cs b/Pilz.Dalamud/Nameplates/EventArgs/HookManagedBaseEventArgs.cs new file mode 100644 index 0000000..71df630 --- /dev/null +++ b/Pilz.Dalamud/Nameplates/EventArgs/HookManagedBaseEventArgs.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Pilz.Dalamud.Nameplates.EventArgs +{ + public abstract class HookManagedBaseEventArgs + { + public HookBaseEventArgs OriginalEventArgs { get; internal set; } + } +} diff --git a/Pilz.Dalamud/Nameplates/EventArgs/HookWithResultBaseEventArgs.cs b/Pilz.Dalamud/Nameplates/EventArgs/HookWithResultBaseEventArgs.cs new file mode 100644 index 0000000..ad3c130 --- /dev/null +++ b/Pilz.Dalamud/Nameplates/EventArgs/HookWithResultBaseEventArgs.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Pilz.Dalamud.Nameplates.EventArgs +{ + public abstract class HookWithResultBaseEventArgs + { + internal event Func CallOriginal; + + public TResult Result { get; set; } + + // Call Original based on the given properties + public TResult Original() + { + return CallOriginal.Invoke(); + } + } +} diff --git a/Pilz.Dalamud/Nameplates/EventArgs/HookWithResultManagedBaseEventArgs.cs b/Pilz.Dalamud/Nameplates/EventArgs/HookWithResultManagedBaseEventArgs.cs new file mode 100644 index 0000000..59993fd --- /dev/null +++ b/Pilz.Dalamud/Nameplates/EventArgs/HookWithResultManagedBaseEventArgs.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Pilz.Dalamud.Nameplates.EventArgs +{ + public abstract class HookWithResultManagedBaseEventArgs + { + public HookWithResultBaseEventArgs OriginalEventArgs { get; internal set; } + } +} diff --git a/Pilz.Dalamud/Nameplates/Model/SafeAddonNameplate.cs b/Pilz.Dalamud/Nameplates/Model/SafeAddonNameplate.cs new file mode 100644 index 0000000..2b8736b --- /dev/null +++ b/Pilz.Dalamud/Nameplates/Model/SafeAddonNameplate.cs @@ -0,0 +1,43 @@ +using Dalamud.Logging; +using Dalamud.Plugin; +using FFXIVClientStructs.FFXIV.Client.UI; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Pilz.Dalamud.Nameplates.Model +{ + public class SafeAddonNameplate + { + private readonly DalamudPluginInterface Interface; + + public IntPtr Pointer => PluginServices.GameGui.GetAddonByName("NamePlate", 1); + + public SafeAddonNameplate(DalamudPluginInterface pluginInterface) + { + Interface = pluginInterface; + } + + public unsafe SafeNameplateObject GetNamePlateObject(int index) + { + SafeNameplateObject result = null; + + if (Pointer != IntPtr.Zero) + { + var npObjectArrayPtrPtr = Pointer + Marshal.OffsetOf(typeof(AddonNamePlate), nameof(AddonNamePlate.NamePlateObjectArray)).ToInt32(); + var npObjectArrayPtr = Marshal.ReadIntPtr(npObjectArrayPtrPtr); + + if (npObjectArrayPtr != IntPtr.Zero) + { + var npObjectPtr = npObjectArrayPtr + Marshal.SizeOf(typeof(AddonNamePlate.NamePlateObject)) * index; + result = new(npObjectPtr, index); + } + } + + return result; + } + } +} diff --git a/Pilz.Dalamud/Nameplates/Model/SafeNameplateInfo.cs b/Pilz.Dalamud/Nameplates/Model/SafeNameplateInfo.cs new file mode 100644 index 0000000..5076d63 --- /dev/null +++ b/Pilz.Dalamud/Nameplates/Model/SafeNameplateInfo.cs @@ -0,0 +1,57 @@ +using FFXIVClientStructs.FFXIV.Client.System.String; +using FFXIVClientStructs.FFXIV.Client.UI; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Pilz.Dalamud.Nameplates.Model +{ + public class SafeNameplateInfo + { + public readonly IntPtr Pointer; + public readonly RaptureAtkModule.NamePlateInfo Data; + + public SafeNameplateInfo(IntPtr pointer) + { + Pointer = pointer; + Data = Marshal.PtrToStructure(Pointer); + } + + internal IntPtr NameAddress => GetStringPtr(nameof(RaptureAtkModule.NamePlateInfo.Name)); + internal IntPtr FcNameAddress => GetStringPtr(nameof(RaptureAtkModule.NamePlateInfo.FcName)); + internal IntPtr TitleAddress => GetStringPtr(nameof(RaptureAtkModule.NamePlateInfo.Title)); + internal IntPtr DisplayTitleAddress => GetStringPtr(nameof(RaptureAtkModule.NamePlateInfo.DisplayTitle)); + internal IntPtr LevelTextAddress => GetStringPtr(nameof(RaptureAtkModule.NamePlateInfo.LevelText)); + + public string Name => GetString(NameAddress); + public string FcName => GetString(FcNameAddress); + public string Title => GetString(TitleAddress); + public string DisplayTitle => GetString(DisplayTitleAddress); + public string LevelText => GetString(LevelTextAddress); + + //public bool IsPlayerCharacter() => XivApi.IsPlayerCharacter(Data.ObjectID.ObjectID); + + //public bool IsPartyMember() => XivApi.IsPartyMember(Data.ObjectID.ObjectID); + + //public bool IsAllianceMember() => XivApi.IsAllianceMember(Data.ObjectID.ObjectID); + + //public uint GetJobID() => GetJobId(Data.ObjectID.ObjectID); + + private unsafe IntPtr GetStringPtr(string name) + { + var namePtr = Pointer + Marshal.OffsetOf(typeof(RaptureAtkModule.NamePlateInfo), name).ToInt32(); + var stringPtrPtr = namePtr + Marshal.OffsetOf(typeof(Utf8String), nameof(Utf8String.StringPtr)).ToInt32(); + var stringPtr = Marshal.ReadIntPtr(stringPtrPtr); + return stringPtr; + } + + private string GetString(IntPtr stringPtr) + { + return Marshal.PtrToStringUTF8(stringPtr); + } + } +} diff --git a/Pilz.Dalamud/Nameplates/Model/SafeNameplateObject.cs b/Pilz.Dalamud/Nameplates/Model/SafeNameplateObject.cs new file mode 100644 index 0000000..6a39ba0 --- /dev/null +++ b/Pilz.Dalamud/Nameplates/Model/SafeNameplateObject.cs @@ -0,0 +1,128 @@ +using Dalamud.Logging; +using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Component.GUI; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Pilz.Dalamud.Nameplates.Model +{ + public class SafeNameplateObject + { + public IntPtr Pointer { get; } + public AddonNamePlate.NamePlateObject Data { get; } + + private int _Index; + private SafeNameplateInfo _NamePlateInfo; + + public SafeNameplateObject(IntPtr pointer, int index = -1) + { + Pointer = pointer; + Data = Marshal.PtrToStructure(pointer); + _Index = index; + } + + public int Index + { + get + { + int result = _Index; + + if (_Index == -1) + { + var addon = XivApi.GetSafeAddonNamePlate(); + var npObject0 = addon.GetNamePlateObject(0); + + if (npObject0 == null) + result = -1; // NamePlateObject0 was null + else + { + var npObjectBase = npObject0.Pointer; + var npObjectSize = Marshal.SizeOf(typeof(AddonNamePlate.NamePlateObject)); + var index = (Pointer.ToInt64() - npObjectBase.ToInt64()) / npObjectSize; + + if (index < 0 || index >= 50) + result = -2; // NamePlateObject index was out of bounds + else + result = _Index = (int)index; + } + } + + return result; + } + } + + public SafeNameplateInfo NamePlateInfo + { + get + { + SafeNameplateInfo result = null; + + if (_NamePlateInfo != null) + { + var rapturePtr = XivApi.RaptureAtkModulePtr; + + if (rapturePtr != IntPtr.Zero) + { + var npInfoArrayPtr = rapturePtr + Marshal.OffsetOf(typeof(RaptureAtkModule), nameof(RaptureAtkModule.NamePlateInfoArray)).ToInt32(); + var npInfoPtr = npInfoArrayPtr + Marshal.SizeOf(typeof(RaptureAtkModule.NamePlateInfo)) * Index; + result = _NamePlateInfo = new SafeNameplateInfo(npInfoPtr); + } + } + + return result; + } + } + + #region Getters + + public unsafe IntPtr IconImageNodeAddress => Marshal.ReadIntPtr(Pointer + Marshal.OffsetOf(typeof(AddonNamePlate.NamePlateObject), nameof(AddonNamePlate.NamePlateObject.IconImageNode)).ToInt32()); + public unsafe IntPtr NameNodeAddress => Marshal.ReadIntPtr(Pointer + Marshal.OffsetOf(typeof(AddonNamePlate.NamePlateObject), nameof(AddonNamePlate.NamePlateObject.NameText)).ToInt32()); + + public AtkImageNode IconImageNode => Marshal.PtrToStructure(IconImageNodeAddress); + public AtkTextNode NameTextNode => Marshal.PtrToStructure(NameNodeAddress); + + #endregion + + public unsafe bool IsVisible => Data.IsVisible; + public unsafe bool IsLocalPlayer => Data.IsLocalPlayer; + public bool IsPlayer => Data.NameplateKind == 0; + + //public void SetIconScale(float scale, bool force = false) + //{ + // if (force || IconImageNode.AtkResNode.ScaleX != scale || IconImageNode.AtkResNode.ScaleY != scale) + // { + // Instance.SetNodeScale(IconImageNodeAddress, scale, scale); + // } + //} + + //public void SetNameScale(float scale, bool force = false) + //{ + // if (force || NameTextNode.AtkResNode.ScaleX != scale || NameTextNode.AtkResNode.ScaleY != scale) + // { + // Instance.SetNodeScale(NameNodeAddress, scale, scale); + // } + //} + + //public unsafe void SetName(IntPtr ptr) + //{ + // NameTextNode.SetText("aaa"); + //} + + //public void SetIcon(int icon) + //{ + // IconImageNode.LoadIconTexture(icon, 1); + //} + + public void SetIconPosition(short x, short y) + { + var iconXAdjustPtr = Pointer + Marshal.OffsetOf(typeof(AddonNamePlate.NamePlateObject), nameof(AddonNamePlate.NamePlateObject.IconXAdjust)).ToInt32(); + var iconYAdjustPtr = Pointer + Marshal.OffsetOf(typeof(AddonNamePlate.NamePlateObject), nameof(AddonNamePlate.NamePlateObject.IconYAdjust)).ToInt32(); + Marshal.WriteInt16(iconXAdjustPtr, x); + Marshal.WriteInt16(iconYAdjustPtr, y); + } + } +} diff --git a/Pilz.Dalamud/Nameplates/Model/StatusIcons.cs b/Pilz.Dalamud/Nameplates/Model/StatusIcons.cs new file mode 100644 index 0000000..89d7448 --- /dev/null +++ b/Pilz.Dalamud/Nameplates/Model/StatusIcons.cs @@ -0,0 +1,31 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Pilz.Dalamud.Nameplates.Model +{ + [JsonConverter(typeof(StringEnumConverter))] + public enum StatusIcons + { + Disconnecting = 061503, + InDuty = 061506, + ViewingCutscene = 061508, + Busy = 061509, + Idle = 061511, + DutyFinder = 061517, + PartyLeader = 061521, + PartyMember = 061522, + RolePlaying = 061545, + GroupPose = 061546, + NewAdventurer = 061523, + Mentor = 061540, + MentorPvE = 061542, + MentorCrafting = 061543, + MentorPvP = 061544, + Returner = 061547, + } +} diff --git a/Pilz.Dalamud/Nameplates/NameplateHooks.cs b/Pilz.Dalamud/Nameplates/NameplateHooks.cs new file mode 100644 index 0000000..1d28eb4 --- /dev/null +++ b/Pilz.Dalamud/Nameplates/NameplateHooks.cs @@ -0,0 +1,203 @@ +using Dalamud.Hooking; +using Pilz.Dalamud.Nameplates.EventArgs; +using Dalamud.Utility.Signatures; +using ImGuiNET; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Pilz.Dalamud.Nameplates.Model; +using Lumina.Excel.GeneratedSheets; +using System.Xml.Linq; + +namespace Pilz.Dalamud.Nameplates +{ + public class NameplateHooks : IDisposable + { + /// + /// Will be executed when the the Game wants to update the content of a nameplate with the details of the Player. + /// + public event AddonNamePlate_SetPlayerNameEventHandler AddonNamePlate_SetPlayerName; + public delegate void AddonNamePlate_SetPlayerNameEventHandler(AddonNamePlate_SetPlayerNameEventArgs eventArgs); + + /// + /// Will be executed when the the Game wants to update the content of a nameplate with the details of the Player. + /// This will event acts on a higher level with SeString instead of IntPtr e.g. + /// + public event AddonNamePlate_SetPlayerNameManagedEventHandler AddonNamePlate_SetPlayerNameManaged; + public delegate void AddonNamePlate_SetPlayerNameManagedEventHandler(AddonNamePlate_SetPlayerNameManagedEventArgs eventArgs); + + [Signature("48 89 5C 24 ?? 48 89 6C 24 ?? 56 57 41 54 41 56 41 57 48 83 EC 40 44 0F B6 E2", DetourName = nameof(SetPlayerNameplateDetour))] + private readonly Hook? hook_AddonNamePlate_SetPlayerNameplateDetour = null; + private unsafe delegate IntPtr AddonNamePlate_SetPlayerNameplateDetour(IntPtr playerNameplateObjectPtr, bool isTitleAboveName, bool isTitleVisible, IntPtr titlePtr, IntPtr namePtr, IntPtr freeCompanyPtr, int iconId); + + /// + /// Defines if all hooks are enabled. If this is false, then there might be something wrong or the class already has been disposed. + /// + public bool IsValid + { + get + { + var isValid = true; + + isValid &= IsHookEnabled(hook_AddonNamePlate_SetPlayerNameplateDetour); + + return isValid; + } + } + + /// + /// Create a new instance of NAmeplateHooks and automatically initialize and enable all Hooks. + /// + public NameplateHooks() + { + SignatureHelper.Initialise(this); + } + + ~NameplateHooks() + { + Dispose(); + } + + public void Dispose() + { + Unhook(); + } + + /// + /// Initialize and enable all Hooks. + /// + internal void Initialize() + { + hook_AddonNamePlate_SetPlayerNameplateDetour?.Enable(); + } + + /// + /// Disable all Hooks. + /// + internal void Unhook() + { + hook_AddonNamePlate_SetPlayerNameplateDetour?.Disable(); + } + + private static bool IsHookEnabled(Hook hook) where T : Delegate + { + return hook != null && hook.IsEnabled; + } + + private IntPtr SetPlayerNameplateDetour(IntPtr playerNameplateObjectPtr, bool isTitleAboveName, bool isTitleVisible, IntPtr titlePtr, IntPtr namePtr, IntPtr freeCompanyPtr, int iconId) + { + var result = IntPtr.Zero; + + if (IsHookEnabled(hook_AddonNamePlate_SetPlayerNameplateDetour)) + { + var eventArgs = new AddonNamePlate_SetPlayerNameEventArgs + { + PlayerNameplateObjectPtr = playerNameplateObjectPtr, + TitlePtr = titlePtr, + NamePtr = namePtr, + FreeCompanyPtr = freeCompanyPtr, + IsTitleAboveName = isTitleAboveName, + IsTitleVisible = isTitleVisible, + IconID = iconId + }; + + void callOriginal() + { + eventArgs.Result = eventArgs.Original(); + } + + // Add handler for the Original call + eventArgs.CallOriginal += () => + { + return hook_AddonNamePlate_SetPlayerNameplateDetour.Original( + eventArgs.PlayerNameplateObjectPtr, + eventArgs.IsTitleAboveName, + eventArgs.IsTitleVisible, + eventArgs.TitlePtr, + eventArgs.NamePtr, + eventArgs.FreeCompanyPtr, + eventArgs.IconID); + }; + + // Invoke Event + var hasDefaultHookEvent = AddonNamePlate_SetPlayerName != null; + AddonNamePlate_SetPlayerName?.Invoke(eventArgs); + + if (AddonNamePlate_SetPlayerNameManaged != null) + { + var freeTitle = false; + var freeName = false; + var freeFreeCompany = false; + + // Create NamePlateObject if possible + var namePlateObj = new SafeNameplateObject(playerNameplateObjectPtr); + + // Create new event + var managedEventArgs = new AddonNamePlate_SetPlayerNameManagedEventArgs + { + OriginalEventArgs = eventArgs, + SafeNameplateObject = namePlateObj, + Title = GameInterfaceHelper.ReadSeString(eventArgs.TitlePtr), + Name = GameInterfaceHelper.ReadSeString(eventArgs.NamePtr), + FreeCompany = GameInterfaceHelper.ReadSeString(eventArgs.FreeCompanyPtr) + }; + + // Get raw string content + var titleRaw = managedEventArgs.Title.Encode(); + var nameRaw = managedEventArgs.Name.Encode(); + var freeCompanyRaw = managedEventArgs.FreeCompany.Encode(); + + // Invoke Managed Event + AddonNamePlate_SetPlayerNameManaged.Invoke(managedEventArgs); + + // Get new Title string ontent + var titleNewRaw = managedEventArgs.Title.Encode(); + if (!titleRaw.SequenceEqual(titleNewRaw)) + { + eventArgs.TitlePtr = GameInterfaceHelper.PluginAllocate(titleNewRaw); + freeTitle = true; + } + + // Get new Name string ontent + var nameNewRaw = managedEventArgs.Name.Encode(); + if (!nameRaw.SequenceEqual(nameNewRaw)) + { + eventArgs.NamePtr = GameInterfaceHelper.PluginAllocate(nameNewRaw); + freeName = true; + } + + // Get new Free Company string ontent + var freeCompanyNewRaw = managedEventArgs.FreeCompany.Encode(); + if (!freeCompanyRaw.SequenceEqual(freeCompanyNewRaw)) + { + eventArgs.FreeCompanyPtr = GameInterfaceHelper.PluginAllocate(freeCompanyNewRaw); + freeFreeCompany = true; + } + + // Call Original as we changed something + callOriginal(); + + // Free memory + if (freeTitle) + GameInterfaceHelper.PluginFree(eventArgs.TitlePtr); + if (freeName) + GameInterfaceHelper.PluginFree(eventArgs.NamePtr); + if (freeFreeCompany) + GameInterfaceHelper.PluginFree(eventArgs.FreeCompanyPtr); + } + else if(!hasDefaultHookEvent) + { + // Call original in case of nothing get called, just to get secure it will not break the game when not calling it. + callOriginal(); + } + + // Set result + result = eventArgs.Result; + } + + return result; + } + } +} diff --git a/Pilz.Dalamud/Nameplates/NameplateManager.cs b/Pilz.Dalamud/Nameplates/NameplateManager.cs new file mode 100644 index 0000000..8fbd04a --- /dev/null +++ b/Pilz.Dalamud/Nameplates/NameplateManager.cs @@ -0,0 +1,83 @@ +using Dalamud.Hooking; +using Pilz.Dalamud.Nameplates.EventArgs; +using Dalamud.Utility.Signatures; +using FFXIVClientStructs.FFXIV.Client.UI; +using System.Runtime.InteropServices; +using Dalamud.Game.ClientState.Objects.Types; +using Pilz.Dalamud.Nameplates.Model; + +namespace Pilz.Dalamud.Nameplates +{ + public class NameplateManager : IDisposable + { + /// + /// Provides events that you can hook to. + /// + public NameplateHooks Hooks { get; init; } = new(); + + /// + /// Defines if all hooks are enabled and the NameplateManager is ready to go. If this is false, then there might be something wrong or something already has been disposed. + /// + public bool IsValid => Hooks.IsValid; + + /// + /// Creates a new instance of the NameplateManager. + /// + public NameplateManager() + { + Hooks.Initialize(); + } + + ~NameplateManager() + { + Dispose(); + } + + public void Dispose() + { + Hooks?.Dispose(); + } + + public static T? GetNameplateGameObject(SafeNameplateObject namePlateObject) where T : GameObject + { + return GetNameplateGameObject(namePlateObject.Pointer); + } + + public static T? GetNameplateGameObject(IntPtr nameplateObjectPtr) where T : GameObject + { + // Get the nameplate object array + var nameplateAddonPtr = PluginServices.GameGui.GetAddonByName("NamePlate", 1); + var nameplateObjectArrayPtrPtr = nameplateAddonPtr + Marshal.OffsetOf(typeof(AddonNamePlate), nameof(AddonNamePlate.NamePlateObjectArray)).ToInt32(); + var nameplateObjectArrayPtr = Marshal.ReadIntPtr(nameplateObjectArrayPtrPtr); + if (nameplateObjectArrayPtr == IntPtr.Zero) + { + return null; + } + + // Determine the index of the nameplate object within the nameplate object array + var namePlateObjectSize = Marshal.SizeOf(typeof(AddonNamePlate.NamePlateObject)); + var namePlateObjectPtr0 = nameplateObjectArrayPtr + namePlateObjectSize * 0; + var namePlateIndex = (nameplateObjectPtr.ToInt64() - namePlateObjectPtr0.ToInt64()) / namePlateObjectSize; + if (namePlateIndex < 0 || namePlateIndex >= AddonNamePlate.NumNamePlateObjects) + { + return null; + } + + // Get the nameplate info array + IntPtr nameplateInfoArrayPtr = IntPtr.Zero; + unsafe + { + var framework = FFXIVClientStructs.FFXIV.Client.System.Framework.Framework.Instance(); + nameplateInfoArrayPtr = new IntPtr(&framework->GetUiModule()->GetRaptureAtkModule()->NamePlateInfoArray); + } + + // Get the nameplate info for the nameplate object + var namePlateInfoPtr = new IntPtr(nameplateInfoArrayPtr.ToInt64() + Marshal.SizeOf(typeof(RaptureAtkModule.NamePlateInfo)) * namePlateIndex); + RaptureAtkModule.NamePlateInfo namePlateInfo = Marshal.PtrToStructure(namePlateInfoPtr); + + // Return the object for its object id + var objectId = namePlateInfo.ObjectID.ObjectID; + return PluginServices.ObjectTable.SearchById(objectId) as T; + } + } +} diff --git a/Pilz.Dalamud/Nameplates/Tools/NameplateChanges.cs b/Pilz.Dalamud/Nameplates/Tools/NameplateChanges.cs new file mode 100644 index 0000000..b33c372 --- /dev/null +++ b/Pilz.Dalamud/Nameplates/Tools/NameplateChanges.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Pilz.Dalamud.Tools.Strings; + +namespace Pilz.Dalamud.Nameplates.Tools +{ + public class NameplateChanges + { + private readonly Dictionary changes = new(); + + public NameplateChanges() + { + changes.Add(NameplateElements.Title, new()); + changes.Add(NameplateElements.Name, new()); + changes.Add(NameplateElements.FreeCompany, new()); + } + + /// + /// Gets the properties with the changes of an element of your choice where you can add your payloads to a change and setup some options. + /// + /// The position of your choice. + /// + public StringChangesProps GetProps(NameplateElements element) + { + return changes[element]; + } + + /// + /// Gets the changes of an element of your choice where you can add your payloads to a change. + /// + /// The position of your choice. + /// + public StringChanges GetChanges(NameplateElements element) + { + return GetProps(element).StringChanges; + } + + /// + /// Gets a change of the position of the element of your choice where you can add your payloads. + /// + /// The position of your choice. + /// + public StringChange GetChange(NameplateElements element, StringPosition position) + { + return GetChanges(element).GetChange(position); + } + } +} diff --git a/Pilz.Dalamud/Nameplates/Tools/NameplateChangesProps.cs b/Pilz.Dalamud/Nameplates/Tools/NameplateChangesProps.cs new file mode 100644 index 0000000..56f7017 --- /dev/null +++ b/Pilz.Dalamud/Nameplates/Tools/NameplateChangesProps.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Pilz.Dalamud.Nameplates.Tools +{ + public class NameplateChangesProps + { + /// + /// All the changes to the nameplate that should be made. + /// + public NameplateChanges Changes { get; set; } + } +} diff --git a/Pilz.Dalamud/Nameplates/Tools/NameplateElements.cs b/Pilz.Dalamud/Nameplates/Tools/NameplateElements.cs new file mode 100644 index 0000000..0a15cf9 --- /dev/null +++ b/Pilz.Dalamud/Nameplates/Tools/NameplateElements.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Pilz.Dalamud.Nameplates.Tools +{ + public enum NameplateElements + { + Name, + Title, + FreeCompany + } +} diff --git a/Pilz.Dalamud/Nameplates/Tools/NameplateUpdateFactory.cs b/Pilz.Dalamud/Nameplates/Tools/NameplateUpdateFactory.cs new file mode 100644 index 0000000..ebc9518 --- /dev/null +++ b/Pilz.Dalamud/Nameplates/Tools/NameplateUpdateFactory.cs @@ -0,0 +1,52 @@ +using Dalamud.Game.Text.SeStringHandling; +using Dalamud.Game.Text.SeStringHandling.Payloads; +using Pilz.Dalamud.ActivityContexts; +using Pilz.Dalamud.Nameplates.Model; +using Pilz.Dalamud.Tools; +using Pilz.Dalamud.Tools.Strings; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Pilz.Dalamud.Nameplates.Tools +{ + public static class NameplateUpdateFactory + { + public static void ApplyNameplateChanges(NameplateChangesProps props) + { + foreach (NameplateElements element in Enum.GetValues(typeof(NameplateElements))) + { + var change = props.Changes.GetProps(element); + StringUpdateFactory.ApplyStringChanges(change); + } + } + + public static bool ApplyStatusIconWithPrio(ref int statusIcon, int newStatusIcon, StringChange stringChange, ActivityContext activityContext, StatusIconPriorizer priorizer, bool moveIconToNameplateIfPossible) + { + bool? isPrio = null; + var fontIcon = StatusIconFontConverter.GetBitmapFontIconFromStatusIcon((StatusIcons)statusIcon); + + if (moveIconToNameplateIfPossible) + { + if (fontIcon != null) + { + // Set new font icon as string change + var iconPayload = new IconPayload(fontIcon.Value); + stringChange.Payloads.Insert(0, iconPayload); + + // If we moved it, we don't need it as icon anymore, yay :D + isPrio = false; + } + } + + isPrio ??= priorizer.IsPriorityIcon(statusIcon, activityContext); + + if (!isPrio.Value) + statusIcon = newStatusIcon; + + return isPrio.Value; + } + } +} diff --git a/Pilz.Dalamud/Nameplates/Tools/StatusIconPriorizer.cs b/Pilz.Dalamud/Nameplates/Tools/StatusIconPriorizer.cs new file mode 100644 index 0000000..6d0c6fa --- /dev/null +++ b/Pilz.Dalamud/Nameplates/Tools/StatusIconPriorizer.cs @@ -0,0 +1,67 @@ +using Lumina.Excel.GeneratedSheets; +using Pilz.Dalamud.ActivityContexts; +using Pilz.Dalamud.Nameplates.Model; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Pilz.Dalamud.Nameplates.Tools +{ + public class StatusIconPriorizer + { + private static StatusIconPriorizerSettings DefaultSettings { get; } = new(); + public StatusIconPriorizerSettings Settings { get; init; } + + public StatusIconPriorizer() : this(DefaultSettings) + { + } + + public StatusIconPriorizer(StatusIconPriorizerSettings settings) + { + Settings = settings; + } + + /// + /// Check for an icon that should take priority over the job icon, + /// taking into account whether or not the player is in a duty. + /// + /// The incoming icon id that is being overwritten by the plugin. + /// The icon id that should be used. + /// Whether a priority icon was found. + public bool IsPriorityIcon(int iconId, ActivityContext activityContext) + { + bool isPrioIcon; + + if (!Settings.UsePriorizedIcons && iconId != (int)StatusIcons.Disconnecting && iconId != (int)StatusIcons.Disconnecting + 50) + isPrioIcon = false; + else + { + // Select which set of priority icons to use based on whether we're in a duty + // In the future, there can be a third list used when in combat + var priorityIcons = GetPriorityIcons(activityContext); + + // Determine whether the incoming icon should take priority over the job icon + // Check the id plus 50 as that's an alternately sized version + isPrioIcon = priorityIcons.Contains(iconId) || priorityIcons.Contains(iconId + 50); + } + + return isPrioIcon; + } + + private IEnumerable GetPriorityIcons(ActivityContext activityContext) + { + StatusIconPriorizerConditionSets set; + + if (activityContext.ZoneType == ZoneType.Foray) + set = StatusIconPriorizerConditionSets.InForay; + else if (activityContext.IsInDuty) + set = StatusIconPriorizerConditionSets.InDuty; + else + set = StatusIconPriorizerConditionSets.Overworld; + + return Settings.GetConditionSet(set).Select(n => (int)n); + } + } +} diff --git a/Pilz.Dalamud/Nameplates/Tools/StatusIconPriorizerConditionSets.cs b/Pilz.Dalamud/Nameplates/Tools/StatusIconPriorizerConditionSets.cs new file mode 100644 index 0000000..6c78cc7 --- /dev/null +++ b/Pilz.Dalamud/Nameplates/Tools/StatusIconPriorizerConditionSets.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Pilz.Dalamud.Nameplates.Tools +{ + public enum StatusIconPriorizerConditionSets + { + Overworld, + InDuty, + InForay + } +} diff --git a/Pilz.Dalamud/Nameplates/Tools/StatusIconPriorizerSettings.cs b/Pilz.Dalamud/Nameplates/Tools/StatusIconPriorizerSettings.cs new file mode 100644 index 0000000..a5bfe34 --- /dev/null +++ b/Pilz.Dalamud/Nameplates/Tools/StatusIconPriorizerSettings.cs @@ -0,0 +1,110 @@ +using Newtonsoft.Json; +using Pilz.Dalamud.ActivityContexts; +using Pilz.Dalamud.Nameplates.Model; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Pilz.Dalamud.Nameplates.Tools +{ + public class StatusIconPriorizerSettings + { + [JsonProperty("IconConditionSets")] + private Dictionary> iconConditionSets = new(); + public bool UsePriorizedIcons { get; set; } = true; + + [JsonConstructor] + private StatusIconPriorizerSettings(JsonConstructorAttribute dummy) + { + } + + public StatusIconPriorizerSettings() : this(false) + { + } + + public StatusIconPriorizerSettings(bool fillWithDefaultSettings) + { + foreach (StatusIconPriorizerConditionSets set in Enum.GetValues(typeof(StatusIconPriorizerConditionSets))) + iconConditionSets.Add(set, new List()); + + if (fillWithDefaultSettings) + FillWithDefaultSettings(); + } + + public List GetConditionSet(StatusIconPriorizerConditionSets set) + { + return iconConditionSets[set]; + } + + public void ResetToEmpty() + { + foreach (var kvp in iconConditionSets) + kvp.Value.Clear(); + } + + public void ResetToDefault() + { + ResetToEmpty(); + FillWithDefaultSettings(); + } + + private void FillWithDefaultSettings() + { + var setOverworld = GetConditionSet(StatusIconPriorizerConditionSets.Overworld); + setOverworld.AddRange(new[] + { + StatusIcons.Disconnecting, // Disconnecting + StatusIcons.InDuty, // In Duty + StatusIcons.ViewingCutscene, // Viewing Cutscene + StatusIcons.Busy, // Busy + StatusIcons.Idle, // Idle + StatusIcons.DutyFinder, // Duty Finder + StatusIcons.PartyLeader, // Party Leader + StatusIcons.PartyMember, // Party Member + StatusIcons.RolePlaying, // Role Playing + StatusIcons.GroupPose, // Group Pose + StatusIcons.Mentor, + StatusIcons.MentorCrafting, + StatusIcons.MentorPvE, + StatusIcons.MentorPvP, + StatusIcons.Returner, + StatusIcons.NewAdventurer, + }); + + var setInDuty = GetConditionSet(StatusIconPriorizerConditionSets.InDuty); + setInDuty.AddRange(new[] + { + StatusIcons.Disconnecting, // Disconnecting + StatusIcons.ViewingCutscene, // Viewing Cutscene + StatusIcons.Idle, // Idle + StatusIcons.GroupPose, // Group Pose + StatusIcons.Mentor, + StatusIcons.MentorCrafting, + StatusIcons.MentorPvE, + StatusIcons.MentorPvP, + StatusIcons.Returner, + StatusIcons.NewAdventurer, + }); + + var setInForay = GetConditionSet(StatusIconPriorizerConditionSets.InForay); + setInForay.AddRange(new[] + { + // This allows you to see which players don't have a party + StatusIcons.InDuty, // In Duty + + StatusIcons.Disconnecting, // Disconnecting + StatusIcons.ViewingCutscene, // Viewing Cutscene + StatusIcons.Idle, // Idle + StatusIcons.GroupPose, // Group Pose + StatusIcons.Mentor, + StatusIcons.MentorCrafting, + StatusIcons.MentorPvE, + StatusIcons.MentorPvP, + StatusIcons.Returner, + StatusIcons.NewAdventurer, + }); + } + } +} diff --git a/Pilz.Dalamud/Pilz.Dalamud.csproj b/Pilz.Dalamud/Pilz.Dalamud.csproj new file mode 100644 index 0000000..540e7f5 --- /dev/null +++ b/Pilz.Dalamud/Pilz.Dalamud.csproj @@ -0,0 +1,63 @@ + + + + net6.0-windows + enable + annotations + true + latest + false + false + x64 + + + + true + true + + + + $(appdata)\XIVLauncher\addon\Hooks\dev\ + False + Pilzinsel64 + https://github.com/Pilzinsel64/Pilz.Dalamud + https://github.com/Pilzinsel64/Pilz.Dalamud + git + 0.1.1.0 + 0.1.1.0 + 0.1.1 + True + + + + + $(DalamudLibPath)FFXIVClientStructs.dll + false + + + $(DalamudLibPath)Newtonsoft.Json.dll + false + + + $(DalamudLibPath)Dalamud.dll + false + + + $(DalamudLibPath)ImGui.NET.dll + false + + + $(DalamudLibPath)ImGuiScene.dll + false + + + $(DalamudLibPath)Lumina.dll + false + + + $(DalamudLibPath)Lumina.Excel.dll + false + + + + diff --git a/Pilz.Dalamud/PluginServices.cs b/Pilz.Dalamud/PluginServices.cs new file mode 100644 index 0000000..37302ed --- /dev/null +++ b/Pilz.Dalamud/PluginServices.cs @@ -0,0 +1,28 @@ +using Dalamud.Data; +using Dalamud.Game.ClientState; +using Dalamud.Game.ClientState.Objects; +using Dalamud.Game.Gui; +using Dalamud.IoC; +using Dalamud.Plugin; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Pilz.Dalamud +{ + public class PluginServices + { + [PluginService] public static GameGui GameGui { get; set; } = null; + [PluginService] public static DalamudPluginInterface PluginInterface { get; set; } = null; + [PluginService] public static ClientState ClientState { get; set; } = null; + [PluginService] public static DataManager DataManager { get; set; } = null; + [PluginService] public static ObjectTable ObjectTable { get; set; } = null; + + public static void Initialize(DalamudPluginInterface dalamudPluginInterface) + { + dalamudPluginInterface.Create(); + } + } +} diff --git a/Pilz.Dalamud/Tools/StatusIconFontConverter.cs b/Pilz.Dalamud/Tools/StatusIconFontConverter.cs new file mode 100644 index 0000000..d5ecff2 --- /dev/null +++ b/Pilz.Dalamud/Tools/StatusIconFontConverter.cs @@ -0,0 +1,41 @@ +using Dalamud.Game.Text.SeStringHandling; +using Pilz.Dalamud.Nameplates.Model; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Pilz.Dalamud.Tools +{ + public static class StatusIconFontConverter + { + public static StatusIcons? GetStatusIconFromBitmapFontIcon(BitmapFontIcon fontIcon) + { + return fontIcon switch + { + BitmapFontIcon.NewAdventurer => StatusIcons.NewAdventurer, + BitmapFontIcon.Mentor => StatusIcons.Mentor, + BitmapFontIcon.MentorPvE => StatusIcons.MentorPvE, + BitmapFontIcon.MentorCrafting => StatusIcons.MentorCrafting, + BitmapFontIcon.MentorPvP => StatusIcons.MentorPvP, + BitmapFontIcon.Returner => StatusIcons.Returner, + _ => null + }; + } + + public static BitmapFontIcon? GetBitmapFontIconFromStatusIcon(StatusIcons icon) + { + return icon switch + { + StatusIcons.NewAdventurer => BitmapFontIcon.NewAdventurer, + StatusIcons.Mentor => BitmapFontIcon.Mentor, + StatusIcons.MentorPvE => BitmapFontIcon.MentorPvE, + StatusIcons.MentorCrafting => BitmapFontIcon.MentorCrafting, + StatusIcons.MentorPvP => BitmapFontIcon.MentorPvP, + StatusIcons.Returner => BitmapFontIcon.Returner, + _ => null + }; + } + } +} diff --git a/Pilz.Dalamud/Tools/Strings/StringChange.cs b/Pilz.Dalamud/Tools/Strings/StringChange.cs new file mode 100644 index 0000000..f6a019e --- /dev/null +++ b/Pilz.Dalamud/Tools/Strings/StringChange.cs @@ -0,0 +1,24 @@ +using Dalamud.Game.Text.SeStringHandling; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Pilz.Dalamud.Tools.Strings +{ + public class StringChange + { + /// + /// The payloads to use for inserting/replacing. + /// + public List Payloads { get; init; } = new(); + + /// + /// Defines if only one anchor payload should be used, if using anchor payloads. + /// With this true the single anchor payload will be used in StringUpdateFactory instead of the anchor payload list. + /// Not needed to be true for the most cases. + /// + public bool ForceUsingSingleAnchorPayload { get; set; } = false; + } +} diff --git a/Pilz.Dalamud/Tools/Strings/StringChanges.cs b/Pilz.Dalamud/Tools/Strings/StringChanges.cs new file mode 100644 index 0000000..1ae85c0 --- /dev/null +++ b/Pilz.Dalamud/Tools/Strings/StringChanges.cs @@ -0,0 +1,40 @@ +using Dalamud.Game.Text.SeStringHandling; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Pilz.Dalamud.Tools.Strings +{ + public class StringChanges + { + private readonly Dictionary changes = new(); + + public StringChanges() + { + changes.Add(StringPosition.Before, new StringChange()); + changes.Add(StringPosition.After, new StringChange()); + changes.Add(StringPosition.Replace, new StringChange()); + } + + /// + /// Gets a change of the position of your choice where you can add your payloads. + /// + /// The position of your choice. + /// + public StringChange GetChange(StringPosition position) + { + return changes[position]; + } + + /// + /// Checks if there is any string change listed. + /// + /// + public bool Any() + { + return changes.Sum(n => n.Value.Payloads.Count) != 0; + } + } +} diff --git a/Pilz.Dalamud/Tools/Strings/StringChangesProps.cs b/Pilz.Dalamud/Tools/Strings/StringChangesProps.cs new file mode 100644 index 0000000..d0cd253 --- /dev/null +++ b/Pilz.Dalamud/Tools/Strings/StringChangesProps.cs @@ -0,0 +1,31 @@ +using Dalamud.Game.ClientState.Objects.Types; +using Dalamud.Game.Text.SeStringHandling; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Pilz.Dalamud.Tools.Strings +{ + public class StringChangesProps + { + /// + /// The string where the changes should be applied. + /// + public SeString Destination { get; set; } + /// + /// The changes that should be applied to the destination. + /// + public StringChanges StringChanges { get; set; } = new(); + /// + /// Payloads to use as anchor where the changes should be applied to. + /// + public List AnchorPayloads { get; set; } = new(); + /// + /// A single payload to use as anchor where the changes should be applied to. + /// This property will only be used if StringChange.ForceSingleAnchorPayload is true. + /// + public Payload AnchorPayload { get; set; } + } +} diff --git a/Pilz.Dalamud/Tools/Strings/StringPosition.cs b/Pilz.Dalamud/Tools/Strings/StringPosition.cs new file mode 100644 index 0000000..46a05db --- /dev/null +++ b/Pilz.Dalamud/Tools/Strings/StringPosition.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Pilz.Dalamud.Tools.Strings +{ + public enum StringPosition + { + Before, + After, + Replace + } +} diff --git a/Pilz.Dalamud/Tools/Strings/StringUpdateFactory.cs b/Pilz.Dalamud/Tools/Strings/StringUpdateFactory.cs new file mode 100644 index 0000000..ab3423a --- /dev/null +++ b/Pilz.Dalamud/Tools/Strings/StringUpdateFactory.cs @@ -0,0 +1,132 @@ +using Dalamud.Game.Text.SeStringHandling; +using Dalamud.Game.Text.SeStringHandling.Payloads; +using Lumina.Text; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Pilz.Dalamud.Tools.Strings +{ + public static class StringUpdateFactory + { + public static void ApplyStringChanges(StringChangesProps props) + { + if (props.StringChanges != null && props.StringChanges.Any()) + { + var seString = props.Destination; + List stringPositionsOrdered = GetOrderedStringPositions(props); + + foreach (var stringPosition in stringPositionsOrdered) + { + var stringChange = props.StringChanges.GetChange(stringPosition); + if (stringChange != null && stringChange.Payloads.Any()) + { + AddSpacesBetweenTextPayloads(stringChange.Payloads, stringPosition); + + if (stringPosition == StringPosition.Before) + { + Payload anchorFirst = stringChange.ForceUsingSingleAnchorPayload ? props.AnchorPayload : props.AnchorPayloads?.FirstOrDefault(); + + if (anchorFirst != null) + { + var anchorPayloadIndex = seString.Payloads.IndexOf(anchorFirst); + seString.Payloads.InsertRange(anchorPayloadIndex, stringChange.Payloads); + } + else + seString.Payloads.InsertRange(0, stringChange.Payloads); + } + else if (stringPosition == StringPosition.After) + { + Payload anchorLast = stringChange.ForceUsingSingleAnchorPayload ? props.AnchorPayload : props.AnchorPayloads?.LastOrDefault(); + + if (anchorLast != null) + { + var anchorPayloadIndex = seString.Payloads.IndexOf(anchorLast); + seString.Payloads.InsertRange(anchorPayloadIndex + 1, stringChange.Payloads); + } + else + seString.Payloads.AddRange(stringChange.Payloads); + } + else if (stringPosition == StringPosition.Replace) + { + Payload anchorReplace = props.AnchorPayload; + + if (anchorReplace != null) + { + var anchorPayloadIndex = seString.Payloads.IndexOf(anchorReplace); + seString.Payloads.InsertRange(anchorPayloadIndex, stringChange.Payloads); + seString.Remove(anchorReplace); + } + else + { + seString.Payloads.Clear(); + seString.Payloads.AddRange(stringChange.Payloads); + } + } + } + } + } + } + + private static void AddSpacesBetweenTextPayloads(List payloads, StringPosition tagPosition) + { + if (payloads != null && payloads.Any()) + { + var indicesToInsertSpacesAt = new List(); + var lastTextPayloadIndex = -1; + + static TextPayload getNewTextPayload() => new(" "); + + foreach (var payload in payloads.Reverse()) + { + if (payload is IconPayload iconPayload) + lastTextPayloadIndex = -1; + else if (payload is TextPayload textPayload) + { + if (lastTextPayloadIndex != -1) + indicesToInsertSpacesAt.Add(payloads.IndexOf(textPayload) + 1); + lastTextPayloadIndex = payloads.IndexOf(textPayload); + } + } + + foreach (var indexToInsertSpaceAt in indicesToInsertSpacesAt) + payloads.Insert(indexToInsertSpaceAt, getNewTextPayload()); + + // Decide whether to add a space to the end + if (tagPosition == StringPosition.Before) + { + var significantPayloads = payloads.Where(payload => payload is TextPayload || payload is IconPayload); + if (significantPayloads.Last() is TextPayload) + payloads.Add(getNewTextPayload()); + } + // Decide whether to add a space to the beginning + else if (tagPosition == StringPosition.After) + { + var significantPayloads = payloads.Where(payload => payload is TextPayload || payload is IconPayload); + if (significantPayloads.First() is TextPayload) + payloads.Insert(0, getNewTextPayload()); + } + } + } + + private static List GetOrderedStringPositions(StringChangesProps props) + { + var tagPositionsOrdered = new List(); + + // If there's no anchor payload, do replaces first so that befores and afters are based on the replaced data + if (props.AnchorPayloads == null || !props.AnchorPayloads.Any()) + tagPositionsOrdered.Add(StringPosition.Replace); + + tagPositionsOrdered.Add(StringPosition.Before); + tagPositionsOrdered.Add(StringPosition.After); + + // If there is an anchor payload, do replaces last so that we still know which payload needs to be removed + if (props.AnchorPayloads != null && props.AnchorPayloads.Any()) + tagPositionsOrdered.Add(StringPosition.Replace); + + return tagPositionsOrdered; + } + } +} diff --git a/Pilz.Dalamud/XivApi.cs b/Pilz.Dalamud/XivApi.cs new file mode 100644 index 0000000..5d2a35c --- /dev/null +++ b/Pilz.Dalamud/XivApi.cs @@ -0,0 +1,39 @@ +using FFXIVClientStructs.FFXIV.Client.System.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Pilz.Dalamud.Nameplates.Model; + +namespace Pilz.Dalamud +{ + public class XivApi + { + private static IntPtr _RaptureAtkModulePtr = IntPtr.Zero; + + public static IntPtr RaptureAtkModulePtr + { + get + { + if (_RaptureAtkModulePtr == IntPtr.Zero) + { + unsafe + { + var framework = Framework.Instance(); + var uiModule = framework->GetUiModule(); + + _RaptureAtkModulePtr = new IntPtr(uiModule->GetRaptureAtkModule()); + } + } + + return _RaptureAtkModulePtr; + } + } + + public static SafeAddonNameplate GetSafeAddonNamePlate() + { + return new(PluginServices.PluginInterface); + } + } +} diff --git a/Pilz.Dalamud/packages.lock.json b/Pilz.Dalamud/packages.lock.json new file mode 100644 index 0000000..8ee9f35 --- /dev/null +++ b/Pilz.Dalamud/packages.lock.json @@ -0,0 +1,6 @@ +{ + "version": 1, + "dependencies": { + "net6.0-windows7.0": {} + } +} \ No newline at end of file diff --git a/PlayerTags.sln b/PlayerTags.sln index c058c5e..72c2a81 100644 --- a/PlayerTags.sln +++ b/PlayerTags.sln @@ -1,10 +1,12 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29709.97 +# Visual Studio Version 17 +VisualStudioVersion = 17.3.32929.385 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PlayerTags", "PlayerTags\PlayerTags.csproj", "{13C812E9-0D42-4B95-8646-40EEBF30636F}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pilz.Dalamud", "Pilz.Dalamud\Pilz.Dalamud.csproj", "{D0362D71-E77F-4739-80BE-CD4454188B8F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -15,6 +17,10 @@ Global {13C812E9-0D42-4B95-8646-40EEBF30636F}.Debug|x64.Build.0 = Debug|x64 {13C812E9-0D42-4B95-8646-40EEBF30636F}.Release|x64.ActiveCfg = Release|x64 {13C812E9-0D42-4B95-8646-40EEBF30636F}.Release|x64.Build.0 = Release|x64 + {D0362D71-E77F-4739-80BE-CD4454188B8F}.Debug|x64.ActiveCfg = Debug|x64 + {D0362D71-E77F-4739-80BE-CD4454188B8F}.Debug|x64.Build.0 = Debug|x64 + {D0362D71-E77F-4739-80BE-CD4454188B8F}.Release|x64.ActiveCfg = Release|x64 + {D0362D71-E77F-4739-80BE-CD4454188B8F}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/PlayerTags/Configuration/PluginConfiguration.cs b/PlayerTags/Configuration/PluginConfiguration.cs index 7f80754..e04432f 100644 --- a/PlayerTags/Configuration/PluginConfiguration.cs +++ b/PlayerTags/Configuration/PluginConfiguration.cs @@ -1,32 +1,46 @@ using Dalamud.Configuration; using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Pilz.Dalamud.ActivityContexts; +using Pilz.Dalamud.Nameplates.Tools; 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 { [Serializable] public class PluginConfiguration : IPluginConfiguration { - public int Version { get; set; } = 0; + 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 Dictionary GeneralOptions = new Dictionary() + [JsonProperty("GeneralOptionsV2")] + public Dictionary GeneralOptions = new() { - { ActivityContext.None, new GeneralOptionsClass() }, - { ActivityContext.PveDuty, new GeneralOptionsClass() }, - { ActivityContext.PvpDuty, new GeneralOptionsClass() } + { ActivityType.None, new GeneralOptionsClass() }, + { ActivityType.PveDuty, new GeneralOptionsClass() }, + { ActivityType.PvpDuty, new GeneralOptionsClass() } }; + public DefaultPluginDataTemplate DefaultPluginDataTemplate = DefaultPluginDataTemplate.Simple; + 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 IsPlayersTabFriendsVisible = true; public bool IsPlayersTabPartyVisible = true; public bool IsPlayersTabAllianceVisible = true; public bool IsPlayersTabEnemiesVisible = true; @@ -65,6 +79,18 @@ namespace PlayerTags.Configuration #region Obsulate Properties + [Obsolete] + [JsonProperty("GeneralOptions")] + private Dictionary GeneralOptionsV1 + { + set + { + GeneralOptions.Clear(); + foreach (var kvp in value) + GeneralOptions.Add((ActivityType)kvp.Key, kvp.Value); + } + } + [JsonProperty("NameplateFreeCompanyVisibility"), Obsolete] private NameplateFreeCompanyVisibility NameplateFreeCompanyVisibilityV1 { @@ -202,9 +228,56 @@ namespace PlayerTags.Configuration Identities = pluginData.Identities; - PluginServices.DalamudPluginInterface.SavePluginConfig(this); + 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); + } + + public static PluginConfiguration LoadPluginConfig() + { + var configFilePath = GetConfigFilePath(); + object config = null; + + if (File.Exists(configFilePath)) + { + var configFileContent = File.ReadAllText(configFilePath); + config = JsonConvert.DeserializeObject(configFileContent, GetJsonSettings()); + } + else + { + // Try loading the old settings, if possible + configFilePath = PluginServices.DalamudPluginInterface.ConfigFile.FullName; + config = PluginServices.DalamudPluginInterface.GetPluginConfig(); + } + + 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; + } } public class GeneralOptionsClass @@ -212,7 +285,7 @@ namespace PlayerTags.Configuration public NameplateFreeCompanyVisibility NameplateFreeCompanyVisibility = NameplateFreeCompanyVisibility.Default; public NameplateTitleVisibility NameplateTitleVisibility = NameplateTitleVisibility.WhenHasTags; public NameplateTitlePosition NameplateTitlePosition = NameplateTitlePosition.AlwaysAboveName; - + public DeadPlayerHandling NameplateDeadPlayerHandling = DeadPlayerHandling.Include; public bool IsApplyTagsToAllChatMessagesEnabled = true; } } diff --git a/PlayerTags/Configuration/PluginConfigurationUI.cs b/PlayerTags/Configuration/PluginConfigurationUI.cs index 078c69a..cbc334e 100644 --- a/PlayerTags/Configuration/PluginConfigurationUI.cs +++ b/PlayerTags/Configuration/PluginConfigurationUI.cs @@ -1,10 +1,17 @@ using Dalamud.Configuration; using Dalamud.Game.ClientState.Objects.SubKinds; +using Dalamud.Game.Text; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Interface; using Dalamud.Logging; +using FFXIVClientStructs.FFXIV.Client.Game.UI; +using FFXIVClientStructs.Havok; using ImGuiNET; using Lumina.Excel.GeneratedSheets; +using Pilz.Dalamud.ActivityContexts; +using Pilz.Dalamud.Icons; +using Pilz.Dalamud.Nameplates.Model; +using Pilz.Dalamud.Nameplates.Tools; using PlayerTags.Data; using PlayerTags.Inheritables; using PlayerTags.PluginStrings; @@ -13,6 +20,8 @@ using System; using System.Collections.Generic; using System.Linq; using System.Numerics; +using System.Threading.Tasks; +using System.Transactions; namespace PlayerTags.Configuration { @@ -29,6 +38,7 @@ namespace PlayerTags.Configuration private PropertyProxy propertyProxy; private InheritableValue? m_ColorPickerPopupDataContext; + private Dictionary inheritableTEnumProxies = new(); public PluginConfigurationUI(PluginConfiguration config, PluginData pluginData) { @@ -77,6 +87,7 @@ namespace PlayerTags.Configuration DrawComboBox(true, true, false, ref propertyProxy.NameplateFreeCompanyVisibility, () => SaveSettings(true)); DrawComboBox(true, true, false, ref propertyProxy.NameplateTitleVisibility, () => SaveSettings(true)); DrawComboBox(true, true, false, ref propertyProxy.NameplateTitlePosition, () => SaveSettings(true)); + DrawComboBox(true, true, false, ref propertyProxy.NameplateDeadPlayerHandling, () => SaveSettings(true)); ImGui.Spacing(); ImGui.Spacing(); @@ -97,6 +108,11 @@ namespace PlayerTags.Configuration { ImGui.Spacing(); ImGui.Spacing(); + DrawComboBox(true, true, false, ref m_PluginConfiguration.DefaultPluginDataTemplate, () => + { + m_PluginData.ReloadDefault(); + SaveSettings(); + }, true, true); DrawCheckbox(nameof(m_PluginConfiguration.IsShowInheritedPropertiesEnabled), true, ref m_PluginConfiguration.IsShowInheritedPropertiesEnabled, () => SaveSettings()); ImGui.BeginGroup(); ImGui.Columns(2); @@ -238,6 +254,81 @@ namespace PlayerTags.Configuration ImGui.EndTabItem(); } + if (ImGui.BeginTabItem(Strings.Loc_Static_StatusIconPrioList)) + { + ImGui.Spacing(); + ImGui.Spacing(); + + var isPriorizerEnabled = m_PluginConfiguration.StatusIconPriorizerSettings.UsePriorizedIcons; + DrawCheckbox(nameof(StatusIconPriorizerSettings.UsePriorizedIcons), true, ref isPriorizerEnabled, () => + { + m_PluginConfiguration.StatusIconPriorizerSettings.UsePriorizedIcons = isPriorizerEnabled; + SaveSettings(); + }); + + DrawCheckbox(nameof(PluginConfiguration.MoveStatusIconToNameplateTextIfPossible), true, ref m_PluginConfiguration.MoveStatusIconToNameplateTextIfPossible, () => SaveSettings()); + + if (isPriorizerEnabled) + { + var statusIcons = Enum.GetValues(); + + ImGui.Spacing(); + ImGui.Spacing(); + + if (ImGui.Button(Strings.Loc_StatusIconPriorizer_ResetToDefault)) + { + m_PluginConfiguration.StatusIconPriorizerSettings.ResetToDefault(); + SaveSettings(); + } + if (ImGui.IsItemHovered()) + ImGui.SetTooltip(Strings.Loc_StatusIconPriorizer_ResetToDefault_Description); + + ImGui.SameLine(); + + if (ImGui.Button(Strings.Loc_StatusIconPriorizer_ResetToEmpty)) + { + m_PluginConfiguration.StatusIconPriorizerSettings.ResetToEmpty(); + SaveSettings(); + } + if (ImGui.IsItemHovered()) + ImGui.SetTooltip(Strings.Loc_StatusIconPriorizer_ResetToEmpty_Description); + + ImGui.Spacing(); + ImGui.Spacing(); + + foreach (var conditionSetName in Enum.GetValues()) + { + if (ImGui.CollapsingHeader(Localizer.GetString(conditionSetName, false))) + { + var conditionSet = m_PluginConfiguration.StatusIconPriorizerSettings.GetConditionSet(conditionSetName); + + foreach (var statusIcon in statusIcons) + { + var isChecked = conditionSet.Contains(statusIcon); + DrawCheckbox(Localizer.GetName(statusIcon), true, ref isChecked, () => + { + if (isChecked) + { + if (!conditionSet.Contains(statusIcon)) + conditionSet.Add(statusIcon); + } + else if (conditionSet.Contains(statusIcon)) + conditionSet.Remove(statusIcon); + SaveSettings(); + }); + } + } + + if (ImGui.IsItemHovered()) + ImGui.SetTooltip(Localizer.GetString(conditionSetName, true)); + + ImGui.Spacing(); + } + } + + ImGui.EndTabItem(); + } + ImGui.EndTabBar(); } @@ -691,6 +782,14 @@ namespace PlayerTags.Configuration { DrawInheritable(selectedInheritable.Inheritable.Key, true, false, inheritableNameplateTitlePosition); } + else if (selectedInheritable.Inheritable.Value is InheritableValue inheritableJobIconSetName) + { + DrawInheritable(selectedInheritable.Inheritable.Key, false, false, inheritableJobIconSetName); + } + else if (selectedInheritable.Inheritable.Value is InheritableReference> inheritableXivChatType) + { + DrawMultiselect(selectedInheritable.Inheritable.Key, inheritableXivChatType); + } else if (selectedInheritable.Inheritable.Value is InheritableReference inheritableString) { DrawInheritable(selectedInheritable.Inheritable.Key, inheritableString); @@ -1052,12 +1151,78 @@ namespace PlayerTags.Configuration ImGui.TextColored(new Vector4(0.7f, 0.6f, 1f, 1f), label); } - private void DrawComboBox(bool isLabelVisible, bool shouldLocalizeNames, bool shouldOrderNames, ref TEnum currentValue, System.Action changed) + private void DrawMultiselect(string localizedStringName, InheritableReference> inheritable) where TEnum : Enum + { + bool isDisabled = inheritable.Behavior == InheritableBehavior.Inherit; + List proxyKey = isDisabled ? inheritable.InheritedValue : inheritable.Value; + + if (isDisabled) + proxyKey = inheritable.InheritedValue; + if (proxyKey == null) + proxyKey = inheritable.Value; + + if (isDisabled) + ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.25f); + + var isExpanded = ImGui.CollapsingHeader(Localizer.GetString(localizedStringName, false)); + + if (ImGui.IsItemHovered()) + ImGui.SetTooltip(Localizer.GetString(localizedStringName, true)); + + if (isDisabled) + { + ImGui.SameLine(); + ImGui.Text(Strings.Loc_Static_Inherited); + } + else + DrawRemovePropertyOverrideButton(inheritable); + + if (isExpanded) + { + bool isClicked = false; + var typeofEnum = typeof(TEnum); + EnumMultiselectProxy proxy; + + if (inheritableTEnumProxies.ContainsKey(proxyKey)) + proxy = inheritableTEnumProxies[proxyKey] as EnumMultiselectProxy; + else + { + proxy = new EnumMultiselectProxy(proxyKey); + inheritableTEnumProxies.Add(proxyKey, proxy); + } + + foreach (var entry in proxy.Entries) + { + var entryName = Enum.GetName(typeofEnum, entry.Value); + var tempval = entry.Enabled; + + isClicked = ImGui.Checkbox(Localizer.GetString(entryName), ref isDisabled ? ref tempval : ref entry.Enabled); + + if (ImGui.IsItemHovered()) + ImGui.SetTooltip(Localizer.GetString(entryName, true)); + + if (isClicked && !isDisabled) + { + var newList = proxyKey.ToList(); + proxy.ApplyTo(newList); + inheritable.Value = newList; + SaveSettings(); + } + } + } + + if (isDisabled) + ImGui.PopStyleVar(); + } + + private void DrawComboBox(bool isLabelVisible, bool shouldLocalizeNames, bool shouldOrderNames, ref TEnum currentValue, System.Action changed, bool showToolTipToLabel = false, bool showLabelInSameLine = false) where TEnum : Enum { if (isLabelVisible) { ImGui.Text(Localizer.GetString(false)); + if (showLabelInSameLine) + ImGui.SameLine(); } var currentDisplayName = shouldLocalizeNames ? Localizer.GetString(currentValue, false) : currentValue.ToString(); @@ -1098,7 +1263,10 @@ namespace PlayerTags.Configuration if (ImGui.IsItemHovered() && shouldLocalizeNames) { - ImGui.SetTooltip(Localizer.GetString(currentValue, true)); + if (showToolTipToLabel) + ImGui.SetTooltip(Localizer.GetString(typeof(TEnum).Name, true)); + else + ImGui.SetTooltip(Localizer.GetString(currentValue, true)); } } @@ -1202,6 +1370,7 @@ namespace PlayerTags.Configuration public NameplateFreeCompanyVisibility NameplateFreeCompanyVisibility; public NameplateTitleVisibility NameplateTitleVisibility; public NameplateTitlePosition NameplateTitlePosition; + public DeadPlayerHandling NameplateDeadPlayerHandling; public bool IsApplyTagsToAllChatMessagesEnabled; public PropertyProxy(PluginConfiguration config) @@ -1216,6 +1385,7 @@ namespace PlayerTags.Configuration NameplateFreeCompanyVisibility = pluginConfig.GeneralOptions[currentActivityContext].NameplateFreeCompanyVisibility; NameplateTitleVisibility = pluginConfig.GeneralOptions[currentActivityContext].NameplateTitleVisibility; NameplateTitlePosition = pluginConfig.GeneralOptions[currentActivityContext].NameplateTitlePosition; + NameplateDeadPlayerHandling = pluginConfig.GeneralOptions[currentActivityContext].NameplateDeadPlayerHandling; IsApplyTagsToAllChatMessagesEnabled = pluginConfig.GeneralOptions[currentActivityContext].IsApplyTagsToAllChatMessagesEnabled; } @@ -1233,31 +1403,32 @@ namespace PlayerTags.Configuration applyChanges(GetActivityContext(CurrentActivityContext)); } - void applyChanges(ActivityContext key) + void applyChanges(ActivityType key) { pluginConfig.GeneralOptions[key].NameplateFreeCompanyVisibility = NameplateFreeCompanyVisibility; pluginConfig.GeneralOptions[key].NameplateTitleVisibility = NameplateTitleVisibility; pluginConfig.GeneralOptions[key].NameplateTitlePosition = NameplateTitlePosition; + pluginConfig.GeneralOptions[key].NameplateDeadPlayerHandling = NameplateDeadPlayerHandling; pluginConfig.GeneralOptions[key].IsApplyTagsToAllChatMessagesEnabled = IsApplyTagsToAllChatMessagesEnabled; } } - private ActivityContext GetActivityContext(ActivityContextSelection selection) + private ActivityType GetActivityContext(ActivityContextSelection selection) { - ActivityContext result; + ActivityType result; switch (selection) { case ActivityContextSelection.PveDuty: - result = ActivityContext.PveDuty; + result = ActivityType.PveDuty; break; case ActivityContextSelection.PvpDuty: - result = ActivityContext.PvpDuty; + result = ActivityType.PvpDuty; break; case ActivityContextSelection.All: case ActivityContextSelection.None: default: - result = ActivityContext.None; + result = ActivityType.None; break; } @@ -1272,5 +1443,42 @@ namespace PlayerTags.Configuration PveDuty, PvpDuty } + + private class EnumMultiselectProxy where TEnum : Enum + { + public List Entries { get; } = new(); + + public EnumMultiselectProxy(List target) + { + foreach (TEnum value in Enum.GetValues(typeof(TEnum))) + Entries.Add(new(value, target.Contains(value))); + } + + public void ApplyTo(List target) + { + foreach (var entry in Entries) + { + if (entry.Enabled) + { + if (!target.Contains(entry.Value)) + target.Add(entry.Value); + } + else if (target.Contains(entry.Value)) + target.Remove(entry.Value); + } + } + + public class Entry + { + public TEnum Value { get; set; } + public bool Enabled; + + public Entry(TEnum value, bool enabled) + { + Value = value; + Enabled = enabled; + } + } + } } } diff --git a/PlayerTags/Data/ActivityContext.cs b/PlayerTags/Data/ActivityContext.cs index 6b5ef7c..ec78386 100644 --- a/PlayerTags/Data/ActivityContext.cs +++ b/PlayerTags/Data/ActivityContext.cs @@ -3,6 +3,7 @@ using System; namespace PlayerTags.Data { + [Obsolete] [Flags] [JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] public enum ActivityContext diff --git a/PlayerTags/Data/ActivityContextHelper.cs b/PlayerTags/Data/ActivityContextHelper.cs index c843b33..cf82c52 100644 --- a/PlayerTags/Data/ActivityContextHelper.cs +++ b/PlayerTags/Data/ActivityContextHelper.cs @@ -1,25 +1,21 @@ -namespace PlayerTags.Data +using Pilz.Dalamud.ActivityContexts; + +namespace PlayerTags.Data { public static class ActivityContextHelper { - public static bool GetIsVisible(ActivityContext playerContext, bool desiredPveDutyVisibility, bool desiredPvpDutyVisibility, bool desiredOthersVisibility) + public static bool GetIsVisible(ActivityType playerContext, bool desiredPveDutyVisibility, bool desiredPvpDutyVisibility, bool desiredOthersVisibility) { bool isVisible = false; - if (playerContext.HasFlag(ActivityContext.PveDuty)) - { + if (playerContext.HasFlag(ActivityType.PveDuty)) isVisible |= desiredPveDutyVisibility; - } - if (playerContext.HasFlag(ActivityContext.PvpDuty)) - { + if (playerContext.HasFlag(ActivityType.PvpDuty)) isVisible |= desiredPvpDutyVisibility; - } - if (playerContext == ActivityContext.None) - { + if (playerContext == ActivityType.None) isVisible |= desiredOthersVisibility; - } return isVisible; } diff --git a/PlayerTags/Data/ActivityContextManager.cs b/PlayerTags/Data/ActivityContextManager.cs deleted file mode 100644 index 2225db0..0000000 --- a/PlayerTags/Data/ActivityContextManager.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Dalamud.Plugin; -using Lumina.Excel.GeneratedSheets; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace PlayerTags.Data -{ - public class ActivityContextManager : IDisposable - { - private ActivityContext m_CurrentActivityContext; - - public ActivityContext CurrentActivityContext => m_CurrentActivityContext; - - public ActivityContextManager() - { - m_CurrentActivityContext = ActivityContext.None; - PluginServices.ClientState.TerritoryChanged += ClientState_TerritoryChanged; - } - - private void ClientState_TerritoryChanged(object? sender, ushort e) - { - m_CurrentActivityContext = ActivityContext.None; - - var contentFinderConditionsSheet = PluginServices.DataManager.GameData.GetExcelSheet(); - if (contentFinderConditionsSheet != null) - { - var foundContentFinderCondition = contentFinderConditionsSheet.FirstOrDefault(contentFinderCondition => contentFinderCondition.TerritoryType.Row == PluginServices.ClientState.TerritoryType); - if (foundContentFinderCondition != null) - { - if (foundContentFinderCondition.PvP) - { - m_CurrentActivityContext = ActivityContext.PvpDuty; - } - else - { - m_CurrentActivityContext = ActivityContext.PveDuty; - } - } - } - } - - public void Dispose() - { - PluginServices.ClientState.TerritoryChanged -= ClientState_TerritoryChanged; - } - } -} diff --git a/PlayerTags/Data/DeadPlayerHandling.cs b/PlayerTags/Data/DeadPlayerHandling.cs new file mode 100644 index 0000000..9fd6883 --- /dev/null +++ b/PlayerTags/Data/DeadPlayerHandling.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace PlayerTags.Data +{ + public enum DeadPlayerHandling + { + Ignore, + Include, + GrayOut + } +} diff --git a/PlayerTags/Data/DefaultPluginData.cs b/PlayerTags/Data/DefaultPluginData.cs index 143fc64..e7365ba 100644 --- a/PlayerTags/Data/DefaultPluginData.cs +++ b/PlayerTags/Data/DefaultPluginData.cs @@ -1,5 +1,7 @@ -using Dalamud.Game.Text.SeStringHandling; +using Dalamud.Game.Text; +using Dalamud.Game.Text.SeStringHandling; using Lumina.Excel.GeneratedSheets; +using System; using System.Collections.Generic; using System.Linq; @@ -7,18 +9,232 @@ namespace PlayerTags.Data { public class DefaultPluginData { - public Tag AllTags { get; } + public Tag AllTags { get; private set; } - public Tag AllRoleTags { get; } - public Dictionary RoleTags { get; } - public Dictionary DpsRoleTags { get; } - public Dictionary RangedDpsRoleTags { get; } - public Dictionary LandHandRoleTags { get; } - public Dictionary JobTags { get; } + public Tag AllRoleTags { get; private set; } + public Dictionary RoleTags { get; private set; } + public Dictionary DpsRoleTags { get; private set; } + public Dictionary RangedDpsRoleTags { get; private set; } + public Dictionary LandHandRoleTags { get; private set; } + public Dictionary JobTags { get; private set; } - public Tag AllCustomTags { get; } + public Tag AllCustomTags { get; private set; } - public DefaultPluginData() + public DefaultPluginData(DefaultPluginDataTemplate template) + { + SetupTemplate(template); + } + + private void SetupTemplate(DefaultPluginDataTemplate template) + { + Clear(); + + 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() + { + + RoleTags = new Dictionary(); + DpsRoleTags = new Dictionary(); + RangedDpsRoleTags = new Dictionary(); + LandHandRoleTags = new Dictionary(); + } + + private void SetupTemplateNone() + { + AllTags = new Tag() + { + IsSelected = true, + IsExpanded = 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, + }; + + 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() + { + AllTags = new Tag() + { + IsSelected = true, + IsExpanded = true, + + TagPositionInChat = TagPosition.Before, + InsertBehindNumberPrefixInChat = true, + TagPositionInNameplates = TagPosition.Replace, + TagTargetInNameplates = NameplateElement.Title, + + TargetChatTypes = new List(Enum.GetValues()), + }; + + 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() { @@ -34,30 +250,27 @@ namespace PlayerTags.Data IsVisibleInPveDuties = true, IsVisibleInPvpDuties = true, - //NameplateFreeCompanyVisibility = NameplateFreeCompanyVisibility.Never, - //NameplateTitleVisibility = NameplateTitleVisibility.Always, - //NameplateTitlePosition = NameplateTitlePosition.AlwaysAboveName, - IsVisibleForSelf = true, IsVisibleForFriendPlayers = true, IsVisibleForPartyPlayers = true, IsVisibleForAlliancePlayers = true, IsVisibleForEnemyPlayers = true, IsVisibleForOtherPlayers = true, + + TargetChatTypes = new List(Enum.GetValues()), }; AllRoleTags = new Tag() { IsSelected = false, IsExpanded = true, - IsIconVisibleInChat = true, + IsRoleIconVisibleInChat = true, IsTextVisibleInChat = true, - IsIconVisibleInNameplates = true, + IsRoleIconVisibleInNameplates = true, IsTextVisibleInNameplates = true, IsTextColorAppliedToChatName = true, }; - RoleTags = new Dictionary(); RoleTags[Role.LandHand] = new Tag() { IsSelected = false, @@ -90,7 +303,6 @@ namespace PlayerTags.Data TextColor = 508, }; - DpsRoleTags = new Dictionary(); DpsRoleTags[DpsRole.Melee] = new Tag() { IsSelected = false, @@ -103,7 +315,6 @@ namespace PlayerTags.Data IsExpanded = true, }; - RangedDpsRoleTags = new Dictionary(); RangedDpsRoleTags[RangedDpsRole.Magical] = new Tag() { IsSelected = false, @@ -116,7 +327,6 @@ namespace PlayerTags.Data IsExpanded = false, }; - LandHandRoleTags = new Dictionary(); LandHandRoleTags[LandHandRole.Land] = new Tag() { IsSelected = false, @@ -129,6 +339,133 @@ namespace PlayerTags.Data 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(Enum.GetValues()), + }; + + 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 = new Dictionary(); var classJobs = PluginServices.DataManager.GetExcelSheet(); @@ -150,14 +487,6 @@ namespace PlayerTags.Data } } } - - AllCustomTags = new Tag() - { - IsSelected = false, - IsExpanded = true, - IsTextVisibleInChat = true, - IsTextVisibleInNameplates = true, - }; } } } diff --git a/PlayerTags/Data/DefaultPluginDataTemplate.cs b/PlayerTags/Data/DefaultPluginDataTemplate.cs new file mode 100644 index 0000000..98211ea --- /dev/null +++ b/PlayerTags/Data/DefaultPluginDataTemplate.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace PlayerTags.Data +{ + public enum DefaultPluginDataTemplate + { + None, + Basic, + Simple, + Full + } +} diff --git a/PlayerTags/Data/PluginData.cs b/PlayerTags/Data/PluginData.cs index 50df87d..c8b5481 100644 --- a/PlayerTags/Data/PluginData.cs +++ b/PlayerTags/Data/PluginData.cs @@ -25,13 +25,17 @@ namespace PlayerTags.Data public List CustomTags; public List Identities; - private PluginConfiguration m_PluginConfiguration; + private PluginConfiguration pluginConfiguration; public PluginData(PluginConfiguration pluginConfiguration) { - m_PluginConfiguration = pluginConfiguration; + this.pluginConfiguration = pluginConfiguration; + ReloadDefault(); + } - Default = new DefaultPluginData(); + public void ReloadDefault() + { + Default = new DefaultPluginData(pluginConfiguration.DefaultPluginDataTemplate); // Set the default changes and saved changes AllTags = new Tag(new LocalizedPluginString(nameof(AllTags)), Default.AllTags); @@ -261,7 +265,7 @@ namespace PlayerTags.Data if (identity.WorldId == null && worldId != null) { identity.WorldId = worldId; - m_PluginConfiguration.Save(this); + pluginConfiguration.Save(this); return identity; } diff --git a/PlayerTags/Data/Tag.cs b/PlayerTags/Data/Tag.cs index 51e9d0a..9774806 100644 --- a/PlayerTags/Data/Tag.cs +++ b/PlayerTags/Data/Tag.cs @@ -1,9 +1,14 @@ -using Dalamud.Game.Text.SeStringHandling; +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 { @@ -11,7 +16,10 @@ namespace PlayerTags.Data { public IPluginString Name { get; init; } + [JsonProperty("Parent")] private Tag? m_Parent = null; + + [JsonIgnore] public Tag? Parent { get => m_Parent; @@ -42,6 +50,7 @@ namespace PlayerTags.Data public List Children { get; } = new List(); + [JsonIgnore] public IEnumerable Descendents { get @@ -57,7 +66,9 @@ namespace PlayerTags.Data } } + [JsonIgnore] private Dictionary? m_Inheritables = null; + [JsonIgnore] public Dictionary Inheritables { get @@ -96,12 +107,28 @@ namespace PlayerTags.Data public InheritableValue CustomId = new InheritableValue(Guid.Empty); + [JsonProperty, Obsolete] + private InheritableValue IsIconVisibleInChat + { + set => IsRoleIconVisibleInChat = value; + } + + [JsonProperty, Obsolete] + private InheritableValue IsIconVisibleInNameplate + { + set => IsRoleIconVisibleInNameplates = value; + } + [InheritableCategory("IconCategory")] public InheritableValue Icon = new InheritableValue(BitmapFontIcon.Aethernet); [InheritableCategory("IconCategory")] - public InheritableValue IsIconVisibleInChat = new InheritableValue(false); + public InheritableValue IsRoleIconVisibleInChat = new InheritableValue(false); [InheritableCategory("IconCategory")] - public InheritableValue IsIconVisibleInNameplates = new InheritableValue(false); + public InheritableValue IsRoleIconVisibleInNameplates = new InheritableValue(false); + [InheritableCategory("IconCategory")] + public InheritableValue IsJobIconVisibleInNameplates = new InheritableValue(false); + [InheritableCategory("IconCategory")] + public InheritableValue JobIconSet = new InheritableValue(JobIconSetName.Framed); [InheritableCategory("TextCategory")] public InheritableReference Text = new InheritableReference(""); @@ -160,6 +187,10 @@ namespace PlayerTags.Data [InheritableCategory("PlayerCategory")] public InheritableValue IsVisibleForOtherPlayers = new InheritableValue(false); + [InheritableCategory("ChatFeatureCategory")] + public InheritableReference> TargetChatTypes = new(new List(Enum.GetValues())); + + [JsonIgnore] public string[] IdentitiesToAddTo { get @@ -174,6 +205,7 @@ namespace PlayerTags.Data } private Tag? m_Defaults; + [JsonIgnore] public bool HasDefaults { get { return m_Defaults != null; } @@ -209,7 +241,7 @@ namespace PlayerTags.Data { var inheritableData = inheritable.GetData(); if (inheritableData.Behavior != defaultInheritableData.Behavior || - !inheritableData.Value.Equals(defaultInheritableData.Value)) + !EqualsInheritableData(inheritableData, defaultInheritableData)) { changes[name] = inheritable.GetData(); } @@ -224,6 +256,31 @@ namespace PlayerTags.Data return changes; } + private static bool EqualsInheritableData(InheritableData data1, InheritableData data2) + { + if (data1.Value is List) + return EqualsInheritableDataListXivChatType(data1, data2); + else + return data1.Value.Equals(data2.Value); + } + + private static bool EqualsInheritableDataListXivChatType(InheritableData data1, InheritableData data2) + { + var list1 = data1.Value as List; + var list2 = data2.Value as List; + + 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; + } + public void SetChanges(IEnumerable> changes) { foreach ((var name, var inheritableData) in changes) diff --git a/PlayerTags/Features/ChatTagTargetFeature.cs b/PlayerTags/Features/ChatTagTargetFeature.cs index a3518b3..57d9da8 100644 --- a/PlayerTags/Features/ChatTagTargetFeature.cs +++ b/PlayerTags/Features/ChatTagTargetFeature.cs @@ -5,6 +5,7 @@ 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; @@ -120,7 +121,7 @@ namespace PlayerTags.Features private void Chat_ChatMessage(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled) { - if (m_PluginConfiguration.GeneralOptions[ActivityContextManager.CurrentActivityContext].IsApplyTagsToAllChatMessagesEnabled || Enum.IsDefined(type)) + if (m_PluginConfiguration.GeneralOptions[ActivityContextManager.CurrentActivityContext.ActivityType].IsApplyTagsToAllChatMessagesEnabled && Enum.IsDefined(type)) { AddTagsToChat(sender, type, true); AddTagsToChat(message, type, false); @@ -129,9 +130,9 @@ namespace PlayerTags.Features protected override bool IsIconVisible(Tag tag) { - if (tag.IsIconVisibleInChat.InheritedValue != null) + if (tag.IsRoleIconVisibleInChat.InheritedValue != null) { - return tag.IsIconVisibleInChat.InheritedValue.Value; + return tag.IsRoleIconVisibleInChat.InheritedValue.Value; } return false; @@ -337,14 +338,14 @@ namespace PlayerTags.Features var stringMatches = GetStringMatches(message); foreach (var stringMatch in stringMatches) { - Dictionary stringChanges = new Dictionary(); + StringChanges stringChanges = new(); if (stringMatch.GameObject is PlayerCharacter playerCharacter) { // Add the job tag if (playerCharacter.ClassJob.GameData != null && m_PluginData.JobTags.TryGetValue(playerCharacter.ClassJob.GameData.Abbreviation, out var jobTag)) { - if (jobTag.TagPositionInChat.InheritedValue != null) + if (jobTag.TagPositionInChat.InheritedValue != null && jobTag.TargetChatTypes.InheritedValue != null && jobTag.TargetChatTypes.Value.Contains(chatType)) { var payloads = GetPayloads(jobTag, stringMatch.GameObject); if (payloads.Any()) @@ -364,7 +365,7 @@ namespace PlayerTags.Features var generatedName = BuildPlayername(RandomNameGenerator.Generate(playerName)); if (generatedName != null) { - AddPayloadChanges(TagPosition.Replace, Enumerable.Empty().Append(new TextPayload(generatedName)), stringChanges, false); + AddPayloadChanges(StringPosition.Replace, Enumerable.Empty().Append(new TextPayload(generatedName)), stringChanges, false); } } } @@ -396,7 +397,7 @@ namespace PlayerTags.Features { var insertBehindNumberPrefix = tag.InsertBehindNumberPrefixInChat?.Value ?? true; var insertPositionInChat = tag.TagPositionInChat.InheritedValue.Value; - AddPayloadChanges(insertPositionInChat, payloads, stringChanges, insertBehindNumberPrefix); + AddPayloadChanges((StringPosition)insertPositionInChat, payloads, stringChanges, insertBehindNumberPrefix); } // An additional step to apply text color to additional locations diff --git a/PlayerTags/Features/NameplateTagTargetFeature.cs b/PlayerTags/Features/NameplateTagTargetFeature.cs index 1bd6379..37f4d83 100644 --- a/PlayerTags/Features/NameplateTagTargetFeature.cs +++ b/PlayerTags/Features/NameplateTagTargetFeature.cs @@ -2,7 +2,13 @@ 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.Tools.Strings; using PlayerTags.Configuration; using PlayerTags.Data; using PlayerTags.GameInterface.Nameplates; @@ -18,25 +24,31 @@ namespace PlayerTags.Features /// public class NameplateTagTargetFeature : TagTargetFeature { - private PluginConfiguration m_PluginConfiguration; - private PluginData m_PluginData; + private readonly PluginConfiguration m_PluginConfiguration; + private readonly PluginData m_PluginData; + private readonly StatusIconPriorizer statusiconPriorizer; + private readonly JobIconSets jobIconSets = new(); private Nameplate? m_Nameplate; public NameplateTagTargetFeature(PluginConfiguration pluginConfiguration, PluginData pluginData) { m_PluginConfiguration = pluginConfiguration; m_PluginData = pluginData; + statusiconPriorizer = new(pluginConfiguration.StatusIconPriorizerSettings); 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(); } @@ -79,9 +91,9 @@ namespace PlayerTags.Features protected override bool IsIconVisible(Tag tag) { - if (tag.IsIconVisibleInNameplates.InheritedValue != null) + if (tag.IsRoleIconVisibleInNameplates.InheritedValue != null) { - return tag.IsIconVisibleInNameplates.InheritedValue.Value; + return tag.IsRoleIconVisibleInNameplates.InheritedValue.Value; } return false; @@ -97,11 +109,15 @@ namespace PlayerTags.Features return false; } - private void Nameplate_PlayerNameplateUpdated(PlayerNameplateUpdatedArgs args) + private unsafe void Nameplate_PlayerNameplateUpdated(PlayerNameplateUpdatedArgs args) { var beforeTitleBytes = args.Title.Encode(); - AddTagsToNameplate(args.PlayerCharacter, args.Name, args.Title, args.FreeCompany); - var generalOptions = m_PluginConfiguration.GeneralOptions[ActivityContextManager.CurrentActivityContext]; + var iconID = args.IconId; + var generalOptions = m_PluginConfiguration.GeneralOptions[ActivityContextManager.CurrentActivityContext.ActivityType]; + + AddTagsToNameplate(args.PlayerCharacter, args.Name, args.Title, args.FreeCompany, ref iconID, generalOptions); + + args.IconId = iconID; if (generalOptions.NameplateTitlePosition == NameplateTitlePosition.AlwaysAboveName) args.IsTitleAboveName = true; @@ -129,19 +145,24 @@ namespace PlayerTags.Features /// The position of the changes. /// The payload changes to add. /// The dictionary to add changes to. - private void AddPayloadChanges(NameplateElement nameplateElement, TagPosition tagPosition, IEnumerable payloadChanges, Dictionary> nameplateChanges, bool forceUsingSingleAnchorPayload) + private void AddPayloadChanges(NameplateElement nameplateElement, TagPosition tagPosition, IEnumerable payloadChanges, NameplateChanges nameplateChanges, bool forceUsingSingleAnchorPayload) { - if (!payloadChanges.Any()) + if (payloadChanges.Any()) { - return; + var changes = nameplateChanges.GetChanges((NameplateElements)nameplateElement); + AddPayloadChanges((StringPosition)tagPosition, payloadChanges, changes, forceUsingSingleAnchorPayload); } + } - if (!nameplateChanges.Keys.Contains(nameplateElement)) - { - nameplateChanges[nameplateElement] = new(); - } + 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; - AddPayloadChanges(tagPosition, payloadChanges, nameplateChanges[nameplateElement], forceUsingSingleAnchorPayload); + return nameplateChanges; } /// @@ -151,14 +172,19 @@ namespace PlayerTags.Features /// The name text to change. /// The title text to change. /// The free company text to change. - private void AddTagsToNameplate(GameObject gameObject, SeString name, SeString title, SeString freeCompany) + private void AddTagsToNameplate(GameObject gameObject, SeString name, SeString title, SeString freeCompany, ref int statusIcon, GeneralOptionsClass generalOptions) { - Dictionary> nameplateChanges = new(); + var playerCharacter = gameObject as PlayerCharacter; + int? newStatusIcon = null; + NameplateChanges nameplateChanges = GenerateEmptyNameplateChanges(name, title, freeCompany); - if (gameObject is PlayerCharacter playerCharacter) + if (playerCharacter != null && (!playerCharacter.IsDead || generalOptions.NameplateDeadPlayerHandling != DeadPlayerHandling.Ignore)) { + var classJob = playerCharacter.ClassJob; + var classJobGameData = classJob?.GameData; + // Add the job tags - if (playerCharacter.ClassJob.GameData != null && m_PluginData.JobTags.TryGetValue(playerCharacter.ClassJob.GameData.Abbreviation, out var jobTag)) + if (classJobGameData != null && m_PluginData.JobTags.TryGetValue(classJobGameData.Abbreviation, out var jobTag)) { if (jobTag.TagTargetInNameplates.InheritedValue != null && jobTag.TagPositionInNameplates.InheritedValue != null) checkTag(jobTag); @@ -193,29 +219,29 @@ namespace PlayerTags.Features 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); } } - // Build the final strings out of the payloads - foreach ((var nameplateElement, var stringChanges) in nameplateChanges) + // Apply new status icon + if (newStatusIcon != null) { - SeString? seString = null; - - if (nameplateElement == NameplateElement.Name) - seString = name; - else if (nameplateElement == NameplateElement.Title) - seString = title; - else if (nameplateElement == NameplateElement.FreeCompany) - seString = freeCompany; - - if (seString != null) - ApplyStringChanges(seString, stringChanges); + var change = nameplateChanges.GetChange(NameplateElements.Name, StringPosition.Before); + NameplateUpdateFactory.ApplyStatusIconWithPrio(ref statusIcon, (int)newStatusIcon, change, ActivityContextManager.CurrentActivityContext, statusiconPriorizer, m_PluginConfiguration.MoveStatusIconToNameplateTextIfPossible); } - if (gameObject is PlayerCharacter playerCharacter1) + // 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)) { // An additional step to apply text color to additional locations - Identity identity = m_PluginData.GetIdentity(playerCharacter1); + Identity identity = m_PluginData.GetIdentity(playerCharacter); foreach (var customTagId in identity.CustomTagIds) { var customTag = m_PluginData.CustomTags.FirstOrDefault(tag => tag.CustomId.Value == customTagId); @@ -223,7 +249,7 @@ namespace PlayerTags.Features applyTextFormatting(customTag); } - if (playerCharacter1.ClassJob.GameData != null && m_PluginData.JobTags.TryGetValue(playerCharacter1.ClassJob.GameData.Abbreviation, out var jobTag)) + if (playerCharacter.ClassJob.GameData != null && m_PluginData.JobTags.TryGetValue(playerCharacter.ClassJob.GameData.Abbreviation, out var jobTag)) applyTextFormatting(jobTag); void applyTextFormatting(Tag tag) @@ -234,5 +260,26 @@ namespace PlayerTags.Features } } } + + private void GrayOutNameplate(GameObject gameObject, NameplateChanges nameplateChanges) + { + if (gameObject is PlayerCharacter playerCharacter) + { + foreach (NameplateElements element in Enum.GetValues()) + { + nameplateChanges.GetChange(element, StringPosition.Before).Payloads.Add(new UIForegroundPayload(3)); + nameplateChanges.GetChange(element, StringPosition.After).Payloads.Add(new UIForegroundPayload(0)); + } + } + } + + protected void ApplyNameplateChanges(NameplateChanges nameplateChanges) + { + var props = new NameplateChangesProps + { + Changes = nameplateChanges + }; + NameplateUpdateFactory.ApplyNameplateChanges(props); + } } } diff --git a/PlayerTags/Features/TagTargetFeature.cs b/PlayerTags/Features/TagTargetFeature.cs index eb2be97..a0e6c0b 100644 --- a/PlayerTags/Features/TagTargetFeature.cs +++ b/PlayerTags/Features/TagTargetFeature.cs @@ -4,6 +4,8 @@ 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.GameConfig; using PlayerTags.Data; using PlayerTags.Inheritables; @@ -20,12 +22,6 @@ namespace PlayerTags.Features /// public abstract class TagTargetFeature : IDisposable { - protected class StringChanges - { - public List Payloads { get; init; } = new(); - public bool ForceUsingSingleAnchorPayload { get; set; } = false; - } - public ActivityContextManager ActivityContextManager { get; init; } public TagTargetFeature() @@ -44,7 +40,7 @@ namespace PlayerTags.Features protected bool IsTagVisible(Tag tag, GameObject? gameObject) { - bool isVisibleForActivity = ActivityContextHelper.GetIsVisible(ActivityContextManager.CurrentActivityContext, + bool isVisibleForActivity = ActivityContextHelper.GetIsVisible(ActivityContextManager.CurrentActivityContext.ActivityType, tag.IsVisibleInPveDuties.InheritedValue ?? false, tag.IsVisibleInPvpDuties.InheritedValue ?? false, tag.IsVisibleInOverworld.InheritedValue ?? false); @@ -153,68 +149,6 @@ namespace PlayerTags.Features return newPayloads.ToArray(); } - /// - /// Adds an additional space text payload in between any existing text payloads. If there is an icon payload between two text payloads then the space is skipped. - /// Also adds an extra space to the beginning or end depending on the tag position and whether the most significant payload in either direction is a text payload. - /// In spirit, this is to ensure there is always a space between 2 text payloads, including between these payloads and the target payload. - /// - /// The payloads to add spaces between. - private void AddSpacesBetweenTextPayloads(List payloads, TagPosition tagPosition) - { - if (payloads == null) - { - return; - } - - if (!payloads.Any()) - { - return; - } - - List indicesToInsertSpacesAt = new List(); - int lastTextPayloadIndex = -1; - foreach (var payload in payloads.Reverse()) - { - if (payload is IconPayload iconPayload) - { - lastTextPayloadIndex = -1; - } - else if (payload is TextPayload textPayload) - { - if (lastTextPayloadIndex != -1) - { - indicesToInsertSpacesAt.Add(payloads.IndexOf(textPayload) + 1); - } - - lastTextPayloadIndex = payloads.IndexOf(textPayload); - } - } - - foreach (var indexToInsertSpaceAt in indicesToInsertSpacesAt) - { - payloads.Insert(indexToInsertSpaceAt, new TextPayload($" ")); - } - - // Decide whether to add a space to the end - if (tagPosition == TagPosition.Before) - { - var significantPayloads = payloads.Where(payload => payload is TextPayload || payload is IconPayload); - if (significantPayloads.Last() is TextPayload) - { - payloads.Add(new TextPayload($" ")); - } - } - // Decide whether to add a space to the beginning - else if (tagPosition == TagPosition.After) - { - var significantPayloads = payloads.Where(payload => payload is TextPayload || payload is IconPayload); - if (significantPayloads.First() is TextPayload) - { - payloads.Insert(0, new TextPayload($" ")); - } - } - } - protected static string BuildPlayername(string name) { var logNameType = GameConfigHelper.Instance.GetLogNameType(); @@ -259,14 +193,11 @@ namespace PlayerTags.Features /// The position to add changes to. /// The payloads to add. /// The dictionary to add the changes to. - protected void AddPayloadChanges(TagPosition tagPosition, IEnumerable payloads, Dictionary stringChanges, bool forceUsingSingleAnchorPayload) + protected void AddPayloadChanges(StringPosition tagPosition, IEnumerable payloads, StringChanges stringChanges, bool forceUsingSingleAnchorPayload) { if (payloads != null && payloads.Any() && stringChanges != null) { - if (!stringChanges.Keys.Contains(tagPosition)) - stringChanges[tagPosition] = new(); - - var changes = stringChanges[tagPosition]; + var changes = stringChanges.GetChange(tagPosition); changes.Payloads.AddRange(payloads); changes.ForceUsingSingleAnchorPayload = forceUsingSingleAnchorPayload; } @@ -278,83 +209,21 @@ namespace PlayerTags.Features /// The string to apply changes to. /// The changes to apply. /// The payload in the string that changes should be anchored to. If there is no anchor, the changes will be applied to the entire string. - protected void ApplyStringChanges(SeString seString, Dictionary stringChanges, List anchorPayloads = null, Payload anchorReplacePayload = null) + protected void ApplyStringChanges(SeString seString, StringChanges stringChanges, List anchorPayloads = null, Payload anchorReplacePayload = null) { - if (stringChanges.Count == 0) + var props = new StringChangesProps { - return; - } + Destination = seString, + AnchorPayload = anchorReplacePayload + }; - List tagPositionsOrdered = new List(); - // If there's no anchor payload, do replaces first so that befores and afters are based on the replaced data - if (anchorPayloads == null || !anchorPayloads.Any()) - { - tagPositionsOrdered.Add(TagPosition.Replace); - } + props.AnchorPayloads = anchorPayloads; + props.StringChanges = stringChanges; - tagPositionsOrdered.Add(TagPosition.Before); - tagPositionsOrdered.Add(TagPosition.After); - - // If there is an anchor payload, do replaces last so that we still know which payload needs to be removed - if (anchorPayloads != null && anchorPayloads.Any()) - { - tagPositionsOrdered.Add(TagPosition.Replace); - } - - foreach (var tagPosition in tagPositionsOrdered) - { - if (stringChanges.TryGetValue(tagPosition, out var payloads) && payloads.Payloads.Any()) - { - AddSpacesBetweenTextPayloads(stringChanges[tagPosition].Payloads, tagPosition); - if (tagPosition == TagPosition.Before) - { - Payload anchorFirst = payloads.ForceUsingSingleAnchorPayload ? anchorReplacePayload : anchorPayloads?.FirstOrDefault(); - - if (anchorFirst != null) - { - var anchorPayloadIndex = seString.Payloads.IndexOf(anchorFirst); - seString.Payloads.InsertRange(anchorPayloadIndex, payloads.Payloads); - } - else - { - seString.Payloads.InsertRange(0, payloads.Payloads); - } - } - else if (tagPosition == TagPosition.After) - { - Payload anchorLast = payloads.ForceUsingSingleAnchorPayload? anchorReplacePayload : anchorPayloads?.LastOrDefault(); - - if (anchorLast != null) - { - var anchorPayloadIndex = seString.Payloads.IndexOf(anchorLast); - seString.Payloads.InsertRange(anchorPayloadIndex + 1, payloads.Payloads); - } - else - { - seString.Payloads.AddRange(payloads.Payloads); - } - } - else if (tagPosition == TagPosition.Replace) - { - Payload anchorReplace = anchorReplacePayload; - - if (anchorReplace != null) - { - var anchorPayloadIndex = seString.Payloads.IndexOf(anchorReplace); - seString.Payloads.InsertRange(anchorPayloadIndex, payloads.Payloads); - seString.Remove(anchorReplace); - } - else - { - seString.Payloads.Clear(); - seString.Payloads.AddRange(payloads.Payloads); - } - } - } - } + StringUpdateFactory.ApplyStringChanges(props); } - protected void ApplyTextFormatting(GameObject gameObject, Tag tag, SeString[] destStrings, InheritableValue[] textColorApplied, List preferedPayloads) + protected void ApplyTextFormatting(GameObject gameObject, Tag tag, SeString[] destStrings, InheritableValue[] textColorApplied, List preferedPayloads, ushort? overwriteTextColor = null) { if (IsTagVisible(tag, gameObject)) { @@ -370,11 +239,12 @@ namespace PlayerTags.Features void applyTextColor(SeString destPayload, InheritableValue enableFlag, InheritableValue colorValue) { + var colorToUse = overwriteTextColor ?? colorValue?.InheritedValue; if (shouldApplyFormattingPayloads(destPayload) && enableFlag.InheritedValue != null && enableFlag.InheritedValue.Value - && colorValue.InheritedValue != null) - applyTextFormattingPayloads(destPayload, new UIForegroundPayload(colorValue.InheritedValue.Value), new UIForegroundPayload(0)); + && colorToUse != null) + applyTextFormattingPayloads(destPayload, new UIForegroundPayload(colorToUse.Value), new UIForegroundPayload(0)); } //void applyTextGlowColor(SeString destPayload, InheritableValue enableFlag, InheritableValue colorValue) diff --git a/PlayerTags/GameInterface/Nameplates/Nameplate.cs b/PlayerTags/GameInterface/Nameplates/Nameplate.cs index f2bbb38..62c5350 100644 --- a/PlayerTags/GameInterface/Nameplates/Nameplate.cs +++ b/PlayerTags/GameInterface/Nameplates/Nameplate.cs @@ -4,7 +4,9 @@ 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; @@ -15,9 +17,7 @@ namespace PlayerTags.GameInterface.Nameplates /// public class Nameplate : IDisposable { - [Signature("48 89 5C 24 ?? 48 89 6C 24 ?? 56 57 41 54 41 56 41 57 48 83 EC 40 44 0F B6 E2", DetourName = nameof(SetPlayerNameplateDetour))] - private readonly Hook? hook_AddonNamePlate_SetPlayerNameplateDetour = null; - private unsafe delegate IntPtr AddonNamePlate_SetPlayerNameplateDetour(IntPtr playerNameplateObjectPtr, bool isTitleAboveName, bool isTitleVisible, IntPtr titlePtr, IntPtr namePtr, IntPtr freeCompanyPtr, int iconId); + public NameplateManager NameplateManager { get; init; } /// /// Occurs when a player nameplate is updated by the game. @@ -29,140 +29,37 @@ namespace PlayerTags.GameInterface.Nameplates /// public bool IsValid { - get - { - return hook_AddonNamePlate_SetPlayerNameplateDetour != null - && hook_AddonNamePlate_SetPlayerNameplateDetour.IsEnabled; - } + get => NameplateManager != null && NameplateManager.IsValid; } public Nameplate() { - SignatureHelper.Initialise(this); - hook_AddonNamePlate_SetPlayerNameplateDetour?.Enable(); + NameplateManager = new(); + NameplateManager.Hooks.AddonNamePlate_SetPlayerNameManaged += Hooks_AddonNamePlate_SetPlayerNameManaged; } public void Dispose() { - hook_AddonNamePlate_SetPlayerNameplateDetour?.Disable(); + NameplateManager.Hooks.AddonNamePlate_SetPlayerNameManaged -= Hooks_AddonNamePlate_SetPlayerNameManaged; + NameplateManager.Dispose(); } - private IntPtr SetPlayerNameplateDetour(IntPtr playerNameplateObjectPtr, bool isTitleAboveName, bool isTitleVisible, IntPtr titlePtr, IntPtr namePtr, IntPtr freeCompanyPtr, int iconId) + private void Hooks_AddonNamePlate_SetPlayerNameManaged(Pilz.Dalamud.Nameplates.EventArgs.AddonNamePlate_SetPlayerNameManagedEventArgs eventArgs) { - if (hook_AddonNamePlate_SetPlayerNameplateDetour == null) - { - return IntPtr.Zero; - } - try { - PlayerCharacter? playerCharacter = GetNameplateGameObject(playerNameplateObjectPtr); + PlayerCharacter? playerCharacter = NameplateManager.GetNameplateGameObject(eventArgs.SafeNameplateObject); + if (playerCharacter != null) { - PlayerNameplateUpdatedArgs playerNameplateUpdatedArgs = new PlayerNameplateUpdatedArgs( - playerCharacter, - GameInterfaceHelper.ReadSeString(namePtr), - GameInterfaceHelper.ReadSeString(titlePtr), - GameInterfaceHelper.ReadSeString(freeCompanyPtr), - isTitleVisible, - isTitleAboveName, - iconId); - - byte[] beforeNameBytes = playerNameplateUpdatedArgs.Name.Encode(); - byte[] beforeTitleBytes = playerNameplateUpdatedArgs.Title.Encode(); - byte[] beforeFreeCompanyBytes = playerNameplateUpdatedArgs.FreeCompany.Encode(); - + var playerNameplateUpdatedArgs = new PlayerNameplateUpdatedArgs(playerCharacter, eventArgs); PlayerNameplateUpdated?.Invoke(playerNameplateUpdatedArgs); - - byte[] afterNameBytes = playerNameplateUpdatedArgs.Name.Encode(); - byte[] afterTitleBytes = playerNameplateUpdatedArgs.Title.Encode(); - byte[] afterFreeCompanyBytes = playerNameplateUpdatedArgs.FreeCompany.Encode(); - - IntPtr newNamePtr = namePtr; - bool hasNameChanged = !beforeNameBytes.SequenceEqual(afterNameBytes); - if (hasNameChanged) - { - newNamePtr = GameInterfaceHelper.PluginAllocate(afterNameBytes); - } - - IntPtr newTitlePtr = titlePtr; - bool hasTitleChanged = !beforeTitleBytes.SequenceEqual(afterTitleBytes); - if (hasTitleChanged) - { - newTitlePtr = GameInterfaceHelper.PluginAllocate(afterTitleBytes); - } - - IntPtr newFreeCompanyPtr = freeCompanyPtr; - bool hasFreeCompanyChanged = !beforeFreeCompanyBytes.SequenceEqual(afterFreeCompanyBytes); - if (hasFreeCompanyChanged) - { - newFreeCompanyPtr = GameInterfaceHelper.PluginAllocate(afterFreeCompanyBytes); - } - - var result = hook_AddonNamePlate_SetPlayerNameplateDetour.Original(playerNameplateObjectPtr, playerNameplateUpdatedArgs.IsTitleAboveName, playerNameplateUpdatedArgs.IsTitleVisible, newTitlePtr, newNamePtr, newFreeCompanyPtr, playerNameplateUpdatedArgs.IconId); - - if (hasNameChanged) - { - GameInterfaceHelper.PluginFree(ref newNamePtr); - } - - if (hasTitleChanged) - { - GameInterfaceHelper.PluginFree(ref newTitlePtr); - } - - if (hasFreeCompanyChanged) - { - GameInterfaceHelper.PluginFree(ref newFreeCompanyPtr); - } - - return result; } } catch (Exception ex) { PluginLog.Error(ex, $"SetPlayerNameplateDetour"); } - - return hook_AddonNamePlate_SetPlayerNameplateDetour.Original(playerNameplateObjectPtr, isTitleAboveName, isTitleVisible, titlePtr, namePtr, freeCompanyPtr, iconId); - } - - private T? GetNameplateGameObject(IntPtr nameplateObjectPtr) - where T : GameObject - { - // Get the nameplate object array - var nameplateAddonPtr = PluginServices.GameGui.GetAddonByName("NamePlate", 1); - var nameplateObjectArrayPtrPtr = nameplateAddonPtr + Marshal.OffsetOf(typeof(AddonNamePlate), nameof(AddonNamePlate.NamePlateObjectArray)).ToInt32(); - var nameplateObjectArrayPtr = Marshal.ReadIntPtr(nameplateObjectArrayPtrPtr); - if (nameplateObjectArrayPtr == IntPtr.Zero) - { - return null; - } - - // Determine the index of the nameplate object within the nameplate object array - var namePlateObjectSize = Marshal.SizeOf(typeof(AddonNamePlate.NamePlateObject)); - var namePlateObjectPtr0 = nameplateObjectArrayPtr + namePlateObjectSize * 0; - var namePlateIndex = (nameplateObjectPtr.ToInt64() - namePlateObjectPtr0.ToInt64()) / namePlateObjectSize; - if (namePlateIndex < 0 || namePlateIndex >= AddonNamePlate.NumNamePlateObjects) - { - return null; - } - - // Get the nameplate info array - IntPtr nameplateInfoArrayPtr = IntPtr.Zero; - unsafe - { - var framework = FFXIVClientStructs.FFXIV.Client.System.Framework.Framework.Instance(); - nameplateInfoArrayPtr = new IntPtr(&framework->GetUiModule()->GetRaptureAtkModule()->NamePlateInfoArray); - } - - // Get the nameplate info for the nameplate object - var namePlateInfoPtr = new IntPtr(nameplateInfoArrayPtr.ToInt64() + Marshal.SizeOf(typeof(RaptureAtkModule.NamePlateInfo)) * namePlateIndex); - RaptureAtkModule.NamePlateInfo namePlateInfo = Marshal.PtrToStructure(namePlateInfoPtr); - - // Return the object for its object id - var objectId = namePlateInfo.ObjectID.ObjectID; - return PluginServices.ObjectTable.SearchById(objectId) as T; } } } diff --git a/PlayerTags/GameInterface/Nameplates/PlayerNameplateUpdatedArgs.cs b/PlayerTags/GameInterface/Nameplates/PlayerNameplateUpdatedArgs.cs index 517410a..8621f90 100644 --- a/PlayerTags/GameInterface/Nameplates/PlayerNameplateUpdatedArgs.cs +++ b/PlayerTags/GameInterface/Nameplates/PlayerNameplateUpdatedArgs.cs @@ -1,33 +1,52 @@ 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; } + public SeString Name + { + get => eventArgs.Name; + } - public SeString Title { get; } + public SeString Title + { + get => eventArgs.Title; + } - public SeString FreeCompany { get; } + public SeString FreeCompany + { + get => eventArgs.FreeCompany; + } - public bool IsTitleVisible { get; set; } + public bool IsTitleVisible + { + get => eventArgs.IsTitleVisible; + set => eventArgs.IsTitleVisible = value; + } - public bool IsTitleAboveName { get; set; } + public bool IsTitleAboveName + { + get => eventArgs.IsTitleAboveName; + set => eventArgs.IsTitleAboveName = value; + } - public int IconId { get; set; } + public int IconId + { + get => eventArgs.IconID; + set => eventArgs.IconID = value; + } - public PlayerNameplateUpdatedArgs(PlayerCharacter playerCharacter, SeString name, SeString title, SeString freeCompany, bool isTitleVisible, bool isTitleAboveName, int iconId) + public PlayerNameplateUpdatedArgs(PlayerCharacter playerCharacter, AddonNamePlate_SetPlayerNameManagedEventArgs eventArgs) { PlayerCharacter = playerCharacter; - Name = name; - Title = title; - FreeCompany = freeCompany; - IsTitleVisible = isTitleVisible; - IsTitleAboveName = isTitleAboveName; - IconId = iconId; + this.eventArgs = eventArgs; } } } diff --git a/PlayerTags/GeneralConverter.cs b/PlayerTags/GeneralConverter.cs deleted file mode 100644 index eca30aa..0000000 --- a/PlayerTags/GeneralConverter.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Newtonsoft.Json; -using System; - -namespace PlayerTags -{ - public class GeneralConverter : JsonConverter - { - public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) - { - if (value != null && value.GetType().IsEnum) - { - writer.WriteValue(Enum.GetName(value.GetType(), value)); - } - else - { - writer.WriteValue(value); - } - } - - public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) - { - return reader.Value; - } - - public override bool CanConvert(Type objectType) - { - return true; - } - } -} diff --git a/PlayerTags/Inheritables/InheritableBehavior.cs b/PlayerTags/Inheritables/InheritableBehavior.cs index 752c2e1..1691864 100644 --- a/PlayerTags/Inheritables/InheritableBehavior.cs +++ b/PlayerTags/Inheritables/InheritableBehavior.cs @@ -1,4 +1,7 @@ -namespace PlayerTags.Inheritables +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace PlayerTags.Inheritables { public enum InheritableBehavior { diff --git a/PlayerTags/Inheritables/InheritableData.cs b/PlayerTags/Inheritables/InheritableData.cs index 88d8d9b..9b95b85 100644 --- a/PlayerTags/Inheritables/InheritableData.cs +++ b/PlayerTags/Inheritables/InheritableData.cs @@ -12,7 +12,6 @@ namespace PlayerTags.Inheritables public InheritableBehavior Behavior; [JsonProperty("Value")] - [JsonConverter(typeof(GeneralConverter))] public object Value; } } diff --git a/PlayerTags/Inheritables/InheritableReference.cs b/PlayerTags/Inheritables/InheritableReference.cs index abc101f..cabcf62 100644 --- a/PlayerTags/Inheritables/InheritableReference.cs +++ b/PlayerTags/Inheritables/InheritableReference.cs @@ -1,4 +1,8 @@ -namespace PlayerTags.Inheritables +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using System.Collections.Generic; + +namespace PlayerTags.Inheritables { public class InheritableReference : IInheritable where T : class @@ -7,8 +11,10 @@ public InheritableBehavior Behavior { get; set; } + [JsonProperty] public T Value; + [JsonIgnore] public T? InheritedValue { get diff --git a/PlayerTags/Inheritables/InheritableValue.cs b/PlayerTags/Inheritables/InheritableValue.cs index e49e304..f17e55a 100644 --- a/PlayerTags/Inheritables/InheritableValue.cs +++ b/PlayerTags/Inheritables/InheritableValue.cs @@ -1,4 +1,5 @@ using Dalamud.Logging; +using Newtonsoft.Json; using System; namespace PlayerTags.Inheritables @@ -10,8 +11,10 @@ namespace PlayerTags.Inheritables public InheritableBehavior Behavior { get; set; } + [JsonProperty] public T Value; + [JsonIgnore] public T? InheritedValue { get diff --git a/PlayerTags/PlayerTags.csproj b/PlayerTags/PlayerTags.csproj index f1a8fd5..6311c54 100644 --- a/PlayerTags/PlayerTags.csproj +++ b/PlayerTags/PlayerTags.csproj @@ -1,7 +1,7 @@  r00telement;Pilzinsel64 - 1.7.5.1 + 1.8.0.6 @@ -26,6 +26,7 @@ + $(DalamudLibPath)FFXIVClientStructs.dll false diff --git a/PlayerTags/Plugin.cs b/PlayerTags/Plugin.cs index 293885d..a1d6d1a 100644 --- a/PlayerTags/Plugin.cs +++ b/PlayerTags/Plugin.cs @@ -1,4 +1,5 @@ using Dalamud.Game.Command; +using Dalamud.Logging; using Dalamud.Plugin; using Dalamud.Plugin.Internal; using FFXIVClientStructs.FFXIV.Client.UI.Misc; @@ -28,8 +29,9 @@ namespace PlayerTags public Plugin(DalamudPluginInterface pluginInterface) { PluginServices.Initialize(pluginInterface); + Pilz.Dalamud.PluginServices.Initialize(pluginInterface); - m_PluginConfiguration = PluginServices.DalamudPluginInterface.GetPluginConfig() as PluginConfiguration ?? new PluginConfiguration(); + m_PluginConfiguration = PluginConfiguration.LoadPluginConfig() ?? new PluginConfiguration(); m_PluginData = new PluginData(m_PluginConfiguration); m_PluginConfigurationUI = new PluginConfigurationUI(m_PluginConfiguration, m_PluginData); diff --git a/PlayerTags/Resources/Strings.Designer.cs b/PlayerTags/Resources/Strings.Designer.cs index ceb3809..e0808c2 100644 --- a/PlayerTags/Resources/Strings.Designer.cs +++ b/PlayerTags/Resources/Strings.Designer.cs @@ -177,6 +177,15 @@ namespace PlayerTags.Resources { } } + /// + /// Sucht eine lokalisierte Zeichenfolge, die Advanced Chat Options ähnelt. + /// + public static string Loc_ChatFeatureCategory { + get { + return ResourceManager.GetString("Loc_ChatFeatureCategory", resourceCulture); + } + } + /// /// Sucht eine lokalisierte Zeichenfolge, die Custom id ähnelt. /// @@ -195,6 +204,167 @@ namespace PlayerTags.Resources { } } + /// + /// Sucht eine lokalisierte Zeichenfolge, die Handling for dead players ähnelt. + /// + public static string Loc_DeadPlayerHandling { + get { + return ResourceManager.GetString("Loc_DeadPlayerHandling", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Gray out ähnelt. + /// + public static string Loc_DeadPlayerHandling_GrayOut { + get { + return ResourceManager.GetString("Loc_DeadPlayerHandling_GrayOut", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Apply any tag but gray out the nameplate. ähnelt. + /// + public static string Loc_DeadPlayerHandling_GrayOut_Description { + get { + return ResourceManager.GetString("Loc_DeadPlayerHandling_GrayOut_Description", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Ignore ähnelt. + /// + public static string Loc_DeadPlayerHandling_Ignore { + get { + return ResourceManager.GetString("Loc_DeadPlayerHandling_Ignore", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Don't process dead players and don't apply any tag for. ähnelt. + /// + public static string Loc_DeadPlayerHandling_Ignore_Description { + get { + return ResourceManager.GetString("Loc_DeadPlayerHandling_Ignore_Description", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Include ähnelt. + /// + public static string Loc_DeadPlayerHandling_Include { + get { + return ResourceManager.GetString("Loc_DeadPlayerHandling_Include", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Handle dead players as they are alive. No difference between dead and alive players. ähnelt. + /// + public static string Loc_DeadPlayerHandling_Include_Description { + get { + return ResourceManager.GetString("Loc_DeadPlayerHandling_Include_Description", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Template ähnelt. + /// + public static string Loc_DefaultPluginDataTemplate { + get { + return ResourceManager.GetString("Loc_DefaultPluginDataTemplate", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Basic ähnelt. + /// + public static string Loc_DefaultPluginDataTemplate_Basic { + get { + return ResourceManager.GetString("Loc_DefaultPluginDataTemplate_Basic", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Same as Empty, but includes a very basic pre-confiuration for formatting and coloring. + ///Can also be used if you want to make your own cofiguration but want basic formatting. ähnelt. + /// + public static string Loc_DefaultPluginDataTemplate_Basic_Description { + get { + return ResourceManager.GetString("Loc_DefaultPluginDataTemplate_Basic_Description", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Here you can choose the template for all the settings below. + ///The template is used as basic set of properties. Every change you are making is what get saved. But all options from this template will not be saved. + ///This helpes you by not needing to overwrite so much properties - or by doing your completely own configuration without a template. + /// + ///Warning: + ///Changing this can cause properties to be resetted. You may loose a part of your configuration. + ///After you changed this, ensure everything is setted up how [Rest der Zeichenfolge wurde abgeschnitten]"; ähnelt. + /// + public static string Loc_DefaultPluginDataTemplate_Description { + get { + return ResourceManager.GetString("Loc_DefaultPluginDataTemplate_Description", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Full ähnelt. + /// + public static string Loc_DefaultPluginDataTemplate_Full { + get { + return ResourceManager.GetString("Loc_DefaultPluginDataTemplate_Full", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Show the job tag with color and also color the player name element. ähnelt. + /// + public static string Loc_DefaultPluginDataTemplate_Full_Description { + get { + return ResourceManager.GetString("Loc_DefaultPluginDataTemplate_Full_Description", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Empty ähnelt. + /// + public static string Loc_DefaultPluginDataTemplate_None { + get { + return ResourceManager.GetString("Loc_DefaultPluginDataTemplate_None", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die No single configuration made. This is an completley empty template. + ///Use this if you want to to have every option under your control or just want to make only a few configurations. ähnelt. + /// + public static string Loc_DefaultPluginDataTemplate_None_Description { + get { + return ResourceManager.GetString("Loc_DefaultPluginDataTemplate_None_Description", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Simple ähnelt. + /// + public static string Loc_DefaultPluginDataTemplate_Simple { + get { + return ResourceManager.GetString("Loc_DefaultPluginDataTemplate_Simple", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Shows the job tag with color and the role icon by replacing the title. ähnelt. + /// + public static string Loc_DefaultPluginDataTemplate_Simple_Description { + get { + return ResourceManager.GetString("Loc_DefaultPluginDataTemplate_Simple_Description", resourceCulture); + } + } + /// /// Sucht eine lokalisierte Zeichenfolge, die Melee ähnelt. /// @@ -358,29 +528,20 @@ namespace PlayerTags.Resources { } /// - /// Sucht eine lokalisierte Zeichenfolge, die Whether the icon will be shown in chat. ähnelt. + /// Sucht eine lokalisierte Zeichenfolge, die Show job icon in nameplates ähnelt. /// - public static string Loc_IsIconVisibleInChat_Description { + public static string Loc_IsJobIconVisibleInNameplates { get { - return ResourceManager.GetString("Loc_IsIconVisibleInChat_Description", resourceCulture); + return ResourceManager.GetString("Loc_IsJobIconVisibleInNameplates", resourceCulture); } } /// - /// Sucht eine lokalisierte Zeichenfolge, die Show in nameplates ähnelt. + /// Sucht eine lokalisierte Zeichenfolge, die Whether the job icon will be shown in nameplates. ähnelt. /// - public static string Loc_IsIconVisibleInNameplates { + public static string Loc_IsJobIconVisibleInNameplates_Description { get { - return ResourceManager.GetString("Loc_IsIconVisibleInNameplates", resourceCulture); - } - } - - /// - /// Sucht eine lokalisierte Zeichenfolge, die Whether the icon will be shown in nameplates. ähnelt. - /// - public static string Loc_IsIconVisibleInNameplates_Description { - get { - return ResourceManager.GetString("Loc_IsIconVisibleInNameplates_Description", resourceCulture); + return ResourceManager.GetString("Loc_IsJobIconVisibleInNameplates_Description", resourceCulture); } } @@ -546,6 +707,42 @@ namespace PlayerTags.Resources { } } + /// + /// Sucht eine lokalisierte Zeichenfolge, die Whether the role icon will be shown in chat. ähnelt. + /// + public static string Loc_IsRoleIconVisibleInChat_Description { + get { + return ResourceManager.GetString("Loc_IsRoleIconVisibleInChat_Description", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Show role icon in nameplates ähnelt. + /// + public static string Loc_IsRoleIconVisibleInNameplates { + get { + return ResourceManager.GetString("Loc_IsRoleIconVisibleInNameplates", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Whether the role icon will be shown in nameplates. ähnelt. + /// + public static string Loc_IsRoleIconVisibleInNameplates_Description { + get { + return ResourceManager.GetString("Loc_IsRoleIconVisibleInNameplates_Description", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Show role icon in nameplate ähnelt. + /// + public static string Loc_IsRoleJobIconVisibleInNameplates { + get { + return ResourceManager.GetString("Loc_IsRoleJobIconVisibleInNameplates", resourceCulture); + } + } + /// /// Sucht eine lokalisierte Zeichenfolge, die Selected ähnelt. /// @@ -861,6 +1058,24 @@ namespace PlayerTags.Resources { } } + /// + /// Sucht eine lokalisierte Zeichenfolge, die Job icon set ähnelt. + /// + public static string Loc_JobIconSet { + get { + return ResourceManager.GetString("Loc_JobIconSet", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die The icon set to use for displaying the job icon. You can also choose the role icon set to display the role icon instead. ähnelt. + /// + public static string Loc_JobIconSet_Description { + get { + return ResourceManager.GetString("Loc_JobIconSet_Description", resourceCulture); + } + } + /// /// Sucht eine lokalisierte Zeichenfolge, die Hand ähnelt. /// @@ -879,6 +1094,24 @@ namespace PlayerTags.Resources { } } + /// + /// Sucht eine lokalisierte Zeichenfolge, die Move Status Icon to Nameplate text if possible ähnelt. + /// + public static string Loc_MoveStatusIconToNameplateTextIfPossible { + get { + return ResourceManager.GetString("Loc_MoveStatusIconToNameplateTextIfPossible", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die If the current status icon is available as Font Icon then move it to the Player Name text in the Nameplate, so there is place for another icon to use, e.g. the job icon. ähnelt. + /// + public static string Loc_MoveStatusIconToNameplateTextIfPossible_Description { + get { + return ResourceManager.GetString("Loc_MoveStatusIconToNameplateTextIfPossible_Description", resourceCulture); + } + } + /// /// Sucht eine lokalisierte Zeichenfolge, die Nameplate properties ähnelt. /// @@ -1437,6 +1670,15 @@ namespace PlayerTags.Resources { } } + /// + /// Sucht eine lokalisierte Zeichenfolge, die Status Icon Priorizer ähnelt. + /// + public static string Loc_Static_StatusIconPrioList { + get { + return ResourceManager.GetString("Loc_Static_StatusIconPrioList", resourceCulture); + } + } + /// /// Sucht eine lokalisierte Zeichenfolge, die Tagged Players ähnelt. /// @@ -1464,6 +1706,240 @@ namespace PlayerTags.Resources { } } + /// + /// Sucht eine lokalisierte Zeichenfolge, die Reset to default ähnelt. + /// + public static string Loc_StatusIconPriorizer_ResetToDefault { + get { + return ResourceManager.GetString("Loc_StatusIconPriorizer_ResetToDefault", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Resets all condition sets to the default settings ähnelt. + /// + public static string Loc_StatusIconPriorizer_ResetToDefault_Description { + get { + return ResourceManager.GetString("Loc_StatusIconPriorizer_ResetToDefault_Description", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Reset to empty ähnelt. + /// + public static string Loc_StatusIconPriorizer_ResetToEmpty { + get { + return ResourceManager.GetString("Loc_StatusIconPriorizer_ResetToEmpty", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Clears all condition sets to an empty collection. None status icons will be priorized. ähnelt. + /// + public static string Loc_StatusIconPriorizer_ResetToEmpty_Description { + get { + return ResourceManager.GetString("Loc_StatusIconPriorizer_ResetToEmpty_Description", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die In Duty ähnelt. + /// + public static string Loc_StatusIconPriorizerConditionSets_InDuty { + get { + return ResourceManager.GetString("Loc_StatusIconPriorizerConditionSets_InDuty", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Status icons that should get priorized within duties. ähnelt. + /// + public static string Loc_StatusIconPriorizerConditionSets_InDuty_Description { + get { + return ResourceManager.GetString("Loc_StatusIconPriorizerConditionSets_InDuty_Description", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die In Foray ähnelt. + /// + public static string Loc_StatusIconPriorizerConditionSets_InForay { + get { + return ResourceManager.GetString("Loc_StatusIconPriorizerConditionSets_InForay", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Status icons that should get priorized within foraies. ähnelt. + /// + public static string Loc_StatusIconPriorizerConditionSets_InForay_Description { + get { + return ResourceManager.GetString("Loc_StatusIconPriorizerConditionSets_InForay_Description", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Overworld ähnelt. + /// + public static string Loc_StatusIconPriorizerConditionSets_Overworld { + get { + return ResourceManager.GetString("Loc_StatusIconPriorizerConditionSets_Overworld", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Status icons that should get priorized at overworld. ähnelt. + /// + public static string Loc_StatusIconPriorizerConditionSets_Overworld_Description { + get { + return ResourceManager.GetString("Loc_StatusIconPriorizerConditionSets_Overworld_Description", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Busy ähnelt. + /// + public static string Loc_StatusIcons_Busy { + get { + return ResourceManager.GetString("Loc_StatusIcons_Busy", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Disconnecting ähnelt. + /// + public static string Loc_StatusIcons_Disconnecting { + get { + return ResourceManager.GetString("Loc_StatusIcons_Disconnecting", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Duty Finder ähnelt. + /// + public static string Loc_StatusIcons_DutyFinder { + get { + return ResourceManager.GetString("Loc_StatusIcons_DutyFinder", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Group Pose ähnelt. + /// + public static string Loc_StatusIcons_GroupPose { + get { + return ResourceManager.GetString("Loc_StatusIcons_GroupPose", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Idle ähnelt. + /// + public static string Loc_StatusIcons_Idle { + get { + return ResourceManager.GetString("Loc_StatusIcons_Idle", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die In Duty ähnelt. + /// + public static string Loc_StatusIcons_InDuty { + get { + return ResourceManager.GetString("Loc_StatusIcons_InDuty", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Mentor ähnelt. + /// + public static string Loc_StatusIcons_Mentor { + get { + return ResourceManager.GetString("Loc_StatusIcons_Mentor", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Mentor Crafting ähnelt. + /// + public static string Loc_StatusIcons_MentorCrafting { + get { + return ResourceManager.GetString("Loc_StatusIcons_MentorCrafting", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Mentor PvE ähnelt. + /// + public static string Loc_StatusIcons_MentorPvE { + get { + return ResourceManager.GetString("Loc_StatusIcons_MentorPvE", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Mentor PvP ähnelt. + /// + public static string Loc_StatusIcons_MentorPvP { + get { + return ResourceManager.GetString("Loc_StatusIcons_MentorPvP", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die New Adventurer ähnelt. + /// + public static string Loc_StatusIcons_NewAdventurer { + get { + return ResourceManager.GetString("Loc_StatusIcons_NewAdventurer", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Party Leader ähnelt. + /// + public static string Loc_StatusIcons_PartyLeader { + get { + return ResourceManager.GetString("Loc_StatusIcons_PartyLeader", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Party Member ähnelt. + /// + public static string Loc_StatusIcons_PartyMember { + get { + return ResourceManager.GetString("Loc_StatusIcons_PartyMember", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Returner ähnelt. + /// + public static string Loc_StatusIcons_Returner { + get { + return ResourceManager.GetString("Loc_StatusIcons_Returner", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Role Playing ähnelt. + /// + public static string Loc_StatusIcons_RolePlaying { + get { + return ResourceManager.GetString("Loc_StatusIcons_RolePlaying", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Viewing Cutscene ähnelt. + /// + public static string Loc_StatusIcons_ViewingCutscene { + get { + return ResourceManager.GetString("Loc_StatusIcons_ViewingCutscene", resourceCulture); + } + } + /// /// Sucht eine lokalisierte Zeichenfolge, die After ähnelt. /// @@ -1572,6 +2048,24 @@ namespace PlayerTags.Resources { } } + /// + /// Sucht eine lokalisierte Zeichenfolge, die Target Chat Types ähnelt. + /// + public static string Loc_TargetChatTypes { + get { + return ResourceManager.GetString("Loc_TargetChatTypes", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Defines for which chat type the chat features of this tag should be enabled for. ähnelt. + /// + public static string Loc_TargetChatTypes_Description { + get { + return ResourceManager.GetString("Loc_TargetChatTypes_Description", resourceCulture); + } + } + /// /// Sucht eine lokalisierte Zeichenfolge, die Text ähnelt. /// @@ -1634,5 +2128,24 @@ namespace PlayerTags.Resources { return ResourceManager.GetString("Loc_TextGlowColor_Description", resourceCulture); } } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Use priorized icons ähnelt. + /// + public static string Loc_UsePriorizedIcons { + get { + return ResourceManager.GetString("Loc_UsePriorizedIcons", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die This option checked will force a set of status icons to be priorized over job icons, if you enabled job icons. + ///When disabled, only the disconnected status icon is priorized. ähnelt. + /// + public static string Loc_UsePriorizedIcons_Description { + get { + return ResourceManager.GetString("Loc_UsePriorizedIcons_Description", resourceCulture); + } + } } } diff --git a/PlayerTags/Resources/Strings.de.resx b/PlayerTags/Resources/Strings.de.resx index 96de658..fbf466b 100644 --- a/PlayerTags/Resources/Strings.de.resx +++ b/PlayerTags/Resources/Strings.de.resx @@ -354,15 +354,6 @@ Zeige im Chat - - Bestimmt, ob das Symbol im Chat dargestellt wird. - - - Zeige in der Namensanzeige - - - Bestimmt, ob das Symbol in der Namensanzeige dargestellt wird. - Text diff --git a/PlayerTags/Resources/Strings.es-ES.resx b/PlayerTags/Resources/Strings.es-ES.resx index 35cfbd2..b2c166b 100644 --- a/PlayerTags/Resources/Strings.es-ES.resx +++ b/PlayerTags/Resources/Strings.es-ES.resx @@ -354,15 +354,6 @@ Mostrar en el chat - - Si el icono se mostrará o no dentro del chat. - - - Mostrar en placas de nombre - - - Si el icono se mostrará o no en las placas de nombre. - Texto @@ -621,25 +612,4 @@ Otros (Experimental) - - Insert behind group number prefix in chat - - - If enabled, the Tag and Icon will get insert behind the Group/Alliance number prefix in Chat instead of before. - - - Nameplate properties - - - Defines whenever the free company element of the nameplate should be visible or not. - - - Defines the position for the title element of the nameplate. Should it be above or below the name? You can decide! - - - Defines whenever the title element of the nameplate should be visible or not. - - - Chat - \ No newline at end of file diff --git a/PlayerTags/Resources/Strings.fr.resx b/PlayerTags/Resources/Strings.fr.resx index a894726..12643c9 100644 --- a/PlayerTags/Resources/Strings.fr.resx +++ b/PlayerTags/Resources/Strings.fr.resx @@ -354,15 +354,6 @@ Afficher dans le chat - - Si l'icône doit être affichée dans le chat. - - - Afficher dans les plaques de noms - - - Si l'icône doit être affichée dans les plaques de noms. - Texte @@ -621,25 +612,4 @@ Autre (expérimental) - - Insert behind group number prefix in chat - - - If enabled, the Tag and Icon will get insert behind the Group/Alliance number prefix in Chat instead of before. - - - Nameplate properties - - - Defines whenever the free company element of the nameplate should be visible or not. - - - Defines the position for the title element of the nameplate. Should it be above or below the name? You can decide! - - - Defines whenever the title element of the nameplate should be visible or not. - - - Chat - \ No newline at end of file diff --git a/PlayerTags/Resources/Strings.resx b/PlayerTags/Resources/Strings.resx index c272ddc..43bbdae 100644 --- a/PlayerTags/Resources/Strings.resx +++ b/PlayerTags/Resources/Strings.resx @@ -354,14 +354,14 @@ Show in chat - - Whether the icon will be shown in chat. + + Whether the role icon will be shown in chat. - - Show in nameplates + + Show role icon in nameplates - - Whether the icon will be shown in nameplates. + + Whether the role icon will be shown in nameplates. Text @@ -642,4 +642,181 @@ Chat + + Show role icon in nameplate + + + Show job icon in nameplates + + + Whether the job icon will be shown in nameplates. + + + Job icon set + + + The icon set to use for displaying the job icon. You can also choose the role icon set to display the role icon instead. + + + Advanced Chat Options + + + Target Chat Types + + + Defines for which chat type the chat features of this tag should be enabled for. + + + Status Icon Priorizer + + + In Duty + + + Status icons that should get priorized within duties. + + + In Foray + + + Status icons that should get priorized within foraies. + + + Overworld + + + Status icons that should get priorized at overworld. + + + Reset to default + + + Resets all condition sets to the default settings + + + Reset to empty + + + Clears all condition sets to an empty collection. None status icons will be priorized. + + + Use priorized icons + + + This option checked will force a set of status icons to be priorized over job icons, if you enabled job icons. +When disabled, only the disconnected status icon is priorized. + + + Busy + + + Disconnecting + + + Duty Finder + + + Group Pose + + + Idle + + + In Duty + + + Mentor + + + Mentor Crafting + + + Mentor PvE + + + Mentor PvP + + + New Adventurer + + + Party Leader + + + Party Member + + + Returner + + + Role Playing + + + Viewing Cutscene + + + Template + + + Basic + + + Same as Empty, but includes a very basic pre-confiuration for formatting and coloring. +Can also be used if you want to make your own cofiguration but want basic formatting. + + + Here you can choose the template for all the settings below. +The template is used as basic set of properties. Every change you are making is what get saved. But all options from this template will not be saved. +This helpes you by not needing to overwrite so much properties - or by doing your completely own configuration without a template. + +Warning: +Changing this can cause properties to be resetted. You may loose a part of your configuration. +After you changed this, ensure everything is setted up how you like it. + + + Full + + + Show the job tag with color and also color the player name element. + + + Empty + + + No single configuration made. This is an completley empty template. +Use this if you want to to have every option under your control or just want to make only a few configurations. + + + Simple + + + Shows the job tag with color and the role icon by replacing the title. + + + Gray out + + + Ignore + + + Include + + + Handling for dead players + + + Apply any tag but gray out the nameplate. + + + Don't process dead players and don't apply any tag for. + + + Handle dead players as they are alive. No difference between dead and alive players. + + + Move Status Icon to Nameplate text if possible + + + If the current status icon is available as Font Icon then move it to the Player Name text in the Nameplate, so there is place for another icon to use, e.g. the job icon. + \ No newline at end of file diff --git a/PlayerTags/packages.lock.json b/PlayerTags/packages.lock.json index abf5900..d25458d 100644 --- a/PlayerTags/packages.lock.json +++ b/PlayerTags/packages.lock.json @@ -13,6 +13,9 @@ "requested": "[2.1.8, )", "resolved": "2.1.8", "contentHash": "YqagNXs9InxmqkXzq7kLveImxnodkBEicAhydMXVp7dFjC7xb76U6zGgAax4/BWIWfZeWzr5DJyQSev31kj81A==" + }, + "pilz.dalamud": { + "type": "Project" } } }