diff --git a/TickTracker/Helpers/NativeUi.cs b/TickTracker/Helpers/NativeUi.cs index 974a2dc..beb5ebc 100644 --- a/TickTracker/Helpers/NativeUi.cs +++ b/TickTracker/Helpers/NativeUi.cs @@ -4,6 +4,10 @@ using Dalamud.Interface; using FFXIVClientStructs.FFXIV.Component.GUI; using FFXIVClientStructs.FFXIV.Client.System.Memory; +using TickTracker.Windows; +using Dalamud.Plugin.Services; +using Lumina.Data.Files; +using System.Linq; namespace TickTracker.Helpers; @@ -12,7 +16,18 @@ public static unsafe class NativeUi { private static readonly Dictionary NodeIds = new(StringComparer.Ordinal); private static readonly Dictionary NodeNames = new(); + private static Dictionary TextureDictionary = new(); private static uint NodeIdBase = 0x5469636B; + private static int CurrentNodePartsListAmount = 0; + + private static IDataManager? DataManager; + private static IPluginLog Log = null!; + + public static void InitServices(IDataManager _dataManager, IPluginLog _log) + { + DataManager = _dataManager; + Log = _log; + } public static uint Get(string name, int index = 0) { @@ -82,12 +97,162 @@ public static void FreeImageComponents(ref AtkImageNode* imageNode) { IMemorySpace.Free(imageNode->PartsList->Parts->UldAsset, (ulong)(sizeof(AtkUldAsset) * imageNode->PartsList->PartCount)); IMemorySpace.Free(imageNode->PartsList->Parts, (ulong)(sizeof(AtkUldPart) * imageNode->PartsList->PartCount)); - IMemorySpace.Free(imageNode->PartsList, (ulong)sizeof(AtkUldPartsList)); + IMemorySpace.Free(imageNode->PartsList, (ulong)(sizeof(AtkUldPartsList)* CurrentNodePartsListAmount)); imageNode->AtkResNode.Destroy(false); IMemorySpace.Free(imageNode, (ulong)sizeof(AtkImageNode)); } - private static AtkUldPartsList* CreateAtkUldPartsList(UldWrapper uld, int partListIndex, string texturePath) + private static void PopulateTextureDictionary(UldFile uldFile) + { + if (DataManager is null) + { + return; + } + TextureDictionary.Clear(); + for (var i = 0; i < uldFile.AssetData.Length; i++) + { + var rawTexturePath = uldFile.AssetData[i].Path; + if (rawTexturePath is null) + { + DevWindow.Print($"{rawTexturePath} was skipped"); + continue; + } + + var textureId = uldFile.AssetData[i].Id; + var texturePath = new string(rawTexturePath, 0, rawTexturePath.Length).Trim('\0').Trim(); + var hqTexturePath = texturePath.Replace(".tex", "_hr1.tex"); + + if (DataManager.FileExists(hqTexturePath)) + { + TextureDictionary.Add(textureId, hqTexturePath); + } + else if (DataManager.FileExists(texturePath)) + { + TextureDictionary.Add(textureId, texturePath); + } + } + } + + private static void ParseUldFile(UldFile uld, out int uldPartsListAmount, out uint uldPartAmount) + { + uldPartsListAmount = uld.Parts.Length; + uldPartAmount = 0; + for (var i = 0; i < uldPartsListAmount; i++) + { + var currentPartList = uld.Parts[i]; + uldPartAmount += currentPartList.PartCount; + } + } + + private static AtkUldPartsList* CreateCompleteAtkUldPartsList(UldWrapper uld) + { + if (!uld.Valid || uld.Uld is null) + { + return null; + } + var uldFile = uld.Uld; + ParseUldFile(uldFile, out var uldPartsListAmount, out var uldPartAmount); + CurrentNodePartsListAmount = uldPartsListAmount; + PopulateTextureDictionary(uldFile); + if (TextureDictionary.Count is 0) + { + return null; + } + + var atkPartsList = (AtkUldPartsList*)IMemorySpace.GetUISpace()->Malloc((ulong)(sizeof(AtkUldPartsList) * uldPartsListAmount), 8); + if (atkPartsList is null) + { + return null; + } + for (var i = 0; i < uldPartsListAmount; i++) + { + atkPartsList[i].Id = uldFile.Parts[i].Id; + atkPartsList[i].PartCount = uldFile.Parts[i].PartCount; + } + + var atkUldPart = (AtkUldPart*)IMemorySpace.GetUISpace()->Malloc((ulong)(sizeof(AtkUldPart) * uldPartAmount), 8); + if (atkUldPart is null) + { + IMemorySpace.Free(atkPartsList, (ulong)(sizeof(AtkUldPartsList) * uldPartsListAmount)); + return null; + } + for (var i = 0; i < uldPartsListAmount; i++) + { + for (var j = 0; j < atkPartsList[i].PartCount; j++) + { + // j + i uld part? + var currentUldPart = uldFile.Parts[i].Parts[j]; + atkUldPart[j].U = currentUldPart.U; + atkUldPart[j].V = currentUldPart.V; + atkUldPart[j].Width = currentUldPart.W; + atkUldPart[j].Height = currentUldPart.H; + } + atkPartsList[i].Parts = atkUldPart; + } + + var atkUldAsset = (AtkUldAsset*)IMemorySpace.GetUISpace()->Malloc((ulong)(sizeof(AtkUldAsset) * uldPartAmount), 8); + if (atkUldAsset is null) + { + IMemorySpace.Free(atkUldPart, (ulong)(sizeof(AtkUldPart) * uldPartAmount)); + IMemorySpace.Free(atkPartsList, (ulong)(sizeof(AtkUldPartsList) * uldPartsListAmount)); + return null; + } + for (var i = 0; i < uldPartsListAmount; i++) + { + var currentPartsList = &atkPartsList[i]; + var currentUldPartsList = uldFile.Parts[i]; + for (var j = 0; j < currentPartsList->PartCount; j++) + { + var currentPart = ¤tPartsList->Parts[j]; + var currentUldPart = currentUldPartsList.Parts[j]; + atkUldAsset[j].Id = currentUldPart.TextureId; + atkUldAsset[j].AtkTexture.Ctor(); + if (TextureDictionary.ContainsKey(atkUldAsset[j].Id)) + { + var texturePath = TextureDictionary[atkUldAsset[j].Id]; + Log.Debug("Loading texture {path}", texturePath); + atkUldAsset[j].AtkTexture.LoadTexture(texturePath); + } + currentPart->UldAsset = &atkUldAsset[j]; + } + } + + if (atkPartsList is null) + { + IMemorySpace.Free(atkUldAsset, (ulong)(sizeof(AtkUldAsset) * uldPartAmount)); + IMemorySpace.Free(atkUldPart, (ulong)(sizeof(AtkUldPart) * uldPartAmount)); + IMemorySpace.Free(atkPartsList, (ulong)(sizeof(AtkUldPartsList) * uldPartsListAmount)); + return null; + } + + return atkPartsList; + } + + public static AtkImageNode* CreateCompleteImageNode(UldWrapper uld, uint ImageNodeID, AtkComponentNode* parent, AtkResNode* targetNode, bool visibility) + { + var imageNode = IMemorySpace.GetUISpace()->Create(); + if (imageNode is null) + { + return null; + } + var atkPartList = CreateCompleteAtkUldPartsList(uld); + imageNode->AtkResNode.Type = NodeType.Image; + imageNode->AtkResNode.NodeID = ImageNodeID; + imageNode->PartsList = atkPartList; + imageNode->PartId = 0; + imageNode->AtkResNode.NodeFlags = NodeFlags.AnchorTop | NodeFlags.AnchorLeft | NodeFlags.Enabled | NodeFlags.Visible | NodeFlags.EmitsEvents; + imageNode->AtkResNode.DrawFlags |= 1; + imageNode->WrapMode = 1; + imageNode->Flags = 0; + imageNode->AtkResNode.SetWidth(160); + imageNode->AtkResNode.SetHeight(20); + imageNode->AtkResNode.SetScale(1, 1); + imageNode->AtkResNode.ToggleVisibility(visibility); + LinkNodeAfterTargetNode(&imageNode->AtkResNode, parent, targetNode); + return imageNode; + } + + private static AtkUldPartsList* CreateAtkUldPartsList(UldWrapper uld, int partListIndex) { if (!uld.Valid || uld.Uld is null) { @@ -134,7 +299,7 @@ public static void FreeImageComponents(ref AtkImageNode* imageNode) atkUldAsset[i].AtkTexture.Ctor(); // Technically not a proper call, because the texturePath is blindly passed here // Different parts can belong to different textures - atkUldAsset[i].AtkTexture.LoadTexture(texturePath); + //atkUldAsset[i].AtkTexture.LoadTexture(texturePath); atkPartsList->Parts[i].UldAsset = &atkUldAsset[i]; } @@ -148,7 +313,7 @@ public static void FreeImageComponents(ref AtkImageNode* imageNode) { return null; } - var atkPartList = CreateAtkUldPartsList(uld, partListIndex, texturePath); + var atkPartList = CreateAtkUldPartsList(uld, partListIndex); if (atkPartList is null) { IMemorySpace.Free(imageNode, (ulong)sizeof(AtkImageNode)); diff --git a/TickTracker/Helpers/Utilities.cs b/TickTracker/Helpers/Utilities.cs index 87f4462..ee18513 100644 --- a/TickTracker/Helpers/Utilities.cs +++ b/TickTracker/Helpers/Utilities.cs @@ -18,6 +18,7 @@ using Dalamud.Interface.Windowing; using TickTracker.Enums; using TickTracker.Windows; +using Lumina.Data.Files; namespace TickTracker.Helpers; diff --git a/TickTracker/Plugin.cs b/TickTracker/Plugin.cs index ca587be..25ebb32 100644 --- a/TickTracker/Plugin.cs +++ b/TickTracker/Plugin.cs @@ -23,7 +23,6 @@ using Dalamud.Game.ClientState.Objects.SubKinds; using TickTracker.Windows; using TickTracker.Helpers; -using static Lumina.Data.Parsing.Uld.NodeData; namespace TickTracker; @@ -88,6 +87,8 @@ public sealed class Plugin : IDalamudPlugin private const float RegularTickInterval = 3, FastTickInterval = 1.5f; private const uint DiscipleOfTheLand = 32, PugilistId = 2, LancerId = 4, ArcherId = 5, HPGaugeNodeId = 3, MPGaugeNodeId = 4, ParamFrameImageNode = 4; private const byte NonCombatJob = 0, MeleeDPS = 3, PhysRangedDPS = 4; + private const string ParamWidgetUldPath = "ui/uld/parameter.uld"; + private const string GatchaUldPath = "ui/uld/Gacha.uld"; private readonly List barWindows; private readonly uint mpTickerImageID = NativeUi.Get("TickerImageMP"); @@ -100,7 +101,6 @@ public sealed class Plugin : IDalamudPlugin private Task? loadingTask; private unsafe AtkUnitBase* NameplateAddon => (AtkUnitBase*)gameGui.GetAddonByName("NamePlate"); private unsafe AtkUnitBase* ParamWidget => (AtkUnitBase*)gameGui.GetAddonByName("_ParameterWidget"); - private unsafe AtkUnitBase* ContentFinderAddon => (AtkUnitBase*)gameGui.GetAddonByName("ContentsFinder"); private unsafe AtkImageNode* hpTicker, mpTicker; public Plugin(DalamudPluginInterface _pluginInterface, @@ -135,6 +135,8 @@ public Plugin(DalamudPluginInterface _pluginInterface, } receiveActorUpdateHook.Enable(); + NativeUi.InitServices(_dataManager, log); + config = pluginInterface.GetPluginConfig() as Configuration ?? new Configuration(); utilities = new Utilities(pluginInterface, config, condition, _dataManager, clientState, log); @@ -502,33 +504,14 @@ private unsafe void DevWindowThings(PlayerCharacter? player, double currentTime, DevWindow.Print("Fast Value: " + fastValue.ToString(cultureFormat)); DevWindow.Print("Swapchain resolution: " + Resolution.X.ToString(cultureFormat) + "x" + Resolution.Y.ToString(cultureFormat)); - var randomUld = pluginInterface.UiBuilder.LoadUld("ui/uld/Gacha.uld"); - if (randomUld.Valid) - { - var uld = randomUld.Uld; - var partListCount = uld.Parts.Length; - var path = uld.FilePath.Path; - DevWindow.Print(path); - DevWindow.Print($"Gacha amount of PartLists? {partListCount}"); - for (var i = 0; i < partListCount; i++) - { - var currentPartList = uld.Parts[i]; - DevWindow.Print($"PartList {i} has {currentPartList.PartCount} parts"); - for (var j = 0; j < currentPartList.PartCount; j++) - { - var currentPart = currentPartList.Parts[j]; - DevWindow.Print($"Part texture id {currentPart.TextureId}"); - } - } - } - if (!utilities.IsAddonReady(ParamWidget)) { return; } - var frameImageNode = NativeUi.GetNodeByID(&ParamWidget->GetNodeById(HPGaugeNodeId)->GetComponent()->UldManager, mpTickerImageID); + var frameImageNode = NativeUi.GetNodeByID(&ParamWidget->GetNodeById(MPGaugeNodeId)->GetComponent()->UldManager, mpTickerImageID); if (frameImageNode is null) { + DevWindow.Print("Image Node is... null?"); return; } DevWindow.Print($"NodeId: {frameImageNode->AtkResNode.NodeID}\n" + @@ -619,6 +602,7 @@ private unsafe void HandleNativeNode(uint gaugeBarNodeId, uint frameImageId, uin tickerNode->AtkResNode.MultiplyBlue = (byte)(255 * Color.Z); tickerNode->AtkResNode.SetAlpha((byte)(255 * Color.W)); tickerNode->AtkResNode.ToggleVisibility(visibility); + tickerNode->PartId = (ushort)DevWindow.partId; return; } var gaugeBarNode = ParamWidget->GetNodeById(gaugeBarNodeId); @@ -644,14 +628,19 @@ private unsafe void HandleNativeNode(uint gaugeBarNodeId, uint frameImageId, uin if (tickerNode is null && !failed) { - tickerNode = NativeUi.CreateImageNode(pluginInterface.UiBuilder.LoadUld("ui/uld/parameter.uld"), + tickerNode = NativeUi.CreateCompleteImageNode(pluginInterface.UiBuilder.LoadUld(GatchaUldPath), + tickerImageId, + gaugeBarNode->GetAsAtkComponentNode(), + (AtkResNode*)frameImageNode, + visibility); + /*tickerNode = NativeUi.CreateImageNode(pluginInterface.UiBuilder.LoadUld("ui/uld/parameter.uld"), 0, tickerImageId, "ui/uld/Parameter_Gauge_hr1.tex", 0, gaugeBarNode->GetAsAtkComponentNode(), (AtkResNode*)frameImageNode, - visibility); + visibility);*/ if (tickerNode is null) { failed = true; diff --git a/TickTracker/Windows/DevWindow.cs b/TickTracker/Windows/DevWindow.cs index 15fdda7..64dbcbf 100644 --- a/TickTracker/Windows/DevWindow.cs +++ b/TickTracker/Windows/DevWindow.cs @@ -11,6 +11,7 @@ public sealed class DevWindow : Window { private static readonly List PrintLines = new(); public int partId; + public int partListIndex; public DevWindow() : base("DevWindow") { @@ -28,11 +29,13 @@ public override void Draw() ImGui.TextUnformatted(line); } PrintLines.Clear(); - ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X / 2); + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X / 3); if (ImGui.InputInt("ImageNode PartId", ref partId, 1)) { partId = Math.Clamp(partId, 0, 6); } + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X / 3); + ImGui.InputInt("ImageNode PartsList Index", ref partListIndex, 1); } public static void Print(string text)