/*
 * MegaMek -
 * Copyright (C) 2000,2001,2002,2003,2004,2005 Ben Mazur (bmazur@sev.org)
 *
 *  This program is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU General Public License as published by the Free
 *  Software Foundation; either version 2 of the License, or (at your option)
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful, but
 *  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 *  or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
 *  for more details.
 */

package megamek.common;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import megamek.common.actions.AbstractAttackAction;
import megamek.common.actions.ChargeAttackAction;
import megamek.common.actions.DfaAttackAction;
import megamek.common.actions.DisplacementAttackAction;
import megamek.common.actions.EntityAction;
import megamek.common.actions.PushAttackAction;
import megamek.common.actions.WeaponAttackAction;
import megamek.common.event.GameEntityChangeEvent;
import megamek.common.preference.PreferenceManager;
import megamek.common.util.StringUtil;
import megamek.common.weapons.ACWeapon;
import megamek.common.weapons.BayWeapon;
import megamek.common.weapons.CapitalLaserBayWeapon;
import megamek.common.weapons.GaussWeapon;
import megamek.common.weapons.ISBombastLaser;
import megamek.common.weapons.WeaponHandler;

/**
 * Entity is a master class for basically anything on the board except terrain.
 */
public abstract class Entity extends TurnOrdered implements Transporter, Targetable, RoundUpdated {
    /**
     *
     */
    private static final long serialVersionUID = 1430806396279853295L;

    public static final int NONE = -1;

    public static final int LOC_NONE = -1;
    public static final int LOC_DESTROYED = -2;

    public static final int MAX_C3_NODES = 12;

    public static final int GRAPPLE_BOTH = 0;
    public static final int GRAPPLE_RIGHT = 1;
    public static final int GRAPPLE_LEFT = 2;

    protected transient IGame game;

    protected int id = Entity.NONE;

    /** ID settable by external sources (such as mm.net) */
    protected int externalId = Entity.NONE;

    protected float weight;
    protected boolean omni = false;
    protected String chassis;
    protected String model;
    protected String fluff = null;
    protected int year;
    protected int techLevel;
    protected Engine engine;
    protected boolean mixedTech = false;
    protected boolean designValid = true;

    protected String displayName = null;
    protected String shortName = null;
    public int duplicateMarker = 1;

    protected transient Player owner;
    protected int ownerId;

    /**
     * The pilot of the entity. Even infantry has a 'pilot'.
     */
    public Pilot crew = new Pilot();

    protected boolean shutDown = false;
    protected boolean shutDownThisPhase = false;
    protected boolean doomed = false;
    protected boolean destroyed = false;

    protected Coords position = null;

    protected int facing = 0;
    protected int sec_facing = 0;

    protected int walkMP = 0;
    protected int jumpMP = 0;

    protected int targSys = MiscType.T_TARGSYS_STANDARD;

    protected boolean done = false;

    protected boolean prone = false;
    protected boolean hullDown = false;
    protected boolean findingClub = false;
    protected boolean armsFlipped = false;
    protected boolean unjammingRAC = false;
    protected boolean hasSpotlight = false;
    protected boolean illuminated = false;
    protected boolean spotlightIsActive = false;
    protected boolean usedSearchlight = false;
    protected boolean stuckInSwamp = false;
    protected boolean canUnstickByJumping = false;
    protected int taggedBy = -1;
    protected boolean layingMines = false;
    protected boolean _isEMId = false;

    protected DisplacementAttackAction displacementAttack = null;

    public int heat = 0;
    public int heatBuildup = 0;
    public int heatFromExternal = 0;
    public int delta_distance = 0;
    public int mpUsed = 0;
    public int moved = IEntityMovementType.MOVE_NONE;
    protected int mpUsedLastRound = 0;
    public boolean gotPavementBonus = false;
    public boolean hitThisRoundByAntiTSM = false;
    public boolean inReverse = false;

    private int[] exposure;
    private int[] armor;
    private int[] internal;
    private int[] orig_armor;
    private int[] orig_internal;
    public int damageThisPhase;
    public int damageThisRound;
    public int engineHitsThisRound;
    public boolean rolledForEngineExplosion = false; // So that we don't roll
    // twice in one round
    public boolean dodging;
    public boolean reckless;
    private boolean evading = false;

    public boolean spotting;
    private boolean clearingMinefield = false;
    protected int killerId = Entity.NONE;
    private int offBoardDistance = 0;
    private int offBoardDirection = IOffBoardDirections.NONE;
    private int retreatedDirection = IOffBoardDirections.NONE;

    protected int[] vectors = { 0, 0, 0, 0, 0, 0 };
    private int recoveryTurn = 0;
    // need to keep a list of areas that this entity has passed through on the
    // current turn
    private Vector<Coords> passedThrough = new Vector<Coords>();
    private boolean ramming;
    // to determine what arcs have fired for large craft
    private boolean[] frontArcFired;
    private boolean[] rearArcFired;

    /**
     * The object that tracks this unit's Inferno round hits.
     */
    public InfernoTracker infernos = new InfernoTracker();
    public ArtilleryTracker aTracker = new ArtilleryTracker();
    public TeleMissileTracker tmTracker = new TeleMissileTracker();

    protected String c3NetIdString = null;
    protected int c3Master = NONE;
    protected int c3CompanyMasterIndex = LOC_DESTROYED;

    protected int armorType = EquipmentType.T_ARMOR_UNKNOWN;
    protected int armorTechLevel = TechConstants.T_TECH_UNKNOWN;
    protected int structureType = EquipmentType.T_STRUCTURE_UNKNOWN;

    protected String source = "";

    /**
     * A list of all mounted equipment. (Weapons, ammo, and misc)
     */
    protected ArrayList<Mounted> equipmentList = new ArrayList<Mounted>();

    /**
     * A list of all mounted weapons. This only includes regular weapons, not
     * bay mounts or grouped weapon mounts.
     */
    protected ArrayList<Mounted> weaponList = new ArrayList<Mounted>();

    /**
     * A list of all mounted weapon bays
     */
    protected ArrayList<Mounted> weaponBayList = new ArrayList<Mounted>();

    /**
     * A list of all mounted weapon groups
     */
    protected ArrayList<Mounted> weaponGroupList = new ArrayList<Mounted>();

    /**
     * A list of every weapon mount, including bay mounts and weapon group
     * mounts
     */
    protected ArrayList<Mounted> totalWeaponList = new ArrayList<Mounted>();

    /**
     * A list of all mounted ammo.
     */
    protected ArrayList<Mounted> ammoList = new ArrayList<Mounted>();

    /**
     * A list of all mounted bombs.
     */
    protected ArrayList<Mounted> bombList = new ArrayList<Mounted>();

    /**
     * A list of all remaining equipment.
     */
    protected ArrayList<Mounted> miscList = new ArrayList<Mounted>();

    protected ArrayList<INarcPod> pendingINarcPods = new ArrayList<INarcPod>();
    protected ArrayList<INarcPod> iNarcPods = new ArrayList<INarcPod>();
    protected ArrayList<NarcPod> pendingNarcPods = new ArrayList<NarcPod>();
    protected ArrayList<NarcPod> narcPods = new ArrayList<NarcPod>();

    protected ArrayList<String> failedEquipmentList = new ArrayList<String>();

    // which teams have NARCd us? a long allows for 64 teams.
    protected long m_lNarcedBy = 0;
    protected long m_lPendingNarc = 0;

    /**
     * This matrix stores critical slots in the format [location][slot #]. What
     * locations entities have and how many slots there are in each is
     * determined by the subclasses of Entity such as Mech.
     */
    protected CriticalSlot[][] crits; // [loc][slot]

    /**
     * Stores the current movement mode.
     */
    protected int movementMode = IEntityMovementMode.NONE;

    protected boolean isHidden = false;

    protected boolean carcass = false;

    /**
     * The components of this entity that can transport other entities.
     */
    private Vector<Transporter> transports = new Vector<Transporter>();

    /**
     * The ids of the MechWarriors this entity has picked up
     */
    private Vector<Integer> pickedUpMechWarriors = new Vector<Integer>();

    /**
     * The ID of the <code>Entity</code> that has loaded this unit.
     */
    private int conveyance = Entity.NONE;

    /**
     * Set to <code>true</code> if this unit was unloaded this turn.
     */
    private boolean unloadedThisTurn = false;

    /**
     * The id of the <code>Entity</code> that is the current target of a swarm
     * attack by this unit.
     */
    private int swarmTargetId = Entity.NONE;

    /**
     * The id of the <code>Entity</code> that is attacking this unit with a
     * swarm attack.
     */
    private int swarmAttackerId = Entity.NONE;

    /**
     * Flag that indicates that the unit can still be salvaged (given enough
     * time and parts).
     */
    private boolean salvageable = true;

    /**
     * The removal condition is set when the entitiy is removed from the game.
     */
    private int removalCondition = IEntityRemovalConditions.REMOVE_UNKNOWN;

    /**
     * The round this unit will be deployed
     */
    private int deployRound = 0;

    /**
     * Marks an entity as having been deployed
     */
    private boolean deployed = false;

    /**
     * The unit number of this entity. All entities which are members of the
     * same low-level unit are expected to share the same unit number. Future
     * implementations may store multiple unit designations in the same unit
     * number (e.g. battalion, company, platoon, and lance).
     */
    private char unitNumber = (char) Entity.NONE;

    /**
     * Indicates whether this entity has been seen by the enemy during the
     * course of this game. Used in double-blind.
     */
    private boolean seenByEnemy = false;

    /**
     * Indicates whether this entity can currently be seen by the enemy. Used in
     * double-blind.
     */
    private boolean visibleToEnemy = false;

    /** Whether this entity is captured or not. */
    private boolean captured = false;

    /**
     * this is the elevation of the Entity--with respect to the surface of the
     * hex it's in. In other words, this may need to *change* as it moves from
     * hex to hex--without it going up or down. I.e.--level 0 hex, elevation
     * 5--it moves to a level 2 hex, without going up or down. elevation is now
     * 3.
     */
    protected int elevation = 0;

    /**
     * 2 vectors holding entity and weapon ids. to see who hit us this round
     * with a swarm volley from what launcher. This vector holds the Entity ids.
     *
     * @see megamek.common.Entity#hitBySwarmsWeapon
     */
    private Vector<Integer> hitBySwarmsEntity = new Vector<Integer>();

    /**
     * A vector that stores from which launcher we where hit by a swarm weapon
     * this round. This vector holds the weapon ID's.
     *
     * @see megamek.common.Entity#hitBySwarmsEntity
     */
    private Vector<Integer> hitBySwarmsWeapon = new Vector<Integer>();

    /**
     * True if and only if this is a canon (published) unit.
     */
    private boolean canon;

    private int assaultDropInProgress = 0;
    private boolean climbMode = false; // save climb mode from turn to turn for
    // convenience

    protected int lastTarget = Entity.NONE;

    /**
     * the entity id of our current spot-target
     */
    private int spotTargetId = Entity.NONE;

    private boolean isCommander = false;

    protected boolean isCarefulStanding = false;

    /**
     * a vector of currently active sensors that might be able to check range
     */
    private Vector<Sensor> sensors = new Vector<Sensor>();
    // the currently selected sensor
    private Sensor activeSensor;
    // the sensor chosen for next turn
    private Sensor nextSensor;
    // roll for sensor check
    private int sensorCheck;

    // the roll for ghost targets
    private int ghostTargetRoll;
    // the roll to override ghost targets
    private int ghostTargetOverride;

    // Tac Ops HeatSink Coolant Failure number
    protected int heatSinkCoolantFailureFactor;

    // for how many rounds should this unit stay shutdown due to tasering
    protected int taserShutdownRounds = 0;

    // is this unit shutdown by a BA taser?
    protected boolean shutdownByBATaser = false;

    // for how many more rounds does this unit suffer from taser feedback?
    protected int taserFeedBackRounds = 0;

    protected int taserInterference = 0;
    protected int taserInterferenceRounds = 0;

    // contains a HTML string describing BV calculation
    protected StringBuffer bvText = null;

    /**
     * Generates a new, blank, entity.
     */
    public Entity() {
        armor = new int[locations()];
        internal = new int[locations()];
        orig_armor = new int[locations()];
        orig_internal = new int[locations()];
        crits = new CriticalSlot[locations()][];
        exposure = new int[locations()];
        for (int i = 0; i < locations(); i++) {
            crits[i] = new CriticalSlot[getNumberOfCriticals(i)];
        }
        setC3NetId(this);
    }

    /**
     * Restores the entity after serialization
     */
    public void restore() {
        // restore all mounted equipments
        for (Mounted mounted : equipmentList) {
            mounted.restore();
        }
        // set game options, we derive some equipment's modes from this
        setGameOptions();
    }

    /**
     * Returns the ID number of this Entity.
     *
     * @return ID Number.
     */
    public int getId() {
        return id;
    }

    /**
     * Sets the ID number of this Entity, which will also set the display name
     * and short name to null.
     *
     * @param id
     *            the new ID.
     */
    public void setId(int id) {
        this.id = id;
        displayName = null;
        shortName = null;
    }

    /**
     * this returns the external ID.
     *
     * @return the ID settable by external sources (such as mm.net)
     * @see megamek.common.Entity#externalId
     */
    public int getExternalId() {
        return externalId;
    }

    /**
     * This sets the external ID.
     *
     * @param externalId
     *            the new external ID for this Entity.
     * @see megamek.common.Entity#externalId
     */
    public void setExternalId(int externalId) {
        this.externalId = externalId;
    }

    /**
     * This returns the game this Entity belongs to.
     *
     * @return the game.
     */
    public IGame getGame() {
        return game;
    }

    /**
     * This sets the game the entity belongs to. It also restores the entity and
     * checks that the game is in a consistent state. This function takes care
     * of the units transported by this entity.
     *
     * @param game
     *            the game.
     */
    public void setGame(IGame game) {
        this.game = game;
        restore();
        // Make sure the owner is set.
        if (null == owner) {
            if (Entity.NONE == ownerId) {
                throw new IllegalStateException("Entity doesn't know its owner's ID.");
            }
            Player player = game.getPlayer(ownerId);
            if (null == player) {
                System.err.println("Entity can't find player #" + ownerId);
            } else {
                setOwner(player);
            }
        }

        // Also set game for each entity "loaded" in this entity.
        Enumeration<Entity> iter = getLoadedUnits().elements();
        while (iter.hasMoreElements()) {
            iter.nextElement().setGame(game);
        }
    }

    /**
     * Returns the unit code for this entity.
     */
    public String getModel() {
        return model;
    }

    /**
     * Sets the unit code for this Entity.
     *
     * @param model
     *            The unit code.
     */
    public void setModel(String model) {
        this.model = model;
    }

    /**
     * Returns the chassis name for this entity.
     */
    public String getChassis() {
        return chassis;
    }

    /**
     * sets the chassis name for this entity.
     *
     * @param chassis
     *            The chassis name.
     */
    public void setChassis(String chassis) {
        this.chassis = chassis;
    }

    /**
     * Returns the fluff for this entity.
     */
    public String getFluff() {
        return fluff;
    }

    /**
     * sets the fluff text for this entity.
     *
     * @param fluff
     *            The fluff text.
     */
    public void setFluff(String fluff) {
        this.fluff = fluff;
    }

    /**
     * Returns the unit tech for this entity.
     */
    public int getTechLevel() {
        return techLevel;
    }

    /**
     * Sets the tech level for this Entity.
     *
     * @param techLevel
     *            The tech level, it must be one of the
     *            {@link megamek.common.TechConstants TechConstants }.
     */
    public void setTechLevel(int techLevel) {
        this.techLevel = techLevel;
    }

    public int getRecoveryTurn() {
        return recoveryTurn;
    }

    public void setRecoveryTurn(int r) {
        recoveryTurn = r;
    }

    /**
     * Checks if this is a clan unit. It is determined by tech level.
     *
     * @return true if this unit is a clan unit.
     * @see megamek.common.Entity#setTechLevel(int)
     */
    public boolean isClan() {
        return ((techLevel == TechConstants.T_CLAN_TW) || (techLevel == TechConstants.T_CLAN_ADVANCED) || (techLevel == TechConstants.T_CLAN_EXPERIMENTAL) || (techLevel == TechConstants.T_CLAN_UNOFFICIAL));
    }

    public boolean isClanArmor() {
        if (getArmorTechLevel() == TechConstants.T_TECH_UNKNOWN) {
            return isClan();
        }
        return ((getArmorTechLevel() == TechConstants.T_CLAN_TW) || (getArmorTechLevel() == TechConstants.T_CLAN_ADVANCED) || (getArmorTechLevel() == TechConstants.T_CLAN_EXPERIMENTAL) || (getArmorTechLevel() == TechConstants.T_CLAN_UNOFFICIAL));
    }

    public boolean isMixedTech() {
        return mixedTech;
    }

    public void setMixedTech(boolean mixedTech) {
        this.mixedTech = mixedTech;
    }

    public boolean isDesignValid() {
        return designValid;
    }

    public void setDesignValid(boolean designValid) {
        this.designValid = designValid;
    }

    public int getYear() {
        return year;
    }

    public void setYear(int year) {
        this.year = year;
    }

    public float getWeight() {
        return weight;
    }

    public int getWeightClass() {
        return EntityWeightClass.getWeightClass((int) getWeight());
    }

    public String getWeightClassName() {
        return EntityWeightClass.getClassName(getWeightClass());
    }

    public void setWeight(float weight) {
        this.weight = weight;
    }

    public boolean isOmni() {
        return omni;
    }

    public void setOmni(boolean omni) {
        this.omni = omni;
    }

    /**
     * Returns the number of locations in the entity
     */
    public abstract int locations();

    /**
     * Returns the player that "owns" this entity.
     */
    public Player getOwner() {
        return owner;
    }

    public void setOwner(Player player) {
        owner = player;
        ownerId = player.getId();

        generateDisplayName();
    }

    public int getOwnerId() {
        return ownerId;
    }

    /**
     * Returns true if the other entity is an enemy of this entity. This is more
     * reliable than Player.isEnemyOf since it knows that an entity will never
     * be an enemy of itself.
     */
    public boolean isEnemyOf(Entity other) {
        if (null == owner) {
            return ((id != other.getId()) && (ownerId != other.ownerId));
        }
        return (id != other.getId()) && owner.isEnemyOf(other.getOwner());
    }

    public Pilot getCrew() {
        return crew;
    }

    public void setCrew(Pilot crew) {
        this.crew = crew;
    }

    public boolean isShutDown() {
        return shutDown;
    }

    public void setShutDown(boolean shutDown) {
        this.shutDown = shutDown;
        setShutDownThisPhase(shutDown);
    }

    public void setShutDownThisPhase(boolean shutDown) {
        shutDownThisPhase = shutDown;
    }

    public boolean isShutDownThisPhase() {
        return shutDownThisPhase;
    }

    public boolean isDoomed() {
        return doomed;
    }

    public void setDoomed(boolean doomed) {
        // Doomed entities aren't in retreat.
        if (doomed) {
            setRemovalCondition(IEntityRemovalConditions.REMOVE_SALVAGEABLE);
        }
        this.doomed = doomed;
    }

    public boolean isDestroyed() {
        return destroyed;
    }

    public void setDestroyed(boolean destroyed) {
        this.destroyed = destroyed;
    }

    // Targetable interface
    public int getTargetType() {
        return Targetable.TYPE_ENTITY;
    }

    public int getTargetId() {
        return getId();
    }

    public int getHeight() {
        return height();
    }

    // End Targetable interface

    public boolean isDone() {
        return done;
    }

    public void setDone(boolean done) {
        this.done = done;
    }

    /**
     * This method should <strong>only</stong> be called when needed to remove a
     * dead swarmer's game turn.
     */
    public void setUnloaded(boolean unloaded) {
        unloadedThisTurn = unloaded;
    }

    /**
     * Determine if this entity participate in the current game phase.
     *
     * @return <code>true</code> if this entity is not shut down, is not
     *         destroyed, has an active crew, and was not unloaded from a
     *         transport this turn. <code>false</code> otherwise.
     */
    public boolean isActive() {
        return this.isActive(-1);
    }

    public boolean isActive(int turn) {
        boolean isActive = !shutDown && !destroyed && getCrew().isActive() && !unloadedThisTurn;

        if ((turn > -1) && isActive) {
            isActive = !deployed && shouldDeploy(turn);
        } else {
            isActive = isActive && deployed;
        }

        return isActive;
    }

    /**
     * Returns true if this entity is selectable for action. Transported
     * entities can not be selected.
     */
    public boolean isSelectableThisTurn() {
        return !done && (conveyance == Entity.NONE) && !unloadedThisTurn && !isClearingMinefield() && !isCarcass();
    }

    /**
     * Returns true if this entity could potentially be loaded (did not move
     * from starting hex)
     */
    public boolean isLoadableThisTurn() {
        return (delta_distance == 0) && (conveyance == Entity.NONE) && !unloadedThisTurn && !isClearingMinefield();
    }

    /**
     * Determine if this <code>Entity</code> was unloaded previously this turn.
     *
     * @return <code>true</code> if this entity was unloaded for any reason
     *         during this turn.
     */
    public boolean isUnloadedThisTurn() {
        return unloadedThisTurn;
    }

    /**
     * Returns true if this entity is targetable for attacks
     */
    public boolean isTargetable() {
        return !destroyed && !doomed && deployed && !isOffBoard();
    }

    public boolean isProne() {
        return prone;
    }

    public void setProne(boolean prone) {
        this.prone = prone;
        if (prone) {
            hullDown = false;
        }
    }

    public boolean isHullDown() {
        return hullDown;
    }

    public void setHullDown(boolean down) {
        hullDown = down;
        if (hullDown) {
            prone = false;
        }
    }

    /**
     * Is this entity shut down or is the crew unconscious?
     */
    public boolean isImmobile() {
        return isShutDown() || crew.isUnconscious();
    }

    public boolean isCharging() {
        return displacementAttack instanceof ChargeAttackAction;
    }

    public boolean isPushing() {
        return displacementAttack instanceof PushAttackAction;
    }

    public boolean isMakingDfa() {
        return displacementAttack instanceof DfaAttackAction;
    }

    public boolean hasDisplacementAttack() {
        return displacementAttack != null;
    }

    public DisplacementAttackAction getDisplacementAttack() {
        return displacementAttack;
    }

    public void setDisplacementAttack(DisplacementAttackAction displacementAttack) {
        this.displacementAttack = displacementAttack;
    }

    /**
     * Returns true if any other entities this entity knows of are making a
     * displacement attack on this entity.
     */
    public boolean isTargetOfDisplacementAttack() {
        return findTargetedDisplacement() != null;
    }

    /**
     * Returns any known displacement attacks (should only be one) that this
     * entity is a target of.
     */
    public DisplacementAttackAction findTargetedDisplacement() {
        for (Enumeration<Entity> i = game.getEntities(); i.hasMoreElements();) {
            Entity other = i.nextElement();
            if (other.hasDisplacementAttack() && (other.getDisplacementAttack().getTargetId() == id)) {
                return other.getDisplacementAttack();
            }
        }
        return null;
    }

    public boolean isUnjammingRAC() {
        return unjammingRAC;
    }

    public void setUnjammingRAC(boolean u) {
        unjammingRAC = u;
    }

    public boolean isFindingClub() {
        return findingClub;
    }

    public void setFindingClub(boolean findingClub) {
        this.findingClub = findingClub;
    }

    /**
     * Set whether or not the mech's arms are flipped to the rear
     */
    public void setArmsFlipped(boolean armsFlipped) {
        this.armsFlipped = armsFlipped;
        game.processGameEvent(new GameEntityChangeEvent(this, this));
    }

    /**
     * Returns true if the mech's arms are flipped to the rear
     */
    public boolean getArmsFlipped() {
        return armsFlipped;
    }

    /**
     * Returns the current position of this entity on the board. This is not
     * named getLocation(), since I want the word location to refer to hit
     * locations on a mech or vehicle.
     */
    public Coords getPosition() {
        return position;
    }

    /**
     * Sets the current position of this entity on the board.
     *
     * @param position
     *            the new position.
     */
    public void setPosition(Coords position) {
        this.position = position;
    }

    /**
     *
     * @return the coords of the second to last position on the passed through
     *         vector or the current position if too small
     */

    public Coords getPriorPosition() {
        if (passedThrough.size() < 2) {
            return getPosition();
        }
        return passedThrough.elementAt(passedThrough.size() - 2);
    }

    /**
     * Sets the current elevation of this entity above the ground.
     *
     * @param elevation
     *            an <code>int</code> representing the new position.
     */
    public void setElevation(int elevation) {
        this.elevation = elevation;
    }

    /**
     * A helper function for fiddling with elevation. Takes the current hex, a
     * hex being moved to, returns the elevation the Entity will be considered
     * to be at w/r/t it's new hex.
     */
    public int calcElevation(IHex current, IHex next, int assumedElevation, boolean climb, boolean wigeEndClimbPrevious) {
        int retVal = assumedElevation;
        if (this instanceof Aero) {
            return retVal;
        }
        if ((getMovementMode() == IEntityMovementMode.SUBMARINE) || ((getMovementMode() == IEntityMovementMode.INF_UMU) && (current.containsTerrain(Terrains.WATER) || next.containsTerrain(Terrains.WATER))) || (getMovementMode() == IEntityMovementMode.VTOL)
                // a WIGE in climb mode or that ended climb mode in the previous
                // hex stays at the same flight level, like a VTOL
                || ((getMovementMode() == IEntityMovementMode.WIGE) && (climb || wigeEndClimbPrevious) && (assumedElevation > 0)) || ((getMovementMode() == IEntityMovementMode.QUAD_SWIM) && hasUMU()) || ((getMovementMode() == IEntityMovementMode.BIPED_SWIM) && hasUMU())) {
            retVal += current.surface();
            retVal -= next.surface();
        } else {
            if ((getMovementMode() != IEntityMovementMode.HOVER) && (getMovementMode() != IEntityMovementMode.NAVAL) && (getMovementMode() != IEntityMovementMode.HYDROFOIL) && (getMovementMode() != IEntityMovementMode.WIGE)) {
                int prevWaterLevel = 0;
                if (current.containsTerrain(Terrains.WATER)) {
                    prevWaterLevel = current.terrainLevel(Terrains.WATER);
                    if (!(current.containsTerrain(Terrains.ICE)) || (assumedElevation < 0)) {
                        // count water, only if the entity isn't on ice surface
                        retVal += current.terrainLevel(Terrains.WATER);
                    }
                }
                if (next.containsTerrain(Terrains.WATER)) {
                    int waterLevel = next.terrainLevel(Terrains.WATER);
                    if (next.containsTerrain(Terrains.ICE)) {
                        // a mech can only climb out onto ice in depth 2 or
                        // shallower water
                        // mech on the surface will stay on the surface

                        if (((waterLevel == 1) && (prevWaterLevel == 1)) || ((prevWaterLevel <= 2) && climb) || (assumedElevation >= 0)) {
                            retVal += waterLevel;
                        }
                    }
                    retVal -= waterLevel;
                }
            }
            if (next.containsTerrain(Terrains.BUILDING) || current.containsTerrain(Terrains.BUILDING)) {
                int bldcur = Math.max(0, current.terrainLevel(Terrains.BLDG_ELEV));
                int bldnex = Math.max(0, next.terrainLevel(Terrains.BLDG_ELEV));

                if (((assumedElevation == bldcur) && climb && (this instanceof Mech)) || (retVal > bldnex)) {
                    retVal = bldnex;
                } else if (bldnex + next.surface() > bldcur + current.surface()) {
                    retVal += current.surface();
                    retVal -= next.surface();
                }
            }
            if ((getMovementMode() != IEntityMovementMode.NAVAL) && (getMovementMode() != IEntityMovementMode.HYDROFOIL) && (next.containsTerrain(Terrains.BRIDGE) || current.containsTerrain(Terrains.BRIDGE))) {
                int brdnex = Math.max(-(next.depth()), next.terrainLevel(Terrains.BRIDGE_ELEV));
                if (Math.abs((next.surface() + brdnex) - (current.surface() + assumedElevation)) <= getMaxElevationChange()) {
                    // bridge is reachable at least
                    if (climb || (Math.abs((next.surface() + retVal) - (current.surface() + assumedElevation)) > getMaxElevationChange()) || !isElevationValid(retVal, next)) {
                        // use bridge if you can't use the base terrain or if
                        // you prefer to by climb mode
                        retVal = brdnex;
                    }
                }
            }
        }
        return retVal;
    }

    public int calcElevation(IHex current, IHex next) {
        return calcElevation(current, next, elevation, false, false);
    }

    /**
     * Returns the elevation of this entity.
     */
    public int getElevation() {
        if (Entity.NONE != getTransportId()) {
            return game.getEntity(getTransportId()).getElevation();
        }

        if ((null == getPosition()) && (isDeployed())) {
            throw new IllegalStateException("Entity #" + getId() + " does not know its position.");
        }

        if (isOffBoard()) {
            return 0;
        }

        return elevation;
    }

    public boolean canGoDown() {
        return canGoDown(elevation, getPosition());
    }

    /**
     * is it possible to go down, or are we landed/just above the
     * water/treeline? assuming passed elevation.
     */
    public boolean canGoDown(int assumedElevation, Coords assumedPos) {
        boolean inWaterOrWoods = false;
        IHex hex = getGame().getBoard().getHex(assumedPos);
        int altitude = assumedElevation + hex.surface();
        int minAlt = hex.surface();
        if (hex.containsTerrain(Terrains.WOODS) || hex.containsTerrain(Terrains.WATER) || hex.containsTerrain(Terrains.JUNGLE)) {
            inWaterOrWoods = true;
        }
        switch (getMovementMode()) {
        case IEntityMovementMode.INF_JUMP:
        case IEntityMovementMode.INF_LEG:
        case IEntityMovementMode.INF_MOTORIZED:
            minAlt -= Math.max(0, hex.terrainLevel(Terrains.BLDG_BASEMENT));
            break;
        case IEntityMovementMode.VTOL:
        case IEntityMovementMode.WIGE:
            minAlt = hex.ceiling();
            if (inWaterOrWoods) {
                minAlt++; // can't land here
            }
            break;
        case IEntityMovementMode.AERODYNE:
        case IEntityMovementMode.SPHEROID:
            if (game.getBoard().inAtmosphere()) {
                altitude = assumedElevation;
                minAlt = hex.ceiling() + 1;
                // if sensors are damaged then, one higher
                if (((Aero) this).getSensorHits() > 0) {
                    minAlt++;
                }
            }
            break;
        case IEntityMovementMode.SUBMARINE:
        case IEntityMovementMode.INF_UMU:
        case IEntityMovementMode.BIPED_SWIM:
        case IEntityMovementMode.QUAD_SWIM:
            minAlt = hex.floor();
            break;
        default:
            return false;
        }
        return (altitude > minAlt);
    }

