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() { // 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 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(string addonName, float scale = 1f) { if (Addons.Any(x => x.AddonName == addonName) == false) { Addons.Add(new ActorAddon(addonName, scale)); } foreach (PawnKeyframe keyframe in Keyframes) { if (keyframe.AddonKeyframes.Any(x => x.AddonName == addonName) == false) { keyframe.AddonKeyframes.Add(new AddonKeyframe(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() { PawnAnimationClip clip = Workspace.GetCurrentPawnAnimationClip(); List keyframes = clip?.Keyframes; if (clip == null || 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 % clip.duration) / clip.duration; PawnKeyframe keyframe = new PawnKeyframe(); keyframe.BodyAngle = clip.BodyAngle.Evaluate(clipPercent); keyframe.HeadAngle = clip.HeadAngle.Evaluate(clipPercent); keyframe.HeadBob = clip.HeadBob.Evaluate(clipPercent); keyframe.BodyOffsetX = clip.BodyOffsetX.Evaluate(clipPercent); keyframe.BodyOffsetZ = clip.BodyOffsetZ.Evaluate(clipPercent); keyframe.HeadFacing = (int)clip.HeadFacing.Evaluate(clipPercent); keyframe.BodyFacing = (int)clip.BodyFacing.Evaluate(clipPercent); keyframe.GenitalAngle = clip.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); } clip.BuildSimpleCurves(); Workspace.RecordEvent("Keyframe addition"); } public void ClonePawnKeyframe() { List keyframesToClone = Workspace.GetPawnKeyframesByID(Workspace.keyframeID); foreach (PawnKeyframe keyframe in keyframesToClone) { PawnAnimationClip clip = Workspace.GetAnimationClipThatOwnsKeyframe(keyframe.keyframeID); if (clip == null) { Debug.LogWarning("Cannot clone pawn keyframe - no clip owns this keyframe"); continue; } if (clip.Keyframes.FirstOrDefault(x => x.atTick == Workspace.StageTick) != null) { Debug.LogWarning("Cannot clone pawn keyframe - a keyframe already exists at this tick"); return; } PawnKeyframe cloneFrame = keyframe.Copy(); cloneFrame.GenerateKeyframeID(clip.GetOwningActorID()); cloneFrame.atTick = Workspace.StageTick; PawnKeyframe nextKeyframe = clip.Keyframes.FirstOrDefault(x => x.atTick > Workspace.StageTick); if (nextKeyframe != null) { clip.Keyframes.Insert(clip.Keyframes.IndexOf(nextKeyframe), cloneFrame); } else { clip.Keyframes.Add(cloneFrame); } clip.BuildSimpleCurves(); } Workspace.RecordEvent("Keyframe clone"); } public void CopyPawnKeyframes() { Workspace.copiedKeyframes.Clear(); List keyframesToClone = Workspace.GetPawnKeyframesByID(Workspace.keyframeID); foreach (PawnKeyframe keyframe in keyframesToClone) { Workspace.copiedKeyframes.Add(keyframe.Copy()); } } 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.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(); } 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(); 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() { foreach (ActorAddon addon in Addons) { if (addon.Render) { addons.Add(addon); } } } public void OnPostLoad() { Addons = addons.Copy(); foreach (PawnKeyframe keyframe in Keyframes) { keyframe.OnPostLoad(); } AddActorAddon("left hand", 0.667f); AddActorAddon("right hand", 0.667f); AddActorAddon("dildo"); } } }