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 addons; [XmlAttribute("Class")] public string className = "Rimworld_Animations.PawnAnimationClip"; [XmlArray("keyframes"), XmlArrayItem("li")] public List keyframes; [XmlArray("tags"), XmlArrayItem("li")] public List 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 Addons { get { return addons.NullOrEmpty() ? addons = new List() : addons; } set { addons = value.NotNullOrEmpty() ? value : null; } } [XmlIgnore] public List Keyframes { get { return keyframes.NullOrEmpty() ? keyframes = new List() : keyframes; } set { keyframes = value.NotNullOrEmpty() ? value : null; } } [XmlIgnore] public List Tags { get { return tags.NullOrEmpty() ? tags = new List() : 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.HasValidKeyframeID() == false) { keyframe.GenerateKeyframeID(GetOwningActorID()); } if (keyframe.atTick.HasValue) { if (i + 1 < Keyframes.Count) { Keyframes[i].TickDuration = Keyframes[i + 1].atTick.Value - Keyframes[i].atTick.Value; } // Safeguard - if two keys end up in having the same atTick, the duration of the first will be 0 and should be deleted if (Keyframes[i].TickDuration == 0) { Keyframes.RemoveAt(i); continue; } 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); } } 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 keyframesToClone = Workspace.GetPawnKeyframesByID(Workspace.keyframeID); foreach (PawnKeyframe keyframe in keyframesToClone) { Workspace.copiedKeyframes.Add(keyframe.GetClone()); } } public void PastePawnKeyframes() { int originalWindowSize = Workspace.StageWindowSize; List 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.GetClone(); 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; Keyframes.Remove(keyframe); if (Workspace.GetAllPawnKeyframesAtTick(GetOwningActorID(), Constants.minTick).NullOrEmpty()) { PawnKeyframe newKeyframe = new PawnKeyframe(); newKeyframe.GenerateKeyframeID(GetOwningActorID()); newKeyframe.atTick = Constants.minTick; Keyframes.Insert(0, newKeyframe); } // Add missing second keyframe (if needed) if (Keyframes.Count == 1) { PawnKeyframe newKeyframe = Workspace.GetAllPawnKeyframesAtTick(GetOwningActorID(), Constants.minTick).First().GetClone(); newKeyframe.atTick = 10; Keyframes.Add(newKeyframe); } BuildSimpleCurves(); EventsManager.OnKeyframeCountChanged(this); Workspace.RecordEvent("Keyframe deletion"); } public bool IsOwnerOfKeyframe(int keyframeID) { return Keyframes.Any(x => x.keyframeID == keyframeID); } // 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() { foreach (PawnKeyframe keyframe in Keyframes) { keyframe.OnPostLoad(); } } } }