package net.sf.colossus.variant; import java.awt.Color; import java.util.logging.Level; import java.util.logging.Logger; import net.sf.colossus.util.HTMLColor; /** * Class BattleHex holds game state for battle hex. * * @author David Ripton * @author Romain Dolbeau */ public class BattleHex extends Hex { private static final Logger LOGGER = Logger.getLogger(BattleHex.class .getName()); /** Valid elevations are 0, 1, and 2. Also 3 for JDG Badlands. */ private int elevation; // TODO the hexsides could/should be an enum // Hexside terrain types are: // d, c, s, w, space // dune, cliff, slope, wall, no obstacle // also // r // river /** * The array of all the valid terrain type for a BattleHex Side. */ private static final char[] allHexsides = { ' ', 'd', 'c', 's', 'w', 'r' }; //{ "Nothing", "Dune", "Cliff", "Slope", "Wall", "River"}; /** * Hold the HazardHexside type of the six side of the BattleHex * (e.g. Slope, Dune, River...). * The hexside is marked only in the higher hex. */ private final HazardHexside[] hexsideHazards = new HazardHexside[6]; /** * Links to the neighbors of the BattleHex. * Neighbors have one hex side in common. * Non-existent neighbor are marked with <b>null</b>. */ private final BattleHex[] neighbors = new BattleHex[6]; // Hex labels are: // A1-A3, B1-B4, C1-C5, D1-D6, E1-E5, F1-F4. // Letters increase left to right; numbers increase bottom to top. /* * TODO this should be final, but can't be at the moment */ private HazardTerrain terrain; /** Movement costs */ public static final int IMPASSIBLE_COST = 99; private static final int SLOW_COST = 2; private static final int NORMAL_COST = 1; private static final int SLOW_INCREMENT_COST = SLOW_COST - NORMAL_COST; public BattleHex(int xCoord, int yCoord) { super(createLabel(xCoord, yCoord), xCoord, yCoord); for (int i = 0; i < 6; i++) { setHexsideHazard(i, HazardHexside.NOTHING); } terrain = HazardTerrain.PLAINS; } private static String createLabel(int xCoord, int yCoord) { String label; if (xCoord < 0) { label = "X" + yCoord; } else { final int yLabel = 6 - yCoord - Math.abs(((xCoord - 3) / 2)); label = "" + _intXCoordToXLabel(xCoord) + yLabel; } return label; } /** a char for an int: 0:'A'=0, 1:'B', ... int(w):'W', else:'?', <0:undef. * */ private final static char _intXCoordToXLabel(final int x) { return (x < 'X') // 'X' is used for -1 ? (char)('A' + x) : '?'; } public HazardTerrain getTerrain() { return this.terrain; } public void setTerrain(HazardTerrain terrain) { this.terrain = terrain; } @Override public String getTerrainName() { String terrainName = terrain.getName(); if (elevation == 0) { return terrainName; } else { return terrainName + " (" + elevation + ")"; } } // TODO this probably should rather be HazardTerrain.getColor(int elevation) public Color getTerrainColor() { if (terrain.equals(HazardTerrain.PLAINS)) { switch (elevation) { case 0: return HTMLColor.lightOlive; case 1: return HTMLColor.darkYellow; case 2: return Color.yellow; default: case 3: return HTMLColor.lightYellow; } } else if (terrain.equals(HazardTerrain.TOWER)) { switch (elevation) { case 0: return HTMLColor.dimGray; case 1: return HTMLColor.darkGray; case 2: return Color.gray; default: case 3: return HTMLColor.lightGray; } } else if (terrain.equals(HazardTerrain.BRAMBLES)) { switch (elevation) { case 0: return Color.green; case 1: return HTMLColor.brambleGreen1; case 2: return HTMLColor.brambleGreen2; default: case 3: return HTMLColor.darkGreen; } } else if (terrain.equals(HazardTerrain.SAND)) { return Color.orange; } else if (terrain.equals(HazardTerrain.TREE)) { return HTMLColor.brown; } else if (terrain.equals(HazardTerrain.BOG)) { return Color.gray; } else if (terrain.equals(HazardTerrain.VOLCANO)) { switch (elevation) { case 3: return Color.red; default: case 2: return HTMLColor.darkRed; } } else if (terrain.equals(HazardTerrain.DRIFT)) { return Color.blue; } else if (terrain.equals(HazardTerrain.LAKE)) { return HTMLColor.skyBlue; } else if (terrain.equals(HazardTerrain.STONE)) { return HTMLColor.dimGray; } else if (terrain.equals(HazardTerrain.SPRING)) { return HTMLColor.springBlue; } else if (terrain.equals(HazardTerrain.TARPIT)) { return HTMLColor.darkSlateGray; } else { return Color.black; } } public boolean isNativeBonusTerrain() { boolean result; result = terrain.isNativeBonusTerrain(); for (int i = 0; i < 6; i++) { HazardHexside hazard = getHexsideHazard(i); result = result || hazard.isNativeBonusHexside(); } return result; } public boolean isNonNativePenaltyTerrain() { boolean result; result = terrain.isNonNativePenaltyTerrain(); for (int i = 0; i < 6; i++) { HazardHexside hazard = getOppositeHazard(i); result = result || hazard.isNonNativePenaltyHexside(); } return result; } public void setHexsideHazard(int i, HazardHexside hazard) { this.hexsideHazards[i] = hazard; } /** * TODO use side enumeration types instead of integers * Return the HazardHexside (enumType) at the hex' side number i * @param i The side number, from 0 to 5 * @return The HazardHexside type at that side */ public HazardHexside getHexsideHazard(int i) { if (i >= 0 && i <= 5) { return hexsideHazards[i]; } else { LOGGER.log(Level.WARNING, "Called BattleHex.getHexsideHazard() " + "with illegal hexside number " + i); return HazardHexside.NOTHING; } } // The hexside hazard of a tower is a wall, and previously this code // returned Wall in this case, so I keep it like that for now. // This method is nowadays used only by the painting code to // derive the image name. // TODO get rid of this, when HazardHexside uses correct text. // public String getHexsideImageName(int i) { HazardHexside hazard = hexsideHazards[i]; if (hazard == HazardHexside.TOWER) { return "Wall"; } else { return hazard.getName(); } } /** Return the hazard type of opposite side of side i. */ public HazardHexside getOppositeHazard(int i) { HazardHexside hazard = HazardHexside.NOTHING; BattleHex neighbor = getNeighbor(i); if (neighbor != null) { hazard = neighbor.getHexsideHazard((i + 3) % 6); } return hazard; } /** TODO get rid of this char based one * Return the character code of the hazard type * of opposite side of side i. */ public char getOppositeHexside(int i) { return getOppositeHazard(i).getCode(); } public int getElevation() { return elevation; } public void setElevation(int elevation) { this.elevation = elevation; } public BattleHex getNeighbor(int i) { assert (i >= 0) && (i <= 5) : "Neighbor index out of range"; return neighbors[i]; } public void setNeighbor(int i, BattleHex hex) { assert (i >= 0) && (i <= 5) : "Neighbor index out of range"; neighbors[i] = hex; } public boolean isEntrance() { return (getXCoord() == -1); } public boolean hasWall() { for (int i = 0; i < 6; i++) { if (hexsideHazards[i] == HazardHexside.TOWER) { return true; } } return false; } /** Whether this hex blocks rangestrike. * @return Whether this hex blocks rangestrike. */ public boolean blocksLineOfSight() { return terrain.blocksLineOfSight(); } /** * Return the number of movement points it costs to enter this hex. * For fliers, this is the cost to land in this hex, not fly over it. * If entry is illegal, just return a cost greater than the maximum * possible number of movement points. This caller is responsible * for checking to see if this hex is already occupied. * @param creature The Creature that is trying to move into the BattleHex. * @param cameFrom The HexSide through which the Creature try to enter. * @return Cost to enter the BattleHex. */ public int getEntryCost(CreatureType creature, int cameFrom, boolean cumul) { int cost = NORMAL_COST; // Check to see if the hex is occupied or totally impassable. if (terrain.blocksGround() || (terrain.isGroundNativeOnly() && (!creature.isNativeIn(terrain)))) { cost += IMPASSIBLE_COST; } else { HazardHexside hazard = getHexsideHazard(cameFrom); // Non-fliers may not cross cliffs. if ((hazard == HazardHexside.CLIFF || getOppositeHazard(cameFrom) == HazardHexside.CLIFF) && !creature.isFlier()) { cost += IMPASSIBLE_COST; } else { // river slows both way, except native & water dwellers if ((hazard == HazardHexside.RIVER || getOppositeHazard(cameFrom) == HazardHexside.RIVER) && !creature.isFlier() && !creature.isWaterDwelling() && !creature.isNativeRiver()) { cost += SLOW_INCREMENT_COST; } // Check for a slowing hexside. if ((hazard == HazardHexside.TOWER || (hazard == HazardHexside.SLOPE && !creature .isNativeSlope())) && !creature.isFlier() && elevation > getNeighbor(cameFrom).getElevation()) { cost += SLOW_INCREMENT_COST; } // check whether that terrain is slowing us. if (terrain.slows(creature.isNativeIn(terrain), creature.isFlier())) { cost += SLOW_INCREMENT_COST; } } } if (cost > IMPASSIBLE_COST) { // max out impassible at IMPASSIBLE_COST cost = IMPASSIBLE_COST; } if ((cost < IMPASSIBLE_COST) && (cost > SLOW_COST) && (!cumul)) { // No cumulation of slowing: cost = SLOW_COST; } return cost; } /** * Check if the Creature given in parameter can fly over * the BattleHex, or not. * @param creature The Creature that want to fly over this BattleHex * @return If the Creature can fly over here or not. */ public boolean canBeFlownOverBy(CreatureType creature) { if (!creature.isFlier()) { // non-flyer can't fly, obviously... return false; } boolean denyBecauseForeigner = (terrain.isFlyersNativeOnly() && !creature .isNativeIn(terrain)); // (...||...): It's forbidden if it blocks flying generally, // or denies flying to foreigners (and cre is foreigner) // !(): It's allowed if it's not forbidden return !(terrain.blocksFlyers() || denyBecauseForeigner); } /** * Return how much the hex slows the creature for the rest of the battle * @param creature The Creature that may be slowed. * @return How much the Creature is slowed for the rest of the battle. */ public int slowsCreature(CreatureType creature) { if (terrain.isSlowingToNonNative() && (!creature.isNativeIn(terrain))) { // Non-native slowed permanently in TarPit return 1; } if (terrain.isHealing()) { // Spring cures slowing (washes off tar from TarPits, alleviates effect // from creature if creature that slows is added) return -1; } // default : no slowing ! return 0; } /** * Return how much damage the Creature should take from this Hex. * @param creature The Creature that may suffer damage. * @return How much damage the Creature should take from being there. */ public int damageToCreature(CreatureType creature) { if (terrain.isDamagingToNonNative() && (!creature.isNativeIn(terrain))) { // Non-native take damage in Drift return 1; } if (terrain.isDamagingToWaterDweller() && (creature.isWaterDwelling())) { // Water Dweller (amphibious) take damage in Sand return 1; } if (terrain.isHealing()) { // All creatures heal in Spring return -1; } // default : no damage ! return 0; } public boolean isCliff(int hexside) { return getHexsideHazard(hexside) == HazardHexside.CLIFF || getOppositeHazard(hexside) == HazardHexside.CLIFF; } public static char[] getHexsides() { return allHexsides; } }