package vooga.fighter.model.utils;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import vooga.fighter.model.objects.GameObject;
import util.Location;
import util.Pixmap;
/**
* Holds a series of rectangles and images representing one state of an object.
* Additionally holds a series of dimensions representing the size of each frame,
* as well as a series of integers representing frame delay for each frame.
* Used for collision detection and handling, as well as animation.
*
* @author James Wei, David Le
*
*/
public class State {
public int myNumFrames;
public int myCurrentFrame;
/**
* The priority of the hitbox when interacting with other hitboxes.
* Lower numbers indicate higher priority.
*/
private int myPriority;
/**
* The depth of the animations when overlapping with other animations.
* Lower numbers indicate "deeper" images, aka images with lower depth
* are drawn first (and thus other animations are drawn on top of them).
*/
private int myDepth;
/**
* True if the animation for this state loops, false otherwise.
*/
private boolean myLooping;
/**
* Maintains current frame delay count. Used to remain in the same frame of
* an animation for more than one game loop.
*/
private int myDelay;
private Pixmap[] myImages;
private Rectangle[] myRectangles;
private Dimension[] mySizes;
private Integer[] myFrameDelays;
private GameObject myOwner;
/**
* Creates a state with the given owner, number of frames, and default priority
* and depth of zero. State has default frame delays of zero. Looping set to
* true by default.
*/
public State(GameObject owner, int numFrames) {
this(owner, numFrames, 0, 0);
}
/**
* Creates a new state that is a deep copy of another. Note that the owner of the
* deep copy must be explicitly set outside of this constructor by the calling
* object.
*/
public State(State other) {
this(null, other.getNumFrames(), other.getPriority(), other.getDepth());
for (int i=0; i<myNumFrames; i++) {
Rectangle newRectangle = new Rectangle(other.getRectangle(i));
Pixmap newPixmap = new Pixmap(other.getImage(i));
Dimension newSize = new Dimension(other.getSize(i));
populateRectangle(newRectangle, i);
populateImage(newPixmap, i);
populateSize(newSize, i);
}
}
/**
* Creates a state with the given owner, number of frames, priority, and depth.
* State has default frame delays of zero. Looping set to false by default.
*/
public State(GameObject owner, int numFrames, int priority, int depth) {
myOwner = owner;
myNumFrames = numFrames;
myPriority = priority;
myDepth = depth;
mySizes = new Dimension[myNumFrames];
myRectangles = new Rectangle[myNumFrames];
myImages = new Pixmap[myNumFrames];
myFrameDelays = new Integer[myNumFrames];
myCurrentFrame = 0;
myDelay = 0;
myLooping = false;
populateAllDelays(0);
}
/**
* Sets the owner of this State.
*/
public void setOwner(GameObject owner) {
myOwner = owner;
}
/**
* Sets looping boolean. If this method is not called, looping defaults to false.
*/
public void setLooping(boolean looping) {
myLooping = looping;
}
/**
* Returns the number of frames in this state.
*/
public int getNumFrames() {
return myNumFrames;
}
/**
* Adds a rectangle this state's rectangle array.
*/
public void populateRectangle(Rectangle rect, int index) {
myRectangles[index] = rect;
}
/**
* Adds a Pixmap into this state's Pixmap array.
*/
public void populateImage(Pixmap image, int index) {
myImages[index] = image;
mySizes[index] = image.getSize();
}
/**
* Adds a Dimension into this state's Dimension array.
*/
public void populateSize(Dimension size, int index) {
mySizes[index] = size;
}
/**
* Adds a frame delay represented by an Integer into this state's Integer
* array. By default, all frame delays are 0. If a frame has a delay greater
* than 0, then that frame will persist for an extra number of frames equal
* to the frame delay. Negative delays are not allowed.
*/
public void populateFrameDelay(Integer delay, int index) {
if (delay < 0) {
return;
}
myFrameDelays[index] = delay;
}
/**
* Shortcut method for populating the entire size array. Useful if a state
* never changes sizes throughout its animation.
*/
public void populateAllSizes(Dimension size) {
for (int i=0; i<mySizes.length; i++) {
mySizes[i] = size;
}
}
/**
* Shortcut method for populating the entire delay array. Useful if a state
* never changes frame delay throughout its animation.
*/
public void populateAllDelays(Integer delay) {
for (int i=0; i<myFrameDelays.length; i++) {
myFrameDelays[i] = delay;
}
}
/**
* Returns the current active rectangle for this state.
*/
public Rectangle getCurrentRectangle() {
Rectangle currentRect = myRectangles[myCurrentFrame];
if (currentRect == null) {
return new Rectangle();
}
Location location = myOwner.getLocation().getLocation();
Dimension size = currentRect.getSize();
Point origin = new Point((int) location.getX() - (int) size.getWidth()/2,
(int) location.getY() - (int) size.getHeight()/2);
Rectangle result = new Rectangle(origin, size);
return result;
}
/**
* Returns the current active image for this state.
*/
public Pixmap getCurrentImage() {
return myImages[myCurrentFrame];
}
/**
* Returns the current active size for this state.
*/
public Dimension getCurrentSize() {
return mySizes[myCurrentFrame];
}
/**
* Returns the current active rectangle for this state.
*/
public Rectangle getRectangle(int index) {
return myRectangles[index];
}
/**
* Returns the current active image for this state.
*/
public Pixmap getImage(int index) {
return myImages[index];
}
/**
* Returns the current active size for this state.
*/
public Dimension getSize(int index) {
return mySizes[index];
}
/**
* Returns the priority of this state. Lower numbers are considered higher
* priority.
*/
public int getPriority() {
return myPriority;
}
/**
* Sets priority for this state.
*/
public void setPriority(int num){
myPriority = num;
}
/**
* Returns the depth of this state. Lower numbers are considered lower depth,
* i.e. an image with a lower depth will be drawn first (and thus other
* images will be drawn on top of it).
*/
public int getDepth() {
return myDepth;
}
/**
* Returns the owner of this state.
*/
public GameObject getOwner() {
return myOwner;
}
/**
* Returns true if this state's hitbox has priority over another, false otherwise.
* Note that returning false DOES NOT necessarily indicate that the other
* hitbox has priority, as the two could have equal priorities.
*/
public boolean hasPriority(State other) {
int difference = myPriority - other.getPriority();
return (difference < 0);
}
/**
* Returns true if this state's image is deeper than another, false otherwise.
* Note that returning false DOES NOT necessarily indicate that the other
* image is deeper, as the two could have equal depths.
*/
public boolean hasDepth(State other) {
int difference = myDepth - other.getDepth();
return (difference < 0);
}
/**
* Returns true if the state has been properly initialized, i.e. if all arrays
* have been properly populated. Since frame delay array is populated with zeroes
* by default, it is not checked. Useful for debugging.
*/
public boolean checkInitialization() {
for (int i=0; i<myNumFrames; i++) {
if (mySizes[i] == null || myImages[i] == null || myRectangles[i] == null) {
return false;
}
}
return true;
}
/**
* Progresses this state to the next frame in its animation and hitbox. If the
* state progresses past its final frame and is looping, it will reset. Note
* that it is possible for a state to progress past its final frame--this is
* the moment when the state is considered "complete". It is incumbent on the
* developer to properly handle state switching when this occurs if the state
* is not looping, or exceptions will be encountered.
*/
public void update() {
if (myDelay == myFrameDelays[myCurrentFrame]) {
myCurrentFrame++;
myDelay = 0;
} else {
myDelay++;
}
if (hasCompleted() && myLooping) {
resetState();
}
}
/**
* Resets the current frame to the beginning of the state.
*/
public void resetState() {
myCurrentFrame = 0;
}
/**
* Returns true if the state's animation has concluded, false otherwise.
* Concluded in this sense means after the final animation has updated, i.e.
* a concluded state has progressed to a frame beyond the number of frames
* it actually has.
*/
public boolean hasCompleted() {
return (myCurrentFrame >= myNumFrames);
}
}