From 4741d8c4375fcbf7be734ba6a6689a895005f570 Mon Sep 17 00:00:00 2001 From: Pilzinsel64 Date: Fri, 4 Nov 2022 17:57:03 +0100 Subject: [PATCH] =?UTF-8?q?Projektdateien=20hinzuf=C3=BCgen.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Pilz.Dalamud.sln | 25 +++ .../ActivityContexts/ActivityContext.cs | 25 +++ .../ActivityContextManager.cs | 85 +++++++++ Pilz.Dalamud/ActivityContexts/ActivityType.cs | 18 ++ Pilz.Dalamud/ActivityContexts/ZoneType.cs | 20 ++ Pilz.Dalamud/Extensions.cs | 41 ++++ Pilz.Dalamud/GameInterfaceHelper.cs | 153 +++++++++++++++ Pilz.Dalamud/Icons/JobIconSet.cs | 27 +++ Pilz.Dalamud/Icons/JobIconSetName.cs | 24 +++ Pilz.Dalamud/Icons/JobIconSets.cs | 128 +++++++++++++ .../AddonNamePlate_SetPlayerNameEventArgs.cs | 19 ++ ...NamePlate_SetPlayerNameManagedEventArgs.cs | 37 ++++ .../Nameplates/EventArgs/HookBaseEventArgs.cs | 13 ++ .../EventArgs/HookManagedBaseEventArgs.cs | 13 ++ .../EventArgs/HookWithResultBaseEventArgs.cs | 13 ++ .../HookWithResultManagedBaseEventArgs.cs | 13 ++ .../Nameplates/Model/SafeAddonNameplate.cs | 43 +++++ .../Nameplates/Model/SafeNameplateInfo.cs | 57 ++++++ .../Nameplates/Model/SafeNameplateObject.cs | 128 +++++++++++++ Pilz.Dalamud/Nameplates/Model/StatusIcons.cs | 25 +++ Pilz.Dalamud/Nameplates/NameplateHooks.cs | 179 ++++++++++++++++++ Pilz.Dalamud/Nameplates/NameplateManager.cs | 37 ++++ .../Nameplates/Tools/NameplateChanges.cs | 51 +++++ .../Nameplates/Tools/NameplateChangesProps.cs | 16 ++ .../Nameplates/Tools/NameplateElements.cs | 15 ++ .../Tools/NameplateUpdateFactory.cs | 22 +++ .../Nameplates/Tools/StatusIconPriorizer.cs | 71 +++++++ .../Tools/StatusIconPriorizerConditionSets.cs | 15 ++ .../Tools/StatusIconPriorizerSettings.cs | 92 +++++++++ Pilz.Dalamud/Pilz.Dalamud.csproj | 54 ++++++ Pilz.Dalamud/PluginServices.cs | 30 +++ Pilz.Dalamud/Tools/Strings/StringChange.cs | 24 +++ Pilz.Dalamud/Tools/Strings/StringChanges.cs | 40 ++++ .../Tools/Strings/StringChangesProps.cs | 31 +++ Pilz.Dalamud/Tools/Strings/StringPosition.cs | 15 ++ .../Tools/Strings/StringUpdateFactory.cs | 132 +++++++++++++ Pilz.Dalamud/XivApi.cs | 39 ++++ Pilz.Dalamud/packages.lock.json | 6 + 38 files changed, 1776 insertions(+) create mode 100644 Pilz.Dalamud.sln create mode 100644 Pilz.Dalamud/ActivityContexts/ActivityContext.cs create mode 100644 Pilz.Dalamud/ActivityContexts/ActivityContextManager.cs create mode 100644 Pilz.Dalamud/ActivityContexts/ActivityType.cs create mode 100644 Pilz.Dalamud/ActivityContexts/ZoneType.cs create mode 100644 Pilz.Dalamud/Extensions.cs create mode 100644 Pilz.Dalamud/GameInterfaceHelper.cs create mode 100644 Pilz.Dalamud/Icons/JobIconSet.cs create mode 100644 Pilz.Dalamud/Icons/JobIconSetName.cs create mode 100644 Pilz.Dalamud/Icons/JobIconSets.cs create mode 100644 Pilz.Dalamud/Nameplates/EventArgs/AddonNamePlate_SetPlayerNameEventArgs.cs create mode 100644 Pilz.Dalamud/Nameplates/EventArgs/AddonNamePlate_SetPlayerNameManagedEventArgs.cs create mode 100644 Pilz.Dalamud/Nameplates/EventArgs/HookBaseEventArgs.cs create mode 100644 Pilz.Dalamud/Nameplates/EventArgs/HookManagedBaseEventArgs.cs create mode 100644 Pilz.Dalamud/Nameplates/EventArgs/HookWithResultBaseEventArgs.cs create mode 100644 Pilz.Dalamud/Nameplates/EventArgs/HookWithResultManagedBaseEventArgs.cs create mode 100644 Pilz.Dalamud/Nameplates/Model/SafeAddonNameplate.cs create mode 100644 Pilz.Dalamud/Nameplates/Model/SafeNameplateInfo.cs create mode 100644 Pilz.Dalamud/Nameplates/Model/SafeNameplateObject.cs create mode 100644 Pilz.Dalamud/Nameplates/Model/StatusIcons.cs create mode 100644 Pilz.Dalamud/Nameplates/NameplateHooks.cs create mode 100644 Pilz.Dalamud/Nameplates/NameplateManager.cs create mode 100644 Pilz.Dalamud/Nameplates/Tools/NameplateChanges.cs create mode 100644 Pilz.Dalamud/Nameplates/Tools/NameplateChangesProps.cs create mode 100644 Pilz.Dalamud/Nameplates/Tools/NameplateElements.cs create mode 100644 Pilz.Dalamud/Nameplates/Tools/NameplateUpdateFactory.cs create mode 100644 Pilz.Dalamud/Nameplates/Tools/StatusIconPriorizer.cs create mode 100644 Pilz.Dalamud/Nameplates/Tools/StatusIconPriorizerConditionSets.cs create mode 100644 Pilz.Dalamud/Nameplates/Tools/StatusIconPriorizerSettings.cs create mode 100644 Pilz.Dalamud/Pilz.Dalamud.csproj create mode 100644 Pilz.Dalamud/PluginServices.cs create mode 100644 Pilz.Dalamud/Tools/Strings/StringChange.cs create mode 100644 Pilz.Dalamud/Tools/Strings/StringChanges.cs create mode 100644 Pilz.Dalamud/Tools/Strings/StringChangesProps.cs create mode 100644 Pilz.Dalamud/Tools/Strings/StringPosition.cs create mode 100644 Pilz.Dalamud/Tools/Strings/StringUpdateFactory.cs create mode 100644 Pilz.Dalamud/XivApi.cs create mode 100644 Pilz.Dalamud/packages.lock.json diff --git a/Pilz.Dalamud.sln b/Pilz.Dalamud.sln new file mode 100644 index 0000000..6334bda --- /dev/null +++ b/Pilz.Dalamud.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.3.32929.385 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pilz.Dalamud", "Pilz.Dalamud\Pilz.Dalamud.csproj", "{35197085-3110-46FD-8336-42937673FBAB}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {35197085-3110-46FD-8336-42937673FBAB}.Debug|x64.ActiveCfg = Debug|x64 + {35197085-3110-46FD-8336-42937673FBAB}.Debug|x64.Build.0 = Debug|x64 + {35197085-3110-46FD-8336-42937673FBAB}.Release|x64.ActiveCfg = Release|x64 + {35197085-3110-46FD-8336-42937673FBAB}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {8292530B-CC9E-447D-BCE1-251CDF7646C6} + EndGlobalSection +EndGlobal 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..687433f --- /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, + PveDuty, + PvpDuty + } +} 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..66da9c1 --- /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 + }); + } + + 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..fdad563 --- /dev/null +++ b/Pilz.Dalamud/Nameplates/EventArgs/AddonNamePlate_SetPlayerNameManagedEventArgs.cs @@ -0,0 +1,37 @@ +using Dalamud.Game.Text.SeStringHandling; +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 + { + private new AddonNamePlate_SetPlayerNameEventArgs OriginalEventArgs + => base.OriginalEventArgs as AddonNamePlate_SetPlayerNameEventArgs; + + 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..1418566 --- /dev/null +++ b/Pilz.Dalamud/Nameplates/EventArgs/HookBaseEventArgs.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 HookBaseEventArgs + { + public bool CallOriginal { get; set; } = true; + } +} 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..4805bb8 --- /dev/null +++ b/Pilz.Dalamud/Nameplates/EventArgs/HookWithResultBaseEventArgs.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 HookWithResultBaseEventArgs : HookBaseEventArgs + { + public TResult Result { get; set; } + } +} 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..7c1abd5 --- /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); + } + + public IntPtr NameAddress => GetStringPtr(nameof(RaptureAtkModule.NamePlateInfo.Name)); + public IntPtr FcNameAddress => GetStringPtr(nameof(RaptureAtkModule.NamePlateInfo.FcName)); + public IntPtr TitleAddress => GetStringPtr(nameof(RaptureAtkModule.NamePlateInfo.Title)); + public IntPtr DisplayTitleAddress => GetStringPtr(nameof(RaptureAtkModule.NamePlateInfo.DisplayTitle)); + public 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..04aa2c6 --- /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 readonly IntPtr Pointer; + public readonly AddonNamePlate.NamePlateObject Data; + + 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..3c5910c --- /dev/null +++ b/Pilz.Dalamud/Nameplates/Model/StatusIcons.cs @@ -0,0 +1,25 @@ +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 + } +} diff --git a/Pilz.Dalamud/Nameplates/NameplateHooks.cs b/Pilz.Dalamud/Nameplates/NameplateHooks.cs new file mode 100644 index 0000000..be8ff2c --- /dev/null +++ b/Pilz.Dalamud/Nameplates/NameplateHooks.cs @@ -0,0 +1,179 @@ +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; + +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 IntPtr 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 IntPtr 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() + { + Initialize(); + } + + ~NameplateHooks() + { + Dispose(); + } + + public void Dispose() + { + Unhook(); + } + + /// + /// Initialize and enable all Hooks. + /// + internal void Initialize() + { + SignatureHelper.Initialise(this); + 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 freeTitle = false; + var freeName = false; + var freeFreeCompany = false; + + var eventArgs = new AddonNamePlate_SetPlayerNameEventArgs + { + PlayerNameplateObjectPtr = playerNameplateObjectPtr, + TitlePtr = titlePtr, + NamePtr = namePtr, + FreeCompanyPtr = freeCompanyPtr, + IsTitleAboveName = isTitleAboveName, + IsTitleVisible = isTitleVisible, + IconID = iconId + }; + + // Invoke Event + AddonNamePlate_SetPlayerName?.Invoke(eventArgs); + + if (AddonNamePlate_SetPlayerNameManaged != null) + { + var managedEventArgs = new AddonNamePlate_SetPlayerNameManagedEventArgs + { + OriginalEventArgs = eventArgs, + 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 and set result + if (eventArgs.CallOriginal) + result = hook_AddonNamePlate_SetPlayerNameplateDetour.Original( + eventArgs.PlayerNameplateObjectPtr, + eventArgs.IsTitleAboveName, + eventArgs.IsTitleVisible, + eventArgs.TitlePtr, + eventArgs.NamePtr, + eventArgs.FreeCompanyPtr, + eventArgs.IconID); + else + result = eventArgs.Result; + + // Free memory + if (freeTitle) + GameInterfaceHelper.PluginFree(eventArgs.TitlePtr); + if (freeName) + GameInterfaceHelper.PluginFree(eventArgs.NamePtr); + if (freeFreeCompany) + GameInterfaceHelper.PluginFree(eventArgs.FreeCompanyPtr); + } + + return result; + } + } +} diff --git a/Pilz.Dalamud/Nameplates/NameplateManager.cs b/Pilz.Dalamud/Nameplates/NameplateManager.cs new file mode 100644 index 0000000..8861636 --- /dev/null +++ b/Pilz.Dalamud/Nameplates/NameplateManager.cs @@ -0,0 +1,37 @@ +using Dalamud.Hooking; +using Pilz.Dalamud.Nameplates.EventArgs; +using Dalamud.Utility.Signatures; + +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(); + } + } +} diff --git a/Pilz.Dalamud/Nameplates/Tools/NameplateChanges.cs b/Pilz.Dalamud/Nameplates/Tools/NameplateChanges.cs new file mode 100644 index 0000000..da182b3 --- /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 StringChangesProps()); + changes.Add(NameplateElements.Name, new StringChangesProps()); + changes.Add(NameplateElements.FreeCompany, new StringChangesProps()); + } + + /// + /// 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..c563e4d --- /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 + { + Title, + Name, + FreeCompany + } +} diff --git a/Pilz.Dalamud/Nameplates/Tools/NameplateUpdateFactory.cs b/Pilz.Dalamud/Nameplates/Tools/NameplateUpdateFactory.cs new file mode 100644 index 0000000..b9133e3 --- /dev/null +++ b/Pilz.Dalamud/Nameplates/Tools/NameplateUpdateFactory.cs @@ -0,0 +1,22 @@ +using Dalamud.Game.Text.SeStringHandling; +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); + } + } + } +} diff --git a/Pilz.Dalamud/Nameplates/Tools/StatusIconPriorizer.cs b/Pilz.Dalamud/Nameplates/Tools/StatusIconPriorizer.cs new file mode 100644 index 0000000..61e2166 --- /dev/null +++ b/Pilz.Dalamud/Nameplates/Tools/StatusIconPriorizer.cs @@ -0,0 +1,71 @@ +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. + private bool IsPriorityIcon(int iconId, out int priorityIconId, ActivityContext activityContext) + { + bool isPrioIcon; + priorityIconId = iconId; + + 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); + + // Save the id of the icon + priorityIconId = iconId; + } + + 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..1222e6c --- /dev/null +++ b/Pilz.Dalamud/Nameplates/Tools/StatusIconPriorizerSettings.cs @@ -0,0 +1,92 @@ +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 + }); + + var setInDuty = GetConditionSet(StatusIconPriorizerConditionSets.InDuty); + setInDuty.AddRange(new[] + { + StatusIcons.Disconnecting, // Disconnecting + StatusIcons.ViewingCutscene, // Viewing Cutscene + StatusIcons.Idle, // Idle + StatusIcons.GroupPose, // Group Pose + }); + + 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 + }); + } + } +} diff --git a/Pilz.Dalamud/Pilz.Dalamud.csproj b/Pilz.Dalamud/Pilz.Dalamud.csproj new file mode 100644 index 0000000..a1864a6 --- /dev/null +++ b/Pilz.Dalamud/Pilz.Dalamud.csproj @@ -0,0 +1,54 @@ + + + + net6.0-windows + enable + annotations + true + latest + false + false + x64 + + + + true + true + + + + $(appdata)\XIVLauncher\addon\Hooks\dev\ + + + + + $(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..3c90f40 --- /dev/null +++ b/Pilz.Dalamud/PluginServices.cs @@ -0,0 +1,30 @@ +using Dalamud.Data; +using Dalamud.Game.ClientState; +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 +{ + internal class PluginServices + { + [PluginService] + public static GameGui GameGui { get; private set; } = null!; + [PluginService] + public static DalamudPluginInterface PluginInterface { get; private set; } = null!; + [PluginService] + public static ClientState ClientState { get; private set; } = null!; + [PluginService] + public static DataManager DataManager { get; private set; } = null!; + + public static void Initialize(DalamudPluginInterface dalamudPluginInterface) + { + dalamudPluginInterface.Create(); + } + } +} 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..8a81bfb --- /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; } + /// + /// Payloads to use as anchor where the changes should be applied to. + /// + public List AnchorPayloads { get; set; } + /// + /// 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..f8ecd16 --- /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.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