package org.erikaredmark.monkeyshines;
import java.awt.Graphics;
import org.erikaredmark.monkeyshines.resource.WorldResource;
/**
*
* Represents a specific goodie instance on the map
*
* @author Erika Redmark
*
*/
public class Goodie {
/**
*
* Creates a goodie for the specified screen, for the specified world.
*
* @param type
*
* @param location
*
* @param screenId
*
*/
public static Goodie newGoodie(final Type type,
final ImmutablePoint2D location,
final int screenID,
final WorldResource rsrc) {
return new Goodie(type, location, screenID, rsrc);
}
private Goodie(final Type type, final ImmutablePoint2D location, final int screenId, final WorldResource rsrc) {
this.screenID = screenId;
this.location = location;
this.rsrc = rsrc;
goodieType = type;
taken = false;
dead = false;
yumSprite = -1; // The yumsprite will be set to zero before any drawing goes. This insures the first frame is not skipped.
drawToX = (int)location.x() * GameConstants.GOODIE_SIZE_X;
drawToY = (int)location.y() * GameConstants.GOODIE_SIZE_Y;
drawX = type.getDrawX();
drawY = type.getDrawY();
}
// Very simple animation.
public void update() {
if (!taken && !dead) {
if (readyToAnimate() ) {
if (drawY == 0)
drawY = GameConstants.GOODIE_SIZE_Y;
else
drawY = 0;
}
} else if (taken && !dead) {
yumSprite++;
if (yumSprite >= 15) {
dead = true;
}
}
}
/**
*
* Resets this goodie, making it appear back on the screen, ONLY if it is persistant.
* TODO this method is not currently used
*
*/
public void resetIfApplicable() {
if (goodieType.persistant) {
// Both must reset; together they make up taken, but not finished playing 'Yum', vs not taken.
taken = false;
dead = false;
}
}
/**
*
* Tells the goodie that it has been taken. This starts the 'Yum' animation and plays the appropriate
* sound. A goodie may normally only be taken once. If a goodie is already taken, this method does nothing.
* <p/>
* Some goodies are reset when a level screen is restarted.
*
* @param bonzo
* a reference to bonzo, so that the goodies effects (score and misc.) may be applied to him.
*
* @param world
* a reference to the world, so that goodies effects (keys) can apply there
*
* @return
* {@code true} if the goodie was just taken, {@code false} if it was already taken
*
*/
public boolean take(final Bonzo bonzo, final World world) {
if (taken) return false;
taken = true;
rsrc.getSoundManager().playOnce(goodieType.soundEffect);
// If bonzo under the effects if a powerup? Multiply the score
int extraScore = bonzo.getCurrentPowerup() == null
? goodieType.score
: bonzo.getCurrentPowerup().multiplier() * goodieType.score;
bonzo.incrementScore(extraScore);
goodieType.affectBonzo(this, bonzo, world);
// Finally, if this is a powerup, grant bonzo the powerup.
Powerup powerup = goodieType.powerupForGoodie();
if (powerup != null) {
bonzo.powerupCollected(powerup);
}
return true;
}
public int getScreenID() {
return screenID;
}
/**
*
* Paints the goodie based on state. If the goodie has not been taken yet, it just animates there. Once it is taken,
* it will display the "yum" (taken && !dead) until that animation completes and it is dead, in which from there
* it will not logner be painted.
*
* @param g2d
*/
public void paint(Graphics g2d) {
if (!taken && !dead)
g2d.drawImage(rsrc.getGoodieSheet(), drawToX , drawToY, // Destination 1
drawToX + GameConstants.GOODIE_SIZE_X, drawToY + GameConstants.GOODIE_SIZE_Y, // Destination 2
drawX, drawY, drawX + GameConstants.GOODIE_SIZE_X, drawY + GameConstants.GOODIE_SIZE_Y,
null);
else if (taken && !dead) {
g2d.drawImage(rsrc.getYumSheet(), drawToX , drawToY, // Destination 1
drawToX + GameConstants.GOODIE_SIZE_X, drawToY + GameConstants.GOODIE_SIZE_Y, // Destination 2
yumSprite * GameConstants.GOODIE_SIZE_X, 0, // Source 1
yumSprite * GameConstants.GOODIE_SIZE_X + GameConstants.GOODIE_SIZE_X, GameConstants.GOODIE_SIZE_Y, // Source 2
null);
}
}
/**
* Returns the type of this goodie, representing what kind of goodie it is
*
* @return
* goodie id
*/
public Type getGoodieType() { return goodieType; }
/**
* Returns this goodie's location
*
* @return
* the location this goodie resides on whatever level it is on
*/
public ImmutablePoint2D getLocation() { return location; }
/**
*
* Two goodies are considered equal if they share the same, id, type, and location. State information (
* such as taken or dying) is NOT a factor in equality. Two goodies can NOT share the same id, type, and location
* and yet have different states.
*
*/
@Override public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Goodie) ) return false;
Goodie other = (Goodie) o;
return this.goodieType.equals(other.goodieType)
&& this.screenID == other.screenID
&& this.location.equals(other.location);
}
@Override public int hashCode() {
int result = 17;
result += result * 31 + goodieType.hashCode();
result += result * 31 + screenID;
result += result * 31 + location.hashCode();
return result;
}
private boolean readyToAnimate() {
if (timeToNextFrame >= TICKS_BETWEEN_ANIMATIONS) {
timeToNextFrame = 0;
return true;
} else {
++timeToNextFrame;
return false;
}
}
public enum Type {
// WARNING: xOffset is used as ID for encoding this in save file format!
// Adding the concept of another row to this sprite sheet will require
// modifying that logic!
// Original game had all non essential goodies (ones that didn't do anything) and keys give 100 score, and everything
// else gave nothing. For this port, extra lives will give 200 score.
RED_KEY(0, 100, GameSoundEffect.YUM_COLLECT, false) {
@Override public void affectBonzo(Goodie goodie, Bonzo bonzo, World world) {
world.collectedRedKey(goodie);
}
},
BLUE_KEY(1, 100, GameSoundEffect.YUM_COLLECT, false) {
@Override public void affectBonzo(Goodie goodie, Bonzo bonzo, World world) {
world.collectedBlueKey(goodie);
}
},
APPLE(2, 100, GameSoundEffect.YUM_COLLECT, false),
ORANGE(3, 100, GameSoundEffect.YUM_COLLECT, false),
PEAR(4, 100,GameSoundEffect.YUM_COLLECT, false),
PURPLE_GRAPES(5, 100, GameSoundEffect.YUM_COLLECT, false),
BLUE_GRAPES(6, 100, GameSoundEffect.YUM_COLLECT, false),
BANANA(7, 100, GameSoundEffect.YUM_COLLECT, false),
ENERGY(8, 0, GameSoundEffect.POWERUP_SHIELD, false) {
@Override public void affectBonzo(Goodie goodie, Bonzo bonzo, World world) {
bonzo.incrementHealth(GameConstants.LIFE_INCREASE);
}
},
X2MULTIPLIER(9, 0, GameSoundEffect.POWERUP_SHIELD, false) {
@Override public Powerup powerupForGoodie() { return Powerup.X2; }
},
WHITE_MELRODE_WINGS(10, 0, GameSoundEffect.POWERUP_WING, true) {
@Override public Powerup powerupForGoodie() { return Powerup.WING; }
},
SHIELD(11, 0, GameSoundEffect.POWERUP_SHIELD, true) {
@Override public Powerup powerupForGoodie() { return Powerup.SHIELD; }
},
EXTRA_LIFE(12, 200, GameSoundEffect.POWERUP_EXTRA_LIFE, false) {
@Override public void affectBonzo(Goodie goodie, Bonzo bonzo, World world) {
bonzo.incrementLives(1);
}
},
X3MULTIPLIER(13, 0, GameSoundEffect.POWERUP_SHIELD, false) {
@Override public Powerup powerupForGoodie() { return Powerup.X3; }
},
X4MULTIPLIER(14, 0, GameSoundEffect.POWERUP_SHIELD, false) {
@Override public Powerup powerupForGoodie() { return Powerup.X4; }
};
private final int xOffset;
public final int score;
private final GameSoundEffect soundEffect;
private final boolean persistant;
/**
*
* @param xOffset
* offset in the spirtesheet for drawing, in units (basically id)
*
* @param score
* score that will be added to bonzo upon grabbing this goodie
*
* @param soundEffect
* sound effect played when bonzo grabs goodie
*
* @param persistant
* {@code true} to allow the goodie to return to the world when the screen
* it is on is reset (leaving the scren, bonzo dying) {@code false} if otherwise
*
*/
private Type(final int xOffset, final int score, final GameSoundEffect soundEffect, final boolean persistant) {
this.xOffset = xOffset;
this.score = score;
this.soundEffect = soundEffect;
this.persistant = persistant;
}
/**
*
* Returns the x position in the sprite sheet that goodies of this type draw from.
*
* @return
*/
public int getDrawX() { return xOffset * GameConstants.GOODIE_SIZE_X; }
/**
*
* Returns the y position in the sprite sheet that goodies of this type draw from
*
* @return
*/
public int getDrawY() { return 0; }
/**
*
* Returns a goodie type based on the given index value. Goodies are numbered 0-14, and when reference in the
* sprite sheet their 'id' is effectively their 'x offset * tile size' in the sprite sheet. Intended only for
* systems where an id is required to be resolved, such as the level editor or the save file format for id
* resolution.
*
* @param val
* the integer value
*
* @return
* the goodie type for this id
*
* @throws IllegalArgumentException
* if the given id is not within the 0-14 bounds
*
*/
public static Type byValue(int val) {
for (Type g : Type.values() ) {
if (g.xOffset == val) return g;
}
throw new IllegalArgumentException("Value out of range");
}
/**
*
* Returns the powerup that this goodie represents. If this goodie is not a powerup, returns {@code null}
*
* @return
* powerup for goodie or {@code null} if goodie not a powerup
*
*/
public Powerup powerupForGoodie() { return null; }
/**
*
* Returns the numerical id of the enumeration. This is used for encoding the goodie
* for .world files.
*
* @return
* unique id for enumeration.
*
*/
public int id() {
// Id == xOffset.
return xOffset;
}
/**
*
* Some goodies produce different effects on bonzo. The default is to do nothing (except increment
* any, if valid, score).
*
* @param goodie
* reference to the actual goodie itself
*
* @param bonzo
* reference to bonzo to affect him
*
* @param world
* reference to world in case effects a more global, such as red and blue keys
*
*/
public void affectBonzo(Goodie goodie, Bonzo bonzo, World world) { /* No op by default, overriden where required */ }
}
// These values indicate starting information for object construction. ONLY THESE
// THREE VALUES determine object equality and hashcode.
private final Type goodieType;
private final int screenID;
private final ImmutablePoint2D location;
// The sheet that contains all the goodies
// These values represent game state information
// Taken means gone but animating the YUM. Dead means gone.
private boolean taken;
private boolean dead;
private int yumSprite;
// Drawing info
private int drawToX;
private int drawToY;
private int drawX;
private int drawY;
private WorldResource rsrc;
// When this reaches TICKS_BETWEEN_ANIMATIONS, the y-level for drawing swaps between either the top
// row or the bottom row.
private int timeToNextFrame = 0;
private static final int TICKS_BETWEEN_ANIMATIONS = 5;
}