Initial commit: AetherBags + KamiToolKit for FC Gitea
Debug Build and Test / Build against Latest Dalamud (push) Has been cancelled
Debug Build and Test / Build against Staging Dalamud (push) Has been cancelled

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-02-08 14:46:31 -05:00
commit 8db4ce6094
375 changed files with 34124 additions and 0 deletions
+8
View File
@@ -0,0 +1,8 @@
using System.Numerics;
namespace KamiToolKit.Classes;
internal class AddonConfig {
public Vector2 Position = Vector2.Zero;
public float Scale = 1.0f;
}
+8
View File
@@ -0,0 +1,8 @@
using System;
using KamiToolKit.Premade.Color;
namespace KamiToolKit.Classes;
internal readonly struct BatchToken(ColorPickerWidget owner) : IDisposable {
public void Dispose() => owner.EndBatchUpdate();
}
+23
View File
@@ -0,0 +1,23 @@
using System.Numerics;
namespace KamiToolKit.Classes;
public class Bounds {
public required Vector2 TopLeft { get; set; }
public required Vector2 BottomRight { get; set; }
public float Top => TopLeft.Y;
public float Left => TopLeft.X;
public float Bottom => BottomRight.Y;
public float Right => BottomRight.X;
public float Width => BottomRight.X - TopLeft.X;
public float Height => BottomRight.Y - TopLeft.Y;
public Vector2 Size => new(Width, Height);
public float CenterX => (TopLeft.X + BottomRight.X) / 2.0f;
public float CenterY => (TopLeft.Y + BottomRight.Y) / 2.0f;
public Vector2 Center => new(CenterX, CenterY);
public override string ToString() => $"{TopLeft}, {BottomRight}";
}
+18
View File
@@ -0,0 +1,18 @@
using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;
namespace KamiToolKit.Classes;
public static unsafe class ColorHelper {
public static Vector4 GetColor(uint colorId)
=> ConvertToVector4(AtkStage.Instance()->AtkUIColorHolder->GetColor(true, colorId));
private static Vector4 ConvertToVector4(uint color) {
var a = (byte)(color >> 24);
var b = (byte)(color >> 16);
var g = (byte)(color >> 8);
var r = (byte)color;
return new Vector4(r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f);
}
}
@@ -0,0 +1,44 @@
using System;
using System.Runtime.InteropServices;
using FFXIVClientStructs.FFXIV.Component.GUI;
using static FFXIVClientStructs.FFXIV.Component.GUI.AtkModuleInterface;
namespace KamiToolKit.Classes;
public unsafe class CustomEventInterface : IDisposable {
private readonly AtkEventInterface* eventInterface;
private AtkEventInterface.Delegates.ReceiveEvent? receiveEventDelegate;
private AtkEventInterface.Delegates.ReceiveEventWithResult? receiveEventWithResultDelegate;
public CustomEventInterface(AtkEventInterface.Delegates.ReceiveEvent eventHandler, AtkEventInterface.Delegates.ReceiveEventWithResult? receiveEventWithResult = null) {
receiveEventDelegate = eventHandler;
receiveEventWithResultDelegate = receiveEventWithResult;
eventInterface = NativeMemoryHelper.UiAlloc<AtkEventInterface>();
eventInterface->VirtualTable = (AtkEventInterface.AtkEventInterfaceVirtualTable*)NativeMemoryHelper.Malloc((ulong)sizeof(void*) * 2);
eventInterface->VirtualTable->ReceiveEvent = (delegate* unmanaged<AtkEventInterface*, AtkValue*, AtkValue*, uint, ulong, AtkValue*>)Marshal.GetFunctionPointerForDelegate(receiveEventDelegate);
if (receiveEventWithResultDelegate is not null) {
eventInterface->VirtualTable->ReceiveEventWithResult = (delegate* unmanaged<AtkEventInterface*, AtkValue*, AtkValue*, uint, ulong, AtkValue*>)Marshal.GetFunctionPointerForDelegate(receiveEventWithResultDelegate);
}
else {
eventInterface->VirtualTable->ReceiveEventWithResult = (delegate* unmanaged<AtkEventInterface*, AtkValue*, AtkValue*, uint, ulong, AtkValue*>)(delegate* unmanaged<void>)&NullSub;
}
}
public void Dispose() {
if (eventInterface is null) return;
NativeMemoryHelper.Free(eventInterface->VirtualTable, (ulong)sizeof(void*) * 2);
NativeMemoryHelper.UiFree(eventInterface);
receiveEventDelegate = null;
receiveEventWithResultDelegate = null;
}
[UnmanagedCallersOnly] private static void NullSub() { }
public static implicit operator AtkEventInterface*(CustomEventInterface listener) => listener.eventInterface;
}
@@ -0,0 +1,35 @@
using System;
using System.Runtime.InteropServices;
using FFXIVClientStructs.FFXIV.Component.GUI;
namespace KamiToolKit.Classes;
public unsafe class CustomEventListener : IDisposable {
private readonly AtkEventListener* eventListener;
private AtkEventListener.Delegates.ReceiveEvent? receiveEventDelegate;
public CustomEventListener(AtkEventListener.Delegates.ReceiveEvent eventHandler) {
receiveEventDelegate = eventHandler;
eventListener = NativeMemoryHelper.UiAlloc<AtkEventListener>();
eventListener->VirtualTable = (AtkEventListener.AtkEventListenerVirtualTable*)NativeMemoryHelper.Malloc((ulong)sizeof(void*) * 3);
eventListener->VirtualTable->Dtor = (delegate* unmanaged<AtkEventListener*, byte, AtkEventListener*>)(delegate* unmanaged<void>)&NullSub;
eventListener->VirtualTable->ReceiveGlobalEvent = (delegate* unmanaged<AtkEventListener*, AtkEventType, int, AtkEvent*, AtkEventData*, void>)(delegate* unmanaged<void>)&NullSub;
eventListener->VirtualTable->ReceiveEvent = (delegate* unmanaged<AtkEventListener*, AtkEventType, int, AtkEvent*, AtkEventData*, void>)Marshal.GetFunctionPointerForDelegate(receiveEventDelegate);
}
public virtual void Dispose() {
if (eventListener is null) return;
NativeMemoryHelper.Free(eventListener->VirtualTable, (ulong)sizeof(void*) * 3);
NativeMemoryHelper.UiFree(eventListener);
receiveEventDelegate = null;
}
[UnmanagedCallersOnly] private static void NullSub() { }
public static implicit operator AtkEventListener*(CustomEventListener listener) => listener.eventListener;
}
+76
View File
@@ -0,0 +1,76 @@
using System;
using System.IO;
using System.Runtime.CompilerServices;
using Dalamud.Interface.Textures.TextureWraps;
using Dalamud.IoC;
using Dalamud.Plugin;
using Dalamud.Plugin.Services;
namespace KamiToolKit.Classes;
internal class DalamudInterface {
private static DalamudInterface? instance;
public static DalamudInterface Instance => instance ??= new DalamudInterface();
[PluginService] public IPluginLog Log { get; set; } = null!;
[PluginService] public IAddonLifecycle AddonLifecycle { get; set; } = null!;
[PluginService] public IDataManager DataManager { get; set; } = null!;
[PluginService] public ITextureProvider TextureProvider { get; set; } = null!;
[PluginService] public IFramework Framework { get; set; } = null!;
[PluginService] public IAddonEventManager AddonEventManager { get; set; } = null!;
[PluginService] public IDalamudPluginInterface PluginInterface { get; set; } = null!;
[PluginService] public IGameGui GameGui { get; set; } = null!;
[PluginService] public IGameInteropProvider GameInteropProvider { get; set; } = null!;
[PluginService] public ISeStringEvaluator SeStringEvaluator { get; set; } = null!;
private DalamudInterface() {
if (!KamiToolKitLibrary.IsInitialized)
throw new Exception("KamiToolKit not initialized! You must call KamiToolKitLibrary.Initialize() before using KamiToolKit.\n" +
"Don't forget to call KamiToolKitLibrary.Dispose() in your plugins dispose to ensure all assets are freed and to trigger bad practice warnings.");
}
public string GetAssetDirectoryPath()
=> Path.Combine(PluginInterface.AssemblyLocation.DirectoryName ?? throw new Exception("Directory from Dalamud is Invalid Somehow"), "Assets");
public string GetAssetPath(string assetName)
=> Path.Combine(GetAssetDirectoryPath(), assetName);
public IDalamudTextureWrap? LoadAsset(string assetName)
=> TextureProvider.GetFromFile(GetAssetPath(assetName)).GetWrapOrDefault();
}
internal static class Log {
private static readonly bool ExcessiveLogging = false;
internal static void Debug(string message) {
DalamudInterface.Instance.Log.Debug($"[KamiToolKit] {message}");
}
internal static void Fatal(string message) {
DalamudInterface.Instance.Log.Fatal($"[KamiToolKit] {message}");
}
internal static void Warning(string message) {
DalamudInterface.Instance.Log.Warning($"[KamiToolKit] {message}");
}
internal static void Verbose(string message) {
DalamudInterface.Instance.Log.Verbose($"[KamiToolKit] {message}");
}
internal static void Excessive(string message) {
if (ExcessiveLogging) {
Verbose($"[KamiToolKit] {message}");
}
}
internal static void Error(string message) {
DalamudInterface.Instance.Log.Error($"[KamiToolKit] {message}");
}
internal static void Exception(Exception exception, [CallerMemberName] string? callerName = null) {
DalamudInterface.Instance.Log.Error(exception, $"Exception in {callerName}");
}
}
+69
View File
@@ -0,0 +1,69 @@
using FFXIVClientStructs.FFXIV.Component.GUI;
using Lumina.Text;
using Lumina.Text.ReadOnly;
namespace KamiToolKit.Classes;
public unsafe class DragDropPayload {
public DragDropType Type { get; set; } = DragDropType.Nothing;
public short ReferenceIndex { get; set; }
/// <remarks> Index (like AtkDragDropInterface.ReferenceIndex), InventoryType, etc. </remarks>
public int Int1 { get; set; }
/// <remarks> ActionId, ItemId, EmoteId, InventorySlotIndex, ListIndex, MacroIndex etc. </remarks>
public int Int2 { get; set; } = -1;
// unknown usage
// public ulong Unk8 { get; set; }
// unknown usage
// public AtkValue* AtkValue { get; set; }
public ReadOnlySeString Text { get; set; }
// unknown usage
// public uint Flags { get; set; }
public static DragDropPayload FromDragDropInterface(AtkDragDropInterface* dragDropInterface) {
var payloadContainer = dragDropInterface->GetPayloadContainer();
return new DragDropPayload {
Type = dragDropInterface->DragDropType,
ReferenceIndex = dragDropInterface->DragDropReferenceIndex,
Int1 = payloadContainer->Int1,
Int2 = payloadContainer->Int2,
Text = new ReadOnlySeString(payloadContainer->Text),
};
}
public void ToDragDropInterface(AtkDragDropInterface* dragDropInterface, bool writeToPayloadContainer = true) {
dragDropInterface->DragDropType = Type;
dragDropInterface->DragDropReferenceIndex = ReferenceIndex;
if (writeToPayloadContainer) {
var payloadContainer = dragDropInterface->GetPayloadContainer();
payloadContainer->Clear();
payloadContainer->Int1 = Int1;
payloadContainer->Int2 = Int2;
if (Text.IsEmpty) {
payloadContainer->Text.Clear();
}
else {
var stringBuilder = new SeStringBuilder().Append(Text);
payloadContainer->Text.SetString(stringBuilder.GetViewAsSpan());
}
}
}
public void Clear() {
Type = DragDropType.Nothing;
ReferenceIndex = 0;
Int1 = 0;
Int2 = -1;
Text = default;
}
}
+39
View File
@@ -0,0 +1,39 @@
using System.Diagnostics;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
namespace KamiToolKit.Classes;
/// WARNING: These features are potentially extremely volatile, use at your own risk.
public unsafe class Experimental {
private static Experimental? instance;
public static Experimental Instance => instance ??= new Experimental();
public void EnableHooks() { }
public void DisposeHooks() {
}
// WARNING: May result in undefined state or accidental network requests
// Use at your own risk.
[Conditional("DEBUG")]
public static void ForceOpenAddon(AgentId agentId, int delayTicks = 0) {
if (delayTicks is not 0) {
DalamudInterface.Instance.Framework.RunOnTick(() => {
AgentModule.Instance()->GetAgentByInternalId(agentId)->Show();
}, delayTicks: delayTicks);
}
else {
DalamudInterface.Instance.Framework.RunOnFrameworkThread(() => {
AgentModule.Instance()->GetAgentByInternalId(agentId)->Show();
});
}
}
// WARNING: May result in undefined state or accidental network requests
// Use at your own risk.
[Conditional("DEBUG")]
public static void ForceCloseAddon(AgentId agentId)
=> DalamudInterface.Instance.Framework.RunOnFrameworkThread(() => {
AgentModule.Instance()->GetAgentByInternalId(agentId)->Hide();
});
}
+23
View File
@@ -0,0 +1,23 @@
using System.Numerics;
namespace KamiToolKit.Classes;
public static class FlagHelper {
public static bool ReadFlag<T>(ref T flagsField, int flag) where T : struct, IBinaryInteger<T>
=> (flagsField & T.One << BitOperations.Log2((uint)flag)) != T.Zero;
public static void SetFlag<T>(ref T flagsField, int flag) where T : struct, IBinaryInteger<T>
=> flagsField |= T.One << BitOperations.Log2((uint)flag);
public static void ClearFlag<T>(ref T flagsField, int flag) where T : struct, IBinaryInteger<T>
=> flagsField &= ~(T.One << BitOperations.Log2((uint)flag));
public static void UpdateFlag<T>(ref T flagsField, int flag, bool enable) where T : struct, IBinaryInteger<T> {
if (enable) {
SetFlag(ref flagsField, flag);
}
else {
ClearFlag(ref flagsField, flag);
}
}
}
+18
View File
@@ -0,0 +1,18 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace KamiToolKit.Classes;
internal static class GenericUtil {
public static bool AreEqual<T>(T? left, T? right) {
if (default(T) == null) return ReferenceEquals(left, right);
if (left == null || right == null) return left == null && right == null;
var leftSpan = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As<T, byte>(ref left), Unsafe.SizeOf<T>());
var rightSpan = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As<T, byte>(ref right), Unsafe.SizeOf<T>());
return leftSpan.SequenceEqual(rightSpan);
}
}
+11
View File
@@ -0,0 +1,11 @@
using FFXIVClientStructs.FFXIV.Component.GUI;
using ListItemInfo = FFXIVClientStructs.FFXIV.Component.GUI.AtkComponentListItemPopulator.ListItemInfo;
namespace KamiToolKit.Classes;
public unsafe class ListPopulatorData {
public AtkUnitBase* Addon { get; init; }
public ListItemInfo* ItemInfo { get; init; }
public AtkResNode** NodeList { get; init; }
public uint Index { get; init; }
}
+75
View File
@@ -0,0 +1,75 @@
using System;
using System.Runtime.InteropServices;
using FFXIVClientStructs.FFXIV.Client.System.Memory;
namespace KamiToolKit.Classes;
internal static class NativeMemoryHelper {
public static unsafe T* UiAlloc<T>(int elementCount, ulong alignment = 8) where T : unmanaged
=> UiAlloc<T>((uint)elementCount, alignment);
public static unsafe T* UiAlloc<T>(uint elementCount = 1, ulong alignment = 8) where T : unmanaged {
var allocSize = (ulong)sizeof(T) * elementCount;
var memory = (T*)IMemorySpace.GetUISpace()->Malloc(allocSize, alignment);
IMemorySpace.Memset(memory, 0, allocSize);
if (memory is null) {
throw new Exception($"Unable to allocate memory for {typeof(T)}");
}
return memory;
}
public static unsafe void UiFree<T>(T* memory) where T : unmanaged
=> IMemorySpace.Free(memory);
public static unsafe void UiFree<T>(T* memory, uint elementCount) where T : unmanaged
=> IMemorySpace.Free(memory, (ulong)sizeof(T) * elementCount);
public static unsafe T* Create<T>() where T : unmanaged, ICreatable {
var memory = IMemorySpace.GetUISpace()->Create<T>();
if (memory is null) {
throw new Exception($"Unable to allocate memory for {typeof(T)}");
}
return memory;
}
public static unsafe nint Malloc(ulong size, ulong alignment = 8)
=> (nint)IMemorySpace.GetUISpace()->Malloc(size, alignment);
public static unsafe void Free(void* memory, ulong size)
=> IMemorySpace.Free(memory, size);
public static unsafe void ResizeArray<T>(ref T* array, int oldSize, uint newSize) where T : unmanaged
=> ResizeArray(ref array, oldSize, (int)newSize);
public static unsafe void ResizeArray<T>(ref T* array, uint oldSize, uint newSize) where T : unmanaged
=> ResizeArray(ref array, (int)oldSize, (int)newSize);
public static unsafe void ResizeArray<T>(ref T* array, uint oldSize, int newSize) where T : unmanaged
=> ResizeArray(ref array, (int)oldSize, newSize);
public static unsafe void ResizeArray<T>(ref T* array, int oldSize, int newSize) where T : unmanaged {
var newBuffer = UiAlloc<T>((uint)newSize);
Copy(array, newBuffer, oldSize);
if (array is not null) {
UiFree(array, (uint)oldSize);
}
array = newBuffer;
}
public static unsafe void Copy<T>(T* oldBuffer, T* newBuffer, int count) where T : unmanaged
=> Copy(oldBuffer, newBuffer, (uint)count);
public static unsafe void Copy<T>(T* oldBuffer, T* newBuffer, uint count) where T : unmanaged
=> NativeMemory.Copy(oldBuffer, newBuffer, (nuint)(sizeof(T) * count));
public static unsafe void MemCopy<T>(T* oldBuffer, T* newBuffer, uint byteCount) where T : unmanaged
=> NativeMemory.Copy(oldBuffer, newBuffer, byteCount);
}
+199
View File
@@ -0,0 +1,199 @@
using System;
using FFXIVClientStructs.FFXIV.Component.GUI;
namespace KamiToolKit.Classes;
public enum NodePosition {
BeforeTarget,
AfterTarget,
BeforeAllSiblings,
AfterAllSiblings,
AsLastChild,
AsFirstChild,
}
internal static unsafe class NodeLinker {
internal static void AttachNode(AtkResNode* node, AtkResNode* attachTargetNode, NodePosition position) {
switch (position) {
case NodePosition.BeforeTarget:
EmplaceBefore(node, attachTargetNode);
break;
case NodePosition.AfterTarget:
EmplaceAfter(node, attachTargetNode);
break;
case NodePosition.BeforeAllSiblings:
EmplaceBeforeSiblings(node, attachTargetNode);
break;
case NodePosition.AfterAllSiblings:
EmplaceAfterSiblings(node, attachTargetNode);
break;
case NodePosition.AsLastChild:
EmplaceAsLastChild(node, attachTargetNode);
break;
case NodePosition.AsFirstChild:
EmplaceAsFirstChild(node, attachTargetNode);
break;
default:
throw new ArgumentOutOfRangeException(nameof(position), position, null);
}
}
private static void EmplaceBefore(AtkResNode* node, AtkResNode* attachTargetNode) {
node->ParentNode = attachTargetNode->ParentNode;
// Target node is the head of the nodelist, we will be the new head.
if (attachTargetNode->NextSiblingNode is null) {
attachTargetNode->ParentNode->ChildNode = node;
}
// We have a node that will be before us
if (attachTargetNode->NextSiblingNode is not null) {
attachTargetNode->NextSiblingNode->PrevSiblingNode = node;
node->NextSiblingNode = attachTargetNode->NextSiblingNode;
}
attachTargetNode->NextSiblingNode = node;
node->PrevSiblingNode = attachTargetNode;
if (attachTargetNode->ParentNode->GetNodeType() is not NodeType.Component) {
attachTargetNode->ParentNode->ChildCount++;
}
}
private static void EmplaceAfter(AtkResNode* node, AtkResNode* attachTargetNode) {
node->ParentNode = attachTargetNode->ParentNode;
// We have a node that will be after us
if (attachTargetNode->PrevSiblingNode is not null) {
attachTargetNode->PrevSiblingNode->NextSiblingNode = node;
node->PrevSiblingNode = attachTargetNode->PrevSiblingNode;
}
attachTargetNode->PrevSiblingNode = node;
node->NextSiblingNode = attachTargetNode;
if (attachTargetNode->ParentNode->GetNodeType() is not NodeType.Component) {
attachTargetNode->ParentNode->ChildCount++;
}
}
private static void EmplaceBeforeSiblings(AtkResNode* node, AtkResNode* attachTargetNode) {
var current = attachTargetNode;
var previous = current;
while (current is not null) {
previous = current;
current = current->NextSiblingNode;
}
if (previous is not null) {
EmplaceBefore(node, previous);
}
if (attachTargetNode->ParentNode->GetNodeType() is not NodeType.Component) {
attachTargetNode->ParentNode->ChildCount++;
}
}
private static void EmplaceAfterSiblings(AtkResNode* node, AtkResNode* attachTargetNode) {
var current = attachTargetNode;
var previous = current;
while (current is not null) {
previous = current;
current = current->PrevSiblingNode;
}
if (previous is not null) {
EmplaceAfter(node, previous);
}
if (attachTargetNode->ParentNode->GetNodeType() is not NodeType.Component) {
attachTargetNode->ParentNode->ChildCount++;
}
}
private static void EmplaceAsLastChild(AtkResNode* node, AtkResNode* attachTargetNode) {
// If the child list is empty
if (attachTargetNode->ChildNode is null && attachTargetNode->GetNodeType() is not NodeType.Component) {
if (attachTargetNode->GetNodeType() is not NodeType.Component) {
attachTargetNode->ChildNode = node;
node->ParentNode = attachTargetNode;
attachTargetNode->ChildCount++;
}
else {
node->ParentNode = attachTargetNode;
}
}
// Else Add to the List
else {
var currentNode = attachTargetNode->ChildNode;
while (currentNode is not null && currentNode->PrevSiblingNode != null) {
currentNode = currentNode->PrevSiblingNode;
}
node->ParentNode = attachTargetNode;
node->NextSiblingNode = currentNode;
if (currentNode is not null) {
currentNode->PrevSiblingNode = node;
}
if (attachTargetNode->GetNodeType() is not NodeType.Component) {
attachTargetNode->ChildCount++;
}
}
}
private static void EmplaceAsFirstChild(AtkResNode* node, AtkResNode* attachTargetNode) {
// If the child list is empty
if (attachTargetNode->ChildNode is null && attachTargetNode->ChildCount is 0) {
if (attachTargetNode->GetNodeType() is not NodeType.Component) {
attachTargetNode->ChildNode = node;
node->ParentNode = attachTargetNode;
attachTargetNode->ChildCount++;
}
else {
node->ParentNode = attachTargetNode;
}
}
// Else Add to the List as the First Child
else {
if (attachTargetNode->GetNodeType() is not NodeType.Component) {
attachTargetNode->ChildNode->NextSiblingNode = node;
node->PrevSiblingNode = attachTargetNode->ChildNode;
attachTargetNode->ChildNode = node;
node->ParentNode = attachTargetNode;
attachTargetNode->ChildCount++;
}
else {
node->PrevSiblingNode = attachTargetNode->ChildNode;
node->ParentNode = attachTargetNode;
}
}
}
public static void DetachNode(AtkResNode* node) {
if (node is null) return;
if (node->ParentNode is null) return;
if (node->ParentNode->ChildNode == node)
node->ParentNode->ChildNode = node->PrevSiblingNode;
if (node->PrevSiblingNode != null)
node->PrevSiblingNode->NextSiblingNode = node->NextSiblingNode;
if (node->NextSiblingNode != null)
node->NextSiblingNode->PrevSiblingNode = node->PrevSiblingNode;
if (node->ParentNode->GetNodeType() is not NodeType.Component) {
node->ParentNode->ChildCount--;
}
}
}
+34
View File
@@ -0,0 +1,34 @@
using System.Numerics;
namespace KamiToolKit.Classes;
public class Part {
public float Width { get; set; }
public float Height { get; set; }
public Vector2 Size {
get => new(Width, Height);
set {
Width = value.X;
Height = value.Y;
}
}
public float U { get; set; }
public float V { get; set; }
public Vector2 TextureCoordinates {
get => new(U, V);
set {
U = value.X;
V = value.Y;
}
}
public uint Id { get; set; }
public string TexturePath { get; set; } = string.Empty;
}
+83
View File
@@ -0,0 +1,83 @@
using System;
using System.Linq;
using FFXIVClientStructs.FFXIV.Component.GUI;
namespace KamiToolKit.Classes;
/// <summary>
/// Wrapper around a AtkUldPartsList, manages adding multiple parts more easily.
/// </summary>
public unsafe class PartsList : IDisposable {
internal AtkUldPartsList* InternalPartsList;
private bool isDisposed;
public PartsList() {
InternalPartsList = NativeMemoryHelper.UiAlloc<AtkUldPartsList>();
InternalPartsList->Parts = null;
InternalPartsList->PartCount = 0;
InternalPartsList->Id = 0;
}
public void Dispose() {
if (!isDisposed) {
foreach (var partIndex in Enumerable.Range(0, (int)PartCount)) {
ref var part = ref InternalPartsList->Parts[partIndex];
if (part.UldAsset is not null && part.UldAsset->AtkTexture.IsTextureReady()) {
part.UldAsset->AtkTexture.ReleaseTexture();
part.UldAsset->AtkTexture.KernelTexture = null;
part.UldAsset->AtkTexture.TextureType = 0;
}
NativeMemoryHelper.UiFree(part.UldAsset);
part.UldAsset = null;
}
NativeMemoryHelper.UiFree(InternalPartsList);
InternalPartsList = null;
}
isDisposed = true;
}
private uint PartCount {
get => InternalPartsList->PartCount;
set => InternalPartsList->PartCount = value;
}
public void Add(params Part[] items) {
foreach (var part in items) {
Add(part);
}
}
public AtkUldPart* Add(Part item) {
NativeMemoryHelper.ResizeArray(ref InternalPartsList->Parts, PartCount, PartCount + 1);
ref var newPart = ref InternalPartsList->Parts[PartCount];
newPart.Width = (ushort) item.Width;
newPart.Height = (ushort) item.Height;
newPart.U = (ushort) item.U;
newPart.V = (ushort) item.V;
newPart.UldAsset = NativeMemoryHelper.UiAlloc<AtkUldAsset>();
newPart.UldAsset->Id = item.Id;
newPart.UldAsset->AtkTexture.Ctor();
newPart.LoadTexture(item.TexturePath);
return &InternalPartsList->Parts[PartCount++];
}
public AtkUldPart* this[int index] {
get {
if (InternalPartsList is null) return null;
if (PartCount <= index) return null;
return &InternalPartsList->Parts[index];
}
}
}
+3
View File
@@ -0,0 +1,3 @@
namespace KamiToolKit.Classes;
public record TabbedNodeEntry<T>(T Node, int Tab) where T : NodeBase;
@@ -0,0 +1,26 @@
using FFXIVClientStructs.FFXIV.Component.GUI;
namespace KamiToolKit.Classes;
public unsafe class ViewportEventListener(AtkEventListener.Delegates.ReceiveEvent eventHandler) : CustomEventListener(eventHandler) {
public void AddEvent(AtkEventType eventType, AtkResNode* node) {
DalamudInterface.Instance.Framework.RunOnFrameworkThread(() => {
Log.Verbose($"Registering ViewportEvent: {eventType}");
AtkStage.Instance()->ViewportEventManager.RegisterEvent(eventType, 0, node, &node->AtkEventTarget, this, false);
});
}
public void RemoveEvent(AtkEventType eventType) {
DalamudInterface.Instance.Framework.RunOnFrameworkThread(() => {
Log.Verbose($"Unregistering ViewportEvent: {eventType}");
AtkStage.Instance()->ViewportEventManager.UnregisterEvent(eventType, 0, this, false);
});
}
public override void Dispose() {
Log.Verbose("Disposing ViewportEventListener");
RemoveEvent(AtkEventType.UnregisterAll);
base.Dispose();
}
}