Initial commit: AetherBags + KamiToolKit for FC Gitea
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace KamiToolKit.Timelines;
|
||||
|
||||
public class FrameSetBuilder(TimelineBuilder parent, int startFrameId, int endFrameId) {
|
||||
|
||||
private readonly List<TimelineKeyFrame> animationKeyFrames = [];
|
||||
private readonly List<TimelineKeyFrame> labelKeyFrames = [];
|
||||
|
||||
public FrameSetBuilder AddFrame(params TimelineKeyFrame[] keyFrame) {
|
||||
foreach (var frame in keyFrame) {
|
||||
|
||||
switch (frame.GroupType) {
|
||||
case AtkTimelineKeyGroupType.Label:
|
||||
labelKeyFrames.Add(frame);
|
||||
break;
|
||||
|
||||
case AtkTimelineKeyGroupType.Float2:
|
||||
case AtkTimelineKeyGroupType.Float:
|
||||
case AtkTimelineKeyGroupType.Byte:
|
||||
case AtkTimelineKeyGroupType.NodeTint:
|
||||
case AtkTimelineKeyGroupType.UShort:
|
||||
case AtkTimelineKeyGroupType.RGB:
|
||||
case AtkTimelineKeyGroupType.Short:
|
||||
case AtkTimelineKeyGroupType.None:
|
||||
default:
|
||||
animationKeyFrames.Add(frame);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public FrameSetBuilder AddEmptyFrame(int frameId) {
|
||||
animationKeyFrames.Add(new TimelineAnimationKeyFrame {
|
||||
FrameIndex = frameId, GroupType = AtkTimelineKeyGroupType.None,
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public FrameSetBuilder AddFrame(
|
||||
int frameId, Vector2? position = null, byte? alpha = null, Vector3? addColor = null, Vector3? multiplyColor = null,
|
||||
float? rotation = null, Vector2? scale = null, Vector3? textColor = null, Vector3? textOutlineColor = null, uint? partId = null, AtkTimelineInterpolation? interpolation = null,
|
||||
float? rotationDegrees = null) {
|
||||
if (position is not null) {
|
||||
animationKeyFrames.Add(new TimelineAnimationKeyFrame {
|
||||
FrameIndex = frameId, Position = position.Value, Interpolation = interpolation ?? AtkTimelineInterpolation.Linear,
|
||||
});
|
||||
}
|
||||
|
||||
if (alpha is not null) {
|
||||
animationKeyFrames.Add(new TimelineAnimationKeyFrame {
|
||||
FrameIndex = frameId, Alpha = alpha.Value, Interpolation = interpolation ?? AtkTimelineInterpolation.Linear,
|
||||
});
|
||||
}
|
||||
|
||||
if (addColor is not null || multiplyColor is not null) {
|
||||
animationKeyFrames.Add(new TimelineAnimationKeyFrame {
|
||||
FrameIndex = frameId, AddColor = addColor ?? new Vector3(0.0f, 0.0f, 0.0f), MultiplyColor = multiplyColor ?? new Vector3(100.0f, 100.0f, 100.0f), Interpolation = interpolation ?? AtkTimelineInterpolation.Linear,
|
||||
});
|
||||
}
|
||||
|
||||
if (rotation is not null) {
|
||||
animationKeyFrames.Add(new TimelineAnimationKeyFrame {
|
||||
FrameIndex = frameId, Rotation = rotation.Value, Interpolation = interpolation ?? AtkTimelineInterpolation.Linear,
|
||||
});
|
||||
}
|
||||
|
||||
if (rotationDegrees is not null) {
|
||||
animationKeyFrames.Add(new TimelineAnimationKeyFrame {
|
||||
FrameIndex = frameId, Rotation = rotationDegrees.Value * MathF.PI / 180.0f, Interpolation = interpolation ?? AtkTimelineInterpolation.Linear,
|
||||
});
|
||||
}
|
||||
|
||||
if (scale is not null) {
|
||||
animationKeyFrames.Add(new TimelineAnimationKeyFrame {
|
||||
FrameIndex = frameId, Scale = scale.Value, Interpolation = interpolation ?? AtkTimelineInterpolation.Linear,
|
||||
});
|
||||
}
|
||||
|
||||
if (textColor is not null) {
|
||||
animationKeyFrames.Add(new TimelineAnimationKeyFrame {
|
||||
FrameIndex = frameId, TextColor = textColor.Value, Interpolation = interpolation ?? AtkTimelineInterpolation.Linear,
|
||||
});
|
||||
}
|
||||
|
||||
if (textOutlineColor is not null) {
|
||||
animationKeyFrames.Add(new TimelineAnimationKeyFrame {
|
||||
FrameIndex = frameId, TextEdgeColor = textOutlineColor.Value, Interpolation = interpolation ?? AtkTimelineInterpolation.Linear,
|
||||
});
|
||||
}
|
||||
|
||||
if (partId is not null) {
|
||||
animationKeyFrames.Add(new TimelineAnimationKeyFrame {
|
||||
FrameIndex = frameId, PartId = partId.Value, Interpolation = interpolation ?? AtkTimelineInterpolation.Linear,
|
||||
});
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public FrameSetBuilder AddLabel(int frameId, int labelId, AtkTimelineJumpBehavior jumpBehavior, int labelTarget) {
|
||||
labelKeyFrames.Add(new TimelineLabelSetKeyFrame {
|
||||
FrameIndex = frameId,
|
||||
GroupType = AtkTimelineKeyGroupType.Label,
|
||||
JumpBehavior = jumpBehavior,
|
||||
LabelId = labelId,
|
||||
JumpLabelId = labelTarget,
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public FrameSetBuilder AddLabelPair(int frameStart, int frameStop, int labelId) {
|
||||
labelKeyFrames.Add(new TimelineLabelSetKeyFrame {
|
||||
FrameIndex = frameStart,
|
||||
GroupType = AtkTimelineKeyGroupType.Label,
|
||||
JumpBehavior = AtkTimelineJumpBehavior.Start,
|
||||
LabelId = labelId,
|
||||
});
|
||||
|
||||
labelKeyFrames.Add(new TimelineLabelSetKeyFrame {
|
||||
FrameIndex = frameStop,
|
||||
GroupType = AtkTimelineKeyGroupType.Label,
|
||||
JumpBehavior = AtkTimelineJumpBehavior.PlayOnce,
|
||||
LabelId = 0,
|
||||
JumpLabelId = 0,
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public KeyFrameBuilder BeginFrameBuilder(int frame)
|
||||
=> new(this, frame);
|
||||
|
||||
public TimelineBuilder EndFrameSet() {
|
||||
if (labelKeyFrames.Count != 0) {
|
||||
parent.LabelSets.Add(new TimelineLabelSet {
|
||||
StartFrameId = startFrameId, EndFrameId = endFrameId, Labels = labelKeyFrames,
|
||||
});
|
||||
}
|
||||
|
||||
if (animationKeyFrames.Count != 0) {
|
||||
parent.Animations.Add(new TimelineAnimation {
|
||||
StartFrameId = startFrameId, EndFrameId = endFrameId, KeyFrames = animationKeyFrames,
|
||||
});
|
||||
}
|
||||
|
||||
return parent;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
using System.Collections.Generic;
|
||||
using FFXIVClientStructs.FFXIV.Common.Math;
|
||||
|
||||
namespace KamiToolKit.Timelines;
|
||||
|
||||
public class KeyFrameBuilder(FrameSetBuilder parent, int frame) {
|
||||
|
||||
private readonly List<TimelineKeyFrame> animationKeyFrames = [];
|
||||
|
||||
public KeyFrameBuilder Position(Vector2 position) {
|
||||
animationKeyFrames.Add(new TimelineAnimationKeyFrame {
|
||||
FrameIndex = frame, Position = position,
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public KeyFrameBuilder Alpha(byte alpha) {
|
||||
animationKeyFrames.Add(new TimelineAnimationKeyFrame {
|
||||
FrameIndex = frame, Alpha = alpha,
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public KeyFrameBuilder AddColor(Vector3 color) {
|
||||
animationKeyFrames.Add(new TimelineAnimationKeyFrame {
|
||||
FrameIndex = frame, AddColor = color,
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public KeyFrameBuilder MultiplyColor(Vector3 color) {
|
||||
animationKeyFrames.Add(new TimelineAnimationKeyFrame {
|
||||
FrameIndex = frame, MultiplyColor = color,
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public KeyFrameBuilder MultiplyColor(float color) {
|
||||
animationKeyFrames.Add(new TimelineAnimationKeyFrame {
|
||||
FrameIndex = frame, MultiplyColor = new Vector3(color, color, color),
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public KeyFrameBuilder Rotation(float rotation) {
|
||||
animationKeyFrames.Add(new TimelineAnimationKeyFrame {
|
||||
FrameIndex = frame, Rotation = rotation,
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public KeyFrameBuilder Scale(Vector2 scale) {
|
||||
animationKeyFrames.Add(new TimelineAnimationKeyFrame {
|
||||
FrameIndex = frame, Scale = scale,
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public KeyFrameBuilder Scale(float scale) {
|
||||
animationKeyFrames.Add(new TimelineAnimationKeyFrame {
|
||||
FrameIndex = frame, Scale = new Vector2(scale, scale),
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public KeyFrameBuilder TextColor(Vector3 textColor) {
|
||||
animationKeyFrames.Add(new TimelineAnimationKeyFrame {
|
||||
FrameIndex = frame, TextColor = textColor,
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public KeyFrameBuilder TextOutlineColor(Vector3 textColor) {
|
||||
animationKeyFrames.Add(new TimelineAnimationKeyFrame {
|
||||
FrameIndex = frame, TextEdgeColor = textColor,
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public KeyFrameBuilder Part(uint partId) {
|
||||
animationKeyFrames.Add(new TimelineAnimationKeyFrame {
|
||||
FrameIndex = frame, PartId = partId,
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public FrameSetBuilder EndFrameBuilder() {
|
||||
parent.AddFrame(animationKeyFrames.ToArray());
|
||||
return parent;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using System.Numerics;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace KamiToolKit.Timelines;
|
||||
|
||||
public class NodeTint {
|
||||
|
||||
public Vector3 AddColor;
|
||||
|
||||
public Vector3 MultiplyColor;
|
||||
|
||||
public static implicit operator AtkTimelineNodeTint(NodeTint tint) => new() {
|
||||
MultiplyRGB = new ByteColor {
|
||||
R = (byte)tint.MultiplyColor.X, G = (byte)tint.MultiplyColor.Y, B = (byte)tint.MultiplyColor.Z,
|
||||
},
|
||||
AddRGBBitfield = Convert(tint.AddColor),
|
||||
};
|
||||
|
||||
public static implicit operator NodeTint(AtkTimelineNodeTint tint) => new() {
|
||||
AddColor = new Vector3(tint.AddR, tint.AddG, tint.AddB), MultiplyColor = tint.MultiplyRGB.ToVector4().AsVector3(),
|
||||
};
|
||||
|
||||
private static uint Convert(Vector3 color) {
|
||||
var red = (short)(color.X + 255);
|
||||
var green = (short)(color.Y + 255);
|
||||
var blue = (short)(color.Z + 255);
|
||||
|
||||
return (uint)(red & 0x3FF | (green & 0xFFF) << 10 | (blue & 0x3FF) << 22);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using FFXIVClientStructs.Interop;
|
||||
using KamiToolKit.Classes;
|
||||
|
||||
namespace KamiToolKit.Timelines;
|
||||
|
||||
public unsafe class Timeline : IDisposable {
|
||||
|
||||
private readonly TimelineResource internalTimelineResource;
|
||||
|
||||
internal AtkTimeline* InternalTimeline;
|
||||
|
||||
public Timeline() {
|
||||
InternalTimeline = NativeMemoryHelper.UiAlloc<AtkTimeline>();
|
||||
|
||||
internalTimelineResource = new TimelineResource();
|
||||
InternalTimeline->Resource = internalTimelineResource.InternalResource;
|
||||
InternalTimeline->LabelResource = null;
|
||||
InternalTimeline->ActiveAnimation = null;
|
||||
InternalTimeline->OwnerNode = null;
|
||||
}
|
||||
|
||||
internal AtkResNode* OwnerNode {
|
||||
get => InternalTimeline->OwnerNode;
|
||||
set => InternalTimeline->OwnerNode = value;
|
||||
}
|
||||
|
||||
public float FrameTime {
|
||||
get => InternalTimeline->FrameTime;
|
||||
set => InternalTimeline->FrameTime = value;
|
||||
}
|
||||
|
||||
public float ParentFrameTime {
|
||||
get => InternalTimeline->ParentFrameTime;
|
||||
set => InternalTimeline->ParentFrameTime = value;
|
||||
}
|
||||
|
||||
public int LabelFrameIdxDuration {
|
||||
get => InternalTimeline->LabelFrameIdxDuration;
|
||||
set => InternalTimeline->LabelFrameIdxDuration = (ushort)value;
|
||||
}
|
||||
|
||||
public int LabelEndFrameIdx {
|
||||
get => InternalTimeline->LabelEndFrameIdx;
|
||||
set => InternalTimeline->LabelEndFrameIdx = (ushort)value;
|
||||
}
|
||||
|
||||
public int ActiveLabelId {
|
||||
get => InternalTimeline->ActiveLabelId;
|
||||
set => InternalTimeline->ActiveLabelId = (ushort)value;
|
||||
}
|
||||
|
||||
public AtkTimelineMask Mask {
|
||||
get => InternalTimeline->Mask;
|
||||
set => InternalTimeline->Mask = value;
|
||||
}
|
||||
|
||||
public AtkTimelineFlags Flags {
|
||||
get => InternalTimeline->Flags;
|
||||
set => InternalTimeline->Flags = value;
|
||||
}
|
||||
|
||||
public List<TimelineAnimation> Animations {
|
||||
set => internalTimelineResource.Animations = value;
|
||||
}
|
||||
|
||||
public List<TimelineLabelSet> LabelSets {
|
||||
set => internalTimelineResource.LabelSets = value;
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
internalTimelineResource.Dispose();
|
||||
|
||||
NativeMemoryHelper.UiFree(InternalTimeline);
|
||||
InternalTimeline = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plays the specified animation via label ID
|
||||
/// </summary>
|
||||
/// <param name="labelId">The label ID to play</param>
|
||||
/// <param name="force">Force the animation to restart even if it was already playing</param>
|
||||
public void PlayAnimation(int labelId, bool force = false)
|
||||
=> PlayAnimation(AtkTimelineJumpBehavior.Start, labelId, force);
|
||||
|
||||
public void PlayAnimation(AtkTimelineJumpBehavior behavior, int labelId, bool force = false) {
|
||||
if (InternalTimeline is null) return;
|
||||
|
||||
if (InternalTimeline->ActiveLabelId != labelId || force) {
|
||||
InternalTimeline->PlayAnimation(behavior, (ushort)labelId);
|
||||
}
|
||||
}
|
||||
|
||||
public void StopAnimation() {
|
||||
if (InternalTimeline is null) return;
|
||||
|
||||
InternalTimeline->PlayAnimation(AtkTimelineJumpBehavior.Start, 0);
|
||||
}
|
||||
|
||||
public void UpdateKeyFrame(
|
||||
int frameId, KeyFrameGroupType groupType, Vector2? position = null, byte? alpha = null, Vector3? addColor = null, Vector3? multiplyColor = null,
|
||||
float? rotation = null, Vector2? scale = null, Vector3? textColor = null, Vector3? textOutlineColor = null, uint? partId = null, AtkTimelineInterpolation? interpolation = null) {
|
||||
|
||||
var keyFrame = GetKeyFrame(groupType, frameId);
|
||||
if (keyFrame is null) return;
|
||||
|
||||
if (position is not null) {
|
||||
*keyFrame = new TimelineAnimationKeyFrame {
|
||||
FrameIndex = frameId, Position = position.Value, Interpolation = interpolation ?? AtkTimelineInterpolation.Linear,
|
||||
};
|
||||
}
|
||||
|
||||
if (alpha is not null) {
|
||||
*keyFrame = new TimelineAnimationKeyFrame {
|
||||
FrameIndex = frameId, Alpha = alpha.Value, Interpolation = interpolation ?? AtkTimelineInterpolation.Linear,
|
||||
};
|
||||
}
|
||||
|
||||
if (addColor is not null || multiplyColor is not null) {
|
||||
*keyFrame = new TimelineAnimationKeyFrame {
|
||||
FrameIndex = frameId, AddColor = addColor ?? new Vector3(0.0f, 0.0f, 0.0f), MultiplyColor = multiplyColor ?? new Vector3(100.0f, 100.0f, 100.0f), Interpolation = interpolation ?? AtkTimelineInterpolation.Linear,
|
||||
};
|
||||
}
|
||||
|
||||
if (rotation is not null) {
|
||||
*keyFrame = new TimelineAnimationKeyFrame {
|
||||
FrameIndex = frameId, Rotation = rotation.Value, Interpolation = interpolation ?? AtkTimelineInterpolation.Linear,
|
||||
};
|
||||
}
|
||||
|
||||
if (scale is not null) {
|
||||
*keyFrame = new TimelineAnimationKeyFrame {
|
||||
FrameIndex = frameId, Scale = scale.Value, Interpolation = interpolation ?? AtkTimelineInterpolation.Linear,
|
||||
};
|
||||
}
|
||||
|
||||
if (textColor is not null) {
|
||||
*keyFrame = new TimelineAnimationKeyFrame {
|
||||
FrameIndex = frameId, TextColor = textColor.Value, Interpolation = interpolation ?? AtkTimelineInterpolation.Linear,
|
||||
};
|
||||
}
|
||||
|
||||
if (textOutlineColor is not null) {
|
||||
*keyFrame = new TimelineAnimationKeyFrame {
|
||||
FrameIndex = frameId, TextEdgeColor = textOutlineColor.Value, Interpolation = interpolation ?? AtkTimelineInterpolation.Linear,
|
||||
};
|
||||
}
|
||||
|
||||
if (partId is not null) {
|
||||
*keyFrame = new TimelineAnimationKeyFrame {
|
||||
FrameIndex = frameId, PartId = partId.Value, Interpolation = interpolation ?? AtkTimelineInterpolation.Linear,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private AtkTimelineKeyFrame* GetKeyFrame(KeyFrameGroupType type, int frameIndex) {
|
||||
var animation = GetAnimationForFrameId(frameIndex);
|
||||
if (animation is null) return null;
|
||||
|
||||
var keyGroup = animation->KeyGroups.GetPointer((int)type);
|
||||
for (var i = 0; i < keyGroup->KeyFrameCount; i++) {
|
||||
var keyFrame = &keyGroup->KeyFrames[i];
|
||||
|
||||
if (keyFrame->FrameIdx == frameIndex) {
|
||||
return keyFrame;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private AtkTimelineAnimation* GetAnimationForFrameId(int frameId) {
|
||||
if (InternalTimeline is null) return null;
|
||||
if (InternalTimeline->Resource is null) return null;
|
||||
|
||||
for (var index = 0; index < InternalTimeline->Resource->AnimationCount; index++) {
|
||||
var animation = &InternalTimeline->Resource->Animations[index];
|
||||
|
||||
if (animation->StartFrameIdx <= frameId && frameId <= animation->EndFrameIdx)
|
||||
return animation;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using KamiToolKit.Classes;
|
||||
|
||||
namespace KamiToolKit.Timelines;
|
||||
|
||||
public unsafe class TimelineAnimation : IDisposable {
|
||||
|
||||
internal AtkTimelineAnimation* InternalAnimation;
|
||||
|
||||
private List<TimelineKeyFrame> internalKeyFrames = [];
|
||||
|
||||
public TimelineAnimation() {
|
||||
InternalAnimation = NativeMemoryHelper.UiAlloc<AtkTimelineAnimation>();
|
||||
|
||||
InternalAnimation->StartFrameIdx = 0;
|
||||
InternalAnimation->EndFrameIdx = 0;
|
||||
|
||||
foreach (ref var value in InternalAnimation->KeyGroups) {
|
||||
value.Type = AtkTimelineKeyGroupType.None;
|
||||
}
|
||||
}
|
||||
|
||||
public int StartFrameId {
|
||||
get => InternalAnimation->StartFrameIdx;
|
||||
set => InternalAnimation->StartFrameIdx = (ushort)value;
|
||||
}
|
||||
|
||||
public int EndFrameId {
|
||||
get => InternalAnimation->EndFrameIdx;
|
||||
set => InternalAnimation->EndFrameIdx = (ushort)value;
|
||||
}
|
||||
|
||||
public List<TimelineKeyFrame> KeyFrames {
|
||||
get => internalKeyFrames;
|
||||
set {
|
||||
internalKeyFrames = value;
|
||||
Resync();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
if (InternalAnimation is null) return;
|
||||
|
||||
foreach (ref var spanGroup in InternalAnimation->KeyGroups) {
|
||||
NativeMemoryHelper.UiFree(spanGroup.KeyFrames);
|
||||
spanGroup.KeyFrames = null;
|
||||
spanGroup.KeyFrameCount = 0;
|
||||
}
|
||||
|
||||
NativeMemoryHelper.UiFree(InternalAnimation);
|
||||
InternalAnimation = null;
|
||||
}
|
||||
|
||||
private void Resync() {
|
||||
foreach (var keyFrameSet in internalKeyFrames.GroupBy(frame => frame.GroupSelector)) {
|
||||
ref var keyFrameGroup = ref InternalAnimation->KeyGroups[(int)keyFrameSet.Key];
|
||||
keyFrameGroup.Type = keyFrameSet.First().GroupType;
|
||||
|
||||
if (keyFrameGroup.KeyFrames is not null) {
|
||||
NativeMemoryHelper.UiFree(keyFrameGroup.KeyFrames, keyFrameGroup.KeyFrameCount);
|
||||
keyFrameGroup.KeyFrames = null;
|
||||
}
|
||||
|
||||
keyFrameGroup.KeyFrames = NativeMemoryHelper.UiAlloc<AtkTimelineKeyFrame>(keyFrameSet.Count());
|
||||
|
||||
var index = 0;
|
||||
foreach (var keyframe in keyFrameSet) {
|
||||
keyFrameGroup.KeyFrames[index] = keyframe;
|
||||
index++;
|
||||
}
|
||||
|
||||
keyFrameGroup.KeyFrameCount = (ushort)keyFrameSet.Count();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum KeyFrameGroupType {
|
||||
Position = 0,
|
||||
Rotation = 1,
|
||||
Scale = 2,
|
||||
Alpha = 3,
|
||||
Tint = 4,
|
||||
|
||||
PartId = 5,
|
||||
TextColor = 5,
|
||||
|
||||
TextEdge = 6,
|
||||
TextLabel = 7,
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using KamiToolKit.Classes;
|
||||
|
||||
namespace KamiToolKit.Timelines;
|
||||
|
||||
public unsafe class TimelineAnimationArray : IDisposable {
|
||||
|
||||
internal AtkTimelineAnimation* InternalTimelineArray = null;
|
||||
|
||||
private List<TimelineAnimation> timelineAnimations = [];
|
||||
public uint Count { get; private set; }
|
||||
|
||||
public List<TimelineAnimation> Animations {
|
||||
get => timelineAnimations;
|
||||
set {
|
||||
timelineAnimations = value;
|
||||
Resync();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
foreach (var animation in timelineAnimations) {
|
||||
animation.Dispose();
|
||||
}
|
||||
|
||||
NativeMemoryHelper.UiFree(InternalTimelineArray, Count);
|
||||
InternalTimelineArray = null;
|
||||
}
|
||||
|
||||
private void Resync() {
|
||||
// Free existing array, we will completely rebuild it
|
||||
if (InternalTimelineArray is not null) {
|
||||
NativeMemoryHelper.UiFree(InternalTimelineArray, Count);
|
||||
InternalTimelineArray = null;
|
||||
}
|
||||
|
||||
// Allocate new array
|
||||
InternalTimelineArray = NativeMemoryHelper.UiAlloc<AtkTimelineAnimation>(timelineAnimations.Count);
|
||||
|
||||
// Copy all Animations into it
|
||||
foreach (var index in Enumerable.Range(0, timelineAnimations.Count)) {
|
||||
InternalTimelineArray[index] = *timelineAnimations[index].InternalAnimation;
|
||||
}
|
||||
|
||||
Count = (uint)timelineAnimations.Count;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
using System.Numerics;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using FFXIVClientStructs.STD;
|
||||
|
||||
namespace KamiToolKit.Timelines;
|
||||
|
||||
public class TimelineAnimationKeyFrame : TimelineKeyFrame {
|
||||
|
||||
private readonly NodeTint nodeTint = new();
|
||||
|
||||
public Vector2 Position {
|
||||
get => new(Value.Float2.Item1, Value.Float2.Item2);
|
||||
set {
|
||||
Value = new AtkTimelineKeyValue {
|
||||
Float2 = new StdPair<float, float>(value.X, value.Y),
|
||||
};
|
||||
|
||||
GroupSelector = KeyFrameGroupType.Position;
|
||||
GroupType = AtkTimelineKeyGroupType.Float2;
|
||||
}
|
||||
}
|
||||
|
||||
public byte Alpha {
|
||||
get => Value.Byte;
|
||||
set {
|
||||
Value = new AtkTimelineKeyValue {
|
||||
Byte = value,
|
||||
};
|
||||
|
||||
GroupType = AtkTimelineKeyGroupType.Byte;
|
||||
GroupSelector = KeyFrameGroupType.Alpha;
|
||||
}
|
||||
}
|
||||
|
||||
public Vector3 AddColor {
|
||||
set {
|
||||
nodeTint.AddColor = value;
|
||||
UpdateNodeTint();
|
||||
}
|
||||
}
|
||||
|
||||
public Vector3 MultiplyColor {
|
||||
set {
|
||||
nodeTint.MultiplyColor = value;
|
||||
UpdateNodeTint();
|
||||
}
|
||||
}
|
||||
|
||||
public float Rotation {
|
||||
get => Value.Float;
|
||||
set {
|
||||
Value = new AtkTimelineKeyValue {
|
||||
Float = value,
|
||||
};
|
||||
|
||||
GroupType = AtkTimelineKeyGroupType.Float;
|
||||
GroupSelector = KeyFrameGroupType.Rotation;
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 Scale {
|
||||
get => new(Value.Float2.Item1, Value.Float2.Item2);
|
||||
set {
|
||||
Value = new AtkTimelineKeyValue {
|
||||
Float2 = new StdPair<float, float>(value.X, value.Y),
|
||||
};
|
||||
|
||||
GroupType = AtkTimelineKeyGroupType.Float2;
|
||||
GroupSelector = KeyFrameGroupType.Scale;
|
||||
}
|
||||
}
|
||||
|
||||
public Vector3 TextColor {
|
||||
get => new Vector3(Value.RGB.R, Value.RGB.G, Value.RGB.B) * 255.0f;
|
||||
set {
|
||||
Value = new AtkTimelineKeyValue {
|
||||
RGB = value.AsVector4().ToByteColor(),
|
||||
};
|
||||
|
||||
GroupType = AtkTimelineKeyGroupType.RGB;
|
||||
GroupSelector = KeyFrameGroupType.TextColor;
|
||||
}
|
||||
}
|
||||
|
||||
public Vector3 TextEdgeColor {
|
||||
get => new Vector3(Value.RGB.R, Value.RGB.G, Value.RGB.B) * 255.0f;
|
||||
set {
|
||||
Value = new AtkTimelineKeyValue {
|
||||
RGB = value.AsVector4().ToByteColor(),
|
||||
};
|
||||
|
||||
GroupType = AtkTimelineKeyGroupType.RGB;
|
||||
GroupSelector = KeyFrameGroupType.TextEdge;
|
||||
}
|
||||
}
|
||||
|
||||
public uint PartId {
|
||||
set {
|
||||
Value = new AtkTimelineKeyValue {
|
||||
UShort = (ushort)value,
|
||||
};
|
||||
|
||||
GroupType = AtkTimelineKeyGroupType.UShort;
|
||||
GroupSelector = KeyFrameGroupType.PartId;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateNodeTint() {
|
||||
Value = new AtkTimelineKeyValue {
|
||||
NodeTint = nodeTint,
|
||||
};
|
||||
|
||||
GroupType = AtkTimelineKeyGroupType.NodeTint;
|
||||
GroupSelector = KeyFrameGroupType.Tint;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
namespace KamiToolKit.Timelines;
|
||||
|
||||
public class TimelineBuilder {
|
||||
|
||||
internal List<TimelineAnimation> Animations = [];
|
||||
internal List<TimelineLabelSet> LabelSets = [];
|
||||
|
||||
public FrameSetBuilder BeginFrameSet(int startFrameId, int endFrameId)
|
||||
=> new(this, startFrameId, endFrameId);
|
||||
|
||||
public TimelineBuilder AddFrameSetWithFrame(
|
||||
int startFrameId, int endFrameId, int frameId, Vector2? position = null, byte? alpha = null, Vector3? addColor = null, Vector3? multiplyColor = null,
|
||||
float? rotation = null, Vector2? scale = null, Vector3? textColor = null, Vector3? textOutlineColor = null, uint? partId = null) {
|
||||
|
||||
new FrameSetBuilder(this, startFrameId, endFrameId)
|
||||
.AddFrame(frameId, position, alpha, addColor, multiplyColor, rotation, scale, textColor, textOutlineColor, partId)
|
||||
.EndFrameSet();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public KeyFrameBuilder AddFrame(int frameSetStart, int frameSetEnd, int frameIndex)
|
||||
=> new(new FrameSetBuilder(this, frameSetStart, frameSetEnd), frameIndex);
|
||||
|
||||
public Timeline Build() {
|
||||
var newTimeline = new Timeline();
|
||||
|
||||
if (LabelSets.Count != 0) {
|
||||
newTimeline.LabelSets = LabelSets;
|
||||
newTimeline.LabelFrameIdxDuration = LabelSets.Max(label => label.EndFrameId) - 1;
|
||||
newTimeline.LabelEndFrameIdx = LabelSets.Max(label => label.EndFrameId);
|
||||
}
|
||||
|
||||
if (Animations.Count != 0) {
|
||||
newTimeline.Animations = Animations;
|
||||
}
|
||||
|
||||
return newTimeline;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace KamiToolKit.Timelines;
|
||||
|
||||
public abstract class TimelineKeyFrame {
|
||||
|
||||
public KeyFrameGroupType GroupSelector { get; set; }
|
||||
public AtkTimelineKeyGroupType GroupType { get; set; }
|
||||
|
||||
public float SpeedStart { get; set; } = 0.0f;
|
||||
public float SpeedEnd { get; set; } = 1.0f;
|
||||
public required int FrameIndex { get; set; }
|
||||
public AtkTimelineInterpolation Interpolation { get; set; } = AtkTimelineInterpolation.Linear;
|
||||
public AtkTimelineKeyValue Value { get; set; }
|
||||
|
||||
public static implicit operator AtkTimelineKeyFrame(TimelineKeyFrame frame) => new() {
|
||||
Interpolation = frame.Interpolation,
|
||||
SpeedCoefficient1 = frame.SpeedStart,
|
||||
SpeedCoefficient2 = frame.SpeedEnd,
|
||||
FrameIdx = (ushort)frame.FrameIndex,
|
||||
Value = frame.Value,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using KamiToolKit.Classes;
|
||||
|
||||
namespace KamiToolKit.Timelines;
|
||||
|
||||
public unsafe class TimelineLabelSet : IDisposable {
|
||||
|
||||
private List<TimelineKeyFrame> internalKeyFrames = [];
|
||||
|
||||
internal AtkTimelineLabelSet* InternalLabelSet;
|
||||
|
||||
public TimelineLabelSet() {
|
||||
InternalLabelSet = NativeMemoryHelper.UiAlloc<AtkTimelineLabelSet>();
|
||||
|
||||
InternalLabelSet->StartFrameIdx = 0;
|
||||
InternalLabelSet->EndFrameIdx = 0;
|
||||
InternalLabelSet->LabelKeyGroup.Type = AtkTimelineKeyGroupType.Label;
|
||||
}
|
||||
|
||||
public int StartFrameId {
|
||||
get => InternalLabelSet->StartFrameIdx;
|
||||
set => InternalLabelSet->StartFrameIdx = (ushort)value;
|
||||
}
|
||||
|
||||
public int EndFrameId {
|
||||
get => InternalLabelSet->EndFrameIdx;
|
||||
set => InternalLabelSet->EndFrameIdx = (ushort)value;
|
||||
}
|
||||
|
||||
public List<TimelineKeyFrame> Labels {
|
||||
get => internalKeyFrames;
|
||||
set {
|
||||
internalKeyFrames = value;
|
||||
Resync();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
NativeMemoryHelper.UiFree(InternalLabelSet);
|
||||
InternalLabelSet = null;
|
||||
}
|
||||
|
||||
private void Resync() {
|
||||
ref var keyGroup = ref InternalLabelSet->LabelKeyGroup;
|
||||
|
||||
// Free existing array, we will completely rebuild it
|
||||
if (keyGroup.KeyFrames is null) {
|
||||
NativeMemoryHelper.UiFree(keyGroup.KeyFrames, keyGroup.KeyFrameCount);
|
||||
keyGroup.KeyFrames = null;
|
||||
}
|
||||
|
||||
// Allocate new array
|
||||
keyGroup.KeyFrames = NativeMemoryHelper.UiAlloc<AtkTimelineKeyFrame>(internalKeyFrames.Count);
|
||||
|
||||
var index = 0;
|
||||
foreach (var keyFrame in internalKeyFrames) {
|
||||
keyGroup.KeyFrames[index] = keyFrame;
|
||||
index++;
|
||||
}
|
||||
|
||||
keyGroup.KeyFrameCount = (ushort)internalKeyFrames.Count;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using KamiToolKit.Classes;
|
||||
|
||||
namespace KamiToolKit.Timelines;
|
||||
|
||||
public unsafe class TimelineLabelSetArray : IDisposable {
|
||||
|
||||
internal AtkTimelineLabelSet* InternalLabelSetArray = null;
|
||||
|
||||
private List<TimelineLabelSet> labelSets = [];
|
||||
|
||||
public uint Count { get; private set; }
|
||||
|
||||
public List<TimelineLabelSet> LabelSets {
|
||||
get => labelSets;
|
||||
set {
|
||||
labelSets = value;
|
||||
Resync();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
foreach (var labelSet in labelSets) {
|
||||
labelSet.Dispose();
|
||||
}
|
||||
|
||||
NativeMemoryHelper.UiFree(InternalLabelSetArray, Count);
|
||||
InternalLabelSetArray = null;
|
||||
}
|
||||
|
||||
private void Resync() {
|
||||
// Free existing array, we will completely rebuild it
|
||||
if (InternalLabelSetArray is not null) {
|
||||
NativeMemoryHelper.UiFree(InternalLabelSetArray, Count);
|
||||
InternalLabelSetArray = null;
|
||||
}
|
||||
|
||||
// Allocate new array
|
||||
InternalLabelSetArray = NativeMemoryHelper.UiAlloc<AtkTimelineLabelSet>(labelSets.Count);
|
||||
|
||||
// Copy all Animations into it
|
||||
foreach (var index in Enumerable.Range(0, labelSets.Count)) {
|
||||
InternalLabelSetArray[index] = *labelSets[index].InternalLabelSet;
|
||||
}
|
||||
|
||||
Count = (uint)labelSets.Count;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
namespace KamiToolKit.Timelines;
|
||||
|
||||
public class TimelineLabelSetKeyFrame : TimelineKeyFrame {
|
||||
|
||||
private AtkTimelineLabel data;
|
||||
|
||||
public AtkTimelineJumpBehavior JumpBehavior {
|
||||
get => data.JumpBehavior;
|
||||
set {
|
||||
data.JumpBehavior = value;
|
||||
UpdateValue();
|
||||
}
|
||||
}
|
||||
|
||||
public int LabelId {
|
||||
get => data.LabelId;
|
||||
set {
|
||||
data.LabelId = (ushort)value;
|
||||
UpdateValue();
|
||||
}
|
||||
}
|
||||
|
||||
public int JumpLabelId {
|
||||
get => data.JumpLabelId;
|
||||
set {
|
||||
data.JumpLabelId = (byte)value;
|
||||
UpdateValue();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateValue() {
|
||||
Value = new AtkTimelineKeyValue {
|
||||
Label = data,
|
||||
};
|
||||
|
||||
GroupType = AtkTimelineKeyGroupType.Label;
|
||||
SpeedEnd = 0.0f;
|
||||
Interpolation = AtkTimelineInterpolation.None;
|
||||
GroupSelector = KeyFrameGroupType.TextLabel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using KamiToolKit.Classes;
|
||||
|
||||
namespace KamiToolKit.Timelines;
|
||||
|
||||
public unsafe class TimelineResource : IDisposable {
|
||||
|
||||
private readonly TimelineAnimationArray animationArray;
|
||||
private readonly TimelineLabelSetArray labelsArray;
|
||||
|
||||
internal AtkTimelineResource* InternalResource;
|
||||
|
||||
public TimelineResource() {
|
||||
InternalResource = NativeMemoryHelper.UiAlloc<AtkTimelineResource>();
|
||||
|
||||
InternalResource->Id = 2;
|
||||
InternalResource->AnimationCount = 0;
|
||||
InternalResource->LabelSetCount = 0;
|
||||
|
||||
animationArray = new TimelineAnimationArray();
|
||||
InternalResource->Animations = animationArray.InternalTimelineArray;
|
||||
|
||||
labelsArray = new TimelineLabelSetArray();
|
||||
InternalResource->LabelSets = labelsArray.InternalLabelSetArray;
|
||||
}
|
||||
|
||||
public List<TimelineAnimation> Animations {
|
||||
get => animationArray.Animations;
|
||||
set {
|
||||
animationArray.Animations = value;
|
||||
InternalResource->Animations = animationArray.InternalTimelineArray;
|
||||
InternalResource->AnimationCount = (ushort)animationArray.Count;
|
||||
}
|
||||
}
|
||||
|
||||
public List<TimelineLabelSet> LabelSets {
|
||||
get => labelsArray.LabelSets;
|
||||
set {
|
||||
labelsArray.LabelSets = value;
|
||||
InternalResource->LabelSets = labelsArray.InternalLabelSetArray;
|
||||
InternalResource->LabelSetCount = (ushort)labelsArray.Count;
|
||||
}
|
||||
}
|
||||
|
||||
public int Id {
|
||||
get => (int)InternalResource->Id;
|
||||
set => InternalResource->Id = (uint)value;
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
animationArray.Dispose();
|
||||
labelsArray.Dispose();
|
||||
|
||||
NativeMemoryHelper.UiFree(InternalResource);
|
||||
InternalResource = null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user