From 0807f2b3cebe9661d311b9218ed0697488d8a96d Mon Sep 17 00:00:00 2001 From: Kurochi51 Date: Mon, 29 Jan 2024 07:30:03 +0200 Subject: [PATCH] Created `ImageNode.cs` which can be used to instantiate a new `AtkImageNode*` from any given `UldWrapper`. Cleaned up old or broken code. --- TickTracker/Helpers/NativeUi.cs | 284 +------------------- TickTracker/Helpers/Utilities.cs | 1 - TickTracker/NativeNodes/ImageNode.cs | 385 +++++++++++++++++++++++++++ TickTracker/Plugin.cs | 153 ++++------- TickTracker/Windows/DevWindow.cs | 23 +- 5 files changed, 463 insertions(+), 383 deletions(-) create mode 100644 TickTracker/NativeNodes/ImageNode.cs diff --git a/TickTracker/Helpers/NativeUi.cs b/TickTracker/Helpers/NativeUi.cs index beb5ebc..6007c4e 100644 --- a/TickTracker/Helpers/NativeUi.cs +++ b/TickTracker/Helpers/NativeUi.cs @@ -1,50 +1,33 @@ using System; +using System.Globalization; using System.Collections.Generic; -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; -// Heavily inspired by SimpleTweaks and ReadyCheckHelper +// Mostly from SimpleTweaks 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) { - if (TryGet(name, index, out var id)) return id; + var key = name + "#" + index.ToString(CultureInfo.InvariantCulture); + if (NodeIds.TryGetValue(key, out var id)) + { + return id; + } lock (NodeIds) { - lock (NodeNames) - { - id = NodeIdBase; - NodeIdBase += 16; - NodeIds.Add($"{name}#{index}", id); - NodeNames.Add(id, $"{name}#{index}"); - return id; - } + id = NodeIdBase; + NodeIdBase += 16; + NodeIds.Add(key, id); + return id; } } - public static bool TryGet(string name, int index, out uint id) => NodeIds.TryGetValue($"{name}#{index}", out id); + public static AtkResNode* GetNodeByID(AtkUldManager* uldManager, uint nodeId, NodeType? type = null) => GetNodeByID(uldManager, nodeId, type); public static T* GetNodeByID(AtkUldManager* uldManager, uint nodeId, NodeType? type = null) where T : unmanaged { @@ -92,247 +75,4 @@ public static void UnlinkNode(T* atkNode, AtkComponentBase* componentBase) wh } componentBase->UldManager.UpdateDrawNodeList(); } - - 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)* CurrentNodePartsListAmount)); - imageNode->AtkResNode.Destroy(false); - IMemorySpace.Free(imageNode, (ulong)sizeof(AtkImageNode)); - } - - 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) - { - return null; - } - var uldFile = uld.Uld; - var uldPartList = uldFile.Parts[partListIndex]; - - var atkPartsList = (AtkUldPartsList*)IMemorySpace.GetUISpace()->Malloc((ulong)sizeof(AtkUldPartsList), 8); - if (atkPartsList is null) - { - return null; - } - atkPartsList->Id = uldPartList.Id; - atkPartsList->PartCount = uldPartList.PartCount; - var atkUldPart = (AtkUldPart*)IMemorySpace.GetUISpace()->Malloc((ulong)sizeof(AtkUldPart) * atkPartsList->PartCount, 8); - if (atkUldPart is null) - { - IMemorySpace.Free(atkPartsList, (ulong)sizeof(AtkUldPartsList)); - return null; - } - for (var i = 0; i < uldPartList.PartCount; i++) - { - var part = uldPartList.Parts[i]; - atkUldPart[i].U = part.U; - atkUldPart[i].V = part.V; - atkUldPart[i].Width = part.W; - atkUldPart[i].Height = part.H; - } - atkPartsList->Parts = atkUldPart; - - var atkUldAsset = (AtkUldAsset*)IMemorySpace.GetUISpace()->Malloc((ulong)(sizeof(AtkUldAsset) * atkPartsList->PartCount), 8); - if (atkUldAsset is null) - { - IMemorySpace.Free(atkUldPart, (ulong)sizeof(AtkUldPart) * atkPartsList->PartCount); - IMemorySpace.Free(atkPartsList, (ulong)sizeof(AtkUldPartsList)); - return null; - } - - for (var i = 0; i < uldPartList.PartCount; i++) - { - var partTextureId = uldPartList.Parts[i].TextureId; - atkUldAsset[i].Id = partTextureId; - 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); - atkPartsList->Parts[i].UldAsset = &atkUldAsset[i]; - } - - return atkPartsList; - } - - public static AtkImageNode* CreateImageNode(UldWrapper uld, int partListIndex, uint ImageNodeID, string texturePath, ushort partIndex, AtkComponentNode* parent, AtkResNode* targetNode, bool visibility) - { - var imageNode = IMemorySpace.GetUISpace()->Create(); - if (imageNode is null) - { - return null; - } - var atkPartList = CreateAtkUldPartsList(uld, partListIndex); - if (atkPartList is null) - { - IMemorySpace.Free(imageNode, (ulong)sizeof(AtkImageNode)); - return null; - } - imageNode->AtkResNode.Type = NodeType.Image; - imageNode->AtkResNode.NodeID = ImageNodeID; - imageNode->PartsList = atkPartList; - imageNode->PartId = partIndex; - imageNode->LoadTexture(texturePath); - 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; - } } diff --git a/TickTracker/Helpers/Utilities.cs b/TickTracker/Helpers/Utilities.cs index ee18513..87f4462 100644 --- a/TickTracker/Helpers/Utilities.cs +++ b/TickTracker/Helpers/Utilities.cs @@ -18,7 +18,6 @@ using Dalamud.Interface.Windowing; using TickTracker.Enums; using TickTracker.Windows; -using Lumina.Data.Files; namespace TickTracker.Helpers; diff --git a/TickTracker/NativeNodes/ImageNode.cs b/TickTracker/NativeNodes/ImageNode.cs new file mode 100644 index 0000000..f44c656 --- /dev/null +++ b/TickTracker/NativeNodes/ImageNode.cs @@ -0,0 +1,385 @@ +using System; +using System.Numerics; +using System.Collections.Generic; + +using Dalamud.Interface; +using Dalamud.Plugin.Services; +using static Lumina.Data.Parsing.Uld.UldRoot; +using FFXIVClientStructs.FFXIV.Component.GUI; +using FFXIVClientStructs.FFXIV.Client.System.Memory; +using TickTracker.Helpers; + +namespace TickTracker.NativeNodes; + +public sealed unsafe class ImageNode : IDisposable +{ + public required uint NodeID { get; set; } + public NodeType Type { get; } = NodeType.Image; + public NodeFlags NodeFlags { get; set; } = NodeFlags.AnchorTop | NodeFlags.AnchorLeft | NodeFlags.Enabled | NodeFlags.Visible | NodeFlags.EmitsEvents; + public uint DrawFlags { get; set; } = 1; + public AtkImageNode* imageNode { get; private set; } + public int atkUldPartsListsAvailable { get; init; } + private bool isDisposed; + + private readonly IDataManager dataManager; + private readonly IPluginLog log; + private readonly PartsData[]? uldPartsListArray; + private readonly AtkUldPartsList*[] atkUldPartsListArray; + + private readonly Dictionary textureDictionary = new(); + private Vector2 nodePosition = new(-1, -1); + private AtkResNode* imageNodeParent; + + public ImageNode(IDataManager _dataManager, IPluginLog _log, UldWrapper uld) + { + dataManager = _dataManager; + log = _log; + + ParseUld(uld, out var uldPartsListAmount, out uldPartsListArray); + atkUldPartsListsAvailable = uldPartsListAmount; + atkUldPartsListArray = new AtkUldPartsList*[uldPartsListAmount]; + CreateAtkUldPartsListArray(); + } + + private void ParseUld(UldWrapper uld, out int uldPartsListAmount, out PartsData[]? uldPartsListArray) + { + uldPartsListAmount = 0; + uldPartsListArray = null; + if (!uld.Valid || uld.Uld is null) + { + return; + } + uldPartsListAmount = uld.Uld.Parts.Length; + uldPartsListArray = uld.Uld.Parts; + + textureDictionary.Clear(); + for (var i = 0; i < uld.Uld.AssetData.Length; i++) + { + var rawTexturePath = uld.Uld.AssetData[i].Path; + if (rawTexturePath is null) + { + continue; + } + + var textureId = uld.Uld.AssetData[i].Id; + var texturePath = new string(rawTexturePath, 0, rawTexturePath.Length).Trim('\0').Trim(); + var hqTexturePath = texturePath.Replace(".tex", "_hr1.tex", StringComparison.Ordinal); + + if (dataManager.FileExists(hqTexturePath)) + { + textureDictionary.Add(textureId, hqTexturePath); + } + else if (dataManager.FileExists(texturePath)) + { + textureDictionary.Add(textureId, texturePath); + } + } + } + + private void CreateAtkUldPartsListArray() + { + if (textureDictionary.Count is 0 || uldPartsListArray is null) + { + return; + } + for (var i = 0; i < atkUldPartsListArray.Length; i++) + { + var currentAtkPartsList = (AtkUldPartsList*)IMemorySpace.GetUISpace()->Malloc((ulong)sizeof(AtkUldPartsList), 8); + if (currentAtkPartsList is null) + { + for (var j = i - 1; j >= 0; j--) + { + for (var k = 0; k < atkUldPartsListArray[j]->PartCount; k++) + { + IMemorySpace.Free(atkUldPartsListArray[j]->Parts[k].UldAsset, (ulong)sizeof(AtkUldAsset)); + } + IMemorySpace.Free(atkUldPartsListArray[j]->Parts, (ulong)(sizeof(AtkUldPart) * atkUldPartsListArray[j]->PartCount)); + IMemorySpace.Free(atkUldPartsListArray[j], (ulong)sizeof(AtkUldPartsList)); + } + return; + } + currentAtkPartsList->Id = uldPartsListArray[i].Id; + currentAtkPartsList->PartCount = uldPartsListArray[i].PartCount; + var currentAtkPartList = (AtkUldPart*)IMemorySpace.GetUISpace()->Malloc((ulong)(sizeof(AtkUldPart) * currentAtkPartsList->PartCount), 8); + if (currentAtkPartList is null) + { + IMemorySpace.Free(currentAtkPartsList, (ulong)sizeof(AtkUldPartsList)); + for (var j = i - 1; j >= 0; j--) + { + for (var k = 0; k < atkUldPartsListArray[j]->PartCount; k++) + { + IMemorySpace.Free(atkUldPartsListArray[j]->Parts[k].UldAsset, (ulong)sizeof(AtkUldAsset)); + } + IMemorySpace.Free(atkUldPartsListArray[j]->Parts, (ulong)(sizeof(AtkUldPart) * atkUldPartsListArray[j]->PartCount)); + IMemorySpace.Free(atkUldPartsListArray[j], (ulong)sizeof(AtkUldPartsList)); + } + return; + } + currentAtkPartsList->Parts = currentAtkPartList; + if (!PopulatePartsList(ref currentAtkPartsList, uldPartsListArray[i])) + { + IMemorySpace.Free(currentAtkPartList, (ulong)(sizeof(AtkUldPart) * currentAtkPartsList->PartCount)); + IMemorySpace.Free(currentAtkPartsList, (ulong)sizeof(AtkUldPartsList)); + for (var j = i - 1; j >= 0; j--) + { + for (var k = 0; k < atkUldPartsListArray[j]->PartCount; k++) + { + IMemorySpace.Free(atkUldPartsListArray[j]->Parts[k].UldAsset, (ulong)sizeof(AtkUldAsset)); + } + IMemorySpace.Free(atkUldPartsListArray[j]->Parts, (ulong)(sizeof(AtkUldPart) * atkUldPartsListArray[j]->PartCount)); + IMemorySpace.Free(atkUldPartsListArray[j], (ulong)sizeof(AtkUldPartsList)); + } + return; + } + atkUldPartsListArray[i] = currentAtkPartsList; + } + } + + private bool PopulatePartsList(ref AtkUldPartsList* currentPartsList, PartsData currentUldPartsList) + { + for (var i = 0; i < currentPartsList->PartCount; i++) + { + var currentAsset = (AtkUldAsset*)IMemorySpace.GetUISpace()->Malloc((ulong)sizeof(AtkUldAsset), 8); + if (currentAsset is null) + { + for (var j = i - 1; j >= 0; j--) + { + IMemorySpace.Free(currentPartsList->Parts[j].UldAsset, (ulong)sizeof(AtkUldAsset)); + } + return false; + } + currentPartsList->Parts[i].U = currentUldPartsList.Parts[i].U; + currentPartsList->Parts[i].V = currentUldPartsList.Parts[i].V; + currentPartsList->Parts[i].Width = currentUldPartsList.Parts[i].W; + currentPartsList->Parts[i].Height = currentUldPartsList.Parts[i].H; + currentAsset->Id = currentUldPartsList.Parts[i].TextureId; + currentAsset->AtkTexture.Ctor(); + if (textureDictionary.TryGetValue(currentAsset->Id, out var texturePath)) + { + currentAsset->AtkTexture.LoadTexture(texturePath); + } + currentPartsList->Parts[i].UldAsset = currentAsset; + } + return true; + } + + private AtkUldPartsList* GetImagePartsList(int partsListIndex) + { + var imageNodePartsList = (AtkUldPartsList*)IMemorySpace.GetUISpace()->Malloc((ulong)sizeof(AtkUldPartsList), 8); + if (imageNodePartsList is null) + { + log.Error("Memory for the node AtkUldPartsList could not be allocated."); + return null; + } + imageNodePartsList->PartCount = atkUldPartsListArray[partsListIndex]->PartCount; + imageNodePartsList->Id = atkUldPartsListArray[partsListIndex]->Id; + var imageNodePartList = (AtkUldPart*)IMemorySpace.GetUISpace()->Malloc((ulong)(sizeof(AtkUldPart) * imageNodePartsList->PartCount), 8); + if (imageNodePartList is null) + { + IMemorySpace.Free(imageNodePartsList, (ulong)sizeof(AtkUldPartsList)); + log.Error("Memory for the node AtkUldParts could not be allocated."); + return null; + } + imageNodePartsList->Parts = imageNodePartList; + for (var i = 0; i < imageNodePartsList->PartCount; i++) + { + var currentAsset = (AtkUldAsset*)IMemorySpace.GetUISpace()->Malloc((ulong)sizeof(AtkUldAsset), 8); + if (currentAsset is null) + { + log.Error("Memory for the node AtkUldAsset of AtkUldPart {i} could not be allocated.", i); + for (var j = i - 1; j >= 0; j--) + { + IMemorySpace.Free(imageNodePartsList->Parts[j].UldAsset, (ulong)sizeof(AtkUldAsset)); + } + IMemorySpace.Free(imageNodePartsList->Parts, (ulong)(sizeof(AtkUldPart) * imageNodePartsList->PartCount)); + IMemorySpace.Free(imageNodePartsList, (ulong)sizeof(AtkUldPartsList)); + return null; + } + + imageNodePartsList->Parts[i].U = atkUldPartsListArray[partsListIndex]->Parts[i].U; + imageNodePartsList->Parts[i].V = atkUldPartsListArray[partsListIndex]->Parts[i].V; + imageNodePartsList->Parts[i].Width = atkUldPartsListArray[partsListIndex]->Parts[i].Width; + imageNodePartsList->Parts[i].Height = atkUldPartsListArray[partsListIndex]->Parts[i].Height; + currentAsset->Id = atkUldPartsListArray[partsListIndex]->Parts[i].UldAsset->Id; + currentAsset->AtkTexture.Ctor(); + if (textureDictionary.TryGetValue(currentAsset->Id, out var texturePath)) + { + currentAsset->AtkTexture.LoadTexture(texturePath); + } + imageNodePartsList->Parts[i].UldAsset = currentAsset; + } + + return imageNodePartsList; + } + + /// + /// Creates a new , that can be accessed through , with a created from the provided . + /// + /// + /// The existing is destroyed if present. + /// + /// The index of the to fetch from the + /// Optional that can be used to attach the created + /// The after the desired position to place our + public void CreateCompleteImageNode(int partsListIndex, AtkResNode* parent = null, AtkResNode* targetNode = null) + { + DestroyNode(); + imageNode = IMemorySpace.GetUISpace()->Create(); + if (imageNode is null || partsListIndex > atkUldPartsListArray.Length - 1) + { + log.Error("Memory for the node could not be allocated or index is out of bounds."); + return; + } + var atkPartsList = GetImagePartsList(partsListIndex); + if (atkPartsList is null) + { + IMemorySpace.Free(imageNode, (ulong)sizeof(AtkImageNode)); + return; + } + if (parent is not null && targetNode is null) + { + log.Error("targetNode must be provided!"); + } + + if (parent is null && targetNode is not null) + { + log.Error("parent must be provided!"); + } + + imageNode->AtkResNode.Type = Type; + imageNode->AtkResNode.NodeID = NodeID; + imageNode->PartsList = atkPartsList; + + imageNode->PartId = 0; + imageNode->AtkResNode.NodeFlags = NodeFlags; + imageNode->AtkResNode.DrawFlags = DrawFlags; + imageNode->WrapMode = 1; + imageNode->Flags = 0; + imageNode->AtkResNode.SetScale(1, 1); + imageNode->AtkResNode.ToggleVisibility(enable: true); + + if (parent is not null && targetNode is not null) + { + imageNodeParent = parent; + NativeUi.LinkNodeAfterTargetNode(&imageNode->AtkResNode, parent->GetAsAtkComponentNode(), targetNode); + } + nodePosition.X = imageNode->AtkResNode.GetX(); + nodePosition.Y = imageNode->AtkResNode.GetY(); + } + + public void SetNodePosition(float X, float Y) + { + imageNode->AtkResNode.SetX(X); + imageNode->AtkResNode.SetY(Y); + } + + public void ResetNodePosition() + { + if (nodePosition == new Vector2(-1, -1)) + { + log.Error("Image node not initialised"); + return; + } + imageNode->AtkResNode.SetX(nodePosition.X); + imageNode->AtkResNode.SetY(nodePosition.Y); + } + + /// + /// Changes the texture color using the provided vector. + /// + /// + /// must contain values only from 0 to 1. + /// + public void ChangeNodeColorAndAlpha(Vector4 Color) + { + if (Color.X > 1 || Color.Y > 1 || Color.Z > 1 || Color.W > 1) + { + return; + } + imageNode->AtkResNode.MultiplyRed = (byte)(255 * Color.X); + imageNode->AtkResNode.MultiplyGreen = (byte)(255 * Color.Y); + imageNode->AtkResNode.MultiplyBlue = (byte)(255 * Color.Z); + imageNode->AtkResNode.SetAlpha((byte)(255 * Color.W)); + } + + public void ChangePartsList(int partsListIndex, ushort partId = 0) + { + if (partsListIndex > atkUldPartsListArray.Length - 1) + { + log.Error("partsListIndex out of bounds"); + return; + } + var desiredPartsList = GetImagePartsList(partsListIndex); + if (desiredPartsList is null) + { + log.Error("The desired partsList could not be created"); + return; + } + DestroyImagePartsList(); + imageNode->PartsList = desiredPartsList; + imageNode->PartId = partId; + } + + private void DestroyImagePartsList() + { + for (var i = 0; i < imageNode->PartsList->PartCount; i++) + { + IMemorySpace.Free(imageNode->PartsList->Parts[i].UldAsset, (ulong)sizeof(AtkUldAsset)); + } + IMemorySpace.Free(imageNode->PartsList->Parts, (ulong)(sizeof(AtkUldPart) * imageNode->PartsList->PartCount)); + IMemorySpace.Free(imageNode->PartsList, (ulong)sizeof(AtkUldPartsList)); + } + + /// + /// Destroys the current . + /// + public void DestroyNode() + { + if (imageNode is null) + { + return; + } + if (imageNodeParent is not null) + { + NativeUi.UnlinkNode(imageNode, imageNodeParent->GetComponent()); + } + for (var i = 0; i < imageNode->PartsList->PartCount; i++) + { + IMemorySpace.Free(imageNode->PartsList->Parts[i].UldAsset, (ulong)sizeof(AtkUldAsset)); + } + IMemorySpace.Free(imageNode->PartsList->Parts, (ulong)(sizeof(AtkUldPart) * imageNode->PartsList->PartCount)); + IMemorySpace.Free(imageNode->PartsList, (ulong)sizeof(AtkUldPartsList)); + imageNode->AtkResNode.Destroy(free: false); + IMemorySpace.Free(imageNode, (ulong)sizeof(AtkImageNode)); + } + + private void FreeResources() + { + for (var i = 0; i < atkUldPartsListArray.Length; i++) + { + for (var j = 0; j < atkUldPartsListArray[i]->PartCount; j++) + { + IMemorySpace.Free(atkUldPartsListArray[i]->Parts[j].UldAsset, (ulong)sizeof(AtkUldAsset)); + } + IMemorySpace.Free(atkUldPartsListArray[i]->Parts, (ulong)(sizeof(AtkUldPart) * atkUldPartsListArray[i]->PartCount)); + IMemorySpace.Free(atkUldPartsListArray[i], (ulong)sizeof(AtkUldPartsList)); + } + } + + /// + /// Unlinks and destroys the existing , and frees the array of created on class construction from the . + /// + public void Dispose() + { + if (isDisposed) + { + return; + } + if (imageNode is not null) + { + DestroyNode(); + } + FreeResources(); + isDisposed = true; + } +} diff --git a/TickTracker/Plugin.cs b/TickTracker/Plugin.cs index 25ebb32..c25d95b 100644 --- a/TickTracker/Plugin.cs +++ b/TickTracker/Plugin.cs @@ -23,6 +23,7 @@ using Dalamud.Game.ClientState.Objects.SubKinds; using TickTracker.Windows; using TickTracker.Helpers; +using TickTracker.NativeNodes; namespace TickTracker; @@ -88,11 +89,12 @@ public sealed class Plugin : IDalamudPlugin 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"); private readonly uint hpTickerImageID = NativeUi.Get("TickerImageHP"); + private readonly uint mpTickerImageID = NativeUi.Get("TickerImageMP"); + private readonly ImageNode hpTickerNode; + private readonly ImageNode mpTickerNode; private double syncValue, regenValue, fastValue; private bool finishedLoading, nativeHpBarCreationFailed, nativeMpBarCreationFailed; @@ -101,7 +103,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 AtkImageNode* hpTicker, mpTicker; public Plugin(DalamudPluginInterface _pluginInterface, IClientState _clientState, @@ -135,7 +136,16 @@ public Plugin(DalamudPluginInterface _pluginInterface, } receiveActorUpdateHook.Enable(); - NativeUi.InitServices(_dataManager, log); + var tickerUld = pluginInterface.UiBuilder.LoadUld(ParamWidgetUldPath); + hpTickerNode = new ImageNode(_dataManager, log, tickerUld) + { + NodeID = hpTickerImageID, + }; + mpTickerNode = new ImageNode(_dataManager, log, tickerUld) + { + NodeID = mpTickerImageID, + }; + tickerUld.Dispose(); config = pluginInterface.GetPluginConfig() as Configuration ?? new Configuration(); utilities = new Utilities(pluginInterface, config, condition, _dataManager, clientState, log); @@ -503,11 +513,14 @@ private unsafe void DevWindowThings(PlayerCharacter? player, double currentTime, DevWindow.Print("Regen Value: " + regenValue.ToString(cultureFormat)); DevWindow.Print("Fast Value: " + fastValue.ToString(cultureFormat)); DevWindow.Print("Swapchain resolution: " + Resolution.X.ToString(cultureFormat) + "x" + Resolution.Y.ToString(cultureFormat)); - + DevWindow.partListIndex = Math.Clamp(DevWindow.partListIndex, 0, mpTickerNode.atkUldPartsListsAvailable - 1); + DevWindow.partId = Math.Clamp(DevWindow.partId, 0, (int)mpTickerNode.imageNode->PartsList->PartCount - 1); if (!utilities.IsAddonReady(ParamWidget)) { return; } + mpTickerNode.ChangePartsList(DevWindow.partListIndex); + mpTickerNode.imageNode->PartId = (ushort)DevWindow.partId; var frameImageNode = NativeUi.GetNodeByID(&ParamWidget->GetNodeById(MPGaugeNodeId)->GetComponent()->UldManager, mpTickerImageID); if (frameImageNode is null) { @@ -515,41 +528,14 @@ private unsafe void DevWindowThings(PlayerCharacter? player, double currentTime, return; } DevWindow.Print($"NodeId: {frameImageNode->AtkResNode.NodeID}\n" + - $"Uses partId of {frameImageNode->PartId}\n" + - $"Has {frameImageNode->PartsList->PartCount} Parts in PartsList?\n" + - $"With partsList id {frameImageNode->PartsList->Id}\n" + - $"Parts UldAsset id of {frameImageNode->PartsList->Parts->UldAsset->Id}\n"); - for ( var i = 0; i < frameImageNode->PartsList->PartCount; i++) + $"Has {frameImageNode->PartsList->PartCount} Parts\n" + + $"Current partsList: {frameImageNode->PartsList->Id}\n" + + $"Current part: {frameImageNode->PartId}\n"); + for (var i = 0; i < frameImageNode->PartsList->PartCount; i++) { - var part = &frameImageNode->PartsList->Parts[i]; - DevWindow.Print($"AtkUldPart {i} info: Part UldAsset AtkTexture - LoadState {part->UldAsset->AtkTexture.GetLoadState()}" + - $" ; Texture ready: {part->UldAsset->AtkTexture.IsTextureReady()}" + - $" ; Texture type: {part->UldAsset->AtkTexture.TextureType}" + - $" ; Texture.Resource version: {part->UldAsset->AtkTexture.Resource->Version}" + - $" ; Texture.Resource pathHash: {part->UldAsset->AtkTexture.Resource->TexPathHash}"); + DevWindow.Print($"Part {i} Texture id: {frameImageNode->PartsList->Parts[i].UldAsset->Id}" + + $" ; Texture.Resource version: {frameImageNode->PartsList->Parts[i].UldAsset->AtkTexture.Resource->Version}"); } - - /*var contentFinderImageNode = ContentFinderAddon->GetImageNodeById(2); - if (contentFinderImageNode is null) - { - return; - } - DevWindow.Print( - // Seems to be the specific part in the given PartsList - $"Uses partId of {contentFinderImageNode->PartId}\n" + - // Amount of parts in the PartsList, said parts don't have to be from the same texture - $"Has {contentFinderImageNode->PartsList->PartCount} Parts in PartsList\n" + - // PartsList that has above counter, and below Parts - $"With partsList id {contentFinderImageNode->PartsList->Id}\n" + - // .tex Texture id? inside the parent .uld - // This seems to corespond with the Texture Id, therefor all parts loaded from the same texture have - $"Parts UldAsset id of {contentFinderImageNode->PartsList->Parts->UldAsset->Id}\n" + - $"Texture something {contentFinderImageNode->PartsList->Parts->UldAsset->AtkTexture.Resource->IconID}"); - for (var i = 0; i < contentFinderImageNode->PartsList->PartCount; i++) - { - var part = &contentFinderImageNode->PartsList->Parts[i]; - DevWindow.Print($"AtkUldPart {i} info: Part Height {part->Height} Part Width {part->Width} Part U {part->U} part V {part->V} part UldAsset/Texture id {part->UldAsset->Id}"); - }*/ } #endif @@ -557,124 +543,88 @@ private unsafe void DrawNativeNodes() { if (!config.HPNativeUiVisible) { - NativeUiDispose(ref hpTicker, ParamWidget, HPGaugeNodeId); + hpTickerNode.DestroyNode(); } if (!config.MPNativeUiVisible) { - NativeUiDispose(ref mpTicker, ParamWidget, MPGaugeNodeId); + mpTickerNode.DestroyNode(); } if (!nativeHpBarCreationFailed && config.HPNativeUiVisible) { - HandleNativeNode(HPGaugeNodeId, + HandleNativeNode(hpTickerNode, + HPGaugeNodeId, ParamFrameImageNode, - hpTickerImageID, config.HPNativeUiVisible, HPBarWindow.Progress, config.HPNativeUiColor, - ref nativeHpBarCreationFailed, - ref hpTicker); + ref nativeHpBarCreationFailed); } if (!nativeMpBarCreationFailed && config.MPNativeUiVisible) { - HandleNativeNode(MPGaugeNodeId, + HandleNativeNode(mpTickerNode, + MPGaugeNodeId, ParamFrameImageNode, - mpTickerImageID, config.MPNativeUiVisible, MPBarWindow.Progress, config.MPNativeUiColor, - ref nativeMpBarCreationFailed, - ref mpTicker); + ref nativeMpBarCreationFailed); } } - private unsafe void HandleNativeNode(uint gaugeBarNodeId, uint frameImageId, uint tickerImageId, bool visibility, double progress, Vector4 Color, ref bool failed, ref AtkImageNode* tickerNode) + private unsafe void HandleNativeNode(ImageNode tickerNode, uint gaugeBarNodeId, uint frameImageId, bool visibility, double progress, Vector4 Color, ref bool failed) { if (!utilities.IsAddonReady(ParamWidget) || ParamWidget->UldManager.LoadedState != AtkLoadState.Loaded || !ParamWidget->IsVisible) { return; } - if (tickerNode is not null) + if (tickerNode.imageNode is not null) { - tickerNode->WrapMode = 1; - tickerNode->AtkResNode.SetWidth(progress > 0 ? (ushort)((progress * 152) + 4) : (ushort)0); - tickerNode->AtkResNode.MultiplyRed = (byte)(255 * Color.X); - tickerNode->AtkResNode.MultiplyGreen = (byte)(255 * Color.Y); - tickerNode->AtkResNode.MultiplyBlue = (byte)(255 * Color.Z); - tickerNode->AtkResNode.SetAlpha((byte)(255 * Color.W)); - tickerNode->AtkResNode.ToggleVisibility(visibility); - tickerNode->PartId = (ushort)DevWindow.partId; + tickerNode.imageNode->AtkResNode.SetWidth(progress > 0 ? (ushort)((progress * 152) + 4) : (ushort)0); + tickerNode.ChangeNodeColorAndAlpha(Color); + tickerNode.imageNode->AtkResNode.ToggleVisibility(visibility); return; } var gaugeBarNode = ParamWidget->GetNodeById(gaugeBarNodeId); if (gaugeBarNode is null) { log.Error("Couldn't locate the gauge bar node {nodeId}.", gaugeBarNodeId); + failed = true; return; } var gaugeBar = gaugeBarNode->GetComponent(); if (gaugeBar is null) { log.Error("Couldn't retrieve the ComponentBase of the gauge bar."); + failed = true; return; } - tickerNode = NativeUi.GetNodeByID(&gaugeBar->UldManager, tickerImageId); var frameImageNode = NativeUi.GetNodeByID(&gaugeBar->UldManager, frameImageId); if (frameImageNode is null) { log.Error("Couldn't retrieve the target ImageNode of the gauge bar."); + failed = true; return; } - if (tickerNode is null && !failed) - { - 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);*/ - if (tickerNode is null) + if (tickerNode.imageNode is null && !failed) + { + tickerNode.CreateCompleteImageNode(0, gaugeBarNode, (AtkResNode*)frameImageNode); + if (tickerNode.imageNode is null) { + log.Error("ImageNode could not be created."); failed = true; + return; } + tickerNode.imageNode->AtkResNode.SetWidth(160); + tickerNode.imageNode->AtkResNode.SetHeight(20); } } private unsafe void NativeUiDisposeListener(AddonEvent type, AddonArgs args) { - var currentAddon = (AtkUnitBase*)args.Addon; - log.Debug("Listener dispose triggered"); - NativeUiDispose(ref hpTicker, currentAddon, HPGaugeNodeId); - NativeUiDispose(ref mpTicker, currentAddon, MPGaugeNodeId); - } - - private unsafe void NativeUiDispose(ref AtkImageNode* atkImageNode, AtkUnitBase* baseWidget, uint componentBaseId) - { - if (!utilities.IsAddonReady(baseWidget) && atkImageNode is not null) - { - log.Error("Couldn't dipose of nodes due to addon unavailability."); - return; - } - if (atkImageNode is not null) - { - NativeUi.UnlinkNode(atkImageNode, baseWidget->GetNodeById(componentBaseId)->GetComponent()); - NativeUi.FreeImageComponents(ref atkImageNode); - atkImageNode = null; - } - } - - private unsafe void NativeUiDispose() - { - NativeUiDispose(ref hpTicker, ParamWidget, HPGaugeNodeId); - NativeUiDispose(ref mpTicker, ParamWidget, MPGaugeNodeId); + hpTickerNode.Dispose(); + mpTickerNode.Dispose(); } public void Dispose() @@ -687,7 +637,8 @@ public void Dispose() framework.Update -= OnFrameworkUpdate; addonLifecycle.UnregisterListener(AddonEvent.PreFinalize, "_ParameterWidget", NativeUiDisposeListener); addonLifecycle.UnregisterListener(AddonEvent.PostUpdate, addonsLookup, CheckBarCollision); - NativeUiDispose(); + hpTickerNode.Dispose(); + mpTickerNode.Dispose(); pluginInterface.UiBuilder.Draw -= DrawUI; clientState.TerritoryChanged -= TerritoryChanged; gameConfig.SystemChanged -= CheckResolutionChange; diff --git a/TickTracker/Windows/DevWindow.cs b/TickTracker/Windows/DevWindow.cs index 64dbcbf..02f896c 100644 --- a/TickTracker/Windows/DevWindow.cs +++ b/TickTracker/Windows/DevWindow.cs @@ -10,8 +10,8 @@ namespace TickTracker.Windows; public sealed class DevWindow : Window { private static readonly List PrintLines = new(); - public int partId; - public int partListIndex; + public int partId { get; set; } + public int partListIndex { get; set; } public DevWindow() : base("DevWindow") { @@ -24,18 +24,23 @@ public DevWindow() : base("DevWindow") public override void Draw() { - foreach (var line in PrintLines) + var pId = partId; + var pListIndex = partListIndex; + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X / 3); + if (ImGui.InputInt("ImageNode PartId", ref pId, 1)) { - ImGui.TextUnformatted(line); + partId = pId; } - PrintLines.Clear(); ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X / 3); - if (ImGui.InputInt("ImageNode PartId", ref partId, 1)) + if (ImGui.InputInt("ImageNode PartsList Index", ref pListIndex, 1)) { - partId = Math.Clamp(partId, 0, 6); + partListIndex = pListIndex; } - ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X / 3); - ImGui.InputInt("ImageNode PartsList Index", ref partListIndex, 1); + foreach (var line in PrintLines) + { + ImGui.TextUnformatted(line); + } + PrintLines.Clear(); } public static void Print(string text)