MPMelevationHack = true;
// surface to fire
ae.setElevation(0);
}
// check LOS (indirect LOS is from the spotter)
LosEffects los;
ToHitData losMods;
if (!isIndirect || (isIndirect && (spotter == null))) {
los = LosEffects.calculateLos(game, attackerId, target);
if (ae.hasActiveEiCockpit()) {
if (los.getLightWoods() > 0) {
eistatus = 2;
} else {
eistatus = 1;
}
}
if ((wtype instanceof MekMortarWeapon) && isIndirect) {
los.setArcedAttack(true);
}
losMods = los.losModifiers(game, eistatus);
if ((atype != null)
&& ((atype.getAmmoType() == AmmoType.T_LRM_TORPEDO)
|| (atype.getAmmoType() == AmmoType.T_SRM_TORPEDO) || (((atype
.getAmmoType() == AmmoType.T_SRM)
|| (atype.getAmmoType() == AmmoType.T_MRM)
|| (atype.getAmmoType() == AmmoType.T_LRM) || (atype
.getAmmoType() == AmmoType.T_MML)) && (munition == AmmoType.M_TORPEDO)))
&& (los.getMinimumWaterDepth() < 1)) {
return new ToHitData(TargetRoll.IMPOSSIBLE,
"Torpedos must follow water their entire LOS");
}
} else {
los = LosEffects.calculateLos(game, spotter.getId(), target);
// do not count attacker partial cover in indirect fire
los.setAttackerCover(LosEffects.COVER_NONE);
if (!narcSpotter && spotter.hasActiveEiCockpit()) {
if (los.getLightWoods() > 0) {
eistatus = 2;
} else {
eistatus = 1;
}
}
if (wtype instanceof MekMortarWeapon) {
los.setArcedAttack(true);
}
losMods = los.losModifiers(game);
}
if (MPMelevationHack) {
// return to depth 1
ae.setElevation(-1);
}
// Leg attacks, Swarm attacks, and
// Mine Launchers don't use gunnery.
if (Infantry.LEG_ATTACK.equals(wtype.getInternalName())) {
toHit = Compute.getLegAttackBaseToHit(ae, te);
if (toHit.getValue() == TargetRoll.IMPOSSIBLE) {
return toHit;
}
// If the attacker has Assault claws, give a -1 modifier.
// We can stop looking when we find our first match.
for (Mounted mount : ae.getMisc()) {
EquipmentType equip = mount.getType();
if (BattleArmor.ASSAULT_CLAW.equals(equip.getInternalName())) {
toHit.addModifier(-1, "attacker has assault claws");
break;
}
}
} else if (Infantry.SWARM_MEK.equals(wtype.getInternalName())) {
toHit = Compute.getSwarmMekBaseToHit(ae, te);
if (toHit.getValue() == TargetRoll.IMPOSSIBLE) {
return toHit;
}
if (te instanceof Tank) {
toHit.addModifier(-2, "target is vehicle");
}
// If the attacker has assault claws, give a -1 modifier.
// We can stop looking when we find our first match.
for (Mounted mount : ae.getMisc()) {
EquipmentType equip = mount.getType();
if (BattleArmor.ASSAULT_CLAW.equals(equip.getInternalName())) {
toHit.addModifier(-1, "attacker has assault claws");
break;
}
}
//MD Infantry with grappler/magnets get bonus
if(ae.crew.getOptions().booleanOption("grappler")) {
toHit.addModifier(-2, "MD Grapple/Magnet");
}
// If the defender carries mechanized BA, they can fight off the
// swarm
for (Entity e : te.getExternalUnits()) {
if (e instanceof BattleArmor) {
BattleArmor ba = (BattleArmor) e;
int def = ba.getShootingStrength();
int att = ((Infantry) ae).getShootingStrength();
if (!(ae instanceof BattleArmor)) {
if (att >= 28) {
att = 5;
} else if (att >= 24) {
att = 4;
} else if (att >= 21) {
att = 3;
} else if (att >= 18) {
att = 2;
} else {
att = 1;
}
}
def = def + 2 - att;
if (def > 0) {
toHit.addModifier(def, "Defending mechanized BA");
}
}
}
} else if (Infantry.STOP_SWARM.equals(wtype.getInternalName())) {
// Can't stop if we're not swarming, otherwise automatic.
return new ToHitData(TargetRoll.AUTOMATIC_SUCCESS,
"End swarm attack.");
} else if (BattleArmor.MINE_LAUNCHER.equals(wtype.getInternalName())) {
// Mine launchers can not hit infantry.
toHit = new ToHitData(8, "magnetic mine attack");
} else if ((atype != null) && (atype.getAmmoType() == AmmoType.T_BA_MICRO_BOMB)) {
if (ae.getPosition().equals(target.getPosition())) {
// Micro bombs use anti-mech skill
return new ToHitData(ae.getCrew().getPiloting(), "anti-mech skill");
} else {
return new ToHitData(TargetRoll.IMPOSSIBLE, "out of range");
}
}
// Swarming infantry always hit their target, but
// they can only target the Mek they're swarming.
else if ((te != null) && (ae.getSwarmTargetId() == te.getId())) {
int side = te instanceof Tank ? ToHitData.SIDE_RANDOM
: ToHitData.SIDE_FRONT;
if (ae instanceof BattleArmor) {
return new ToHitData(TargetRoll.AUTOMATIC_SUCCESS,
"Attack during swarm.", ToHitData.HIT_SWARM, side);
} else {
return new ToHitData(TargetRoll.AUTOMATIC_SUCCESS,
"Attack during swarm.",
ToHitData.HIT_SWARM_CONVENTIONAL, side);
}
} else if (isArtilleryFLAK) {
toHit = new ToHitData(9, "artillery FLAK");
} else {
toHit = new ToHitData(ae.crew.getGunnery(), "gunnery skill");
if (game.getOptions().booleanOption("rpg_gunnery")) {
if (wtype.hasFlag(WeaponType.F_ENERGY)) {
toHit = new ToHitData(ae.crew.getGunneryL(),
"gunnery (L) skill");
}
if (wtype.hasFlag(WeaponType.F_MISSILE)) {
toHit = new ToHitData(ae.crew.getGunneryM(),
"gunnery (M) skill");
}
if (wtype.hasFlag(WeaponType.F_BALLISTIC)) {
toHit = new ToHitData(ae.crew.getGunneryB(),
"gunnery (B) skill");
}
}
if (ae.getTaserFeedBackRounds() > 0) {
toHit.addModifier(1, "Taser feedback");
}
if (ae.getTaserInterferenceRounds() > 0) {
toHit.addModifier(ae.getTaserInterference(), "Taser interference");
}
}
// Engineer's fire extinguisher has fixed to hit number,
// Note that coolant trucks make a regular attack.
if (wtype.hasFlag(WeaponType.F_EXTINGUISHER)) {
toHit = new ToHitData(8, "fire extinguisher");
if (((target instanceof Entity)
&& ((Entity) target).infernos.isStillBurning())
|| ((target instanceof Tank)
&& ((Tank) target).isInfernoFire())) {
toHit.addModifier(2, "inferno fire");
}
if ((Targetable.TYPE_HEX_EXTINGUISH == target.getTargetType())
&& game.getBoard().isInfernoBurning(target.getPosition())) {
toHit.addModifier(2, "inferno fire");
}
return toHit;
}
// if we're spotting for indirect fire, add +1
if (ae.isSpotting()) {
toHit.addModifier(+1, "attacker is spotting for indirect LRM fire");
}
//fatigue
if(game.getOptions().booleanOption("tacops_fatigue") && ae.crew.isGunneryFatigued(game.getRoundCount())) {
toHit.addModifier(1,"fatigue");
}
// If a unit is suffering from electromagnetic interference, they get a
// blanket +2.
// Sucks to be them.
if (ae.isSufferingEMI()) {
toHit.addModifier(+2, "electromagnetic interference");
}
// evading bonuses (
if ((target.getTargetType() == Targetable.TYPE_ENTITY) && te.isEvading()) {
toHit.addModifier(te.getEvasionBonus(), "target is evading");
}
// ghost target modifier
if (game.getOptions().booleanOption("tacops_ghost_target")) {
int ghostTargetMod = Compute.getGhostTargetNumber(ae, ae
.getPosition(), target.getPosition());
if ((ghostTargetMod > -1)
&& !((ae instanceof Infantry) && !(ae instanceof BattleArmor))) {
int bapMod = 0;
if (ae.hasBAP()) {
bapMod = 1;
}
int tcMod = 0;
if (ae.hasTargComp()
&& wtype.hasFlag(WeaponType.F_DIRECT_FIRE)
&& (!usesAmmo || !(((atype.getAmmoType() == AmmoType.T_AC_LBX) || (atype
.getAmmoType() == AmmoType.T_AC_LBX_THB)) && (atype
.getMunitionType() == AmmoType.M_CLUSTER)))) {
tcMod = 2;
}
int ghostTargetMoF = (ae.getCrew().getSensorOps() + ghostTargetMod)
- (ae.getGhostTargetOverride() + bapMod + tcMod);
if (ghostTargetMoF > 0) {
toHit.addModifier(Math.min(4, ghostTargetMoF / 2),
"ghost targets");
}
}
}
//Space ECM
if(game.getBoard().inSpace() && game.getOptions().booleanOption("stratops_ecm")) {
int ecm = Compute.getLargeCraftECM(ae, ae.getPosition(), target.getPosition());
if(!ae.isLargeCraft()) {
ecm += Compute.getSmallCraftECM(ae, ae.getPosition(), target.getPosition());
}
ecm = Math.min(4,ecm);
int eccm = 0;
if(ae.isLargeCraft()) {
eccm = ((Aero)ae).getECCMBonus();
}
if(ecm > 0) {
toHit.addModifier(ecm, "ECM");
if(eccm > 0) {
toHit.addModifier(-1*Math.min(ecm, eccm), "ECCM");
}
}
}
// Aeros may suffer from criticals
if (ae instanceof Aero) {
Aero aero = (Aero) ae;
// sensor hits
int sensors = aero.getSensorHits();
if(!aero.isCapitalFighter()) {
if ((sensors > 0) && (sensors < 3)) {
toHit.addModifier(sensors, "sensor damage");
}
if (sensors > 2) {
toHit.addModifier(+5, "sensors destroyed");
}
}
// FCS hits
int fcs = aero.getFCSHits();
if ((fcs > 0) && !aero.isCapitalFighter()) {
toHit.addModifier(fcs * 2, "fcs damage");
}
// pilot hits
int pilothits = aero.getCrew().getHits();
if ((pilothits > 0) && !aero.isCapitalFighter()) {
toHit.addModifier(pilothits, "pilot hits");
}
// out of control
if (aero.isOutControlTotal()) {
toHit.addModifier(+2, "out-of-control");
}
if (aero instanceof Jumpship) {
Jumpship js = (Jumpship) aero;
int cic = js.getCICHits();
if (cic > 0) {
toHit.addModifier(cic * 2, "CIC damage");
}
}
// targeting mods for evasive action by large craft
if (aero.isEvading()) {
toHit.addModifier(+2, "attacker is evading");
}
// check for heavy gauss rifle on fighter of small craft
if ((weapon.getType() instanceof ISHGaussRifle) && (ae instanceof Aero)
&& !(ae instanceof Dropship) && !(ae instanceof Jumpship)) {
toHit.addModifier(+1, "weapon to-hit modifier");
}
// check for NOE
// if the target is NOE in atmosphere
if (game.getBoard().inAtmosphere()
&& (1 == (ae.getElevation() - game.getBoard().getHex(
ae.getPosition()).ceiling()))) {
if (ae.isOmni()) {
toHit.addModifier(+1, "attacker is flying at NOE (omni)");
} else {
toHit.addModifier(+2, "attacker is flying at NOE");
}
}
// check for particular kinds of weapons in weapon bays
if (ae.usesWeaponBays()) {
// any heavy lasers
if (wtype.getAtClass() == WeaponType.CLASS_LASER) {
for (int wId : weapon.getBayWeapons()) {
Mounted bweap = ae.getEquipment(wId);
WeaponType bwtype = (WeaponType) bweap.getType();
if ((bwtype.getInternalName().indexOf("Heavy") != -1)
&& (bwtype.getInternalName().indexOf("Laser") != -1)) {
toHit.addModifier(+1, "bay contains heavy laser");
break;
}
}
}
// barracuda and piranha missiles
else if (wtype.getAtClass() == WeaponType.CLASS_CAPITAL_MISSILE) {
boolean onlyBarracuda = true;
boolean onlyPiranha = true;
for (int wId : weapon.getBayWeapons()) {
Mounted bweap = ae.getEquipment(wId);
Mounted bammo = bweap.getLinked();
if (bammo != null) {
AmmoType batype = (AmmoType) bammo.getType();
if (batype.getAmmoType() != AmmoType.T_BARRACUDA) {
onlyBarracuda = false;
}
if ((batype.getAmmoType() != AmmoType.T_PIRANHA) && (batype.getAmmoType() != AmmoType.T_BARRACUDA)) {
onlyPiranha = false;
}
}
}
if (onlyBarracuda) {
toHit.addModifier(-2, "barracuda missile");
} else if (onlyPiranha) {
toHit.addModifier(-1, "piranha missile");
}
}
// barracuda missiles in an AR10 launcher (must all be
// barracuda)
else if (wtype.getAtClass() == WeaponType.CLASS_AR10) {
boolean onlyBarracuda = true;
for (int wId : weapon.getBayWeapons()) {
Mounted bweap = ae.getEquipment(wId);
Mounted bammo = bweap.getLinked();
if (bammo != null) {
AmmoType batype = (AmmoType) bammo.getType();
if (!batype.hasFlag(AmmoType.F_AR10_BARRACUDA)) {
onlyBarracuda = false;
}
}
}
if (onlyBarracuda) {
toHit.addModifier(-2, "barracuda missile");
}
}
// LBX cluster
else if (wtype.getAtClass() == WeaponType.CLASS_LBX_AC) {
boolean onlyCluster = true;
for (int wId : weapon.getBayWeapons()) {
Mounted bweap = ae.getEquipment(wId);
Mounted bammo = bweap.getLinked();
if (bammo != null) {
AmmoType batype = (AmmoType) bammo.getType();
if (batype.getMunitionType() != AmmoType.M_CLUSTER) {
onlyCluster = false;
break;
}
}
}
if (onlyCluster) {
toHit.addModifier(-1, "cluster ammo");
}
}
}
}
if(wtype.hasFlag(WeaponType.F_ANTI_SHIP) && (target instanceof Entity) && (te.getWeight() < 500)) {
toHit.addModifier(4, "Anti-ship missile at a small target");
}
if (target instanceof Aero) {
Aero a = (Aero) target;
// is the target at zero velocity
if (a.getCurrentVelocity() == 0) {
toHit.addModifier(-2, "target is not moving");
}
// capital weapon (except missiles) penalties at small targets
if (wtype.isCapital()
&& (wtype.getAtClass() != WeaponType.CLASS_CAPITAL_MISSILE)
&& (wtype.getAtClass() != WeaponType.CLASS_AR10)
&& !te.isLargeCraft()) {
//check to see if we are using AAA mode
int aaaMod = 0;
if(wtype.hasModes() && weapon.curMode().equals("AAA")) {
aaaMod = 2;
}
if(wtype.isSubCapital()) {
toHit.addModifier(3-aaaMod, "sub-capital weapon at small target");
} else {
toHit.addModifier(5-aaaMod, "capital weapon at small target");
}
}
//AAA mode makes targeting large craft more difficult
if(wtype.hasModes() && weapon.curMode().equals("AAA") && te.isLargeCraft()) {
toHit.addModifier(+1, "AAA mode at large craft");
}
//check for bracketing mode
if(wtype.hasModes() && weapon.curMode().equals("Bracket 80%")) {
toHit.addModifier(-1, "Bracketing 80%");
}
if(wtype.hasModes() && weapon.curMode().equals("Bracket 60%")) {
toHit.addModifier(-2, "Bracketing 60%");
}
if(wtype.hasModes() && weapon.curMode().equals("Bracket 40%")) {
toHit.addModifier(-3, "Bracketing 40%");
}
//sensor shadows
if(game.getOptions().booleanOption("stratops_sensor_shadow") && game.getBoard().inSpace()) {
for(Entity en : Compute.getAdjacentEntitiesAlongAttack(ae.getPosition(), target.getPosition(), game)) {
if(!en.isEnemyOf(a) && en.isLargeCraft()
&& ((en.getWeight() - a.getWeight()) >= -100000.0)) {
toHit.addModifier(+1, "Sensor Shadow");
break;
}
}
for (Enumeration<Entity> i = game.getEntities(target.getPosition()); i.hasMoreElements();) {
Entity en = i.nextElement();
if(!en.isEnemyOf(a) && en.isLargeCraft() && !en.equals(a)
&& ((en.getWeight() - a.getWeight()) >= -100000.0)) {
toHit.addModifier(+1, "Sensor Shadow");
break;
}
}
}
}
// Vehicles may suffer from criticals
if (ae instanceof Tank) {
Tank tank = (Tank) ae;
if (tank.isCommanderHit()) {
if (ae instanceof VTOL) {
toHit.addModifier(+1, "copilot injured");
} else {
toHit.addModifier(+1, "commander injured");
}
}
int sensors = tank.getSensorHits();
if (sensors > 0) {
toHit.addModifier(sensors, "sensor damage");
}
if (tank.isStabiliserHit(weapon.getLocation())) {
toHit.addModifier(Compute.getAttackerMovementModifier(game,
tank.getId()).getValue(), "stabiliser damage");
}
}
if (ae.hasFunctionalArmAES(weapon.getLocation()) && !weapon.isSplit()) {
toHit.addModifier(-1, "AES modifer");
}
if (ae.hasShield()) {
// active shield has already been checked as it makes shots
// impossible
// time to check passive defense and no defense
if (ae.hasPassiveShield(weapon.getLocation(), weapon
.isRearMounted())) {
toHit.addModifier(+2, "weapon hampered by passive shield");
} else if (ae.hasNoDefenseShield(weapon.getLocation())) {
toHit.addModifier(+1, "weapon hampered by shield");
}
}
// if we have BAP with MaxTech rules, and there are woods in the
// way, and we are within BAP range, we reduce the BTH by 1
if (game.getOptions().booleanOption("tacops_bap")
&& !isIndirect
&& (te != null)
&& ae.hasBAP()
&& (ae.getBAPRange() >= Compute.effectiveDistance(game, ae, te))
&& !Compute.isAffectedByECM(ae, ae.getPosition(), te
.getPosition())
&& (game.getBoard().getHex(te.getPosition()).containsTerrain(
Terrains.WOODS)
|| game.getBoard().getHex(te.getPosition())
.containsTerrain(Terrains.JUNGLE)
|| (los.getLightWoods() > 0) || (los.getHeavyWoods() > 0) || (los
.getUltraWoods() > 0))) {
toHit
.addModifier(-1,
"target in/behind woods and attacker has BAP");
}
// Is the pilot a weapon specialist?
if (ae.crew.getOptions().stringOption("weapon_specialist").equals(
wtype.getName())) {
toHit.addModifier(-2, "weapon specialist");
}
// Has the pilot the appropriate gunnery skill?
if (ae.crew.getOptions().booleanOption("gunnery_laser")
&& wtype.hasFlag(WeaponType.F_ENERGY)) {
toHit.addModifier(-1, "Gunnery/Laser");
}
if (ae.crew.getOptions().booleanOption("gunnery_ballistic")
&& wtype.hasFlag(WeaponType.F_BALLISTIC)) {
toHit.addModifier(-1, "Gunnery/Ballistic");
}
if (ae.crew.getOptions().booleanOption("gunnery_missile")
&& wtype.hasFlag(WeaponType.F_MISSILE)) {
toHit.addModifier(-1, "Gunnery/Missile");
}
// check for VDNI
if (ae.crew.getOptions().booleanOption("vdni")
|| ae.crew.getOptions().booleanOption("bvdni")) {
toHit.addModifier(-1, "VDNI");
}
//check for pl-masc
if (ae.crew.getOptions().booleanOption("pl_masc")
&& (ae.getMovementMode() == IEntityMovementMode.INF_LEG)) {
toHit.addModifier(+1, "PL-MASC");
}
//check for cyber eye laser sighting
if (ae.crew.getOptions().booleanOption("cyber_eye_tele")) {
toHit.addModifier(-1, "MD laser-sighting");
}
// If it has a torso-mounted cockpit and two head sensor hits or three
// sensor hits...
// It gets a =4 penalty for being blind!
if ((ae instanceof Mech)
&& (((Mech) ae).getCockpitType() == Mech.COCKPIT_TORSO_MOUNTED)) {
int sensorHits = ae.getBadCriticals(CriticalSlot.TYPE_SYSTEM,
Mech.SYSTEM_SENSORS, Mech.LOC_HEAD);
if (sensorHits == 2) {
toHit.addModifier(4,
"Head Sensors Destroyed for Torso-Mounted Cockpit");
}
}
// industrial cockpit: +1 to hit
if ((ae instanceof Mech) && (((Mech)ae).getCockpitType() == Mech.COCKPIT_INDUSTRIAL)) {
toHit.addModifier(1, "industrial cockpit without advanced fire control");
}
// primitive industrial cockpit: +2 to hit
if ((ae instanceof Mech) && (((Mech)ae).getCockpitType() == Mech.COCKPIT_PRIMITIVE_INDUSTRIAL)) {
toHit.addModifier(2, "primitive industrial cockpit without advanced fire control");
}
// primitive industrial cockpit with advanced firing control: +1 to hit
if ((ae instanceof Mech) && (((Mech)ae).getCockpitType() == Mech.COCKPIT_PRIMITIVE) && ((Mech)ae).isIndustrial()) {
toHit.addModifier(1, "primitive industrial cockpit with advanced fire control");
}
// Do we use Listen-Kill ammo from War of 3039 sourcebook?
if (!isECMAffected
&& (atype != null)
&& ((atype.getAmmoType() == AmmoType.T_LRM)
|| (atype.getAmmoType() == AmmoType.T_MML) || (atype
.getAmmoType() == AmmoType.T_SRM))
&& (atype.getMunitionType() == AmmoType.M_LISTEN_KILL)
&& !((te != null) && te.isClan())) {
toHit.addModifier(-1, "Listen-Kill ammo");
}
// determine some more variables
int aElev = ae.getElevation();
int tElev = target.getElevation();
int distance = Compute.effectiveDistance(game, ae, target);
toHit.append(AbstractAttackAction.nightModifiers(game, target, atype, ae, true));
// weather mods (not in space)
int weatherMod = game.getPlanetaryConditions().getWeatherHitPenalty(ae);
if ((weatherMod != 0) && !game.getBoard().inSpace()) {
toHit.addModifier(weatherMod, game.getPlanetaryConditions()
.getWeatherCurrentName());
}
// wind mods (not in space)
if (!game.getBoard().inSpace()) {
int windCond = game.getPlanetaryConditions().getWindStrength();
if (windCond == PlanetaryConditions.WI_MOD_GALE) {
if (wtype.hasFlag(WeaponType.F_MISSILE)) {
toHit.addModifier(1, PlanetaryConditions
.getWindDisplayableName(windCond));
}
} else if (windCond == PlanetaryConditions.WI_STRONG_GALE) {
if (wtype.hasFlag(WeaponType.F_BALLISTIC)) {
toHit.addModifier(1, PlanetaryConditions
.getWindDisplayableName(windCond));
} else if (wtype.hasFlag(WeaponType.F_MISSILE)) {
toHit.addModifier(2, PlanetaryConditions
.getWindDisplayableName(windCond));
}
} else if (windCond == PlanetaryConditions.WI_STORM) {
if (wtype.hasFlag(WeaponType.F_BALLISTIC)) {
toHit.addModifier(2, PlanetaryConditions
.getWindDisplayableName(windCond));
} else if (wtype.hasFlag(WeaponType.F_MISSILE)) {
toHit.addModifier(3, PlanetaryConditions
.getWindDisplayableName(windCond));
}
} else if (windCond == PlanetaryConditions.WI_TORNADO_F13) {
if (wtype.hasFlag(WeaponType.F_ENERGY)) {
toHit.addModifier(2, PlanetaryConditions
.getWindDisplayableName(windCond));
} else {
toHit.addModifier(3, PlanetaryConditions
.getWindDisplayableName(windCond));
}
} else if (windCond == PlanetaryConditions.WI_TORNADO_F4) {
toHit.addModifier(3, PlanetaryConditions
.getWindDisplayableName(windCond));
}
}
// fog mods (not in space)
if (wtype.hasFlag(WeaponType.F_ENERGY)
&& !game.getBoard().inSpace()
&& (game.getPlanetaryConditions().getFog() == PlanetaryConditions.FOG_HEAVY)) {
toHit.addModifier(1, "heavy fog");
}
//blowind sand mods
if(wtype.hasFlag(WeaponType.F_ENERGY) && !game.getBoard().inSpace()
&& game.getPlanetaryConditions().isSandBlowing()
&& (game.getPlanetaryConditions().getWindStrength() > PlanetaryConditions.WI_LIGHT_GALE)) {
toHit.addModifier(1, "blowing sand");
}
// gravity mods (not in space)
if (!game.getBoard().inSpace()) {
int mod = (int) Math.ceil(Math.abs((game.getPlanetaryConditions()
.getGravity() - 1.0f) / 0.2f));
if ((mod != 0)
&& (wtype.hasFlag(WeaponType.F_BALLISTIC) || wtype
.hasFlag(WeaponType.F_MISSILE))) {
toHit.addModifier(mod, "gravity");
}
}
// Electro-Magnetic Interference
if (game.getPlanetaryConditions().hasEMI()
&& !((ae instanceof Infantry) && !(ae instanceof BattleArmor))) {
toHit.addModifier(2, "EMI");
}
// handle LAM speial rules
// a temporary variable so I don't need to keep casting.
LandAirMech lam;
if (ae instanceof LandAirMech) {
lam = (LandAirMech) ae;
if (lam.isInMode(LandAirMech.MODE_AIRMECH)) {
toHit.addModifier(2, "Attacker is a Flying Airmek");
}
}
if (target instanceof LandAirMech) {
lam = (LandAirMech) target;
if (lam.isInMode(LandAirMech.MODE_AIRMECH) && lam.isFlying()) {
if (ae.isFlying()) {
toHit.addModifier(-1, "Target is a flying Airmek"); // and
// we
// are
// too.
} else {
toHit.addModifier(4, "Target is a flying Airmek");// and
// we
// are
// on
// the
// ground
}
}
}
// Handle direct artillery attacks.
if (isArtilleryDirect) {
if (!isArtilleryFLAK) {
toHit.addModifier(4, "direct artillery modifer");
}
toHit.append(Compute.getAttackerMovementModifier(game, attackerId));
toHit.append(losMods);
toHit.append(Compute.getSecondaryTargetMod(game, ae, target));
// actuator & sensor damage to attacker
toHit.append(Compute.getDamageWeaponMods(ae, weapon));
// heat
if (ae.getHeatFiringModifier() != 0) {
toHit.addModifier(ae.getHeatFiringModifier(), "heat");
}
// weapon to-hit modifier
if (wtype instanceof VariableSpeedPulseLaserWeapon) {
int nRange = ae.getPosition().distance(target.getPosition());
int[] nRanges = wtype.getRanges(weapon);
int modifier = wtype.getToHitModifier();
if (nRange <= nRanges[RangeType.RANGE_SHORT]) {
modifier -= RangeType.RANGE_SHORT;
} else if (nRange <= nRanges[RangeType.RANGE_MEDIUM]) {
modifier -= RangeType.RANGE_MEDIUM;
} else if (nRange <= nRanges[RangeType.RANGE_LONG]) {
modifier -= RangeType.RANGE_LONG;
} else {
modifier = 0;
}
toHit.addModifier(modifier, "weapon to-hit modifier");
} else if (wtype instanceof ISBombastLaser) {
int damage = (int) Math.ceil((Compute.dialDownDamage(weapon,
wtype) - 7) / 2);
if (damage > 0) {
toHit.addModifier(damage, "weapon to-hit modifier");
}
} else if (wtype.getToHitModifier() != 0) {
toHit.addModifier(wtype.getToHitModifier(),
"weapon to-hit modifier");
}
// ammo to-hit modifier
if (usesAmmo && (atype.getToHitModifier() != 0)) {
toHit.addModifier(atype.getToHitModifier(),
"ammunition to-hit modifier");
}
if (isHoming) {
return new ToHitData(4, "Homing shot");
}
if (game.getEntity(attackerId).getOwner().getArtyAutoHitHexes()
.contains(target.getPosition())
&& !isArtilleryFLAK) {
return new ToHitData(TargetRoll.AUTOMATIC_SUCCESS,
"Artillery firing at designated artillery target.");
}
return toHit;
}
if (isArtilleryIndirect) {
if (isHoming) {
return new ToHitData(4, "Homing shot (will miss if TAG misses)");
}
if (game.getEntity(attackerId).getOwner().getArtyAutoHitHexes()
.contains(target.getPosition())) {
return new ToHitData(TargetRoll.AUTOMATIC_SUCCESS,
"Artillery firing at designated artillery target.");
}
toHit.addModifier(7, "indirect artillery modifier");
int adjust = ae.aTracker.getModifier(weapon, target.getPosition());
if (adjust == TargetRoll.AUTOMATIC_SUCCESS) {
return new ToHitData(TargetRoll.AUTOMATIC_SUCCESS,
"Artillery firing at target that's been hit before.");
} else if (adjust != 0) {
toHit.addModifier(adjust, "adjusted fire");
}
return toHit;
}
// Attacks against adjacent buildings automatically hit.
if ((distance == 1)
&& ((target.getTargetType() == Targetable.TYPE_BUILDING)
|| (target.getTargetType() == Targetable.TYPE_BLDG_IGNITE)
|| (target.getTargetType() == Targetable.TYPE_FUEL_TANK)
|| (target.getTargetType() == Targetable.TYPE_FUEL_TANK_IGNITE) || (target instanceof GunEmplacement))) {
return new ToHitData(TargetRoll.AUTOMATIC_SUCCESS,
"Targeting adjacent building.");
}
// Attacks against buildings from inside automatically hit.
if ((null != los.getThruBldg())
&& ((target.getTargetType() == Targetable.TYPE_BUILDING)
|| (target.getTargetType() == Targetable.TYPE_BLDG_IGNITE)
|| (target.getTargetType() == Targetable.TYPE_FUEL_TANK)
|| (target.getTargetType() == Targetable.TYPE_FUEL_TANK_IGNITE) || (target instanceof GunEmplacement))) {
return new ToHitData(TargetRoll.AUTOMATIC_SUCCESS,
"Targeting building from inside (are you SURE this is a good idea?).");
}
// add range mods
toHit.append(Compute.getRangeMods(game, ae, weaponId, target));
// If it's an anti-air system, add mods for that
if ((ae.getTargSysType() == MiscType.T_TARGSYS_ANTI_AIR)
&& (target instanceof Entity)) {
if (target instanceof VTOL) {
toHit.addModifier(-2, "anti-air targetting system vs. VTOL");
} else {
toHit.addModifier(1,
"anti-air targetting system vs. non-aerial unit");
}
}
// Battle Armor targets are hard for Meks and Tanks to hit.
if (!isAttackerInfantry && (te != null) && (te instanceof BattleArmor)) {
toHit.addModifier(1, "battle armor target");
}
// Ejected MechWarriors are harder to hit
if ((te != null) && (te instanceof MechWarrior)) {
toHit.addModifier(2, "ejected MechWarrior target");
}
// Indirect fire has a +1 mod
if (isIndirect) {
toHit.addModifier(1, "indirect fire");
}
if (wtype instanceof MekMortarWeapon) {
if (isIndirect) {
if (spotter == null) {
toHit.addModifier(2, "no spotter");
}
} else {
toHit.addModifier(3, "direct fire");
}
}
// attacker movement
toHit.append(Compute.getAttackerMovementModifier(game, attackerId));
// target movement
if (te != null) {
ToHitData thTemp = Compute.getTargetMovementModifier(game, target
.getTargetId());
toHit.append(thTemp);
toSubtract += thTemp.getValue();
// semiguided ammo negates this modifier, if TAG succeeded
if ((atype != null)
&& ((atype.getAmmoType() == AmmoType.T_LRM) || (atype
.getAmmoType() == AmmoType.T_MML))
&& (atype.getMunitionType() == AmmoType.M_SEMIGUIDED)
&& (te.getTaggedBy() != -1)) {
int nAdjust = thTemp.getValue();
if (nAdjust > 0) {
toHit.append(new ToHitData(-nAdjust,
"Semi-guided ammo vs tagged target"));
}
}
// precision ammo reduces this modifier
else if ((atype != null)
&& ((atype.getAmmoType() == AmmoType.T_AC) || (atype
.getAmmoType() == AmmoType.T_LAC))
&& (atype.getMunitionType() == AmmoType.M_PRECISION)) {
int nAdjust = Math.min(2, thTemp.getValue());
if (nAdjust > 0) {
toHit.append(new ToHitData(-nAdjust, "Precision Ammo"));
}
}
}
// Armor Piercing ammo is a flat +1
if ((atype != null)
&& ((atype.getAmmoType() == AmmoType.T_AC) || (atype.getAmmoType() == AmmoType.T_LAC))
&& (atype.getMunitionType() == AmmoType.M_ARMOR_PIERCING)) {
toHit.addModifier(1, "Armor-Piercing Ammo");
}
// spotter movement, if applicable
if (isIndirect) {
// semiguided ammo negates this modifier, if TAG succeeded
if ((atype != null)
&& ((atype.getAmmoType() == AmmoType.T_LRM) || (atype
.getAmmoType() == AmmoType.T_MML))
&& (atype.getMunitionType() == AmmoType.M_SEMIGUIDED)
&& (te.getTaggedBy() != -1)) {
toHit
.addModifier(-1,
"semiguided ignores spotter movement & indirect fire penalties");
} else if (!narcSpotter && (spotter != null)) {
toHit.append(Compute.getSpotterMovementModifier(game, spotter
.getId()));
if (spotter.isAttackingThisTurn()) {
toHit.addModifier(1,
"spotter is making an attack this turn");
}
}
}
// attacker terrain
toHit.append(Compute.getAttackerTerrainModifier(game, attackerId));
// target terrain, not applicable when delivering minefields
if (target.getTargetType() != Targetable.TYPE_MINEFIELD_DELIVER) {
toHit.append(Compute.getTargetTerrainModifier(game, target,
eistatus, inSameBuilding));
toSubtract += Compute.getTargetTerrainModifier(game, target,
eistatus, inSameBuilding).getValue();
}
// target in water?
IHex targHex = game.getBoard().getHex(target.getPosition());
if ((target.getTargetType() == Targetable.TYPE_ENTITY)
&& targHex.containsTerrain(Terrains.WATER)
&& (targHex.terrainLevel(Terrains.WATER) == 1) && (targEl == 0)
&& (te.height() > 0)) { // target in partial water
los.setTargetCover(los.getTargetCover()
| LosEffects.COVER_HORIZONTAL);
losMods = los.losModifiers(game, eistatus);
}
if ((target instanceof Infantry) && !wtype.hasFlag(WeaponType.F_FLAMER)) {
if (targHex.containsTerrain(Terrains.FORTIFIED)
|| (((Infantry) target).getDugIn() == Infantry.DUG_IN_COMPLETE)) {
toHit.addModifier(2, "infantry dug in");
}
}
// add in LOS mods that we've been keeping
toHit.append(losMods);
if ((te != null)
&& te.isHullDown()
&& (((te instanceof Mech)
&& (los.getTargetCover() > LosEffects.COVER_NONE)) || ((te instanceof Tank)
&& targHex.containsTerrain(Terrains.FORTIFIED)
&& (te.sideTable(ae.getPosition()) == ToHitData.SIDE_FRONT)))) {
toHit.addModifier(2, "Hull down target");
}
// secondary targets modifier,
// if this is not a iNarc Nemesis confused attack
if (!isNemesisConfused) {
toHit.append(Compute.getSecondaryTargetMod(game, ae, target,
exchangeSwarmTarget));
}
// heat
if (ae.getHeatFiringModifier() != 0) {
toHit.addModifier(ae.getHeatFiringModifier(), "heat");
}
// actuator & sensor damage to attacker
toHit.append(Compute.getDamageWeaponMods(ae, weapon));
// target immobile
ToHitData immobileMod = Compute.getImmobileMod(target, aimingAt,
aimingMode);
if (immobileMod != null) {
toHit.append(immobileMod);
toSubtract += immobileMod.getValue();
}
// attacker prone
toHit.append(Compute.getProneMods(game, ae, weaponId));
// target prone
ToHitData proneMod = null;
if ((te != null) && te.isProne()) {
// easier when point-blank
if (distance <= 1) {
// TW, pg. 221: Swarm Mek attacks apply prone/immobile mods as
// normal.
proneMod = new ToHitData(-2, "target prone and adjacent");
} else {
// Harder at range.
proneMod = new ToHitData(1, "target prone and at range");
}
}
if (proneMod != null) {
toHit.append(proneMod);
toSubtract += proneMod.getValue();
}
// weapon to-hit modifier
if (wtype instanceof VariableSpeedPulseLaserWeapon) {
int nRange = ae.getPosition().distance(target.getPosition());
int[] nRanges = wtype.getRanges(weapon);
int modifier = wtype.getToHitModifier();
if (nRange <= nRanges[RangeType.RANGE_SHORT]) {
modifier += RangeType.RANGE_SHORT;
} else if (nRange <= nRanges[RangeType.RANGE_MEDIUM]) {
modifier += RangeType.RANGE_MEDIUM;
} else if (nRange <= nRanges[RangeType.RANGE_LONG]) {
modifier += RangeType.RANGE_LONG;
} else {
modifier = 0;
}
toHit.addModifier(modifier, "weapon to-hit modifier");
} else if (wtype instanceof ISBombastLaser) {
double damage = Compute.dialDownDamage(weapon, wtype);
damage = Math.ceil((damage - 7) / 2);
if (damage > 0) {
toHit.addModifier((int) damage, "weapon to-hit modifier");
}
} else if (wtype.getToHitModifier() != 0) {
toHit.addModifier(wtype.getToHitModifier(),
"weapon to-hit modifier");
}
// ammo to-hit modifier
if ((te != null)
&& ((te.getMovementMode() == IEntityMovementMode.VTOL)
|| (te.getMovementMode() == IEntityMovementMode.AERODYNE)
|| (te.getMovementMode() == IEntityMovementMode.AIRMECH)
|| (te.getMovementMode() == IEntityMovementMode.AEROSPACE)
|| (te.getMovementMode() == IEntityMovementMode.SPHEROID) || (te
.getMovementMode() == IEntityMovementMode.WIGE))
&& (atype != null)
&& ((((atype.getAmmoType() == AmmoType.T_AC_LBX)
|| (atype.getAmmoType() == AmmoType.T_AC_LBX_THB)
|| (atype.getAmmoType() == AmmoType.T_SBGAUSS))
&& (atype.getMunitionType() == AmmoType.M_CLUSTER))
|| (atype.getMunitionType() == AmmoType.M_FLAK)
|| (atype.getAmmoType() == AmmoType.T_HAG))
&& (te.getElevation() > 0)
&& (te.getElevation() > game.getBoard().getHex(te.getPosition())
.terrainLevel(Terrains.BLDG_ELEV))
&& (te.getElevation() != game.getBoard()
.getHex(te.getPosition()).terrainLevel(
Terrains.BRIDGE_ELEV))) {
toHit.addModifier(-2, "flak to-hit modifier");
}
if (usesAmmo && (atype.getToHitModifier() != 0)) {
toHit.addModifier(atype.getToHitModifier(),
"ammunition to-hit modifier");
}
// add iNarc bonus
if (isINarcGuided) {
toHit.addModifier(-1, "iNarc homing pod");
}
if (isHaywireINarced) {
toHit.addModifier(1, "iNarc Haywire pod");
}
// `Screen launchers hit automatically (if in range)
if ((toHit.getValue() != TargetRoll.IMPOSSIBLE)
&& ((wtype.getAmmoType() == AmmoType.T_SCREEN_LAUNCHER) || (wtype instanceof ScreenLauncherBayWeapon))) {
return new ToHitData(TargetRoll.AUTOMATIC_SUCCESS,
"Screen launchers always hit");
}
// Heat Seeking Missles
if (bHeatSeeking) {
if (te == null) {
if ((target.getTargetType() == Targetable.TYPE_BUILDING)
|| (target.getTargetType() == Targetable.TYPE_BLDG_IGNITE)
|| (target.getTargetType() == Targetable.TYPE_FUEL_TANK)
|| (target.getTargetType() == Targetable.TYPE_FUEL_TANK_IGNITE)
|| (target instanceof GunEmplacement)) {
IHex hexTarget = game.getBoard().getHex(
target.getPosition());
if (hexTarget.containsTerrain(Terrains.FIRE)) {
toHit.addModifier(-2, "ammunition to-hit modifier");
}
}
} else if ((te instanceof Aero)
&& (toHit.getSideTable() == ToHitData.SIDE_REAR)) {
toHit.addModifier(-2, "ammunition to-hit modifier");
} else if (te.heat == 0) {
toHit.addModifier(1, "ammunition to-hit modifier");
} else {
toHit.addModifier(-te.getHeatMPReduction(),
"ammunition to-hit modifier");
}
if (!(ae instanceof Aero)
&& LosEffects.hasFireBetween(ae.getPosition(), target
.getPosition(), game)) {
toHit.addModifier(2, "fire between target and attacker");
}
}
if (bFTL) {
toHit.addModifier(2, "ammunition to-hit modifier");
}
if (bApollo) {
toHit.addModifier(-1, "Apollo FCS");
}
// Heavy infantry have +1 penalty
if ((ae instanceof Infantry)
&& ae.hasWorkingMisc(MiscType.F_TOOLS, MiscType.S_HEAVY_ARMOR)) {
toHit.addModifier(1, "Heavy Armor");
}
//penalty for void sig system
if(ae.isVoidSigActive()) {
toHit.addModifier(1, "Void signature active");
}
// add targeting computer (except with LBX cluster ammo)
if ((aimingMode == IAimingModes.AIM_MODE_TARG_COMP)
&& (aimingAt != Entity.LOC_NONE)) {
if (ae.hasActiveEiCockpit()) {
if (ae.hasTargComp()) {
toHit.addModifier(2,
"aiming with targeting computer & EI system");
} else {
toHit.addModifier(6, "aiming with EI system");
}
} else {
toHit.addModifier(3, "aiming with targeting computer");
}
} else {
if (ae.hasTargComp()
&& wtype.hasFlag(WeaponType.F_DIRECT_FIRE)
&& (!usesAmmo || !(((atype.getAmmoType() == AmmoType.T_AC_LBX) || (atype
.getAmmoType() == AmmoType.T_AC_LBX_THB)) && (atype
.getMunitionType() == AmmoType.M_CLUSTER)))) {
toHit.addModifier(-1, "targeting computer");
}
}
// Change hit table for elevation differences inside building.
if ((null != los.getThruBldg()) && (aElev != tElev)) {
// Tanks get hit in a random side.
if (target instanceof Tank) {
toHit.setSideTable(ToHitData.SIDE_RANDOM);
} else if (target instanceof Mech) {
// Meks have special tables for shots from above and below.
if (aElev > tElev) {
toHit.setHitTable(ToHitData.HIT_ABOVE);
} else {
toHit.setHitTable(ToHitData.HIT_BELOW);
}
}
}
// Aeros in atmosphere can hit above and below
if ((ae instanceof Aero) && (target instanceof Aero)
&& game.getBoard().inAtmosphere()) {
if ((aElev - tElev) > 2) {
toHit.setHitTable(ToHitData.HIT_ABOVE);
} else if ((tElev - aElev) > 2) {
toHit.setHitTable(ToHitData.HIT_BELOW);
}
}
//is this attack originating from underwater
//TODO: assuming that torpedoes are underwater attacks even if fired from surface vessel, awaiting rules clarification
//http://www.classicbattletech.com/forums/index.php/topic,48744.0.html
boolean underWater = (ae.getLocationStatus(weapon.getLocation()) == ILocationExposureStatus.WET) || (wtype instanceof SRTWeapon) || (wtype instanceof LRTWeapon);
// Change hit table for partial cover, accomodate for partial
// underwater(legs)
if (los.getTargetCover() != LosEffects.COVER_NONE) {
if (underWater
&& (targHex.containsTerrain(Terrains.WATER) && (targEl == 0) && (te
.height() > 0))) {
// weapon underwater, target in partial water
toHit.setHitTable(ToHitData.HIT_PARTIAL_COVER);
toHit.setCover(LosEffects.COVER_UPPER);
} else {
if (game.getOptions().booleanOption("tacops_partial_cover")) {
toHit.setHitTable(ToHitData.HIT_PARTIAL_COVER);
toHit.setCover(los.getTargetCover());
} else {
toHit.setHitTable(ToHitData.HIT_PARTIAL_COVER);
toHit.setCover(LosEffects.COVER_HORIZONTAL);
}
}
// XXX what to do about GunEmplacements with partial cover?
}
//change hit table for surface vessels hit by underwater attacks
if(underWater && targHex.containsTerrain(Terrains.WATER) && (null != te)
&& te.isSurfaceNaval()) {
toHit.setHitTable(ToHitData.HIT_UNDERWATER);
}
// factor in target side
if (isAttackerInfantry && (0 == distance)) {
// Infantry attacks from the same hex are resolved against the
// front.
toHit.setSideTable(ToHitData.SIDE_FRONT);
} else {
toHit.setSideTable(Compute.targetSideTable(ae, target));
}
if (target instanceof Aero) {
// hit locations for spheroids in atmosphere are handled differently
// TODO: awaiting rules clarification on forums
// http://www.classicbattletech.com/forums/index.php/topic,29329.0.
// html
// Until then assume that above/below are actually nose/aft
if (((Aero) target).isSpheroid() && game.getBoard().inAtmosphere()) {
if (toHit.getHitTable() == ToHitData.HIT_ABOVE) {
toHit.setSideTable(ToHitData.SIDE_FRONT);
toHit.setHitTable(ToHitData.HIT_NORMAL);
}
if (toHit.getHitTable() == ToHitData.HIT_BELOW) {
toHit.setSideTable(ToHitData.SIDE_REAR);
toHit.setHitTable(ToHitData.HIT_NORMAL);
}
} else {
// get mods for direction of attack
int side = toHit.getSideTable();
// if this is an aero attack using advanced movement rules then
// determine side differently
if ((target instanceof Aero) && game.useVectorMove()) {
boolean usePrior = false;
Coords attackPos = ae.getPosition();
if(game.getBoard().inSpace() && ae.getPosition().equals(target.getPosition())) {
if(((Aero)ae).shouldMoveBackHex((Aero)target)) {
attackPos = ae.getPriorPosition();
}
usePrior = ((Aero)target).shouldMoveBackHex((Aero)ae);
}
side = ((Entity) target).chooseSide(attackPos,usePrior);
}
if (side == ToHitData.SIDE_FRONT) {
toHit.addModifier(+1, "attack against nose");
}
if ((side == ToHitData.SIDE_LEFT) || (side == ToHitData.SIDE_RIGHT)) {
toHit.addModifier(+2, "attack against side");
}
}
}
// deal with grapples
if (target instanceof Entity) {
int grapple = ((Entity) target).getGrappled();
if (grapple != Entity.NONE) {
if ((grapple == ae.getId())
&& (((Entity) target).getGrappleSide() == Entity.GRAPPLE_BOTH)) {
toHit.addModifier(-4, "target grappled");
} else if ((grapple == ae.getId())
&& (((Entity) target).getGrappleSide() != Entity.GRAPPLE_BOTH)) {
toHit.addModifier(-2, "target grappled (Chain Whip)");
} else if (!exchangeSwarmTarget) {
toHit.addModifier(1, "CQC, possible friendly fire");
} else {
// this -1 cancels the original +1
toHit.addModifier(-1, "friendly fire");
return toHit;
}
}
}
// remove old target movement and terrain mods,
// add those for new target.
if (exchangeSwarmTarget) {
toHit.addModifier(-toSubtract, "original target mods");
toHit.append(Compute
.getImmobileMod(oldTarget, aimingAt, aimingMode));
toHit.append(Compute.getTargetMovementModifier(game, oldTarget.getId()));
toHit.append(Compute.getTargetTerrainModifier(game, game
.getEntity(oldTarget.getId()), eistatus, inSameBuilding));
toHit.setCover(LosEffects.COVER_NONE);
distance = Compute.effectiveDistance(game, ae, oldTarget);
LosEffects swarmlos = LosEffects.calculateLos(game, te.getId(), oldTarget);
// reset cover
if (swarmlos.getTargetCover() != LosEffects.COVER_NONE) {
if (game.getOptions().booleanOption("tacops_partial_cover")) {
toHit.setHitTable(ToHitData.HIT_PARTIAL_COVER);
toHit.setCover(swarmlos.getTargetCover());
} else {
toHit.setHitTable(ToHitData.HIT_PARTIAL_COVER);
toHit.setCover(LosEffects.COVER_HORIZONTAL);
}
}