    /**
     * is it possible to go up, or are we at maximum altitude? assuming passed
     * elevation.
     */
    public boolean canGoUp(int assumedElevation, Coords assumedPos) {
        IHex hex = getGame().getBoard().getHex(assumedPos);
        int altitude = assumedElevation + hex.surface();
        int maxAlt = hex.surface();
        switch (getMovementMode()) {
        case IEntityMovementMode.INF_JUMP:
        case IEntityMovementMode.INF_LEG:
        case IEntityMovementMode.INF_MOTORIZED:
            maxAlt += Math.max(0, hex.terrainLevel(Terrains.BLDG_ELEV));
            break;
        case IEntityMovementMode.VTOL:
            maxAlt = hex.surface() + 50;
            break;
        case IEntityMovementMode.AERODYNE:
        case IEntityMovementMode.SPHEROID:
            if (game.getBoard().inAtmosphere()) {
                altitude = assumedElevation;
                maxAlt = 10;
            }
            break;
        case IEntityMovementMode.SUBMARINE:
        case IEntityMovementMode.INF_UMU:
        case IEntityMovementMode.BIPED_SWIM:
        case IEntityMovementMode.QUAD_SWIM:
            maxAlt = hex.surface();
            break;
        case IEntityMovementMode.WIGE:
            maxAlt = hex.surface() + 1;
            break;
        default:
            return false;
        }
        return (altitude < maxAlt);
    }

