Targetable target, int weaponId, int aimingAt, int aimingMode,
boolean isNemesisConfused, boolean exchangeSwarmTarget,
Entity oldTarget) {
final Entity ae = game.getEntity(attackerId);
final Mounted weapon = ae.getEquipment(weaponId);
final WeaponType wtype = (WeaponType) weapon.getType();
if (exchangeSwarmTarget) {
// Quick check, is the new target out of range for the weapon?
if (RangeType.rangeBracket(ae.getPosition().distance(
target.getPosition()), wtype.getRanges(weapon), game
.getOptions().booleanOption("tacops_range")) == RangeType.RANGE_OUT) {
return new ToHitData(TargetRoll.AUTOMATIC_FAIL,
"swarm target out of range");
}
// this is a swarm attack against a new target
// first, exchange old and new targets to get all mods
// as if firing against old target.
// at the end of this function, we remove target terrain
// and movement mods, and add those for the new target
Targetable tempTarget = target;
target = oldTarget;
oldTarget = (Entity) tempTarget;
}
Entity te = null;
if (target.getTargetType() == Targetable.TYPE_ENTITY) {
te = (Entity) target;
}
boolean isAttackerInfantry = ae instanceof Infantry;
boolean isWeaponInfantry = wtype.hasFlag(WeaponType.F_INFANTRY);
// 2003-01-02 BattleArmor MG and Small Lasers have unlimited ammo.
// 2002-09-16 Infantry weapons have unlimited ammo.
final boolean usesAmmo = (wtype.getAmmoType() != AmmoType.T_NA)
&& !isWeaponInfantry;
final Mounted ammo = usesAmmo ? weapon.getLinked() : null;
final AmmoType atype = ammo == null ? null : (AmmoType) ammo.getType();
final boolean targetInBuilding = Compute.isInBuilding(game, te);
boolean isIndirect = wtype.hasModes()
&& weapon.curMode().equals("Indirect");
boolean isInferno = ((atype != null)
&& ((atype.getAmmoType() == AmmoType.T_SRM) || (atype
.getAmmoType() == AmmoType.T_MML))
&& (atype.getMunitionType() == AmmoType.M_INFERNO))
|| (isWeaponInfantry && (wtype.hasFlag(WeaponType.F_INFERNO)));
boolean isArtilleryDirect = wtype.hasFlag(WeaponType.F_ARTILLERY)
&& (game.getPhase() == IGame.Phase.PHASE_FIRING);
boolean isArtilleryIndirect = wtype.hasFlag(WeaponType.F_ARTILLERY)
&& ((game.getPhase() == IGame.Phase.PHASE_TARGETING) || (game
.getPhase() == IGame.Phase.PHASE_OFFBOARD));
// hack, otherwise when actually resolves shot labeled impossible.
boolean isArtilleryFLAK = isArtilleryDirect
&& (target.getTargetType() == Targetable.TYPE_ENTITY)
&& (te.getMovementMode() == IEntityMovementMode.VTOL)
&& (te.getElevation() > 0)
&& (usesAmmo && (atype.getMunitionType() == AmmoType.M_STANDARD));
boolean isHaywireINarced = ae.isINarcedWith(INarcPod.HAYWIRE);
boolean isINarcGuided = false;
// for attacks where ECM along flight path makes a difference
boolean isECMAffected = Compute.isAffectedByECM(ae, ae.getPosition(),
target.getPosition());
// for attacks where only ECM on the target hex makes a difference
boolean isTargetECMAffected = Compute.isAffectedByECM(ae, target
.getPosition(), target.getPosition());
boolean isTAG = wtype.hasFlag(WeaponType.F_TAG);
boolean isHoming = false;
boolean bHeatSeeking = (atype != null)
&& ((atype.getAmmoType() == AmmoType.T_SRM)
|| (atype.getAmmoType() == AmmoType.T_MML) || (atype
.getAmmoType() == AmmoType.T_LRM))
&& (atype.getMunitionType() == AmmoType.M_HEAT_SEEKING);
boolean bFTL = (atype != null)
&& ((atype.getAmmoType() == AmmoType.T_MML) || (atype
.getAmmoType() == AmmoType.T_LRM))
&& (atype.getMunitionType() == AmmoType.M_FOLLOW_THE_LEADER);
Mounted mLinker = weapon.getLinkedBy();
boolean bApollo = ((mLinker != null)
&& (mLinker.getType() instanceof MiscType)
&& !mLinker.isDestroyed() && !mLinker.isMissing()
&& !mLinker.isBreached() && mLinker.getType().hasFlag(
MiscType.F_APOLLO))
&& (atype.getAmmoType() == AmmoType.T_MRM);
boolean inSameBuilding = Compute.isInSameBuilding(game, ae, te);
if (te != null) {
if (!isTargetECMAffected
&& te.isINarcedBy(ae.getOwner().getTeam())
&& (atype != null)
&& ((atype.getAmmoType() == AmmoType.T_LRM)
|| (atype.getAmmoType() == AmmoType.T_MML) || (atype
.getAmmoType() == AmmoType.T_SRM))
&& (atype.getMunitionType() == AmmoType.M_NARC_CAPABLE)) {
isINarcGuided = true;
}
}
int toSubtract = 0;
final int ttype = target.getTargetType();
ToHitData toHit = null;
String reason = null;
reason = WeaponAttackAction.toHitIsImpossible(game, ae, target, weapon, atype, wtype,
ttype, exchangeSwarmTarget, usesAmmo, te, isTAG, isInferno,
isAttackerInfantry, isIndirect, attackerId, weaponId,
isArtilleryIndirect, ammo, isArtilleryFLAK, targetInBuilding,
isArtilleryDirect, isTargetECMAffected);
if (reason != null) {
return new ToHitData(TargetRoll.IMPOSSIBLE, reason);
}
//if this is a bombing attack then get the to hit and return
//TODO: this should probably be its own kind of attack
if ( wtype.hasFlag(WeaponType.F_SPACE_BOMB) ) {
toHit = Compute.getSpaceBombBaseToHit( ae, te, game );
return toHit;
}
//B-Pod firing at infantry in the same hex autohit
if (wtype.hasFlag(WeaponType.F_B_POD) && (target instanceof Infantry)
&& target.getPosition().equals(ae.getPosition())) {
return new ToHitData(TargetRoll.AUTOMATIC_SUCCESS, "B-Pod firing at infantry");
}
long munition = AmmoType.M_STANDARD;
if (atype != null) {
munition = atype.getMunitionType();
}
if (munition == AmmoType.M_HOMING) {
// target type checked later because its different for
// direct/indirect (BMRr p77 on board arrow IV)
isHoming = true;
}
int targEl;
if (te == null) {
targEl = game.getBoard().getHex(target.getPosition()).floor();
} else {
targEl = te.absHeight();
}
// TODO: mech making DFA could be higher if DFA target hex is higher
// BMRr pg. 43, "attacking unit is considered to be in the air
// above the hex, standing on an elevation 1 level higher than
// the target hex or the elevation of the hex the attacker is
// in, whichever is higher."
// if we're doing indirect fire, find a spotter
Entity spotter = null;
boolean narcSpotter = false;
if (isIndirect) {
if ((target instanceof Entity)
&& !isTargetECMAffected
&& (te != null)
&& (atype != null)
&& usesAmmo
&& (atype.getMunitionType() == AmmoType.M_NARC_CAPABLE)
&& (te.isNarcedBy(ae.getOwner().getTeam()) || te
.isINarcedBy(ae.getOwner().getTeam()))) {
spotter = te;
narcSpotter = true;
} else {
spotter = Compute.findSpotter(game, ae, target);
}
}
// EI system
// 0 if no EI (or switched off)
// 1 if no intervening light woods
// 2 if intervening light woods (because target in woods + intervening
// woods is only +1 total)
int eistatus = 0;
boolean MPMelevationHack = false;
if (usesAmmo
&& (wtype.getAmmoType() == AmmoType.T_LRM)
&& (atype != null)
&& (atype.getMunitionType() == AmmoType.M_MULTI_PURPOSE)
&& (ae.getElevation() == -1)
&& (ae.getLocationStatus(weapon.getLocation()) == ILocationExposureStatus.WET)) {
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;
}
}
}