using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Newtonsoft.Json; using StardewValley; using StardustCore.UIUtilities; namespace StardustCore.Animations { /// Used to play animations for Stardust.CoreObject type objects and all objects that extend from it. In draw code of object make sure to use this info instead. public class AnimationManager { public Dictionary> animations = new SerializableDictionary>(); public string currentAnimationName; public int currentAnimationListIndex; public List currentAnimationList = new List(); public Texture2DExtended objectTexture; ///Might not be necessary if I use the CoreObject texture sheet. public Animation defaultDrawFrame; public Animation currentAnimation; public bool enabled; public bool loopAnimation; public string animationDataString; [JsonIgnore] public bool requiresUpdate; public bool IsNull => this.defaultDrawFrame == null && this.objectTexture == null; /// /// Checks to see if there is an animation playing. /// public bool IsAnimationPlaying { get { return !(this.defaultDrawFrame == this.currentAnimation); } } /// Construct an instance. public AnimationManager() { } /// Constructor for Animation Manager class. /// The texture that will be used for the animation. This is typically the same as the object this class is attached to. /// This is used if no animations will be available to the animation manager. /// Whether or not animations play by default. Default value is true. public AnimationManager(Texture2DExtended ObjectTexture, Animation DefaultFrame, bool EnabledByDefault = true) { this.currentAnimationListIndex = 0; this.objectTexture = ObjectTexture; this.defaultDrawFrame = DefaultFrame; this.enabled = EnabledByDefault; this.currentAnimation = this.defaultDrawFrame; this.currentAnimationName = ""; this.animationDataString = ""; } public AnimationManager(Texture2DExtended ObjectTexture, Animation DefaultFrame, string animationString, string startingAnimationKey, int startingAnimationFrame = 0, bool EnabledByDefault = true) { this.currentAnimationListIndex = 0; this.objectTexture = ObjectTexture; this.defaultDrawFrame = DefaultFrame; this.enabled = EnabledByDefault; this.animationDataString = animationString; this.animations = parseAnimationsFromXNB(animationString); if (this.animations.TryGetValue(startingAnimationKey, out this.currentAnimationList)) this.setAnimation(startingAnimationKey, startingAnimationFrame); else { this.currentAnimation = this.defaultDrawFrame; this.currentAnimationName = ""; } } public AnimationManager(Texture2DExtended ObjectTexture, Animation DefaultFrame, Dictionary> animationString, string startingAnimationKey, int startingAnimationFrame = 0, bool EnabledByDefault = true) { this.currentAnimationListIndex = 0; this.objectTexture = ObjectTexture; this.defaultDrawFrame = DefaultFrame; this.enabled = EnabledByDefault; this.animations = animationString; if (this.animations.TryGetValue(startingAnimationKey, out this.currentAnimationList)) this.setAnimation(startingAnimationKey, startingAnimationFrame); else { this.currentAnimation = this.defaultDrawFrame; this.currentAnimationName = ""; } } /// Update the animation frame once after drawing the object. public void tickAnimation() { try { if (this.currentAnimation.frameDuration == -1 || !this.enabled || this.currentAnimation == this.defaultDrawFrame) return; //This is if this is a default animation or the animation stops here. if (this.currentAnimation.frameCountUntilNextAnimation == 0) this.getNextAnimation(); this.currentAnimation.tickAnimationFrame(); this.requiresUpdate = true; } catch (Exception err) { ModCore.ModMonitor.Log("An internal error occured when trying to tick the animation."); ModCore.ModMonitor.Log(err.ToString(), StardewModdingAPI.LogLevel.Error); } } /// Get the next animation frame in the list of animations. private void getNextAnimation() { this.currentAnimationListIndex++; if (this.currentAnimationListIndex == this.currentAnimationList.Count) //If the animation frame I'm tryting to get is 1 outside my list length, reset the list. if (this.loopAnimation) { this.currentAnimationListIndex = 0; } else { this.requiresUpdate = true; this.playDefaultAnimation(); return; } //Get the next animation from the list and reset it's counter to the starting frame value. this.currentAnimation = this.currentAnimationList[this.currentAnimationListIndex]; this.currentAnimation.startAnimation(); this.requiresUpdate = true; } /// Gets the animation from the dictionary of all animations available. /// /// public bool setAnimation(string AnimationName, int StartingFrame = 0) { if (this.animations.TryGetValue(AnimationName, out List dummyList)) { if (dummyList.Count != 0 || StartingFrame >= dummyList.Count) { this.currentAnimationList = dummyList; this.currentAnimation = this.currentAnimationList[StartingFrame]; this.currentAnimationName = AnimationName; this.requiresUpdate = true; return true; } else { if (dummyList.Count == 0) ModCore.ModMonitor.Log("Error: Current animation " + AnimationName + " has no animation frames associated with it."); if (dummyList.Count > dummyList.Count) ModCore.ModMonitor.Log("Error: Animation frame " + StartingFrame + " is outside the range of provided animations. Which has a maximum count of " + dummyList.Count); return false; } } else { ModCore.ModMonitor.Log("Error setting animation: " + AnimationName + " animation does not exist in list of available animations. Did you make sure to add it in?"); return false; } } /// /// Plays the animation for the animation manager. /// /// /// /// /// public bool playAnimation(string AnimationName,bool overrideSameAnimation=false,int StartingFrame = 0) { if (this.animations.TryGetValue(AnimationName, out List dummyList)) { if (overrideSameAnimation == false) { if (this.currentAnimationName == AnimationName) return true; } if (dummyList.Count != 0 || StartingFrame >= dummyList.Count) { this.currentAnimationList = dummyList; this.currentAnimation = this.currentAnimationList[StartingFrame]; this.currentAnimationName = AnimationName; this.currentAnimation.startAnimation(); this.loopAnimation = true; this.requiresUpdate = true; return true; } else { if (dummyList.Count == 0) ModCore.ModMonitor.Log("Error: Current animation " + AnimationName + " has no animation frames associated with it."); if (dummyList.Count > dummyList.Count) ModCore.ModMonitor.Log("Error: Animation frame " + StartingFrame + " is outside the range of provided animations. Which has a maximum count of " + dummyList.Count); return false; } } else { ModCore.ModMonitor.Log("Error setting animation: " + AnimationName + " animation does not exist in list of available animations. Did you make sure to add it in?"); return false; } } /// /// Plays the animation for the animation manager only once. /// /// /// /// /// public bool playAnimationOnce(string AnimationName, bool overrideSameAnimation = false, int StartingFrame = 0) { if (this.animations.TryGetValue(AnimationName, out List dummyList)) { if (overrideSameAnimation == false) { if (this.currentAnimationName == AnimationName) return true; } if (dummyList.Count != 0 || StartingFrame >= dummyList.Count) { this.currentAnimationList = dummyList; this.currentAnimation = this.currentAnimationList[StartingFrame]; this.currentAnimationName = AnimationName; this.currentAnimation.startAnimation(); this.loopAnimation = false; this.requiresUpdate = true; return true; } else { if (dummyList.Count == 0) ModCore.ModMonitor.Log("Error: Current animation " + AnimationName + " has no animation frames associated with it."); if (dummyList.Count > dummyList.Count) ModCore.ModMonitor.Log("Error: Animation frame " + StartingFrame + " is outside the range of provided animations. Which has a maximum count of " + dummyList.Count); return false; } } else { ModCore.ModMonitor.Log("Error setting animation: " + AnimationName + " animation does not exist in list of available animations. Did you make sure to add it in?"); return false; } } /// /// Plays the default animation. /// public void playDefaultAnimation() { this.currentAnimation = this.defaultDrawFrame; this.currentAnimationName = ""; this.currentAnimationListIndex = 0; this.requiresUpdate = true; } /// Sets the animation manager to an on state, meaning that this animation will update on the draw frame. public void enableAnimation() { this.enabled = true; } /// Sets the animation manager to an off state, meaning that this animation will no longer update on the draw frame. public void disableAnimation() { this.enabled = false; } public static Dictionary> parseAnimationsFromXNB(string s) { string[] array = s.Split('*'); Dictionary> parsedDic = new Dictionary>(); foreach (string v in array) { // Log.AsyncC(v); string[] animationArray = v.Split(' '); if (parsedDic.ContainsKey(animationArray[0])) { List animations = parseAnimationFromString(v); foreach (var animation in animations) { parsedDic[animationArray[0]].Add(animation); } } else { parsedDic.Add(animationArray[0], new List()); List aniList = new List(); aniList = parseAnimationFromString(v); foreach (var ani in aniList) { parsedDic[animationArray[0]].Add(ani); } } } return parsedDic; } public static List parseAnimationFromString(string s) { List ok = new List(); string[] array2 = s.Split('>'); foreach (string q in array2) { string[] array = q.Split(' '); try { Animation ani = new Animation(new Rectangle(Convert.ToInt32(array[1]), Convert.ToInt32(array[2]), Convert.ToInt32(array[3]), Convert.ToInt32(array[4])), Convert.ToInt32(array[5])); // ModCore.ModMonitor.Log(ani.sourceRectangle.ToString()); ok.Add(ani); } catch { } } return ok; } /// Used to handle general drawing functionality using the animation manager. /// We need a spritebatch to draw. /// The texture to draw. /// The onscreen position to draw to. /// The source rectangle on the texture to draw. /// The color to draw the thing passed in. /// The rotation of the animation texture being drawn. /// The origin of the texture. /// The scale of the texture. /// Effects that get applied to the sprite. /// The dept at which to draw the texture. public void draw(SpriteBatch spriteBatch, Texture2D texture, Vector2 Position, Rectangle? sourceRectangle, Color drawColor, float rotation, Vector2 origin, float scale, SpriteEffects spriteEffects, float LayerDepth) { //Log.AsyncC("Animation Manager is working!"); spriteBatch.Draw(texture, Position, sourceRectangle, drawColor, rotation, origin, scale, spriteEffects, LayerDepth); try { this.tickAnimation(); // Log.AsyncC("Tick animation"); } catch (Exception err) { ModCore.ModMonitor.Log(err.ToString()); } } /// /// Draws the texture to the screen. /// /// /// /// /// /// /// /// /// /// /// public void draw(SpriteBatch spriteBatch, Texture2D texture, Vector2 Position, Rectangle? sourceRectangle, Color drawColor, float rotation, Vector2 origin, Vector2 scale, SpriteEffects spriteEffects, float LayerDepth) { //Log.AsyncC("Animation Manager is working!"); spriteBatch.Draw(texture, Position, sourceRectangle, drawColor, rotation, origin, scale, spriteEffects, LayerDepth); try { this.tickAnimation(); // Log.AsyncC("Tick animation"); } catch (Exception err) { ModCore.ModMonitor.Log(err.ToString()); } } /// /// Used to draw the current animation to the screen. /// /// /// /// /// /// /// public void draw(SpriteBatch b,Vector2 Position,Color drawColor,float scale,SpriteEffects flipped,float depth) { b.Draw(this.objectTexture.texture, Position, this.currentAnimation.sourceRectangle, drawColor, 0f, Vector2.Zero, scale, flipped, depth); try { this.tickAnimation(); // Log.AsyncC("Tick animation"); } catch (Exception err) { ModCore.ModMonitor.Log(err.ToString()); } } public void draw(SpriteBatch b, Vector2 Position, Color drawColor, float scale,float Rotation ,SpriteEffects flipped, float depth) { b.Draw(this.objectTexture.texture, Position, this.currentAnimation.sourceRectangle, drawColor, Rotation, Vector2.Zero, scale, flipped, depth); try { this.tickAnimation(); // Log.AsyncC("Tick animation"); } catch (Exception err) { ModCore.ModMonitor.Log(err.ToString()); } } /// /// Draws the animated texture to the screen. /// /// The Sprite Batch used to draw. /// The position to draw the sprite to. /// The color to draw the sprite to. /// The scale for the sprite as a Vector2. (Width,Height) /// If the sprite is flipped. /// The depth of the sprite. public void draw(SpriteBatch b, Vector2 Position, Color drawColor, Vector2 scale, SpriteEffects flipped, float depth) { b.Draw(this.objectTexture.texture, Position, this.currentAnimation.sourceRectangle, drawColor, 0f, Vector2.Zero, scale, flipped, depth); try { this.tickAnimation(); // Log.AsyncC("Tick animation"); } catch (Exception err) { ModCore.ModMonitor.Log(err.ToString()); } } public void draw(SpriteBatch b, Vector2 Position, Color drawColor, Vector2 scale, float Rotation,SpriteEffects flipped, float depth) { b.Draw(this.objectTexture.texture, Position, this.currentAnimation.sourceRectangle, drawColor, Rotation, Vector2.Zero, scale, flipped, depth); try { this.tickAnimation(); // Log.AsyncC("Tick animation"); } catch (Exception err) { ModCore.ModMonitor.Log(err.ToString()); } } public Texture2DExtended getExtendedTexture() { return this.objectTexture; } public void setExtendedTexture(Texture2DExtended texture) { this.objectTexture = texture; } public void setEnabled(bool enabled) { this.enabled = enabled; } public Texture2D getTexture() { return this.objectTexture.getTexture(); } } }