int aElev = ae.getElevation(); int tElev = target.getElevation();
    private static String toHitIsImpossible(IGame game, Entity ae,                               Targetable target, Mounted weapon, AmmoType atype,                              WeaponType wtype, int ttype,                               boolean exchangeSwarmTarget, boolean usesAmmo,                              Entity te, boolean isTAG, boolean isInferno,                               boolean isAttackerInfantry, boolean isIndirect,                               int attackerId, int weaponId,                               boolean isArtilleryIndirect, Mounted ammo,                              boolean isArtilleryFLAK, boolean targetInBuilding,                              boolean isArtilleryDirect) {        boolean isHoming = false;        ToHitData toHit = null;        if ( ae.hasShield() && ae.hasActiveShield(weapon.getLocation(),weapon.isRearMounted()) ) {            return "Weapon blocked by active shield";        }        // 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);            int sensorHits2 = ae.getBadCriticals(CriticalSlot.TYPE_SYSTEM, Mech.SYSTEM_SENSORS, Mech.LOC_CT);            if ((sensorHits + sensorHits2) == 3) {                return "Sensors Completely Destroyed for Torso-Mounted Cockpit";            }        }        // missing, breached or jammed weapons can't fire        if (!weapon.canFire() && !exchangeSwarmTarget) {            return "Weapon is not in a state where it can be fired";        }        // If we're lying mines, we can't shoot.        if (ae.isLayingMines()) {            return "Can't fire weapons when laying mines";        }        // make sure weapon can deliver minefield        if (target.getTargetType() == Targetable.TYPE_MINEFIELD_DELIVER &&            !AmmoType.canDeliverMinefield(atype)) {            return "Weapon can't deliver minefields";        }        if (target.getTargetType() == Targetable.TYPE_FLARE_DELIVER                && !(usesAmmo                && atype.getAmmoType() == AmmoType.T_LRM                && atype.getMunitionType() == AmmoType.M_FLARE)) {            return "Weapon can't deliver flares";        }        if (game.getPhase() == IGame.PHASE_TARGETING && !isArtilleryIndirect) {            return "Only indirect artillery can be fired in the targeting phase";        }        if (game.getPhase() == IGame.PHASE_OFFBOARD && !isTAG) {            return "Only TAG can be fired in the offboard attack phase";        }        if (game.getPhase() != IGame.PHASE_OFFBOARD && isTAG) {            return "TAG can only be fired in the offboard attack phase";        }                if ( atype != null &&             atype.getAmmoType() == AmmoType.T_LRM &&             ( atype.getMunitionType() == AmmoType.M_THUNDER ||               atype.getMunitionType() == AmmoType.M_THUNDER_ACTIVE ||               atype.getMunitionType() == AmmoType.M_THUNDER_INFERNO ||               atype.getMunitionType() == AmmoType.M_THUNDER_VIBRABOMB||               atype.getMunitionType() == AmmoType.M_THUNDER_AUGMENTED ) &&             target.getTargetType() != Targetable.TYPE_MINEFIELD_DELIVER) {            return "Weapon can only deliver minefields";        }        if ( atype != null &&             atype.getAmmoType() == AmmoType.T_LRM &&             atype.getMunitionType() == AmmoType.M_FLARE &&             target.getTargetType() != Targetable.TYPE_FLARE_DELIVER) {            return "Weapon can only deliver flares";        }        // some weapons can only target infantry        if ( wtype.hasFlag(WeaponType.F_INFANTRY_ONLY) ) {            if (te != null && !(te instanceof Infantry) ||                target.getTargetType() != Targetable.TYPE_ENTITY) {                return "Weapon can only be used against infantry";            }        }            // make sure weapon can clear minefield        if (target instanceof MinefieldTarget &&            !AmmoType.canClearMinefield(atype)) {            return "Weapon can't clear minefields";        }                // Arty shots have to be with arty, non arty shots with non arty.        if (wtype.hasFlag(WeaponType.F_ARTILLERY)) {            //check artillery is targetted appropriately for its ammo            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;            }             else if (ttype != Targetable.TYPE_HEX_ARTILLERY && !isArtilleryFLAK) {                    return "Weapon must make artillery attacks.";            }        } else {            //weapon is not artillery            if (ttype == Targetable.TYPE_HEX_ARTILLERY) {                return "Weapon can't make artillery attacks.";            }        }                // can't target yourself, unless those are swarm missiles that        // continued to a new target        if (ae.equals(te) && !exchangeSwarmTarget) {            return "You can't target yourself";        }            // weapon operational?        if (weapon.isDestroyed() || weapon.isBreached()) {            return "Weapon not operational.";        }            // got ammo?        // don't check if it's a swarm-missile-follow-on-attack, we used the ammo previously        if ( usesAmmo && !exchangeSwarmTarget && (ammo == null || ammo.getShotsLeft() == 0 || ammo.isBreached()) ) {            return "Weapon out of ammo.";        }            // Are we dumping that ammo?        if ( usesAmmo && ammo.isDumping() ) {            ae.loadWeaponWithSameAmmo( weapon );            if ( ammo.getShotsLeft() == 0 || ammo.isDumping() ) {                return "Dumping remaining ammo.";            }        }            // is the attacker even active?        if (ae.isShutDown() || !ae.getCrew().isActive()) {            return "Attacker is in no condition to fire weapons.";        }            // sensors operational?        int sensorHits = ae.getBadCriticals(CriticalSlot.TYPE_SYSTEM, Mech.SYSTEM_SENSORS, Mech.LOC_HEAD);        if (ae instanceof Mech                && ((Mech)ae).getCockpitType() == Mech.COCKPIT_TORSO_MOUNTED) {            sensorHits += ae.getBadCriticals(CriticalSlot.TYPE_SYSTEM, Mech.SYSTEM_SENSORS, Mech.LOC_CT);            if (sensorHits > 2)                return "Attacker sensors destroyed.";        } else if (sensorHits > 1) {            return "Attacker sensors destroyed.";        }            // Is the weapon blocked by a passenger?        if ( ae.isWeaponBlockedAt(weapon.getLocation(), weapon.isRearMounted()) ) {            return "Weapon blocked by passenger.";        }            // Can't target an entity conducting a swarm attack.        if ( te != null && Entity.NONE != te.getSwarmTargetId() ) {            return "Target is swarming a Mek.";        }                //"Cool" mode for vehicle flamer requires coolant system        boolean vf_cool = false;        if(atype != null                 && wtype.hasFlag(WeaponType.F_FLAMER)                && weapon.curMode().equals("Cool")) {            vf_cool = true;            if(!ae.hasWorkingMisc(MiscType.F_COOLANT_SYSTEM,-1)) {                return "Vehicle does not have a working coolant system";            }        }        if(Targetable.TYPE_HEX_EXTINGUISH == target.getTargetType()) {            if(!wtype.hasFlag(WeaponType.F_EXTINGUISHER)                    && !vf_cool) {                return "Weapon can't put out fires";            }            IHex hexTarget = game.getBoard().getHex(target.getPosition());             if(!hexTarget.containsTerrain(Terrains.FIRE)) {                return "Target is not on fire.";            }        }        else if(wtype.hasFlag(WeaponType.F_EXTINGUISHER)) {            if(!((target instanceof Tank && ((Tank)target).isOnFire())                || (target instanceof Entity && ((Entity)target).infernos.getTurnsLeftToBurn() > 0))) {                return "Target is not on fire.";            }        }            // can't target non-wood hexes for clearing (except thin ice)        if (Targetable.TYPE_HEX_CLEAR == target.getTargetType()) {            IHex hexTarget = game.getBoard().getHex(target.getPosition());             if(!hexTarget.containsTerrain(Terrains.WOODS) &&               !hexTarget.containsTerrain(Terrains.JUNGLE) &&               !(hexTarget.containsTerrain(Terrains.ICE) && hexTarget.containsTerrain(Terrains.WATER))) {                return "Target terrain cannot be cleared.";            }            // Infantry can't clear woods.            if ( isAttackerInfantry ) {                return "Infantry can not clear terrain.";            }        }        // Infantry can't clear woods.        if ( isAttackerInfantry &&             Targetable.TYPE_HEX_CLEAR == target.getTargetType() ) {            return "Infantry can not clear woods.";        }            // Some weapons can't cause fires, but Infernos always can.        if ( (vf_cool || wtype.hasFlag(WeaponType.F_NO_FIRES) && !isInferno)            && Targetable.TYPE_HEX_IGNITE == target.getTargetType() ) {            return "Weapon can not cause fires.";        }        // only woods and buildings can be set intentionally on fire        if (target.getTargetType() == Targetable.TYPE_HEX_IGNITE                && game.getOptions().booleanOption("no_ignite_clear")                && !(game.getBoard().getHex(((HexTarget) target).getPosition()).containsTerrain(Terrains.WOODS)                    || game.getBoard().getHex(((HexTarget) target).getPosition()).containsTerrain(Terrains.JUNGLE)                    || game.getBoard().getHex(((HexTarget) target).getPosition()).containsTerrain(Terrains.FUEL_TANK)                    || game.getBoard().getHex(((HexTarget) target).getPosition()).containsTerrain(Terrains.BUILDING))) {            return "Only woods and building hexes can be set on fire intentionally.";        }            // Can't target infantry with Inferno rounds (BMRr, pg. 141).        //  Also, enforce options for keeping vehicles and protos safe        //  if those options are checked.        if ( isInferno &&             (te instanceof Infantry              || te instanceof Tank && game.getOptions().booleanOption("vehicles_safe_from_infernos")              || te instanceof Protomech && game.getOptions().booleanOption("protos_safe_from_infernos"))              ) {            return "Can not target that unit type with Inferno rounds.";        }            // The TAG system cannot target infantry.        if( isTAG && te instanceof Infantry ) {            return "Can not target infantry with TAG.";        }        // Can't raise the heat of infantry or tanks.        if ( wtype.hasFlag(WeaponType.F_FLAMER) &&             wtype.hasModes() &&             weapon.curMode().equals("Heat") &&             !(te instanceof Mech) ) {            return "Can only raise the heat level of Meks.";        }            // Handle solo attack weapons.        if ( wtype.hasFlag(WeaponType.F_SOLO_ATTACK) ) {            for ( Enumeration i = game.getActions();                  i.hasMoreElements(); ) {                Object o = i.nextElement();                if (!(o instanceof WeaponAttackAction)) {                    continue;                }                WeaponAttackAction prevAttack = (WeaponAttackAction)o;                if (prevAttack.getEntityId() == attackerId) {                        // If the attacker fires another weapon, this attack fails.                    if ( weaponId != prevAttack.getWeaponId() ) {                        return "Other weapon attacks declared.";                    }                }            }        } else if(isAttackerInfantry && !(ae instanceof BattleArmor)) {            // check for trying to fire field gun after moving            if(!wtype.hasFlag(WeaponType.F_INFANTRY) && ae.moved != IEntityMovementType.MOVE_NONE) {                    return "Can't fire field guns in same turn as moving";            }            //check for mixing infantry and field gun attacks            for ( Enumeration i = game.getActions();                  i.hasMoreElements(); ) {                Object o = i.nextElement();                if (!(o instanceof WeaponAttackAction)) {                    continue;                }                WeaponAttackAction prevAttack = (WeaponAttackAction)o;                if (prevAttack.getEntityId() == attackerId) {                    Mounted prevWeapon = ae.getEquipment(prevAttack.getWeaponId());                    if( prevWeapon.getType().hasFlag(WeaponType.F_INFANTRY) != wtype.hasFlag(WeaponType.F_INFANTRY) ) {                        return "Can't fire field guns and small arms at the same time.";                    }                }            }        }        // check if indirect fire is valid        if (isIndirect                && !game.getOptions().booleanOption("indirect_fire")) {            return "Indirect fire option not enabled";        }        if (isIndirect && game.getOptions().booleanOption("indirect_fire") &&            !game.getOptions().booleanOption("indirect_always_possible") &&            LosEffects.calculateLos(game, attackerId, target).canSee()) {            return "Indirect fire impossible with direct LOS";        }                // hull down vees can't fire front weapons        if(ae instanceof Tank && ae.isHullDown() && weapon.getLocation() == Tank.LOC_FRONT) {            return "Nearby terrain blocks front weapons.";        }            // Weapon in arc?        if (!Compute.isInArc(game, attackerId, weaponId, target)) {            return "Target not in arc.";        }                // BA Micro bombs only when flying        if (atype != null && atype.getAmmoType() == AmmoType.T_BA_MICRO_BOMB) {            if (ae.getElevation() == 0) {                return "attacker must be at least at elevation 1";            } else if (target.getTargetType() != Targetable.TYPE_HEX_BOMB) {                return "must target hex with bombs";            }        }        if (target.getTargetType() == Targetable.TYPE_HEX_BOMB &&            !(usesAmmo &&              atype.getAmmoType() == AmmoType.T_BA_MICRO_BOMB)) {            return "Weapon can't deliver bombs";        }        Entity spotter = null;        if (isIndirect) {            spotter = Compute.findSpotter(game, ae, target);            if (spotter == null) {                return "No available spotter";            }        }                int eistatus=0;        // check LOS (indirect LOS is from the spotter)        LosEffects los;        ToHitData losMods;        if (!isIndirect) {            los = LosEffects.calculateLos(game, attackerId, target);            if(ae.hasActiveEiCockpit()) {                if(los.getLightWoods() > 0)                    eistatus = 2;                else                    eistatus = 1;            }            losMods = los.losModifiers(game, eistatus);        } else {            los = LosEffects.calculateLos(game, spotter.getId(), target);            // do not count attacker partial cover in indirect fire            los.setAttackerCover(LosEffects.COVER_NONE);            if(spotter.hasActiveEiCockpit()) {                if(los.getLightWoods() > 0)                    eistatus = 2;                else                    eistatus = 1;            }            losMods = los.losModifiers(game);        }            // if LOS is blocked, block the shot        if (losMods.getValue() == ToHitData.IMPOSSIBLE && !isArtilleryIndirect) {            return losMods.getDesc();        }            // Must target infantry in buildings from the inside.        if ( targetInBuilding &&             te instanceof Infantry &&             null == los.getThruBldg() ) {            return "Attack on infantry crosses building exterior wall.";        }                // attacker partial cover means no leg weapons        if (los.isAttackerCover() && ae.locationIsLeg(weapon.getLocation()) &&                ae.getLocationStatus(weapon.getLocation()) != ILocationExposureStatus.WET) {            return "Nearby terrain blocks leg weapons.";        }        // Leg attacks, Swarm attacks, and        // Mine Launchers don't use gunnery.        if ( Infantry.LEG_ATTACK.equals( wtype.getInternalName() ) ) {            toHit = Compute.getLegAttackBaseToHit( ae, te );                // Return if the attack is impossible.            if ( ToHitData.IMPOSSIBLE == toHit.getValue() ) {                return toHit.getDesc();            }        }        else if ( Infantry.SWARM_MEK.equals( wtype.getInternalName() ) ) {            toHit = Compute.getSwarmMekBaseToHit( ae, te );                // Return if the attack is impossible.            if ( ToHitData.IMPOSSIBLE == toHit.getValue() ) {                return toHit.getDesc();            }        }        else if ( Infantry.STOP_SWARM.equals( wtype.getInternalName() ) ) {            // Can't stop if we're not swarming, otherwise automatic.            if ( Entity.NONE == ae.getSwarmTargetId() ) {                return "Not swarming a Mek.";            }        }        else if ( BattleArmor.MINE_LAUNCHER.equals(wtype.getInternalName()) ) {            // Mine launchers can not hit infantry.            if ( te instanceof Infantry ) {                return "Can not attack infantry.";            }        }        // Swarming infantry always hit their target, but        // they can only target the Mek they're swarming.        else if ( te != null && ae.getSwarmTargetId() == te.getId() ) {            // Only certain weapons can be used in a swarm attack.            if ( wtype.getDamage() == 0 ) {                return "Weapon causes no damage.";            }        }        else if ( Entity.NONE != ae.getSwarmTargetId() ) {            return "Must target the Mek being swarmed.";        }                int aElev = ae.getElevation();        int tElev = target.getElevation();        int distance = Compute.effectiveDistance(game, ae, target);        if(atype != null && atype.getAmmoType() == AmmoType.T_LRM && atype.getMunitionType() == AmmoType.M_SEMIGUIDED) {            if (te == null || te.getTaggedBy() == -1) {                // from a rules clarification                return "Semi-guided LRMs must target a unit tagged this turn";            }        }                // Handle direct artillery attacks.        if (isArtilleryDirect) {            if (distance >17) {                return "Direct artillery attack at range >17 hexes.";            }            if (isHoming) {                if (te == null || te.getTaggedBy() == -1) {                    // see BMRr p77 on board arrow IV                    return "On board homing shot must target a unit tagged this turn";                }            }        }        if (isArtilleryIndirect) {            int boardRange=(int)Math.ceil((double) (distance / 17f));            if (boardRange>wtype.getLongRange()) {                return "Indirect artillery attack out of range";            }            if (distance<=17  && !(losMods.getValue()==ToHitData.IMPOSSIBLE)) {                return "Cannot fire indirectly at range <=17 hexes unless no LOS.";            }            if (isHoming) {                if(ttype != Targetable.TYPE_HEX_ARTILLERY) {                    return "Off board homing shot must target a map sheet";                }            }        }                if(ae instanceof Mech) {            int grapple = ((Mech)ae).getGrappled();            if(grapple != Entity.NONE) {                if(grapple != target.getTargetId()) {                    return "Can only attack grappled mech";                }                int loc = weapon.getLocation();                if((loc != Mech.LOC_CT                        && loc != Mech.LOC_LT                        && loc != Mech.LOC_RT                        && loc != Mech.LOC_HEAD)                        || weapon.isRearMounted()) {                    return "Can only fire head and front torso weapons when grappled";                }            }        }        return null;    }