Initial commit: AetherBags + KamiToolKit for FC Gitea
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
using System.Numerics;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using ModifierFlag = FFXIVClientStructs.FFXIV.Component.GUI.AtkEventData.AtkMouseData.ModifierFlag;
|
||||
|
||||
namespace KamiToolKit.Extensions;
|
||||
|
||||
public static class AtkEventDataExtensions {
|
||||
extension(ref AtkEventData data) {
|
||||
public Vector2 MousePosition => new(data.MouseData.PosX, data.MouseData.PosY);
|
||||
public bool IsLeftClick => data.MouseData.ButtonId is 0;
|
||||
public bool IsRightClick => data.MouseData.ButtonId is 1;
|
||||
public bool IsNoModifiers => data.MouseData.Modifier is 0;
|
||||
public bool IsAltHeld => data.MouseData.Modifier.HasFlag(ModifierFlag.Alt);
|
||||
public bool IsControlHeld => data.MouseData.Modifier.HasFlag(ModifierFlag.Ctrl);
|
||||
public bool IsShiftHeld => data.MouseData.Modifier.HasFlag(ModifierFlag.Shift);
|
||||
public bool IsDragging => data.MouseData.Modifier.HasFlag(ModifierFlag.Dragging);
|
||||
public bool IsScrollUp => data.MouseData.WheelDirection >= 1;
|
||||
public bool IsScrollDown => data.MouseData.WheelDirection <= -1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace KamiToolKit.Extensions;
|
||||
|
||||
public static unsafe class AtkImageNodeExtensions {
|
||||
extension(ref AtkImageNode node) {
|
||||
public uint IconId => node.GetIconId();
|
||||
|
||||
private uint GetIconId() {
|
||||
if (node.PartsList is null) return 0;
|
||||
if (node.PartsList->Parts is null) return 0;
|
||||
if (node.PartsList->Parts->UldAsset is null) return 0;
|
||||
if (node.PartsList->Parts->UldAsset->AtkTexture.TextureType is not TextureType.Resource) return 0;
|
||||
if (node.PartsList->Parts->UldAsset->AtkTexture.Resource is null) return 0;
|
||||
|
||||
return node.PartsList->Parts->UldAsset->AtkTexture.Resource->IconId;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
using System.Numerics;
|
||||
using Dalamud.Interface;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using KamiToolKit.Classes;
|
||||
using KamiToolKit.Enums;
|
||||
|
||||
namespace KamiToolKit.Extensions;
|
||||
|
||||
public static unsafe class AtkResNodeExtensions {
|
||||
extension(ref AtkResNode node) {
|
||||
public Vector2 Position {
|
||||
get => new(node.X, node.Y);
|
||||
set => node.SetPositionFloat(value.X, value.Y);
|
||||
}
|
||||
|
||||
public Vector2 ScreenPosition
|
||||
=> new(node.ScreenX, node.ScreenY);
|
||||
|
||||
public Vector2 Size {
|
||||
get => new(node.GetWidth(), node.GetHeight());
|
||||
set {
|
||||
node.SetWidth((ushort) value.X);
|
||||
node.SetHeight((ushort) value.Y);
|
||||
}
|
||||
}
|
||||
|
||||
public Bounds Bounds => new() {
|
||||
TopLeft = node.Position,
|
||||
BottomRight = node.Position + node.Size,
|
||||
};
|
||||
|
||||
public Vector2 Center
|
||||
=> node.Position + node.Size / 2.0f;
|
||||
|
||||
public Vector2 Scale {
|
||||
get => new (node.GetScaleX(), node.GetScaleY());
|
||||
set => node.SetScale(value.X, value.Y);
|
||||
}
|
||||
|
||||
public float RotationDegrees {
|
||||
get => node.GetRotationDegrees();
|
||||
set => node.SetRotationDegrees(value - (int)(value / 360.0f) * 360.0f);
|
||||
}
|
||||
|
||||
public Vector2 Origin {
|
||||
get => new(node.OriginX, node.OriginY);
|
||||
set => node.SetOrigin(value.X, value.Y);
|
||||
}
|
||||
|
||||
public bool Visible {
|
||||
get => node.IsVisible();
|
||||
set => node.ToggleVisibility(value);
|
||||
}
|
||||
|
||||
public Vector4 ColorVector {
|
||||
get => node.Color.ToVector4();
|
||||
set => node.Color = value.ToByteColor();
|
||||
}
|
||||
|
||||
public ColorHelpers.HsvaColor ColorHsva {
|
||||
get => ColorHelpers.RgbaToHsv(node.ColorVector);
|
||||
set => node.Color = ColorHelpers.HsvToRgb(value).ToByteColor();
|
||||
}
|
||||
|
||||
public Vector3 AddColor {
|
||||
get => new Vector3(node.AddRed, node.AddGreen, node.AddBlue) / 255.0f;
|
||||
set {
|
||||
node.AddRed = (short)(value.X * 255);
|
||||
node.AddGreen = (short)(value.Y * 255);
|
||||
node.AddBlue = (short)(value.Z * 255);
|
||||
}
|
||||
}
|
||||
|
||||
public ColorHelpers.HsvaColor AddColorHsva {
|
||||
get => ColorHelpers.RgbaToHsv(node.AddColor.AsVector4());
|
||||
set => node.AddColor = ColorHelpers.HsvToRgb(value).AsVector3();
|
||||
}
|
||||
|
||||
public Vector3 MultiplyColor {
|
||||
get => new Vector3(node.MultiplyRed, node.MultiplyGreen, node.MultiplyBlue) / 100.0f;
|
||||
set {
|
||||
node.MultiplyRed = (byte)(value.X * 100.0f);
|
||||
node.MultiplyGreen = (byte)(value.Y * 100.0f);
|
||||
node.MultiplyBlue = (byte)(value.Z * 100.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public ColorHelpers.HsvaColor MultiplyColorHsva {
|
||||
get => ColorHelpers.RgbaToHsv(node.MultiplyColor.AsVector4());
|
||||
set => node.MultiplyColor = ColorHelpers.HsvToRgb(value).AsVector3();
|
||||
}
|
||||
|
||||
public void AddNodeFlag(params NodeFlags[] flags) {
|
||||
foreach (var flag in flags) {
|
||||
node.NodeFlags |= flag;
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveNodeFlag(params NodeFlags[] flags) {
|
||||
foreach (var flag in flags) {
|
||||
node.NodeFlags &= ~flag;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddDrawFlag(params DrawFlags[] flags) {
|
||||
foreach (var flag in flags) {
|
||||
node.DrawFlags |= (uint)flag;
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveDrawFlag(params DrawFlags[] flags) {
|
||||
foreach (var flag in flags) {
|
||||
node.DrawFlags &= (uint)flag;
|
||||
}
|
||||
}
|
||||
|
||||
public bool CheckCollision(short x, short y, bool inclusive = true)
|
||||
=> node.CheckCollisionAtCoords(x, y, inclusive);
|
||||
|
||||
public bool CheckCollision(Vector2 position, bool inclusive = true)
|
||||
=> node.CheckCollisionAtCoords((short) position.X, (short) position.Y, inclusive);
|
||||
|
||||
public bool CheckCollision(AtkEventData* eventData, bool inclusive = true)
|
||||
=> node.CheckCollisionAtCoords(eventData->MouseData.PosX, eventData->MouseData.PosY, inclusive);
|
||||
|
||||
public bool IsActuallyVisible {
|
||||
get {
|
||||
if (!node.Visible) return false;
|
||||
|
||||
var targetNode = node.ParentNode;
|
||||
while (targetNode is not null) {
|
||||
if (!targetNode->Visible) return false;
|
||||
targetNode = targetNode->ParentNode;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace KamiToolKit.Extensions;
|
||||
|
||||
public static unsafe class AtkStageExtensions {
|
||||
extension(ref AtkStage atkStage) {
|
||||
public void ClearNodeFocus(AtkResNode* targetNode) {
|
||||
if (targetNode is null) return;
|
||||
|
||||
foreach (ref var focusEntry in atkStage.AtkInputManager->FocusList) {
|
||||
|
||||
// If this entry has no listener/addon, skip it
|
||||
if (focusEntry.AtkEventListener is null) continue;
|
||||
|
||||
// If this entry has our target node
|
||||
if (focusEntry.AtkEventTarget == targetNode) {
|
||||
|
||||
// Clear the entry
|
||||
focusEntry.AtkEventTarget = null;
|
||||
focusEntry.FocusParam = 0;
|
||||
|
||||
// Clear the input managers focused node
|
||||
atkStage.AtkInputManager->FocusedNode = null;
|
||||
|
||||
// Clear collision managers collision node
|
||||
atkStage.AtkCollisionManager->IntersectingCollisionNode = null;
|
||||
|
||||
// Also remove this node from any additional focus nodes the addon might reference
|
||||
var addon = (AtkUnitBase*) focusEntry.AtkEventListener;
|
||||
foreach (ref var node in addon->AdditionalFocusableNodes) {
|
||||
if (node.Value == targetNode) {
|
||||
node = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using FFXIVClientStructs.Interop;
|
||||
using KamiToolKit.Classes;
|
||||
|
||||
namespace KamiToolKit.Extensions;
|
||||
|
||||
public static unsafe class AtkUldManagerExtensions {
|
||||
extension(ref AtkUldManager manager) {
|
||||
private bool IsNodeInObjectList(AtkResNode* node) {
|
||||
foreach (var objectNode in manager.ObjectNodeSpan) {
|
||||
if (objectNode.Value == node) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsNodeInDrawList(AtkResNode* node) {
|
||||
foreach (var drawNode in manager.Nodes) {
|
||||
if (drawNode.Value == node) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds node and all children nodes to this UldManager's Object List
|
||||
/// </summary>
|
||||
public void AddNodeToObjectList(NodeBase node) {
|
||||
manager.AddNodeToObjectList(node.ResNode);
|
||||
|
||||
foreach (var child in NodeBase.GetLocalChildren(node)) {
|
||||
manager.AddNodeToObjectList(child.ResNode);
|
||||
}
|
||||
|
||||
manager.UpdateDrawNodeList();
|
||||
}
|
||||
|
||||
public void AddNodeToObjectList(AtkResNode* newNode) {
|
||||
if (newNode is null) return;
|
||||
|
||||
// If the node is already in the object list, skip.
|
||||
if (manager.IsNodeInObjectList(newNode)) return;
|
||||
|
||||
var oldSize = manager.Objects->NodeCount;
|
||||
var newSize = oldSize + 1;
|
||||
var newBuffer = (AtkResNode**)NativeMemoryHelper.Malloc((ulong)(newSize * 8));
|
||||
|
||||
if (oldSize > 0) {
|
||||
foreach (var index in Enumerable.Range(0, oldSize)) {
|
||||
newBuffer[index] = manager.Objects->NodeList[index];
|
||||
}
|
||||
|
||||
NativeMemoryHelper.Free(manager.Objects->NodeList, (ulong)(oldSize * 8));
|
||||
}
|
||||
|
||||
newBuffer[newSize - 1] = newNode;
|
||||
|
||||
manager.Objects->NodeList = newBuffer;
|
||||
manager.Objects->NodeCount = newSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes node and all children nodes from this UldManager's Object List
|
||||
/// </summary>
|
||||
public void RemoveNodeFromObjectList(NodeBase node) {
|
||||
manager.RemoveNodeFromObjectList(node.ResNode);
|
||||
|
||||
foreach (var child in NodeBase.GetLocalChildren(node)) {
|
||||
manager.RemoveNodeFromObjectList(child.ResNode);
|
||||
}
|
||||
|
||||
manager.UpdateDrawNodeList();
|
||||
}
|
||||
|
||||
public void RemoveNodeFromObjectList(AtkResNode* node) {
|
||||
if (node is null) return;
|
||||
|
||||
// If the node isn't in the object list, skip.
|
||||
if (!manager.IsNodeInObjectList(node)) return;
|
||||
|
||||
var oldSize = manager.Objects->NodeCount;
|
||||
var newSize = oldSize - 1;
|
||||
var newBuffer = (AtkResNode**)NativeMemoryHelper.Malloc((ulong)(newSize * 8));
|
||||
|
||||
var newIndex = 0;
|
||||
foreach (var index in Enumerable.Range(0, oldSize)) {
|
||||
if (manager.Objects->NodeList[index] != node) {
|
||||
newBuffer[newIndex] = manager.Objects->NodeList[index];
|
||||
newIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
NativeMemoryHelper.Free(manager.Objects->NodeList, (ulong)(oldSize * 8));
|
||||
manager.Objects->NodeList = newBuffer;
|
||||
manager.Objects->NodeCount = newSize;
|
||||
}
|
||||
|
||||
public void PrintObjectList() {
|
||||
Log.Debug("Beginning NodeList");
|
||||
|
||||
foreach (var index in Enumerable.Range(0, manager.Objects->NodeCount)) {
|
||||
var nodePointer = manager.Objects->NodeList[index];
|
||||
Log.Debug($"[{index}]: {(nint)nodePointer:X}");
|
||||
}
|
||||
}
|
||||
|
||||
public uint GetMaxNodeId() {
|
||||
uint max = 1;
|
||||
foreach (var child in manager.Nodes) {
|
||||
if (child.Value is null) continue;
|
||||
|
||||
max = Math.Max(child.Value->NodeId, max);
|
||||
}
|
||||
|
||||
return max;
|
||||
}
|
||||
|
||||
public Span<Pointer<AtkResNode>> ObjectNodeSpan
|
||||
=> new(manager.Objects->NodeList, manager.Objects->NodeCount);
|
||||
|
||||
public T* SearchNodeById<T>(uint nodeId) where T : unmanaged {
|
||||
foreach (var node in manager.Nodes) {
|
||||
if (node.Value is not null) {
|
||||
if (node.Value->NodeId == nodeId)
|
||||
return (T*) node.Value;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public AtkResNode* SearchNodeById(uint nodeId)
|
||||
=> manager.SearchNodeById<AtkResNode>(nodeId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Dalamud.Interface.Textures.TextureWraps;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using KamiToolKit.Classes;
|
||||
|
||||
namespace KamiToolKit.Extensions;
|
||||
|
||||
public static unsafe class AtkUldPartExtensions {
|
||||
extension(ref AtkUldPart part) {
|
||||
public bool IsTextureReady => part.UldAsset is not null && part.UldAsset->AtkTexture.IsTextureReady();
|
||||
public Vector2 LoadedTextureSize => part.GetActualTextureSize();
|
||||
public string LoadedPath => part.GetLoadedPath();
|
||||
|
||||
public void LoadTexture(string path, bool resolveTheme = true) {
|
||||
try {
|
||||
if (part.UldAsset is null) return;
|
||||
|
||||
part.TryUnloadTexture();
|
||||
|
||||
var texturePath = path.Replace("_hr1", string.Empty);
|
||||
|
||||
var themedPath = texturePath.Replace("uld", GetThemePathModifier());
|
||||
if (DalamudInterface.Instance.DataManager.FileExists(themedPath) && resolveTheme) {
|
||||
texturePath = themedPath;
|
||||
}
|
||||
|
||||
if (DalamudInterface.Instance.DataManager.FileExists(texturePath)) {
|
||||
part.UldAsset->AtkTexture.LoadTextureWithDefaultVersion(texturePath);
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
Log.Exception(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadIcon(uint iconId)
|
||||
=> part.UldAsset->AtkTexture.LoadIconTexture(iconId, GetIconSubFolder(iconId));
|
||||
|
||||
private Vector2 GetActualTextureSize() {
|
||||
if (part.UldAsset is null) return Vector2.Zero;
|
||||
if (!part.UldAsset->AtkTexture.IsTextureReady()) return Vector2.Zero;
|
||||
if (part.UldAsset->AtkTexture.TextureType is 0) return Vector2.Zero;
|
||||
if (part.UldAsset->AtkTexture.KernelTexture is null) return Vector2.Zero;
|
||||
|
||||
var width = part.UldAsset->AtkTexture.GetTextureWidth();
|
||||
var height = part.UldAsset->AtkTexture.GetTextureHeight();
|
||||
return new Vector2(width, height);
|
||||
}
|
||||
|
||||
public void LoadTexture(Texture* texture) {
|
||||
if (part.UldAsset is null) return;
|
||||
|
||||
part.TryUnloadTexture();
|
||||
part.UldAsset->AtkTexture.KernelTexture = texture;
|
||||
part.UldAsset->AtkTexture.TextureType = TextureType.KernelTexture;
|
||||
}
|
||||
|
||||
public void LoadTexture(IDalamudTextureWrap textureWrap) {
|
||||
var texturePointer = (Texture*)DalamudInterface.Instance.TextureProvider.ConvertToKernelTexture(textureWrap, true);
|
||||
if (texturePointer is null) return;
|
||||
|
||||
part.LoadTexture(texturePointer);
|
||||
}
|
||||
|
||||
private string GetLoadedPath() {
|
||||
if (part.UldAsset is null) return string.Empty;
|
||||
if (part.UldAsset->AtkTexture.Resource is null) return string.Empty;
|
||||
if (part.UldAsset->AtkTexture.Resource->TexFileResourceHandle is null) return string.Empty;
|
||||
|
||||
return part.UldAsset->AtkTexture.Resource->TexFileResourceHandle->FileName.ToString();
|
||||
}
|
||||
|
||||
private void TryUnloadTexture() {
|
||||
if (part.UldAsset is null) return;
|
||||
if (!part.UldAsset->AtkTexture.IsTextureReady()) return;
|
||||
if (part.UldAsset->AtkTexture.TextureType is 0) return;
|
||||
if (part.UldAsset->AtkTexture.KernelTexture is null) return;
|
||||
|
||||
part.UldAsset->AtkTexture.ReleaseTexture();
|
||||
part.UldAsset->AtkTexture.KernelTexture = null;
|
||||
part.UldAsset->AtkTexture.TextureType = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetThemePathModifier() => AtkStage.Instance()->AtkUIColorHolder->ActiveColorThemeType switch {
|
||||
not 0 => $"uld/img{AtkStage.Instance()->AtkUIColorHolder->ActiveColorThemeType:00}",
|
||||
_ => "uld",
|
||||
};
|
||||
|
||||
public static IconSubFolder GetIconSubFolder(uint iconId) {
|
||||
var textureManager = AtkStage.Instance()->AtkTextureResourceManager;
|
||||
Span<byte> buffer = stackalloc byte[0x100];
|
||||
buffer.Clear();
|
||||
var bytePointer = (byte*) Unsafe.AsPointer(ref buffer[0]);
|
||||
|
||||
var textureScale = textureManager->DefaultTextureScale;
|
||||
var targetFolder = (IconSubFolder)textureManager->IconLanguage;
|
||||
|
||||
// Try to resolve the path using the current language
|
||||
AtkTexture.GetIconPath(bytePointer, iconId, textureScale, targetFolder);
|
||||
var pathResult = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(bytePointer).String;
|
||||
|
||||
// If the resolved path doesn't exist, re-process with default folder
|
||||
return DalamudInterface.Instance.DataManager.FileExists(pathResult) ? targetFolder : IconSubFolder.None;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
using FFXIVClientStructs.Attributes;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace KamiToolKit.Extensions;
|
||||
|
||||
public static unsafe class AtkUnitBaseExtensions {
|
||||
|
||||
public static string GetAddonTypeName<T>() where T : unmanaged {
|
||||
var type = typeof(T);
|
||||
var attribute = type.GetCustomAttributes().OfType<AddonAttribute>().FirstOrDefault();
|
||||
|
||||
if (attribute is null) throw new Exception("Unable to find AddonAttribute to resolve addon name.");
|
||||
var addonName = attribute.AddonIdentifiers.FirstOrDefault();
|
||||
|
||||
if (addonName is null) throw new Exception("Addon attribute names are empty.");
|
||||
return addonName;
|
||||
}
|
||||
|
||||
extension(ref AtkUnitBase addon) {
|
||||
public Vector2 Size => addon.GetSize();
|
||||
public Vector2 RootSize => addon.GetRootSize();
|
||||
public Vector2 Position => new(addon.X, addon.Y);
|
||||
|
||||
private Vector2 GetSize() {
|
||||
var width = stackalloc short[1];
|
||||
var height = stackalloc short[1];
|
||||
|
||||
addon.GetSize(width, height, false);
|
||||
return new Vector2(*width, *height);
|
||||
}
|
||||
|
||||
private Vector2 GetRootSize() {
|
||||
if (addon.RootNode is null) return Vector2.Zero;
|
||||
|
||||
return new Vector2(addon.RootNode->Width, addon.RootNode->Height);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System.Numerics;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics;
|
||||
|
||||
namespace KamiToolKit.Extensions;
|
||||
|
||||
public static class ByteColorExtensions {
|
||||
public static Vector4 ToVector4(this ByteColor color)
|
||||
=> new(color.R / 255.0f, color.G / 255.0f, color.B / 255.0f, color.A / 255.0f);
|
||||
|
||||
public static ByteColor ToByteColor(this Vector4 v)
|
||||
=> new() { A = (byte)(v.W * 255), R = (byte)(v.X * 255), G = (byte)(v.Y * 255), B = (byte)(v.Z * 255) };
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Dalamud.Utility;
|
||||
|
||||
namespace KamiToolKit.Extensions;
|
||||
|
||||
internal static class EnumExtensions {
|
||||
extension(Enum enumValue) {
|
||||
public string Description => enumValue.GetDescription();
|
||||
|
||||
private string GetDescription() {
|
||||
var attribute = enumValue.GetAttribute<DescriptionAttribute>();
|
||||
return attribute?.Description ?? enumValue.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
extension<T>(ref T flagValue) where T : unmanaged, Enum {
|
||||
public void SetFlags(params T[] flags) {
|
||||
foreach (var flag in flags) {
|
||||
flagValue.SetFlag(flag, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearFlags(params T[] flags) {
|
||||
foreach (var flag in flags) {
|
||||
flagValue.SetFlag(flag, false);
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void SetFlag(T flag, bool enable) {
|
||||
switch (sizeof(T)) {
|
||||
case 1: flagValue.SetFlag<T, byte>(flag, enable); break;
|
||||
case 2: flagValue.SetFlag<T, ushort>(flag, enable); break;
|
||||
case 4: flagValue.SetFlag<T, uint>(flag, enable); break;
|
||||
case 8: flagValue.SetFlag<T, ulong>(flag, enable); break;
|
||||
default: throw new NotSupportedException("Unsupported enum size");
|
||||
}
|
||||
}
|
||||
|
||||
private void SetFlag<TUnderlying>(T flag, bool enable) where TUnderlying : unmanaged, IBinaryInteger<TUnderlying> {
|
||||
ref var value = ref Unsafe.As<T, TUnderlying>(ref flagValue);
|
||||
var mask = Unsafe.As<T, TUnderlying>(ref flag);
|
||||
|
||||
if (enable)
|
||||
value |= mask;
|
||||
else
|
||||
value &= ~mask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System.Drawing;
|
||||
using System.Numerics;
|
||||
using Dalamud.Interface;
|
||||
using Vector4 = System.Numerics.Vector4;
|
||||
|
||||
namespace KamiToolKit.Extensions;
|
||||
|
||||
public static class KnownColorExtensions {
|
||||
public static Vector3 Vector3(this KnownColor color) {
|
||||
var color4 = color.Vector();
|
||||
return new Vector3(color4.X, color4.Y, color4.Z);
|
||||
}
|
||||
|
||||
public static Vector3 AsVector3Color(this Vector4 vector4)
|
||||
=> new(vector4.X, vector4.Y, vector4.Z);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using Dalamud.Utility;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Framework;
|
||||
using KamiToolKit.Classes;
|
||||
|
||||
namespace KamiToolKit.Extensions;
|
||||
|
||||
public static unsafe class MainThreadSafety {
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if <em>not</em> on the main thread. Use this to return early.
|
||||
/// </summary>
|
||||
public static bool TryAssertMainThread([CallerFilePath] string? callerFilePath = null, [CallerMemberName] string? callerName = null) {
|
||||
if (Framework.Instance()->IsDestroying) return true;
|
||||
|
||||
if (!ThreadSafety.IsMainThread) {
|
||||
Log.Error($"{callerFilePath?.Split(@"\")[^1][..^2]}{callerName} must be invoked from the main thread.");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace KamiToolKit.Extensions;
|
||||
|
||||
public static class ReadOnlySpanExtensions {
|
||||
extension(ReadOnlySpan<byte> span) {
|
||||
public string String => Encoding.UTF8.GetString(span);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System.Diagnostics;
|
||||
using KamiToolKit.Classes;
|
||||
|
||||
namespace KamiToolKit.Extensions;
|
||||
|
||||
public static class StopwatchExtensions {
|
||||
extension(Stopwatch stopwatch) {
|
||||
public void LogTime(string logMessage) {
|
||||
DalamudInterface.Instance.Log.Debug($"{logMessage, -15}: {stopwatch, 15} :: {stopwatch.ElapsedMilliseconds} ms");
|
||||
stopwatch.Restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user