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

418 lines
16 KiB
Java

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;
/**
* The original thread that this code belongs to can be found here:
* https://www.spigotmc.org/threads/armor-stand-animator-class.152863/
* MIT License
Copyright (c) 2016 Bram Stout
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
* @author Bram
*
*/
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;
}
}
}