mirror of
https://gitgud.io/AbstractConcept/rimworld-animation-studio.git
synced 2024-08-15 00:43:27 +00:00
354 lines
15 KiB
C#
354 lines
15 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Xml;
|
|
using System.Xml.Serialization;
|
|
using UnityEngine;
|
|
|
|
namespace RimWorldAnimationStudio
|
|
{
|
|
public class PawnAnimationClip
|
|
{
|
|
// Data to/from animationDef
|
|
public string layer = "Pawn";
|
|
[XmlArray("addons"), XmlArrayItem("li")] public List<ActorAddon> addons;
|
|
[XmlAttribute("Class")] public string className = "Rimworld_Animations.PawnAnimationClip";
|
|
[XmlArray("keyframes"), XmlArrayItem("li")] public List<PawnKeyframe> keyframes;
|
|
[XmlArray("tags"), XmlArrayItem("li")] public List<string> tags;
|
|
|
|
// Data serialization control
|
|
public bool ShouldSerializeaddons() { return addons.Where(x => x.Render)?.Any() == true; }
|
|
public bool ShouldSerializekeyframes() { return keyframes.NotNullOrEmpty(); }
|
|
public bool ShouldSerializetags() { return tags.NotNullOrEmpty(); }
|
|
|
|
// Data helper functions
|
|
[XmlIgnore] public string Layer
|
|
{
|
|
get { return layer; }
|
|
set { layer = value; EventsManager.OnPawnAnimationClipChanged(this); }
|
|
}
|
|
|
|
[XmlIgnore] public List<ActorAddon> Addons
|
|
{
|
|
get { return addons.NullOrEmpty() ? addons = new List<ActorAddon>() : addons; }
|
|
set { addons = value.NotNullOrEmpty() ? value : null; }
|
|
}
|
|
|
|
[XmlIgnore] public List<PawnKeyframe> Keyframes
|
|
{
|
|
get { return keyframes.NullOrEmpty() ? keyframes = new List<PawnKeyframe>() : keyframes; }
|
|
set { keyframes = value.NotNullOrEmpty() ? value : null; }
|
|
}
|
|
|
|
[XmlIgnore]
|
|
public List<string> Tags
|
|
{
|
|
get { return tags.NullOrEmpty() ? tags = new List<string>() : tags; }
|
|
set { tags = value.NotNullOrEmpty() ? value : null; }
|
|
}
|
|
|
|
// Local data
|
|
[XmlIgnore] public int duration { get { return Keyframes.Max(x => x.atTick.Value); } }
|
|
|
|
[XmlIgnore] public SimpleCurve GenitalAngle = new SimpleCurve();
|
|
[XmlIgnore] public SimpleCurve BodyAngle = new SimpleCurve();
|
|
[XmlIgnore] public SimpleCurve HeadAngle = new SimpleCurve();
|
|
[XmlIgnore] public SimpleCurve HeadBob = new SimpleCurve();
|
|
[XmlIgnore] public SimpleCurve BodyOffsetX = new SimpleCurve();
|
|
[XmlIgnore] public SimpleCurve BodyOffsetZ = new SimpleCurve();
|
|
[XmlIgnore] public SimpleCurve HeadFacing = new SimpleCurve();
|
|
[XmlIgnore] public SimpleCurve BodyFacing = new SimpleCurve();
|
|
|
|
// Methods
|
|
public void BuildSimpleCurves()
|
|
{
|
|
// Add addon data (if missing)
|
|
foreach (ActorAddonDef actorAddonDef in ActorAddonDefs.allDefs)
|
|
{ AddActorAddon(actorAddonDef); }
|
|
|
|
// Clear simple curve data
|
|
BodyAngle.Clear();
|
|
HeadAngle.Clear();
|
|
BodyOffsetX.Clear();
|
|
BodyOffsetZ.Clear();
|
|
HeadFacing.Clear();
|
|
BodyFacing.Clear();
|
|
HeadBob.Clear();
|
|
GenitalAngle.Clear();
|
|
|
|
foreach (ActorAddon addon in Addons)
|
|
{
|
|
addon.PosX.Clear();
|
|
addon.PosZ.Clear();
|
|
addon.Rotation.Clear();
|
|
}
|
|
|
|
// Start building simple curves
|
|
int keyframePosition = 0;
|
|
int duration = 0;
|
|
|
|
Keyframes[Keyframes.Count - 1].TickDuration = 1;
|
|
|
|
foreach (PawnKeyframe frame in Keyframes)
|
|
{ duration += frame.TickDuration; }
|
|
|
|
for (int i = 0; i < Keyframes.Count; i++)
|
|
{
|
|
PawnKeyframe keyframe = Keyframes[i];
|
|
|
|
if (keyframe.atTick.HasValue)
|
|
{
|
|
if (keyframe.HasValidKeyframeID() == false)
|
|
{ keyframe.GenerateKeyframeID(Workspace.animationDef.AnimationStages[Workspace.StageID].AnimationClips.IndexOf(this)); }
|
|
|
|
BodyAngle.Add((float)keyframe.atTick / (float)duration, keyframe.BodyAngle, true);
|
|
HeadAngle.Add((float)keyframe.atTick / (float)duration, keyframe.HeadAngle, true);
|
|
BodyOffsetX.Add((float)keyframe.atTick / (float)duration, keyframe.BodyOffsetX, true);
|
|
BodyOffsetZ.Add((float)keyframe.atTick / (float)duration, keyframe.BodyOffsetZ, true);
|
|
HeadFacing.Add((float)keyframe.atTick / (float)duration, keyframe.HeadFacing, true);
|
|
BodyFacing.Add((float)keyframe.atTick / (float)duration, keyframe.BodyFacing, true);
|
|
HeadBob.Add((float)keyframe.atTick / (float)duration, keyframe.HeadBob, true);
|
|
GenitalAngle.Add((float)keyframe.atTick / (float)duration, keyframe.GenitalAngle, true);
|
|
|
|
foreach (ActorAddon addon in Addons)
|
|
{
|
|
if (keyframe.AddonKeyframes.Any(x => x.AddonName == addon.AddonName) == false)
|
|
{ keyframe.AddonKeyframes.Add(new AddonKeyframe(addon.AddonName)); }
|
|
|
|
addon.PosX.Add((float)keyframe.atTick / (float)duration, keyframe.GetAddonKeyframe(addon.AddonName).PosX, true);
|
|
addon.PosZ.Add((float)keyframe.atTick / (float)duration, keyframe.GetAddonKeyframe(addon.AddonName).PosZ, true);
|
|
addon.Rotation.Add((float)keyframe.atTick / (float)duration, keyframe.GetAddonKeyframe(addon.AddonName).Rotation, true);
|
|
}
|
|
|
|
if (i + 1 < Keyframes.Count)
|
|
{ Keyframes[i].TickDuration = Keyframes[i + 1].atTick.Value - Keyframes[i].atTick.Value; }
|
|
}
|
|
|
|
else
|
|
{
|
|
BodyAngle.Add((float)keyframePosition / (float)duration, keyframe.BodyAngle, true);
|
|
HeadAngle.Add((float)keyframePosition / (float)duration, keyframe.HeadAngle, true);
|
|
BodyOffsetX.Add((float)keyframePosition / (float)duration, keyframe.BodyOffsetX, true);
|
|
BodyOffsetZ.Add((float)keyframePosition / (float)duration, keyframe.BodyOffsetZ, true);
|
|
HeadFacing.Add((float)keyframePosition / (float)duration, keyframe.HeadFacing, true);
|
|
BodyFacing.Add((float)keyframePosition / (float)duration, keyframe.BodyFacing, true);
|
|
HeadBob.Add((float)keyframePosition / (float)duration, keyframe.HeadBob, true);
|
|
GenitalAngle.Add((float)keyframePosition / (float)duration, keyframe.GenitalAngle, true);
|
|
|
|
foreach (ActorAddon addon in Addons)
|
|
{
|
|
if (keyframe.AddonKeyframes.Any(x => x.AddonName == addon.AddonName) == false)
|
|
{ keyframe.AddonKeyframes.Add(new AddonKeyframe(addon.AddonName)); }
|
|
|
|
addon.PosX.Add((float)keyframePosition / (float)duration, keyframe.GetAddonKeyframe(addon.AddonName).PosX, true);
|
|
addon.PosZ.Add((float)keyframePosition / (float)duration, keyframe.GetAddonKeyframe(addon.AddonName).PosZ, true);
|
|
addon.Rotation.Add((float)keyframePosition / (float)duration, keyframe.GetAddonKeyframe(addon.AddonName).Rotation, true);
|
|
}
|
|
|
|
keyframe.atTick = keyframePosition + Constants.minTick;
|
|
keyframePosition += keyframe.TickDuration;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void AddActorAddon(ActorAddonDef actorAddonDef)
|
|
{
|
|
if (Addons.Any(x => x.AddonName == actorAddonDef.addonName) == false)
|
|
{ Addons.Add(new ActorAddon(actorAddonDef)); }
|
|
|
|
foreach (PawnKeyframe keyframe in Keyframes)
|
|
{
|
|
if (keyframe.AddonKeyframes.Any(x => x.AddonName == actorAddonDef.addonName) == false)
|
|
{ keyframe.AddonKeyframes.Add(new AddonKeyframe(actorAddonDef.addonName)); }
|
|
}
|
|
}
|
|
|
|
public void ShowOrHideActorAddon(string addonName, bool flag)
|
|
{
|
|
ActorAddon addon = GetActorAddon(addonName);
|
|
|
|
if (addon != null)
|
|
{ addon.Render = flag; }
|
|
}
|
|
|
|
public bool IsActorAddonVisible(string addonName)
|
|
{
|
|
ActorAddon addon = GetActorAddon(addonName);
|
|
|
|
if (addon != null)
|
|
{ return addon.Render; }
|
|
|
|
return false;
|
|
}
|
|
|
|
public ActorAddon GetActorAddon(string addonName)
|
|
{
|
|
return Addons.FirstOrDefault(x => x.AddonName == addonName);
|
|
}
|
|
|
|
public int GetOwningActorID()
|
|
{
|
|
if (Workspace.animationDef == null) return -1;
|
|
|
|
return Workspace.GetCurrentAnimationStage().AnimationClips.IndexOf(this);
|
|
}
|
|
|
|
public void AddPawnKeyframe()
|
|
{
|
|
if (Keyframes == null)
|
|
{ Debug.LogWarning("Cannot add pawn keyframe - the AnimationDef is invalid"); return; }
|
|
|
|
if (Keyframes.FirstOrDefault(x => x.atTick == Workspace.StageTick) != null)
|
|
{ Debug.LogWarning("Cannot add pawn keyframe - a keyframe already exists at this tick"); return; }
|
|
|
|
float clipPercent = (float)(Workspace.StageTick % duration) / duration;
|
|
|
|
PawnKeyframe keyframe = new PawnKeyframe();
|
|
keyframe.BodyAngle = BodyAngle.Evaluate(clipPercent);
|
|
keyframe.HeadAngle = HeadAngle.Evaluate(clipPercent);
|
|
keyframe.HeadBob = HeadBob.Evaluate(clipPercent);
|
|
keyframe.BodyOffsetX = BodyOffsetX.Evaluate(clipPercent);
|
|
keyframe.BodyOffsetZ = BodyOffsetZ.Evaluate(clipPercent);
|
|
keyframe.HeadFacing = (int)HeadFacing.Evaluate(clipPercent);
|
|
keyframe.BodyFacing = (int)BodyFacing.Evaluate(clipPercent);
|
|
keyframe.GenitalAngle = GenitalAngle.Evaluate(clipPercent);
|
|
|
|
keyframe.atTick = Workspace.StageTick;
|
|
|
|
PawnKeyframe nextKeyframe = Keyframes.FirstOrDefault(x => x.atTick > Workspace.StageTick);
|
|
|
|
if (nextKeyframe != null)
|
|
{ keyframes.Insert(keyframes.IndexOf(nextKeyframe), keyframe); }
|
|
|
|
else
|
|
{ keyframes.Add(keyframe); }
|
|
|
|
BuildSimpleCurves();
|
|
|
|
EventsManager.OnKeyframeCountChanged(this);
|
|
Workspace.RecordEvent("Keyframe addition");
|
|
}
|
|
|
|
public void CopyPawnKeyframes()
|
|
{
|
|
Workspace.copiedKeyframes.Clear();
|
|
|
|
List<PawnKeyframe> keyframesToClone = Workspace.GetPawnKeyframesByID(Workspace.keyframeID);
|
|
|
|
foreach (PawnKeyframe keyframe in keyframesToClone)
|
|
{ Workspace.copiedKeyframes.Add(keyframe.Copy()); }
|
|
}
|
|
|
|
public void PastePawnKeyframes()
|
|
{
|
|
int originalWindowSize = Workspace.StageWindowSize;
|
|
|
|
List<int> actorsInvolved = Workspace.copiedKeyframes.Select(x => x.actorID)?.ToList();
|
|
actorsInvolved = actorsInvolved?.Distinct()?.ToList();
|
|
|
|
if (actorsInvolved.NullOrEmpty()) { Debug.Log("Cannot paste keyframes - there were no copied keyframes to paste"); return; }
|
|
if (actorsInvolved.Count > 1 && actorsInvolved.Contains(Workspace.ActorID) == false) { Debug.Log("Cannot paste keyframes - keyframes copied across multiple timelines can only be pasted back into these source timelines"); return; }
|
|
|
|
int earliestTick = actorsInvolved.Count == 1 ? Workspace.GetEarliestAtTickInCopiedKeyframes(actorsInvolved[0]) : Workspace.GetEarliestAtTickInCopiedKeyframes(Workspace.ActorID);
|
|
if (earliestTick < 1) { Debug.Log("Unknown error occured during keyframe paste operation"); return; }
|
|
|
|
foreach (PawnKeyframe copiedKeyframe in Workspace.copiedKeyframes)
|
|
{
|
|
int tickToPasteAt = Workspace.StageTick + (copiedKeyframe.atTick.Value - earliestTick);
|
|
|
|
if (tickToPasteAt < 1) continue;
|
|
if (tickToPasteAt > Workspace.StageWindowSize)
|
|
{
|
|
if (Workspace.stretchKeyframes)
|
|
{ Workspace.GetCurrentAnimationStage().ResizeStageWindow(tickToPasteAt); }
|
|
|
|
else continue;
|
|
}
|
|
|
|
int targetActorID = actorsInvolved.Count == 1 ? Workspace.ActorID : copiedKeyframe.actorID;
|
|
|
|
if (Workspace.DoesPawnKeyframeExistAtTick(Workspace.StageID, targetActorID, tickToPasteAt))
|
|
{
|
|
PawnKeyframe oldKeyframe = Workspace.GetPawnAnimationClip(targetActorID).Keyframes.First(x => x.atTick == tickToPasteAt);
|
|
Workspace.GetAnimationClipThatOwnsKeyframe(oldKeyframe.keyframeID).RemovePawnKeyframe(oldKeyframe.keyframeID, true);
|
|
}
|
|
|
|
PawnKeyframe clonedKeyframe = copiedKeyframe.Copy();
|
|
clonedKeyframe.GenerateKeyframeID(targetActorID);
|
|
clonedKeyframe.atTick = tickToPasteAt;
|
|
|
|
PawnAnimationClip clip = Workspace.animationDef.AnimationStages[Workspace.StageID].AnimationClips[targetActorID];
|
|
PawnKeyframe nextKeyframe = clip.Keyframes.FirstOrDefault(x => x.atTick > tickToPasteAt);
|
|
|
|
if (nextKeyframe != null)
|
|
{ clip.Keyframes.Insert(clip.Keyframes.IndexOf(nextKeyframe), clonedKeyframe); }
|
|
|
|
else
|
|
{ clip.Keyframes.Add(clonedKeyframe); }
|
|
|
|
clip.BuildSimpleCurves();
|
|
|
|
EventsManager.OnKeyframeCountChanged(clip);
|
|
}
|
|
|
|
if (originalWindowSize != Workspace.StageWindowSize)
|
|
{
|
|
Workspace.GetCurrentAnimationStage().StretchStageWindow(originalWindowSize);
|
|
Workspace.GetCurrentAnimationStage().ResizeStageWindow(originalWindowSize);
|
|
}
|
|
|
|
Workspace.RecordEvent("Keyframe pasted");
|
|
}
|
|
|
|
public void RemovePawnKeyframe(int keyframeID, bool force = false)
|
|
{
|
|
PawnKeyframe keyframe = Workspace.GetPawnKeyframe(keyframeID);
|
|
if (keyframe == null || IsOwnerOfKeyframe(keyframeID) == false) return;
|
|
|
|
if (keyframe.atTick == Constants.minTick && force == false)
|
|
{ Debug.LogWarning("Cannot delete key frame - the first key frame of an animation clip cannot be deleted"); return; }
|
|
|
|
if (Keyframes.Count <= 2 && force == false)
|
|
{ Debug.LogWarning("Cannot delete key frame - an animation clip must have two or more keyframes"); return; }
|
|
|
|
Keyframes.Remove(keyframe);
|
|
BuildSimpleCurves();
|
|
|
|
EventsManager.OnKeyframeCountChanged(this);
|
|
Workspace.RecordEvent("Keyframe deletion");
|
|
}
|
|
|
|
public bool IsOwnerOfKeyframe(int keyframeID)
|
|
{
|
|
return Keyframes.Any(x => x.keyframeID == keyframeID);
|
|
}
|
|
|
|
public float GetStageTickPercentage()
|
|
{
|
|
return (float)(Workspace.StageTick % duration) / duration;
|
|
}
|
|
|
|
// Pre-save / post-load
|
|
public void OnPreSave()
|
|
{
|
|
var temp = Addons.Copy();
|
|
Addons.Clear();
|
|
|
|
foreach (ActorAddon addon in temp)
|
|
{
|
|
if (addon.Render)
|
|
{ addons.Add(addon); }
|
|
}
|
|
}
|
|
|
|
public void OnPostLoad()
|
|
{
|
|
Addons = addons.Copy();
|
|
|
|
foreach (PawnKeyframe keyframe in Keyframes)
|
|
{
|
|
keyframe.OnPostLoad();
|
|
}
|
|
}
|
|
}
|
|
}
|