package net.sf.colossus.game;
import java.util.Comparator;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.sf.colossus.variant.BattleHex;
import net.sf.colossus.variant.CreatureType;
import net.sf.colossus.variant.HazardHexside;
import net.sf.colossus.variant.HazardTerrain;
/**
* A particular creature in a game.
*
* This represents a creature in a game, such as a specific Cyclops as part of
* a legion.
*
* TODO it should include the state for battles, i.e. the damage taken so far.
* Currently that happens only on the server side.
*/
public class Creature
{
/**
* Implements an order on Critters by some definition of importance.
*
* The order is:
* - titans first
* - then sorted by points value
* - then sorted by rangestriker or not
* - then sorted by flyer or not
* - then by name
*/
public static final Comparator<Creature> IMPORTANCE_ORDER = new Comparator<Creature>()
{
public int compare(Creature critter1, Creature critter2)
{
if (critter1.isTitan() && !critter2.isTitan())
{
return -1;
}
if (critter2.isTitan() && !critter1.isTitan())
{
return 1;
}
int diff = critter2.getPointValue() - critter1.getPointValue();
if (diff != 0)
{
return diff;
}
if (critter1.isRangestriker() && !critter2.isRangestriker())
{
return -1;
}
if (!critter1.isRangestriker() && critter2.isRangestriker())
{
return 1;
}
if (critter1.isFlier() && !critter2.isFlier())
{
return -1;
}
if (!critter1.isFlier() && critter2.isFlier())
{
return 1;
}
return critter1.getName().compareTo(critter2.getName());
}
};
private static final Logger LOGGER = Logger.getLogger(Creature.class
.getName());
final private CreatureType type;
protected Legion legion;
private BattleHex currentHex;
private BattleHex startingHex;
/**
* Damage taken
*/
private int hits = 0;
private int poisonDamage = 0;
private int slowed = 0;
private boolean struck;
public Creature(CreatureType type, Legion legion)
{
this.type = type;
this.legion = legion;
currentHex = null;
startingHex = null;
}
public CreatureType getType()
{
return type;
}
/**
* Calculates the Striking Power of this Creature when striking directly at
* target under the circumstances in parameters.
*
* @param target The Creature that is struck by the current Creature
* @param myElevation Height of the Hex on which stands the current Creature
* @param targetElevation Height of the hex on which stands the target Creature
* @param myHexTerrain Type of Hazard of the current Hex
* @param targetHexTerrain Type of Hazard of the target hex
* @param myHexside Type of hexside hazard between the current hex and the target hex
* @param targetHexside Type of hexside hazard between the target hex and the current hex
* @return The Power Factor of the current Creature when all modifiers are factored in
*/
public int getStrikingPower(Creature target, int myElevation,
int targetElevation, HazardTerrain myHexTerrain,
HazardTerrain targetHexTerrain, HazardHexside myHexside,
HazardHexside targetHexside)
{
CreatureType myType = this.getType();
CreatureType targetType = target.getType();
int dice = getPower();
// Dice can be modified by terrain.
dice += myHexTerrain.getPowerBonusStrikeFrom(
myType.isNativeIn(myHexTerrain),
targetType.isNativeIn(myHexTerrain));
// Native striking down a dune hexside: +2
if (myHexside.equals(HazardHexside.DUNE) && myType.isNativeDune())
{
dice += 2;
}
// Native striking down a slope hexside: +1
else if (myHexside.equals(HazardHexside.SLOPE)
&& myType.isNativeSlope())
{
dice++;
}
// Non-native striking up a dune hexside: -1
else if (!myType.isNativeDune()
&& targetHexside.equals(HazardHexside.DUNE))
{
dice--;
}
LOGGER.finest("Found " + dice + " dice for " + myType.getName() + " ["
+ myHexTerrain.getName() + " " + myElevation + ", "
+ myHexside.getName() + " ] vs. " + targetType.getName() + " ["
+ targetHexTerrain.getName() + " " + targetElevation + ", "
+ targetHexside.getName() + " ]");
return dice;
}
/**
* Calculates the Striking Skill of this Creature when striking directly at
* target under the circumstances in parameters.
*
* @param target The Creature that is struck by the current Creature
* @param myElevation Height of the Hex on which stands the current Creature
* @param targetElevation Height of the hex on which stands the target Creature
* @param myHexTerrain Type of Hazard of the current Hex
* @param targetHexTerrain Type of Hazard of the target hex
* @param myHexside Type of hexside hazard between the current hex and the target hex
* @param targetHexside Type of hexside hazard between the target hex and the current hex
* @return The Skill Factor of the current Creature when all modifiers are factored in
*/
public int getStrikingSkill(Creature target, int myElevation,
int targetElevation, HazardTerrain myHexTerrain,
HazardTerrain targetHexTerrain, HazardHexside myHexside,
HazardHexside targetHexside)
{
CreatureType myType = this.getType();
CreatureType targetType = target.getType();
int attackerSkill = myType.getSkill();
// Skill can be modified by terrain.
// striking out of possible hazard
attackerSkill -= myHexTerrain.getSkillPenaltyStrikeFrom(
myType.isNativeIn(myHexTerrain),
targetType.isNativeIn(myHexTerrain));
if (myElevation > targetElevation)
{
// Striking down across wall: +1
if (myHexside.equals(HazardHexside.TOWER))
{
attackerSkill++;
}
}
else if (myElevation < targetElevation)
{
// Non-native striking up slope: -1
// Striking up across wall: -1
if ((targetHexside.equals(HazardHexside.SLOPE) && !myType
.isNativeSlope()) || targetHexside.equals(HazardHexside.TOWER))
{
attackerSkill--;
}
}
LOGGER.finest("Found skill " + attackerSkill + " for "
+ myType.getName() + " [" + myHexTerrain.getName() + " "
+ myElevation + ", " + myHexside.getName() + " ] vs. "
+ targetType.getName() + " [" + targetHexTerrain.getName() + " "
+ targetElevation + ", " + targetHexside.getName() + " ]");
return attackerSkill;
}
public Legion getLegion()
{
return legion;
}
public Player getPlayer()
{
return legion.getPlayer();
}
public int getPower()
{
if (isTitan())
{
return getTitanPower();
}
return getType().getPower();
}
public int getTitanPower()
{
Player player = getPlayer();
if (player != null)
{
return player.getTitanPower();
}
else
{
// Just in case player is dead.
LOGGER.warning("asked for Titan power of dead (null) player!");
return 6;
}
}
public String getMarkerId()
{
return legion.getMarkerId();
}
public String getName()
{
return getType().getName();
}
public boolean isTitan()
{
return getType().isTitan();
}
public String getDescription()
{
return getName() + " in " + getCurrentHex().getDescription();
}
public BattleHex getStartingHex()
{
return startingHex;
}
public void setStartingHex(BattleHex hex)
{
this.startingHex = hex;
}
public BattleHex getCurrentHex()
{
return currentHex;
}
public void setCurrentHex(BattleHex hex)
{
this.currentHex = hex;
}
public void moveToHex(BattleHex hex)
{
setCurrentHex(hex);
}
public void commitMove()
{
setStartingHex(getCurrentHex());
}
public boolean hasMoved()
{
return !getCurrentHex().equals(getStartingHex());
}
// TODO never used?
public void setMoved(boolean moved)
{
assert (moved == hasMoved()) : "Oups, setMoved on immobile Creature";
}
public boolean isDemiLord()
{
return getType().isDemiLord();
}
public boolean isFlier()
{
return getType().isFlier();
}
public boolean isImmortal()
{
return getType().isImmortal();
}
public boolean isLord()
{
return getType().isLord();
}
public boolean isLordOrDemiLord()
{
return getType().isLordOrDemiLord();
}
public boolean isRangestriker()
{
return getType().isRangestriker();
}
public boolean useMagicMissile()
{
return getType().useMagicMissile();
}
public boolean isSummonable()
{
return getType().isSummonable();
}
public boolean isNativeAt(HazardHexside hexside)
{
return getType().isNativeAt(hexside);
}
public boolean isNativeIn(HazardTerrain terrain)
{
return getType().isNativeIn(terrain);
}
public int getPointValue()
{
// Must use our local, Titan-aware getPower()
// return getCreature().getPointValue();
return getPower() * getSkill();
}
public int getSkill()
{
return getType().getSkill();
}
public int getHits()
{
return hits;
}
public int getPoison()
{
return getType().getPoison();
}
public int getPoisonDamage()
{
return poisonDamage;
}
public int getSlowed()
{
return slowed;
}
public int getSlows()
{
return getType().getSlows();
}
public boolean hasStruck()
{
return struck;
}
public void setHits(int hits)
{
this.hits = hits;
}
public void setPoisonDamage(int damage)
{
poisonDamage = damage;
}
public void addPoisonDamage(int damage)
{
poisonDamage += damage;
}
public void setSlowed(int slowValue)
{
slowed = slowValue;
}
public void addSlowed(int slowValue)
{
slowed += slowValue;
}
public void setStruck(boolean struck)
{
this.struck = struck;
}
public boolean isDead()
{
return getHits() >= getPower();
}
public void setDead(boolean dead)
{
if (dead)
{
setHits(getPower());
}
else
{
assert (getHits() < getPower()) : "Oups, making NOT dead but should be";
}
}
public String[] getImageNames()
{
return getType().getImageNames();
}
public String getPluralName()
{
return getType().getPluralName();
}
public int getMaxCount()
{
return getType().getMaxCount();
}
public void heal()
{
setHits(0);
setPoisonDamage(0);
setSlowed(0);
}
public boolean wouldDieFrom(int additionalDamage)
{
return (this.hits + additionalDamage >= getPower());
}
/**
* Apply damage or healing to this critter. Return the amount of excess damage
* done, which may sometimes carry to another target.
*/
public int adjustHits(int damage)
{
int excess = 0;
int tmp_hits = getHits();
int oldhits = tmp_hits;
tmp_hits = (tmp_hits + damage) > 0 ? tmp_hits + damage : 0;
if (tmp_hits > getPower())
{
excess = tmp_hits - getPower();
tmp_hits = getPower();
}
LOGGER.log(Level.INFO, "Critter " + getDescription() + ": " + oldhits
+ " + " + damage + " => " + tmp_hits + "; " + excess + " excess");
// Check for death.
if (tmp_hits >= getPower())
{
LOGGER.log(Level.INFO, "Critter " + getDescription()
+ " is now dead: (hits=" + tmp_hits + " > power=" + getPower()
+ ")");
setDead(true);
}
setHits(tmp_hits);
return excess;
}
}