Files
AetherBags/KamiToolKit/Timelines/Timeline.cs
T
KnackAtNite 8db4ce6094
Debug Build and Test / Build against Latest Dalamud (push) Has been cancelled
Debug Build and Test / Build against Staging Dalamud (push) Has been cancelled
Initial commit: AetherBags + KamiToolKit for FC Gitea
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 14:46:31 -05:00

189 lines
6.6 KiB
C#

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;
}
}