Core/src/main/java/eu/univento/core/api/entity/ArmorStandAnimator.java

396 lines
15 KiB
Java

/*
* Copyright (c) 2017 univento.eu - All rights reserved
* You are not allowed to use, distribute or modify this code
*/
package eu.univento.core.api.entity;
import org.bukkit.Location;
import org.bukkit.entity.ArmorStand;
import org.bukkit.util.EulerAngle;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class ArmorStandAnimator {
/**
* This is a map containing the already loaded frames. This way we don't have to parse the same animation over and over.
*/
private static Map<String, Frame[]> animCache = new HashMap<>();
/**
* This is a list with all the animator instances. This makes it easy to update all the instances at one.
*/
private static Set<ArmorStandAnimator> animators = new HashSet<>();
/** This void updates all the animator instances at once */
public static void updateAll() {
for (ArmorStandAnimator ani : animators) {
ani.update();
}
}
/** Returns all the animator instances */
public static Set<ArmorStandAnimator> getAnimators() {
return animators;
}
/** Clears the animation cache in case you want to update an animation */
public static void clearCache() {
animCache.clear();
}
/** The armor stand to animate */
private ArmorStand armorStand;
/** The amount of frames this animation has */
private int length;
/** All the frames of the animation */
private Frame[] frames;
/** Says when the animation is paused */
private boolean paused = false;
/** The current frame we're on */
private int currentFrame;
/** The start location of the animation */
private Location startLocation;
/** If this is true. The animator is going to guess the frames that aren't specified */
private boolean interpolate = false;
/**
* Constructor of the animator. Takes in the path to the file with the animation and the armor stand to animate.
*
* @param aniFile
* @param armorStand
*/
public ArmorStandAnimator(File aniFile, ArmorStand armorStand) {
// set all the stuff
this.armorStand = armorStand;
startLocation = armorStand.getLocation();
// checks if the file has been loaded before. If so return the cached version
if (animCache.containsKey(aniFile.getAbsolutePath())) {
frames = animCache.get(aniFile.getAbsolutePath());
} else {
// File has not been loaded before so load it.
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader(aniFile));
String line = "";
// create the current frame variable
Frame currentFrame = null;
while ((line = br.readLine()) != null) {
// set the length
if (line.startsWith("length")) {
length = (int) Float.parseFloat(line.split(" ")[1]);
frames = new Frame[length];
}
// sets the current frame
else if (line.startsWith("frame")) {
if (currentFrame != null) {
frames[currentFrame.frameID] = currentFrame;
}
int frameID = Integer.parseInt(line.split(" ")[1]);
currentFrame = new Frame();
currentFrame.frameID = frameID;
}
// check if we need to interpolate
else if (line.contains("interpolate")) {
interpolate = true;
}
// sets the position and rotation or the main armor stand
else if (line.contains("Armorstand_Position")) {
currentFrame.x = Float.parseFloat(line.split(" ")[1]);
currentFrame.y = Float.parseFloat(line.split(" ")[2]);
currentFrame.z = Float.parseFloat(line.split(" ")[3]);
currentFrame.r = Float.parseFloat(line.split(" ")[4]);
}
// sets the rotation for the middle
else if (line.contains("Armorstand_Middle")) {
float x = (float) Math.toRadians(Float.parseFloat(line.split(" ")[1]));
float y = (float) Math.toRadians(Float.parseFloat(line.split(" ")[2]));
float z = (float) Math.toRadians(Float.parseFloat(line.split(" ")[3]));
currentFrame.middle = new EulerAngle(x, y, z);
}
// sets the rotation for the right leg
else if (line.contains("Armorstand_Right_Leg")) {
float x = (float) Math.toRadians(Float.parseFloat(line.split(" ")[1]));
float y = (float) Math.toRadians(Float.parseFloat(line.split(" ")[2]));
float z = (float) Math.toRadians(Float.parseFloat(line.split(" ")[3]));
currentFrame.rightLeg = new EulerAngle(x, y, z);
}
// sets the rotation for the left leg
else if (line.contains("Armorstand_Left_Leg")) {
float x = (float) Math.toRadians(Float.parseFloat(line.split(" ")[1]));
float y = (float) Math.toRadians(Float.parseFloat(line.split(" ")[2]));
float z = (float) Math.toRadians(Float.parseFloat(line.split(" ")[3]));
currentFrame.leftLeg = new EulerAngle(x, y, z);
}
// sets the rotation for the left arm
else if (line.contains("Armorstand_Left_Arm")) {
float x = (float) Math.toRadians(Float.parseFloat(line.split(" ")[1]));
float y = (float) Math.toRadians(Float.parseFloat(line.split(" ")[2]));
float z = (float) Math.toRadians(Float.parseFloat(line.split(" ")[3]));
currentFrame.leftArm = new EulerAngle(x, y, z);
}
// sets the rotation for the right arm
else if (line.contains("Armorstand_Right_Arm")) {
float x = (float) Math.toRadians(Float.parseFloat(line.split(" ")[1]));
float y = (float) Math.toRadians(Float.parseFloat(line.split(" ")[2]));
float z = (float) Math.toRadians(Float.parseFloat(line.split(" ")[3]));
currentFrame.rightArm = new EulerAngle(x, y, z);
}
// sets the rotation for the head
else if (line.contains("Armorstand_Head")) {
float x = (float) Math.toRadians(Float.parseFloat(line.split(" ")[1]));
float y = (float) Math.toRadians(Float.parseFloat(line.split(" ")[2]));
float z = (float) Math.toRadians(Float.parseFloat(line.split(" ")[3]));
currentFrame.head = new EulerAngle(x, y, z);
}
}
if (currentFrame != null) {
frames[currentFrame.frameID] = currentFrame;
}
} catch (Exception ex) {
ex.printStackTrace();
} finally {
// make sure to close the stream!
if (br != null) {
try {
br.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
// add the animation to the cache, else adding the whole cache thing has no point.
animCache.put(aniFile.getAbsolutePath(), frames);
}
// register this instance of the animator
animators.add(this);
}
/**
* This method removes this instance from the animator instances list. When you don't want to use this instance any more, you can call this method.
*/
public void remove() {
animators.remove(this);
}
/** Pauses the animation */
public void pause() {
paused = true;
}
/**
* Pauses the animation and sets the current frame to 0. It also updates the animation one more time to set the armor stand to the first frame.
*/
public void stop() {
// set the current frame to 0 and update the frame and set it to 0 again
currentFrame = 0;
update();
currentFrame = 0;
paused = true;
}
/** Plays the animation */
public void play() {
paused = false;
}
/** Updates the animation and goes to the next frame */
public void update() {
// make sure that the animation isn't paused
if (!paused) {
// makes sure that the frame is in bounds
if (currentFrame >= (length - 1) || currentFrame < 0) {
currentFrame = 0;
}
// get the frame
Frame f = frames[currentFrame];
//checks if we need to interpolate. If so interpolate.
if(interpolate) {
if(f == null) {
f = interpolate(currentFrame);
}
}
// make sure it's not null
if (f != null) {
// get the new location
Location newLoc = startLocation.clone().add(f.x, f.y, f.z);
newLoc.setYaw(f.r + newLoc.getYaw());
// set all the values
armorStand.teleport(newLoc);
armorStand.setBodyPose(f.middle);
armorStand.setLeftLegPose(f.leftLeg);
armorStand.setRightLegPose(f.rightLeg);
armorStand.setLeftArmPose(f.leftArm);
armorStand.setRightArmPose(f.rightArm);
armorStand.setHeadPose(f.head);
}
// go one frame higher
currentFrame++;
}
}
/** Returns the current frame */
public int getCurrentFrame() {
return currentFrame;
}
/** Sets the current frame */
public void setCurrentFrame(int currentFrame) {
this.currentFrame = currentFrame;
}
/** Returns the armor stand this instance animates */
public ArmorStand getArmorStand() {
return armorStand;
}
/** Returns the amount of frame this animation has */
public int getLength() {
return length;
}
/** Returns the list of frames */
public Frame[] getFrames() {
return frames;
}
/** Returns if the animation is paused */
public boolean isPaused() {
return paused;
}
/** Gets the start location */
public Location getStartLocation() {
return startLocation;
}
/**
* Sets the start location. If you want to teleport the armor stand this is the recommended function
*
* @param location
*/
public void setStartLocation(Location location) {
startLocation = location;
}
/** Returns interpolate */
public boolean isInterpolated() {
return interpolate;
}
/** Sets interpolate */
public void setInterpolated(boolean interpolate) {
this.interpolate = interpolate;
}
/**Returns an interpolated frame*/
private Frame interpolate(int frameID) {
//get the minimum and maximum frames that are the closest
Frame minFrame = null;
for (int i = frameID; i >= 0; i--) {
if (frames[i] != null) {
minFrame = frames[i];
break;
}
}
Frame maxFrame = null;
for (int i = frameID; i < frames.length; i++) {
if (frames[i] != null) {
maxFrame = frames[i];
break;
}
}
//make sure that those frame weren't the last one
Frame res = null;
if(maxFrame == null || minFrame == null) {
if(maxFrame == null && minFrame != null) {
return minFrame;
}
if(minFrame == null && maxFrame != null) {
return maxFrame;
}
res = new Frame();
res.frameID = frameID;
return res;
}
//create the frame and interpolate
res = new Frame();
res.frameID = frameID;
//this part calculates the distance the current frame is from the minimum and maximum frame and this allows for an easy linear interpolation
float Dmin = frameID - minFrame.frameID;
float D = maxFrame.frameID - minFrame.frameID;
float D0 = Dmin / D;
res = minFrame.mult(1 - D0, frameID).add(maxFrame.mult(D0, frameID), frameID);
return res;
}
/**
* The frame class. This class holds all the information of one frame.
*/
public static class Frame {
/**The Frame ID*/
int frameID;
/**the location and rotation*/
float x, y, z, r;
/**The rotation of the body parts*/
EulerAngle middle;
EulerAngle rightLeg;
EulerAngle leftLeg;
EulerAngle rightArm;
EulerAngle leftArm;
EulerAngle head;
/**This multiplies every value with another value.
* Used for interpolation
* @param a
* @param frameID
* @return
*/
public Frame mult(float a, int frameID) {
Frame f = new Frame();
f.frameID = frameID;
f.x = f.x * a;
f.y = f.y * a;
f.z = f.z * a;
f.r = f.r * a;
f.middle = new EulerAngle(middle.getX() * a, middle.getY() * a, middle.getZ() * a);
f.rightLeg = new EulerAngle(rightLeg.getX() * a, rightLeg.getY() * a, rightLeg.getZ() * a);
f.leftLeg = new EulerAngle(leftLeg.getX() * a, leftLeg.getY() * a, leftLeg.getZ() * a);
f.rightArm = new EulerAngle(rightArm.getX() * a, rightArm.getY() * a, rightArm.getZ() * a);
f.leftArm = new EulerAngle(leftArm.getX() * a, leftArm.getY() * a, leftArm.getZ() * a);
f.head = new EulerAngle(head.getX() * a, head.getY() * a, head.getZ() * a);
return f;
}
/**This adds a value to every value.
* Used for interpolation
* @param a
* @param frameID
* @return
*/
public Frame add(Frame a, int frameID) {
Frame f = new Frame();
f.frameID = frameID;
f.x = f.x + a.x;
f.y = f.y + a.y;
f.z = f.z + a.z;
f.r = f.r + a.r;
f.middle = new EulerAngle(middle.getX() + a.middle.getX(), middle.getY() + a.middle.getY(), middle.getZ() + a.middle.getZ());
f.rightLeg = new EulerAngle(rightLeg.getX() + a.rightLeg.getX(), rightLeg.getY() + a.rightLeg.getY(), rightLeg.getZ() + a.rightLeg.getZ());
f.leftLeg = new EulerAngle(leftLeg.getX() + a.leftLeg.getX(), leftLeg.getY() + a.leftLeg.getY(), leftLeg.getZ() + a.leftLeg.getZ());
f.rightArm = new EulerAngle(rightArm.getX() + a.rightArm.getX(), rightArm.getY() + a.rightArm.getY(), rightArm.getZ() + a.rightArm.getZ());
f.leftArm = new EulerAngle(leftArm.getX() + a.leftArm.getX(), leftArm.getY() + a.leftArm.getY(), leftArm.getZ() + a.leftArm.getZ());
f.head = new EulerAngle(head.getX() + a.head.getX(), head.getY() + a.head.getY(), head.getZ() + a.head.getZ());
return f;
}
}
}