package util; import java.awt.Dimension; import java.awt.Point; import java.awt.Rectangle; import vooga.fighter.model.objects.AttackObject; import vooga.fighter.model.objects.GameObject; /** * 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. * Useful for collision detection and handling, as well as animation. * * @author James Wei * */ public class State { private static final String myDisabledHitboxError = "Hitbox parameter is disabled"; private static final String myDisabledImageError = "Image parameter is disabled"; private static final String myDisabledSizeError = "Size parameter is disabled"; private static final String myStateExpiredError = "Cannot update nonlooping state" + "once it has completed, either reset state or set looping to true"; public int myNumFrames; public int myCurrentFrame; /** * 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 Rectangle[] myHitboxes; private Pixmap[] myImages; private Dimension[] mySizes; private Integer[] myFrameDelays; /** * Owner of this State, i.e. the object that this State object describes. */ private Object myOwner; /** * Booleans for whether images, shapes, and sizes are disabled. Disabling * one of these three will prevent the respective array from being populated, * and prevents accesses to that array. */ private boolean myHitboxEnabled; private boolean myImageEnabled; private boolean mySizeEnabled; /** * Creates a new state that is a deep copy of another. The only parameters * not copied are current frame and delay. */ public State(State other) { this(null, other.myNumFrames); myImageEnabled = other.myImageEnabled; myHitboxEnabled = other.myHitboxEnabled; mySizeEnabled = other.mySizeEnabled; myLooping = other.myLooping; myOwner = other.myOwner; for (int i=0; i<myNumFrames; i++) { try { Rectangle newRectangle = new Rectangle(other.myHitboxes[i]); populateHitbox(newRectangle, i); } catch (StateParameterDisabledException e) { } try { Pixmap newPixmap = new Pixmap(other.myImages[i]); populateImage(newPixmap, i); } catch (StateParameterDisabledException e) { } try { Dimension newSize = new Dimension(other.mySizes[i]); populateSize(newSize, i); } catch (StateParameterDisabledException e) { } } } /** * Creates a state with the given owner, number of frames, priority, and depth. * All frame delays zero by default. All state parameters (image, hitbox, and size) * enabled by default. Looping disabled by default. */ public State(Object owner, int numFrames) { myOwner = owner; myNumFrames = numFrames; myHitboxes = new Rectangle[myNumFrames]; myImages = new Pixmap[myNumFrames]; mySizes = new Dimension[myNumFrames]; myFrameDelays = new Integer[myNumFrames]; myCurrentFrame = 0; myDelay = 0; myLooping = false; myHitboxEnabled = true; myImageEnabled = true; mySizeEnabled = true; populateAllDelays(0); } /** * Sets the owner of this State. */ public void setOwner(Object 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 hitbox this state's hitbox array. */ public void populateHitbox(Rectangle rect, int index) throws StateParameterDisabledException { if (!myHitboxEnabled) { throw new StateParameterDisabledException(myDisabledHitboxError); } myHitboxes[index] = rect; } /** * Adds a image into this state's image array. */ public void populateImage(Pixmap image, int index) throws StateParameterDisabledException { if (!myImageEnabled) { throw new StateParameterDisabledException(myDisabledImageError); } myImages[index] = image; } /** * Adds a size into this state's size array. */ public void populateSize(Dimension size, int index) throws StateParameterDisabledException { if (!mySizeEnabled) { throw new StateParameterDisabledException(myDisabledSizeError); } 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) throws StateParameterDisabledException { if (!mySizeEnabled) { throw new StateParameterDisabledException(myDisabledSizeError); } for (int i=0; i<mySizes.length; i++) { mySizes[i] = size; } } /** * Shortcut method for populating the entire size array with the size of the * image stored in the same frame. This should be called after the image * array is populated. If this method encounters a null image, it will * populate the corresponding size array element with a null size. */ public void populateAllSizesWithImages() throws StateParameterDisabledException { if (!mySizeEnabled) { throw new StateParameterDisabledException(myDisabledSizeError); } for (int i=0; i<mySizes.length; i++) { if (myImages[i] == null) { mySizes[i] = null; } else { mySizes[i] = myImages[i].getSize(); } } } /** * Shortcut method for populating the entire delay array. Useful if a state * never changes frame delay throughout its animation. */ public void populateAllDelays(Integer delay) { if (delay < 0) { return; } for (int i=0; i<myFrameDelays.length; i++) { myFrameDelays[i] = delay; } } /** * Returns the current active hitbox for this state. */ public Rectangle getCurrentHitbox() throws StateParameterDisabledException { if (!myHitboxEnabled) { throw new StateParameterDisabledException(myDisabledHitboxError); } return myHitboxes[myCurrentFrame]; } /** * Returns the current active image for this state. */ public Pixmap getCurrentImage() throws StateParameterDisabledException { if (!myImageEnabled) { throw new StateParameterDisabledException(myDisabledImageError); } return myImages[myCurrentFrame]; } /** * Returns the current active size for this state. */ public Dimension getCurrentSize() throws StateParameterDisabledException { if (!mySizeEnabled) { throw new StateParameterDisabledException(myDisabledSizeError); } return mySizes[myCurrentFrame]; } /** * Returns the owner of this state. */ public Object getOwner() { return myOwner; } /** * Returns true if hitboxes are enabled for this State. */ public boolean checkHitboxEnabled() { return myHitboxEnabled; } /** * Returns true if images are enabled for this State. */ public boolean checkImageEnabled() { return myImageEnabled; } /** * Returns true if sizes are enabled for this State. */ public boolean checkSizeEnabled() { return mySizeEnabled; } /** * Returns true if the state has been properly initialized, i.e. if all arrays * have been properly populated. Frame delays are not checked, nor are any * fields that have been disabled. Useful for debugging. */ public boolean checkInitialization() { for (int i=0; i<myNumFrames; i++) { if (mySizes[i] == null && mySizeEnabled) { return false; } if (myHitboxes[i] == null && myHitboxEnabled) { return false; } if (myImages[i] == null && myImageEnabled) { 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() throws StateExpiredException { if (hasCompleted() && !myLooping) { throw new StateExpiredException(myStateExpiredError); } 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); } }