using System; using System.Text; using System.Collections.Generic; using System.Linq; using System.IO; using System.Xml.Serialization; using UnityEngine; using UnityEngine.UI; namespace RimWorldAnimationStudio { public class AnimationController : Singleton { [Header("Object references")] public Transform animationTimelines; public Transform actorBodies; public Dropdown stageLoopDropdown; [Header("Prefabs")] public ActorBody actorBodyPrefab; public GameObject animationTimelinePrefab; // Private timing variables private float timeSinceLastUpdate = 0; private int loopCount = 0; private float playBackSpeed = 1f; public void Start() { EventsManager.onAnimationChanged.AddListener(delegate{ Initialize(); }); EventsManager.onStageIDChanged.AddListener(delegate { Initialize(); }); EventsManager.onActorCountChanged.AddListener(delegate { Initialize(); }); } public void Update() { if (Workspace.animationDef == null) return; // Update stage tick / loop count if animating if (Workspace.IsAnimating) { timeSinceLastUpdate += Time.deltaTime; if (timeSinceLastUpdate < 1 / (playBackSpeed * 60f)) { return; } timeSinceLastUpdate -= 1 / (playBackSpeed * 60f); Workspace.StageTick += 1; if (Workspace.StageTick >= Workspace.StageWindowSize) { switch (stageLoopDropdown.value) { case 1: Workspace.StageTick = Constants.minTick; break; case 2: UpdateLoopCount(Workspace.GetCurrentAnimationStage().StageLoopsNormal); break; case 3: UpdateLoopCount(Workspace.GetCurrentAnimationStage().StageLoopsQuick); break; default: Workspace.IsAnimating = false; break; } } } // Update animation preview UpdateAnimationPreview(); } public void UpdateLoopCount(int stageLoops) { loopCount++; Workspace.StageTick = Constants.minTick; if (loopCount >= stageLoops) { if (Workspace.StageID >= Workspace.animationDef.AnimationStages.Count - 1) { Workspace.IsAnimating = false; } else { Workspace.StageID++; loopCount = 0; } } } public void UpdateAnimationPreview() { if (Workspace.animationDef == null || Workspace.StageID >= Workspace.animationDef?.AnimationStages.Count) return; List actorBodiesList = actorBodies.GetComponentsInChildren().ToList(); for (int actorID = 0; actorID < Workspace.animationDef.actors.Count; actorID++) { // Get the current actor Actor actor = Workspace.GetActor(actorID); // Get their animation clip and rebuild it to ensure it is up to date PawnAnimationClip clip = Workspace.GetPawnAnimationClip(actorID); clip.BuildSimpleCurves(); // Get flags bool quiver = Workspace.IsAnimating && Workspace.GetCurrentOrPreviousKeyframe(actorID).Quiver == true; bool requiresGenitals = actor.RequiredGenitals.Any(x => x == "Penis") || actor.IsFucking; // Get clip percentage float clipPercent = (float)(Workspace.StageTick % clip.duration) / clip.duration; if (Workspace.StageTick > Constants.minTick && Workspace.StageTick == clip.duration) clipPercent = 1f; if (Workspace.GetCurrentAnimationStage().IsLooping == false) { clipPercent = (float)Workspace.StageTick / clip.duration; } // Get the actors race and body type PawnRaceDef pawnRaceDef = actor.GetPawnRaceDef(); string bodyType = pawnRaceDef.isHumanoid ? actor.bodyType : "None"; // Evalute the actor's basic offsets and facing ActorPosition actorPosition = actor.GetCurrentPosition(); float bodyPosX = actorPosition.bodyOffsetX; float bodyPosZ = actorPosition.bodyOffsetZ; float bodyAngle = actorPosition.bodyAngle + (quiver ? UnityEngine.Random.value * 2f - 1f : 0f); float headBob = actorPosition.headBob; float headAngle = actorPosition.headAngle; float genitalAngle = actorPosition.genitalAngle; int bodyFacing = actorPosition.bodyFacing; int headFacing = actorPosition.headFacing; // Convert values to world coordinates Vector3 bodyPos = new Vector3(bodyPosX, bodyPosZ, 0f); Vector3 headPos = new Vector3(0f, headBob, 0f) + PawnUtility.BaseHeadOffsetAt(bodyType, bodyFacing); Vector3 appendagePos = PawnUtility.GroinOffsetAt(bodyType, bodyFacing); // Update actorbody ActorBody actorBody = actorBodiesList[actorID]; ActorBodyPart actorBodypart; actorBody.transform.localScale = new Vector3(pawnRaceDef.scale, pawnRaceDef.scale, pawnRaceDef.scale); // Body actorBody.transform.position = bodyPos + actor.GetFinalTransformOffset(); actorBody.transform.eulerAngles = new Vector3(0, 0, -bodyAngle); actorBody.bodyRenderer.sortingLayerName = clip.Layer; actorBody.bodyRenderer.sprite = pawnRaceDef.GetBodyTypeGraphic((CardinalDirection)bodyFacing, bodyType); actorBody.bodyRenderer.flipX = bodyFacing == 3; actorBody.bodyRenderer.gameObject.SetActive(actorBody.bodyRenderer.sprite != null); // Head actorBodypart = actorBody.GetActorBodyPart("head"); actorBodypart.transform.localPosition = headPos; actorBodypart.transform.eulerAngles = new Vector3(0, 0, -headAngle); actorBodypart.bodyPartRenderer.sortingLayerName = clip.Layer; actorBodypart.bodyPartRenderer.sortingOrder = bodyFacing == 0 ? -1 : 1; actorBodypart.bodyPartRenderer.sprite = pawnRaceDef.isHumanoid ? pawnRaceDef.GetHeadGraphic((CardinalDirection)headFacing) : null; actorBodypart.bodyPartRenderer.flipX = headFacing == 3; actorBodypart.gameObject.SetActive(actorBodypart.bodyPartRenderer.sprite != null); // Appendage actorBodypart = actorBody.GetActorBodyPart("appendage"); actorBodypart.transform.localPosition = new Vector3(appendagePos.x, appendagePos.z, 0f); actorBodypart.transform.eulerAngles = new Vector3(0, 0, -genitalAngle); actorBodypart.bodyPartRenderer.sortingLayerName = clip.Layer; actorBodypart.bodyPartRenderer.sprite = requiresGenitals && pawnRaceDef.isHumanoid ? Resources.Load("Textures/Humanlike/Appendages/Appendage" + bodyFacing) : null; //actorBody.appendageRenderer.flipX = bodyFacing == 3; actorBodypart.gameObject.SetActive(actorBodypart.bodyPartRenderer.sprite != null); // Add-ons foreach (ActorAddon addon in clip.Addons) { actorBodypart = actorBody.GetActorBodyPart(addon.AddonName); if (actorBodypart == null) continue; ActorBody anchoringActorBody = actorBodies.GetComponentsInChildren()?.FirstOrDefault(x => x.actorID == addon.AnchoringActor); Vector3 anchor = PawnUtility.GetBodyPartAnchor(anchoringActorBody, addon.anchorName); actorBodypart.transform.position = anchor + new Vector3(addon.PosX.Evaluate(clipPercent), addon.PosZ.Evaluate(clipPercent), 0); actorBodypart.transform.eulerAngles = new Vector3(0, 0, -addon.Rotation.Evaluate(clipPercent)); actorBodypart.bodyPartRenderer.sortingLayerName = addon.Layer; //actorBodypart.bodyPartRenderer.sprite actorBodypart.gameObject.SetActive(addon.Render); } // Play sounds if (Workspace.IsAnimating) { PawnKeyframe keyframe = clip.keyframes.FirstOrDefault(x => x.atTick == (int)(clip.duration * clipPercent)); if (keyframe != null && string.IsNullOrEmpty(keyframe.soundEffect) == false) { actorBody.GetComponent().PlaySound(keyframe.soundEffect); } } } } public void Initialize() { //Debug.Log("Initializing animation preview"); int actorCount = Workspace.animationDef.Actors.Count; int childCount = animationTimelines.GetComponentsInChildren().Count(); for (int actorID = 0; actorID < Mathf.Max(actorCount, childCount); actorID++) { // Add new actors as required if (actorID >= childCount) { Instantiate(animationTimelinePrefab, animationTimelines); Instantiate(actorBodyPrefab, actorBodies.transform); } // Get objects to update AnimationTimeline animationTimeline = animationTimelines.GetComponentsInChildren()[actorID]; ActorBody actorBody = actorBodies.GetComponentsInChildren()[actorID]; // Update values if (actorID < actorCount) { animationTimeline.Initialize(actorID); actorBody.Initialize(actorID); } // Remove excess objects as required else { Destroy(animationTimeline.transform.parent.gameObject); Destroy(actorBody.gameObject); } } EventsManager.OnAnimationTimelinesChanged(); } public void Reset() { Workspace.IsAnimating = false; timeSinceLastUpdate = 0; loopCount = 0; foreach (Transform child in transform) { child.gameObject.SetActive(true); } } } }