package net.sf.colossus.variant; import java.util.Comparator; import java.util.HashSet; import java.util.Set; import java.util.logging.Logger; /** * A type of creature in a variant. * * This class models a generic creature type, i.e. all features that are common * through all creatures of a specific type. * * Default equality and sorting order is class (in case of subclasses) then name. */ public class CreatureType implements Comparable<CreatureType> { private static final Logger LOGGER = Logger.getLogger(CreatureType.class .getName()); /** * A comparator sorting creature types by name. */ public static final Comparator<CreatureType> NAME_ORDER = new Comparator<CreatureType>() { public int compare(CreatureType type1, CreatureType type2) { return type1.getName().compareTo(type2.getName()); } }; private static boolean noBaseColor = false; public static void setNoBaseColor(boolean b) { noBaseColor = b; } private final String name; private final String pluralName; private final int power; private final int skill; private final boolean rangestrikes; private final boolean flies; private final boolean nativeSlope; private final boolean nativeRiver; private final boolean nativeDune; private final boolean waterDwelling; private final boolean magicMissile; private final boolean lord; private final boolean demilord; private int maxCount; private final int poison; private final int slows; private final String baseColor; private final Set<HazardTerrain> nativeTerrains = new HashSet<HazardTerrain>(); private final boolean isSummonable; /* NOTE for variant builder: * The subclasses in variant must use the following signature for the * constructor: * public CreatureXXX(String name, Integer power, Integer skill, Boolean rangestrikes, Boolean flies, HashSet<HazardTerrain> nativeTerrrains, Boolean nativeSlope, Boolean nativeRiver, Boolean nativeDune, Boolean waterDwelling, Boolean magicMissile, Boolean summonable, Boolean lord, Boolean demilord, Integer maxCount, String pluralName, String baseColor, int poison, int slows); * Otherwise the class loading system won't find it. Not that this is * really HashSet, not just Set. */ public CreatureType(String name, int power, int skill, boolean rangestrikes, boolean flies, Set<HazardTerrain> nativeTerrains, boolean nativeSlope, boolean nativeRiver, boolean nativeDune, boolean waterDwelling, boolean magicMissile, boolean summonable, boolean lord, boolean demilord, int maxCount, String pluralName, String baseColor, int poison, int slows) { this.name = name; this.pluralName = pluralName; // defensive, but shallow copy of terrains this.nativeTerrains.addAll(nativeTerrains); this.isSummonable = summonable; this.power = power; this.skill = skill; this.rangestrikes = rangestrikes; this.flies = flies; this.nativeSlope = nativeSlope; this.nativeRiver = nativeRiver; this.nativeDune = nativeDune; this.waterDwelling = waterDwelling; this.magicMissile = magicMissile; this.lord = lord; this.demilord = demilord; this.maxCount = maxCount; this.baseColor = baseColor; this.poison = poison; this.slows = slows; /* warn about likely inappropriate combinations */ if (waterDwelling && isNativeDune()) { LOGGER.warning("Creature " + name + " is both a Water Dweller and native to Dune."); } } /** * The name used for creatures of this type. */ public String getName() { return name; } /** * The name used for multiple creatures of this type. */ public String getPluralName() { return pluralName; } /** * Checks if the type of creature is native in a terrain type. * * @param terrain The terrain to check. Not null. * @return true iff creatures of this type are native in the terrain. */ public boolean isNativeIn(HazardTerrain terrain) { assert terrain != null; return nativeTerrains.contains(terrain); } public boolean isSummonable() { return isSummonable; } /** * Returns true if this is a Titan. * * The default implementation is a constant false, to be overridden in classes * representing Titans. * * @return true iff this creature type is a Titan. */ public boolean isTitan() { return false; } @Override public String toString() { return getName(); } /** Compare by name. */ @Override public final boolean equals(Object object) { if (object == null) { return false; } if (object.getClass() != this.getClass()) { return false; } CreatureType other = (CreatureType)object; return getName().equals(other.getName()); } public int getMaxCount() { return maxCount; } /** Only called on Titans after numPlayers is known. * 08/2009 Clemens: And on Balrogs when players scores raise. */ public void setMaxCount(int maxCount) { this.maxCount = maxCount; } public boolean isLord() { return lord; } public boolean isDemiLord() { return demilord; } public boolean isLordOrDemiLord() { return (isLord() || isDemiLord()); } public boolean isImmortal() { // might not the same for derived class return isLordOrDemiLord(); } /** true if any if the values can change during the game returned by: * - getPower, getSkill, (and therefore getPointValue) * - isRangestriker, isFlier, useMagicMissile * - isNativeTerraion(t), for all t * - isNativeHexSide(h) for all h * In Standard game only the titans change their attributes */ public boolean canChangeValue() { return isTitan(); } protected String getImageName() { return getName(); } public String[] getImageNames() { String[] tempNames; if (baseColor != null) { int specialIncrement = ((isFlier() || isRangestriker()) ? 1 : 0); tempNames = new String[4 + specialIncrement]; String colorSuffix = "-" + (noBaseColor ? "black" : baseColor); tempNames[0] = getImageName(); tempNames[1] = "Power-" + getPower() + colorSuffix; tempNames[2] = "Skill-" + getSkill() + colorSuffix; tempNames[3] = getName() + "-Name" + colorSuffix; if (specialIncrement > 0) { tempNames[4] = (isFlier() ? "Flying" : "") + (isRangestriker() ? "Rangestrike" : "") + colorSuffix; } } else { tempNames = new String[1]; tempNames[0] = getImageName(); } return tempNames; } public int getPower() { return power; } public int getSkill() { return skill; } public int getPointValue() { // this function is replicated in Critter return getPower() * getSkill(); } public boolean isRangestriker() { return rangestrikes; } public boolean isFlier() { return flies; } public boolean isPoison() { return poison > 0; } public boolean slows() { return slows > 0; } public boolean isNativeAt(HazardHexside hazard) { return isNativeAt(hazard.getCode()); } // TODO get rid of this char based version. // Only used by ShowCreatureDetails nowadays. public boolean isNativeAt(char h) { switch (h) { default: return false; case ' ': /* undefined */ return false; case 'd': return isNativeDune(); case 'c': /* undefined */ return false; case 's': return isNativeSlope(); case 'w': /* undefined, beneficial for everyone */ return true; case 'r': return isNativeRiver(); } } public boolean isNativeSlope() { return nativeSlope; } public boolean isNativeRiver() { return nativeRiver; } public boolean isNativeDune() { return nativeDune; } public boolean isWaterDwelling() { return waterDwelling; } public boolean useMagicMissile() { return magicMissile; } @Override public int hashCode() { return getName().hashCode(); } public String getBaseColor() { if (baseColor != null) { return baseColor; } else { return ""; } } public int getPoison() { return poison; } public int getSlows() { return slows; } /** * Get the non-terrainified part of the kill-value. * * TODO this is not model, but AI related (but also used in client for * sorting creatures -- the client uses the AI for recruit hints, too) */ public int getKillValue() { int val = 10 * getPointValue(); final int lskill = getSkill(); if (lskill >= 4) { val += 2; } else if (lskill <= 2) { val += 1; } if (isFlier()) { val += 4; } if (isRangestriker()) { val += 5; } if (useMagicMissile()) { val += 4; } if (isTitan()) { val += 1000; } if (isPoison()) { val += 4; } if (slows()) { val += 3; } return val; } public int compareTo(CreatureType o) { if (o.getClass() != this.getClass()) { return this.getClass().getName().compareTo(o.getClass().getName()); } return this.getName().compareTo(o.getName()); } }