if (bECMAffected) { phaseReport.append(" (ECM prevents bonus)"); }
    private void resolveWeaponAttack(WeaponAttackAction waa, int lastEntityId) {        final Entity ae = game.getEntity(waa.getEntityId());        final Entity te = game.getEntity(waa.getTargetId());        final Mounted weapon = ae.getEquipment(waa.getWeaponId());        final WeaponType wtype = (WeaponType)weapon.getType();                // 2002-09-16 Infantry weapons have unlimited ammo.        final boolean isWeaponInfantry = wtype.hasFlag(WeaponType.F_INFANTRY);        final boolean usesAmmo = wtype.getAmmoType() != AmmoType.T_NA &&            !isWeaponInfantry;        Mounted ammo = null;        Infantry platoon = null;        if (usesAmmo) {            if (waa.getAmmoId() > -1) {                ammo = ae.getEquipment(waa.getAmmoId());                weapon.setLinked(ammo);            }            else {                ammo = weapon.getLinked();            }        }        final AmmoType atype = ammo == null ? null : (AmmoType)ammo.getType();                        if (lastEntityId != waa.getEntityId()) {            phaseReport.append("\nWeapons fire for " + ae.getDisplayName() + "\n");        }                phaseReport.append("    " + wtype.getName() + " at " + te.getDisplayName());                // has this weapon fired already?        if (weapon.isUsedThisRound()) {            phaseReport.append(" but the weapon has already fired this round!\n");            return;        }                // is the weapon functional?        if (weapon.isDestroyed()) {            phaseReport.append(" but the weapon has been destroyed in a previous round!\n");            return;        }                // is it jammed?        if (weapon.isJammed()) {            phaseReport.append(" but the weapon is jammed!\n");            return;        }                // try reloading        if (usesAmmo && (ammo == null || ammo.getShotsLeft() == 0 || ammo.isDumping())) {            ae.loadWeapon(weapon);            ammo = weapon.getLinked();        }        //        // should we even bother?//        if (te.isDestroyed() || te.isDoomed() || te.crew.isDead()) {//            phaseReport.append(" but the target is already destroyed!\n");//            return;//        }                // compute to-hit        ToHitData toHit = Compute.toHitWeapon(game, waa, attacks);        if (toHit.getValue() == ToHitData.IMPOSSIBLE) {            phaseReport.append(", but the shot is impossible (" + toHit.getDesc() + ")\n");            return;        } else if (toHit.getValue() == ToHitData.AUTOMATIC_FAIL) {            phaseReport.append(", the shot is an automatic miss (" + toHit.getDesc() + "), ");        } else {            phaseReport.append("; needs " + toHit.getValue() + ", ");        }                        int nShots = 1;        // figure out # of shots for variable-shot weapons        if (wtype.getAmmoType() == AmmoType.T_AC_ULTRA && weapon.curMode().equals("Ultra")) {            nShots = 2;        } else if (wtype.getAmmoType() == AmmoType.T_AC_ROTARY) {            if (weapon.curMode().equals("2-shot")) {                nShots = 2;            } else if (weapon.curMode().equals("4-shot")) {                nShots = 4;            } else if (weapon.curMode().equals("6-shot")) {                nShots = 6;            }        }                // make sure there's enough ammo to support that many shots        if (usesAmmo && nShots > 1) {            int nAvail = ae.getTotalAmmoOfType(atype);            if (nAvail < nShots) {                phaseReport.append("(Not enough ammo for " + weapon.curMode() + ". ");                phaseReport.append("Firing at single rate)");                nShots = 1;            }        }                // use up ammo (if weapon is out of ammo, we shouldn't be here)        if (usesAmmo) {            for (int x = 0; x < nShots; x++) {                if (ammo.getShotsLeft() <= 0) {                    ae.loadWeapon(weapon);                    ammo = weapon.getLinked();                }                ammo.setShotsLeft(ammo.getShotsLeft() - 1);            }        }                // build up some heat        ae.heatBuildup += (wtype.getHeat() * nShots);                // set the weapon as having fired        weapon.setUsedThisRound(true);                // if firing an HGR unbraced, schedule a PSR        if (wtype.getAmmoType() == AmmoType.T_GAUSS_HEAVY && ae.mpUsed > 0) {            // the mod is weight-based            int nMod;            if (ae.getWeight() <= Mech.WEIGHT_LIGHT) {                nMod = 2;            } else if (ae.getWeight() <= Mech.WEIGHT_MEDIUM) {                nMod = 1;            } else if (ae.getWeight() <= Mech.WEIGHT_HEAVY) {                nMod = 0;            } else {                nMod = -1;            }                        pilotRolls.addElement(new PilotingRollData(ae.getId(), nMod, "fired HeavyGauss unbraced"));        }                // roll        int roll = Compute.d6(2);        phaseReport.append("rolls " + roll + " : ");                // check for AC jams        if (nShots > 1) {            int jamCheck = 0;            if (wtype.getAmmoType() == AmmoType.T_AC_ULTRA && weapon.curMode().equals("Ultra")) {                jamCheck = 2;            } else if (wtype.getAmmoType() == AmmoType.T_AC_ROTARY) {                if (nShots == 2) {                    jamCheck = 2;                }                else if (nShots == 4) {                    jamCheck = 3;                }                else if (nShots == 6) {                    jamCheck = 4;                }            }                    if (jamCheck > 0 && roll <= jamCheck) {                phaseReport.append("misses AND THE AUTOCANNON JAMS.\n");                weapon.setJammed(true);                // ultras are destroyed by jamming                if (wtype.getAmmoType() == AmmoType.T_AC_ULTRA) {                    weapon.setHit(true);                }                return;            }        }                // do we hit?        if (roll < toHit.getValue()) {            // miss            phaseReport.append("misses.\n");            if (wtype.getAmmoType() == AmmoType.T_SRM_STREAK) {                // this is sort of a hack...                ae.heatBuildup -= wtype.getHeat();                ammo.setShotsLeft(ammo.getShotsLeft() + 1);                phaseReport.append("    Streak fails to achieve lock on target.\n");                return;            }            // if the target is in the woods and the weapon misses, and the weapon is a large weapon, set the woods on fire.            Hex targetHex = game.getBoard().getHex(te.getPosition());            if (targetHex.contains(Terrain.WOODS) && !(targetHex.contains(Terrain.FIRE))            && toHit.getValue() != TargetRoll.AUTOMATIC_FAIL) {                // 11 or 12 is the same odds as 2 or 3                if (wtype.getFireTN() != TargetRoll.IMPOSSIBLE && burn(targetHex, 11) == true)                {                    sendChangedHex(te.getPosition());                    phaseReport.append("           Missed shot sets the woods on fire! \n");                }            }// end if target has woods and no fire            return;        }                // special case NARC hits.  No damage, but a beacon is appended        if (wtype.getAmmoType() == AmmoType.T_NARC) {            te.setNarcedBy(ae.getOwner().getTeam());            phaseReport.append("hits.  Pod attached.\n");            return;        }                // yeech.  handle damage. . different weapons do this in very different ways        int hits = 1, amsHits = 0, nCluster = 1, nSalvoBonus = 0;        int nDamPerHit = wtype.getDamage();        boolean bSalvo = false;        String sSalvoType = " shot(s) ";        if (isWeaponInfantry) {            // Infantry damage depends on # men left in platoon.            bSalvo = true;            platoon = (Infantry)ae;            nCluster = 5;            nDamPerHit = 1;            hits = platoon.getDamage(platoon.getShootingStrength());        } else if (wtype.getDamage() == WeaponType.DAMAGE_MISSILE) {            sSalvoType = " missile(s) ";            bSalvo = true;            nDamPerHit = atype.getDamagePerShot();                        if (wtype.getAmmoType() == AmmoType.T_LRM || wtype.getAmmoType() == AmmoType.T_MRM) {                nCluster = 5;            }                        // calculate # of missiles hitting            if (wtype.getAmmoType() == AmmoType.T_LRM || wtype.getAmmoType() == AmmoType.T_SRM) {                // check for narc                if (te.isNarcedBy(ae.getOwner().getTeam())) {                    nSalvoBonus += 2;                }                                // check for artemis                Mounted mLinker = weapon.getLinkedBy();                if (mLinker != null && mLinker.getType() instanceof MiscType &&                         !mLinker.isDestroyed() && !mLinker.isMissing() &&                        mLinker.getType().hasFlag(MiscType.F_ARTEMIS)) {                    nSalvoBonus += 2;                }                             }                        if (wtype.getAmmoType() == AmmoType.T_SRM_STREAK) {                hits = wtype.getRackSize();            } else if (wtype.getRackSize() == 30 || wtype.getRackSize() == 40) {                // I'm going to assume these are MRMs                hits = Compute.missilesHit(wtype.getRackSize() / 2) + Compute.missilesHit(wtype.getRackSize() / 2);            } else {                hits = Compute.missilesHit(wtype.getRackSize(), nSalvoBonus);            }                        // adjust for AMS            Vector vCounters = waa.getCounterEquipment();            if (vCounters != null) {                for (int x = 0; x < vCounters.size(); x++) {                    Mounted counter = (Mounted)vCounters.elementAt(x);                    if (counter.getType() instanceof WeaponType &&                             ((WeaponType)counter.getType()).getAmmoType() == AmmoType.T_AMS) {                        if (!counter.isReady() || counter.isMissing()) {                            continue;                        }                        amsHits = Compute.d6(((WeaponType)counter.getType()).getDamage());                                                if (amsHits > hits) amsHits = hits;                                                // build up some heat                        ae.heatBuildup += ((WeaponType)counter.getType()).getHeat();                            // decrement the ammo                        Mounted mAmmo = counter.getLinked();                        mAmmo.setShotsLeft(Math.max(0, mAmmo.getShotsLeft() - amsHits));                                                // set the ams as having fired                        weapon.setUsedThisRound(true);                    }                }            }                    } else if (atype != null && atype.hasFlag(AmmoType.F_CLUSTER)) {            // Cluster shots break into single point clusters.            bSalvo = true;            hits = Compute.missilesHit(wtype.getRackSize());            nDamPerHit = 1;        } else if (nShots > 1) {            // this should handle multiple attacks from ultra and rotary ACs            bSalvo = true;            hits = Compute.missilesHit(nShots);        } else if (atype != null && atype.hasFlag(AmmoType.F_MG) && !isWeaponInfantry && (te instanceof Infantry)) {            // Mech and Vehicle MGs do *DICE* of damage to PBI.            // 2002-10-24 Suvarov454 : no need for so many lines in the report.            nDamPerHit = Compute.d6(wtype.getDamage());            phaseReport.append( "riddles the target with " +                 nDamPerHit + sSalvoType + "and " );        }        else if (wtype.getAmmoType() == AmmoType.T_GAUSS_HEAVY) {            // HGR does range-dependent damage            int nRange = ae.getPosition().distance(te.getPosition());            if (nRange <= wtype.getShortRange()) {                nDamPerHit = 25;            } else if (nRange <= wtype.getMediumRange()) {                nDamPerHit = 20;            } else {                nDamPerHit = 10;            }        }        // Report the number of hits.        if (bSalvo) {            phaseReport.append(hits + sSalvoType + "hit" + toHit.getTableDesc());            if (nSalvoBonus > 0) {                phaseReport.append(" (w/ +").append(nSalvoBonus).append(" bonus)");            }            phaseReport.append(".");                        if (amsHits > 0) {                phaseReport.append("\n\tAMS shoots down " + amsHits + " missile(s).");                hits -= amsHits;            }        }        // for each cluster of hits, do a chunk of damage        while (hits > 0) {            int nDamage;            // if we're targeting a hex, then             if(te instanceof HexEntity) {              nDamage = nDamPerHit * hits;              phaseReport.append("hits!\n");              phaseReport.append("    Terrain takes " + nDamage + " damage.\n");              Coords c = te.getPosition();              Hex h = game.getBoard().getHex(c);              if(!h.contains(Terrain.WOODS)) return; // only woods can be lit on fire or cleared at the moment              int TN = 14 - nDamage;              if(!wtype.hasFlag(WeaponType.F_FLAMER)) {                int Woods = h.levelOf(Terrain.WOODS);                if(Woods < 1) return;                roll = Compute.d6(2);                phaseReport.append("    Checking to clear woods; needs " + TN + ", rolls " + roll + ": ");                if(roll >= TN) {                  if(Woods > 1) {                     h.removeTerrain(Terrain.WOODS);                     h.addTerrain(new Terrain(Terrain.WOODS, Woods - 1));                     phaseReport.append(" Heavy Woods converted to Light Woods!\n");                  }                  else if(Woods == 1) {                     h.removeTerrain(Terrain.WOODS);                     h.addTerrain(new Terrain(Terrain.ROUGH, 1));                     phaseReport.append(" Light Woods converted to Rough!\n");                  }                  sendChangedHex(c);                } else {                  phaseReport.append(" fails!\n");                }              }              TN = wtype.getFireTN();              phaseReport.append("    Checking to start fire in hex; needs " + TN + ", ");              roll = Compute.d6(2);              phaseReport.append("rolls " + roll + " : ");              if(roll >= TN) {                 phaseReport.append(" Fire started!\n");                 h.addTerrain(new Terrain(Terrain.FIRE, 1));                 sendChangedHex(c);              }              else {                 phaseReport.append(" No fire.\n");              }                           return;            }            // Flamers do heat, not damage.            if (wtype.hasFlag(WeaponType.F_FLAMER) && game.getOptions().booleanOption("flamer_heat")) {                nDamage = nDamPerHit * hits;                phaseReport.append("\n        Target gains ").append(nDamage).append(" more heat during heat phase.");                te.heatBuildup += nDamage;                hits = 0;            }            else {                HitData hit = te.rollHitLocation(toHit.getHitTable(), toHit.getSideTable());                nDamage = nDamPerHit * Math.min(nCluster, hits);                if (!bSalvo) {                    phaseReport.append("hits" + toHit.getTableDesc() + " " + te.getLocationAbbr(hit));                }                phaseReport.append(damageEntity(te, hit, nDamage));                hits -= nCluster;            }        } // Handle the next cluster.        phaseReport.append("\n");    }