using System; using System.Collections.Generic; using System.Linq; using RimWorld; using rjw; using UnityEngine; using Verse; using Verse.AI; using Verse.Sound; namespace Rimworld_Animations { public class CompExtendedAnimator : ThingComp { // CompExtendedAnimator // Helps manage AnimationQueue, AbsolutePosition //ticks of current animation private int animationTicks; private List animationQueue; private BaseExtendedAnimatorAnchor anchor; private int? rotation; private Vector3? offset; private bool isAnimating = false; public bool IsAnimating { get { return isAnimating; } } public bool IsAnchored { get { return anchor != null; } } public Vector3? Offset { get { return offset; } set { this.offset = value; } } public int? Rotation { get { return rotation; } set { this.rotation = value; } } public int AnimationLength { get { if (!IsAnimating) return 0; int groupAnimLength = 0; foreach(AnimationDef anim in animationQueue) { groupAnimLength += anim.durationTicks; } return groupAnimLength; } } public AnimationDef CurrentAnimation { get { return IsAnimating ? animationQueue[0] : null; } } public Vector3 getAnchor() { return anchor.getDrawPos(); } public override void CompTick() { if (isAnimating) { animationTicks++; //if animationticks is equal to cur. anim duration, if (animationTicks >= animationQueue[0].durationTicks) { //dequeue; returns false if more animations if (!PopAnimationQueue()) { //play next if more anims still PlayNextAnimation(); } else { StopAnimating(); } } CheckAndPlaySounds(); } base.CompTick(); } //returns false if still more animations public bool PopAnimationQueue() { if (!animationQueue.Empty()) { //pop queue animationQueue.RemoveAt(0); } return animationQueue.Empty(); } public void PlayNextAnimation() { if (!animationQueue.Empty()) { isAnimating = true; animationTicks = 0; pawn.Drawer.renderer.SetAnimation(animationQueue[0]); } } public void StopAnimating() { isAnimating = false; animationQueue = null; anchor = null; offset = null; pawn.Drawer.renderer.SetAnimation(null); } public void PlayGroupAnimation(List groupAnimation, Vector3? positionOffset, int? rotationOffset) { this.Offset = positionOffset; this.Rotation = rotationOffset; animationQueue = groupAnimation; PlayNextAnimation(); } public void PlayGroupAnimation(List groupAnimation, Vector3? positionOffset, int? rotationOffset, BaseExtendedAnimatorAnchor anchor) { this.anchor = anchor; PlayGroupAnimation(groupAnimation, positionOffset, rotationOffset); } public override void PostExposeData() { base.PostExposeData(); Scribe_Values.Look(ref this.isAnimating, "animations_isAnimating", false); Scribe_Values.Look(ref this.animationTicks, "animations_ticks", 0); Scribe_Collections.Look(ref animationQueue, "animations_queue"); Scribe_Deep.Look(ref this.anchor, "animations_anchor"); } public override List CompRenderNodes() { //only if pawn is animating for performance if (IsAnimating) { List animRenderNodes = new List(); // for all animationpropdefs, foreach (AnimationPropDef animationProp in DefDatabase.AllDefsListForReading) { //if animation makes use of prop, if (AnimationMakesUseOfProp(animationProp)) { PawnRenderNodeProperties props = animationProp.animPropProperties; if (props.texPath.NullOrEmpty()) { props.texPath = "AnimationProps/MissingTexture/MissingTexture"; } //create new render node PawnRenderNode animRenderNode = (PawnRenderNode)Activator.CreateInstance(props.nodeClass, new object[] { this.pawn, props, pawn.Drawer.renderer.renderTree }); animRenderNodes.Add(animRenderNode); } } //return list of rendernodes that should animate return animRenderNodes; } else { return null; } } public void CheckAndPlaySounds() { PawnRenderNode rootNode = pawn.Drawer?.renderer?.renderTree?.rootNode; //check if the rootnode has sounds; if so play it if (rootNode?.AnimationWorker is AnimationWorker_KeyframesExtended animWorker) { SoundDef sound = animWorker.soundAtTick(rootNode.tree.AnimationTick); if (sound != null) { sound.PlayOneShot(new TargetInfo(pawn.Position, pawn.Map)); } } if (rootNode?.children != null) { foreach (PawnRenderNode node in rootNode?.children) { if (node?.AnimationWorker is AnimationWorker_KeyframesExtended childrenAnimWorker) { SoundDef sound = childrenAnimWorker.soundAtTick(node.tree.AnimationTick); if (sound != null) { sound.PlayOneShot(new TargetInfo(pawn.Position, pawn.Map)); } } } } //do the same for all the child nodes } public bool AnimationMakesUseOfProp(AnimationPropDef animationProp) { // never true if not animating; anim props shouldn't be attached if (!IsAnimating) return false; //for ONLY THE CURRENT animation, foreach (PawnRenderNodeTagDef propTag in animationQueue[0].animationParts.Keys) { // if that proptag is the same as the one for animationProp, if (propTag == animationProp.animPropProperties.tagDef) { //that prop is being used in the animation return true; } } return false; } private Pawn pawn => base.parent as Pawn; } }