using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using UnityEngine; using UnityEngine.Events; namespace RimWorldAnimationStudio { public static class Workspace { // Animation def public static AnimationDef animationDef; // Animation indices set / get public static int StageID { get { return Mathf.Clamp(stageID, 0, animationDef.AnimationStages.Count - 1); } set { bool triggerEvent = stageID != value; stageID = value; if (triggerEvent) { StageTick = Constants.minTick; EventsManager.OnStageIDChanged(); } } } public static int ActorID { get { return Mathf.Clamp(actorID, 0, animationDef.Actors.Count - 1); } set { bool triggerEvent = actorID != value; actorID = value; if (triggerEvent) { EventsManager.OnActorIDChanged(); } } } public static int StageTick { get { return Mathf.Clamp(stageTick, Constants.minTick, StageWindowSize); } set { bool triggerEvent = stageTick != value; stageTick = value; if (triggerEvent) { EventsManager.OnStageTickChanged(); } } } public static int StageWindowSize { get { if (animationDef == null) { return -1; } if (animationDef.AnimationStages[StageID].stageWindowSize < 0) { animationDef.AnimationStages[StageID].stageWindowSize = animationDef.AnimationStages[StageID].AnimationClips.Select(x => x.duration).Max(); } return animationDef.AnimationStages[StageID].stageWindowSize; } } // Selected keyframes and copied keyframe data public static List keyframeID = new List(); public static List copiedKeyframes = new List(); // Actor manipulation public static ActorManipulationMode actorManipulationMode = ActorManipulationMode.Pan; public static ActorBodyPart selectedBodyPart; // Current save path public static string animationSavePath; // Stage controls private static float playBackSpeed = 1f; private static bool isAnimating; public static bool stretchKeyframes; // Stage controls set / get public static float PlayBackSpeed { get { return playBackSpeed; } set { Mathf.Clamp(Workspace.playBackSpeed, 0.01f, 10f); } } public static bool IsAnimating { get { return isAnimating; } set { isAnimating = value; EventsManager.OnAnimationToggled(); } } // Animation indices private static int stageID = 0; private static int actorID = 0; private static int stageTick = Constants.minTick; // Workspace history private static LinkedList pastSnapshots = new LinkedList(); private static LinkedList futureSnapshots = new LinkedList(); private static int maxHistoryDepth = 100; public static Actor GetCurrentActor() { return GetActor(ActorID); } public static AnimationStage GetCurrentAnimationStage() { return GetAnimationStage(StageID); } public static PawnAnimationClip GetCurrentPawnAnimationClip() { return GetPawnAnimationClip(ActorID); } public static PawnKeyframe GetCurrentPawnKeyframe(bool makeKeyframe = false) { PawnKeyframe keyframe = animationDef?.AnimationStages[StageID]?.AnimationClips[ActorID]?.Keyframes.FirstOrDefault(x => x.atTick == StageTick); if (keyframe != null || makeKeyframe == false) { return keyframe; } GetCurrentPawnAnimationClip().AddPawnKeyframe(); return animationDef?.AnimationStages[StageID]?.AnimationClips[ActorID]?.Keyframes.FirstOrDefault(x => x.atTick == StageTick); } public static Actor GetActor(int actorID) { return animationDef?.Actors.ElementAtOrDefault(actorID); } public static AnimationStage GetAnimationStage(int stageID) { return animationDef?.AnimationStages.ElementAtOrDefault(stageID); } public static PawnAnimationClip GetPawnAnimationClip(int actorID) { return GetCurrentAnimationStage()?.AnimationClips.ElementAtOrDefault(actorID); } public static PawnKeyframe GetPawnKeyframe(int keyframeID) { foreach (AnimationStage stage in animationDef?.AnimationStages) { foreach (PawnAnimationClip clip in stage.animationClips) { PawnKeyframe keyframe = clip.Keyframes.FirstOrDefault(x => x.keyframeID == keyframeID); if (keyframe != null) return keyframe; } } return null; } public static PawnAnimationClip GetAnimationClipThatOwnsKeyframe(int keyframeID) { foreach (AnimationStage stage in animationDef?.AnimationStages) { foreach (PawnAnimationClip clip in stage.animationClips) { PawnKeyframe keyframe = clip.Keyframes.FirstOrDefault(x => x.keyframeID == keyframeID); if (keyframe != null) return clip; } } return null; } public static List GetAllPawnKeyframesAtTick(int actorID, int atTick) { return animationDef?.AnimationStages[StageID]?.AnimationClips[actorID]?.Keyframes.Where(x => x.atTick == atTick)?.ToList(); } public static List GetPawnKeyframesByID(List keyframeIDs) { List pawnKeyframes = new List(); foreach (PawnAnimationClip clip in animationDef.AnimationStages[StageID].AnimationClips) { foreach (PawnKeyframe keyframe in clip.Keyframes) { if (keyframeIDs.Contains(keyframe.keyframeID)) { pawnKeyframes.Add(keyframe); } } } return pawnKeyframes; } public static bool DoesPawnKeyframeExistAtTick(int stageID, int actorID, int atTick) { return animationDef.AnimationStages[stageID].AnimationClips[actorID].Keyframes.Any(x => x.atTick == atTick); } public static PawnKeyframe GetNextKeyframe(int actorID) { PawnKeyframe pawnKeyframe = null; PawnAnimationClip clip = GetPawnAnimationClip(actorID); foreach (PawnKeyframe keyframe in clip.Keyframes) { if (keyframe.atTick > StageTick) { pawnKeyframe = keyframe; break; } } return pawnKeyframe; } public static PawnKeyframe GetPreviousKeyframe(int actorID) { PawnKeyframe pawnKeyframe = null; PawnAnimationClip clip = GetPawnAnimationClip(actorID); foreach (PawnKeyframe keyframe in clip.Keyframes) { if (keyframe.atTick < StageTick) { pawnKeyframe = keyframe; } } return pawnKeyframe; } public static PawnKeyframe GetCurrentOrPreviousKeyframe(int actorID) { PawnKeyframe pawnKeyframe = null; PawnAnimationClip clip = GetPawnAnimationClip(actorID); foreach (PawnKeyframe keyframe in clip.Keyframes) { if (keyframe.atTick <= StageTick) { pawnKeyframe = keyframe; } } return pawnKeyframe; } public static int FindClosestKeyFrameAtTick(int atTick, int searchDistance = int.MaxValue, int excludedActorID = -1) { List keyframesToCheck; if (excludedActorID >= 0) { keyframesToCheck = animationDef.AnimationStages[StageID].AnimationClips.Where(x => animationDef.AnimationStages[StageID].AnimationClips.IndexOf(x) != excludedActorID).SelectMany(x => x.Keyframes)?.ToList(); } else { keyframesToCheck = animationDef.AnimationStages[StageID].AnimationClips.SelectMany(x => x.Keyframes)?.ToList(); } keyframesToCheck = keyframesToCheck.Where(x => Mathf.Abs(atTick - x.atTick.Value) <= searchDistance)?.ToList(); if (keyframesToCheck.NullOrEmpty()) { return atTick; } int minDist = int.MaxValue; int bestAtTick = -1; foreach (PawnKeyframe keyframe_ in keyframesToCheck) { if (Mathf.Abs(keyframe_.atTick.Value - atTick) < minDist) { minDist = Mathf.Abs(keyframe_.atTick.Value - atTick); bestAtTick = keyframe_.atTick.Value; } } return bestAtTick; } public static int GetEarliestAtTickInCopiedKeyframes(int actorID) { IEnumerable keyframes = copiedKeyframes.Where(x => x.actorID == actorID); if (keyframes == null || keyframes.Any() == false) return -1; return keyframes.Min(x => x.atTick).Value; } public static void Reset() { ActorID = 0; StageID = 0; keyframeID.Clear(); selectedBodyPart = null; animationSavePath = null; ClearHistory(); } public static void MakeHistoricRecord(string eventDesc) { WorkspaceRecord record = new WorkspaceRecord(); record.recordID = pastSnapshots.Count; record.eventDesc = eventDesc; record.animationDef = animationDef.Copy(); record.stageID = StageID; futureSnapshots.Clear(); pastSnapshots.AddLast(record); if (pastSnapshots.Count > maxHistoryDepth) { pastSnapshots.RemoveFirst(); } } public static void RestoreToHistoricRecord(WorkspaceRecord record) { animationDef = record.animationDef.Copy(); StageID = record.stageID; StageCardManager.Instance.Initialize(); } public static void Undo() { WorkspaceRecord recordToRead = pastSnapshots.Last?.Previous?.Value; WorkspaceRecord recordToStore = pastSnapshots.Last?.Value; if (recordToRead == null || recordToStore == null) return; pastSnapshots.RemoveLast(); futureSnapshots.AddLast(recordToStore); RestoreToHistoricRecord(recordToRead); Debug.Log("Undoing : " + recordToStore.eventDesc); EventsManager.OnAnimationChanged(); } public static void Redo() { WorkspaceRecord recordToReadAndStore = futureSnapshots.Last?.Value; if (recordToReadAndStore == null) return; futureSnapshots.RemoveLast(); pastSnapshots.AddLast(recordToReadAndStore); RestoreToHistoricRecord(recordToReadAndStore); Debug.Log("Redoing : " + recordToReadAndStore.eventDesc); EventsManager.OnAnimationChanged(); } public static void ClearHistory() { pastSnapshots.Clear(); futureSnapshots.Clear(); } public static void RecordEvent(string eventDesc) { //Debug.Log("Recording event: " + eventDesc + " (record ID: " + pastSnapshots.Count + ")"); MakeHistoricRecord(eventDesc); } } }