    /**
     * Check if this entity can legally occupy the requested elevation. Does not
     * check stacking, only terrain limitations
     */
    public boolean isElevationValid(int assumedElevation, IHex hex) {
        int altitude = assumedElevation + hex.surface();
        if (getMovementMode() == IEntityMovementMode.VTOL) {
            if ((this instanceof Infantry) && (hex.containsTerrain(Terrains.BUILDING) || hex.containsTerrain(Terrains.WOODS) || hex.containsTerrain(Terrains.JUNGLE))) {
                // VTOL BA (sylph) can move as ground unit as well
                return ((assumedElevation <= 50) && (altitude >= hex.floor()));
            } else if (hex.containsTerrain(Terrains.WOODS) || hex.containsTerrain(Terrains.WATER) || hex.containsTerrain(Terrains.JUNGLE)) {
                return ((assumedElevation <= 50) && (altitude > hex.ceiling()));
            }
            return ((assumedElevation <= 50) && (altitude >= hex.ceiling()));
        } else if ((getMovementMode() == IEntityMovementMode.SUBMARINE) || ((getMovementMode() == IEntityMovementMode.INF_UMU) && hex.containsTerrain(Terrains.WATER)) || ((getMovementMode() == IEntityMovementMode.QUAD_SWIM) && hasUMU()) || ((getMovementMode() == IEntityMovementMode.BIPED_SWIM) && hasUMU())) {
            return ((altitude >= hex.floor()) && (altitude <= hex.surface()));
        } else if ((getMovementMode() == IEntityMovementMode.HYDROFOIL) || (getMovementMode() == IEntityMovementMode.NAVAL)) {
            return altitude == hex.surface();
        } else if (getMovementMode() == IEntityMovementMode.WIGE) {
            // WiGEs can possibly be at any location above or on the surface
            return (altitude >= hex.floor());
        } else {
            // regular ground units
            if (hex.containsTerrain(Terrains.ICE) || ((getMovementMode() == IEntityMovementMode.HOVER) && hex.containsTerrain(Terrains.WATER))) {
                // surface of ice is OK, surface of water is OK for hovers
                if (altitude == hex.surface()) {
                    return true;
                }
            }
            // only mechs can move underwater
            if (hex.containsTerrain(Terrains.WATER) && (altitude < hex.surface()) && !(this instanceof Mech) && !(this instanceof Protomech)) {
                return false;
            }
            // can move on the ground unless its underwater
            if (altitude == hex.floor()) {
                return true;
            }
            if (hex.containsTerrain(Terrains.BRIDGE)) {
                // can move on top of a bridge
                if (assumedElevation == hex.terrainLevel(Terrains.BRIDGE_ELEV)) {
                    return true;
                }
            }
            if (hex.containsTerrain(Terrains.BUILDING)) {
                // Mechs, protos and infantry can occupy any floor in the
                // building
                if ((this instanceof Mech) || (this instanceof Protomech) || (this instanceof Infantry)) {
                    if ((altitude >= hex.floor()) && (altitude <= hex.ceiling())) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    /**
     * Returns the height of the unit, that is, how many levels above it's
     * elevation is it for LOS purposes. Default is 0.
     */
    public int height() {
        return 0;
    }

    /**
     * Returns the absolute height of the entity
     */
    public int absHeight() {
        return getElevation() + height();
    }

    /**
     * Returns the display name for this entity.
     */
    public String getDisplayName() {
        if (displayName == null) {
            generateDisplayName();
        }
        return displayName;
    }

    /**
     * Generates the display name for this entity.
     * <p/>
     * Sub-classes are allowed to override this method. The display name is in
     * the format [Chassis] [Model] ([Player Name]).
     */
    public void generateDisplayName() {
        StringBuffer nbuf = new StringBuffer();
        nbuf.append(chassis);
        if ((model != null) && (model.length() > 0)) {
            nbuf.append(" ").append(model);
        }
        // if show unit id is on, append the id
        if (PreferenceManager.getClientPreferences().getShowUnitId()) {
            nbuf.append(" ID:").append(getId());
        } else if (duplicateMarker > 1) {
            // if not, and a player has more than one unit with the same name,
            // append "#N" after the model to differentiate.
            nbuf.append(" #" + duplicateMarker);
        }
        if (getOwner() != null) {
            nbuf.append(" (").append(getOwner().getName()).append(")");
        }
        if (PreferenceManager.getClientPreferences().getShowUnitId()) {
            nbuf.append(" ID:").append(getId());
        }

        displayName = nbuf.toString();
    }

    /**
     * A short name, suitable for displaying above a unit icon. The short name
     * is basically the same as the display name, minus the player name.
     */
    public String getShortName() {
        if (shortName == null) {
            generateShortName();
        }
        return shortName;
    }

    /**
     * Generate the short name for a unit
     * <p/>
     * Sub-classes are allowed to override this method. The display name is in
     * the format [Chassis] [Model].
     */
    public void generateShortName() {
        StringBuffer nbuf = new StringBuffer();
        nbuf.append(chassis);
        if ((model != null) && (model.length() > 0)) {
            nbuf.append(" ").append(model);
        }
        // if show unit id is on, append the id
        if (PreferenceManager.getClientPreferences().getShowUnitId()) {
            nbuf.append(" ID:").append(getId());
        } else if (duplicateMarker > 1) {
            // if not, and a player has more than one unit with the same name,
            // append "#N" after the model to differentiate.
            nbuf.append(" #" + duplicateMarker);
        }

        shortName = nbuf.toString();
    }

    public String getShortNameRaw() {
        StringBuffer nbuf = new StringBuffer();
        nbuf.append(chassis);
        if ((model != null) && (model.length() > 0)) {
            nbuf.append(" ").append(model);
        }
        return nbuf.toString();
    }

    /**
     * Returns the primary facing, or -1 if n/a
     */
    public int getFacing() {
        if (Entity.NONE != conveyance) {
            return game.getEntity(conveyance).getFacing();
        }
        return facing;
    }

    /**
     * Sets the primary facing.
     */
    public void setFacing(int facing) {
        this.facing = facing;
        if (game != null) {
            game.processGameEvent(new GameEntityChangeEvent(this, this));
        }
    }

    /**
     * Returns the secondary facing, or -1 if n/a
     */
    public int getSecondaryFacing() {
        return sec_facing;
    }

    /**
     * Sets the secondary facing.
     */
    public void setSecondaryFacing(int sec_facing) {
        this.sec_facing = sec_facing;
        if (game != null) {
            game.processGameEvent(new GameEntityChangeEvent(this, this));
        }
    }

    /**
     * Can this entity change secondary facing at all?
     */
    public abstract boolean canChangeSecondaryFacing();

    /**
     * Can this entity torso/turret twist the given direction?
     */
    public abstract boolean isValidSecondaryFacing(int dir);

    /**
     * Returns the closest valid secondary facing to the given direction.
     *
     * @return the the closest valid secondary facing.
     */
    public abstract int clipSecondaryFacing(int dir);

    /**
     * Returns true if the entity has an RAC which is jammed and not destroyed
     */
    public boolean canUnjamRAC() {
        for (Mounted mounted : getTotalWeaponList()) {
            WeaponType wtype = (WeaponType) mounted.getType();
            if ((wtype.getAmmoType() == AmmoType.T_AC_ROTARY) && mounted.isJammed() && !mounted.isDestroyed()) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns true if the entity can flip its arms
     */
    public boolean canFlipArms() {
        return false;
    }

    /**
     * Returns this entity's original walking movement points
     */
    public int getOriginalWalkMP() {
        return walkMP;
    }

    /**
     * Sets this entity's original walking movement points
     */
    public void setOriginalWalkMP(int walkMP) {
        this.walkMP = walkMP;
    }

    /**
     * Returns this entity's walking/cruising mp, factored for heat and gravity.
     */

    public int getWalkMP() {
        return getWalkMP(true, false);
    }

    /**
     * Returns this entity's walking/cruising mp, factored for heat and possibly
     * gravity.
     *
     * @param gravity
     *            Should the movement be factored for gravity
     * @param ignoreheat
     *            Should heat be ignored?
     */
    public int getWalkMP(boolean gravity, boolean ignoreheat) {
        int mp = getOriginalWalkMP();
        if (!ignoreheat) {
            mp = Math.max(0, mp - getHeatMPReduction());
        }
        mp = Math.max(mp - getCargoMpReduction(), 0);
        if (null != game) {
            int weatherMod = game.getPlanetaryConditions().getMovementMods(this);
            if (weatherMod != 0) {
                mp = Math.max(mp + weatherMod, 0);
            }
        }
        if (gravity) {
            mp = applyGravityEffectsOnMP(mp);
        }
        return mp;
    }

    /**
     * This returns how much MP is removed due to heat
     *
     * @return
     */
    public int getHeatMPReduction() {
        int minus;

        if ((game != null) && game.getOptions().booleanOption("tacops_heat")) {
            if (heat < 30) {
                minus = (heat / 5);
            } else if (heat >= 49) {
                minus = 9;
            } else if (heat >= 43) {
                minus = 8;
            } else if (heat >= 37) {
                minus = 7;
            } else if (heat >= 31) {
                minus = 6;
            } else {
                minus = 5;
            }
        } else {
            minus = heat / 5;
        }

        return minus;
    }

    /**
     * get the heat generated by this Entity when standing still
     */
    public int getStandingHeat() {
        return 0;
    }

    /**
     * get the heat generated by this Entity when walking/cruising
     */
    public int getWalkHeat() {
        return 0;
    }

    /**
     * Returns this entity's unmodified running/flank mp.
     */
    protected int getOriginalRunMP() {
        return (int) Math.ceil(getOriginalWalkMP() * 1.5);
    }

    /**
     * Returns this entity's running/flank mp modified for heat and gravity.
     */
    public int getRunMP() {
        return getRunMP(true, false);
    }

    public int getRunMP(boolean gravity, boolean ignoreheat) {
        return (int) Math.ceil(getWalkMP(gravity, ignoreheat) * 1.5);
    }

    /**
     * Returns run MP without considering MASC
     */
    public int getRunMPwithoutMASC() {
        return getRunMPwithoutMASC(true, false);
    }

    /**
     * Returns run MP without considering MASC, optionally figuring in gravity
     * and possibly ignoring heat
     */
    public abstract int getRunMPwithoutMASC(boolean gravity, boolean ignoreheat);

    /**
     * Returns this entity's running/flank mp as a string.
     */
    public String getRunMPasString() {
        return Integer.toString(getRunMP());
    }

    /**
     * get the heat generated by this Entity when running/flanking
     */
    public int getRunHeat() {
        return 0;
    }

    /**
     * Returns this entity's original jumping mp.
     */
    public int getOriginalJumpMP() {

        if (hasModularArmor()) {
            return jumpMP - 1;
        }

        return jumpMP;
    }

    /**
     * Sets this entity's original jump movement points
     */
    public void setOriginalJumpMP(int jumpMP) {
        this.jumpMP = jumpMP;
    }

    /**
     * Returns this entity's current jumping MP, not effected by terrain,
     * factored for gravity.
     */
    public int getJumpMP() {
        return getJumpMP(true);
    }

    /**
     * return this entity's current jump MP, possibly affected by gravity
     *
     * @param gravity
     * @return
     */
    public int getJumpMP(boolean gravity) {
        if (gravity) {
            return applyGravityEffectsOnMP(getOriginalJumpMP());
        }
        return getOriginalJumpMP();
    }

    public int getJumpType() {
        return 0;
    }

    /**
     * get the heat generated by this Entity when jumping for a certain amount
     * of MP
     *
     * @param movedMP
     *            the number of movement points spent
     */
    public int getJumpHeat(int movedMP) {
        return 0;
    }

    /**
     * Returns this entity's current jumping MP, effected by terrain (like
     * water.)
     */
    public int getJumpMPWithTerrain() {
        return getJumpMP();
    }

    /**
     * Returns the elevation that this entity would be on if it were placed into
     * the specified hex. Hovercraft, naval vessels, and hydrofoils move on the
     * surface of the water
     */
    public int elevationOccupied(IHex hex) {
        if (hex == null) {
            return 0;
        }
        if ((movementMode == IEntityMovementMode.VTOL) || (movementMode == IEntityMovementMode.WIGE)) {
            return hex.surface() + elevation;
        } else if (((movementMode == IEntityMovementMode.HOVER) || (movementMode == IEntityMovementMode.NAVAL) || (movementMode == IEntityMovementMode.HYDROFOIL) || hex.containsTerrain(Terrains.ICE)) && hex.containsTerrain(Terrains.WATER)) {
            return hex.surface();
        } else if (hex.containsTerrain(Terrains.BLDG_ELEV)) {
            return hex.floor() + hex.terrainLevel(Terrains.BLDG_ELEV);
        } else {
            return hex.floor();
        }
    }

    /**
     * Returns true if the specified hex contains some sort of prohibited
     * terrain.
     */
    public boolean isHexProhibited(IHex hex) {

        if (hex.containsTerrain(Terrains.IMPASSABLE)) {
            return true;
        }

        if (hex.containsTerrain(Terrains.SPACE) && doomedInSpace()) {
            return true;
        }

        return false;
    }

    /**
     * Returns true if the the given board is prohibited
     */
    public boolean isBoardProhibited(int mapType) {

        if ((mapType == Board.T_GROUND) && doomedOnGround()) {
            return true;
        }

        if ((mapType == Board.T_ATMOSPHERE) && doomedInAtmosphere()) {
            return true;
        }

        if ((mapType == Board.T_SPACE) && doomedInSpace()) {
            return true;
        }

        return false;
    }

    /**
     * Returns the name of the type of movement used.
     */
    public abstract String getMovementString(int mtype);

    /**
     * Returns the abbreviation of the name of the type of movement used.
     */
    public abstract String getMovementAbbr(int mtype);

    /**
     * Returns the name of the location specified.
     */
    public String getLocationName(HitData hit) {
        return getLocationName(hit.getLocation());
    }

    protected abstract String[] getLocationNames();

    /**
     * Returns the name of the location specified.
     */
    public String getLocationName(int loc) {
        String[] locationNames = getLocationNames();

        if ((null == locationNames) || (loc >= locationNames.length)) {
            return "";
        }

        return locationNames[loc];
    }

    protected abstract String[] getLocationAbbrs();

    /**
     * Returns the abbreviated name of the location specified.
     */
    public String getLocationAbbr(HitData hit) {
        return getLocationAbbr(hit.getLocation()) + (hit.isRear() && hasRearArmor(hit.getLocation()) ? "R" : "") + (hit.getEffect() == HitData.EFFECT_CRITICAL ? " (critical)" : "");
    }

    /**
     * Returns the abbreviated name of the location specified.
     */
    public String getLocationAbbr(int loc) {
        String[] locationAbbrs = getLocationAbbrs();

        if ((null == locationAbbrs) || (loc >= locationAbbrs.length)) {
            return "";
        }
        return locationAbbrs[loc];
    }

    /**
     * Returns the location that the specified abbreviation indicates
     */
    public int getLocationFromAbbr(String abbr) {
        for (int i = 0; i < locations(); i++) {
            if (getLocationAbbr(i).equalsIgnoreCase(abbr)) {
                return i;
            }
        }
        return Entity.LOC_NONE;
    }

    /**
     * Rolls the to-hit number
     */
    public abstract HitData rollHitLocation(int table, int side, int aimedLocation, int aimingMode);

    /**
     * Rolls up a hit location
     */
    public abstract HitData rollHitLocation(int table, int side);

    /**
     * Gets the location that excess damage transfers to. That is, one location
     * inwards.
     */
    public abstract HitData getTransferLocation(HitData hit);

    /** int version */
    public int getTransferLocation(int loc) {
        return getTransferLocation(new HitData(loc)).getLocation();
    }

    /**
     * Gets the location that is destroyed recursively. That is, one location
     * outwards.
     */
    public abstract int getDependentLocation(int loc);

    /**
     * Does this location have rear armor?
     */
    public abstract boolean hasRearArmor(int loc);

    /**
     * Returns the amount of armor in the location specified, or ARMOR_NA, or
     * ARMOR_DESTROYED. Only works on front locations.
     */
    public int getArmor(int loc) {
        return getArmor(loc, false);
    }

    /**
     * Returns the amount of armor in the location hit, or IArmorState.ARMOR_NA,
     * or IArmorState.ARMOR_DESTROYED.
     */
    public int getArmor(HitData hit) {
        return getArmor(hit.getLocation(), hit.isRear());
    }

    /**
     * Returns the amount of armor in the location specified, or
     * IArmorState.ARMOR_NA, or IArmorState.ARMOR_DESTROYED.
     */
    public int getArmor(int loc, boolean rear) {
        if (loc >= armor.length) {
            return IArmorState.ARMOR_NA;
        }
        return armor[loc];
    }

    /**
     * Returns the original amount of armor in the location specified. Only
     * works on front locations.
     */
    public int getOArmor(int loc) {
        return getOArmor(loc, false);
    }

    /**
     * Returns the original amount of armor in the location hit.
     */
    public int getOArmor(HitData hit) {
        return getOArmor(hit.getLocation(), hit.isRear());
    }

    /**
     * Returns the original amount of armor in the location specified, or
     * ARMOR_NA, or ARMOR_DESTROYED.
     *
     * @param loc
     *            the location to check.
     * @param rear
     *            if true inspect the rear armor, else check the front.
     */
    public int getOArmor(int loc, boolean rear) {
        return orig_armor[loc];
    }

    /**
     * Sets the amount of armor in the location specified.
     */
    public void setArmor(int val, HitData hit) {
        setArmor(val, hit.getLocation(), hit.isRear());
    }

    /**
     * Sets the amount of armor in the front location specified.
     */
    public void setArmor(int val, int loc) {
        setArmor(val, loc, false);
    }

    /**
     * Sets the amount of armor in the location specified.
     *
     * @param val
     *            the value of the armor (eg how many armor points)
     * @param loc
     *            the location of the armor
     * @param rear
     *            true iff the armor is rear mounted.
     */
    public void setArmor(int val, int loc, boolean rear) {
        armor[loc] = val;
    }

    public void refreshLocations() {
        armor = new int[locations()];
        internal = new int[locations()];
        orig_armor = new int[locations()];
        orig_internal = new int[locations()];
        crits = new CriticalSlot[locations()][];
        exposure = new int[locations()];
        for (int i = 0; i < locations(); i++) {
            crits[i] = new CriticalSlot[getNumberOfCriticals(i)];
        }
    }

    /**
     * Initializes the armor on the unit. Sets the original and starting point
     * of the armor to the same number.
     */
    public void initializeArmor(int val, int loc) {
        orig_armor[loc] = val;
        setArmor(val, loc);
    }

    /**
     * Returns the total amount of armor on the entity.
     */
    public int getTotalArmor() {
        int totalArmor = 0;
        for (int i = 0; i < locations(); i++) {
            if (getArmor(i) > 0) {
                totalArmor += getArmor(i);
            }
            if (hasRearArmor(i) && (getArmor(i, true) > 0)) {
                totalArmor += getArmor(i, true);
            }
        }
        return totalArmor;
    }

    /**
     * Returns the total amount of armor on the entity.
     */
    public int getTotalOArmor() {
        int totalArmor = 0;
        for (int i = 0; i < locations(); i++) {
            if (getOArmor(i) > 0) {
                totalArmor += getOArmor(i);
            }
            if (hasRearArmor(i) && (getOArmor(i, true) > 0)) {
                totalArmor += getOArmor(i, true);
            }
        }
        return totalArmor;
    }

    /**
     * Returns the percent of the armor remaining
     */
    public double getArmorRemainingPercent() {
        if (getTotalOArmor() == 0) {
            return IArmorState.ARMOR_NA;
        }
        return ((double) getTotalArmor() / (double) getTotalOArmor());
    }

    /**
     * Returns the amount of internal structure in the location hit.
     */
    public int getInternal(HitData hit) {
        return getInternal(hit.getLocation());
    }

    /**
     * Returns the amount of internal structure in the location specified, or
     * ARMOR_NA, or ARMOR_DESTROYED.
     */
    public int getInternal(int loc) {
        return internal[loc];
    }

    /**
     * Returns the original amount of internal structure in the location hit.
     */
    public int getOInternal(HitData hit) {
        return getOInternal(hit.getLocation());
    }

    /**
     * Returns the original amount of internal structure in the location
     * specified, or ARMOR_NA, or ARMOR_DESTROYED.
     */
    public int getOInternal(int loc) {
        return orig_internal[loc];
    }

    /**
     * Sets the amount of armor in the location specified.
     */
    public void setInternal(int val, HitData hit) {
        setInternal(val, hit.getLocation());
    }

    /**
     * Sets the amount of armor in the location specified.
     */
    public void setInternal(int val, int loc) {
        internal[loc] = val;
    }

    /**
     * Initializes the internal structure on the unit. Sets the original and
     * starting point of the internal structure to the same number.
     */
    public void initializeInternal(int val, int loc) {
        orig_internal[loc] = val;
        setInternal(val, loc);
    }

    /**
     * Set the internal structure to the appropriate value for the mech's weight
     * class
     */
    public abstract void autoSetInternal();

    /**
     * Returns the total amount of internal structure on the entity.
     */
    public int getTotalInternal() {
        int totalInternal = 0;
        for (int i = 0; i < locations(); i++) {
            if (getInternal(i) > 0) {
                totalInternal += getInternal(i);
            }
        }
        return totalInternal;
    }

    /**
     * Returns the total original amount of internal structure on the entity.
     */
    public int getTotalOInternal() {
        int totalInternal = 0;
        for (int i = 0; i < locations(); i++) {
            if (getOInternal(i) > 0) {
                totalInternal += getOInternal(i);
            }
        }
        return totalInternal;
    }

    /**
     * Returns the percent of the armor remaining
     */
    public double getInternalRemainingPercent() {
        return ((double) getTotalInternal() / (double) getTotalOInternal());
    }

    /**
     * Is this location destroyed or breached?
     */
    public boolean isLocationBad(int loc) {
        return getInternal(loc) == IArmorState.ARMOR_DESTROYED;
    }

    /**
     * Is this location destroyed or breached?
     */
    public boolean isLocationDoomed(int loc) {
        return getInternal(loc) == IArmorState.ARMOR_DOOMED;
    }

    /**
     * returns exposure or breached flag for location
     */
    public int getLocationStatus(int loc) {
        return exposure[loc];
    }

    /**
     * sets location exposure
     *
     * @param loc
     *            the location who's exposure is to be set
     * @param status
     *            the status to set
     */
    public void setLocationStatus(int loc, int status) {
        if (exposure[loc] > ILocationExposureStatus.BREACHED) { // can't change
            // BREACHED
            // status
            exposure[loc] = status;
        }
    }

    /**
     * Returns true is the location is a leg
     *
     * @param loc
     *            the location to check.
     */
    public boolean locationIsLeg(int loc) {
        return false;
    }

    /**
     * Returns a string representing the armor in the location
     */
    public String getArmorString(int loc) {
        return getArmorString(loc, false);
    }

    /**
     * Returns a string representing the armor in the location
     */
    public String getArmorString(int loc, boolean rear) {
        return Entity.armorStringFor(getArmor(loc, rear));
    }

    /**
     * Returns a string representing the internal structure in the location
     */
    public String getInternalString(int loc) {
        return Entity.armorStringFor(getInternal(loc));
    }

    /**
     * Parses the game's internal armor representation into a human-readable
     * string.
     */
    public static String armorStringFor(int value) {
        if (value == IArmorState.ARMOR_NA) {
            return "N/A";
        } else if ((value == IArmorState.ARMOR_DOOMED) || (value == IArmorState.ARMOR_DESTROYED)) {
            return "***";
        } else {
            return Integer.toString(value);
        }
    }

    /**
     * Returns the modifier to weapons fire due to heat.
     */
    public int getHeatFiringModifier() {
        int mod = 0;
        if (heat >= 8) {
            mod++;
        }
        if (heat >= 13) {
            mod++;
        }
        if (heat >= 17) {
            mod++;
        }
        if (heat >= 24) {
            mod++;
        }
        boolean mtHeat = game.getOptions().booleanOption("tacops_heat");
        if (mtHeat && (heat >= 33)) {
            mod++;
        }
        if (mtHeat && (heat >= 41)) {
            mod++;
        }
        if (mtHeat && (heat >= 48)) {
            mod++;
        }
        return mod;
    }

    /**
     * Creates a new mount for this equipment and adds it in.
     */
    public Mounted addEquipment(EquipmentType etype, int loc) throws LocationFullException {
        return addEquipment(etype, loc, false);
    }

    /**
     * Creates a new mount for this equipment and adds it in.
     */
    public Mounted addEquipment(EquipmentType etype, int loc, boolean rearMounted) throws LocationFullException {
        return addEquipment(etype, loc, rearMounted, false, false);
    }

    /**
     * Creates a new mount for this equipment and adds it in.
     */
    public Mounted addEquipment(EquipmentType etype, int loc, boolean rearMounted, boolean bodyMounted, boolean isArmored) throws LocationFullException {
        Mounted mounted = new Mounted(this, etype);
        mounted.setArmored(isArmored);
        mounted.setBodyMounted(bodyMounted);
        addEquipment(mounted, loc, rearMounted);
        return mounted;
    }

    /**
     * mounting weapons needs to take account of ammo
     *
     * @param etype
     * @param loc
     * @param rearMounted
     * @param nAmmo
     * @return
     * @throws LocationFullException
     */
    public Mounted addEquipment(EquipmentType etype, int loc, boolean rearMounted, int nAmmo) throws LocationFullException {
        Mounted mounted = new Mounted(this, etype);
        addEquipment(mounted, loc, rearMounted, nAmmo);
        return mounted;

    }

    /*
     * indicate whether this is a bomb mount
     */
    public Mounted addBomb(EquipmentType etype, int loc) throws LocationFullException {
        Mounted mounted = new Mounted(this, etype);
        addBomb(mounted, loc);
        return mounted;
    }

    public Mounted addWeaponGroup(EquipmentType etype, int loc) throws LocationFullException {
        Mounted mounted = new Mounted(this, etype);
        addEquipment(mounted, loc, false, true);
        return mounted;
    }

    /**
     * indicate whether this is bodymounted for BAs
     */
    public Mounted addEquipment(EquipmentType etype, int loc, boolean rearMounted, boolean bodyMounted) throws LocationFullException {
        Mounted mounted = new Mounted(this, etype);
        mounted.setBodyMounted(bodyMounted);
        addEquipment(mounted, loc, rearMounted);
        return mounted;
    }

    protected void addEquipment(Mounted mounted, int loc, boolean rearMounted, int nAmmo) throws LocationFullException {
        if ((mounted.getType() instanceof AmmoType) && (nAmmo > 1)) {
            mounted.setByShot(true);
            mounted.setShotsLeft(nAmmo);
        }

        addEquipment(mounted, loc, rearMounted);
    }

    protected void addBomb(Mounted mounted, int loc) throws LocationFullException {
        mounted.setBombMounted(true);
        addEquipment(mounted, loc, false);
    }

    protected void addEquipment(Mounted mounted, int loc, boolean rearMounted, boolean isWeaponGroup) throws LocationFullException {
        mounted.setWeaponGroup(true);

        addEquipment(mounted, loc, rearMounted);
    }

    protected void addEquipment(Mounted mounted, int loc, boolean rearMounted) throws LocationFullException {
        mounted.setLocation(loc, rearMounted);
        equipmentList.add(mounted);

        // add it to the proper sub-list
        if (mounted.getType() instanceof WeaponType) {
            totalWeaponList.add(mounted);
            if (mounted.isWeaponGroup()) {
                weaponGroupList.add(mounted);
            } else if (mounted.getType() instanceof BayWeapon) {
                weaponBayList.add(mounted);
            } else {
                weaponList.add(mounted);
            }
            if (mounted.getType().hasFlag(WeaponType.F_ARTILLERY)) {
                aTracker.addWeapon(mounted);
            }

            // one-shot launchers need their single shot of ammo added.
            if (mounted.getType().hasFlag(WeaponType.F_ONESHOT)) {
                Mounted m = new Mounted(this, AmmoType.getOneshotAmmo(mounted));
                m.setShotsLeft(1);
                mounted.setLinked(m);
                // Oneshot ammo will be identified by having a location
                // of null. Other areas in the code will rely on this.
                addEquipment(m, Entity.LOC_NONE, false);
            }
        }
        if (mounted.getType() instanceof AmmoType) {
            ammoList.add(mounted);
        }
        if (mounted.getType() instanceof BombType) {
            bombList.add(mounted);
        }
        if (mounted.getType() instanceof MiscType) {
            miscList.add(mounted);
        }
    }

    public void addFailedEquipment(String s) {
        failedEquipmentList.add(s);
    }

    /**
     * Returns the equipment number of the specified equipment, or -1 if
     * equipment is not present.
     */
    public int getEquipmentNum(Mounted mounted) {
        if (mounted != null) {
            return equipmentList.indexOf(mounted);
        }
        return -1;
    }

    /**
     * Returns an enumeration of all equipment
     */
    public ArrayList<Mounted> getEquipment() {
        return equipmentList;
    }

    /**
     * Returns the equipment, specified by number
     */
    public Mounted getEquipment(int index) {
        try {
            return equipmentList.get(index);
        } catch (IndexOutOfBoundsException ex) {
            return null;
        }
    }

    public EquipmentType getEquipmentType(CriticalSlot cs) {
        if (cs.getType() != CriticalSlot.TYPE_EQUIPMENT) {
            return null;
        }
        Mounted m = equipmentList.get(cs.getIndex());
        return m.getType();
    }

    /**
     * Returns an enumeration which contains the name of each piece of equipment
     * that failed to load.
     */
    public Iterator<String> getFailedEquipment() {
        return failedEquipmentList.iterator();
    }

    public int getTotalAmmoOfType(EquipmentType et) {
        int totalShotsLeft = 0;
        for (Mounted amounted : getAmmo()) {
            if ((amounted.getType() == et) && !amounted.isDumping()) {
                totalShotsLeft += amounted.getShotsLeft();
            }
        }
        return totalShotsLeft;
    }

    /**
     * Determine how much ammunition (of all munition types) remains which is
     * compatable with the given ammo.
     *
     * @param et
     *            - the <code>EquipmentType</code> of the ammo to be found. This
     *            value may be <code>null</code>.
     * @return the <code>int</code> count of the amount of shots of all
     *         munitions equivalent to the given ammo type.
     */
    public int getTotalMunitionsOfType(EquipmentType et) {
        int totalShotsLeft = 0;
        for (Mounted amounted : getAmmo()) {
            if (amounted.getType().equals(et) && !amounted.isDumping()) {
                totalShotsLeft += amounted.getShotsLeft();
            }
        }
        return totalShotsLeft;
    }

    /**
     * Returns the Rules.ARC that the weapon, specified by number, fires into.
     */
    public abstract int getWeaponArc(int wn);

    /**
     * Returns true if this weapon fires into the secondary facing arc. If
     * false, assume it fires into the primary.
     */
    public abstract boolean isSecondaryArcWeapon(int weaponId);

    public Iterator<Mounted> getWeapons() {
        if (usesWeaponBays()) {
            return weaponBayList.iterator();
        }
        if (isCapitalFighter()) {
            return weaponGroupList.iterator();
        }

        return weaponList.iterator();
    }

    public ArrayList<Mounted> getWeaponList() {
        if (usesWeaponBays()) {
            return weaponBayList;
        }
        if (isCapitalFighter()) {
            return weaponGroupList;
        }

        return weaponList;
    }

    public ArrayList<Mounted> getTotalWeaponList() {
        // return full weapon list even bay mounts and weapon groups
        return totalWeaponList;
    }

    public ArrayList<Mounted> getWeaponBayList() {
        return weaponBayList;
    }

    public ArrayList<Mounted> getWeaponGroupList() {
        return weaponGroupList;
    }

    /**
     * Returns the first ready weapon
     *
     * @return the index number of the first available weapon, or -1 if none are
     *         ready.
     */
    public int getFirstWeapon() {
        for (Mounted mounted : getWeaponList()) {
            if (mounted.isReady()) {
                return getEquipmentNum(mounted);
            }
        }
        return -1;
    }

    /**
     * Returns the next ready weapon, starting at the specified index
     */
    public int getNextWeapon(int start) {
        boolean past = false;
        for (Mounted mounted : getWeaponList()) {
            // FIXME
            // Logic must be inserted here to NOT always skip AMS once the
            // MaxTech rule for firing AMSes is implemented.
            if (past && (mounted != null) && (mounted.isReady()) && (!mounted.getType().hasFlag(WeaponType.F_AMS)) && ((mounted.getLinked() == null) || (mounted.getLinked().getShotsLeft() > 0))) {
                if (mounted.getType().hasFlag(WeaponType.F_TAG) && (game.getPhase() == IGame.Phase.PHASE_FIRING)) {
                    continue;
                }
                if (mounted.getType().hasFlag(WeaponType.F_MG)) {
                    if (hasLinkedMGA(mounted)) {
                        continue;
                    }
                }

                return getEquipmentNum(mounted);
            }

            if (getEquipmentNum(mounted) == start) {
                past = true;
                continue;
            }
            if (past && (getEquipmentNum(mounted) == start)) {
                return getFirstWeapon();
            }
        }
        return getFirstWeapon();
    }

    /**
     * Attempts to load all weapons with ammo
     */
    public void loadAllWeapons() {
        for (Mounted mounted : getTotalWeaponList()) {
            WeaponType wtype = (WeaponType) mounted.getType();
            if (wtype.getAmmoType() != AmmoType.T_NA) {
                loadWeapon(mounted);
            }
        }
    }

    /**
     * Tries to load the specified weapon with the first available ammo
     */
    public void loadWeapon(Mounted mounted) {
        for (Mounted mountedAmmo : getAmmo()) {
            if (loadWeapon(mounted, mountedAmmo)) {
                break;
            }
        }
    }

    /**
     * Tries to load the specified weapon with the first available ammo of the
     * same munition type as currently in use. If this fails, use first ammo.
     */
    public void loadWeaponWithSameAmmo(Mounted mounted) {
        for (Mounted mountedAmmo : getAmmo()) {
            if (loadWeaponWithSameAmmo(mounted, mountedAmmo)) {
                return;
            }
        }
        // fall back to use any ammo
        loadWeapon(mounted);
    }

    /**
     * Tries to load the specified weapon with the specified ammo. Returns true
     * if successful, false otherwise.
     */
    public boolean loadWeapon(Mounted mounted, Mounted mountedAmmo) {
        boolean success = false;
        WeaponType wtype = (WeaponType) mounted.getType();
        AmmoType atype = (AmmoType) mountedAmmo.getType();

        if (mountedAmmo.isAmmoUsable() && !wtype.hasFlag(WeaponType.F_ONESHOT) && (atype.getAmmoType() == wtype.getAmmoType()) && (atype.getRackSize() == wtype.getRackSize())) {
            mounted.setLinked(mountedAmmo);
            success = true;
        }
        return success;
    }

    /**
     * Tries to load the specified weapon with the specified ammo. Returns true
     * if successful, false otherwise.
     */
    public boolean loadWeaponWithSameAmmo(Mounted mounted, Mounted mountedAmmo) {
        AmmoType atype = (AmmoType) mountedAmmo.getType();
        Mounted oldammo = mounted.getLinked();

        if ((oldammo != null) && (((AmmoType) oldammo.getType()).getMunitionType() != atype.getMunitionType())) {
            return false;
        }

        return loadWeapon(mounted, mountedAmmo);
    }

    /**
     * Checks whether a weapon has been fired from the specified location this
     * turn
     */
    public boolean weaponFiredFrom(int loc) {
        // check critical slots for used weapons
        for (int i = 0; i < this.getNumberOfCriticals(loc); i++) {
            CriticalSlot slot = getCritical(loc, i);
            // ignore empty & system slots
            if ((slot == null) || (slot.getType() != CriticalSlot.TYPE_EQUIPMENT)) {
                continue;
            }
            Mounted mounted = getEquipment(slot.getIndex());
            if ((mounted.getType() instanceof WeaponType) && mounted.isUsedThisRound()) {
                return true;
            }
        }
        return false;
    }

    public ArrayList<Mounted> getAmmo() {
        return ammoList;
    }

    public ArrayList<Mounted> getMisc() {
        return miscList;
    }

    public ArrayList<Mounted> getBombs() {
        return bombList;
    }

    /**
     * Removes the first misc eq. whose name equals the specified string. Used
     * for removing broken tree clubs.
     */
    public void removeMisc(String toRemove) {
        for (Mounted mounted : getMisc()) {
            if (mounted.getName().equals(toRemove)) {
                miscList.remove(mounted);
                equipmentList.remove(mounted);
                break;
            }
        }
    }

    public List<Mounted> getClubs() {
        List<Mounted> rv = new ArrayList<Mounted>();
        for (Mounted m : getMisc()) {
            if (m.getType().hasFlag(MiscType.F_CLUB)) {
                rv.add(m);
            }
        }
        return rv;
    }

    /**
     * Check if the entity has an arbitrary type of misc equipment
     *
     * @param flag
     *            A MiscType.F_XXX
     * @param secondary
     *            A MiscType.S_XXX or -1 for don't care
     * @return true if at least one ready item.
     */
    public boolean hasWorkingMisc(long flag, long secondary) {
        for (Mounted m : miscList) {
            if ((m.getType() instanceof MiscType) && m.isReady()) {
                MiscType type = (MiscType) m.getType();
                if (type.hasFlag(flag) && ((secondary == -1) || type.hasSubType(secondary))) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Check if the entity has an arbitrary type of misc equipment
     *
     * @param flag
     *            A MiscType.F_XXX
     * @param secondary
     *            A MiscType.S_XXX or -1 for don't care
     * @param location
     *            The location to check e.g. Mech.LOC_LARM
     * @return true if at least one ready item.
     */
    public boolean hasWorkingMisc(long flag, int secondary, int location) {
        //go through the location slot by slot, because of misc equipment that
        //is spreadable
        for (int slot = 0; slot < getNumberOfCriticals(location); slot++) {
            CriticalSlot crit = getCritical(location, slot);
            if ((null != crit) && (crit.getType() == CriticalSlot.TYPE_EQUIPMENT)) {
                Mounted mount = crit.getMount();
                if (mount == null) {
                    continue;
                }
                if ((mount.getType() instanceof MiscType) && mount.isReady()) {
                    MiscType type = (MiscType) mount.getType();
                    if (type.hasFlag(flag) && ((secondary == -1) || type.hasSubType(secondary))) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    /**
     * Returns the amount of heat that the entity can sink each turn.
     */
    public abstract int getHeatCapacity();

    /**
     * Returns the amount of heat that the entity can sink each turn, factoring
     * in whether the entity is standing in water.
     */
    public abstract int getHeatCapacityWithWater();

    /**
     * Returns extra heat generated by engine crits
     */
    public abstract int getEngineCritHeat();

    /**
     * Returns a critical hit slot
     */
    public CriticalSlot getCritical(int loc, int slot) {
        return crits[loc][slot];
    }

    /**
     * Sets a critical hit slot
     */
    public void setCritical(int loc, int slot, CriticalSlot cs) {
        crits[loc][slot] = cs;
    }

    /**
     * Adds a critical to the first available slot in the location.
     *
     * @return true if there was room for the critical
     */
    public boolean addCritical(int loc, CriticalSlot cs) {
        for (int i = 0; i < getNumberOfCriticals(loc); i++) {
            if (getCritical(loc, i) == null) {
                crits[loc][i] = cs;
                return true;
            }
        }
        return false; // no slot available :(
    }

    /**
     * Attempts to set the given slot to the given critical. If the desired slot
     * is full, adds the critical to the first available slot.
     *
     * @return true if the crit was succesfully added to any slot
     */
    public boolean addCritical(int loc, int slot, CriticalSlot cs) {
        if (getCritical(loc, slot) == null) {
            setCritical(loc, slot, cs);
            return true;
        }
        return addCritical(loc, cs);
    }

    /**
     * Removes all matching critical slots from the location
     */
    public void removeCriticals(int loc, CriticalSlot cs) {
        for (int i = 0; i < getNumberOfCriticals(loc); i++) {
            if ((getCritical(loc, i) != null) && getCritical(loc, i).equals(cs)) {
                setCritical(loc, i, null);
            }
        }
    }

    /**
     * Returns the number of empty critical slots in a location
     */
    public int getEmptyCriticals(int loc) {
        int empty = 0;

        for (int i = 0; i < getNumberOfCriticals(loc); i++) {
            if (getCritical(loc, i) == null) {
                empty++;
            }
        }
        return empty;
    }

    /**
     * Returns the number of operational critical slots remaining in a location
     */
    public int getHittableCriticals(int loc) {
        int hittable = 0;

        for (int i = 0; i < getNumberOfCriticals(loc); i++) {
            if ((getCritical(loc, i) != null) && getCritical(loc, i).isHittable()) {
                hittable++;
            }
        }
        return hittable;
    }

    /**
     * Returns true if this location should transfer criticals to the next
     * location inwards. Checks to see that every critical in this location is
     * either already totally destroyed (not just hit) or was never hittable to
     * begin with.
     */
    public boolean canTransferCriticals(int loc) {
        for (int i = 0; i < getNumberOfCriticals(loc); i++) {
            CriticalSlot crit = getCritical(loc, i);
            if ((crit != null) && !crit.isDestroyed() && crit.isEverHittable()) {
                return false;
            }
        }
        return true;
    }

    /**
     * Only Mechs have Gyros but this helps keep the code a bit cleaner.
     *
     * @return <code>-1</code>
     */
    public int getGyroType() {
        return -1;
    }

    /**
     * Returns the number of operational critical slots of the specified type in
     * the location
     */
    public int getGoodCriticals(CriticalSlot cs, int loc) {
        return getGoodCriticals(cs.getType(), cs.getIndex(), loc);
    }

    /**
     * Returns the number of operational critical slots of the specified type in
     * the location
     */
    public int getGoodCriticals(int type, int index, int loc) {
        int operational = 0;

        int numberOfCriticals = getNumberOfCriticals(loc);
        for (int i = 0; i < numberOfCriticals; i++) {
            CriticalSlot ccs = getCritical(loc, i);

            if ((ccs != null) && (ccs.getType() == type) && (ccs.getIndex() == index) && !ccs.isDestroyed() && !ccs.isBreached()) {
                operational++;
            }
        }
        return operational;
    }

    /**
     * The number of critical slots that are destroyed in the component.
     */
    public int getBadCriticals(int type, int index, int loc) {
        int hits = 0;

        int numberOfCriticals = getNumberOfCriticals(loc);
        for (int i = 0; i < numberOfCriticals; i++) {
            CriticalSlot ccs = getCritical(loc, i);

            if ((ccs != null) && (ccs.getType() == type) && (ccs.getIndex() == index)) {
                if (ccs.isDestroyed() || ccs.isBreached()) {
                    hits++;
                }
            }
        }
        return hits;
    }

    /**
     * Number of slots doomed, missing or destroyed
     */
    public int getHitCriticals(int type, int index, int loc) {
        int hits = 0;
        int numCrits = getNumberOfCriticals(loc);
        for (int i = 0; i < numCrits; i++) {
            CriticalSlot ccs = getCritical(loc, i);

            if ((ccs != null) && (ccs.getType() == type) && (ccs.getIndex() == index)) {
                if (ccs.isDamaged() || ccs.isBreached()) {
                    hits++;
                }
            }
        }
        return hits;
    }

    protected abstract int[] getNoOfSlots();

    /**
     * Returns the number of total critical slots in a location
     */
    public int getNumberOfCriticals(int loc) {
        int[] noOfSlots = getNoOfSlots();
        if ((null == noOfSlots) || (loc >= noOfSlots.length) || (loc == LOC_NONE)) {
            return 0;
        }
        return noOfSlots[loc];
    }

    /**
     * Returns the number of critical slots present in the section, destroyed or
     * not.
     */
    public int getNumberOfCriticals(int type, int index, int loc) {
        int num = 0;
        int numCrits = getNumberOfCriticals(loc);
        for (int i = 0; i < numCrits; i++) {
            CriticalSlot ccs = getCritical(loc, i);
            if ((ccs != null) && (ccs.getType() == type) && (ccs.getIndex() == index)) {
                num++;
            }
        }
        return num;
    }

    /**
     * Returns the number of critical slots present in the section, destroyed or
     * not.
     */
    public int getNumberOfCriticals(EquipmentType etype, int loc) {
        int num = 0;
        int numberOfCriticals = getNumberOfCriticals(loc);
        for (int i = 0; i < numberOfCriticals; i++) {
            CriticalSlot ccs = getCritical(loc, i);
            if ((ccs != null) && (getEquipmentType(ccs) != null) && getEquipmentType(ccs).equals(etype)) {
                num++;
            }
        }
        return num;
    }

    /**
     * Returns the number of critical slots present in the mech, destroyed or
     * not.
     */
    public int getNumberOfCriticals(EquipmentType etype) {
        int num = 0;
        int locations = locations();
        for (int l = 0; l < locations; l++) {
            num += getNumberOfCriticals(etype, l);
        }
        return num;
    }

    /**
     * Returns how many of the given equipment are present in the mech,
     * destroyed or not.
     */
    public int getNumberOf(EquipmentType etype) {
        int total = 0;
        for (Mounted m : equipmentList) {
            if (m.getType().equals(etype)) {
                total++;
            }
        }
        return total;
    }

    /**
     * Returns true if the entity has a hip crit. Overridden by sub-classes.
     */
    public boolean hasHipCrit() {
        return false;
    }

    /**
     * Returns true if the entity has a leg actuator crit
     */
    public boolean hasLegActuatorCrit() {
        boolean hasCrit = false;

        for (int i = 0; i < locations(); i++) {
            if (locationIsLeg(i)) {
                if ((getBadCriticals(CriticalSlot.TYPE_SYSTEM, Mech.ACTUATOR_HIP, i) > 0) || (getBadCriticals(CriticalSlot.TYPE_SYSTEM, Mech.ACTUATOR_UPPER_LEG, i) > 0) || (getBadCriticals(CriticalSlot.TYPE_SYSTEM, Mech.ACTUATOR_LOWER_LEG, i) > 0) || (getBadCriticals(CriticalSlot.TYPE_SYSTEM, Mech.ACTUATOR_FOOT, i) > 0)) {
                    hasCrit = true;
                    break;
                }
            }
        }
        return hasCrit;
    }

    /**
     * Returns true if there is at least 1 functional system of the type
     * specified in the location
     */
    public boolean hasWorkingSystem(int system, int loc) {
        for (int i = 0; i < getNumberOfCriticals(loc); i++) {
            CriticalSlot ccs = getCritical(loc, i);
            if ((ccs != null) && (ccs.getType() == CriticalSlot.TYPE_SYSTEM) && (ccs.getIndex() == system) && !ccs.isDamaged() && !ccs.isBreached()) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns true if the the location has a system of the type, whether is
     * destroyed or not
     */
    public boolean hasSystem(int system, int loc) {
        for (int i = 0; i < getNumberOfCriticals(loc); i++) {
            CriticalSlot ccs = getCritical(loc, i);
            if ((ccs != null) && (ccs.getType() == CriticalSlot.TYPE_SYSTEM) && (ccs.getIndex() == system)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Checks to see if this entity is wielding any vibroblades
     *
     * @return always returns <code>false</code> as Only biped mechs can wield
     *         vibroblades
     */
    public boolean hasVibroblades() {
        return false;
    }

    /**
     * Checks to see if any heat is given off by an active vibro blade
     *
     * @param location
     * @return always returns <code>0</code> as Only biped mechs can wield
     *         vibroblades
     */
    public int getActiveVibrobladeHeat(int location) {
        return 0;
    }

    /**
     * Does the mech have any shields. a mech can have up to 2 shields.
     *
     * @return <code>true</code> if <code>shieldCount</code> is greater then 0
     *         else <code>false</code>
     */
    public boolean hasShield() {
        return false;
    }

    /**
     * Check to see how many shields of a certian size a mek has. you can have
     * up to shields per mech. However they can be of different size and each
     * size has its own draw backs. So check each size and add modifers based on
     * the number shields of that size.
     */
    public int getNumberOfShields(long size) {
        return 0;
    }

    /**
     * Does the mech have an active shield This should only be called after
     * hasShield has been called.
     */
    public boolean hasActiveShield(int location, boolean rear) {
        return true;
    }

    /**
     * Does the mech have an active shield This should only be called by
     * hasActiveShield(location,rear)
     */
    public boolean hasActiveShield(int location) {
        return false;
    }

    /**
     * Does the mech have a passive shield This should only be called after
     * hasShield has been called.
     */
    public boolean hasPassiveShield(int location, boolean rear) {
        return false;
    }

    /**
     * Does the mech have a passive shield This should only be called by
     * hasPassiveShield(location,rear)
     */
    public boolean hasPassiveShield(int location) {
        return false;
    }

    /**
     * Does the mech have an shield in no defense mode
     */
    public boolean hasNoDefenseShield(int location) {
        return false;
    }

    /**
     * This method checks to see if a unit has Underwater Maneuvering Units Only
     * Battle Mechs may have UMU's
     *
     * @return <code>boolean</code> if the entity has usable UMU crits.
     */
    public boolean hasUMU() {
        if (!(this instanceof Mech)) {
            return false;
        }

        int umuCount = getActiveUMUCount();

        return umuCount > 0;
    }

    /**
     * This counts the number of UMU's a Mech has that are still viable
     *
     * @return number <code>int</code>of useable UMU's
     */
    public int getActiveUMUCount() {
        int count = 0;

        if (hasShield() && (getNumberOfShields(MiscType.S_SHIELD_LARGE) > 0)) {
            return 0;
        }

        for (Mounted m : getMisc()) {
            EquipmentType type = m.getType();
            if ((type instanceof MiscType) && type.hasFlag(MiscType.F_UMU) && !(m.isDestroyed() || m.isMissing() || m.isBreached())) {
                count++;
            }
        }

        return count;
    }

    /**
     * This returns all UMU a mech has.
     *
     * @return <code>int</code>Total number of UMUs a mech has.
     */
    public int getAllUMUCount() {
        int count = 0;

        if (!(this instanceof Mech)) {
            return 0;
        }

        if (hasShield() && (getNumberOfShields(MiscType.S_SHIELD_LARGE) > 0)) {
            return 0;
        }

        for (Mounted m : getMisc()) {
            EquipmentType type = m.getType();
            if ((type instanceof MiscType) && type.hasFlag(MiscType.F_UMU)) {
                count++;
            }
        }

        return count;
    }

    /**
     * Does the mech have a functioning ECM unit?
     */
    public boolean hasActiveECM() {
        // no ECM in space unless strat op option enabled
        if (game.getBoard().inSpace() && !game.getOptions().booleanOption("stratops_ecm")) {
            return false;
        }
        if (!isShutDown()) {
            for (Mounted m : getMisc()) {
                EquipmentType type = m.getType();
                // TacOps p. 100 Angle ECM can have 1 ECM and 1 ECCM at the same
                // time
                if ((type instanceof MiscType) && type.hasFlag(MiscType.F_ECM) && (m.curMode().equals("ECM") || m.curMode().equals("ECM & ECCM") || m.curMode().equals("ECM & Ghost Targets"))) {
                    return !(m.isInoperable());
                }
            }
        }
        return false;
    }

    /**
     * Does the mech have a functioning ECM unit?
     */
    public boolean hasActiveAngelECM() {
        // no ECM in space unless strat op option enabled
        if (game.getBoard().inSpace() && !game.getOptions().booleanOption("stratops_ecm")) {
            return false;
        }
        if (game.getOptions().booleanOption("tacops_angel_ecm") && !isShutDown()) {
            for (Mounted m : getMisc()) {
                EquipmentType type = m.getType();
                if ((type instanceof MiscType) && type.hasFlag(MiscType.F_ANGEL_ECM) && m.curMode().equals("ECM")) {
                    return !(m.isInoperable());
                }
            }
        }
        return false;
    }

    /**
     * Does the mech have a functioning ECM unit, tuned to ghost target
     * generation?
     */

    /**
     * Does the mech have a functioning ECM unit, tuned to ghost target
     * generation?
     */
    public boolean hasGhostTargets(boolean active) {
        // no Ghost Targets in space unless strat op option enabled
        if (game.getBoard().inSpace()) {
            return false;
        }

        // if you failed your ghost target PSR, then it doesn't matter
        if ((active && (getGhostTargetRollMoS() < 0)) || isShutDown()) {
            return false;
        }
        boolean hasGhost = false;
        for (Mounted m : getMisc()) {
            EquipmentType type = m.getType();
            // TacOps p. 100 Angle ECM can have ECM/ECCM and Ghost Targets at
            // the same time
            if ((type instanceof MiscType) && type.hasFlag(MiscType.F_ECM) && (m.curMode().equals("Ghost Targets") || m.curMode().equals("ECM & Ghost Targets") || m.curMode().equals("ECCM & Ghost Targets")) && !(m.isInoperable() || getCrew().isUnconscious())) {
                hasGhost = true;
            }
            if ((type instanceof MiscType) && type.hasFlag(MiscType.F_COMMUNICATIONS) && m.curMode().equals("Ghost Targets") && (getTotalCommGearTons() >= 7) && !(m.isInoperable() || getCrew().isUnconscious())) {
                hasGhost = true;
            }
        }
        return hasGhost;
    }

    /**
     * Checks to see if this entity has a functional ECM unit that is using
     * ECCM.
     *
     * @return <code>true</code> if the entity has angelecm and it is in ECCM
     *         mode <code>false</code> if the entity does not have angel ecm or
     *         it is not in eccm mode or it is damaged.
     */
    public boolean hasActiveECCM() {
        // no ECM in space unless strat op option enabled
        if (game.getBoard().inSpace() && !game.getOptions().booleanOption("stratops_ecm")) {
            return false;
        }
        if ((game.getOptions().booleanOption("tacops_eccm") || game.getOptions().booleanOption("stratops_ecm")) && !isShutDown()) {
            for (Mounted m : getMisc()) {
                EquipmentType type = m.getType();
                // TacOps p. 100 Angle ECM can have 1 ECM and 1 ECCM at the same
                // time
                if ((type instanceof MiscType) && ((type.hasFlag(MiscType.F_ECM) && (m.curMode().equals("ECCM") || m.curMode().equals("ECM & ECCM") || m.curMode().equals("ECCM & Ghost Targets"))) || (type.hasFlag(MiscType.F_COMMUNICATIONS) && m.curMode().equals("ECCM")))) {
                    return !m.isInoperable();
                }
            }
        }
        return false;
    }

    /**
     * Checks to see if this unit has a functional AngelECM unit that is using
     * ECCM.
     *
     * @return <code>true</code> if the entity has angelecm and it is in ECCM
     *         mode <code>false</code> if the entity does not have angel ecm or
     *         it is not in eccm mode or it is damaged.
     */
    public boolean hasActiveAngelECCM() {
        if (game.getOptions().booleanOption("tacops_angel_ecm") && game.getOptions().booleanOption("tacops_eccm") && !isShutDown()) {
            for (Mounted m : getMisc()) {
                EquipmentType type = m.getType();
                if ((type instanceof MiscType) && type.hasFlag(MiscType.F_ANGEL_ECM) && m.curMode().equals("ECCM")) {
                    return !(m.isDestroyed() || m.isMissing() || m.isBreached() || isShutDown());
                }
            }
        }
        return false;
    }

    /**
     * What's the range of the ECM equipment? Infantry can have ECM that just
     * covers their own hex.
     *
     * @return the <code>int</code> range of this unit's ECM. This value will be
     *         <code>Entity.NONE</code> if no ECM is active.
     */
    public int getECMRange() {
        // no ECM in space unless strat op option enabled
        if (game.getBoard().inSpace() && !game.getOptions().booleanOption("stratops_ecm")) {
            return Entity.NONE;
        }

        if (!isShutDown()) {
            for (Mounted m : getMisc()) {
                EquipmentType type = m.getType();
                if ((type instanceof MiscType) && type.hasFlag(MiscType.F_ECM) && !m.isInoperable()) {
                    if (BattleArmor.SINGLE_HEX_ECM.equals(type.getInternalName())) {
                        return 0;
                    }
                    if (game.getPlanetaryConditions().hasEMI()) {
                        return 12;
                    }
                    return 6;
                }
            }
        }
        return Entity.NONE;
    }

    /**
     * Does the mech have a functioning BAP? This is just for the basic BAP for
     * Beagle BloodHound WatchDog Clan Active or Light.
     */
    public boolean hasBAP() {
        return hasBAP(true);
    }

    public boolean hasBAP(boolean checkECM) {
        if (((game != null) && game.getPlanetaryConditions().hasEMI()) || isShutDown()) {
            return false;
        }
        for (Mounted m : getMisc()) {
            EquipmentType type = m.getType();
            if ((type instanceof MiscType) && type.hasFlag(MiscType.F_BAP)) {

                if (!m.isInoperable()) {
                    // Beagle Isn't effected by normal ECM
                    if (type.getName().equals("Beagle Active Probe")) {

                        if (checkECM && Compute.isAffectedByAngelECM(this, getPosition(), getPosition())) {
                            return false;
                        }
                        return true;
                    }
                    return !checkECM || !Compute.isAffectedByECM(this, getPosition(), getPosition());
                }
            }
        }
        // check for Manei Domini implants
        if (((crew.getOptions().booleanOption("cyber_eye_im") || crew.getOptions().booleanOption("mm_eye_im")) && (this instanceof Infantry) && !(this instanceof BattleArmor)) || (crew.getOptions().booleanOption("mm_eye_im") && (crew.getOptions().booleanOption("vdni") || crew.getOptions().booleanOption("bvdni")))) {
            return !checkECM || !Compute.isAffectedByECM(this, getPosition(), getPosition());
        }

        return false;
    }

    /**
     * What's the range of the BAP equipment?
     *
     * @return the <code>int</code> range of this unit's BAP. This value will be
     *         <code>Entity.NONE</code> if no BAP is active.
     */
    public int getBAPRange() {
        if (game.getPlanetaryConditions().hasEMI() || isShutDown()) {
            return Entity.NONE;
        }
        // check for Manei Domini implants
        int cyberBonus = 0;
        if (((crew.getOptions().booleanOption("cyber_eye_im") || crew.getOptions().booleanOption("mm_eye_im")) && (this instanceof Infantry) && !(this instanceof BattleArmor)) || (crew.getOptions().booleanOption("mm_eye_im") && (crew.getOptions().booleanOption("vdni") || crew.getOptions().booleanOption("bvdni")))) {
            cyberBonus = 1;
        }

        for (Mounted m : getMisc()) {
            EquipmentType type = m.getType();
            if ((type instanceof MiscType) && type.hasFlag(MiscType.F_BAP) && !m.isInoperable()) {
                // System.err.println("BAP type name: "+m.getName()+"
                // internalName: "+((MiscType)m.getType()).internalName);
                // in space the range of all BAPs is given by the mode
                if (game.getBoard().inSpace()) {
                    if (m.curMode().equals("Medium")) {
                        return 12 + cyberBonus;
                    }
                    return 6 + cyberBonus;
                }

                if (m.getName().equals("Bloodhound Active Probe (THB)") || m.getName().equals(Sensor.BAP)) {
                    return 8 + cyberBonus;
                }
                if ((m.getType()).getInternalName().equals(Sensor.CLAN_AP) || (m.getType()).getInternalName().equals(Sensor.WATCHDOG) || (m.getType().getInternalName().equals(Sensor.CLBALIGHT_AP))) {
                    return 5 + cyberBonus;
                }
                if ((m.getType()).getInternalName().equals(Sensor.LIGHT_AP) || m.getType().getInternalName().equals(Sensor.CLIMPROVED)) {
                    return 3 + cyberBonus;
                }
                if (m.getType().getInternalName().equals(Sensor.ISIMPROVED)) {
                    return 2 + cyberBonus;
                }
                return 4 + cyberBonus;// everthing else should be range 4
            }
        }
        if (cyberBonus > 0) {
            return 2;
        }

        return Entity.NONE;
    }

    /**
     * Returns wether or not this entity has a Targeting Computer.
     */
    public boolean hasTargComp() {
        for (Mounted m : getMisc()) {
            if ((m.getType() instanceof MiscType) && m.getType().hasFlag(MiscType.F_TARGCOMP)) {
                return !m.isInoperable();
            }
        }
        return false;
    }

    /**
     * Returns wether or not this entity has a Targeting Computer that is in
     * aimed shot mode.
     */
    public boolean hasAimModeTargComp() {
        if (hasActiveEiCockpit()) {
            if (this instanceof Mech) {
                if (((Mech) this).getCockpitStatus() == Mech.COCKPIT_AIMED_SHOT) {
                    return true;
                }
            } else {
                return true;
            }
        }
        for (Mounted m : getMisc()) {
            if ((m.getType() instanceof MiscType) && m.getType().hasFlag(MiscType.F_TARGCOMP) && m.curMode().equals("Aimed shot")) {
                return !m.isInoperable();
            }
        }
        return false;
    }

    /**
     * Returns whether this 'mech has a C3 Slave or not.
     */
    public boolean hasC3S() {
        if (isShutDown() || isOffBoard()) {
            return false;
        }
        for (Mounted m : getEquipment()) {
            if ((m.getType() instanceof MiscType) && m.getType().hasFlag(MiscType.F_C3S) && !m.isInoperable()) {
                return true;
            }
        }
        return false;
    }

    /**
     * Only Meks can have CASE II so all other entites return false.
     *
     * @return true iff the mech has CASE II.
     */
    public boolean hasCASEII() {
        return false;
    }

    /**
     * Only Meks have CASE II so all other entites return false.
     *
     * @param location
     * @return true iff the mech has CASE II at this location.
     */
    public boolean hasCASEII(int location) {
        return false;
    }

    /**
     * Checks if the entity has a C3 Master.
     *
     * @return true if it has a working C3M computer and has a master.
     */
    public boolean hasC3M() {
        if (isShutDown() || isOffBoard()) {
            return false;
        }
        for (Mounted m : getEquipment()) {
            if ((m.getType() instanceof WeaponType) && m.getType().hasFlag(WeaponType.F_C3M) && !m.isInoperable()) {
                // If this unit is configured as a company commander,
                // and if this computer is the company master, then
                // this unit does not have a lance master computer.
                if (C3MasterIs(this) && (c3CompanyMasterIndex == getEquipmentNum(m))) {
                    return false;
                }
                return true;
            }
        }
        return false;
    }

    public boolean hasC3MM() {
        if (isShutDown() || isOffBoard()) {
            return false;
        }

        // Have we already determined that there's no company command master?
        if (c3CompanyMasterIndex == LOC_NONE) {
            return false;
        }

        // Do we need to determine that there's no company command master?
        if (c3CompanyMasterIndex == LOC_DESTROYED) {
            Iterator<Mounted> e = getEquipment().iterator();
            while ((c3CompanyMasterIndex == LOC_DESTROYED) && e.hasNext()) {
                Mounted m = e.next();
                if ((m.getType() instanceof WeaponType) && m.getType().hasFlag(WeaponType.F_C3M) && !m.isInoperable()) {
                    // Now look for the company command master.
                    while ((c3CompanyMasterIndex == LOC_DESTROYED) && e.hasNext()) {
                        m = e.next();
                        if ((m.getType() instanceof WeaponType) && m.getType().hasFlag(WeaponType.F_C3M) && !m.isInoperable()) {
                            // Found the comany command master
                            c3CompanyMasterIndex = getEquipmentNum(m);
                        }
                    }
                }
            }
            // If we haven't found the company command master, there is none.
            if (c3CompanyMasterIndex == LOC_DESTROYED) {
                c3CompanyMasterIndex = LOC_NONE;
                return false;
            }
        }

        Mounted m = getEquipment(c3CompanyMasterIndex);
        if (!m.isDestroyed() && !m.isBreached()) {
            return true;
        }
        return false;
    }

    /**
     * Checks if it has any type of C3 computer.
     *
     * @return true iff it has a C3 computer.
     */
    public boolean hasC3() {
        return hasC3S() || hasC3M() || hasC3MM();
    }

    public boolean hasC3i() {
        if (isShutDown() || isOffBoard()) {
            return false;
        }
        for (Mounted m : getEquipment()) {
            if ((m.getType() instanceof MiscType) && m.getType().hasFlag(MiscType.F_C3I) && !m.isInoperable()) {
                return true;
            }
        }
        // check for Manei Domini implants
        if ((this instanceof Infantry) && crew.getOptions().booleanOption("mm_eye_im") && crew.getOptions().booleanOption("boost_comm_implant")) {
            return true;
        }
        return false;
    }

    public String getC3NetId() {
        if (c3NetIdString == null) {
            if (hasC3()) {
                c3NetIdString = "C3." + getId();
            } else if (hasC3i()) {
                c3NetIdString = "C3i." + getId();
            }
        }
        return c3NetIdString;
    }

    public void setC3NetId(Entity e) {
        if (isEnemyOf(e)) {
            return;
        }
        c3NetIdString = e.c3NetIdString;
    }

    /**
     * Determine the remaining number of other C3 Master computers that can
     * connect to this <code>Entity</code>.
     * <p>
     * Please note, if this <code>Entity</code> does not have two C3 Master
     * computers, then it must first be identified as a company commander;
     * otherwise the number of free nodes will be zero.
     *
     * @return a non-negative <code>int</code> value.
     */
    public int calculateFreeC3MNodes() {
        int nodes = 0;
        if (hasC3MM()) {
            nodes = 2;
            if (game != null) {
                for (java.util.Enumeration<Entity> i = game.getEntities(); i.hasMoreElements();) {
                    final Entity e = i.nextElement();
                    if (e.hasC3M() && (e != this)) {
                        final Entity m = e.getC3Master();
                        if (equals(m)) {
                            nodes--;
                        }
                        if (nodes <= 0) {
                            return 0;
                        }
                    }
                }
            }
        } else if (hasC3M() && C3MasterIs(this)) {
            nodes = 3;
            if (game != null) {
                for (java.util.Enumeration<Entity> i = game.getEntities(); i.hasMoreElements();) {
                    final Entity e = i.nextElement();
                    if (e.hasC3() && (e != this)) {
                        final Entity m = e.getC3Master();
                        if (equals(m)) {
                            nodes--;
                        }
                        if (nodes <= 0) {
                            return 0;
                        }
                    }
                }
            }
        }
        return nodes;
    }

    /**
     * Determine the remaining number of other C3 computers that can connect to
     * this <code>Entity</code>.
     * <p>
     * Please note, if this <code>Entity</code> has two C3 Master computers,
     * then this function only returns the remaining number of <b>C3 Slave</b>
     * computers that can connect.
     *
     * @return a non-negative <code>int</code> value.
     */
    public int calculateFreeC3Nodes() {
        int nodes = 0;
        if (hasC3i()) {
            nodes = 5;
            if (game != null) {
                for (Enumeration<Entity> i = game.getEntities(); i.hasMoreElements();) {
                    final Entity e = i.nextElement();
                    if (!equals(e) && onSameC3NetworkAs(e)) {
                        nodes--;
                        if (nodes <= 0) {
                            return 0;
                        }
                    }
                }
            }
        } else if (hasC3M()) {
            nodes = 3;
            if (game != null) {
                for (Enumeration<Entity> i = game.getEntities(); i.hasMoreElements();) {
                    final Entity e = i.nextElement();
                    if (e.hasC3() && !equals(e)) {
                        final Entity m = e.getC3Master();
                        if (equals(m)) {
                            // If this unit is a company commander, and has two
                            // C3 Master computers, only count C3 Slaves here.
                            if (!C3MasterIs(this) || !hasC3MM() || e.hasC3S()) {
                                nodes--;
                            }
                        }
                        if (nodes <= 0) {
                            return 0;
                        }
                    }
                }
            }
        }
        return nodes;
    }

    /**
     * @return the entity "above" this entity in our c3 network, or this entity
     *         itself, if none is above this
     */
    public Entity getC3Top() {
        Entity m = this;
        Entity master = m.getC3Master();
        while ((master != null) && !master.equals(m) && master.hasC3() && !(Compute.isAffectedByECM(m, m.getPosition(), master.getPosition())) && !(Compute.isAffectedByECM(master, master.getPosition(), master.getPosition()))) {
            m = master;
            master = m.getC3Master();
        }
        return m;
    }

    /**
     * Return the unit that is current master of this unit's C3 network. If the
     * master unit has been destroyed or had it's C3 master computer damaged,
     * then this unit is out of the C3 network for the rest of the game. If the
     * master unit has shut down, then this unit may return to the C3 network at
     * a later time.
     *
     * @return the <code>Entity</code> that is the master of this unit's C3
     *         network. This value may be <code>null</code>. If the value master
     *         unit has shut down, then the value will be non-<code>null</code>
     *         after the master unit restarts.
     */
    public Entity getC3Master() {
        if (c3Master == NONE) {
            return null;
        }
        if (hasC3S() && (c3Master > NONE)) {
            // since we can't seem to get the check working in setC3Master(),
            // I'll just do it here, every time. This sucks.
            Entity eMaster = game.getEntity(c3Master);
            // Have we lost our C3Master?
            if (eMaster == null) {
                c3Master = NONE;
            }
            // If our master is shut down, don't clear this slave's setting.
            else if (eMaster.isShutDown()) {
                return null;
            }
            // Slave computers can't connect to single-computer company masters.
            else if (eMaster.C3MasterIs(eMaster) && !eMaster.hasC3MM()) {
                c3Master = NONE;
            }
            // Has our lance master lost its computer?
            else if (!eMaster.hasC3M()) {
                c3Master = NONE;
            }
        } else if (hasC3M() && (c3Master > NONE)) {
            Entity eMaster = game.getEntity(c3Master);
            // Have we lost our C3Master?
            if (eMaster == null) {
                c3Master = NONE;
            }
            // If our master is shut down, don't clear this slave's setting.
            else if (eMaster.isShutDown()) {
                return null;
            }
            // Has our company commander lost his company command computer?
            else if (((eMaster.c3CompanyMasterIndex > LOC_NONE) && !eMaster.hasC3MM()) || ((eMaster.c3CompanyMasterIndex <= LOC_NONE) && !eMaster.hasC3M())) {
                c3Master = NONE;
            }
            // maximum depth of a c3 network is 2 levels.
            else if (eMaster != this) {
                Entity eCompanyMaster = eMaster.getC3Master();
                if ((eCompanyMaster != null) && (eCompanyMaster.getC3Master() != eCompanyMaster)) {
                    c3Master = NONE;
                }
            }
        }
        // If we aren't shut down, and if we don't have a company master
        // computer, but have a C3Master, then we must have lost our network.
        else if (!isShutDown() && !hasC3MM() && (c3Master > NONE)) {
            c3Master = NONE;
        }
        if (c3Master == NONE) {
            return null;
        }
        return game.getEntity(c3Master);
    }

    /**
     * Get the ID of the master unit in this unit's C3 network. If the master
     * unit has shut down, then the ID will still be returned. The only times
     * when the value, <code>Entity.NONE</code> is returned is when this unit is
     * permanently out of the C3 network, or when it was never in a C3 network.
     *
     * @return the <code>int</code> ID of the unit that is the master of this
     *         unit's C3 network, or <code>Entity.NONE</code>.
     */
    public int getC3MasterId() {
        // Make sure that this unit is still on a C3 network.
        // N.B. this call may set this.C3Master to NONE.
        getC3Master();
        return c3Master;
    }

    /**
     * Determines if the passed <code>Entity</code> is the C3 Master of this
     * unit.
     * <p>
     * Please note, that when an <code>Entity</code> is it's own C3 Master, then
     * it is a company commander.
     * <p>
     * Also note that when <code>null</code> is the master for this
     * <code>Entity</code>, then it is an independent master.
     *
     * @param e
     *            - the <code>Entity</code> that may be this unit's C3 Master.
     * @return a <code>boolean</code> that is <code>true</code> when the passed
     *         <code>Entity</code> is this unit's commander. If the passed unit
     *         isn't this unit's commander, this routine returns
     *         <code>false</code>.
     */
    public boolean C3MasterIs(Entity e) {
        if (e == null) {
            if (c3Master == NONE) {
                return true;
            }

            return false; // if this entity has a C3Master then null is not
            // it's master.
        }
        return (e.id == c3Master);
    }

    /**
     * Set another <code>Entity</code> as our C3 Master
     *
     * @param e
     *            - the <code>Entity</code> that should be set as our C3 Master.
     */
    public void setC3Master(Entity e) {
        if (e == null) {
            setC3Master(NONE);
        } else {
            if (isEnemyOf(e)) {
                return;
            }
            setC3Master(e.id);
        }
    }

    /**
     * @param entityId
     */
    public void setC3Master(int entityId) {
        if ((id == entityId) != (id == c3Master)) {
            // this just changed from a company-level to lance-level (or vice
            // versa); have to disconnect all slaved units to maintain
            // integrity.
            for (java.util.Enumeration<Entity> i = game.getEntities(); i.hasMoreElements();) {
                final Entity e = i.nextElement();
                if (e.C3MasterIs(this) && !equals(e)) {
                    e.setC3Master(NONE);
                }
            }
        }
        if (hasC3()) {
            c3Master = entityId;
        }
        if (hasC3() && (entityId == NONE)) {
            c3NetIdString = "C3." + id;
        } else if (hasC3i() && (entityId == NONE)) {
            c3NetIdString = "C3i." + id;
        } else if (hasC3() || hasC3i()) {
            c3NetIdString = game.getEntity(entityId).getC3NetId();
        }
        for (java.util.Enumeration<Entity> i = game.getEntities(); i.hasMoreElements();) {
            final Entity e = i.nextElement();
            if (e.C3MasterIs(this) && !equals(e)) {
                e.c3NetIdString = c3NetIdString;
            }
        }
    }

    public boolean onSameC3NetworkAs(Entity e) {
        return onSameC3NetworkAs(e, false);
    }

    /**
     * Checks if another entity is on the same c3 network as this entity
     *
     * @param e
     *            The <code>Entity</code> to check against this entity
     * @param ignoreECM
     *            a <code>boolean</code> indicating if ECM should be ignored, we
     *            need this for c3i
     * @return a <code>boolean</code> that is <code>true</code> if the given
     *         entity is on the same network, <code>false</code> if not.
     */
    public boolean onSameC3NetworkAs(Entity e, boolean ignoreECM) {
        if (isEnemyOf(e) || isShutDown() || e.isShutDown()) {
            return false;
        }

        // Active Mek Stealth prevents entity from participating in C3.
        // Turn off the stealth, and your back in the network.
        if ((this instanceof Mech) && isStealthActive()) {
            return false;
        }
        if ((e instanceof Mech) && e.isStealthActive()) {
            return false;
        }

        // C3i is easy - if they both have C3i, and their net ID's match,
        // they're on the same network!
        if (hasC3i() && e.hasC3i() && getC3NetId().equals(e.getC3NetId())) {
            if (ignoreECM) {
                return true;
            }
            return !(Compute.isAffectedByECM(e, e.getPosition(), e.getPosition())) && !(Compute.isAffectedByECM(this, getPosition(), getPosition()));
        }

        // simple sanity check - do they both have C3, and are they both on the
        // same network?
        if (!hasC3() || !e.hasC3()) {
            return false;
        }
        if ((getC3Top() == null) || (e.getC3Top() == null)) {
            return false;
        }
        // got the easy part out of the way, now we need to verify that the
        // network isn't down
        return (getC3Top().equals(e.getC3Top()));
    }

    /**
     * Returns whether there is CASE protecting the location.
     */
    public boolean locationHasCase(int loc) {
        for (Mounted mounted : getMisc()) {
            if ((mounted.getLocation() == loc) && mounted.getType().hasFlag(MiscType.F_CASE) && !mounted.isDestroyed()) {
                return true;
            }
        }
        return false;
    }

    /**
     * Hits all criticals of the system occupying the specified critical slot.
     * Used, for example, in a gauss rifle capacitor discharge. Does not apply
     * any special effect of hitting the criticals, like ammo explosion.
     */
    public void hitAllCriticals(int loc, int slot) {
        CriticalSlot orig = getCritical(loc, slot);
        for (int i = 0; i < getNumberOfCriticals(loc); i++) {
            CriticalSlot cs = getCritical(loc, slot);
            if ((cs.getType() == orig.getType()) && (cs.getIndex() == orig.getIndex())) {
                cs.setHit(true);
            }
        }
    }

    /**
     * Start a new round
     *
     * @param roundNumber
     *            the <code>int</code> number of the new round
     */
    public void newRound(int roundNumber) {
        unloadedThisTurn = false;
        done = false;
        delta_distance = 0;
        mpUsedLastRound = mpUsed;
        mpUsed = 0;
        damageThisRound = 0;
        if (assaultDropInProgress == 2) {
            assaultDropInProgress = 0;
        }
        moved = IEntityMovementType.MOVE_NONE;
        gotPavementBonus = false;
        hitThisRoundByAntiTSM = false;
        inReverse = false;
        hitBySwarmsEntity.clear();
        hitBySwarmsWeapon.clear();
        setTaggedBy(-1);
        setLayingMines(false);
        setArmsFlipped(false);
        setDisplacementAttack(null);
        setFindingClub(false);
        setSpotting(false);
        spotTargetId = Entity.NONE;
        setClearingMinefield(false);
        setUnjammingRAC(false);
        crew.setKoThisRound(false);
        m_lNarcedBy |= m_lPendingNarc;
        if (pendingINarcPods.size() > 0) {
            iNarcPods.addAll(pendingINarcPods);
            pendingINarcPods = new ArrayList<INarcPod>();
        }
        if (pendingNarcPods.size() > 0) {
            narcPods.addAll(pendingNarcPods);
            pendingNarcPods.clear();
        }

        for (Mounted m : getEquipment()) {
            m.newRound(roundNumber);
        }

        // reset hexes passed through
        setPassedThrough(new Vector<Coords>());

        resetFiringArcs();

        // reset evasion
        setEvading(false);

        // make sensor checks
        sensorCheck = Compute.d6(2);
        // if the current sensor is BAP and BAP is critted, then switch to the
        // first
        // thing that works
        if ((null != nextSensor) && nextSensor.isBAP() && !hasBAP(false)) {
            for (Sensor sensor : getSensors()) {
                if (!sensor.isBAP()) {
                    nextSensor = sensor;
                    break;
                }
            }
        }
        // change the active sensor, if requested
        if (null != nextSensor) {
            activeSensor = nextSensor;
        }

        // ghost target roll
        ghostTargetRoll = Compute.d6(2);
        ghostTargetOverride = Compute.d6(2);

        // Update the inferno tracker.
        infernos.newRound(roundNumber);
        if (taserShutdownRounds > 0) {
            taserShutdownRounds--;
            if (taserShutdownRounds == 0) {
                shutdownByBATaser = false;
            }
        }
        if (taserInterferenceRounds > 0) {
            taserInterferenceRounds--;
            if (taserInterferenceRounds == 0) {
                taserInterference = 0;
            }
        }
        if (taserFeedBackRounds > 0) {
            taserFeedBackRounds--;
        }
    }

    /**
     * Applies any damage that the entity has suffered. When anything gets hit
     * it is simply marked as "hit" but does not stop working until this is
     * called.
     */
    public void applyDamage() {
        // mark all damaged equipment destroyed and empty
        for (Mounted mounted : getEquipment()) {
            if (mounted.isHit() || mounted.isMissing()) {
                mounted.setShotsLeft(0);
                mounted.setDestroyed(true);
            }
        }

        // destroy criticals that were hit last phase
        for (int i = 0; i < locations(); i++) {
            for (int j = 0; j < getNumberOfCriticals(i); j++) {
                final CriticalSlot cs = getCritical(i, j);
                if (cs != null) {
                    cs.setDestroyed(cs.isDamaged());
                }
            }
        }

        // destroy armor/internals if the section was removed
        for (int i = 0; i < locations(); i++) {
            if (getInternal(i) == IArmorState.ARMOR_DOOMED) {
                setArmor(IArmorState.ARMOR_DESTROYED, i);
                setArmor(IArmorState.ARMOR_DESTROYED, i, true);
                setInternal(IArmorState.ARMOR_DESTROYED, i);
                // destroy any Narc beacons
                for (Iterator<NarcPod> iter = narcPods.iterator(); iter.hasNext();) {
                    NarcPod p = iter.next();
                    if (p.getLocation() == i) {
                        iter.remove();
                    }
                }
                for (Iterator<INarcPod> iter = iNarcPods.iterator(); iter.hasNext();) {
                    INarcPod p = iter.next();
                    if (p.getLocation() == i) {
                        iter.remove();
                    }
                }
                for (Iterator<NarcPod> iter = pendingNarcPods.iterator(); iter.hasNext();) {
                    NarcPod p = iter.next();
                    if (p.getLocation() == i) {
                        iter.remove();
                    }
                }
                for (Iterator<INarcPod> iter = pendingINarcPods.iterator(); iter.hasNext();) {
                    INarcPod p = iter.next();
                    if (p.getLocation() == i) {
                        iter.remove();
                    }
                }
            }
        }
    }

    /**
     * Attempts to reload any empty weapons with the first ammo found
     */
    public void reloadEmptyWeapons() {
        // try to reload weapons
        for (Mounted mounted : getTotalWeaponList()) {
            WeaponType wtype = (WeaponType) mounted.getType();

            if (wtype.getAmmoType() != AmmoType.T_NA) {
                if ((mounted.getLinked() == null) || (mounted.getLinked().getShotsLeft() <= 0) || mounted.getLinked().isDumping()) {
                    loadWeaponWithSameAmmo(mounted);
                }
            }
        }
    }

    /**
     * Assign AMS systems to the most dangerous incoming missile attacks. This
     * should only be called once per turn, or AMS will get extra attacks
     */
    public void assignAMS(Vector<WeaponHandler> vAttacks) {

        HashSet<WeaponAttackAction> targets = new HashSet<WeaponAttackAction>();
        for (Mounted weapon : getWeaponList()) {
            if (weapon.getType().hasFlag(WeaponType.F_AMS)) {
                if (!weapon.isReady() || weapon.isMissing() || weapon.curMode().equals("Off")) {
                    continue;
                }

                // make sure ammo is loaded
                Mounted ammo = weapon.getLinked();
                if (!(weapon.getType().hasFlag(WeaponType.F_ENERGY)) && ((ammo == null) || (ammo.getShotsLeft() == 0) || ammo.isDumping())) {
                    loadWeapon(weapon);
                    ammo = weapon.getLinked();
                }

                // try again
                if (!(weapon.getType().hasFlag(WeaponType.F_ENERGY)) && ((ammo == null) || (ammo.getShotsLeft() == 0) || ammo.isDumping())) {
                    // No ammo for this AMS.
                    continue;
                }

                // make a new vector of only incoming attacks in arc
                Vector<WeaponAttackAction> vAttacksInArc = new Vector<WeaponAttackAction>(vAttacks.size());
                for (WeaponHandler wr : vAttacks) {
                    if (!targets.contains(wr.waa) && Compute.isInArc(game, getId(), getEquipmentNum(weapon), game.getEntity(wr.waa.getEntityId()))) {
                        vAttacksInArc.addElement(wr.waa);
                    }
                }
                // find the most dangerous salvo by expected damage
                WeaponAttackAction waa = Compute.getHighestExpectedDamage(game, vAttacksInArc, true);
                if (waa != null) {
                    waa.addCounterEquipment(weapon);
                    targets.add(waa);
                }
            }
        }
    }

    /**
     * has the team attached a narc pod to me?
     */
    public boolean isNarcedBy(int nTeamID) {
        for (NarcPod p : narcPods) {
            if (p.getTeam() == nTeamID) {
                return true;
            }
        }
        return false;
    }

    /**
     * add a narc pod from this team to the mech. Unremovable
     *
     * @param pod
     *            The <code>NarcPod</code> to be attached.
     */
    public void attachNarcPod(NarcPod pod) {
        pendingNarcPods.add(pod);
    }

    /**
     * attach an iNarcPod
     *
     * @param pod
     *            The <code>INarcPod</code> to be attached.
     */
    public void attachINarcPod(INarcPod pod) {
        pendingINarcPods.add(pod);
    }

    /**
     * Have we been iNarced with a homing pod from that team?
     *
     * @param nTeamID
     *            The id of the team that we are wondering about.
     * @return true if the Entity is narced by that team.
     */
    public boolean isINarcedBy(int nTeamID) {
        for (INarcPod pod : iNarcPods) {
            if ((pod.getTeam() == nTeamID) && (pod.getType() == INarcPod.HOMING)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Have we been iNarced with the named pod from any team?
     *
     * @param type
     *            the <code>int</code> type of iNarc pod.
     * @return <code>true</code> if we have.
     */
    public boolean isINarcedWith(long type) {
        for (INarcPod pod : iNarcPods) {
            if (pod.getType() == type) {
                return true;
            }
        }
        return false;
    }

    /**
     * Remove all attached iNarc Pods
     */
    public void removeAllINarcPods() {
        iNarcPods.clear();
    }

    /**
     * Do we have any iNarc Pods attached?
     *
     * @return true iff one or more iNarcPods are attached.
     */
    public boolean hasINarcPodsAttached() {
        if (iNarcPods.size() > 0) {
            return true;
        }
        return false;
    }

    /**
     * Get an <code>Enumeration</code> of <code>INarcPod</code>s that are
     * attached to this entity.
     *
     * @return an <code>Enumeration</code> of <code>INarcPod</code>s.
     */
    public Iterator<INarcPod> getINarcPodsAttached() {
        return iNarcPods.iterator();
    }

    /**
     * Remove an <code>INarcPod</code> from this entity.
     *
     * @param pod
     *            the <code>INarcPod</code> to be removed.
     * @return <code>true</code> if the pod was removed, <code>false</code> if
     *         the pod was not attached to this entity.
     */
    public boolean removeINarcPod(INarcPod pod) {
        return iNarcPods.remove(pod);
    }

    /**
     * Calculates the battle value of this entity
     */
    public abstract int calculateBattleValue();

    /**
     * Calculates the battle value of this mech. If the parameter is true, then
     * the battle value for c3 will be added whether the mech is currently part
     * of a network or not. This should be overwritten if necessary
     *
     * @param ignoreC3
     *            if the contribution of the C3 computer should be ignored when
     *            calculating BV.
     * @param ignorePilot
     *            if the extra BV due to piloting skill should be ignore, needed
     *            for c3 bv
     */
    public int calculateBattleValue(boolean ignoreC3, boolean ignorePilot) {
        return calculateBattleValue();
    }

    /**
     * Generates a vector containing reports on all useful information about
     * this entity.
     */
    public abstract Vector<Report> victoryReport();

    /**
     * Two entities are equal if their ids are equal
     */
    @Override
    public boolean equals(Object object) {
        if (this == object) {
            return true;
        } else if ((object == null) || (getClass() != object.getClass())) {
            return false;
        }
        Entity other = (Entity) object;
        return other.getId() == id;
    }

    /**
     * Get the movement mode of the entity
     */
    public int getMovementMode() {
        return movementMode;
    }

    /**
     * Get the movement mode of the entity as a String.
     */
    public String getMovementModeAsString() {
        switch (getMovementMode()) {
        case IEntityMovementMode.NONE:
            return "None";
        case IEntityMovementMode.BIPED:
        case IEntityMovementMode.BIPED_SWIM:
            return "Biped";
        case IEntityMovementMode.QUAD:
        case IEntityMovementMode.QUAD_SWIM:
            return "Quad";
        case IEntityMovementMode.TRACKED:
            return "Tracked";
        case IEntityMovementMode.WHEELED:
            return "Wheeled";
        case IEntityMovementMode.HOVER:
            return "Hover";
        case IEntityMovementMode.VTOL:
            return "VTOL";
        case IEntityMovementMode.NAVAL:
            return "Naval";
        case IEntityMovementMode.HYDROFOIL:
            return "Hydrofoil";
        case IEntityMovementMode.SUBMARINE:
        case IEntityMovementMode.INF_UMU:
            return "Submarine";
        case IEntityMovementMode.INF_LEG:
            return "Leg";
        case IEntityMovementMode.INF_MOTORIZED:
            return "Motorized";
        case IEntityMovementMode.INF_JUMP:
            return "Jump";
        case IEntityMovementMode.WIGE:
            return "WiGE";
        case IEntityMovementMode.AERODYNE:
            return "Aerodyne";
        case IEntityMovementMode.SPHEROID:
            return "Spheroid";
        default:
            return "ERROR";
        }
    }

    /**
     * Set the movement type of the entity
     */
    public void setMovementMode(int movementMode) {
        this.movementMode = movementMode;
    }

    /**
     * Helper function to determine if a entity is a biped
     */
    public boolean entityIsBiped() {
        return (getMovementMode() == IEntityMovementMode.BIPED);
    }

    /**
     * Helper function to determine if a entity is a quad
     */
    public boolean entityIsQuad() {
        return (getMovementMode() == IEntityMovementMode.QUAD);
    }

    /**
     * Returns true is the entity needs a roll to stand up
     */
    public boolean needsRollToStand() {
        return true;
    }

    /**
     * Returns an entity's base piloting skill roll needed Only use this version
     * if the entity is through processing movement
     */
    public PilotingRollData getBasePilotingRoll() {
        return getBasePilotingRoll(moved);
    }

    /**
     * Returns an entity's base piloting skill roll needed
     */
    public PilotingRollData getBasePilotingRoll(int moveType) {
        final int entityId = getId();

        PilotingRollData roll;

        // Pilot dead?
        if (getCrew().isDead() || getCrew().isDoomed() || (getCrew().getHits() >= 6)) {
            // Following line switched from impossible to automatic failure
            // -- bug fix for dead units taking PSRs
            return new PilotingRollData(entityId, TargetRoll.AUTOMATIC_FAIL, "Pilot dead");
        }
        // pilot awake?
        else if (!getCrew().isActive()) {
            return new PilotingRollData(entityId, TargetRoll.IMPOSSIBLE, "Pilot unconscious");
        }
        // gyro operational?
        if ((getBadCriticals(CriticalSlot.TYPE_SYSTEM, Mech.SYSTEM_GYRO, Mech.LOC_CT) > 1) && (getGyroType() != Mech.GYRO_HEAVY_DUTY)) {
            return new PilotingRollData(entityId, TargetRoll.AUTOMATIC_FAIL, getCrew().getPiloting() + 6, "Gyro destroyed");
        }

        // Takes 3+ hits to kill an HD Gyro.
        if ((getBadCriticals(CriticalSlot.TYPE_SYSTEM, Mech.SYSTEM_GYRO, Mech.LOC_CT) > 2) && (getGyroType() == Mech.GYRO_HEAVY_DUTY)) {
            return new PilotingRollData(entityId, TargetRoll.AUTOMATIC_FAIL, getCrew().getPiloting() + 6, "Gyro destroyed");
        }

        // both legs present?
        if (this instanceof BipedMech) {
            if (((BipedMech) this).countBadLegs() == 2) {
                return new PilotingRollData(entityId, TargetRoll.AUTOMATIC_FAIL, getCrew().getPiloting() + 10, "Both legs destroyed");
            }
        } else if (this instanceof QuadMech) {
            if (((QuadMech) this).countBadLegs() >= 3) {
                return new PilotingRollData(entityId, TargetRoll.AUTOMATIC_FAIL, getCrew().getPiloting() + ((Mech) this).countBadLegs() * 5, ((Mech) this).countBadLegs() + " legs destroyed");
            }
        }
        // entity shut down?
        if (isShutDown() && isShutDownThisPhase()) {
            return new PilotingRollData(entityId, TargetRoll.AUTOMATIC_FAIL, getCrew().getPiloting() + 3, "Reactor shut down");
        } else if (isShutDown()) {
            return new PilotingRollData(entityId, TargetRoll.AUTOMATIC_FAIL, TargetRoll.IMPOSSIBLE, "Reactor shut down");
        }

        // okay, let's figure out the stuff then
        roll = new PilotingRollData(entityId, getCrew().getPiloting(), "Base piloting skill");

        // Let's see if we have a modifier to our piloting skill roll. We'll
        // pass in the roll
        // object and adjust as necessary
        roll = addEntityBonuses(roll);

        // add planetary condition modifiers
        roll = addConditionBonuses(roll, moveType);

        if (isCarefulStand()) {
            roll.addModifier(-2, "careful stand");
        }

        if (game.getOptions().booleanOption("tacops_fatigue") && crew.isPilotingFatigued(game.getRoundCount())) {
            roll.addModifier(1, "fatigue");
        }

        if (taserInterference > 0) {
            roll.addModifier(taserInterference, "taser interference");
        }

        return roll;
    }

    /**
     * Add in any piloting skill mods
     */
    public abstract PilotingRollData addEntityBonuses(PilotingRollData roll);

    /**
     * Add in any modifiers due to global conditions like light/weather/etc.
     */
    public PilotingRollData addConditionBonuses(PilotingRollData roll, int moveType) {

        PlanetaryConditions conditions = game.getPlanetaryConditions();
        // check light conditions for "running" entities
        if ((moveType == IEntityMovementType.MOVE_RUN) || (moveType == IEntityMovementType.MOVE_VTOL_RUN) || (moveType == IEntityMovementType.MOVE_OVER_THRUST)) {
            int lightPenalty = conditions.getLightPilotPenalty();
            if (lightPenalty > 0) {
                roll.addModifier(lightPenalty, conditions.getLightCurrentName());
            }
        }

        // check weather conditions for all entities
        int weatherMod = conditions.getWeatherPilotPenalty();
        if ((weatherMod != 0) && !game.getBoard().inSpace()) {
            roll.addModifier(weatherMod, conditions.getWeatherCurrentName());
        }

        // check wind conditions for all entities
        int windMod = conditions.getWindPilotPenalty(this);
        if ((windMod != 0) && !game.getBoard().inSpace()) {
            roll.addModifier(windMod, conditions.getWindCurrentName());
        }

        // check gravity conditions for all entities
        int gravMod = conditions.getGravityPilotPenalty();
        if ((gravMod != 0) && !game.getBoard().inSpace()) {
            roll.addModifier(gravMod, "high/low gravity");
        }
        return roll;

    }

    /**
     * Checks if the entity is getting up. If so, returns the target roll for
     * the piloting skill check.
     */
    public PilotingRollData checkGetUp(MoveStep step) {

        if ((step == null) || ((step.getType() != MovePath.STEP_GET_UP) && (step.getType() != MovePath.STEP_CAREFUL_STAND))) {
            return new PilotingRollData(id, TargetRoll.CHECK_FALSE, "Check false: Entity is not attempting to get up.");
        }

        PilotingRollData roll = getBasePilotingRoll(step.getParent().getLastStepMovementType());

        if (this instanceof BipedMech) {
            if ((((Mech) this).countBadLegs() >= 1) && (isLocationBad(Mech.LOC_LARM) && isLocationBad(Mech.LOC_RARM))) {
                roll.addModifier(TargetRoll.IMPOSSIBLE, "can't get up with destroyed leg and arms");
                return roll;
            }
        }

        if (isHullDown() && (this instanceof QuadMech)) {
            roll.addModifier(TargetRoll.AUTOMATIC_SUCCESS, "getting up from hull down");
            return roll;
        }

        if (!needsRollToStand() && (getBadCriticals(CriticalSlot.TYPE_SYSTEM, Mech.SYSTEM_GYRO, Mech.LOC_CT) < 2)) {
            roll.addModifier(TargetRoll.AUTOMATIC_SUCCESS, "\n" + getDisplayName() + " does not need to make a piloting skill check to stand up because it has all four of its legs.");
            return roll;
        }

        // append the reason modifier
        roll.append(new PilotingRollData(getId(), 0, "getting up"));
        addPilotingModifierForTerrain(roll, step);
        return roll;
    }

    /**
     * Checks if the entity is attempting to run with damage that would force a
     * PSR. If so, returns the target roll for the piloting skill check.
     */
    public PilotingRollData checkRunningWithDamage(int overallMoveType) {
        PilotingRollData roll = getBasePilotingRoll(overallMoveType);

        int gyroDamage = getBadCriticals(CriticalSlot.TYPE_SYSTEM, Mech.SYSTEM_GYRO, Mech.LOC_CT);
        if (getGyroType() == Mech.GYRO_HEAVY_DUTY) {
            gyroDamage--; // HD gyro ignores 1st damage
        }
        if ((overallMoveType == IEntityMovementType.MOVE_RUN) && !isProne() && ((gyroDamage > 0) || hasHipCrit())) {
            // append the reason modifier
            roll.append(new PilotingRollData(getId(), 0, "running with damaged hip actuator or gyro"));
        } else {
            roll.addModifier(TargetRoll.CHECK_FALSE, "Check false: Entity is not attempting to run with damage");
        }
        addPilotingModifierForTerrain(roll);
        return roll;
    }

    /**
     * Checks if an entity is passing through certain terrain while not moving
     * carefully
     */
    public PilotingRollData checkRecklessMove(MoveStep step, IHex curHex, Coords lastPos, Coords curPos, IHex prevHex) {
        PilotingRollData roll = getBasePilotingRoll(step.getParent().getLastStepMovementType());
        // no need to go further if movement is careful
        if (step.getParent().isCareful()) {
            roll.addModifier(TargetRoll.CHECK_FALSE, "moving carefully");
            return roll;
        }

        // this only applies in fog, night conditions, or if a hex along the
        // move path has ice
        boolean isFoggy = game.getPlanetaryConditions().getFog() != PlanetaryConditions.FOG_NONE;
        boolean isDark = game.getPlanetaryConditions().getLight() > PlanetaryConditions.L_DUSK;

        // if we are jumping, then no worries
        if (step.getMovementType() == IEntityMovementType.MOVE_JUMP) {
            roll.addModifier(TargetRoll.CHECK_FALSE, "jumping is not reckless?");
            return roll;
        }

        // we need to make this check on the first move forward and anytime the
        // hex is not clear
        // or is a level change
        if ((isFoggy || isDark) && !lastPos.equals(curPos) && lastPos.equals(step.getParent().getEntity().getPosition())) {
            roll.append(new PilotingRollData(getId(), 0, "moving recklessly"));
        }
        // TODO: how do you tell if it is clear?
        // FIXME: no perfect solution in the current code. I will use movement costs
        else if ((isFoggy || isDark) && !lastPos.equals(curPos)
                && ((curHex.movementCost(step.getParent().getLastStepMovementType()) > 0)
                        || ((null != prevHex) && (prevHex.getElevation() != curHex.getElevation())))) {
            roll.append(new PilotingRollData(getId(), 0, "moving recklessly"));
            // ice conditions
        } else if (curHex.containsTerrain(Terrains.ICE)) {
            roll.append(new PilotingRollData(getId(), 0, "moving recklessly"));
        } else {
            roll.addModifier(TargetRoll.CHECK_FALSE, "not moving recklessly");
        }

        return roll;
    }

    /**
     * Checks if the entity is landing (from a jump) with damage that would
     * force a PSR. If so, returns the target roll for the piloting skill check.
     */
    public PilotingRollData checkLandingWithDamage(int overallMoveType) {
        PilotingRollData roll = getBasePilotingRoll(overallMoveType);

        if (((getBadCriticals(CriticalSlot.TYPE_SYSTEM, Mech.SYSTEM_GYRO, Mech.LOC_CT) > 0) && (getGyroType() != Mech.GYRO_HEAVY_DUTY)) || ((getBadCriticals(CriticalSlot.TYPE_SYSTEM, Mech.SYSTEM_GYRO, Mech.LOC_CT) > 1) && (getGyroType() == Mech.GYRO_HEAVY_DUTY)) || hasLegActuatorCrit()) {
            // append the reason modifier
            roll.append(new PilotingRollData(getId(), 0, "landing with damaged leg actuator or gyro"));
            addPilotingModifierForTerrain(roll);
        } else {
            roll.addModifier(TargetRoll.CHECK_FALSE, "Entity does not have gyro or leg accutator damage -- checking for purposes of determining PSR after jump.");
        }

        return roll;
    }

    /**
     * Checks if the entity is landing (from a jump) on ice-covered water.
     */
    public PilotingRollData checkLandingOnIce(int overallMoveType, IHex curHex) {
        PilotingRollData roll = getBasePilotingRoll(overallMoveType);

        if (curHex.containsTerrain(Terrains.ICE) && (curHex.terrainLevel(Terrains.WATER) > 0)) {
            roll.append(new PilotingRollData(getId(), 0, "landing on ice-covered water"));
            addPilotingModifierForTerrain(roll);
        } else {
            roll.addModifier(TargetRoll.CHECK_FALSE, "hex is not covered by ice");
        }

        return roll;
    }

    public PilotingRollData checkMovedTooFast(MoveStep step) {
        PilotingRollData roll = getBasePilotingRoll(step.getParent().getLastStepMovementType());
        addPilotingModifierForTerrain(roll, step);
        switch (step.getMovementType()) {
        case IEntityMovementType.MOVE_WALK:
        case IEntityMovementType.MOVE_RUN:
        case IEntityMovementType.MOVE_VTOL_WALK:
        case IEntityMovementType.MOVE_VTOL_RUN:
            if (step.getMpUsed() > (int) Math.ceil(getOriginalWalkMP() * 1.5)) {
                roll.append(new PilotingRollData(getId(), 0, "used more MPs than at 1G possible"));
            } else {
                roll.addModifier(TargetRoll.CHECK_FALSE, "Check false: Entity did not use more MPs walking/running than possible at 1G");
            }
            break;
        case IEntityMovementType.MOVE_JUMP:
            if (step.getMpUsed() > getJumpMP(false)) {
                roll.append(new PilotingRollData(getId(), 0, "used more MPs than at 1G possible"));
            } else {
                roll.addModifier(TargetRoll.CHECK_FALSE, "Check false: Entity did not use more MPs jumping than possible at 1G");
            }
            break;
        }
        return roll;
    }

    /**
     * Checks if the entity might skid on pavement. If so, returns the target
     * roll for the piloting skill check.
     */
    public PilotingRollData checkSkid(int moveType, IHex prevHex, int overallMoveType, MoveStep prevStep, int prevFacing, int curFacing, Coords lastPos, Coords curPos, boolean isInfantry, int distance) {
        PilotingRollData roll = getBasePilotingRoll(overallMoveType);
        addPilotingModifierForTerrain(roll, lastPos);

        // TODO: add check for elevation of pavement, road,
        // or bridge matches entity elevation.
        if ((moveType != IEntityMovementType.MOVE_JUMP) && (prevHex != null)
                /*
                 * Bug 754610: Revert fix for bug 702735. && (
                 * prevHex.contains(Terrain.PAVEMENT) || prevHex.contains(Terrain.ROAD)
                 * || prevHex.contains(Terrain.BRIDGE) )
                 */
                && ((prevStep.isPavementStep() && (overallMoveType == IEntityMovementType.MOVE_RUN) && (movementMode != IEntityMovementMode.HOVER) && (movementMode != IEntityMovementMode.WIGE)) || (prevHex.containsTerrain(Terrains.ICE) && (movementMode != IEntityMovementMode.HOVER) && (movementMode != IEntityMovementMode.WIGE)) || (((movementMode == IEntityMovementMode.HOVER) || (movementMode == IEntityMovementMode.WIGE)) && ((game.getPlanetaryConditions().getWeather() == PlanetaryConditions.WE_HEAVY_SNOW) || (game.getPlanetaryConditions().getWindStrength() >= PlanetaryConditions.WI_STORM)))) && (prevFacing != curFacing) && !lastPos.equals(curPos) && !isInfantry
                // Bug 912127, a unit that just got up and changed facing
                // on pavement in that getting up does not skid.
                && !prevStep.isHasJustStood()) {
            // append the reason modifier
            if (prevStep.isPavementStep() && !prevHex.containsTerrain(Terrains.ICE)) {
                if (this instanceof Mech) {
                    roll.append(new PilotingRollData(getId(), getMovementBeforeSkidPSRModifier(distance), "running & turning on pavement"));
                } else {
                    roll.append(new PilotingRollData(getId(), getMovementBeforeSkidPSRModifier(distance), "reckless driving on pavement"));
                }
            } else {
                roll.append(new PilotingRollData(getId(), getMovementBeforeSkidPSRModifier(distance), "turning on ice"));
            }
        } else {
            roll.addModifier(TargetRoll.CHECK_FALSE, "Check false: Entity is not apparently skidding");
        }

        return roll;
    }

    /**
     * Checks if the entity is moving into rubble. If so, returns the target
     * roll for the piloting skill check.
     */
    public PilotingRollData checkRubbleMove(MoveStep step, IHex curHex, Coords lastPos, Coords curPos) {
        PilotingRollData roll = getBasePilotingRoll(step.getParent().getLastStepMovementType());
        addPilotingModifierForTerrain(roll, curPos);

        if (!lastPos.equals(curPos) && (step.getMovementType() != IEntityMovementType.MOVE_JUMP) && (curHex.terrainLevel(Terrains.RUBBLE) > 0) && (this instanceof Mech)) {
            int mod = 0;
            if (curHex.terrainLevel(Terrains.RUBBLE) > 5) {
                mod++;
            }
            // append the reason modifier
            roll.append(new PilotingRollData(getId(), mod, "entering Rubble"));
        } else {
            roll.addModifier(TargetRoll.CHECK_FALSE, "Check false: Entity is not entering rubble");
        }

        return roll;
    }

    /**
     * Checks if the entity is moving into a hex that might cause it to bog
     * down. If so, returns the target roll for the piloting skill check.
     */
    public PilotingRollData checkBogDown(MoveStep step, IHex curHex, Coords lastPos, Coords curPos, int lastElev, boolean isPavementStep) {
        PilotingRollData roll = getBasePilotingRoll(step.getParent().getLastStepMovementType());
        int bgMod = curHex.getBogDownModifier(getMovementMode(), this instanceof LargeSupportTank);
        if ((!lastPos.equals(curPos) || (step.getElevation() != lastElev)) && (bgMod != TargetRoll.AUTOMATIC_SUCCESS) && (step.getMovementType() != IEntityMovementType.MOVE_JUMP) && (step.getElevation() == 0) && !isPavementStep) {
            roll.append(new PilotingRollData(getId(), bgMod, "avoid bogging down"));
        } else {
            roll.addModifier(TargetRoll.CHECK_FALSE, "Check false: Not entering bog-down terrain, or jumping/hovering over such terrain");
        }
        return roll;
    }

    /**
     * Checks if the entity is moving into depth 1+ water. If so, returns the
     * target roll for the piloting skill check.
     */
    public PilotingRollData checkWaterMove(MoveStep step, IHex curHex, Coords lastPos, Coords curPos, boolean isPavementStep) {
        if ((curHex.terrainLevel(Terrains.WATER) > 0) && (step.getElevation() < 0) && !lastPos.equals(curPos) && (step.getMovementType() != IEntityMovementType.MOVE_JUMP) && (getMovementMode() != IEntityMovementMode.HOVER) && (getMovementMode() != IEntityMovementMode.VTOL) && (getMovementMode() != IEntityMovementMode.NAVAL) && (getMovementMode() != IEntityMovementMode.HYDROFOIL) && (getMovementMode() != IEntityMovementMode.SUBMARINE) && (getMovementMode() != IEntityMovementMode.INF_UMU) && (getMovementMode() != IEntityMovementMode.BIPED_SWIM) && (getMovementMode() != IEntityMovementMode.QUAD_SWIM) && (getMovementMode() != IEntityMovementMode.WIGE) && !isPavementStep) {
            return checkWaterMove(curHex.terrainLevel(Terrains.WATER), step.getParent().getLastStepMovementType());
        }
        return checkWaterMove(0, step.getParent().getLastStepMovementType());
    }

    /**
     * Checks if the entity is moving into depth 1+ water. If so, returns the
     * target roll for the piloting skill check.
     */
    public PilotingRollData checkWaterMove(int waterLevel, int overallMoveType) {
        PilotingRollData roll = getBasePilotingRoll(overallMoveType);

        int mod;
        if (waterLevel == 1) {
            mod = -1;
        } else if (waterLevel == 2) {
            mod = 0;
        } else {
            mod = 1;
        }

        if ((waterLevel > 0) && !hasUMU()) {
            // append the reason modifier
            roll.append(new PilotingRollData(getId(), mod, "entering Depth " + waterLevel + " Water"));
        } else {
            roll.addModifier(TargetRoll.CHECK_FALSE, "Check false: No water here.");
        }

        return roll;
    }

    /**
     * Checks if the entity is being swarmed. If so, returns the target roll for
     * the piloting skill check to dislodge them.
     */
    public PilotingRollData checkDislodgeSwarmers(MoveStep step) {

        // If we're not being swarmed, return CHECK_FALSE
        if (Entity.NONE == getSwarmAttackerId()) {
            return new PilotingRollData(getId(), TargetRoll.CHECK_FALSE, "Check false: No swarmers attached");
        }

        // append the reason modifier
        PilotingRollData roll = getBasePilotingRoll(step.getParent().getLastStepMovementType());
        roll.append(new PilotingRollData(getId(), 0, "attempting to dislodge swarmers by dropping prone"));
        addPilotingModifierForTerrain(roll, step);

        return roll;
    }

    /**
     * Checks to see if an entity is moving through building walls. Note: this
     * method returns true/false, unlike the other checkStuff() methods above.
     *
     * @return 0, no eligable building; 1, exiting; 2, entering; 3, both; 4,
     *         stepping on roof
     */
    public int checkMovementInBuilding(MoveStep step, MoveStep prevStep, Coords curPos, Coords prevPos) {
        if (prevPos.equals(curPos)) {
            return 0;
        }
        IHex curHex = game.getBoard().getHex(curPos);
        IHex prevHex = game.getBoard().getHex(prevPos);
        // ineligable because of movement type or unit type
        if ((this instanceof Infantry) && (step.getMovementType() != IEntityMovementType.MOVE_JUMP)) {
            return 0;
        }

        if ((this instanceof Protomech) && (prevStep != null) && (prevStep.getMovementType() == IEntityMovementType.MOVE_JUMP)) {
            return 0;
        }

        int rv = 0;
        // check current hex for building
        if (step.getElevation() < curHex.terrainLevel(Terrains.BLDG_ELEV)) {
            rv += 2;
        } else if (((step.getElevation() == curHex.terrainLevel(Terrains.BLDG_ELEV)) || (step.getElevation() == curHex.terrainLevel(Terrains.BRIDGE_ELEV))) && (step.getMovementType() != IEntityMovementType.MOVE_JUMP)) {
            rv += 4;
        }
        // check previous hex for building
        if (prevHex != null) {
            int prevEl = getElevation();
            if (prevStep != null) {
                prevEl = prevStep.getElevation();
            }
            if (prevEl < prevHex.terrainLevel(Terrains.BLDG_ELEV)) {
                rv += 1;
            }
        }

        // check to see if its a wall
        if (rv > 1) {
            Building bldgEntered = null;
            bldgEntered = game.getBoard().getBuildingAt(curPos);
            if (bldgEntered.getType() == Building.WALL) {
                return 4;
            }
        }

        if ((this instanceof Infantry) || (this instanceof Protomech)) {
            if (rv != 2) {
                rv = 0;
            }
        }
        return rv;
    }

    /**
     * Calculates and returns the roll for an entity moving in buildings.
     */
    public PilotingRollData rollMovementInBuilding(Building bldg, int distance, String why, int overallMoveType) {
        PilotingRollData roll = getBasePilotingRoll(overallMoveType);

        int mod = 0;
        String desc;

        if (why == "") {
            desc = "moving through ";
        } else {
            desc = why + " ";
        }

        switch (bldg.getType()) {
        case Building.LIGHT:
            desc = "Light";
            break;
        case Building.MEDIUM:
            mod = 1;
            desc = "Medium";
            break;
        case Building.HEAVY:
            mod = 2;
            desc = "Heavy";
            break;
        case Building.HARDENED:
            mod = 5;
            desc = "Hardened";
            break;
        case Building.WALL:
            mod = 12;
            desc = "";
            break;
        }

        // append the reason modifier
        roll.append(new PilotingRollData(getId(), mod, "moving through " + desc + " " + bldg.getName()));

        // Modify the roll by the distance moved so far.
        if (distance >= 25) {
            roll.addModifier(6, "moved 25+ hexes");
        } else if (distance >= 18) {
            roll.addModifier(5, "moved 18-24 hexes");
        } else if (distance >= 10) {
            roll.addModifier(4, "moved 10+ hexes");
        } else if (distance >= 7) {
            roll.addModifier(3, "moved 7-9 hexes");
        } else if (distance >= 5) {
            roll.addModifier(2, "moved 5-6 hexes");
        } else if (distance >= 3) {
            roll.addModifier(1, "moved 3-4 hexes");
        }

        return roll;
    }

    /**
     * Calculate the piloting skill roll modifier, based upon the number of
     * hexes moved this phase. Used for skidding.
     */
    public int getMovementBeforeSkidPSRModifier(int distance) {
        int mod = -1;

        if (distance > 24) {
            mod = 6;
        } else if (distance > 17) {
            mod = 5;
        } else if (distance > 10) {
            mod = 4;
        } else if (distance > 7) {
            mod = 2;
        } else if (distance > 4) {
            mod = 1;
        } else if (distance > 2) {
            mod = 0;
        } else {
            // 0-2 hexes
            mod = -1;
        }

        if (getCrew().getOptions().booleanOption("maneuvering_ace")) {
            mod--;
        }

        return mod;
    }

    /**
     * The maximum elevation change the entity can cross
     */
    public abstract int getMaxElevationChange();

    /**
     * by default, entities can move as far down as they can move up
     */
    public int getMaxElevationDown() {
        return getMaxElevationChange();
    }

    /**
     * Get a list of the class names of the <code>Transporter</code>s for the
     * given <code>Entity</code>.
     * <p/>
     * This method should <strong>only</strong> be used when serializing the
     * <code>Entity</code>.
     *
     * @param entity
     *            - the <code>Entity</code> being serialized.
     * @return a <code>String</code> listing the <code>Transporter</code> class
     *         names of the <code>Entity</code>. This value will be empty ("")
     *         if the entity has no <code>Transporter</code>s.
     */
    public static String encodeTransporters(Entity entity) {
        StringBuffer buffer = new StringBuffer();
        Enumeration<Transporter> iter = entity.transports.elements();
        boolean isFirst = true;
        while (iter.hasMoreElements()) {

            // Every entry after the first gets a leading comma.
            if (isFirst) {
                isFirst = false;
            } else {
                buffer.append(',');
            }

            // Add the next Transporter's class name.
            Transporter transporter = iter.nextElement();
            buffer.append(transporter.getClass().getName());

            // If this is a TroopSpace transporter, get it's capacity.
            if (transporter instanceof TroopSpace) {
                buffer.append("=").append(((TroopSpace) transporter).totalSpace);
            }
        }
        return buffer.toString();
    }

    /**
     * Set the <code>Transporter</code>s for the given <code>Entity</code>.
     * <p/>
     * This method should <strong>only</strong> be used when deserializing the
     * <code>Entity</code>.
     *
     * @param entity
     *            - the <code>Entity</code> being deserialized.
     * @param transporters
     *            - the <code>String</code> listing the class names of the
     *            <code>Transporter</code>s to be set for the
     *            <code>Entity</code>, separated by commas. This value can be
     *            <code>null</code> or empty ("").
     * @throws IllegalStateException
     *             if any error occurs.
     */
    public static void decodeTransporters(Entity entity, String transporters) throws IllegalStateException {

        // Split the string on the commas, and add transporters to the Entity.
        Enumeration<String> iter = StringUtil.splitString(transporters, ",").elements();
        while (iter.hasMoreElements()) {
            try {
                String name = iter.nextElement();
                Class<?> transporter = Class.forName(name);
                Object object = null;
                if (TroopSpace.class.getName().equals(name)) {
                    // Get the tonnage of space.
                    int tonnage = Integer.parseInt(name.substring(name.lastIndexOf("=")));
                    object = new TroopSpace(tonnage);
                } else {
                    object = transporter.newInstance();
                }
                entity.addTransporter((Transporter) object);
            } catch (Exception err) {
                err.printStackTrace();
                throw new IllegalStateException(err.getMessage());
            }

        } // Handle the next transporter

    }

    /**
     * Add a transportation component to this Entity. Please note, this method
     * should only be called during this entity's construction.
     *
     * @param component
     *            - One of this new entity's <code>Transporter</code>s.
     */
    public void addTransporter(Transporter component) {
        // Add later transporters to the *head* of the list
        // to make Fa Shih's use their magnetic clamps by default.
        transports.insertElementAt(component, 0);
    }

    /**
     * Remove all transportation components from this Entity. Should probably
     * only be called during construction.
     */
    public void removeAllTransporters() {
        transports = new Vector<Transporter>();
    }

    /**
     * Determines if this object can accept the given unit. The unit may not be
     * of the appropriate type or there may be no room for the unit.
     *
     * @param unit
     *            - the <code>Entity</code> to be loaded.
     * @return <code>true</code> if the unit can be loaded, <code>false</code>
     *         otherwise.
     */
    public boolean canLoad(Entity unit) {
        // For now, if it's infantry, it can't load anything.
        // Period!
        if (this instanceof Infantry) {
            return false;
        }

        // one can only load one's own team's units!
        if (!unit.isEnemyOf(this)) {
            // Walk through this entity's transport components;
            // if one of them can load the unit, we can.
            Enumeration<Transporter> iter = transports.elements();
            while (iter.hasMoreElements()) {
                Transporter next = iter.nextElement();
                if (next.canLoad(unit) && (unit.getElevation() == getElevation())) {
                    return true;
                }
            }
        }

        // If we got here, none of our transports can carry the unit.
        return false;
    }

    /**
     * Load the given unit.
     *
     * @param unit
     *            - the <code>Entity</code> to be loaded.
     * @throws IllegalArgumentException
     *             If the unit can't be loaded
     */
    public void load(Entity unit) {
        // Walk through this entity's transport components;
        // find the one that can load the unit.
        // Stop looking after the first match.
        Enumeration<Transporter> iter = transports.elements();
        while (iter.hasMoreElements()) {
            Transporter next = iter.nextElement();
            if (next.canLoad(unit) && (unit.getElevation() == getElevation())) {
                next.load(unit);
                return;
            }
        }

        // If we got to this point, then we can't load the unit.
        throw new IllegalArgumentException(getShortName() + " can not load " + unit.getShortName());
    }

    /**
     * Recover the given unit. Only for ASF and Small Craft
     *
     * @param unit
     *            - the <code>Entity</code> to be loaded.
     * @throws IllegalArgumentException
     *             If the unit can't be loaded
     */
    public void recover(Entity unit) {
        // Walk through this entity's transport components;
        // find the one that can load the unit.
        // Stop looking after the first match.
        Enumeration<Transporter> iter = transports.elements();
        while (iter.hasMoreElements()) {
            Transporter next = iter.nextElement();
            if (next.canLoad(unit) && (unit.getElevation() == getElevation())) {
                if (next instanceof ASFBay) {
                    ((ASFBay) next).recover(unit);
                    return;
                }
                if (next instanceof SmallCraftBay) {
                    ((SmallCraftBay) next).recover(unit);
                    return;
                }
            }
        }

        // If we got to this point, then we can't load the unit.
        throw new IllegalArgumentException(getShortName() + " can not recover " + unit.getShortName());
    }

    /**
     * cycle through and update Bays
     */
    public void updateBays() {
        Enumeration<Transporter> iter = transports.elements();
        while (iter.hasMoreElements()) {
            Transporter next = iter.nextElement();
            if (next instanceof ASFBay) {
                ASFBay nextBay = (ASFBay) next;
                nextBay.updateSlots();
            }
        }
    }

    /**
     * Damages a randomly determined bay door on the entity, if one exists
     *
     */
    public String damageBayDoor() {

        String bayType = "none";

        Vector<Bay> potential;
        potential = new Vector<Bay>();

        Enumeration<Transporter> iter = transports.elements();
        while (iter.hasMoreElements()) {
            Transporter next = iter.nextElement();
            if (next instanceof Bay) {
                Bay nextBay = (Bay) next;
                if (nextBay.getDoors() > 0) {
                    potential.add(nextBay);
                }
            }
        }

        if (potential.size() > 0) {
            Bay chosenBay = potential.elementAt(Compute.randomInt(potential.size()));
            chosenBay.destroyDoor();
            chosenBay.resetDoors();
            chosenBay.setDoors(chosenBay.getDoors() - 1);
            bayType = chosenBay.getType();
        }

        return bayType;
    }

    /**
     * damage the door of the first bay that can load this unit
     */
    public void damageDoorRecovery(Entity en) {
        Enumeration<Transporter> iter = transports.elements();
        while (iter.hasMoreElements()) {
            Transporter next = iter.nextElement();
            if ((next instanceof ASFBay) && next.canLoad(en)) {
                ((ASFBay) next).destroyDoor();
                break;
            }
            if ((next instanceof SmallCraftBay) && next.canLoad(en)) {
                ((SmallCraftBay) next).destroyDoor();
                break;
            }

        }
    }

    /**
     * Damages a randomly determined docking collar on the entity, if one exists
     *
     */
    public boolean damageDockCollar() {

        boolean result = false;

        Vector<DockingCollar> potential;
        potential = new Vector<DockingCollar>();

        Enumeration<Transporter> iter = transports.elements();
        while (iter.hasMoreElements()) {
            Transporter next = iter.nextElement();
            if (next instanceof DockingCollar) {
                DockingCollar nextDC = (DockingCollar) next;
                if (!nextDC.isDamaged()) {
                    potential.add(nextDC);
                }
            }
        }

        if (potential.size() > 0) {
            DockingCollar chosenDC = potential.elementAt(Compute.randomInt(potential.size()));
            chosenDC.setDamaged(true);
            result = true;
        }

        return result;
    }

    public void pickUp(MechWarrior mw) {
        pickedUpMechWarriors.addElement(new Integer(mw.getId()));
    }

    /**
     * Get a <code>List</code> of the units currently loaded into this payload.
     *
     * @return A <code>List</code> of loaded <code>Entity</code> units. This
     *         list will never be <code>null</code>, but it may be empty. The
     *         returned <code>List</code> is independant from the under- lying
     *         data structure; modifying one does not affect the other.
     */
    public Vector<Entity> getLoadedUnits() {
        Vector<Entity> result = new Vector<Entity>();

        // Walk through this entity's transport components;
        // add all of their lists to ours.
        for (Transporter next : transports) {
            for (Entity e : next.getLoadedUnits()) {
                result.addElement(e);
            }
        }

        // Return the list.
        return result;
    }

    /**
     * @return the number of docking collars
     */
    public int getDocks() {
        int n = 0;

        for (Transporter next : transports) {
            if (next instanceof DockingCollar) {
                n++;
            }
        }

        // Return the number
        return n;

    }

    /**
     * only entities in Bays (for cargo damage to Aero units
     *
     * @return
     */
    public Vector<Entity> getBayLoadedUnits() {
        Vector<Entity> result = new Vector<Entity>();

        // Walk through this entity's transport components;
        // add all of their lists to ours.
        for (Transporter next : transports) {
            if (next instanceof Bay) {
                for (Entity e : next.getLoadedUnits()) {
                    result.addElement(e);
                }
            }
        }

        // Return the list.
        return result;
    }

    /**
     * @return only entities in ASF Bays
     */
    public Vector<Entity> getLoadedFighters() {
        Vector<Entity> result = new Vector<Entity>();

        // Walk through this entity's transport components;
        // add all of their lists to ours.

        // I should only add entities in bays that are functional
        for (Transporter next : transports) {
            if ((next instanceof ASFBay) && (((ASFBay) next).getDoors() > 0)) {
                for (Entity e : next.getLoadedUnits()) {
                    result.addElement(e);
                }
            }
        }

        // Return the list.
        return result;
    }

    /**
     * @return only entities in ASF Bays that can be launched (i.e. not in
     *         recovery)
     */
    public Vector<Entity> getLaunchableFighters() {
        Vector<Entity> result = new Vector<Entity>();

        // Walk through this entity's transport components;
        // add all of their lists to ours.

        // I should only add entities in bays that are functional
        for (Transporter next : transports) {
            if ((next instanceof ASFBay) && (((ASFBay) next).getDoors() > 0)) {
                Bay nextbay = (Bay) next;
                for (Entity e : nextbay.getLaunchableUnits()) {
                    result.addElement(e);
                }
            }
        }

        // Return the list.
        return result;
    }

    public Bay getLoadedBay(int bayID) {

        Vector<Bay> bays = getFighterBays();
        for (int nbay = 0; nbay < bays.size(); nbay++) {
            Bay currentBay = bays.elementAt(nbay);
            Vector<Entity> currentFighters = currentBay.getLoadedUnits();
            for (int nfighter = 0; nfighter < currentFighters.size(); nfighter++) {
                Entity fighter = currentFighters.elementAt(nfighter);
                if (fighter.getId() == bayID) {
                    // then we are in the right bay
                    return currentBay;
                }
            }
        }

        return null;

    }

    /**
     * @return get the bays separately
     */
    public Vector<Bay> getFighterBays() {
        Vector<Bay> result = new Vector<Bay>();

        for (Transporter next : transports) {
            if (((next instanceof ASFBay) || (next instanceof SmallCraftBay)) && (((Bay) next).getDoors() > 0)) {
                result.addElement((Bay) next);
            }
        }

        // Return the list.
        return result;

    }

    public Vector<Bay> getTransportBays() {

        Vector<Bay> result = new Vector<Bay>();

        for (Transporter next : transports) {
            if (next instanceof Bay) {
                result.addElement((Bay) next);
            }
        }

        // Return the list.
        return result;
    }

    /**
     * do any damage to bay doors
     */
    public void resetBayDoors() {

        for (Transporter next : transports) {
            if (next instanceof Bay) {
                ((Bay) next).resetDoors();
            }
        }

    }

    /**
     * @return the launch rate for fighters
     */
    public int getFighterLaunchRate() {
        int result = 0;

        // Walk through this entity's transport components;
        for (Transporter next : transports) {
            if (next instanceof ASFBay) {
                result += 2 * ((ASFBay) next).getDoors();
            }
        }
        // Return the number.
        return result;
    }

    public Vector<Entity> getLoadedSmallCraft() {
        Vector<Entity> result = new Vector<Entity>();

        // Walk through this entity's transport components;
        // add all of their lists to ours.
        for (Transporter next : transports) {
            if ((next instanceof SmallCraftBay) && (((SmallCraftBay) next).getDoors() > 0)) {
                for (Entity e : next.getLoadedUnits()) {
                    result.addElement(e);
                }
            }
        }

        // Return the list.
        return result;
    }

    public Vector<Entity> getLaunchableSmallCraft() {
        Vector<Entity> result = new Vector<Entity>();

        // Walk through this entity's transport components;
        // add all of their lists to ours.
        for (Transporter next : transports) {
            if ((next instanceof SmallCraftBay) && (((SmallCraftBay) next).getDoors() > 0)) {
                Bay nextbay = (Bay) next;
                for (Entity e : nextbay.getLaunchableUnits()) {
                    result.addElement(e);
                }
            }
        }

        // Return the list.
        return result;
    }

    /**
     * get the bays separately
     */
    public Vector<SmallCraftBay> getSmallCraftBays() {
        Vector<SmallCraftBay> result = new Vector<SmallCraftBay>();

        for (Transporter next : transports) {
            if ((next instanceof SmallCraftBay) && (((SmallCraftBay) next).getDoors() > 0)) {
                result.addElement((SmallCraftBay) next);
            }
        }

        // Return the list.
        return result;

    }

    /**
     * @return launch rate for Small Craft
     */
    public int getSmallCraftLaunchRate() {
        int result = 0;

        // Walk through this entity's transport components;
        for (Transporter next : transports) {
            if (next instanceof SmallCraftBay) {
                result += 2 * ((SmallCraftBay) next).getDoors();
            }
        }
        // Return the number.
        return result;
    }

    /**
     * Unload the given unit.
     *
     * @param unit
     *            - the <code>Entity</code> to be unloaded.
     * @return <code>true</code> if the unit was contained in this space,
     *         <code>false</code> otherwise.
     */
    public boolean unload(Entity unit) {
        // Walk through this entity's transport components;
        // try to remove the unit from each in turn.
        // Stop after the first match.
        Enumeration<Transporter> iter = transports.elements();
        while (iter.hasMoreElements()) {
            Transporter next = iter.nextElement();
            if (next.unload(unit)) {
                return true;
            }
        }

        // If we got here, none of our transports currently carry the unit.
        return false;
    }

    /**
     * Return a string that identifies the unused capacity of this transporter.
     *
     * @return A <code>String</code> meant for a human.
     */
    public String getUnusedString() {
        StringBuffer result = new StringBuffer();

        // Walk through this entity's transport components;
        // add all of their string to ours.
        Enumeration<Transporter> iter = transports.elements();
        while (iter.hasMoreElements()) {
            Transporter next = iter.nextElement();
            result.append(next.getUnusedString());
            // Add a newline character between strings.
            if (iter.hasMoreElements()) {
                result.append('\n');
            }
        }

        // Return the String.
        return result.toString();
    }

    /**
     * Determine if transported units prevent a weapon in the given location
     * from firing.
     *
     * @param loc
     *            - the <code>int</code> location attempting to fire.
     * @param isRear
     *            - a <code>boolean</code> value stating if the given location
     *            is rear facing; if <code>false</code>, the location is front
     *            facing.
     * @return <code>true</code> if a transported unit is in the way,
     *         <code>false</code> if the weapon can fire.
     */
    public boolean isWeaponBlockedAt(int loc, boolean isRear) {
        // Walk through this entity's transport components;
        // check each for blockage in turn.
        // Stop after the first match.
        for (Transporter next : transports) {
            if (next.isWeaponBlockedAt(loc, isRear)) {
                return true;
            }
        }

        // If we got here, none of our transports
        // carry a blocking unit at that location.
        return false;
    }

    /**
     * If a unit is being transported on the outside of the transporter, it can
     * suffer damage when the transporter is hit by an attack. Currently, no
     * more than one unit can be at any single location; that same unit can be
     * "spread" over multiple locations.
     *
     * @param loc
     *            - the <code>int</code> location hit by attack.
     * @param isRear
     *            - a <code>boolean</code> value stating if the given location
     *            is rear facing; if <code>false</code>, the location is front
     *            facing.
     * @return The <code>Entity</code> being transported on the outside at that
     *         location. This value will be <code>null</code> if no unit is
     *         transported on the outside at that location.
     */
    public Entity getExteriorUnitAt(int loc, boolean isRear) {
        // Walk through this entity's transport components;
        // check each for an exterior unit in turn.
        // Stop after the first match.
        for (Transporter next : transports) {
            Entity exterior = next.getExteriorUnitAt(loc, isRear);
            if (null != exterior) {
                return exterior;
            }
        }

        // If we got here, none of our transports
        // carry an exterior unit at that location.
        return null;
    }

    public ArrayList<Entity> getExternalUnits() {
        ArrayList<Entity> rv = new ArrayList<Entity>();
        for (Transporter t : transports) {
            rv.addAll(t.getExternalUnits());
        }
        return rv;
    }

    public int getCargoMpReduction() {
        int rv = 0;
        for (Transporter t : transports) {
            rv += t.getCargoMpReduction();
        }
        return rv;
    }

    public HitData getTrooperAtLocation(HitData hit, Entity transport) {
        return rollHitLocation(ToHitData.HIT_NORMAL, ToHitData.SIDE_FRONT);
    }

    /**
     * Record the ID of the <code>Entity</code> that has loaded this unit. A
     * unit that is unloaded can neither move nor attack for the rest of the
     * turn.
     *
     * @param transportId
     *            - the <code>int</code> ID of our transport. The ID is
     *            <b>not</b> validated. This value should be
     *            <code>Entity.NONE</code> if this unit has been unloaded.
     */
    public void setTransportId(int transportId) {
        conveyance = transportId;
        // If we were unloaded, set the appropriate flags.
        if (transportId == Entity.NONE) {
            unloadedThisTurn = true;
            done = true;
        }
    }

    /**
     * Get the ID <code>Entity</code> that has loaded this one.
     *
     * @return the <code>int</code> ID of our transport. The ID may be invalid.
     *         This value should be <code>Entity.NONE</code> if this unit has
     *         not been loaded.
     */
    public int getTransportId() {
        return conveyance;
    }

    /**
     * Determine if this unit has an active and working stealth system.
     * <p/>
     * Sub-classes are encouraged to override this method.
     *
     * @return <code>true</code> if this unit has a stealth system that is
     *         currently active and it's actually working,
     *         <code>false</code> if there is no stealth
     *         system or if it is inactive.
     */
    public boolean isStealthActive() {
        return false;
    }

    /**
     * Determine if this unit has an active and working stealth system.
     * <p/>
     * Sub-classes are encouraged to override this method.
     *
     * @return <code>true</code> if this unit has a stealth system that is
     *         currently active and it's actually working,
     *         <code>false</code> if there is no stealth
     *         system or if it is inactive.
     */
    public boolean isStealthOn() {
        return false;
    }

    /**
     * Determine if this unit has an active null-signature system.
     * <p/>
     * Sub-classes are encouraged to override this method.
     *
     * @return <code>true</code> if this unit has a null signature system that
     *         is currently active, <code>false</code> if there is no stealth
     *         system or if it is inactive.
     */
    public boolean isNullSigActive() {
        return false;
    }

    /**
     * Determine if this unit has an active void signature system.
     * <p/>
     * Sub-classes are encouraged to override this method.
     *
     * @return <code>true</code> if this unit has a void signature system that
     *         is currently active, <code>false</code> if there is no stealth
     *         system or if it is inactive.
     */
    public boolean isVoidSigActive() {
        return false;
    }

    /**
     * Determine if this unit has an active chameleon light polarization field.
     * <p/>
     * Sub-classes are encouraged to override this method.
     *
     * @return <code>true</code> if this unit has a void signature system that
     *         is currently active, <code>false</code> if there is no stealth
     *         system or if it is inactive.
     */
    public boolean isChameleonShieldActive() {
        return false;
    }

    /**
     * Determine the stealth modifier for firing at this unit from the given
     * range. If the value supplied for <code>range</code> is not one of the
     * <code>Entity</code> class range constants, an
     * <code>IllegalArgumentException</code> will be thrown.
     * <p/>
     * Sub-classes are encouraged to override this method.
     *
     * @param range
     *            - an <code>int</code> value that must match one of the
     *            <code>Compute</code> class range constants.
     * @param ae
     *            - the entity making the attack, who maybe immune to certain
     *            kinds of stealth
     * @return a <code>TargetRoll</code> value that contains the stealth
     *         modifier for the given range.
     */
    public TargetRoll getStealthModifier(int range, Entity ae) {
        TargetRoll result = null;

        // Stealth must be active.
        if (!isStealthActive()) {
            result = new TargetRoll(0, "stealth not active");
        }

        // Get the range modifier.
        switch (range) {
        case RangeType.RANGE_MINIMUM:
        case RangeType.RANGE_SHORT:
        case RangeType.RANGE_MEDIUM:
        case RangeType.RANGE_LONG:
        case RangeType.RANGE_EXTREME:
            result = new TargetRoll(0, "stealth not installed");
            break;
        default:
            throw new IllegalArgumentException("Unknown range constant: " + range);
        }

        // Return the result.
        return result;
    }

    /**
     * Record the ID of the <code>Entity</code> that is the current target of a
     * swarm attack by this unit. A unit that stops swarming can neither move
     * nor attack for the rest of the turn.
     *
     * @param id
     *            - the <code>int</code> ID of the swarm attack's target. The ID
     *            is <b>not</b> validated. This value should be
     *            <code>Entity.NONE</code> if this unit has stopped swarming.
     */
    public void setSwarmTargetId(int id) {
        swarmTargetId = id;
        // This entity can neither move nor attack for the rest of this turn.
        if (id == Entity.NONE) {
            unloadedThisTurn = true;
            done = true;
        }
    }

    /**
     * Get the ID of the <code>Entity</code> that is the current target of a
     * swarm attack by this unit.
     *
     * @return the <code>int</code> ID of the swarm attack's target The ID may
     *         be invalid. This value should be <code>Entity.NONE</code> if this
     *         unit is not swarming.
     */
    public int getSwarmTargetId() {
        return swarmTargetId;
    }

    /**
     * Record the ID of the <code>Entity</code> that is attacking this unit with
     * a swarm attack.
     *
     * @param id
     *            - the <code>int</code> ID of the swarm attack's attacker. The
     *            ID is <b>not</b> validated. This value should be
     *            <code>Entity.NONE</code> if the swarm attack has ended.
     */
    public void setSwarmAttackerId(int id) {
        swarmAttackerId = id;
    }

    /**
     * Get the ID of the <code>Entity</code> that is attacking this unit with a
     * swarm attack.
     *
     * @return the <code>int</code> ID of the swarm attack's attacker The ID may
     *         be invalid. This value should be <code>Entity.NONE</code> if this
     *         unit is not being swarmed.
     */
    public int getSwarmAttackerId() {
        return swarmAttackerId;
    }

    /**
     * Scans through the ammo on the unit for any inferno rounds.
     *
     * @return <code>true</code> if the unit is still loaded with Inferno
     *         rounds. <code>false</code> if no rounds were ever loaded or if
     *         they have all been fired.
     */
    public boolean hasInfernoAmmo() {
        boolean found = false;

        // Walk through the unit's ammo, stop when we find a match.
        for (Mounted amounted : getAmmo()) {
            AmmoType atype = (AmmoType) amounted.getType();
            if (((atype.getAmmoType() == AmmoType.T_SRM) || (atype.getAmmoType() == AmmoType.T_MML)) && (atype.getMunitionType() == AmmoType.M_INFERNO) && (amounted.getShotsLeft() > 0)) {
                found = true;
            }
        }
        return found;
    }

    /**
     * Record if the unit is just combat-lossed or if it has been utterly
     * destroyed.
     *
     * @param canSalvage
     *            - a <code>boolean</code> that is <code>true</code> if the unit
     *            can be repaired (given time and parts); if this value is
     *            <code>false</code>, the unit is utterly destroyed.
     */
    public void setSalvage(boolean canSalvage) {
        // Unsalvageable entities aren't in retreat or salvageable.
        if (!canSalvage) {
            setRemovalCondition(IEntityRemovalConditions.REMOVE_DEVASTATED);
        }
        salvageable = canSalvage;
    }

    /**
     * Determine if the unit is just combat-lossed or if it has been utterly
     * destroyed.
     *
     * @return A <code>boolean</code> that is <code>true</code> if the unit has
     *         salvageable components; if this value is <code>false</code> the
     *         unit is utterly destroyed.
     * @see #isRepairable()
     */
    public boolean isSalvage() {
        return salvageable;
    }

    /**
     * Determine if the unit can be repaired, or only harvested for spares.
     *
     * @return A <code>boolean</code> that is <code>true</code> if the unit can
     *         be repaired (given enough time and parts); if this value is
     *         <code>false</code>, the unit is only a source of spares.
     * @see #isSalvage()
     */
    public boolean isRepairable() {
        return isSalvage();
    }

    /**
     * Getter for property removalCondition.
     *
     * @return Value of property removalCondition.
     */
    public int getRemovalCondition() {
        return removalCondition;
    }

    /**
     * Setter for property removalCondition.
     *
     * @param removalCondition
     *            New value of property removalCondition.
     */
    public void setRemovalCondition(int removalCondition) {
        // Don't replace a removal condition with a lesser condition.
        if (this.removalCondition < removalCondition) {
            this.removalCondition = removalCondition;
        }
    }

    /**
     * @return whether this entity is clearing a minefield.
     */
    public boolean isClearingMinefield() {
        return clearingMinefield;
    }

    /**
     * @param clearingMinefield
     */
    public void setClearingMinefield(boolean clearingMinefield) {
        this.clearingMinefield = clearingMinefield;
    }

    /**
     * @return whether this entity is spotting this round.
     */
    public boolean isSpotting() {
        return spotting;
    }

    /**
     * @param spotting
     */
    public void setSpotting(boolean spotting) {
        this.spotting = spotting;
    }

    /**
     * Um, basically everything can spot for LRM indirect fire.
     *
     * @return true, if the entity is active
     */
    public boolean canSpot() {
        if (game.getOptions().booleanOption("pilots_cannot_spot") && (this instanceof MechWarrior)) {
            return false;
        }
        return isActive() && !isOffBoard();
    }

    @Override
    public String toString() {
        return "Entity [" + getDisplayName() + ", " + getId() + "]";
    }

    /**
     * This returns a textual description of the entity for visualy impaired
     * users.
     */
    public String statusToString() {
        // should include additional information like imobile.
        String str = "Entity [" + getDisplayName() + ", " + getId() + "]: ";
        if (getPosition() != null) {
            str = str + "Location: (" + (getPosition().x + 1) + ", " + (getPosition().y + 1) + ") ";
        }
        str = str + "Owner: " + owner.getName() + " Armor: " + getTotalArmor() + "/" + getTotalOArmor() + " Internal Structure: " + getTotalInternal() + "/" + getTotalOInternal();

        if (!isActive()) {
            str += " Inactive";
        }
        if (isImmobile()) {
            str += " Immobile";
        }
        if (isProne()) {
            str += " Prone";
        }
        if (isDone()) {
            str += " Done";
        }

        return str;
    }

    /**
     * This returns a textual description of a specific location of the entity
     * for visualy impaired users.
     *
     * @param loc
     *            the location
     * @return a string descibing the status of the location.
     */
    public String statusToString(int loc) {
        if (loc == LOC_NONE) {
            return "No location given.";
        }

        return getLocationName(loc) + " (" + getLocationAbbr(loc) + "): Armor: " + getArmorString(loc) + "/" + getOArmor(loc) + " Structure: " + getInternalString(loc) + "/" + getOInternal(loc);
    }

    /**
     * @param string
     *            a string defining the location
     * @return the status of the given location.
     */
    public String statusToString(String str) {
        int loc = LOC_NONE;
        loc = getLocationFromAbbr(str);

        if (loc == LOC_NONE) {
            try {
                loc = Integer.parseInt(str);
            } catch (NumberFormatException nfe) {
                loc = LOC_NONE;
            }
        }

        return statusToString(loc);
    }

    /**
     * The round the unit will be deployed. We will deploy at the end of a
     * round. So if depoyRound is set to 5, we will deploy when round 5 is over.
     * Any value of zero or less is automatically set to 1
     *
     * @param deployRound
     *            an int
     */
    public void setDeployRound(int deployRound) {
        this.deployRound = deployRound;
    }

    /**
     * The round the unit will be deployed
     *
     * @return an int
     */
    public int getDeployRound() {
        return deployRound;
    }

    /**
     * Toggles if an entity has been deployed
     */
    public void setDeployed(boolean deployed) {
        this.deployed = deployed;
    }

    /**
     * Checks to see if an entity has been deployed
     */
    public boolean isDeployed() {
        return deployed;
    }

    /**
     * Returns true if the entity should be deployed
     */
    public boolean shouldDeploy(int round) {
        return (!deployed && (getDeployRound() <= round) && !isOffBoard());
    }

    /**
     * Set the unit number for this entity.
     *
     * @param unit
     *            the <code>char</code> number for the low-level unit that this
     *            entity belongs to. This entity can be removed from its unit by
     *            passing the value, <code>(char) Entity.NONE</code>.
     */
    public void setUnitNumber(char unit) {
        unitNumber = unit;
    }

    /**
     * Get the unit number of this entity.
     *
     * @return the <code>char</code> unit number. If the entity does not belong
     *         to a unit, <code>(char) Entity.NONE</code> will be returned.
     */
    public char getUnitNumber() {
        return unitNumber;
    }

    /**
     * Returns whether an entity can flee from its current position. Currently
     * returns true if the entity is on the edge of the board.
     */
    public boolean canFlee() {
        Coords pos = getPosition();
        return (pos != null) && (getWalkMP() > 0) && !isProne() && !isStuck() && !isShutDown() && !getCrew().isUnconscious() && ((pos.x == 0) || (pos.x == game.getBoard().getWidth() - 1) || (pos.y == 0) || (pos.y == game.getBoard().getHeight() - 1));
    }

    public void setSeenByEnemy(boolean b) {
        seenByEnemy = b;
    }

    public boolean isSeenByEnemy() {
        return seenByEnemy;
    }

    public void setVisibleToEnemy(boolean b) {
        visibleToEnemy = b;
    }

    public boolean isVisibleToEnemy() {
        return visibleToEnemy;
    }

    protected int applyGravityEffectsOnMP(int MP) {
        int result = MP;
        if (game != null) {
            float fMP = MP / game.getPlanetaryConditions().getGravity();
            fMP = (Math.abs((Math.round(fMP) - fMP)) == 0.5) ? (float) Math.floor(fMP) : Math.round(fMP); // the
            // rule
            // requires
            // rounding down on .5
            result = (int) fMP;
        }
        return result;
    }

    /** Whether this type of unit can perform charges */
    public boolean canCharge() {
        return !isImmobile() && (getWalkMP() > 0) && !isStuck() && !isProne();
    }

    /** Whether this type of unit can perform DFA attacks */
    public boolean canDFA() {
        return !isImmobile() && (getJumpMP() > 0) && !isStuck() && !isProne();
    }

    /** Whether this type of unit can perform Ramming attacks */
    public boolean canRam() {
        return false;
    }

    boolean isUsingManAce() {
        return getCrew().getOptions().booleanOption("maneuvering_ace");
    }

    public Enumeration<Entity> getKills() {
        final int killer = id;
        return game.getSelectedOutOfGameEntities(new EntitySelector() {
            public boolean accept(Entity entity) {
                if (killer == entity.killerId) {
                    return true;
                }
                return false;
            }
        });
    }

    public int getKillNumber() {
        final int killer = id;
        return game.getSelectedOutOfGameEntityCount(new EntitySelector() {
            public boolean accept(Entity entity) {
                if (killer == entity.killerId) {
                    return true;
                }
                return false;
            }
        });
    }

    public void addKill(Entity kill) {
        kill.killerId = id;
    }

    public boolean getGaveKillCredit() {
        return killerId != Entity.NONE;
    }

    /**
     * Determines if an entity is eligible for a phase.
     */
    public boolean isEligibleFor(IGame.Phase phase) {
        // only deploy in deployment phase
        if ((phase == IGame.Phase.PHASE_DEPLOYMENT) == isDeployed()) {
            return false;
        }

        // carcass can't do anything
        if (isCarcass()) {
            return false;
        }

        switch (phase) {
        case PHASE_MOVEMENT:
            return isEligibleForMovement();
        case PHASE_FIRING:
            return isEligibleForFiring();
        case PHASE_PHYSICAL:
            return isEligibleForPhysical();
        case PHASE_TARGETING:
            return isEligibleForTargetingPhase();
        case PHASE_OFFBOARD:
            return isEligibleForOffboard();
        default:
            return true;
        }
    }

    /**
     * Determines if an entity is eligible for a phase. Called only if at least
     * one entity returned true to isEligibleFor() This is for using
     * searchlights in physical&offboard phase, without forcing the phase to be
     * played when not needed. However it could be used for other things in the
     * future
     */
    public boolean canAssist(IGame.Phase phase) {
        if ((phase != IGame.Phase.PHASE_PHYSICAL) && (phase != IGame.Phase.PHASE_FIRING) && (phase != IGame.Phase.PHASE_OFFBOARD)) {
            return false;
        }
        // if you're charging or finding a club, it's already declared
        if (isUnjammingRAC() || isCharging() || isMakingDfa() || isRamming() || isFindingClub() || isOffBoard()) {
            return false;
        }
        // must be active
        if (!isActive()) {
            return false;
        }
        // If we have a searchlight, we can use it to assist
        if (isUsingSpotlight()) {
            return true;
        }
        return false;
    }

    /**
     * An entity is eligible if its to-hit number is anything but impossible.
     * This is only really an issue if friendly fire is turned off.
     */
    public boolean isEligibleForFiring() {
        // if you're charging, no shooting
        if (isUnjammingRAC() || isCharging() || isMakingDfa() || isRamming()) {
            return false;
        }

        // if you're offboard, no shooting
        if (isOffBoard() || isAssaultDropInProgress()) {
            return false;
        }

        // check game options
        if (!game.getOptions().booleanOption("skip_ineligable_firing")) {
            return true;
        }

        // must be active
        if (!isActive()) {
            return false;
        }

        // TODO: check for any weapon attacks

        return true;
    }

    /**
     * Pretty much anybody's eligible for movement. If the game option is
     * toggled on, inactive and immobile entities are not eligible. OffBoard
     * units are always ineligible
     *
     * @return whether or not the entity is allowed to move
     */
    public boolean isEligibleForMovement() {
        // check if entity is offboard
        if (isOffBoard() || isAssaultDropInProgress()) {
            return false;
        }
        // check game options
        if (!game.getOptions().booleanOption("skip_ineligable_movement")) {
            return true;
        }

        // must be active
        if (!isActive() || isImmobile()) {
            return false;
        }

        return true;
    }

    public boolean isEligibleForOffboard() {

        // if you're charging, no shooting
        if (isUnjammingRAC() || isCharging() || isMakingDfa()) {
            return false;
        }

        // if you're offboard, no shooting
        if (isOffBoard() || isAssaultDropInProgress()) {
            return false;
        }
        for (Mounted mounted : getWeaponList()) {
            WeaponType wtype = (WeaponType) mounted.getType();
            if (wtype.hasFlag(WeaponType.F_TAG) && mounted.isReady()) {
                return true;
            }
        }
        return false;// only things w/ tag are
    }

    public boolean isAttackingThisTurn() {
        Vector<EntityAction> actions = game.getActionsVector();
        for (EntityAction ea : actions) {
            if ((ea.getEntityId() == getId()) && (ea instanceof AbstractAttackAction)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Check if the entity has any valid targets for physical attacks.
     */
    public boolean isEligibleForPhysical() {
        boolean canHit = false;
        boolean friendlyFire = game.getOptions().booleanOption("friendly_fire");

        // only mechs and protos have physical attacks (except tank charges)
        if (!((this instanceof Mech) || (this instanceof Protomech) || (this instanceof Infantry))) {
            return false;
        }

        // if you're charging or finding a club, it's already declared
        if (isUnjammingRAC() || isCharging() || isMakingDfa() || isFindingClub() || isOffBoard() || isAssaultDropInProgress()) {
            return false;
        }

        // check game options
        if (game.getOptions().booleanOption("no_clan_physical") && isClan() && !hasINarcPodsAttached() && (getSwarmAttackerId() == NONE)) {
            return false;
        }

        // Issue with Vibroblades only being turned on/off during Physical phase
        // -- Torren
        if (hasVibroblades()) {
            return true;
        }

        if (!game.getOptions().booleanOption("skip_ineligable_physical")) {
            return true;
        }

        // dead mek walking
        if (!isActive()) {
            return false;
        }

        if (getPosition() == null) {
            return false; // not on board?
        }

        // check if we have iNarc pods attached that can be brushed off
        if (hasINarcPodsAttached() && (this instanceof Mech)) {
            return true;
        }

        // Try to find a valid entity target.
        Enumeration<Entity> e = game.getEntities();
        while (!canHit && e.hasMoreElements()) {
            Entity target = e.nextElement();

            // don't shoot at friendlies unless you are into that sort of thing
            // and do not shoot yourself even then
            if (!(isEnemyOf(target) || (friendlyFire && (getId() != target.getId())))) {
                continue;
            }

            // No physical attack works at distances > 1.
            if ((target.getPosition() == null) || (getPosition().distance(target.getPosition()) > 1)) {
                continue;
            }

            canHit |= Compute.canPhysicalTarget(game, getId(), target);
            // check if we can dodge and target can attack us,
            // then we are eligible.
            canHit |= ((this instanceof Mech) && !isProne() && getCrew().getOptions().booleanOption("dodge_maneuver") && Compute.canPhysicalTarget(game, target.getId(), this));
        }

        // If there are no valid Entity targets, check for add valid buildings.
        Enumeration<Building> bldgs = game.getBoard().getBuildings();
        while (!canHit && bldgs.hasMoreElements()) {
            final Building bldg = bldgs.nextElement();

            // Walk through the hexes of the building.
            Enumeration<Coords> hexes = bldg.getCoords();
            while (!canHit && hexes.hasMoreElements()) {
                final Coords coords = hexes.nextElement();

                // No physical attack works at distances > 1.
                if (getPosition().distance(coords) > 1) {
                    continue;
                }

                // Can the entity target *this* hex of the building?
                final BuildingTarget target = new BuildingTarget(coords, game.getBoard(), false);
                canHit |= Compute.canPhysicalTarget(game, getId(), target);

            } // Check the next hex of the building

        } // Check the next building

        return canHit;
    }

    public boolean isEligibleForTargetingPhase() {
        if (isAssaultDropInProgress()) {
            return false;
        }
        for (Mounted mounted : getWeaponList()) {
            WeaponType wtype = (WeaponType) mounted.getType();
            if ((wtype != null) && (wtype.hasFlag(WeaponType.F_ARTILLERY))) {
                return true;
            }
        }
        return false;
    }

    public int getTroopCarryingSpace() {
        int space = 0;
        for (Transporter t : transports) {
            if (t instanceof TroopSpace) {
                space += ((TroopSpace) t).totalSpace;
            }
        }
        return space;
    }

    public boolean hasBattleArmorHandles() {
        for (Transporter t : transports) {
            if (t instanceof BattleArmorHandles) {
                return true;
            }
        }
        return false;
    }

    /*
     * (non-Javadoc)
     *
     * @see megamek.common.Targetable#isOffBoard()
     */
    public boolean isOffBoard() {
        return offBoardDistance > 0;
    }

    /**
     * Set the unit as an offboard deployment. If a non-zero distance is chosen,
     * the direction must <b>not</b> be <code>Entity.NONE</code>. If a direction
     * other than <code>Entity.NONE</code> is chosen, the distance must
     * <b>not</b> be zero (0).
     *
     * @param distance
     *            the <code>int</code> distance in hexes that the unit will be
     *            deployed from the board; this value must not be negative.
     * @param direction
     *            the <code>int</code> direction from the board that the unit
     *            will be deployed; a valid value must be selected from: NONE,
     *            NORTH, SOUTH, EAST, or WEST.
     * @throws IllegalArgumentException
     *             if a negative distance, an invalid direction is selected, or
     *             the distance does not match the direction.
     */
    public void setOffBoard(int distance, int direction) {
        if (distance < 0) {
            throw new IllegalArgumentException("negative number given for distance offboard");
        }
        if ((direction < IOffBoardDirections.NONE) || (direction > IOffBoardDirections.WEST)) {
            throw new IllegalArgumentException("bad direction");
        }
        if ((0 == distance) && (IOffBoardDirections.NONE != direction)) {
            throw new IllegalArgumentException("onboard unit was given an offboard direction");
        }
        if ((0 != distance) && (IOffBoardDirections.NONE == direction)) {
            throw new IllegalArgumentException("offboard unit was not given an offboard direction");
        }
        switch (direction) {
        case IOffBoardDirections.NORTH:
            setFacing(3);
            break;
        case IOffBoardDirections.SOUTH:
            setFacing(0);
            break;
        case IOffBoardDirections.WEST:
            setFacing(2);
            break;
        case IOffBoardDirections.EAST:
            setFacing(4);
            break;
        }
        offBoardDistance = distance;
        offBoardDirection = direction;
    }

    /**
     * Get the distance in hexes from the board that the unit will be deployed.
     * If the unit is to be deployed onboard, the distance will be zero (0).
     *
     * @return the <code>int</code> distance from the board the unit will be
     *         deployed (in hexes); this value will never be negative.
     */
    public int getOffBoardDistance() {
        return offBoardDistance;
    }

    /**
     * Get the direction the board that the unit will be deployed. If the unit
     * is to be deployed onboard, the distance will be
     * <code>IOffBoardDirections.NONE</code>, otherwise it will be one of the
     * values:
     * <ul>
     * <li><code>IOffBoardDirections.NORTH</code></li>
     * <li><code>IOffBoardDirections.SOUTH</code></li>
     * <li><code>IOffBoardDirections.EAST</code></li>
     * <li><code>IOffBoardDirections.WEST</code></li>
     * </ul>
     *
     * @return the <code>int</code> direction from the board the unit will be
     *         deployed. Only valid values will be returned.
     */
    public int getOffBoardDirection() {
        return offBoardDirection;
    }

    /**
     * Deploy this offboard entity at the previously specified distance and
     * direction. This should only be invoked by the <code>Server</code> after
     * the board has been selected and all the players are ready to start. The
     * side effects of this methods set the unit's position and facing as
     * appropriate (as well as deploying the unit).
     * <p/>
     * Onboard units (units with an offboard distance of zero and a direction of
     * <code>Entity.NONE</code>) will be unaffected by this method.
     */
    public void deployOffBoard() {
        if (null == game) {
            throw new IllegalStateException("game not set; possible serialization error");
        }
        // N.B. 17 / 2 = 8, but the middle of 1..17 is 9, so we
        // add a bit (because 17 % 2 == 1 and 16 % 2 == 0).
        switch (offBoardDirection) {
        case IOffBoardDirections.NONE:
            break;
        case IOffBoardDirections.NORTH:
            setPosition(new Coords(game.getBoard().getWidth() / 2 + game.getBoard().getWidth() % 2, -getOffBoardDistance()));
            setFacing(3);
            setDeployed(true);
            break;
        case IOffBoardDirections.SOUTH:
            setPosition(new Coords(game.getBoard().getWidth() / 2 + game.getBoard().getWidth() % 2, game.getBoard().getHeight() + getOffBoardDistance()));
            setFacing(0);
            setDeployed(true);
            break;
        case IOffBoardDirections.EAST:
            setPosition(new Coords(game.getBoard().getWidth() + getOffBoardDistance(), game.getBoard().getHeight() / 2 + game.getBoard().getHeight() % 2));
            setFacing(5);
            setDeployed(true);
            break;
        case IOffBoardDirections.WEST:
            setPosition(new Coords(-getOffBoardDistance(), game.getBoard().getHeight() / 2 + game.getBoard().getHeight() % 2));
            setFacing(1);
            setDeployed(true);
            break;
        }
    }

    public Vector<Integer> getPickedUpMechWarriors() {
        return pickedUpMechWarriors;
    }

    /**
     * Has this entity been captured?
     *
     * @return <code>true</code> if it has.
     */
    public boolean isCaptured() {
        return captured && !isDestroyed();
    }

    /**
     * Specify that this entity has been captured.
     *
     * @param arg
     *            the <code>boolean</code> value to assign.
     */
    public void setCaptured(boolean arg) {
        captured = arg;
    }

    public void setSpotlight(boolean arg) {
        hasSpotlight = arg;
    }

    public boolean hasSpotlight() {
        for (Mounted m : getMisc()) {
            if (m.getType().hasFlag(MiscType.F_SEARCHLIGHT) && !m.isInoperable()) {
                return true;
            }
        }
        return hasSpotlight;
    }

    public void setSpotlightState(boolean arg) {
        if (hasSpotlight) {
            spotlightIsActive = arg;
            if (arg) {
                illuminated = true;
            }
        }
    }

    public boolean isIlluminated() {
        return illuminated;
    }

    public void setIlluminated(boolean arg) {
        illuminated = arg;
    }

    public boolean isUsingSpotlight() {
        return hasSpotlight && spotlightIsActive;
    }

    public void setUsedSearchlight(boolean arg) {
        usedSearchlight = arg;
    }

    public boolean usedSearchlight() {
        return usedSearchlight;
    }

    /**
     * illuminate a hex and all units that are between us and the hex
     *
     * @param target
     *            the <code>HexTarget</code> to illuminate
     */
    public void illuminateTarget(HexTarget target) {
        if (hasSpotlight && spotlightIsActive && (target != null)) {
            illuminated = true;
            ArrayList<Coords> in = Coords.intervening(getPosition(), target.getPosition());
            for (Coords c : in) {
                for (Enumeration<Entity> e = game.getEntities(c); e.hasMoreElements();) {
                    Entity en = e.nextElement();
                    en.setIlluminated(true);
                }
            }
        }
    }

    /**
     * Is the Entity stuck in a swamp?
     */
    public boolean isStuck() {
        return stuckInSwamp;
    }

    /**
     * Set weather this Entity is stuck in a swamp or not
     *
     * @param arg
     *            the <code>boolean</code> value to assign
     */
    public void setStuck(boolean arg) {
        stuckInSwamp = arg;
    }

    /**
     * Is the Entity stuck in a swamp?
     */
    public boolean canUnstickByJumping() {
        return canUnstickByJumping;
    }

    /**
     * Set wether this Enity is stuck in a swamp or not
     *
     * @param arg
     *            the <code>boolean</code> value to assign
     */
    public void setCanUnstickByJumping(boolean arg) {
        canUnstickByJumping = arg;
    }

    /*
     * The following methods support the eventual refactoring into the Entity
     * class of a lot of the Server logic surrounding entity damage and death.
     * They are not currently called in Server anywhere, and so may as well not
     * exist.
     */

    public String destroy(String reason, boolean survivable, boolean canSalvage) {
        StringBuffer sb = new StringBuffer();

        int condition = IEntityRemovalConditions.REMOVE_SALVAGEABLE;
        if (!canSalvage) {
            setSalvage(canSalvage);
            condition = IEntityRemovalConditions.REMOVE_DEVASTATED;
        }

        if (isDoomed() || isDestroyed()) {
            return sb.toString();
        }

        // working under the assumption that entity was neither doomed or
        // destroyed before from here on out

        setDoomed(true);

        Enumeration<Integer> iter = getPickedUpMechWarriors().elements();
        while (iter.hasMoreElements()) {
            Integer mechWarriorId = iter.nextElement();
            Entity mw = game.getEntity(mechWarriorId.intValue());
            mw.setDestroyed(true);
            game.removeEntity(mw.getId(), condition);
            sb.append("\n*** ").append(mw.getDisplayName() + " died in the wreckage. ***\n");
        }
        return sb.toString();
    }

    /**
     * Add a targeting by a swarm volley from a specified entity
     *
     * @param entityId
     *            The <code>int</code> id of the shooting entity
     * @param weaponId
     *            The <code>int</code> id of the shooting lrm launcher
     */
    public void addTargetedBySwarm(int entityId, int weaponId) {
        hitBySwarmsEntity.addElement(new Integer(entityId));
        hitBySwarmsWeapon.addElement(new Integer(weaponId));
    }

    /**
     * Were we targeted by a certain swarm/swarm-i volley this turn?
     *
     * @param entityId
     *            The <code>int</code> id of the shooting entity we are checking
     * @param weaponId
     *            The <code>int</code> id of the launcher to check
     * @return a fitting <code>boolean</code> value
     */
    public boolean getTargetedBySwarm(int entityId, int weaponId) {
        for (int i = 0; i < hitBySwarmsEntity.size(); i++) {
            Integer entityIdToTest = hitBySwarmsEntity.elementAt(i);
            Integer weaponIdToTest = hitBySwarmsWeapon.elementAt(i);
            if ((entityId == entityIdToTest.intValue()) && (weaponId == weaponIdToTest.intValue())) {
                return true;
            }
        }
        return false;
    }

    public int getShortRangeModifier() {
        if (getTargSysType() == MiscType.T_TARGSYS_SHORTRANGE) {
            return -1;
        } else if (getTargSysType() == MiscType.T_TARGSYS_LONGRANGE) {
            return 1;
        }
        return 0;
    }

    public int getMediumRangeModifier() {
        return 2;
    }

    public int getLongRangeModifier() {
        if (getTargSysType() == MiscType.T_TARGSYS_SHORTRANGE) {
            return 5;
        } else if (getTargSysType() == MiscType.T_TARGSYS_LONGRANGE) {
            return 3;
        }
        return 4;
    }

    public int getExtremeRangeModifier() {
        return 6;
    }

    public int getTargSysType() {
        return targSys;
    }

    public void setTargSysType(int targSysType) {
        targSys = targSysType;
    }

    public void setArmorType(int armType) {
        armorType = armType;
    }

    public void setStructureType(int strucType) {
        structureType = strucType;
    }

    public void setArmorType(String armType) {
        setArmorType(EquipmentType.getArmorType(armType));
    }

    public void setStructureType(String strucType) {
        setStructureType(EquipmentType.getStructureType(strucType));
    }

    public int getArmorType() {
        return armorType;
    }

    public void setArmorTechLevel(int newTL) {
        armorTechLevel = newTL;
    }

    public int getArmorTechLevel() {
        return armorTechLevel;
    }

    public int getStructureType() {
        return structureType;
    }

    public void setWeaponDestroyed(Mounted which) {
        if (weaponList.contains(which)) {
            which.setDestroyed(true);
        }
    }

    public void setTaggedBy(int tagger) {
        taggedBy = tagger;
    }

    public int getTaggedBy() {
        return taggedBy;
    }

    public abstract double getCost();

    public int getWeaponsAndEquipmentCost() {
        int cost = 0;
        for (Mounted mounted : getEquipment()) {
            if (mounted.isWeaponGroup()) {
                continue;
            }
            int itemCost = (int) mounted.getType().getCost(this, mounted.isArmored());
            if (itemCost == EquipmentType.COST_VARIABLE) {
                itemCost = mounted.getType().resolveVariableCost(this, mounted.isArmored());
            }
            cost += itemCost;
        }
        return cost;
    }

    public boolean removePartialCoverHits(int location, int cover, int side) {
        if (cover > LosEffects.COVER_NONE) {
            switch (cover) {
            case LosEffects.COVER_LOWLEFT:
                if (location == Mech.LOC_LLEG) {
                    return true;
                }
                break;
            case LosEffects.COVER_LOWRIGHT:
                if (location == Mech.LOC_RLEG) {
                    return true;
                }
                break;
            case LosEffects.COVER_LEFT:
                if ((location == Mech.LOC_LLEG) || (location == Mech.LOC_LARM) || (location == Mech.LOC_LT)) {
                    return true;
                }
                break;
            case LosEffects.COVER_RIGHT:
                if ((location == Mech.LOC_RLEG) || (location == Mech.LOC_RARM) || (location == Mech.LOC_RT)) {
                    return true;
                }
                break;
            case LosEffects.COVER_HORIZONTAL:
                if ((location == Mech.LOC_LLEG) || (location == Mech.LOC_RLEG)) {
                    return true;
                }
                break;
            case LosEffects.COVER_UPPER:
                if ((location == Mech.LOC_LLEG) || (location == Mech.LOC_RLEG)) {
                    return false;
                }
                return true;
            case LosEffects.COVER_FULL:
                return true;
            case LosEffects.COVER_75LEFT:
                if ((location == Mech.LOC_RARM) || (location == Mech.LOC_RLEG)) {
                    return false;
                }
                return true;
            case LosEffects.COVER_75RIGHT:
                if ((location == Mech.LOC_LLEG) || (location == Mech.LOC_LARM)) {
                    return false;
                }
                return true;
            }
        }
        return false;

    }

    /**
     * returns true if this is a valid target for Arrow IV homing which was
     * aimed at hex (tc)
     */
    public boolean isOnSameSheet(Coords tc) {
        if (game.getOptions().booleanOption("a4homing_target_area")) {
            // unofficial rule which may be better with odd sized boards
            if (tc.distance(getPosition()) <= 8) {
                return true;
            }
            return false;
        }
        // using FASA map sheets
        if ((tc.x / 16 == getPosition().x / 16) && (tc.y / 17 == getPosition().y / 17)) {
            return true;
        }
        return false;
    }

    public abstract boolean doomedInVacuum();

    public abstract boolean doomedOnGround();

    public abstract boolean doomedInAtmosphere();

    public abstract boolean doomedInSpace();

    public double getArmorWeight() {
        // this roundabout method is actually necessary to avoid rounding
        // weirdness. Yeah, it's dumb.
        double armorPerTon = 16.0 * EquipmentType.getArmorPointMultiplier(armorType, armorTechLevel);
        if (armorType == EquipmentType.T_ARMOR_HARDENED) {
            armorPerTon = 8.0;
        }
        double points = getTotalOArmor();
        double armorWeight = points / armorPerTon;
        armorWeight = Math.ceil(armorWeight * 2.0) / 2.0;
        return armorWeight;
    }

    public boolean hasTAG() {
        for (Mounted m : getWeaponList()) {
            WeaponType equip = (WeaponType) (m.getType());
            if ((equip != null) && (equip.hasFlag(WeaponType.F_TAG))) {
                return true;
            }
        }
        return false;
    }

    public boolean isCanon() {
        return canon;
    }

    public void setCanon(boolean canon) {
        this.canon = canon;
    }

    public boolean climbMode() {
        return climbMode;
    }

    public void setClimbMode(boolean state) {
        climbMode = state;
    }

    public boolean usedTag() {
        for (Mounted weapon : getWeaponList()) {
            WeaponType wtype = (WeaponType) weapon.getType();
            if (weapon.isUsedThisRound() && wtype.hasFlag(WeaponType.F_TAG)) {
                return true;
            }
        }
        return false;
    }

    public boolean hasEiCockpit() {
        return ((game != null) && game.getOptions().booleanOption("all_have_ei_cockpit"));
    }

    public boolean hasActiveEiCockpit() {
        return (hasEiCockpit() && getCrew().getOptions().booleanOption("ei_implant"));
    }

    public boolean isLayingMines() {
        return layingMines;
    }

    public void setLayingMines(boolean laying) {
        layingMines = laying;
    }

    public boolean canLayMine() {
        for (Object oMount : miscList) {
            Mounted mount = (Mounted) oMount;
            EquipmentType type = mount.getType();
            if (!mount.isMissing() && type.hasFlag(MiscType.F_MINE) && !isLayingMines()) {
                return true;
            }
        }
        return false;
    }

    public int sideTable(Coords src) {
        return sideTable(src, false);
    }

    public int sideTable(Coords src, boolean usePrior) {
        return sideTable(src, usePrior, facing);
    }

    public int sideTable(Coords src, boolean usePrior, int face) {
        Coords effectivePos = position;
        if (usePrior) {
            effectivePos = getPriorPosition();
        }

        if (src.equals(effectivePos)) {
            // most places handle 0 range explicitly,
            // this is a safe default (calculation gives SIDE_RIGHT)
            return ToHitData.SIDE_FRONT;
        }

        // calculate firing angle
        int fa = (effectivePos.degree(src) + (6 - face) * 60) % 360;

        int leftBetter = 2;
        // if we're right on the line, we need to special case this
        // defender would choose along which hex the LOS gets drawn, and that
        // side also determines the side we hit in
        if (fa % 30 == 0) {
            IHex srcHex = game.getBoard().getHex(src);
            IHex curHex = game.getBoard().getHex(getPosition());
            if ((srcHex != null) && (curHex != null)) {
                LosEffects.AttackInfo ai = LosEffects.buildAttackInfo(src, getPosition(), 1, getElevation(), srcHex.floor(), curHex.floor());
                ArrayList<Coords> in = Coords.intervening(ai.attackPos, ai.targetPos, true);
                leftBetter = LosEffects.dividedLeftBetter(in, game, ai, Compute.isInBuilding(game, this), new LosEffects());
            }
        }

        boolean targetIsTank = (this instanceof Tank) || (game.getOptions().booleanOption("tacops_advanced_mech_hit_locations") && (this instanceof QuadMech));
        if (targetIsTank) {
            if ((leftBetter == 1) && (fa == 150)) {
                return ToHitData.SIDE_REAR;
            } else if ((leftBetter == 1) && (fa == 30)) {
                return ToHitData.SIDE_RIGHT;
            } else if ((leftBetter == 0) && (fa == 330)) {
                return ToHitData.SIDE_LEFT;
            } else if ((leftBetter == 0) && (fa == 210)) {
                return ToHitData.SIDE_REAR;
            } else if ((fa > 30) && (fa <= 150)) {
                return ToHitData.SIDE_RIGHT;
            } else if ((fa > 150) && (fa < 210)) {
                return ToHitData.SIDE_REAR;
            } else if ((fa >= 210) && (fa < 330)) {
                return ToHitData.SIDE_LEFT;
            } else {
                return ToHitData.SIDE_FRONT;
            }
        }
        if (this instanceof Aero) {
            Aero a = (Aero) this;
            // Handle spheroids in atmosphere differently
            // TODO: awaiting a rules forum clarification
            // until then assume that the side must either be left or right
            if (a.isSpheroid() && game.getBoard().inAtmosphere()) {
                fa = effectivePos.degree(src);
                if ((fa >= 0) && (fa < 180)) {
                    return ToHitData.SIDE_RIGHT;
                }
                return ToHitData.SIDE_LEFT;
            }
            if ((leftBetter == 1) && (fa == 150)) {
                return ToHitData.SIDE_REAR;
            } else if ((leftBetter == 1) && (fa == 30)) {
                if (a.isRolled()) {
                    return ToHitData.SIDE_LEFT;
                }
                return ToHitData.SIDE_RIGHT;
            } else if ((leftBetter == 0) && (fa == 330)) {
                if (a.isRolled()) {
                    return ToHitData.SIDE_RIGHT;
                }
                return ToHitData.SIDE_LEFT;
            } else if ((leftBetter == 0) && (fa == 210)) {
                return ToHitData.SIDE_REAR;
            } else if ((fa > 30) && (fa <= 150)) {
                if (a.isRolled()) {
                    return ToHitData.SIDE_LEFT;
                }
                return ToHitData.SIDE_RIGHT;
            } else if ((fa > 150) && (fa < 210)) {
                return ToHitData.SIDE_REAR;
            } else if ((fa >= 210) && (fa < 330)) {
                if (a.isRolled()) {
                    return ToHitData.SIDE_RIGHT;
                }
                return ToHitData.SIDE_LEFT;
            } else {
                return ToHitData.SIDE_FRONT;
            }
        }
        if ((fa == 90) && (leftBetter == 1)) {
            return ToHitData.SIDE_RIGHT;
        } else if (((fa == 150) && (leftBetter == 1)) || ((leftBetter == 0) && (fa == 210))) {
            return ToHitData.SIDE_REAR;
        } else if ((leftBetter == 0) && (fa == 270)) {
            return ToHitData.SIDE_LEFT;
        } else if ((fa > 90) && (fa <= 150)) {
            return ToHitData.SIDE_RIGHT;
        } else if ((fa > 150) && (fa < 210)) {
            return ToHitData.SIDE_REAR;
        } else if ((fa >= 210) && (fa < 270)) {
            return ToHitData.SIDE_LEFT;
        } else {
            return ToHitData.SIDE_FRONT;
        }
    }

    public boolean canGoHullDown() {
        return false;
    }

    public boolean canAssaultDrop() {
        return false;
    }

    public void setAssaultDropInProgress(boolean flag) {
        assaultDropInProgress = flag ? 1 : 0;
    }

    public void setLandedAssaultDrop() {
        assaultDropInProgress = 2;
        moved = IEntityMovementType.MOVE_JUMP;
    }

    public boolean isAssaultDropInProgress() {
        return assaultDropInProgress != 0;
    }

    /**
     * Apply PSR modifier for difficult terrain at the specified coordinates
     *
     * @param roll
     *            the PSR to modify
     * @param c
     *            the coordinates where the PSR happens
     */
    public void addPilotingModifierForTerrain(PilotingRollData roll, Coords c) {
        if ((c == null) || (roll == null)) {
            return;
        }
        if (isOffBoard() || !(isDeployed())) {
            return;
        }
        IHex hex = game.getBoard().getHex(c);
        int modifier = hex.terrainPilotingModifier(getMovementMode());
        if (modifier != 0) {
            roll.addModifier(modifier, "difficult terrain");
        }
    }

    /**
     * Apply PSR modifier for difficult terrain at the move step position
     *
     * @param roll
     *            the PSR to modify
     * @param step
     *            the move step the PSR occurs at
     */
    public void addPilotingModifierForTerrain(PilotingRollData roll, MoveStep step) {
        if (step.getElevation() > 0) {
            return;
        }
        addPilotingModifierForTerrain(roll, step.getPosition());
    }

    /**
     * Apply PSR modifier for difficult terrain in the current position
     *
     * @param roll
     *            the PSR to modify
     */
    public void addPilotingModifierForTerrain(PilotingRollData roll) {
        if (getElevation() > 0) {
            return;
        }
        addPilotingModifierForTerrain(roll, getPosition());
    }

    /**
     * defensively check and correct elevation
     */
    public boolean fixElevation() {
        if (!isDeployed() || isOffBoard() || !game.getBoard().contains(getPosition())) {
            return false;
        }
        if (!isElevationValid(getElevation(), game.getBoard().getHex(getPosition()))) {
            System.err.println(getDisplayName() + " in hex " + HexTarget.coordsToId(getPosition()) + " is at invalid elevation: " + getElevation());
            setElevation(elevationOccupied(game.getBoard().getHex(getPosition())));
            System.err.println("   moved to elevation " + getElevation());
            return true;
        }
        return false;
    }

    public Engine getEngine() {
        return engine;
    }

    public boolean itemOppositeTech(String s) {
        if (isClan()) { // Clan base
            if ((s.toLowerCase().indexOf("(IS)") != -1) || (s.toLowerCase().indexOf("Inner Sphere") != -1)) {
                return true;
            }
            return false;
        }
        if ((s.toLowerCase().indexOf("(C)") != -1) || (s.toLowerCase().indexOf("Clan") != -1)) {
            return true;
        }
        return false;
    }

    /**
     * @return Returns the retreatedDirection.
     */
    public int getRetreatedDirection() {
        return retreatedDirection;
    }

    /**
     * @param retreatedDirection
     *            The retreatedDirection to set.
     */
    public void setRetreatedDirection(int retreatedDirection) {
        this.retreatedDirection = retreatedDirection;
    }

    public void setLastTarget(int id) {
        lastTarget = id;
    }

    public int getLastTarget() {
        return lastTarget;
    }

    /**
     * @returns whether or not the unit is suffering from Electromagnetic
     *          Interference
     */
    public boolean isSufferingEMI() {
        return _isEMId;
    }

    public void setEMI(boolean inVal) {
        _isEMId = inVal;
    }

    /**
     * Checks if the unit is hardened agaist nuclear strikes.
     *
     * @return true if this is a hardened unit.
     */
    public abstract boolean isNuclearHardened();

    public void setHidden(boolean inVal) {
        isHidden = inVal;
    }

    public boolean isHidden() {
        return isHidden;
    }

    /**
     * Is this unit a carcass, a carcass can take no action
     */
    public boolean isCarcass() {
        return carcass;
    }

    /**
     * Sets if this unit is a carcass.
     *
     * @param carcass
     *            true if this unit should be a carcass, false otherwise.
     * @see megamek.common.Entity#isCarcass
     */
    public void setCarcass(boolean carcass) {
        this.carcass = carcass;
    }

    /**
     * Marks all equipment in a location on this entity as destroyed.
     *
     * @param loc
     *            The location that is destroyed.
     */
    public void destroyLocation(int loc) {
        // if it's already marked as destroyed, don't bother
        if (getInternal(loc) < 0) {
            return;
        }
        // mark armor, internal as doomed
        setArmor(IArmorState.ARMOR_DOOMED, loc, false);
        setInternal(IArmorState.ARMOR_DOOMED, loc);
        if (hasRearArmor(loc)) {
            setArmor(IArmorState.ARMOR_DOOMED, loc, true);
        }
        // equipment marked missing
        for (Mounted mounted : getEquipment()) {
            if (((mounted.getLocation() == loc) && mounted.getType().isHittable()) || (mounted.isSplit() && (mounted.getSecondLocation() == loc))) {
                mounted.setMissing(true);
            }
        }
        // all critical slots set as missing
        for (int i = 0; i < getNumberOfCriticals(loc); i++) {
            final CriticalSlot cs = getCritical(loc, i);
            if (cs != null) {
                // count engine hits for maxtech engine explosions
                if ((cs.getType() == CriticalSlot.TYPE_SYSTEM) && (cs.getIndex() == Mech.SYSTEM_ENGINE) && !cs.isDamaged()) {
                    engineHitsThisRound++;
                }
                cs.setMissing(true);
            }
        }
        // dependent locations destroyed, unless they are already destroyed
        if ((getDependentLocation(loc) != Entity.LOC_NONE) && !(getInternal(getDependentLocation(loc)) < 0)) {
            destroyLocation(getDependentLocation(loc));
        }
        // remove any narc/inarc pods in this location
        for (Iterator<INarcPod> i = pendingINarcPods.iterator(); i.hasNext();) {
            if (i.next().getLocation() == loc) {
                i.remove();
            }
        }
        for (Iterator<INarcPod> i = iNarcPods.iterator(); i.hasNext();) {
            if (i.next().getLocation() == loc) {
                i.remove();
            }
        }
        for (Iterator<NarcPod> i = pendingNarcPods.iterator(); i.hasNext();) {
            if (i.next().getLocation() == loc) {
                i.remove();
            }
        }
        for (Iterator<NarcPod> i = narcPods.iterator(); i.hasNext();) {
            if (i.next().getLocation() == loc) {
                i.remove();
            }
        }
    }

    public PilotingRollData checkSideSlip(int moveType, IHex prevHex, int overallMoveType, MoveStep prevStep, int prevFacing, int curFacing, Coords lastPos, Coords curPos, int distance) {
        PilotingRollData roll = getBasePilotingRoll(overallMoveType);

        if ((moveType != IEntityMovementType.MOVE_JUMP) && (prevHex != null) && (distance > 1) && ((overallMoveType == IEntityMovementType.MOVE_RUN) || (overallMoveType == IEntityMovementType.MOVE_VTOL_RUN)) && (prevFacing != curFacing) && !lastPos.equals(curPos) && !(this instanceof Infantry)) {
            roll.append(new PilotingRollData(getId(), 0, "flanking and turning"));

        } else {
            roll.addModifier(TargetRoll.CHECK_FALSE, "Check false: not apparently sideslipping");
        }

        return roll;
    }

    /**
     * returns true if the entity is flying.
     */
    public boolean isFlying() {
        // stuff that moves like a VTOL is flying unless at elevation 0 or on
        // top of/in a building,
        if (getMovementMode() == IEntityMovementMode.VTOL) {
            if ((game.getBoard().getHex(getPosition()).terrainLevel(Terrains.BLDG_ELEV) >= getElevation()) || (game.getBoard().getHex(getPosition()).terrainLevel(Terrains.BRIDGE_ELEV) >= getElevation())) {
                return false;
            }
            return getElevation() > 0;
        }
        return false;
    }

    public void setSpotTargetId(int targetId) {
        spotTargetId = targetId;
    }

    public int getSpotTargetId() {
        return spotTargetId;
    }

    public void setCommander(boolean arg) {
        isCommander = arg;
    }

    public boolean isCommander() {
        return isCommander;
    }

    public boolean hasLinkedMGA(Mounted mounted) {
        for (Mounted m : getWeaponList()) {
            if ((m.getLocation() == mounted.getLocation()) && m.getType().hasFlag(WeaponType.F_MGA) && !(m.isDestroyed() || m.isBreached()) && m.getType().hasModes() && m.curMode().equals("Linked") && (((WeaponType) m.getType()).getDamage() == ((WeaponType) mounted.getType()).getDamage())) {
                return true;
            }
        }
        return false;
    }

    public void setReckless(boolean b) {
        reckless = b;
    }

    public boolean isReckless() {
        return reckless;
    }

    public boolean isFighter() {
        return (this instanceof Aero) && !((this instanceof SmallCraft) || (this instanceof Jumpship) || (this instanceof FighterSquadron));
    }

    public boolean isCapitalFighter() {
        if (null == game) {
            return false;
        }
        return game.getOptions().booleanOption("stratops_capital_fighter") && isFighter();
    }

    /**
     * a function that let's us know if this entity has capital-scale armor
     *
     * @return
     */
    public boolean isCapitalScale() {

        if ((this instanceof Jumpship) || (this instanceof FighterSquadron) || isCapitalFighter()) {
            return true;
        }

        return false;

    }

    /**
     * a function that let's us know if this entity is using weapons bays
     *
     * @return
     */
    public boolean usesWeaponBays() {

        if ((this instanceof Jumpship) || (this instanceof Dropship)) {
            return true;
        }

        return false;

    }

    /**
     * return the bay of the current weapon
     *
     * @param bayID
     * @return
     */
    public Mounted whichBay(int bayID) {

        for (Mounted m : getWeaponBayList()) {
            for (int wId : m.getBayWeapons()) {
                // find the weapon and determine if it is there
                if (wId == bayID) {
                    return m;
                }
            }
        }
        return null;
    }

    /**
     * return the first bay of the right type in the right location with enough
     * damage to spare
     *
     * @param wtype
     * @param loc
     * @param rearMount
     * @return
     */
    public Mounted getFirstBay(WeaponType wtype, int loc, boolean rearMount) {

        int weapDamage = wtype.getRoundShortAV();
        if (wtype.isCapital()) {
            weapDamage *= 10;
        }

        for (Mounted m : getWeaponBayList()) {
            BayWeapon bay = (BayWeapon) m.getType();
            int damage = bay.getRoundShortAV() + weapDamage;
            if ((bay.getAtClass() == wtype.getAtClass()) && (m.getLocation() == loc) && (m.isRearMounted() == rearMount) && (damage <= 700)) {
                return m;
            }

        }
        return null;
    }

    public int getHeatInArc(int location, boolean rearMount) {

        int arcHeat = 0;

        for (Mounted mounted : getTotalWeaponList()) {
            // is the weapon usable?
            if (mounted.isDestroyed() || mounted.isJammed()) {
                continue;
            }

            if ((mounted.getLocation() == location) && (mounted.isRearMounted() == rearMount)) {
                arcHeat += mounted.getCurrentHeat();
            }
        }
        return arcHeat;
    }

    public int[] getVectors() {
        return vectors;
    }

    public void setVectors(int[] v) {
        if ((v == null) || (v.length != 6)) {
            return;
        }

        vectors = v;
    }

    public int getVector(int vectorFacing) {
        if (vectorFacing < 6) {
            return vectors[vectorFacing];
        }
        return 0;
    }

    public int getVelocity() {

        int total = 0;
        for (int dir = 0; dir < 6; dir++) {
            total += getVector(dir);
        }

        return total;
    }

    public int chooseSide(Coords attackPos, boolean usePrior) {
        // loop through directions and if we have a non-zero vector, then
        // compute
        // the targetsidetable. If we come to a higher vector, then replace. If
        // we come to an equal vector then take it if it is better
        int thrust = 0;
        int high = -1;
        int side = -1;
        for (int dir = 0; dir < 6; dir++) {
            thrust = getVector(dir);
            if (thrust == 0) {
                continue;
            }

            if (thrust > high) {
                high = thrust;
                side = sideTable(attackPos, usePrior, dir);
            }

            // what if they tie
            if (thrust == high) {
                int newside = sideTable(attackPos, usePrior, dir);
                // choose the best
                if ((newside == ToHitData.SIDE_LEFT) || (newside == ToHitData.SIDE_RIGHT)) {
                    newside = side;
                }
                // that should be the only case, because it can't shift you from
                // front
                // to aft or vice-versa
            }

        }
        return side;
    }

    /**
     * return the heading of the unit based on its active vectors if vectors are
     * tied then return two headings
     */
    public Vector<Integer> getHeading() {

        Vector<Integer> heading = new Vector<Integer>();
        int high = 0;
        int curDir = getFacing();
        for (int dir = 0; dir < 6; dir++) {
            int thrust = getVector(dir);
            if ((thrust >= high) && (thrust > 0)) {
                // if they were equal then add the last direction to the
                // vector before moving on
                if (thrust == high) {
                    heading.addElement(curDir);
                }
                high = getVector(dir);
                curDir = dir;
            }
        }
        heading.addElement(curDir);
        return heading;
    }

    public void setPassedThrough(Vector<Coords> pass) {
        passedThrough = pass;
    }

    public Vector<Coords> getPassedThrough() {
        return passedThrough;
    }

    public void addPassedThrough(Coords c) {
        passedThrough.add(c);
    }

    public void setRamming(boolean b) {
        ramming = b;
    }

    public boolean isRamming() {
        return ramming;
    }

    public void resetFiringArcs() {
        frontArcFired = new boolean[locations()];
        rearArcFired = new boolean[locations()];
        for (int i = 0; i < locations(); i++) {
            frontArcFired[i] = false;
            rearArcFired[i] = false;
        }
    }

    public boolean hasArcFired(int location, boolean rearMount) {
        if ((null == frontArcFired) || (null == rearArcFired)) {
            resetFiringArcs();
        }
        if ((location > locations()) || (location < 0)) {
            return false;
        }

        if (rearMount) {
            return rearArcFired[location];
        }
        return frontArcFired[location];
    }

    public void setArcFired(int location, boolean rearMount) {
        if ((null == frontArcFired) || (null == rearArcFired)) {
            resetFiringArcs();
        }
        if ((location > locations()) || (location < 0)) {
            return;
        }

        if (rearMount) {
            rearArcFired[location] = true;
        } else {
            frontArcFired[location] = true;
        }
    }

    /**
     * Force rapid fire mode to the highest level on RAC and UAC
     */
    public void setRapidFire() {
        for (Mounted m : getTotalWeaponList()) {
            WeaponType wtype = (WeaponType) m.getType();
            if (wtype.getAmmoType() == AmmoType.T_AC_ROTARY) {
                m.setMode("6-shot");
            } else if (wtype.getAmmoType() == AmmoType.T_AC_ULTRA) {
                m.setMode("Ultra");
            }
        }
    }

    /**
     * Set the retractable blade in the given location as extended Takes the
     * first piece of appropriate equipment
     */
    public void extendBlade(int loc) {
        for (Mounted m : getEquipment()) {
            if ((m.getLocation() == loc) && !m.isDestroyed() && !m.isBreached() && (m.getType() instanceof MiscType) && m.getType().hasFlag(MiscType.F_CLUB) && m.getType().hasSubType(MiscType.S_RETRACTABLE_BLADE)) {
                m.setMode("extended");
                return;
            }
        }
    }

    /**
     * destroys the first retractable blade critical slot found
     */
    /**
     * Checks whether a weapon has been fired from the specified location this
     * turn
     */
    public void destroyRetractableBlade(int loc) {
        // check critical slots
        for (int i = 0; i < this.getNumberOfCriticals(loc); i++) {
            CriticalSlot slot = getCritical(loc, i);
            // ignore empty & system slots
            if ((slot == null) || (slot.getType() != CriticalSlot.TYPE_EQUIPMENT)) {
                continue;
            }
            Mounted m = getEquipment(slot.getIndex());
            if ((m.getLocation() == loc) && !m.isDestroyed() && !m.isBreached() && (m.getType() instanceof MiscType) && m.getType().hasFlag(MiscType.F_CLUB) && m.getType().hasSubType(MiscType.S_RETRACTABLE_BLADE)) {
                slot.setDestroyed(true);
                m.setDestroyed(true);
                return;
            }
        }
    }

    public TeleMissileTracker getTMTracker() {
        return tmTracker;
    }

    public void setGrappled(int id, boolean attacker) {
        // TODO?
    }

    public boolean isGrappleAttacker() {
        return false;
    }

    public int getGrappled() {
        return Entity.NONE;
    }

    public void setGameOptions() {

        if (game == null) {
            return;
        }

        // if the small craft does not already have ECM, then give them a single
        // hex ECM so they can change the mode
        if ((this instanceof SmallCraft) && !hasActiveECM() && isMilitary()) {
            try {
                this.addEquipment(EquipmentType.get(BattleArmor.SINGLE_HEX_ECM), Aero.LOC_NOSE, false);
            } catch (LocationFullException ex) {
                // ignore
            }
        }

        for (Mounted mounted : getWeaponList()) {
            if ((mounted.getType() instanceof GaussWeapon) && game.getOptions().booleanOption("tacops_gauss_weapons")) {
                String[] modes = { "Powered Up", "Powered Down" };
                ((WeaponType) mounted.getType()).setModes(modes);
                ((WeaponType) mounted.getType()).setInstantModeSwitch(false);
            } else if ((mounted.getType() instanceof ACWeapon) && game.getOptions().booleanOption("tacops_rapid_ac")) {
                String[] modes = { "", "Rapid" };
                ((WeaponType) mounted.getType()).setModes(modes);
            } else if (mounted.getType() instanceof ISBombastLaser) {
                int damage = 12;
                ArrayList<String> modes = new ArrayList<String>();
                String[] stringArray = {};

                for (; damage >= 7; damage--) {
                    modes.add("Damage " + damage);
                }
                ((WeaponType) mounted.getType()).setModes(modes.toArray(stringArray));
            } else if (((WeaponType) mounted.getType()).isCapital() && (((WeaponType) mounted.getType()).getAtClass() != WeaponType.CLASS_CAPITAL_MISSILE) && (((WeaponType) mounted.getType()).getAtClass() != WeaponType.CLASS_AR10)) {
                ArrayList<String> modes = new ArrayList<String>();
                String[] stringArray = {};
                modes.add("");
                if (game.getOptions().booleanOption("stratops_bracket_fire")) {
                    modes.add("Bracket 80%");
                    modes.add("Bracket 60%");
                    modes.add("Bracket 40%");
                }
                if ((mounted.getType() instanceof CapitalLaserBayWeapon) && game.getOptions().booleanOption("stratops_aaa_laser")) {
                    modes.add("AAA");
                    ((WeaponType) mounted.getType()).addEndTurnMode("AAA");
                }
                if (modes.size() > 1) {
                    ((WeaponType) mounted.getType()).setModes(modes.toArray(stringArray));
                }
            }

        }

        for (Mounted misc : getMisc()) {
            if (misc.getType().hasFlag(MiscType.F_BAP) && (this instanceof Aero) && game.getOptions().booleanOption("stratops_ecm")) {
                ArrayList<String> modes = new ArrayList<String>();
                String[] stringArray = {};
                modes.add("Short");
                modes.add("Medium");
                ((MiscType) misc.getType()).setModes(modes.toArray(stringArray));
                ((MiscType) misc.getType()).setInstantModeSwitch(false);
            }
            if (misc.getType().hasFlag(MiscType.F_ECM)) {
                ArrayList<String> modes = new ArrayList<String>();
                modes.add("ECM");
                String[] stringArray = {};
                if (game.getOptions().booleanOption("tacops_eccm")) {
                    modes.add("ECCM");
                    if (misc.getType().hasFlag(MiscType.F_ANGEL_ECM)) {
                        modes.add("ECM & ECCM");
                    }
                } else if (game.getOptions().booleanOption("stratops_ecm") && (this instanceof Aero)) {
                    modes.add("ECCM");
                    if (misc.getType().hasFlag(MiscType.F_ANGEL_ECM)) {
                        modes.add("ECM & ECCM");
                    }
                }
                if (game.getOptions().booleanOption("tacops_ghost_target")) {
                    if (misc.getType().hasFlag(MiscType.F_ANGEL_ECM)) {
                        modes.add("ECM & Ghost Targets");
                        modes.add("ECCM & Ghost Targets");
                    } else {
                        modes.add("Ghost Targets");
                    }
                }
                ((MiscType) misc.getType()).setModes(modes.toArray(stringArray));
            }
        }

    }

    public void setGrappleSide(int side) {
        // TODO?
    }

    public int getGrappleSide() {
        return Entity.NONE;
    }

    public boolean hasFunctionalArmAES(int location) {
        return false;
    }

    public boolean hasFunctionalLegAES() {
        return false;
    }

    public boolean isEvading() {
        return evading;
    }

    public void setEvading(boolean evasion) {
        evading = evasion;
    }

    public int getEvasionBonus() {
        if (isProne()) {
            return 0;
        }

        if (this instanceof SmallCraft) {
            return 2;
        } else if (this instanceof Jumpship) {
            return 1;
        } else if (this instanceof Aero) {
            return 3;
        } else {
            if (game.getOptions().booleanOption("tacops_skilled_evasion")) {
                int piloting = crew.getPiloting();
                if (piloting < 2) {
                    return 3;
                } else if (piloting < 4) {
                    return 2;
                } else if (piloting < 6) {
                    return 1;
                }
            } else {
                return 1;
            }
        }
        return 0;
    }

    public void setCarefulStand(boolean stand) {
        isCarefulStanding = stand;
    }

    public boolean isCarefulStand() {
        return false;
    }

    public Vector<Sensor> getSensors() {
        return sensors;
    }

    public Sensor getActiveSensor() {
        return activeSensor;
    }

    public Sensor getNextSensor() {
        return nextSensor;
    }

    public void setNextSensor(Sensor s) {
        nextSensor = s;
    }

    public int getSensorCheck() {
        return sensorCheck;
    }

    public boolean hasModularArmor() {
        return hasModularArmor(0);
    }

    public boolean hasModularArmor(int loc) {
        return false;
    }

    public int getDamageReductionFromModularArmor(int loc, int damage, Vector<Report> vDesc) {

        if (!hasModularArmor(loc)) {
            return damage;
        }

        for (Mounted mount : this.getEquipment()) {
            if ((mount.getLocation() == loc) && !mount.isDestroyed() && (mount.getType() instanceof MiscType) && ((MiscType) mount.getType()).hasFlag(MiscType.F_MODULAR_ARMOR)) {

                int damageAbsorption = mount.getBaseDamageCapacity() - mount.getDamageTaken();
                if (damageAbsorption > damage) {
                    mount.damageTaken += damage;
                    Report r = new Report(3535);
                    r.subject = getId();
                    r.add(damage);
                    r.indent(1);
                    r.newlines = 0;
                    vDesc.addElement(r);
                    Report.addNewline(vDesc);

                    return 0;
                }

                if (damageAbsorption == damage) {
                    Report.addNewline(vDesc);
                    Report r = new Report(3535);
                    r.subject = getId();
                    r.add(damage);
                    r.indent(1);
                    r.newlines = 0;
                    vDesc.addElement(r);
                    r = new Report(3536);
                    r.subject = getId();
                    r.indent();
                    vDesc.addElement(r);

                    mount.damageTaken += damage;
                    mount.setDestroyed(true);
                    mount.setHit(true);
                    return 0;
                }

                if (damageAbsorption < damage) {
                    Report.addNewline(vDesc);
                    Report r = new Report(3535);
                    r.subject = getId();
                    r.add(damageAbsorption);
                    r.indent(1);
                    r.newlines = 0;
                    vDesc.addElement(r);
                    r = new Report(3536);
                    r.subject = getId();
                    r.indent(1);
                    vDesc.addElement(r);

                    damage -= mount.baseDamageAbsorptionRate - mount.damageTaken;
                    mount.damageTaken = mount.baseDamageAbsorptionRate;
                    mount.setDestroyed(true);
                    mount.setHit(true);
                }
            }

        }

        return damage;
    }

    public int getGhostTargetRoll() {
        return ghostTargetRoll;
    }

    public int getGhostTargetRollMoS() {
        return ghostTargetRoll - (getCrew().getSensorOps() + 2);
    }

    public int getGhostTargetOverride() {
        return ghostTargetOverride;
    }

    public int getCoolantFailureAmount() {
        return 0;
    }

    public void addCoolantFailureAmount(int amount) {
        // TODO?
    }

    /**
     * @return the tonnage of additional mounted communications equipment
     */
    public int getExtraCommGearTons() {
        int i = 0;
        for (Mounted mounted : miscList) {
            if (mounted.getType().hasFlag(MiscType.F_COMMUNICATIONS)) {
                i += mounted.getType().getTonnage(this);
            }
        }
        return i;
    }

    /**
     * @return the strength of the ECM field this unit emits
     */
    public double getECMStrength() {
        int strength = 0;
        for (Mounted m : getMisc()) {
            if (m.getType().hasFlag(MiscType.F_ECM) && (strength < 1)) {
                strength = 1;
            }
            if (m.getType().hasFlag(MiscType.F_ANGEL_ECM) && (strength < 2)) {
                strength = 2;
            }
        }
        return strength;
    }

    /**
     * @return the strength of the ECCM field this unit emits
     */
    public double getECCMStrength() {
        double strength = 0;
        for (Mounted m : getMisc()) {
            if (m.getType().hasFlag(MiscType.F_COMMUNICATIONS)) {
                if ((getTotalCommGearTons() > 3) && (strength < 0.5)) {
                    strength = 0.5;
                }
                if ((getTotalCommGearTons() > 6) && (strength < 1)) {
                    strength = 1;
                }
            }
            if (m.getType().hasFlag(MiscType.F_ECM) && (strength < 1)) {
                strength = 1;
            }
            if (m.getType().hasFlag(MiscType.F_ANGEL_ECM) && (strength < 2)) {
                strength = 2;
            }
        }
        return strength;
    }

    /**
     * @return the total tonnage of communications gear in this entity
     */
    public abstract int getTotalCommGearTons();

    /**
     * @return the initiative bonus this Entity grants for HQ
     */
    public int getHQIniBonus() {
        int bonus = 0;
        for (Mounted misc : getMisc()) {
            if (misc.getType().hasFlag(MiscType.F_COMMUNICATIONS) && misc.curMode().equals("Default")) {
                if (getTotalCommGearTons() >= 3) {
                    bonus += 1;
                }
                if (getTotalCommGearTons() >= 7) {
                    bonus += 1;
                }
                break;
            }
        }

        return bonus;
    }

    /**
     * @return the initiative bonus this Entity grants for MD implants
     */
    public int getMDIniBonus() {
        if (crew.getOptions().booleanOption("comm_implant") || crew.getOptions().booleanOption("boost_comm_implant")) {
            return 1;
        }
        return 0;
    }

    /**
     * Apply any pending Santa Anna allocations to Killer Whale ammo bins
     * effectively "splitting" the ammo bins in two This is a hack that I should
     * really creat a general method of "splitting" ammo bins for
     */
    public void applySantaAnna() {

        // I can't add the ammo while I am iterating through the ammo list
        // so collect vectors containing number of shots and location
        Vector<Integer> locations = new Vector<Integer>();
        Vector<Integer> ammo = new Vector<Integer>();
        Vector<Mounted> baymounts = new Vector<Mounted>();
        Vector<String> name = new Vector<String>();

        for (Mounted amounted : getAmmo()) {
            if (amounted.getNSantaAnna() > 0) {
                // first reduce the current ammo load by number of santa annas
                int nSantaAnna = amounted.getNSantaAnna();
                amounted.setShotsLeft(Math.max(amounted.getShotsLeft() - nSantaAnna, 0));
                // save the new ammo information
                locations.add(amounted.getLocation());
                ammo.add(nSantaAnna);
                // name depends on type
                if (amounted.getType().getInternalName().indexOf("AR10") != -1) {
                    name.add("AR10 SantaAnna Ammo");
                } else {
                    name.add("SantaAnna Ammo");
                }
                if (null == getBayByAmmo(amounted)) {
                    System.err.println("cannot find the right bay for Santa Anna ammo");
                    return;
                }
                baymounts.add(getBayByAmmo(amounted));
                // now make sure santa anna loadout is reset
                amounted.setNSantaAnna(0);
            }
        }

        // now iterate through to get new mounts
        if ((ammo.size() != locations.size()) || (ammo.size() != name.size()) || (ammo.size() != baymounts.size())) {
            // this shouldn't happen
            System.err.println("cannot load santa anna ammo");
            return;
        }
        for (int i = 0; i < ammo.size(); i++) {
            try {
                Mounted newmount = addEquipment(EquipmentType.get(name.elementAt(i)), locations.elementAt(i), false, ammo.elementAt(i));
                // add this mount to the ammo for the bay
                baymounts.elementAt(i).addAmmoToBay(getEquipmentNum(newmount));
            } catch (LocationFullException ex) {
                // throw new LocationFullException(ex.getMessage());
            }
        }
    }

    /**
     * get the bay that ammo is associated with
     *
     * @param mammo
     * @return
     */
    public Mounted getBayByAmmo(Mounted mammo) {

        for (Mounted m : getWeaponBayList()) {
            for (int bayAmmoId : m.getBayAmmo()) {
                Mounted bayammo = getEquipment(bayAmmoId);
                if (bayammo == mammo) {
                    return m;
                }
            }
        }
        return null;
    }

    /**
     * Return how many BA vibroclaws this <code>Entity</code> is equipped with
     */
    public int getVibroClaws() {
        // generic entities can't carry vibroclaws
        return 0;
    }

    /**
     * shut this unit down due to a BA Taser attack
     *
     * @param turns
     *            - the amount of rounds for which this Entity should be
     *            shutdown
     */
    public void baTaserShutdown(int turns) {
        setShutDown(true);
        taserShutdownRounds = turns;
        shutdownByBATaser = true;
    }

    /**
     * get the number of rounds for which this unit should be shutdown by taser
     *
     * @return
     */
    public int getTaserShutdownRounds() {
        return taserShutdownRounds;
    }

    public void setTaserShutdownRounds(int rounds) {
        taserShutdownRounds = rounds;
    }

    public boolean isBATaserShutdown() {
        return shutdownByBATaser;
    }

    public void setBATaserShutdown(boolean value) {
        shutdownByBATaser = value;
    }

    /**
     * set this entity to suffer from taser feedback
     *
     * @param rounds
     *            - the number of rounds to suffer from taserfeedback
     */
    public void setTaserFeedback(int rounds) {
        taserFeedBackRounds = rounds;
    }

    /**
     * get the rounds for which this entity suffers from taser feedback
     *
     * @return
     */
    public int getTaserFeedBackRounds() {
        return taserFeedBackRounds;
    }

    public void setTaserInterference(int value, int rounds) {
        taserInterference = value;
        taserInterferenceRounds = rounds;
    }

    public int getTaserInterference() {
        return taserInterference;
    }

    public int getTaserInterferenceRounds() {
        return taserInterferenceRounds;
    }

    /**
     * returns whether the unit is a military unit (as opposed to a civilian
     * unit). TODO: for now this just returns true, but at some point the
     * database should be updated
     */
    public boolean isMilitary() {
        return true;
    }

    /**
     * is this entity a large craft? (dropship, jumpship, warship, or space
     * station)
     */
    public boolean isLargeCraft() {
        return (this instanceof Dropship) || (this instanceof Jumpship);
    }

    /**
     * Do units loaded onto this entity still have active ECM/ECCM/etc.?
     */
    public boolean loadedUnitsHaveActiveECM() {
        return false;
    }

    /**
     * is this entity loaded into a fighter squadron?
     */
    public boolean isPartOfFighterSquadron() {
        if (game == null) {
            return false;
        }
        if (conveyance == Entity.NONE) {
            return false;
        }
        return game.getEntity(conveyance) instanceof FighterSquadron;
    }

    /**
     * Return a HTML string that describes the BV calculations
     *
     * @return a <code>String</code> explaining the BV calculation
     */
    public String getBVText() {
        if (bvText == null) {
            return "";
        }

        return bvText.toString();
    }

    /**
     * Return the BAR-rating of this Entity's armor
     *
     * @return the BAR rating
     */
    public int getBARRating() {
        // normal armor has a BAR rating of 10
        return 10;
    }

    /**
     * does this Entity have BAR armor?
     *
     * @return
     */
    public boolean hasBARArmor() {
        return getBARRating() < 10;
    }

    /**
     * does this entity have an armored chassis?
     *
     * @return
     */
    public boolean hasArmoredChassis() {
        // normal entities don't, subclasses should
        // override
        return false;
    }

    /**
     * does this <code>Entity</code> have Environmental sealing? (only Support
     * Vehicles or IndustrialMechs should mount this)
     *
     * @return
     */
    public boolean hasEnvironmentalSealing() {
        for (Mounted misc : miscList) {
            if (misc.getType().hasFlag(MiscType.F_ENVIRONMENTAL_SEALING)) {
                return true;
            }
        }
        return false;
    }

    /**
     * possibly do a ICE-Engine stall PSR, only industrialmechs will actually do
     * such a roll
     *
     * @param vPhaseReport
     *            the <code>Vector<Report></code> containing the phase reports
     * @return a Vector<Report> containing the passed in reports, and any
     *         additional ones
     */
    public Vector<Report> doCheckEngineStallRoll(Vector<Report> vPhaseReport) {
        return vPhaseReport;
    }

    /**
     * check for unstalling of this Entity's engine (only used for ICE
     * industrial Mechs)
     *
     * @param vPhaseReport
     *            the <code>Vector<Report></code> containing the phase reports
     */
    public void checkUnstall(Vector<Report> vPhaseReport) {
        return;
    }

    public boolean hasArmoredEngine() {
        return false;
    }

    /**
     * Is this Entity's ICE Engine stalled?
     *
     * @return if this Entity's ICE engine is stalled
     */
    public boolean isStalled() {
        return false;
    }

    /**
     * is this a naval vessel on the surface of the water?
     */
    public boolean isSurfaceNaval() {
        // TODO: assuming submarines on the surface act like surface naval
        // vessels until rules clarified
        // http://www.classicbattletech.com/forums/index.php/topic,48987.0.html
        return (getElevation() == 0) && ((getMovementMode() == IEntityMovementMode.NAVAL) || (getMovementMode() == IEntityMovementMode.HYDROFOIL) || (getMovementMode() == IEntityMovementMode.SUBMARINE));
    }

    /**
     * used to set the source of the creation of this entity, i.e RS PPU Custom
     * what not Fluff for MMLab
     *
     * @param source
     */
    public void setSource(String source) {
        if (source != null) {
            this.source = source;
        }
    }

    public String getSource() {
        if (source == null) {
            return "";
        }
        return source;
    }

}