package org.erikaredmark.monkeyshines; import java.awt.Graphics2D; import java.util.List; import org.erikaredmark.monkeyshines.editor.HazardMutable; import org.erikaredmark.monkeyshines.resource.WorldResource; import com.google.common.collect.ImmutableList; import com.google.common.primitives.Ints; /** * * Represents a hazard type in a world. Each world defines its own hazards. Hazards have an id (used to indicate which * graphic to display) along with other properties that affect how the hazard operates. This controls what death animation * bonzo goes through, and whether the hazard should explode on contact. * <p/> * Note that hazards that explode, whether killing bonzo or if he has a shield just exploding, come back after a screen is * re-entered. * <p/> * Instances of this class are immutable. Hazards are comparable to other hazards by id. The actual hazard on the world * is controlled by {@code HazardTile}, which contains the mutable state data. * <p/> * * @author Erika Redmark * */ public final class Hazard implements Comparable<Hazard> { private final int id; private final boolean explodes; private final DeathAnimation deathAnimation; // if harmless, death animation should be ignored as bonzo can't die from it. private final boolean harmless; /** * * Constructs a new hazard object for the world * * @param id * the id of the hazard. This maps to the column in the hazard resource as to which set of two icons to animate * between to indicate this hazard is in the world * * @param explodes * this hazard explodes. This means that, upon contact with bonzo, an explosion graphic will form and the hazard * will vanish from the screen (it will return if the screen is reset) * * @param deathAnimation * the death animation that should be played when this hazard is contacted with bonzo * * @param harmless * if {@code true}, this hazard cannot kill bonzo and the deathAnimation is ignored. * * @param rsrc * graphics resource for the world this hazard is in so it may perform painting routines * */ public Hazard(final int id, final boolean explodes, final DeathAnimation deathAnimation, final boolean harmless) { this.id = id; this.explodes = explodes; this.deathAnimation = deathAnimation; this.harmless = harmless; } /** * * Creates a copy of this hazard that is mutable. The returned mutable object can not modify this immutable object. * * @return * a mutable version of this object that can not affect the original * */ public HazardMutable mutableCopy() { return new HazardMutable(this.id, this.explodes, this.deathAnimation, this.harmless); } public DeathAnimation getDeathAnimation() { return deathAnimation; } public int getId() { return id; } public boolean getExplodes() { return explodes; } /** * * If a hazard is harmless, it will not affect him in any way. This does NOT affect the explodes attribute. A harmless * hazard can explode. It will just be like if Bonzo had a shield. * <p/> * Note: The original game had harmless hazards that locked Bonzo's sprite animation and made it look like he was 'sliding'. * This port does NOT keep that (arguably a bug) effect. * * @return * {@code true} if the hazard is harmless and should not kill Bonzo, {@code false} if otherwise * */ public boolean isHarmless() { return harmless; } /** * * Creates the initial hazards for the world based on the number of hazards to create. Each hazard defaults to being a * 'bomb'. It explodes and has the burn death animation. Default created hazards are never 'harmless' * <p/> * Start indicates at what Id the hazards will be created starting at. This is typically 0 for a new set and a higher number * for adding to an existing set. * * @param start * starting id, inclusive, of the generated hazards * * @param count * number of hazards to create. This is typically obtained via the graphics sprite sheet. * * @param rsrc * the number of hazards created is dependent on the graphics context's ability to paint them. * Measurement information is needed from it. * * @return * list of newly created hazards. The list is immutable. The list contains {@code count} elements with hazard ids ordered from {@code start} * to {@code start + (count - 1)} * * */ public static ImmutableList<Hazard> initialise(int start, int count, WorldResource rsrc) { ImmutableList.Builder<Hazard> hazards = new ImmutableList.Builder<>(); for (int i = 0; i < count; i++) { hazards.add(new Hazard(start + i, true, DeathAnimation.BURN, false) ); } return hazards.build(); } /** * * Determines if the given hazard explodes. Exploding hazards always do so when touched by Bonzo, regardless of * him having a shield or being harmless (they just won't kill him in those cases) * * @return * {@code true} if this hazard explodes, {@code false} if otherwise * */ public boolean explodes() { return this.explodes; } /** * * Paints this hazard to the given graphics context at the given cordinates. This is used by {@code HazardTile}, which * will compute and provide the position data/graphics context that this needs to draw on. The hazard object itself merely * provides the reference to the graphics pointer for the world hazards. * * @param g2 * * @param drawToX * x location to draw (in pixels) * * @param drawToY * y location to draw (in pixels) * * @param animationStep * {@code 0} for first frame of hazard, {@code 1} for second. Other values will produce incorrect behaviour * */ public void paint(Graphics2D g2d, int drawToX, int drawToY, WorldResource rsrc, int animationStep) { assert animationStep == 0 || animationStep == 1; int drawFromX = id * GameConstants.TILE_SIZE_X; int drawFromY = animationStep * GameConstants.TILE_SIZE_Y; g2d.drawImage(rsrc.getHazardSheet(), drawToX , drawToY, // Destination 1 (top left) drawToX + GameConstants.TILE_SIZE_X, drawToY + GameConstants.TILE_SIZE_Y, // Destination 2 (bottom right) drawFromX, drawFromY, // Source 1 (top Left) drawFromX + GameConstants.TILE_SIZE_X, drawFromY + GameConstants.TILE_SIZE_Y, // Source 2 (bottom right) null); } /** * * Given a list of hazard in which a hazard of the same id as the replacement exists, replaces that hazard with the new replacement * hazard. * <p/> * The list must be mutable, as it will be mutated by removing a reference to whatever hazard of that id already existed and replacing * it with the replacement * * @param existingHazards * original list of hazards * * @param replacement * hazard to use to replace existing hazard of the same id * * @throws IllegalArgumentException * if no hazard exists of the id of the replacement * */ public static void replaceHazard(List<Hazard> existingHazards, Hazard replacement) { // Find index of old hazard int placementIndex = -1; int index = 0; for (Hazard h : existingHazards) { if (h.getId() == replacement.getId() ) { placementIndex = index; break; } ++index; } if (placementIndex == -1) throw new IllegalArgumentException("No replacement hazard of id " + replacement.getId() ); // Add replacement to same index existingHazards.remove(placementIndex); existingHazards.add(placementIndex, replacement); } /** * * Orders hazards in ascending form based on their id. <strong> inconsistent with equals</strong> as this only cares * about the numerical ordering of id and not graphics or other fields which can't be logically ordered and are normally * equal in circumstances where this is used to sort a list. * */ @Override public int compareTo(final Hazard that) { if (this == that) return 0; return Ints.compare(this.id, that.id); } /** * * Equality of hazards depend on having the same values to all fields. * */ @Override public boolean equals(final Object obj) { if (this == obj) return true; if (!(obj instanceof Hazard) ) return false; final Hazard that = (Hazard) obj; return this.id == that.id && this.explodes == that.explodes && this.deathAnimation == that.deathAnimation; } @Override public int hashCode() { int result = 17; result += result * 31 + id; result += result * 31 + (explodes ? 1 : 0); result += result * 31 + deathAnimation.hashCode(); return result; } /** * * Returns the id of the hazard preceeded by the phrase "Hazard ". This is intended for debugging purposes only. No code should * rely on the format of the string. * */ @Override public String toString() { return "Hazard " + id + ", " + (explodes ? "explodes" : "does not explode") + ", uses " + deathAnimation; } }