package net.sf.colossus.game;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.sf.colossus.client.BattleClientSide;
import net.sf.colossus.variant.BattleHex;
import net.sf.colossus.variant.CreatureType;
import net.sf.colossus.variant.HazardHexside;
import net.sf.colossus.variant.HazardTerrain;
public class BattleStrike
{
private static final Logger LOGGER = Logger.getLogger(BattleStrike.class
.getName());
private final Game game;
public BattleStrike(Game game)
{
this.game = game;
LOGGER.finest(("BattleStrike instantiated."));
}
/** Return the number of dice that will be rolled when striking this
* target, including modifications for terrain.
* WARNING: this is duplicated in CreatureServerSide
* (moved from Strike to here)
* @param striker TODO
* @param target TODO
*/
public int getDice(BattleCritter striker, BattleCritter target)
{
BattleHex hex = striker.getCurrentHex();
BattleHex targetHex = target.getCurrentHex();
// TODO when BattleCritter / BattleUnit would extend Creature,
// could ask directly instead of this helper variable
CreatureType strikerCreType = striker.getType();
int dice = striker.getPower();
boolean rangestrike = !getBattle().isInContact(striker, true);
HazardTerrain terrain = hex.getTerrain();
if (rangestrike)
{
// Divide power in half, rounding down.
dice /= 2;
// volcanoNative rangestriking from volcano: +2
if (terrain.equals(HazardTerrain.VOLCANO)
&& strikerCreType.isNativeIn(terrain))
{
dice += 2;
}
}
else
{
// Dice can be modified by terrain.
// volcanoNative striking from volcano: +2
if (terrain.equals(HazardTerrain.VOLCANO)
&& strikerCreType.isNativeIn(terrain))
{
dice += 2;
}
// Adjacent hex, so only one possible direction.
int direction = Battle.getDirection(hex, targetHex, false);
HazardHexside hazard = hex.getHexsideHazard(direction);
// Native striking down a dune hexside: +2
if (hazard == HazardHexside.DUNE && strikerCreType.isNativeDune())
{
dice += 2;
}
// Native striking down a slope hexside: +1
else if (hazard == HazardHexside.SLOPE
&& strikerCreType.isNativeSlope())
{
dice++;
}
// Non-native striking up a dune hexside: -1
else if (!strikerCreType.isNativeDune()
&& hex.getOppositeHazard(direction) == HazardHexside.DUNE)
{
dice--;
}
}
return dice;
}
/** WARNING: this is duplicated in CreatureServerSide
* (moved from Strike to here)
* @param striker TODO
* @param target TODO
*/
@SuppressWarnings("deprecation")
public int getAttackerSkill(BattleCritter striker, BattleCritter target)
{
BattleHex hex = striker.getCurrentHex();
BattleHex targetHex = target.getCurrentHex();
int attackerSkill = striker.getSkill();
boolean rangestrike = !getBattle().isInContact(striker, true);
// Skill can be modified by terrain.
if (!rangestrike)
{
HazardTerrain terrain = hex.getTerrain();
// striking out of possible hazard
attackerSkill -= hex.getTerrain().getSkillPenaltyStrikeFrom(
striker.getType().isNativeIn(terrain),
target.getType().isNativeIn(terrain));
if (hex.getElevation() > targetHex.getElevation())
{
// Adjacent hex, so only one possible direction.
int direction = BattleClientSide.getDirection(hex, targetHex,
false);
// TODO the hexside should be called WALL...
// Striking down across wall: +1
if (hex.getHexsideHazard(direction) == HazardHexside.TOWER)
{
attackerSkill++;
}
}
else if (hex.getElevation() < targetHex.getElevation())
{
// Adjacent hex, so only one possible direction.
int direction = BattleClientSide.getDirection(targetHex, hex,
false);
HazardHexside hazard = targetHex.getHexsideHazard(direction);
// Non-native striking up slope: -1
// Striking up across wall: -1
// TODO Tower vs. Wall ...
if ((hazard == HazardHexside.SLOPE && !striker.getType()
.isNativeSlope()) || hazard == HazardHexside.TOWER)
{
attackerSkill--;
}
}
}
else if (!striker.getType().useMagicMissile())
{
// Range penalty
int range = Battle.getRange(hex, targetHex, false);
if (range >= 4)
{
attackerSkill -= (range - 3);
}
// Non-native rangestrikes: -1 per intervening bramble hex
if (!striker.getType().isNativeIn(HazardTerrain.BRAMBLES))
{
attackerSkill -= getBattle().countBrambleHexes(hex, targetHex);
}
// Rangestrike up across wall: -1 per wall
if (targetHex.hasWall())
{
int heightDeficit = targetHex.getElevation()
- hex.getElevation();
if (heightDeficit > 0)
{
// Because of the design of the tower map, a strike to
// a higher tower hex always crosses one wall per
// elevation difference.
attackerSkill -= heightDeficit;
}
}
// Rangestrike into volcano: -1
if (targetHex.getTerrain().equals(HazardTerrain.VOLCANO))
{
attackerSkill--;
}
}
return attackerSkill;
}
/** WARNING: this is duplicated in CreatureServerSide
* (moved from Strike to here)
* @param striker TODO
* @param target TODO
*/
public int getStrikeNumber(BattleCritter striker, BattleCritter target)
{
boolean rangestrike = !getBattle().isInContact(striker, true);
int attackerSkill = getAttackerSkill(striker, target);
int defenderSkill = target.getSkill();
int strikeNumber = 4 - attackerSkill + defenderSkill;
HazardTerrain terrain = target.getCurrentHex().getTerrain();
if (!rangestrike)
{
// Strike number can be modified directly by terrain.
strikeNumber += terrain.getSkillBonusStruckIn(striker.getType()
.isNativeIn(terrain), target.getType().isNativeIn(terrain));
}
else
{
// Native defending in bramble, from rangestrike by a non-native
// non-magicMissile: +1
if (terrain.equals(HazardTerrain.BRAMBLES)
&& target.getType().isNativeIn(HazardTerrain.BRAMBLES)
&& !striker.getType().isNativeIn(HazardTerrain.BRAMBLES)
&& !striker.getType().useMagicMissile())
{
strikeNumber++;
}
// Native defending in stone, from rangestrike by a non-native
// non-magicMissile: +1
if (terrain.equals(HazardTerrain.STONE)
&& target.getType().isNativeIn(HazardTerrain.STONE)
&& !striker.getType().isNativeIn(HazardTerrain.STONE)
&& !striker.getType().useMagicMissile())
{
strikeNumber++;
}
}
// Sixes always hit.
if (strikeNumber > 6)
{
strikeNumber = 6;
}
return strikeNumber;
}
public int determineProbabilityBasedHits(Creature striker,
Creature target, int dice, int strikeNumber, List<String> rolls)
{
// for logging
StringBuilder rollString = new StringBuilder(36);
/*
sn 6: 1/6 6-6+1 = 1
sn 5: 2/6 6-5+1 = 2
sn 4: 3/6 6-4+1 = 3
sn 3: 4/6 6-3+1 = 4
sn 2: 5/6 6-2+1 = 5
sn 1: 6/6 6-1+1 = 6
sn N: (6-N+1)/6 6-N+1 => 7.0-N
*/
int damage = 0;
double expected = dice * (7.0 - strikeNumber) / 6.0;
int calculatedMin = (int)Math.floor(expected);
damage = calculatedMin;
// 0 < wastedLuck < 1
double wastedLuck = expected - calculatedMin;
/*
* effective accumulated wasted luck, for logging;
* applyAWL() will fill in the content string.
*/
StringBuffer eawlString = new StringBuffer();
String bonus = "no bonus";
boolean getsOne = striker.getLegion().getPlayer()
.applyAccumulatedWastedLuck(strikeNumber, wastedLuck, eawlString);
if (getsOne)
{
damage++;
bonus = "bonus +1";
}
String dispName = striker.getType().getName();
if (dispName.length() > 4)
{
dispName = dispName.substring(0, 4);
}
if (LOGGER.isLoggable(Level.FINEST))
{
String result = String.format(
"%-4s rolls %2d dice for %1d's, results in %2d hits "
+ "(exp. %5.2f =>%2d, %8s: EAWL=%s)", dispName,
Integer.valueOf(dice), Integer.valueOf(strikeNumber),
Integer.valueOf(damage), Double.valueOf(expected),
Integer.valueOf(calculatedMin), bonus, eawlString.toString());
LOGGER.log(Level.FINEST, result);
}
// create a fakeList, damage times strikeNumer and rest "1"s.
for (int i = 0; i < dice; i++)
{
int roll = (i < damage ? strikeNumber : 1);
rolls.add("" + roll);
rollString.append(roll);
}
LOGGER.log(Level.INFO,
striker.getName() + " in " + striker.getCurrentHex() + " strikes "
+ target.getDescription() + " with strike number "
+ strikeNumber + ", probabilityBased damage is " + damage
+ (damage == 1 ? " hit" : " hits (fake rolls: " + rollString));
return damage;
}
public int rollDice(Creature striker, Creature target, int dice,
int strikeNumber, List<String> rolls, boolean randomized)
{
// for logging
StringBuilder rollString = new StringBuilder(36);
int damage = 0;
// usual rolling, OR roll according to sequence
for (int i = 0; i < dice; i++)
{
int roll = (randomized ? rollPlayersBattleDice(striker) : Dice
.rollDieNonRandom());
rolls.add("" + roll);
rollString.append(roll);
if (roll >= strikeNumber)
{
damage++;
}
}
LOGGER.log(Level.INFO,
striker.getName() + " in " + striker.getCurrentHex() + " strikes "
+ target.getDescription() + " with strike number "
+ strikeNumber + ", rolling: " + rollString + ": " + damage
+ (damage == 1 ? " hit" : " hits"));
return damage;
}
/**
* @param striker The creature that strikes
* @return a battle roll
*/
private int rollPlayersBattleDice(Creature striker)
{
return striker.getPlayer().makeBattleRoll();
}
// Helper method
public Battle getBattle()
{
return game.getBattle();
}
}