/*
* Unit.java
*
* Copyright (C) 2016 MegaMek team
* Copyright (c) 2009 Jay Lawson <jaylawson39 at yahoo.com>. All rights reserved.
*
* This file is part of MekHQ.
*
* MekHQ 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 3 of the License, or
* (at your option) any later version.
*
* MekHQ 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.
*
* You should have received a copy of the GNU General Public License
* along with MekHQ. If not, see <http://www.gnu.org/licenses/>.
*/
package mekhq.campaign.unit;
import java.io.PrintWriter;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import megamek.common.ASFBay;
import megamek.common.Aero;
import megamek.common.AmmoType;
import megamek.common.BattleArmor;
import megamek.common.BattleArmorBay;
import megamek.common.Bay;
import megamek.common.CargoBay;
import megamek.common.Compute;
import megamek.common.ConvFighter;
import megamek.common.Crew;
import megamek.common.CriticalSlot;
import megamek.common.Dropship;
import megamek.common.Engine;
import megamek.common.Entity;
import megamek.common.EntityMovementMode;
import megamek.common.EntityWeightClass;
import megamek.common.EquipmentType;
import megamek.common.FighterSquadron;
import megamek.common.HeavyVehicleBay;
import megamek.common.IArmorState;
import megamek.common.ILocationExposureStatus;
import megamek.common.IPlayer;
import megamek.common.Infantry;
import megamek.common.InfantryBay;
import megamek.common.InsulatedCargoBay;
import megamek.common.Jumpship;
import megamek.common.LightVehicleBay;
import megamek.common.LiquidCargoBay;
import megamek.common.LivestockCargoBay;
import megamek.common.Mech;
import megamek.common.MechBay;
import megamek.common.MiscType;
import megamek.common.Mounted;
import megamek.common.PillionSeatCargoBay;
import megamek.common.Player;
import megamek.common.Protomech;
import megamek.common.ProtomechBay;
import megamek.common.QuadMech;
import megamek.common.RefrigeratedCargoBay;
import megamek.common.SmallCraft;
import megamek.common.SmallCraftBay;
import megamek.common.SpaceStation;
import megamek.common.StandardSeatCargoBay;
import megamek.common.SupportTank;
import megamek.common.Tank;
import megamek.common.TargetRoll;
import megamek.common.TechConstants;
import megamek.common.VTOL;
import megamek.common.Warship;
import megamek.common.WeaponType;
import megamek.common.options.IOption;
import megamek.common.options.IOptionGroup;
import megamek.common.options.PilotOptions;
import megamek.common.weapons.BayWeapon;
import megamek.common.weapons.InfantryAttack;
import megamek.common.weapons.infantry.InfantryWeapon;
import mekhq.MekHQ;
import mekhq.MekHqXmlSerializable;
import mekhq.MekHqXmlUtil;
import mekhq.Utilities;
import mekhq.Version;
import mekhq.campaign.Campaign;
import mekhq.campaign.event.PersonCrewAssignmentEvent;
import mekhq.campaign.event.PersonTechAssignmentEvent;
import mekhq.campaign.event.UnitArrivedEvent;
import mekhq.campaign.parts.AeroHeatSink;
import mekhq.campaign.parts.AeroLifeSupport;
import mekhq.campaign.parts.AeroSensor;
import mekhq.campaign.parts.Armor;
import mekhq.campaign.parts.Avionics;
import mekhq.campaign.parts.BaArmor;
import mekhq.campaign.parts.BattleArmorSuit;
import mekhq.campaign.parts.DropshipDockingCollar;
import mekhq.campaign.parts.EnginePart;
import mekhq.campaign.parts.FireControlSystem;
import mekhq.campaign.parts.InfantryArmorPart;
import mekhq.campaign.parts.InfantryMotiveType;
import mekhq.campaign.parts.LandingGear;
import mekhq.campaign.parts.MekActuator;
import mekhq.campaign.parts.MekCockpit;
import mekhq.campaign.parts.MekGyro;
import mekhq.campaign.parts.MekLifeSupport;
import mekhq.campaign.parts.MekLocation;
import mekhq.campaign.parts.MekSensor;
import mekhq.campaign.parts.MissingAeroHeatSink;
import mekhq.campaign.parts.MissingAeroLifeSupport;
import mekhq.campaign.parts.MissingAeroSensor;
import mekhq.campaign.parts.MissingAvionics;
import mekhq.campaign.parts.MissingBattleArmorSuit;
import mekhq.campaign.parts.MissingDropshipDockingCollar;
import mekhq.campaign.parts.MissingEnginePart;
import mekhq.campaign.parts.MissingFireControlSystem;
import mekhq.campaign.parts.MissingLandingGear;
import mekhq.campaign.parts.MissingMekActuator;
import mekhq.campaign.parts.MissingMekCockpit;
import mekhq.campaign.parts.MissingMekGyro;
import mekhq.campaign.parts.MissingMekLifeSupport;
import mekhq.campaign.parts.MissingMekLocation;
import mekhq.campaign.parts.MissingMekSensor;
import mekhq.campaign.parts.MissingPart;
import mekhq.campaign.parts.MissingProtomekArmActuator;
import mekhq.campaign.parts.MissingProtomekJumpJet;
import mekhq.campaign.parts.MissingProtomekLegActuator;
import mekhq.campaign.parts.MissingProtomekLocation;
import mekhq.campaign.parts.MissingProtomekSensor;
import mekhq.campaign.parts.MissingRotor;
import mekhq.campaign.parts.MissingSpacecraftEngine;
import mekhq.campaign.parts.MissingThrusters;
import mekhq.campaign.parts.MissingTurret;
import mekhq.campaign.parts.MissingVeeSensor;
import mekhq.campaign.parts.MissingVeeStabiliser;
import mekhq.campaign.parts.MotiveSystem;
import mekhq.campaign.parts.Part;
import mekhq.campaign.parts.PodSpace;
import mekhq.campaign.parts.ProtomekArmActuator;
import mekhq.campaign.parts.ProtomekArmor;
import mekhq.campaign.parts.ProtomekJumpJet;
import mekhq.campaign.parts.ProtomekLegActuator;
import mekhq.campaign.parts.ProtomekLocation;
import mekhq.campaign.parts.ProtomekSensor;
import mekhq.campaign.parts.Refit;
import mekhq.campaign.parts.Rotor;
import mekhq.campaign.parts.SpacecraftEngine;
import mekhq.campaign.parts.StructuralIntegrity;
import mekhq.campaign.parts.TankLocation;
import mekhq.campaign.parts.Thrusters;
import mekhq.campaign.parts.Turret;
import mekhq.campaign.parts.TurretLock;
import mekhq.campaign.parts.VeeSensor;
import mekhq.campaign.parts.VeeStabiliser;
import mekhq.campaign.parts.equipment.AmmoBin;
import mekhq.campaign.parts.equipment.BattleArmorAmmoBin;
import mekhq.campaign.parts.equipment.BattleArmorEquipmentPart;
import mekhq.campaign.parts.equipment.EquipmentPart;
import mekhq.campaign.parts.equipment.HeatSink;
import mekhq.campaign.parts.equipment.InfantryWeaponPart;
import mekhq.campaign.parts.equipment.JumpJet;
import mekhq.campaign.parts.equipment.MASC;
import mekhq.campaign.parts.equipment.MissingAmmoBin;
import mekhq.campaign.parts.equipment.MissingBattleArmorEquipmentPart;
import mekhq.campaign.parts.equipment.MissingEquipmentPart;
import mekhq.campaign.parts.equipment.MissingHeatSink;
import mekhq.campaign.parts.equipment.MissingJumpJet;
import mekhq.campaign.personnel.Person;
import mekhq.campaign.personnel.SkillType;
import mekhq.campaign.work.IAcquisitionWork;
import mekhq.campaign.work.IPartWork;
/**
* This is a wrapper class for entity, so that we can add some functionality to
* it
*
* @author Jay Lawson <jaylawson39 at yahoo.com>
*/
public class Unit implements MekHqXmlSerializable {
public static final int SITE_FIELD = 0;
public static final int SITE_MOBILE_BASE = 1;
public static final int SITE_BAY = 2;
public static final int SITE_FACILITY = 3;
public static final int SITE_FACTORY = 4;
public static final int SITE_N = 5;
// To be used for transport and cargo reports
public static final int ETYPE_MOTHBALLED = -9876;
protected Entity entity;
private int site;
private boolean salvaged;
private UUID id;
private int oldId;
private String fluffName = "";
//assignments
private int forceId;
protected int scenarioId;
private ArrayList<UUID> drivers;
private ArrayList<UUID> gunners;
private ArrayList<UUID> vesselCrew;
private UUID navigator;
//this is the id of the tech assigned for maintenance if any
private UUID tech;
//mothballing variables - if mothball time is not zero then mothballing/activating is in progress
private int mothballTime;
private boolean mothballed;
private int daysSinceMaintenance;
private int daysActivelyMaintained;
private int astechDaysMaintained;
//old ids for reverse compatability
private ArrayList<Integer> oldDrivers;
private ArrayList<Integer> oldGunners;
private ArrayList<Integer> oldVesselCrew;
private Integer oldNavigator;
public Campaign campaign;
private ArrayList<Part> parts;
private String lastMaintenanceReport;
private ArrayList<PodSpace> podSpace;
private Refit refit;
//a made-up person to handle repairs on Large Craft
private Person engineer;
//for backwards compatability with 0.1.8, but otherwise is no longer used
@SuppressWarnings("unused")
private int pilotId = -1;
private String history;
//for delivery
protected int daysToArrival;
public Unit() {
this(null, null);
}
public Unit(Entity en, Campaign c) {
this.entity = en;
if (entity != null) {
entity.setCamoCategory(null);
entity.setCamoFileName(null);
}
this.site = SITE_BAY;
this.salvaged = false;
this.campaign = c;
this.parts = new ArrayList<Part>();
this.podSpace = new ArrayList<>();
this.drivers = new ArrayList<UUID>();
this.gunners = new ArrayList<UUID>();
this.vesselCrew = new ArrayList<UUID>();
this.navigator = null;
this.tech = null;
this.mothballTime = 0;
this.mothballed = false;
this.oldDrivers = new ArrayList<Integer>();
this.oldGunners = new ArrayList<Integer>();
this.oldVesselCrew = new ArrayList<Integer>();
this.oldNavigator = -1;
scenarioId = -1;
this.refit = null;
this.engineer = null;
this.history = "";
this.daysSinceMaintenance = 0;
this.daysActivelyMaintained = 0;
this.astechDaysMaintained = 0;
this.lastMaintenanceReport = null;
this.fluffName = "";
reCalc();
}
public static String getDamageStateName(int i) {
switch(i) {
case Entity.DMG_NONE:
return "Undamaged";
case Entity.DMG_LIGHT:
return "Light Damage";
case Entity.DMG_MODERATE:
return "Moderate Damage";
case Entity.DMG_HEAVY:
return "Heavy Damage";
case Entity.DMG_CRIPPLED:
return "Crippled";
default:
return "Unknown";
}
}
/**
* A convenience function to tell whether the unit can be acted upon
* e.g. assigned pilots, techs, repaired, etc.
* @return
*/
public boolean isAvailable() {
return isAvailable(false);
}
/**
* A convenience function to tell whether the unit can be acted upon
* e.g. assigned pilots, techs, repaired, etc.
* @return
*/
public boolean isAvailable(boolean ignoreRefit) {
if (ignoreRefit) {
return isPresent() && !isDeployed() && !isMothballing() && !isMothballed();
}
return isPresent() && !isDeployed() && !isRefitting() && !isMothballing() && !isMothballed();
}
public String getStatus() {
if(isMothballing()) {
if(isMothballed()) {
return "Activating (" + getMothballTime() + "m)";
} else {
return "Mothballing (" + getMothballTime() + "m)";
}
}
if(isMothballed()) {
return "Mothballed";
}
if(isDeployed()) {
return "Deployed";
}
if(!isPresent()) {
return "In transit (" + getDaysToArrival() + " days)";
}
if(isRefitting()) {
return "Refitting";
}
if(!isRepairable()) {
return "Salvage";
}
else if(!isFunctional()) {
return "Inoperable";
}
else {
return getDamageStateName(getDamageState());
}
}
public void reCalc() {
// Do nothing.
}
public void setEntity(Entity en) {
//if there is already an entity, then make sure this
//one gets some of the same things set
if(null != this.entity) {
en.setId(this.entity.getId());
en.duplicateMarker = this.entity.duplicateMarker;
}
this.entity = en;
}
public Entity getEntity() {
return entity;
}
public UUID getId() {
return id;
}
public void setId(UUID i) {
this.id = i;
}
public int getSite() {
return site;
}
public void setSite(int i) {
this.site = i;
}
public boolean isSalvage() {
return salvaged;
}
public void setSalvage(boolean b) {
this.salvaged = b;
}
public String getHistory() {
return history;
}
public void setHistory(String s) {
this.history = s;
}
public static boolean isFunctional(Entity en) {
if (en instanceof Mech) {
// center torso bad?? head bad?
if (en.isLocationBad(Mech.LOC_CT)
|| en.isLocationBad(Mech.LOC_HEAD)) {
return false;
}
// engine destruction?
//cockpit hits
int engineHits = 0;
int cockpitHits = 0;
for (int i = 0; i < en.locations(); i++) {
engineHits += en.getHitCriticals(CriticalSlot.TYPE_SYSTEM,
Mech.SYSTEM_ENGINE, i);
cockpitHits += en.getHitCriticals(CriticalSlot.TYPE_SYSTEM,
Mech.SYSTEM_COCKPIT, i);
}
if (engineHits > 2) {
return false;
}
if(cockpitHits > 0) {
return false;
}
}
if (en instanceof Tank) {
for (int i = 0; i < en.locations(); i++) {
if(i == Tank.LOC_TURRET || i == Tank.LOC_TURRET_2) {
continue;
}
if (en.isLocationBad(i)) {
return false;
}
}
if(en instanceof VTOL) {
if(en.getWalkMP() <= 0) {
return false;
}
}
}
if(en instanceof Aero) {
if(en.getWalkMP() <= 0 && !(en instanceof Jumpship)) {
return false;
}
if(((Aero)en).getSI() <= 0) {
return false;
}
}
return true;
}
public boolean isFunctional() {
return isFunctional(entity);
}
public static boolean isRepairable(Entity en) {
if (en instanceof Mech) {
// you can repair anything so long as one point of CT is left
if (en.getInternal(Mech.LOC_CT) <= 0) {
return false;
}
}
if (en instanceof Tank) {
// can't repair a tank with a destroyed location
for (int i = 0; i < en.locations(); i++) {
if(i == Tank.LOC_TURRET || i == Tank.LOC_TURRET_2 || i == Tank.LOC_BODY) {
continue;
}
if (en.getInternal(i) <= 0) {
return false;
}
}
}
if(en instanceof Aero) {
if(((Aero)en).getSI() <= 0) {
return false;
}
}
return true;
}
public boolean isRepairable() {
return isRepairable(entity);
}
/**
* Is the given location on the entity destroyed?
*
* @param loc
* - an <code>int</code> for the location
* @return <code>true</code> if the location is destroyed
*/
public boolean isLocationDestroyed(int loc) {
if (loc > entity.locations() || loc < 0) {
return false;
}
/*boolean blownOff = entity.isLocationBlownOff(loc);
entity.setLocationBlownOff(loc, false);
boolean isDestroyed = entity.isLocationBad(loc);
entity.setLocationBlownOff(loc, blownOff);
return isDestroyed;
*/
return entity.isLocationTrulyDestroyed(loc);
}
public boolean isLocationBreached(int loc) {
return entity.getLocationStatus(loc) == ILocationExposureStatus.BREACHED;
}
public boolean hasBadHipOrShoulder(int loc) {
return entity instanceof Mech
&& (entity.getDamagedCriticals(CriticalSlot.TYPE_SYSTEM, Mech.ACTUATOR_HIP, loc) > 0
|| entity.getDamagedCriticals(CriticalSlot.TYPE_SYSTEM, Mech.ACTUATOR_SHOULDER, loc) > 0);
}
/**
* Run a diagnostic on this unit
* TODO: This is being called in the PersonnelTableModel after changes to the personnel
* attached to a unit, but I am not sure it needs to be. I don't think any parts check
* attached personnel. I think it could b removed, but I am going to leave it for the
* moment because I have made so many other changes in this version.
*/
public void runDiagnostic(boolean checkForDestruction) {
//need to set up an array of part ids to avoid concurrent modification
//problems because some updateCondition methods will remove the part and put
//in a new one
ArrayList<Part> tempParts = new ArrayList<Part>();
for(Part p : parts) {
tempParts.add(p);
}
for(Part part : tempParts) {
part.updateConditionFromEntity(checkForDestruction);
}
}
public ArrayList<IPartWork> getPartsNeedingFixing() {
ArrayList<IPartWork> brokenParts = new ArrayList<IPartWork>();
for(Part part: parts) {
if(part.needsFixing()) {
brokenParts.add(part);
}
}
for (PodSpace pod : podSpace) {
if (pod.needsFixing()) {
brokenParts.add(pod);
}
}
return brokenParts;
}
public ArrayList<IPartWork> getSalvageableParts() {
ArrayList<IPartWork> salvageParts = new ArrayList<IPartWork>();
for(Part part: parts) {
if(part.isSalvaging()) {
salvageParts.add(part);
}
}
for (PodSpace pod : podSpace) {
if (pod.hasSalvageableParts()) {
salvageParts.add(pod);
}
}
return salvageParts;
}
public ArrayList<IAcquisitionWork> getPartsNeeded() {
ArrayList<IAcquisitionWork> missingParts = new ArrayList<IAcquisitionWork>();
if(isSalvage() || !isRepairable()) {
return missingParts;
}
boolean armorFound = false;
for(Part part: parts) {
if(part instanceof MissingPart && part.needsFixing() && null == ((MissingPart)part).findReplacement(false)) {
missingParts.add((MissingPart)part);
}
//we need to check for armor as well, but this one is funny because we dont want to
//check per location really, since armor can be used anywhere. So stop after we reach
//the first Armor needing replacement
//TODO: we need to adjust for patchwork armor, which can have different armor types by location
if(!armorFound && part instanceof Armor) {
Armor a = (Armor)part;
if(a.needsFixing() && !a.isEnoughSpareArmorAvailable()) {
missingParts.add(a);
armorFound = true;
}
}
if(!armorFound && part instanceof ProtomekArmor) {
ProtomekArmor a = (ProtomekArmor)part;
if(a.needsFixing() && !a.isEnoughSpareArmorAvailable()) {
missingParts.add(a);
armorFound = true;
}
}
if(!armorFound && part instanceof BaArmor) {
BaArmor a = (BaArmor)part;
if(a.needsFixing() && !a.isEnoughSpareArmorAvailable()) {
missingParts.add(a);
armorFound = true;
}
}
if(part instanceof AmmoBin && !((AmmoBin)part).isEnoughSpareAmmoAvailable()) {
missingParts.add((AmmoBin)part);
}
}
return missingParts;
}
public long getValueOfAllMissingParts() {
long value = 0;
for(Part part : parts) {
if(part instanceof MissingAmmoBin) {
AmmoBin newBin = (AmmoBin) ((MissingAmmoBin)part).getNewEquipment();
value += newBin.getValueNeeded();
}
if(part instanceof MissingPart) {
Part newPart = (Part)((MissingPart)part).getNewEquipment();
newPart.setBrandNew(!campaign.getCampaignOptions().useBLCSaleValue());
value += newPart.getActualValue();
}
else if(part instanceof AmmoBin) {
value += ((AmmoBin)part).getValueNeeded();
}
else if(part instanceof Armor) {
value += ((Armor)part).getValueNeeded();
}
}
return value;
}
public void removePart(Part part) {
parts.remove(part);
}
/**
* @param m
* - A Mounted class to find crits for
* @return the number of crits existing for this Mounted
*/
public int getCrits(Mounted m) {
// TODO: I should probably just add this method to Entity in MM
// For the above, Mounted would probably be even better than Entity
int hits = 0;
for (int loc = 0; loc < entity.locations(); loc++) {
for (int i = 0; i < entity.getNumberOfCriticals(loc); i++) {
CriticalSlot slot = entity.getCritical(loc, i);
// ignore empty & system slots
if ((slot == null)
|| (slot.getType() != CriticalSlot.TYPE_EQUIPMENT)) {
continue;
}
Mounted m1 = slot.getMount();
Mounted m2 = slot.getMount2();
if (slot.getIndex() == -1) {
if ((m.equals(m1) || m.equals(m2))
&& (slot.isHit() || slot.isDestroyed())) {
hits++;
}
} else {
if (entity.getEquipmentNum(m) == slot.getIndex()
&& (slot.isHit() || slot.isDestroyed())) {
hits++;
}
}
}
}
return hits;
}
public boolean hasPilot() {
return null != entity.getCrew();
}
public String getPilotDesc() {
if (hasPilot()) {
return entity.getCrew().getName() + " "
+ entity.getCrew().getGunnery() + "/"
+ entity.getCrew().getPiloting();
}
return "NO PILOT";
}
/**
* produce a string in HTML that can be embedded in larger reports
*/
public String getDescHTML() {
String toReturn = "<b>" + getName() + "</b><br/>";
toReturn += getPilotDesc() + "<br/>";
if (isDeployed()) {
toReturn += "DEPLOYED!<br/>";
} else {
toReturn += "Site: " + getCurrentSiteName() + "<br/>";
}
return toReturn;
}
public TargetRoll getSiteMod() {
switch (site) {
case SITE_FIELD:
return new TargetRoll(2, "in the field");
case SITE_MOBILE_BASE:
return new TargetRoll(1, "field workshop");
case SITE_BAY:
return new TargetRoll(0, "transport bay");
case SITE_FACILITY:
return new TargetRoll(-2, "maintenance facility");
case SITE_FACTORY:
return new TargetRoll(-4, "factory");
default:
return new TargetRoll(0, "unknown location");
}
}
public static String getSiteName(int loc) {
switch (loc) {
case SITE_FIELD:
return "In the Field";
case SITE_MOBILE_BASE:
return "Field Workshop";
case SITE_BAY:
return "Transport Bay";
case SITE_FACILITY:
return "Maintenance Facility";
case SITE_FACTORY:
return "Factory";
default:
return "Unknown";
}
}
public String getCurrentSiteName() {
return getSiteName(site);
}
public boolean isDeployed() {
return scenarioId != -1;
}
public void undeploy() {
scenarioId = -1;
}
// TODO: Add support for advanced medical
public String checkDeployment() {
if (!isFunctional()) {
return "unit is not functional";
}
if (isUnmanned()) {
return "unit has no pilot";
}
if(isRefitting()) {
return "unit is being refit";
}
if(entity instanceof Tank
&& getActiveCrew().size() < getFullCrewSize()) {
return "This vehicle requires a crew of " + getFullCrewSize();
}
//Taharqa: I am not going to allow BattleArmor units with unmanned suits to deploy. It is
//possible to hack this to work in MM, but it becomes a serious problem when the unit becomes
//a total loss because the unmanned suits are also treated as destroyed. I tried hacking something
//together in ResolveScenarioTracker and decided that it was not right. If someone wants to deploy
//a non-full strength BA unit, they can salvage the suits that are unmanned and then they can deploy
//it
if(entity instanceof BattleArmor) {
for(int i = BattleArmor.LOC_TROOPER_1; i <= ((BattleArmor)entity).getTroopers(); i++) {
if(entity.getInternal(i) == 0) {
return "This BattleArmor unit has empty suits. Fill them with pilots or salvage them.";
}
}
}
return null;
}
/**
* Have to make one here because the one in MegaMek only returns true if
* operable
*
* @return
*/
public boolean hasTSM() {
for (Mounted mEquip : entity.getMisc()) {
MiscType mtype = (MiscType) mEquip.getType();
if (null != mtype && mtype.hasFlag(MiscType.F_TSM)) {
return true;
}
}
return false;
}
/**
* Returns true if there is at least one missing critical slot for
* this system in the given location
*/
public boolean isSystemMissing(int system, int loc) {
for (int i = 0; i < entity.getNumberOfCriticals(loc); i++) {
CriticalSlot ccs = entity.getCritical(loc, i);
if ((ccs != null) && (ccs.getType() == CriticalSlot.TYPE_SYSTEM)
&& (ccs.getIndex() == system) && ccs.isMissing()) {
return true;
}
}
return false;
}
/**
* Number of slots doomed, missing or destroyed in all locations
* @param type
* @param index
* @return
*/
public int getHitCriticals(int type, int index) {
int hits = 0;
for (int loc = 0; loc < entity.locations(); loc++) {
hits += getHitCriticals(type, index, loc);
}
return hits;
}
/**
* Number of slots doomed, missing or destroyed in a location
*/
public int getHitCriticals(int type, int index, int loc) {
int hits = 0;
Mounted m = null;
if (type == CriticalSlot.TYPE_EQUIPMENT) {
m = entity.getEquipment(index);
}
int numberOfCriticals = entity.getNumberOfCriticals(loc);
for (int i = 0; i < numberOfCriticals; i++) {
CriticalSlot ccs = entity.getCritical(loc, i);
// Check to see if this crit mounts the supplied item
// For systems, we can compare the index, but for equipment we
// need to get the Mounted that is mounted in that index and
// compare types. Superheavies may have two Mounted in each crit
if ((ccs != null) && (ccs.getType() == type)) {
if (ccs.isDestroyed()) {
if ((type == CriticalSlot.TYPE_SYSTEM) && (ccs.getIndex() == index)) {
hits++;
} else if ((type == CriticalSlot.TYPE_EQUIPMENT) && (m.equals(ccs.getMount()) || m.equals(ccs
.getMount2()))) {
hits++;
}
}
}
}
return hits;
}
public void damageSystem(int type, int equipmentNum, int hits) {
//make sure we take note of existing hits to start and as we cycle through locations
int existingHits = getHitCriticals(type, equipmentNum);
int neededHits = Math.max(0, hits - existingHits);
int usedHits = 0;
for (int loc = 0; loc < getEntity().locations(); loc++) {
if(neededHits > usedHits) {
usedHits += damageSystem(type, equipmentNum, loc, neededHits-usedHits);
}
}
}
public int damageSystem(int type, int equipmentNum, int loc, int hits) {
int nhits = 0;
for (int i = 0; i < getEntity().getNumberOfCriticals(loc); i++) {
CriticalSlot cs = getEntity().getCritical(loc, i);
// ignore empty & system slots
if ((cs == null) || (cs.getType() != type)) {
continue;
}
Mounted mounted = getEntity().getEquipment(equipmentNum);
Mounted m1 = cs.getMount();
Mounted m2 = cs.getMount2();
if (cs.getIndex() == equipmentNum || (mounted != null && (mounted.equals(m1) || mounted.equals(m2)))) {
if(nhits < hits) {
cs.setHit(true);
cs.setDestroyed(true);
cs.setRepairable(true);
cs.setMissing(false);
nhits++;
}
}
}
return nhits;
}
public void destroySystem(int type, int equipmentNum) {
for (int loc = 0; loc < getEntity().locations(); loc++) {
destroySystem(type, equipmentNum, loc);
}
}
public void destroySystem(int type, int equipmentNum, int loc) {
for (int i = 0; i < getEntity().getNumberOfCriticals(loc); i++) {
CriticalSlot cs = getEntity().getCritical(loc, i);
// ignore empty & system slots
if ((cs == null) || (cs.getType() != type)) {
continue;
}
Mounted mounted = getEntity().getEquipment(equipmentNum);
Mounted m1 = cs.getMount();
Mounted m2 = cs.getMount2();
if (cs.getIndex() == equipmentNum || (mounted != null && (mounted.equals(m1) || mounted.equals(m2)))) {
cs.setHit(true);
cs.setDestroyed(true);
cs.setRepairable(false);
cs.setMissing(false);
}
}
}
public void destroySystem(int type, int equipmentNum, int loc, int hits) {
int nhits = 0;
for (int i = 0; i < getEntity().getNumberOfCriticals(loc); i++) {
CriticalSlot cs = getEntity().getCritical(loc, i);
// ignore empty & system slots
if ((cs == null) || (cs.getType() != type)) {
continue;
}
Mounted mounted = getEntity().getEquipment(equipmentNum);
Mounted m1 = cs.getMount();
Mounted m2 = cs.getMount2();
if (cs.getIndex() == equipmentNum || (mounted != null && (mounted.equals(m1) || mounted.equals(m2)))) {
if(nhits < hits) {
cs.setHit(true);
cs.setDestroyed(true);
cs.setRepairable(false);
cs.setMissing(false);
nhits++;
} else {
cs.setHit(false);
cs.setDestroyed(false);
cs.setRepairable(true);
cs.setMissing(false);
}
}
}
}
public void repairSystem(int type, int equipmentNum) {
for (int loc = 0; loc < getEntity().locations(); loc++) {
repairSystem(type, equipmentNum, loc);
}
}
public void repairSystem(int type, int equipmentNum, int loc) {
for (int i = 0; i < getEntity().getNumberOfCriticals(loc); i++) {
CriticalSlot cs = getEntity().getCritical(loc, i);
// ignore empty & system slots
if ((cs == null) || (cs.getType() != type)) {
continue;
}
Mounted mounted = getEntity().getEquipment(equipmentNum);
Mounted m1 = cs.getMount();
Mounted m2 = cs.getMount2();
if (cs.getIndex() == equipmentNum || (mounted != null && (mounted.equals(m1) || mounted.equals(m2)))) {
cs.setHit(false);
cs.setMissing(false);
cs.setDestroyed(false);
cs.setBreached(false);
cs.setRepairable(true);
}
}
}
public boolean isDamaged() {
return getDamageState() != Entity.DMG_NONE;
}
public String getHeatSinkTypeString(int year) {
BigInteger heatSinkType = MiscType.F_HEAT_SINK;
boolean heatSinkIsClanTechBase = false;
for (Mounted mounted : getEntity().getEquipment()) {
// Also goes through heat sinks inside the engine
EquipmentType etype = mounted.getType();
boolean isHeatSink = false;
if (etype instanceof MiscType) {
if (etype.hasFlag(MiscType.F_LASER_HEAT_SINK)) {
heatSinkType = MiscType.F_LASER_HEAT_SINK;
isHeatSink = true;
} else if (etype.hasFlag(MiscType.F_DOUBLE_HEAT_SINK)) {
heatSinkType = MiscType.F_DOUBLE_HEAT_SINK;
isHeatSink = true;
} else if (etype.hasFlag(MiscType.F_HEAT_SINK)) {
heatSinkType = MiscType.F_HEAT_SINK;
isHeatSink = true;
}
}
if (isHeatSink) {
if (TechConstants.getTechName(etype.getTechLevel(year)).equals(
"Inner Sphere"))
heatSinkIsClanTechBase = false;
else if (TechConstants.getTechName(etype.getTechLevel(year))
.equals("Clan"))
heatSinkIsClanTechBase = true;
break;
}
}
String heatSinkTypeString = heatSinkIsClanTechBase ? "(CL) " : "(IS) ";
if (heatSinkType == MiscType.F_LASER_HEAT_SINK)
heatSinkTypeString += "Laser Heat Sink";
else if (heatSinkType == MiscType.F_DOUBLE_HEAT_SINK)
heatSinkTypeString += "Double Heat Sink";
else if (heatSinkType == MiscType.F_HEAT_SINK)
heatSinkTypeString += "Heat Sink";
return heatSinkTypeString;
}
public long getSellValue() {
long partsValue = 0;
for(Part part : parts) {
partsValue += part.getActualValue() * part.getQuantity();
}
//TODO: we need to adjust this for equipment that doesn't show up as parts
//Spacecraft need: drive unit, computer, and bridge
if(entity instanceof SmallCraft || entity instanceof Jumpship) {
//bridge
partsValue += 200000 + 10 * entity.getWeight();
//computer
partsValue += 200000;
//drive unit
partsValue += 500 * entity.getOriginalWalkMP() * entity.getWeight()/100;
// KF Drive, Docking Collars, etc...
if (entity instanceof Jumpship && !(entity instanceof SpaceStation)) {
Jumpship js = (Jumpship) entity;
double driveCost = 0;
// coil
driveCost += 60000000 + (75000000 * js.getDocks());
// initiator
driveCost += 25000000 + (5000000 * js.getDocks());
// controller
driveCost += 50000000;
// tankage
driveCost += 50000 * js.getKFIntegrity();
// sail
driveCost += 50000 * (30 + (js.getWeight() / 7500));
// charging system
driveCost += 500000 + (200000 * js.getDocks());
// compact core
if (js instanceof Warship) {
driveCost *= 5;
}
// lithium fusion?
if (js.hasLF()) {
driveCost *= 3;
}
// Drive Support Systems
if (js instanceof Warship) {
driveCost += 20000000 * (50 + js.getWeight() / 10000);
} else {
driveCost += 10000000 * (js.getWeight() / 10000);
}
partsValue += driveCost;
// Docking Collars
partsValue += 100000 * js.getDocks();
// HPG
if (js.hasHPG()) {
partsValue += 1000000000;
}
// fuel tanks
partsValue += 200 * js.getFuel() / js.getFuelPerTon();
// armor
partsValue += js.getArmorWeight(js.locations()) * EquipmentType.getArmorCost(js.getArmorType(0));
// heat sinks
int sinkCost = 2000 + 4000 * js.getHeatType();// == HEAT_DOUBLE ? 6000:
// 2000;
partsValue += sinkCost * js.getHeatSinks();
// grav deck
partsValue += 5000000 * js.getGravDeck();
partsValue += 10000000 * js.getGravDeckLarge();
partsValue += 40000000 * js.getGravDeckHuge();
// get bays
int baydoors = 0;
int bayCost = 0;
for (Bay next : js.getTransportBays()) {
baydoors += next.getDoors();
if ((next instanceof MechBay) || (next instanceof ASFBay) || (next instanceof SmallCraftBay)) {
bayCost += 20000 * next.getCapacity();
}
if ((next instanceof LightVehicleBay) || (next instanceof HeavyVehicleBay)) {
bayCost += 20000 * next.getCapacity();
}
}
partsValue += bayCost + baydoors * 1000;
// life boats and escape pods
partsValue += 5000 * (js.getLifeBoats() + js.getEscapePods());
}
}
//protomeks: heat sinks are unhittable
if(entity instanceof Protomech) {
int sinks = 0;
for (Mounted mount : entity.getWeaponList()) {
if (mount.getType().hasFlag(WeaponType.F_ENERGY)) {
WeaponType wtype = (WeaponType) mount.getType();
sinks += wtype.getHeat();
}
}
partsValue += 2000 * sinks;
}
return (long)(partsValue * getUnitCostMultiplier());
}
public double getCargoCapacity() {
double capacity = 0;
for (Bay bay : entity.getTransportBays()) {
if (bay instanceof CargoBay) {
capacity += bay.getCapacity();
}
if (bay instanceof PillionSeatCargoBay) {
capacity += bay.getCapacity();
}
if (bay instanceof StandardSeatCargoBay) {
capacity += bay.getCapacity();
}
}
return capacity;
}
public double getRefrigeratedCargoCapacity() {
double capacity = 0;
for (Bay bay : entity.getTransportBays()) {
if (bay instanceof RefrigeratedCargoBay) {
capacity += bay.getCapacity();
}
}
return capacity;
}
public double getLiquidCargoCapacity() {
double capacity = 0;
for (Bay bay : entity.getTransportBays()) {
if (bay instanceof LiquidCargoBay) {
capacity += bay.getCapacity();
}
}
return capacity;
}
public double getLivestockCargoCapacity() {
double capacity = 0;
for (Bay bay : entity.getTransportBays()) {
if (bay instanceof LivestockCargoBay) {
capacity += bay.getCapacity();
}
}
return capacity;
}
public double getInsulatedCargoCapacity() {
double capacity = 0;
for (Bay bay : entity.getTransportBays()) {
if (bay instanceof InsulatedCargoBay) {
capacity += bay.getCapacity();
}
}
return capacity;
}
public int getDocks() {
return getEntity().getDocks();
}
public int getLightVehicleCapacity() {
int bays = 0;
for (Bay b : getEntity().getTransportBays()) {
if (b instanceof LightVehicleBay) {
bays += b.getCapacity();
}
}
return bays;
}
public int getHeavyVehicleCapacity() {
int bays = 0;
for (Bay b : getEntity().getTransportBays()) {
if (b instanceof HeavyVehicleBay) {
bays += b.getCapacity();
}
}
return bays;
}
public int getBattleArmorCapacity() {
int bays = 0;
for (Bay b : getEntity().getTransportBays()) {
if (b instanceof BattleArmorBay) {
bays += b.getCapacity();
}
}
return bays;
}
public int getInfantryCapacity() {
int bays = 0;
for (Bay b : getEntity().getTransportBays()) {
if (b instanceof InfantryBay) {
bays += b.getCapacity();
}
}
return bays;
}
public int getASFCapacity() {
int bays = 0;
for (Bay b : getEntity().getTransportBays()) {
if (b instanceof ASFBay) {
bays += b.getCapacity();
}
}
return bays;
}
public int getSmallCraftCapacity() {
int bays = 0;
for (Bay b : getEntity().getTransportBays()) {
if (b instanceof SmallCraftBay) {
bays += b.getCapacity();
}
}
return bays;
}
public int getMechCapacity() {
int bays = 0;
for (Bay b : getEntity().getTransportBays()) {
if (b instanceof MechBay) {
bays += b.getCapacity();
}
}
return bays;
}
public int getProtomechCapacity() {
int bays = 0;
for (Bay b : getEntity().getTransportBays()) {
if (b instanceof ProtomechBay) {
bays += b.getCapacity();
}
}
return bays;
}
public double getUnitCostMultiplier() {
double multiplier = 1.0;
if(!isRepairable()) {
//if the unit is not repairable, set it as equal to its parts separately
//this is not RAW, but not really a way to make that work and this makes more sense
//although we might want to adjust it downward because of the labor cost of salvaging
return 1.0;
}
double tonnage = 100;
if(entity instanceof Mech && ((Mech)entity).isIndustrial()) {
tonnage = 400;
}
else if(entity instanceof VTOL) {
tonnage = 30;
}
else if(entity instanceof Tank) {
if(entity.getMovementMode() == EntityMovementMode.WHEELED || entity.getMovementMode() == EntityMovementMode.NAVAL) {
tonnage = 200;
}
else if(entity.getMovementMode() == EntityMovementMode.HOVER || entity.getMovementMode() == EntityMovementMode.SUBMARINE) {
tonnage = 50;
}
else if(entity.getMovementMode() == EntityMovementMode.HYDROFOIL) {
tonnage = 75;
}
else if(entity.getMovementMode() == EntityMovementMode.WIGE) {
tonnage = 25;
}
}
else if(entity instanceof Dropship) {
if(((Aero)entity).isSpheroid()) {
multiplier = 28;
} else {
multiplier = 36;
}
}
else if(entity instanceof SmallCraft) {
tonnage = 50;
}
else if(entity instanceof SpaceStation) {
multiplier = 5;
}
else if(entity instanceof Warship) {
multiplier = 2;
}
else if(entity instanceof Jumpship) {
multiplier = 1.25;
}
else if(entity instanceof Aero) {
tonnage = 200;
}
if(!(entity instanceof Infantry) && !(entity instanceof Dropship) && !(entity instanceof Jumpship)) {
multiplier = 1 + (entity.getWeight() / tonnage);
}
if(entity.isOmni()) {
multiplier *= 1.25;
}
return multiplier;
}
public long getBuyCost() {
long cost = (long) Math.round(getEntity().getCost(false));
if(entity instanceof Infantry) {
cost = (long) Math.round(getEntity().getAlternateCost());
}
if(entity.isClan()) {
cost *= campaign.getCampaignOptions().getClanPriceModifier();
}
return cost;
}
public int getDamageState() {
return getDamageState(getEntity());
}
public void writeToXml(PrintWriter pw1, int indentLvl) {
pw1.println(MekHqXmlUtil.indentStr(indentLvl) + "<unit id=\"" + id.toString()
+ "\" type=\"" + this.getClass().getName() + "\">");
pw1.println(MekHqXmlUtil.writeEntityToXmlString(entity, indentLvl+1, campaign.getEntities()));
for(UUID did : drivers) {
pw1.println(MekHqXmlUtil.indentStr(indentLvl + 1) + "<driverId>"
+ did.toString() + "</driverId>");
}
for(UUID gid : gunners) {
pw1.println(MekHqXmlUtil.indentStr(indentLvl + 1) + "<gunnerId>"
+ gid.toString() + "</gunnerId>");
}
for(UUID vid : vesselCrew) {
pw1.println(MekHqXmlUtil.indentStr(indentLvl + 1) + "<vesselCrewId>"
+ vid.toString() + "</vesselCrewId>");
}
if(null != navigator) {
pw1.println(MekHqXmlUtil.indentStr(indentLvl+1)
+"<navigatorId>"
+navigator.toString()
+"</navigatorId>");
}
if(null != tech) {
pw1.println(MekHqXmlUtil.indentStr(indentLvl+1)
+"<techId>"
+tech.toString()
+"</techId>");
}
pw1.println(MekHqXmlUtil.indentStr(indentLvl + 1) + "<salvaged>"
+ salvaged + "</salvaged>");
pw1.println(MekHqXmlUtil.indentStr(indentLvl + 1) + "<site>" + site
+ "</site>");
pw1.println(MekHqXmlUtil.indentStr(indentLvl+1)
+"<forceId>"
+forceId
+"</forceId>");
pw1.println(MekHqXmlUtil.indentStr(indentLvl+1)
+"<scenarioId>"
+scenarioId
+"</scenarioId>");
pw1.println(MekHqXmlUtil.indentStr(indentLvl+1)
+"<daysToArrival>"
+daysToArrival
+"</daysToArrival>");
pw1.println(MekHqXmlUtil.indentStr(indentLvl+1)
+"<daysSinceMaintenance>"
+daysSinceMaintenance
+"</daysSinceMaintenance>");
pw1.println(MekHqXmlUtil.indentStr(indentLvl+1)
+"<daysActivelyMaintained>"
+daysActivelyMaintained
+"</daysActivelyMaintained>");
pw1.println(MekHqXmlUtil.indentStr(indentLvl+1)
+"<astechDaysMaintained>"
+astechDaysMaintained
+"</astechDaysMaintained>");
pw1.println(MekHqXmlUtil.indentStr(indentLvl+1)
+"<mothballTime>"
+mothballTime
+"</mothballTime>");
pw1.println(MekHqXmlUtil.indentStr(indentLvl+1)
+"<mothballed>"
+mothballed
+"</mothballed>");
pw1.println(MekHqXmlUtil.indentStr(indentLvl+1)
+"<fluffName>"
+MekHqXmlUtil.escape(fluffName)
+"</fluffName>");
pw1.println(MekHqXmlUtil.indentStr(indentLvl+1)
+"<history>"
+MekHqXmlUtil.escape(history)
+"</history>");
if(null != refit) {
refit.writeToXml(pw1, indentLvl+1);
}
if(null != lastMaintenanceReport && campaign.getCampaignOptions().checkMaintenance()) {
pw1.println(MekHqXmlUtil.indentStr(indentLvl+1)
+"<lastMaintenanceReport><![CDATA["
+lastMaintenanceReport
+"]]></lastMaintenanceReport>");
}
pw1.println(MekHqXmlUtil.indentStr(indentLvl) + "</unit>");
}
public static Unit generateInstanceFromXML(Node wn, Version version) {
Unit retVal = new Unit();
NamedNodeMap attrs = wn.getAttributes();
Node idNode = attrs.getNamedItem("id");
if(version.getMajorVersion() == 0 && version.getMinorVersion() < 2 && version.getSnapshot() < 14) {
retVal.oldId = Integer.parseInt(idNode.getTextContent());
} else {
retVal.id = UUID.fromString(idNode.getTextContent());
}
// Okay, now load Part-specific fields!
NodeList nl = wn.getChildNodes();
try {
for (int x=0; x<nl.getLength(); x++) {
Node wn2 = nl.item(x);
if (wn2.getNodeName().equalsIgnoreCase("site")) {
retVal.site = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("pilotId")) {
retVal.pilotId = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("daysToArrival")) {
retVal.daysToArrival = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("daysActivelyMaintained")) {
retVal.daysActivelyMaintained = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("daysSinceMaintenance")) {
retVal.daysSinceMaintenance = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("mothballTime")) {
retVal.mothballTime = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("astechDaysMaintained")) {
retVal.astechDaysMaintained = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("driverId")) {
if(version.getMajorVersion() == 0 && version.getMinorVersion() < 2 && version.getSnapshot() < 14) {
retVal.oldDrivers.add(Integer.parseInt(wn2.getTextContent()));
} else {
retVal.drivers.add(UUID.fromString(wn2.getTextContent()));
}
} else if (wn2.getNodeName().equalsIgnoreCase("gunnerId")) {
if(version.getMajorVersion() == 0 && version.getMinorVersion() < 2 && version.getSnapshot() < 14) {
retVal.oldGunners.add(Integer.parseInt(wn2.getTextContent()));
} else {
retVal.gunners.add(UUID.fromString(wn2.getTextContent()));
}
} else if (wn2.getNodeName().equalsIgnoreCase("vesselCrewId")) {
if(version.getMajorVersion() == 0 && version.getMinorVersion() < 2 && version.getSnapshot() < 14) {
retVal.oldVesselCrew.add(Integer.parseInt(wn2.getTextContent()));
} else {
retVal.vesselCrew.add(UUID.fromString(wn2.getTextContent()));
}
} else if (wn2.getNodeName().equalsIgnoreCase("navigatorId")) {
if(version.getMajorVersion() == 0 && version.getMinorVersion() < 2 && version.getSnapshot() < 14) {
retVal.oldNavigator = Integer.parseInt(wn2.getTextContent());
} else {
if(!wn2.getTextContent().equals("null")) {
retVal.navigator = UUID.fromString(wn2.getTextContent());
}
}
}
else if (wn2.getNodeName().equalsIgnoreCase("techId")) {
if(!wn2.getTextContent().equals("null")) {
retVal.tech = UUID.fromString(wn2.getTextContent());
}
}
else if (wn2.getNodeName().equalsIgnoreCase("forceId")) {
retVal.forceId = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("scenarioId")) {
retVal.scenarioId = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("salvaged")) {
if (wn2.getTextContent().equalsIgnoreCase("true"))
retVal.salvaged = true;
else
retVal.salvaged = false;
} else if (wn2.getNodeName().equalsIgnoreCase("mothballed")) {
if (wn2.getTextContent().equalsIgnoreCase("true"))
retVal.mothballed = true;
else
retVal.mothballed = false;
} else if (wn2.getNodeName().equalsIgnoreCase("entity")) {
retVal.entity = MekHqXmlUtil.getEntityFromXmlString(wn2);
} else if (wn2.getNodeName().equalsIgnoreCase("refit")) {
retVal.refit = Refit.generateInstanceFromXML(wn2, retVal, version);
} else if (wn2.getNodeName().equalsIgnoreCase("history")) {
retVal.history = wn2.getTextContent();
} else if (wn2.getNodeName().equalsIgnoreCase("fluffName")) {
retVal.fluffName = wn2.getTextContent();
} else if (wn2.getNodeName().equalsIgnoreCase("lastMaintenanceReport")) {
retVal.lastMaintenanceReport = wn2.getTextContent();
}
}
} catch (Exception ex) {
// Doh!
MekHQ.logError(ex);
}
if (retVal.id == null) {
MekHQ.logMessage("ID not pre-defined; generating unit's ID.", 5);
retVal.id = UUID.randomUUID();
}
// Protection for old broken campaign files
// Also for entities that do not have an external ID to match the UUID
if (retVal.entity.getExternalIdAsString().equals("-1")
|| !(retVal.entity.getExternalIdAsString().equals(retVal.id.toString()))) {
retVal.entity.setExternalIdAsString(retVal.id.toString());
}
return retVal;
}
/**
* This function returns an html-coded list that says what
* quirks are enabled for this unit
* @return
*/
public String getQuirksList() {
String quirkString = "";
boolean first = true;
if(null != getEntity().getGame() && getEntity().getGame().getOptions().booleanOption("stratops_quirks")) {
for (Enumeration<IOptionGroup> i = getEntity().getQuirks().getGroups(); i.hasMoreElements();) {
IOptionGroup group = i.nextElement();
for (Enumeration<IOption> j = group.getOptions(); j.hasMoreElements();) {
IOption quirk = j.nextElement();
if (quirk.booleanValue()) {
if(first) {
first = false;
} else {
quirkString += "\n";
}
quirkString += quirk.getDisplayableNameWithValue();
}
}
}
}
if(quirkString.equals("")) {
return null;
}
return quirkString;
}
public void acquireQuirk(String name, Object value) {
for (Enumeration<IOption> i = getEntity().getQuirks().getOptions(); i.hasMoreElements();) {
IOption ability = i.nextElement();
if(ability.getName().equals(name)) {
ability.setValue(value);
}
}
}
/**
* The weekly maintenance cycle combined with a user defined maintenance cycle length
* is confusing and difficult to manage so lets just make maintenance costs relative
* to the length of the maintenance cycle that the user defined
* @return
*/
public int getMaintenanceCost() {
return (int)Math.ceil(getWeeklyMaintenanceCost() * (campaign.getCampaignOptions().getMaintenanceCycleDays() / 7.0));
}
public int getWeeklyMaintenanceCost() {
Entity en = getEntity();
Boolean isOmni = en.isOmni();
double mCost = 0;
double value = 0;
//we will assume sale value for now, but make this customizable
if(campaign.getCampaignOptions().useEquipmentContractSaleValue()) {
value += getSellValue();
} else {
value += getBuyCost();
}
if (campaign.getCampaignOptions().usePercentageMaint()) {
if(en instanceof Mech) {
mCost = value * 0.02;
} else if(en instanceof Warship) {
mCost = value * 0.07;
} else if(en instanceof Jumpship) {
mCost = value * 0.06;
} else if(en instanceof Dropship) {
mCost = value * 0.05;
} else if(en instanceof ConvFighter) {
mCost = value * 0.03;
} else if(en instanceof Aero) {
mCost = value * 0.04;
} else if(en instanceof VTOL) {
mCost = value * 0.02;
} else if(en instanceof Tank) {
mCost = value * 0.015;
} else if(en instanceof BattleArmor) {
mCost = value * 0.03;
} else if(en instanceof Infantry) {
mCost = value * 0.005;
}
// Mothballed Units cost only 10% to maintain
if(isMothballed()) {
mCost *= .1;
}
} else {
if(en instanceof Mech) {
if(isOmni) {
return 100;
} else {
return 75;
}
} else if(en instanceof Warship) {
return 5000;
} else if(en instanceof Jumpship) {
return 800;
} else if(en instanceof Dropship) {
return 500;
} else if(en instanceof ConvFighter) {
return 50;
} else if(en instanceof Aero) {
if(isOmni) {
return 125;
} else {
return 65;
}
} else if(en instanceof VTOL) {
return 65;
} else if(en instanceof Tank) {
return 25;
} else if(en instanceof BattleArmor) {
return ((BattleArmor)en).getTroopers() * 50;
} else if(en instanceof Infantry) {
return ((Infantry)en).getSquadN()*10;
}
}
return (int)Math.ceil(mCost/52);
}
public void addPart(Part part) {
part.setUnit(this);
parts.add(part);
}
/**
* This will check a unit for certain parts and if they are missing, it will create a new
* version and update its condition. checking for existing parts makes this a more complicated
* method but it also ensures that you can call this at any time and you won't overwrite existing
* parts
*/
public void initializeParts(boolean addParts) {
int erating = 0;
if(!(entity instanceof FighterSquadron) && (null != entity.getEngine())) {
erating = entity.getEngine().getRating();
}
ArrayList<Part> partsToAdd = new ArrayList<Part>();
Part gyro = null;
Part engine = null;
Part lifeSupport = null;
Part sensor = null;
Part cockpit = null;
Part rightHand = null;
Part rightLowerArm = null;
Part rightUpperArm = null;
Part leftHand = null;
Part leftLowerArm = null;
Part leftUpperArm = null;
Part rightFoot = null;
Part rightLowerLeg = null;
Part rightUpperLeg = null;
Part leftFoot = null;
Part leftLowerLeg = null;
Part leftUpperLeg = null;
Part rightFrontFoot = null;
Part rightLowerFrontLeg = null;
Part rightUpperFrontLeg = null;
Part leftFrontFoot = null;
Part leftLowerFrontLeg = null;
Part leftUpperFrontLeg = null;
Part structuralIntegrity = null;
Part[] locations = new Part[entity.locations()];
Part[] armor = new Part[entity.locations()];
Part[] armorRear = new Part[entity.locations()];
Part[] stabilisers = new Part[entity.locations()];
Hashtable<Integer,Part> equipParts = new Hashtable<Integer,Part>();
Hashtable<Integer,Part> ammoParts = new Hashtable<Integer,Part>();
Hashtable<Integer,Part> heatSinks = new Hashtable<Integer,Part>();
Hashtable<Integer,Part> jumpJets = new Hashtable<Integer,Part>();
Hashtable<Integer,Part[]> baEquipParts = new Hashtable<Integer, Part[]>();
Part motiveSystem = null;
Part avionics = null;
Part fcs = null;
Part landingGear = null;
Part turretLock = null;
ArrayList<Part> aeroHeatSinks = new ArrayList<Part>();
int podAeroHeatSinks = 0;
Part motiveType = null;
Part primaryW = null;
Part secondaryW = null;
Part infantryArmor = null;
Part dropCollar = null;
Part protoLeftArmActuator = null;
Part protoRightArmActuator = null;
Part protoLegsActuator = null;
ArrayList<Part> protoJumpJets = new ArrayList<Part>();
Part aeroThrustersLeft = null;
Part aeroThrustersRight = null;
for(Part part : parts) {
if(part instanceof MekGyro || part instanceof MissingMekGyro) {
gyro = part;
} else if(part instanceof EnginePart || part instanceof MissingEnginePart) {
//reverse compatability check, spaceships get different engines
if(!(entity instanceof SmallCraft || entity instanceof Jumpship)) {
engine = part;
}
} else if(part instanceof SpacecraftEngine || part instanceof MissingSpacecraftEngine) {
engine = part;
} else if(part instanceof MekLifeSupport || part instanceof MissingMekLifeSupport) {
lifeSupport = part;
} else if(part instanceof AeroLifeSupport || part instanceof MissingAeroLifeSupport) {
lifeSupport = part;
} else if(part instanceof MekSensor || part instanceof MissingMekSensor) {
sensor = part;
} else if(part instanceof ProtomekSensor || part instanceof MissingProtomekSensor) {
sensor = part;
} else if(part instanceof MekCockpit || part instanceof MissingMekCockpit) {
cockpit = part;
} else if(part instanceof VeeSensor || part instanceof MissingVeeSensor) {
sensor = part;
} else if(part instanceof InfantryMotiveType) {
motiveType = part;
} else if(part instanceof InfantryArmorPart) {
infantryArmor = part;
} else if(part instanceof InfantryWeaponPart) {
if(((InfantryWeaponPart)part).isPrimary()) {
primaryW = part;
} else {
secondaryW = part;
}
} else if(part instanceof StructuralIntegrity) {
structuralIntegrity = part;
} else if(part instanceof MekLocation) {
locations[((MekLocation)part).getLoc()] = part;
} else if(part instanceof MissingMekLocation) {
locations[part.getLocation()] = part;
} else if(part instanceof TankLocation) {
locations[((TankLocation)part).getLoc()] = part;
} else if(part instanceof Rotor) {
locations[((Rotor)part).getLoc()] = part;
} else if(part instanceof MissingRotor) {
locations[VTOL.LOC_ROTOR] = part;
} else if(part instanceof Turret) {
locations[((Turret)part).getLoc()] = part;
} else if(part instanceof MissingTurret) {
locations[Tank.LOC_TURRET] = part;
} else if(part instanceof ProtomekLocation) {
locations[((ProtomekLocation)part).getLoc()] = part;
} else if(part instanceof MissingProtomekLocation) {
locations[((MissingProtomekLocation)part).getLoc()] = part;
} else if(part instanceof BattleArmorSuit) {
locations[((BattleArmorSuit)part).getTrooper()] = part;
} else if(part instanceof MissingBattleArmorSuit) {
locations[((MissingBattleArmorSuit)part).getTrooper()] = part;
} else if(part instanceof Armor) {
if(((Armor)part).isRearMounted()) {
armorRear[((Armor)part).getLocation()] = (Armor)part;
} else {
armor[((Armor)part).getLocation()] = (Armor)part;
}
} else if(part instanceof ProtomekArmor) {
armor[((ProtomekArmor)part).getLocation()] = (ProtomekArmor)part;
} else if(part instanceof BaArmor) {
armor[((BaArmor)part).getLocation()] = (BaArmor)part;
} else if(part instanceof VeeStabiliser) {
stabilisers[((VeeStabiliser)part).getLocation()] = part;
} else if(part instanceof MissingVeeStabiliser) {
stabilisers[((MissingVeeStabiliser)part).getLocation()] = part;
} else if(part instanceof AmmoBin) {
ammoParts.put(((AmmoBin)part).getEquipmentNum(), part);
} else if(part instanceof MissingAmmoBin) {
ammoParts.put(((MissingAmmoBin)part).getEquipmentNum(), part);
} else if(part instanceof HeatSink) {
heatSinks.put(((HeatSink)part).getEquipmentNum(), part);
} else if(part instanceof MissingHeatSink) {
heatSinks.put(((MissingHeatSink)part).getEquipmentNum(), part);
} else if(part instanceof JumpJet) {
jumpJets.put(((JumpJet)part).getEquipmentNum(), part);
} else if(part instanceof MissingJumpJet) {
jumpJets.put(((MissingJumpJet)part).getEquipmentNum(), part);
} else if(part instanceof BattleArmorEquipmentPart) {
Part[] parts = baEquipParts.get(((BattleArmorEquipmentPart)part).getEquipmentNum());
if(null == parts) {
parts = new Part[((BattleArmor)entity).getSquadSize()];
}
parts[((BattleArmorEquipmentPart)part).getTrooper()-BattleArmor.LOC_TROOPER_1] = part;
baEquipParts.put(((BattleArmorEquipmentPart)part).getEquipmentNum(), parts);
} else if(part instanceof MissingBattleArmorEquipmentPart) {
Part[] parts = baEquipParts.get(((MissingBattleArmorEquipmentPart)part).getEquipmentNum());
if(null == parts) {
parts = new Part[((BattleArmor)entity).getSquadSize()];
}
parts[((MissingBattleArmorEquipmentPart)part).getTrooper()-BattleArmor.LOC_TROOPER_1] = part;
baEquipParts.put(((MissingBattleArmorEquipmentPart)part).getEquipmentNum(), parts);
} else if(part instanceof EquipmentPart) {
equipParts.put(((EquipmentPart)part).getEquipmentNum(), part);
} else if(part instanceof MissingEquipmentPart) {
equipParts.put(((MissingEquipmentPart)part).getEquipmentNum(), part);
} else if(part instanceof MekActuator || part instanceof MissingMekActuator) {
int type = -1;
int loc = -1;
if(part instanceof MekActuator) {
type = ((MekActuator)part).getType();
loc = ((MekActuator)part).getLocation();
} else {
type = ((MissingMekActuator)part).getType();
loc = ((MissingMekActuator)part).getLocation();
}
if(type == Mech.ACTUATOR_UPPER_ARM) {
if(loc == Mech.LOC_RARM) {
rightUpperArm = part;
} else {
leftUpperArm = part;
}
} else if(type == Mech.ACTUATOR_LOWER_ARM) {
if(loc == Mech.LOC_RARM) {
rightLowerArm = part;
} else {
leftLowerArm = part;
}
} else if(type == Mech.ACTUATOR_HAND) {
if(loc == Mech.LOC_RARM) {
rightHand = part;
} else {
leftHand = part;
}
} else if(type == Mech.ACTUATOR_UPPER_LEG) {
if(loc == Mech.LOC_LARM) {
leftUpperFrontLeg = part;
} else if(loc == Mech.LOC_RARM) {
rightUpperFrontLeg = part;
} else if(loc == Mech.LOC_RLEG) {
rightUpperLeg = part;
} else {
leftUpperLeg = part;
}
} else if(type == Mech.ACTUATOR_LOWER_LEG) {
if(loc == Mech.LOC_LARM) {
leftLowerFrontLeg = part;
} else if(loc == Mech.LOC_RARM) {
rightLowerFrontLeg = part;
} else if(loc == Mech.LOC_RLEG) {
rightLowerLeg = part;
} else {
leftLowerLeg = part;
}
} else if(type == Mech.ACTUATOR_FOOT) {
if(loc == Mech.LOC_LARM) {
leftFrontFoot = part;
} else if(loc == Mech.LOC_RARM) {
rightFrontFoot = part;
} else if(loc == Mech.LOC_RLEG) {
rightFoot = part;
} else {
leftFoot = part;
}
}
} else if(part instanceof Avionics || part instanceof MissingAvionics) {
avionics = part;
} else if(part instanceof FireControlSystem || part instanceof MissingFireControlSystem) {
fcs = part;
//for reverse compatability, calculate costs
if(part instanceof FireControlSystem) {
((FireControlSystem)fcs).calculateCost();
}
} else if(part instanceof AeroSensor || part instanceof MissingAeroSensor) {
sensor = part;
} else if(part instanceof LandingGear || part instanceof MissingLandingGear) {
landingGear = part;
} else if(part instanceof AeroHeatSink || part instanceof MissingAeroHeatSink) {
aeroHeatSinks.add(part);
if (part.isOmniPodded()) {
podAeroHeatSinks++;
}
} else if(part instanceof MotiveSystem) {
motiveSystem = part;
} else if(part instanceof TurretLock) {
turretLock = part;
} else if(part instanceof DropshipDockingCollar || part instanceof MissingDropshipDockingCollar) {
dropCollar = part;
} else if(part instanceof ProtomekArmActuator || part instanceof MissingProtomekArmActuator) {
int loc = -1;
if(part instanceof ProtomekArmActuator) {
loc = ((ProtomekArmActuator)part).getLocation();
} else {
loc = ((MissingProtomekArmActuator)part).getLocation();
}
if(loc == Protomech.LOC_LARM) {
protoLeftArmActuator = part;
}
else if(loc == Protomech.LOC_RARM) {
protoRightArmActuator = part;
}
} else if(part instanceof ProtomekLegActuator || part instanceof MissingProtomekLegActuator) {
protoLegsActuator = part;
} else if(part instanceof ProtomekJumpJet || part instanceof MissingProtomekJumpJet) {
protoJumpJets.add(part);
} else if (part instanceof Thrusters || part instanceof MissingThrusters) {
if (((Thrusters) part).isLeftThrusters()) {
aeroThrustersLeft = ((Thrusters) part);
} else {
aeroThrustersRight = ((Thrusters) part);
}
}
part.updateConditionFromPart();
}
//now check to see what is null
for(int i = 0; i<locations.length; i++) {
if(entity.getOInternal(i) == IArmorState.ARMOR_NA) {
//this is not a valid location, so we should skip it
continue;
}
if(null == locations[i]) {
if(entity instanceof Mech) {
MekLocation mekLocation = new MekLocation(i, (int) getEntity().getWeight(), getEntity().getStructureType(), hasTSM(), entity instanceof QuadMech, false, false, campaign);
addPart(mekLocation);
partsToAdd.add(mekLocation);
} else if(entity instanceof Protomech && i != Protomech.LOC_NMISS) {
ProtomekLocation protomekLocation = new ProtomekLocation(i, (int) getEntity().getWeight(), getEntity().getStructureType(), ((Protomech)getEntity()).hasMyomerBooster(), entity instanceof QuadMech, campaign);
addPart(protomekLocation);
partsToAdd.add(protomekLocation);
} else if(entity instanceof Tank && i != Tank.LOC_BODY) {
if(entity instanceof VTOL) {
if (i == VTOL.LOC_ROTOR) {
Rotor rotor = new Rotor((int)getEntity().getWeight(), campaign);
addPart(rotor);
partsToAdd.add(rotor);
} else if (i == VTOL.LOC_TURRET) {
if(((VTOL)entity).hasNoTurret()) {
continue;
}
Turret turret = new Turret(i, (int)getEntity().getWeight(), campaign);
addPart(turret);
partsToAdd.add(turret);
} else if (i == VTOL.LOC_TURRET_2) {
if(((VTOL)entity).hasNoDualTurret()) {
continue;
}
Turret turret = new Turret(i, (int)getEntity().getWeight(), campaign);
addPart(turret);
partsToAdd.add(turret);
}
} else if(i == Tank.LOC_TURRET) {
if(((Tank)entity).hasNoTurret()) {
continue;
}
Turret turret = new Turret(i, (int)getEntity().getWeight(), campaign);
addPart(turret);
partsToAdd.add(turret);
} else if(i == Tank.LOC_TURRET_2) {
if(((Tank)entity).hasNoDualTurret()) {
continue;
}
Turret turret = new Turret(i, (int)getEntity().getWeight(), campaign);
addPart(turret);
partsToAdd.add(turret);
} else {
TankLocation tankLocation = new TankLocation(i, (int) getEntity().getWeight(), campaign);
addPart(tankLocation);
partsToAdd.add(tankLocation);
}
} else if(entity instanceof BattleArmor && i != 0 && i <= ((BattleArmor)entity).getSquadSize()) {
BattleArmorSuit baSuit = new BattleArmorSuit((BattleArmor)entity, i, campaign);
addPart(baSuit);
partsToAdd.add(baSuit);
}
}
if(null == armor[i]) {
if(entity instanceof Protomech) {
ProtomekArmor a = new ProtomekArmor((int) getEntity().getWeight(), getEntity().getOArmor(i, false), i, true, campaign);
addPart(a);
partsToAdd.add(a);
}
else if(entity instanceof BattleArmor) {
BaArmor a = new BaArmor((int) getEntity().getWeight(), getEntity().getOArmor(i, false), ((BattleArmor)entity).getArmorType(1), i, entity.isClan(), campaign);
addPart(a);
partsToAdd.add(a);
}
else {
Armor a = new Armor((int) getEntity().getWeight(), getEntity().getArmorType(i), getEntity().getOArmor(i, false), i, false, entity.isClanArmor(i), campaign);
addPart(a);
partsToAdd.add(a);
}
}
if(null == armorRear[i] && entity.hasRearArmor(i)) {
Armor a = new Armor((int) getEntity().getWeight(), getEntity().getArmorType(i), getEntity().getOArmor(i, true), i, true, entity.isClanArmor(i), campaign);
addPart(a);
partsToAdd.add(a);
}
if(entity instanceof Tank && null == stabilisers[i] && i != Tank.LOC_BODY) {
VeeStabiliser s = new VeeStabiliser((int)getEntity().getWeight(),i, campaign);
addPart(s);
partsToAdd.add(s);
}
}
for(Mounted m : entity.getEquipment()) {
if(m.getLocation() == Entity.LOC_NONE) {
//FIXME: is this ok? - are there any valid parts in LOC_NONE?
continue;
}
// We want to ignore weapon groups so that we don't get phantom weapons
if (m.isWeaponGroup()) {
continue;
}
// Anti-Mek attacks aren't actual parts
if (m.getType() instanceof InfantryAttack) {
continue;
}
if(!m.getType().isHittable()) {
//there are some kind of non-hittable parts we might want to include for cost calculations
if(!(m.getType() instanceof MiscType)) {
continue;
}
if(!(((MiscType)m.getType()).hasFlag(MiscType.F_BA_MANIPULATOR) ||
((MiscType)m.getType()).hasFlag(MiscType.F_BA_MEA) ||
((MiscType)m.getType()).hasFlag(MiscType.F_AP_MOUNT))) {
continue;
}
}
if(m.getType() instanceof AmmoType) {
int eqnum = entity.getEquipmentNum(m);
Part apart = ammoParts.get(eqnum);
int fullShots = ((AmmoType)m.getType()).getShots();
boolean oneShot = false;
if(m.getLocation() == Entity.LOC_NONE) {
fullShots = 1;
oneShot = true;
}
if(null == apart) {
if(entity instanceof BattleArmor) {
apart = new BattleArmorAmmoBin((int)entity.getWeight(), m.getType(), eqnum, ((BattleArmor)entity).getSquadSize() * (fullShots - m.getBaseShotsLeft()), oneShot, campaign);
} else {
apart = new AmmoBin((int)entity.getWeight(), m.getType(), eqnum,
fullShots - m.getBaseShotsLeft(), oneShot, m.isOmniPodMounted(), campaign);
}
addPart(apart);
partsToAdd.add(apart);
}
} else if(m.getType() instanceof MiscType && (m.getType().hasFlag(MiscType.F_HEAT_SINK) || m.getType().hasFlag(MiscType.F_DOUBLE_HEAT_SINK))) {
if(m.getLocation() == Entity.LOC_NONE) {
//heat sinks located in LOC_NONE are base unhittable heat sinks
continue;
}
int eqnum = entity.getEquipmentNum(m);
Part epart = heatSinks.get(eqnum);
if(null == epart) {
epart = new HeatSink((int)entity.getWeight(), m.getType(), eqnum,
m.isOmniPodMounted(), campaign);
addPart(epart);
partsToAdd.add(epart);
}
} else if(m.getType() instanceof MiscType && m.getType().hasFlag(MiscType.F_JUMP_JET)) {
int eqnum = entity.getEquipmentNum(m);
Part epart = jumpJets.get(eqnum);
if(null == epart) {
epart = new JumpJet((int)entity.getWeight(), m.getType(), eqnum,
m.isOmniPodMounted(), campaign);
addPart(epart);
partsToAdd.add(epart);
}
} else {
int eqnum = entity.getEquipmentNum(m);
EquipmentType type = m.getType();
if(entity instanceof BattleArmor) {
//for BattleArmor we have multiple parts per mount, one for each trooper
Part[] eparts = baEquipParts.get(eqnum);
for(int i = 0; i < ((BattleArmor)entity).getSquadSize(); i++) {
if(null == eparts || null == eparts[i]) {
Part epart = new BattleArmorEquipmentPart((int)entity.getWeight(), type, eqnum, i+BattleArmor.LOC_TROOPER_1, campaign);
addPart(epart);
partsToAdd.add(epart);
}
}
} else {
Part epart = equipParts.get(eqnum);
if(null == epart) {
if(type instanceof InfantryAttack) {
continue;
}
if(entity instanceof Infantry && !(entity instanceof BattleArmor)
&& m.getLocation() != Infantry.LOC_FIELD_GUNS) {
//don't add weapons here for infantry, unless field guns
continue;
}
if(type instanceof BayWeapon) {
//weapon bays aren't real parts
continue;
}
epart = new EquipmentPart((int)entity.getWeight(), type, eqnum,
m.isOmniPodMounted(), campaign);
if(type instanceof MiscType && type.hasFlag(MiscType.F_MASC)) {
epart = new MASC((int)entity.getWeight(), type, eqnum, campaign,
erating, m.isOmniPodMounted());
}
addPart(epart);
partsToAdd.add(epart);
}
}
}
}
if((null == engine) && !(entity instanceof Infantry) && !(entity instanceof FighterSquadron)) {
if(entity instanceof SmallCraft || entity instanceof Jumpship) {
engine = new SpacecraftEngine((int) entity.getWeight(), 0, campaign, entity.isClan());
addPart(engine);
partsToAdd.add(engine);
((SpacecraftEngine)engine).calculateTonnage();
} else if(null != entity.getEngine()) {
engine = new EnginePart((int) entity.getWeight(), new Engine(entity.getEngine().getRating(), entity.getEngine().getEngineType(), entity.getEngine().getFlags()), campaign, entity.getMovementMode() == EntityMovementMode.HOVER && entity instanceof Tank);
addPart(engine);
partsToAdd.add(engine);
}
}
if(entity instanceof Mech) {
if(null == gyro) {
gyro = new MekGyro((int) entity.getWeight(), entity.getGyroType(), entity.getOriginalWalkMP(), entity.isClan(), campaign);
addPart(gyro);
partsToAdd.add(gyro);
}
if(null == lifeSupport) {
lifeSupport = new MekLifeSupport((int) entity.getWeight(), campaign);
addPart(lifeSupport);
partsToAdd.add(lifeSupport);
}
if(null == sensor) {
sensor = new MekSensor((int) entity.getWeight(), campaign);
addPart(sensor);
partsToAdd.add(sensor);
}
if(null == cockpit) {
cockpit = new MekCockpit((int) entity.getWeight(), ((Mech)entity).getCockpitType(), entity.isClan(), campaign);
addPart(cockpit);
partsToAdd.add(cockpit);
}
if(null == rightUpperArm && entity.hasSystem(Mech.ACTUATOR_UPPER_ARM, Mech.LOC_RARM)) {
rightUpperArm = new MekActuator((int)entity.getWeight(), Mech.ACTUATOR_UPPER_ARM, Mech.LOC_RARM, campaign);
addPart(rightUpperArm);
partsToAdd.add(rightUpperArm);
}
if(null == leftUpperArm && entity.hasSystem(Mech.ACTUATOR_UPPER_ARM, Mech.LOC_LARM)) {
leftUpperArm = new MekActuator((int)entity.getWeight(), Mech.ACTUATOR_UPPER_ARM, Mech.LOC_LARM, campaign);
addPart(leftUpperArm);
partsToAdd.add(leftUpperArm);
}
if(null == rightLowerArm && entity.hasSystem(Mech.ACTUATOR_LOWER_ARM, Mech.LOC_RARM)) {
rightLowerArm = new MekActuator((int)entity.getWeight(), Mech.ACTUATOR_LOWER_ARM, Mech.LOC_RARM, campaign);
addPart(rightLowerArm);
partsToAdd.add(rightLowerArm);
}
if(null == leftLowerArm && entity.hasSystem(Mech.ACTUATOR_LOWER_ARM, Mech.LOC_LARM)) {
leftLowerArm = new MekActuator((int)entity.getWeight(), Mech.ACTUATOR_LOWER_ARM, Mech.LOC_LARM, campaign);
addPart(leftLowerArm);
partsToAdd.add(leftLowerArm);
}
if(null == rightHand && entity.hasSystem(Mech.ACTUATOR_HAND, Mech.LOC_RARM)) {
rightHand = new MekActuator((int)entity.getWeight(), Mech.ACTUATOR_HAND, Mech.LOC_RARM, campaign);
addPart(rightHand);
partsToAdd.add(rightHand);
}
if(null == leftHand && entity.hasSystem(Mech.ACTUATOR_HAND, Mech.LOC_LARM)) {
leftHand = new MekActuator((int)entity.getWeight(), Mech.ACTUATOR_HAND, Mech.LOC_LARM, campaign);
addPart(leftHand);
partsToAdd.add(leftHand);
}
if(null == rightUpperLeg && entity.hasSystem(Mech.ACTUATOR_UPPER_LEG, Mech.LOC_RLEG)) {
rightUpperLeg = new MekActuator((int)entity.getWeight(), Mech.ACTUATOR_UPPER_LEG, Mech.LOC_RLEG, campaign);
addPart(rightUpperLeg);
partsToAdd.add(rightUpperLeg);
}
if(null == leftUpperLeg && entity.hasSystem(Mech.ACTUATOR_UPPER_LEG, Mech.LOC_LLEG)) {
leftUpperLeg = new MekActuator((int)entity.getWeight(), Mech.ACTUATOR_UPPER_LEG, Mech.LOC_LLEG, campaign);
addPart(leftUpperLeg);
partsToAdd.add(leftUpperLeg);
}
if(null == rightLowerLeg && entity.hasSystem(Mech.ACTUATOR_LOWER_LEG, Mech.LOC_RLEG)) {
rightLowerLeg = new MekActuator((int)entity.getWeight(), Mech.ACTUATOR_LOWER_LEG, Mech.LOC_RLEG, campaign);
addPart(rightLowerLeg);
partsToAdd.add(rightLowerLeg);
}
if(null == leftLowerLeg && entity.hasSystem(Mech.ACTUATOR_LOWER_LEG, Mech.LOC_LLEG)) {
leftLowerLeg = new MekActuator((int)entity.getWeight(), Mech.ACTUATOR_LOWER_LEG, Mech.LOC_LLEG, campaign);
addPart(leftLowerLeg);
partsToAdd.add(leftLowerLeg);
}
if(null == rightFoot && entity.hasSystem(Mech.ACTUATOR_FOOT, Mech.LOC_RLEG)) {
rightFoot = new MekActuator((int)entity.getWeight(), Mech.ACTUATOR_FOOT, Mech.LOC_RLEG, campaign);
addPart(rightFoot);
partsToAdd.add(rightFoot);
}
if(null == leftFoot && entity.hasSystem(Mech.ACTUATOR_FOOT, Mech.LOC_LLEG)) {
leftFoot = new MekActuator((int)entity.getWeight(), Mech.ACTUATOR_FOOT, Mech.LOC_LLEG, campaign);
addPart(leftFoot);
partsToAdd.add(leftFoot);
}
if(null == rightUpperFrontLeg && entity.hasSystem(Mech.ACTUATOR_UPPER_LEG, Mech.LOC_RARM)) {
rightUpperFrontLeg = new MekActuator((int)entity.getWeight(), Mech.ACTUATOR_UPPER_LEG, Mech.LOC_RARM, campaign);
addPart(rightUpperFrontLeg);
partsToAdd.add(rightUpperFrontLeg);
}
if(null == leftUpperFrontLeg && entity.hasSystem(Mech.ACTUATOR_UPPER_LEG, Mech.LOC_LARM)) {
leftUpperFrontLeg = new MekActuator((int)entity.getWeight(), Mech.ACTUATOR_UPPER_LEG, Mech.LOC_LARM, campaign);
addPart(leftUpperFrontLeg);
partsToAdd.add(leftUpperFrontLeg);
}
if(null == rightLowerFrontLeg && entity.hasSystem(Mech.ACTUATOR_LOWER_LEG, Mech.LOC_RARM)) {
rightLowerFrontLeg = new MekActuator((int)entity.getWeight(), Mech.ACTUATOR_LOWER_LEG, Mech.LOC_RARM, campaign);
addPart(rightLowerFrontLeg);
partsToAdd.add(rightLowerFrontLeg);
}
if(null == leftLowerFrontLeg && entity.hasSystem(Mech.ACTUATOR_LOWER_LEG, Mech.LOC_LARM)) {
leftLowerFrontLeg = new MekActuator((int)entity.getWeight(), Mech.ACTUATOR_LOWER_LEG, Mech.LOC_LARM, campaign);
addPart(leftLowerFrontLeg);
partsToAdd.add(leftLowerFrontLeg);
}
if(null == rightFrontFoot && entity.hasSystem(Mech.ACTUATOR_FOOT, Mech.LOC_RARM)) {
rightFrontFoot = new MekActuator((int)entity.getWeight(), Mech.ACTUATOR_FOOT, Mech.LOC_RARM, campaign);
addPart(rightFrontFoot);
partsToAdd.add(rightFrontFoot);
}
if(null == leftFrontFoot && entity.hasSystem(Mech.ACTUATOR_FOOT, Mech.LOC_LARM)) {
leftFrontFoot = new MekActuator((int)entity.getWeight(), Mech.ACTUATOR_FOOT, Mech.LOC_LARM, campaign);
addPart(leftFrontFoot);
partsToAdd.add(leftFrontFoot);
}
}
if(entity instanceof Aero) {
if(null == structuralIntegrity) {
structuralIntegrity = new StructuralIntegrity((int)entity.getWeight(), campaign);
addPart(structuralIntegrity);
partsToAdd.add(structuralIntegrity);
}
if(null == avionics) {
avionics = new Avionics((int)entity.getWeight(), campaign);
addPart(avionics);
partsToAdd.add(avionics);
}
if(null == fcs) {
fcs = new FireControlSystem((int)entity.getWeight(), 0, campaign);
addPart(fcs);
partsToAdd.add(fcs);
((FireControlSystem)fcs).calculateCost();
}
if(null == sensor) {
sensor = new AeroSensor((int) entity.getWeight(), entity instanceof Dropship || entity instanceof Jumpship, campaign);
addPart(sensor);
partsToAdd.add(sensor);
}
if(null == landingGear) {
landingGear = new LandingGear((int) entity.getWeight(), campaign);
addPart(landingGear);
partsToAdd.add(landingGear);
}
if(null == lifeSupport) {
lifeSupport = new AeroLifeSupport((int) entity.getWeight(), 0, !(entity instanceof SmallCraft || entity instanceof Jumpship), campaign);
addPart(lifeSupport);
partsToAdd.add(lifeSupport);
((AeroLifeSupport)lifeSupport).calculateCost();
}
if(null == dropCollar && entity instanceof Dropship) {
dropCollar = new DropshipDockingCollar((int) entity.getWeight(), campaign);
addPart(dropCollar);
partsToAdd.add(dropCollar);
}
int hsinks = ((Aero)entity).getOHeatSinks() - aeroHeatSinks.size();
int podhsinks = ((Aero)entity).getPodHeatSinks() - podAeroHeatSinks;
while(hsinks > 0) {
AeroHeatSink aHeatSink = new AeroHeatSink((int)entity.getWeight(),
((Aero)entity).getHeatType(), podhsinks > 0, campaign);
addPart(aHeatSink);
partsToAdd.add(aHeatSink);
hsinks--;
if (podhsinks > 0) {
podhsinks--;
}
}
if (aeroThrustersLeft == null) {
aeroThrustersLeft = new Thrusters(0, campaign, true);
addPart(aeroThrustersLeft);
partsToAdd.add(aeroThrustersLeft);
}
if (aeroThrustersRight == null) {
aeroThrustersRight = new Thrusters(0, campaign, false);
addPart(aeroThrustersRight);
partsToAdd.add(aeroThrustersRight);
}
}
if(entity instanceof Tank) {
if(null == motiveSystem) {
motiveSystem = new MotiveSystem((int)entity.getWeight(), campaign);
addPart(motiveSystem);
partsToAdd.add(motiveSystem);
}
if(null == sensor) {
sensor = new VeeSensor((int) entity.getWeight(), campaign);
addPart(sensor);
partsToAdd.add(sensor);
}
if(!(entity instanceof VTOL) && !((Tank)entity).hasNoTurret() && null == turretLock) {
turretLock = new TurretLock(campaign);
addPart(turretLock);
partsToAdd.add(turretLock);
}
}
if(entity instanceof Protomech) {
if(!entity.entityIsQuad()) {
if(null == protoLeftArmActuator) {
protoLeftArmActuator = new ProtomekArmActuator((int)entity.getWeight(),Protomech.LOC_LARM, campaign);
addPart(protoLeftArmActuator);
partsToAdd.add(protoLeftArmActuator);
}
if(null == protoRightArmActuator) {
protoRightArmActuator = new ProtomekArmActuator((int)entity.getWeight(),Protomech.LOC_RARM, campaign);
addPart(protoRightArmActuator);
partsToAdd.add(protoRightArmActuator);
}
}
if(null == protoLegsActuator) {
protoLegsActuator = new ProtomekLegActuator((int)entity.getWeight(), campaign);
addPart(protoLegsActuator);
partsToAdd.add(protoLegsActuator);
}
if(null == sensor) {
sensor = new ProtomekSensor((int) entity.getWeight(), campaign);
addPart(sensor);
partsToAdd.add(sensor);
}
int jj = (entity).getOriginalJumpMP() - protoJumpJets.size();
while(jj > 0) {
ProtomekJumpJet protoJJ = new ProtomekJumpJet((int)entity.getWeight(), campaign);
addPart(protoJJ);
partsToAdd.add(protoJJ);
jj--;
}
}
if(entity instanceof Infantry && !(entity instanceof BattleArmor)) {
if(null == motiveType && entity.getMovementMode() != EntityMovementMode.INF_LEG) {
int number = ((Infantry)entity).getOInternal(Infantry.LOC_INFANTRY);
if(((Infantry)entity).isMechanized()) {
number = ((Infantry)entity).getSquadN();
}
while(number > 0) {
motiveType = new InfantryMotiveType(0, campaign, entity.getMovementMode());
addPart(motiveType);
partsToAdd.add(motiveType);
number--;
}
}
if(null == infantryArmor) {
infantryArmor = new InfantryArmorPart(0, campaign, ((Infantry)entity).getDamageDivisor(), ((Infantry)entity).isArmorEncumbering(), ((Infantry)entity).hasDEST(), ((Infantry)entity).hasSneakCamo(), ((Infantry)entity).hasSneakECM(), ((Infantry)entity).hasSneakIR(), ((Infantry)entity).hasSpaceSuit());
if(infantryArmor.getStickerPrice() > 0) {
int number = ((Infantry)entity).getOInternal(Infantry.LOC_INFANTRY);
while(number > 0) {
infantryArmor = new InfantryArmorPart(0, campaign, ((Infantry)entity).getDamageDivisor(), ((Infantry)entity).isArmorEncumbering(), ((Infantry)entity).hasDEST(), ((Infantry)entity).hasSneakCamo(), ((Infantry)entity).hasSneakECM(), ((Infantry)entity).hasSneakIR(), ((Infantry)entity).hasSpaceSuit());
addPart(infantryArmor);
partsToAdd.add(infantryArmor);
number--;
}
}
}
InfantryWeapon primaryType = ((Infantry)entity).getPrimaryWeapon();
InfantryWeapon secondaryType = ((Infantry)entity).getSecondaryWeapon();
if(null == primaryW && null != primaryType) {
int number = (((Infantry)entity).getSquadSize() - ((Infantry)entity).getSecondaryN()) * ((Infantry)entity).getSquadN();
while(number > 0) {
primaryW = new InfantryWeaponPart((int)entity.getWeight(), primaryType, -1, campaign, true);
addPart(primaryW);
partsToAdd.add(primaryW);
number--;
}
}
if(null == secondaryW && null != secondaryType) {
int number = ((Infantry)entity).getSecondaryN() * ((Infantry)entity).getSquadN();
while(number > 0) {
secondaryW = new InfantryWeaponPart((int)entity.getWeight(), secondaryType, -1, campaign, false);
addPart(secondaryW);
partsToAdd.add(secondaryW);
number--;
}
}
}
if(addParts) {
for(Part p : partsToAdd) {
campaign.addPart(p, 0);
}
}
if (getEntity().isOmni()) {
podSpace.clear();
for (int loc = 0; loc < getEntity().locations(); loc++) {
podSpace.add(new PodSpace(loc, this));
}
podSpace.forEach(ps -> ps.updateConditionFromEntity(false));
}
}
public ArrayList<Part> getParts() {
return parts;
}
public void setParts(ArrayList<Part> newParts) {
parts = newParts;
}
public ArrayList<PodSpace> getPodSpace() {
return podSpace;
}
public void refreshPodSpace() {
podSpace.forEach(ps -> ps.updateConditionFromEntity(false));
}
public ArrayList<AmmoBin> getWorkingAmmoBins() {
ArrayList<AmmoBin> ammo = new ArrayList<AmmoBin>();
for(Part part : parts) {
if(part instanceof AmmoBin) {
ammo.add((AmmoBin)part);
}
}
return ammo;
}
public String getCamoCategory() {
if (null == entity) {
return "";
}
String category = campaign.getCamoCategory();
if (isEntityCamo()) {
category = entity.getCamoCategory();
}
if (Player.ROOT_CAMO.equals(category)) {
category = "";
}
return category;
}
public String getCamoFileName() {
if (null == campaign && null == entity) {
return "";
}
String fileName = campaign.getCamoFileName();
if (isEntityCamo()) {
fileName = entity.getCamoFileName();
}
if (null == fileName) {
fileName = "";
}
return fileName;
}
public Person getCommander() {
//take first by rank
//if rank is tied, take gunners over drivers
//if two of the same type are tie rank, take the first one
int bestRank = -1;
Person commander = null;
for(UUID id : vesselCrew) {
if (id == null) {
continue;
}
Person p = campaign.getPerson(id);
if(null != p && p.getRankNumeric() > bestRank) {
commander = p;
bestRank = p.getRankNumeric();
}
}
for(UUID pid : gunners) {
if (pid == null) {
continue;
}
Person p = campaign.getPerson(pid);
if(p != null && entity != null && (entity instanceof Tank || entity instanceof Infantry) && p.getHits() > 0) {
continue;
}
if(p.getRankNumeric() > bestRank) {
commander = p;
bestRank = p.getRankNumeric();
}
}
for(UUID pid : drivers) {
if (pid == null) {
continue;
}
Person p = campaign.getPerson(pid);
if(p != null && entity != null && (entity instanceof Tank || entity instanceof Infantry) && p.getHits() > 0) {
continue;
}
if(p.getRankNumeric() > bestRank) {
commander = p;
bestRank = p.getRankNumeric();
}
}
if(navigator != null) {
Person p = campaign.getPerson(navigator);
if(null != p && p.getRankNumeric() > bestRank) {
commander = p;
bestRank = p.getRankNumeric();
}
}
return commander;
}
public void resetPilotAndEntity() {
int piloting = 13;
int gunnery = 13;
int artillery = 13;
String driveType = SkillType.getDrivingSkillFor(entity);
String gunType = SkillType.getGunnerySkillFor(entity);
int sumPiloting = 0;
int nDrivers = 0;
int sumGunnery = 0;
int nGunners = 0;
int nCrew = 0;
for(UUID pid : drivers) {
Person p = campaign.getPerson(pid);
if(p.getHits() > 0 && !usesSoloPilot()) {
continue;
}
if(p.hasSkill(driveType)) {
sumPiloting += p.getSkill(driveType).getFinalSkillValue();
nDrivers++;
} else if(entity instanceof Infantry) {
//For infantry we need to assign an 8 if they have no antimech skill
sumPiloting += 8;
nDrivers++;
}
if (entity instanceof Tank
&& Compute.getFullCrewSize(entity) == 1
&& p.hasSkill(gunType)) {
sumGunnery += p.getSkill(gunType).getFinalSkillValue();
nGunners++;
}
if(campaign.getCampaignOptions().useAdvancedMedical()) {
sumPiloting += p.getPilotingInjuryMod();
}
}
for(UUID pid : gunners) {
Person p = campaign.getPerson(pid);
if(p.getHits() > 0 && !usesSoloPilot()) {
continue;
}
if(p.hasSkill(gunType)) {
sumGunnery += p.getSkill(gunType).getFinalSkillValue();
nGunners++;
}
if(p.hasSkill(SkillType.S_ARTILLERY)
&& p.getSkill(SkillType.S_ARTILLERY).getFinalSkillValue() < artillery) {
artillery = p.getSkill(SkillType.S_ARTILLERY).getFinalSkillValue();
}
if(campaign.getCampaignOptions().useAdvancedMedical()) {
sumGunnery += p.getGunneryInjuryMod();
}
}
for(UUID pid : vesselCrew) {
Person p = campaign.getPerson(pid);
if(null !=p && p.getHits() == 0) {
nCrew++;
}
}
if(null != navigator) {
Person p = campaign.getPerson(navigator);
if(null !=p && p.getHits() == 0) {
nCrew++;
}
}
if(nDrivers > 0) {
piloting = (int)Math.round(((double)sumPiloting)/nDrivers);
}
if(nGunners > 0) {
gunnery = (int)Math.round(((double)sumGunnery)/nGunners);
}
if(entity instanceof Infantry) {
if(entity instanceof BattleArmor) {
int ntroopers = 0;
//OK, we want to reorder the way we move through suits, so that we always put BA
//in the suits with more armor. Otherwise, we may put a soldier in a suit with no
//armor when a perfectly good suit is waiting further down the line.
Map<String, Integer> bestSuits = new HashMap<String, Integer>();
for(int i = BattleArmor.LOC_TROOPER_1; i <= ((BattleArmor)entity).getTroopers(); i++) {
bestSuits.put(Integer.toString(i), entity.getArmorForReal(i));
if(entity.getInternal(i)<0) {
bestSuits.put(Integer.toString(i), IArmorState.ARMOR_DESTROYED);
}
bestSuits = Utilities.sortMapByValue(bestSuits, true);
}
bestSuits.keySet();
for(String key : bestSuits.keySet()) {
int i = Integer.parseInt(key);
if(!isBattleArmorSuitOperable(i)) {
//no suit here move along
continue;
}
if(ntroopers < nGunners) {
entity.setInternal(1, i);
ntroopers++;
} else {
entity.setInternal(0, i);
}
}
if(ntroopers < nGunners) {
//TODO: we have too many soldiers assigned to the available suits - do something!
//probably remove some crew and then re-run resetentityandpilot
}
}
entity.setInternal(nGunners, Infantry.LOC_INFANTRY);
}
if(drivers.isEmpty() && gunners.isEmpty()) {
entity.setCrew(null);
return;
}
Person commander = getCommander();
if(null == commander) {
entity.setCrew(null);
return;
}
// Clear any stale game data that may somehow have gotten set incorrectly
campaign.clearGameData(entity);
//TODO: For the moment we need to max these out at 8 so people don't get errors
//when they customize in MM but we should put an option in MM to ignore those limits
//and set it to true when we start up through MHQ
gunnery = Math.min(Math.max(gunnery, 0), 7);
piloting = Math.min(Math.max(piloting, 0), 8);
artillery = Math.min(Math.max(artillery, 0), 7);
Crew pilot = new Crew(commander.getFullTitle(), 1, gunnery, piloting);
pilot.setPortraitCategory(commander.getPortraitCategory());
pilot.setPortraitFileName(commander.getPortraitFileName());
pilot.setNickname(commander.getCallsign());
pilot.setExternalIdAsString(commander.getId().toString());
pilot.setArtillery(artillery);
//create a new set of options. For now we will just assign based on commander, but
//we really should be more detailed about this.
if (campaign.getCampaignOptions().useAbilities()) {
PilotOptions options = new PilotOptions();
for (Enumeration<IOptionGroup> i = options.getGroups(); i.hasMoreElements();) {
IOptionGroup group = i.nextElement();
for (Enumeration<IOption> j = group.getOptions(); j.hasMoreElements();) {
IOption option = j.nextElement();
option.setValue(commander.getOptions().getOption(option.getName()).getValue());
}
}
pilot.setOptions(options);
}
if(usesSoloPilot()) {
if(!commander.isActive()) {
entity.setCrew(null);
return;
}
pilot.setHits(commander.getHits());
}
else if(entity instanceof Tank) {
if(nDrivers == 0 && nGunners == 0) {
//nobody is healthy
entity.setCrew(null);
return;
}
if(commander.getHits() > 0) {
((Tank)entity).setCommanderHit(true);
} else {
((Tank)entity).setCommanderHit(false);
}
if(nDrivers == 0) {
((Tank)entity).setDriverHit(true);
} else {
((Tank)entity).setDriverHit(false);
}
}
else if(entity instanceof Infantry) {
if(nDrivers == 0 && nGunners == 0) {
//nobody is healthy
entity.setCrew(null);
return;
}
}
else if(entity instanceof SmallCraft || entity instanceof Jumpship) {
//assign crew hits based on what percent of the crew is present
int currentSize = nDrivers + nGunners + nCrew;
double percent = Math.max(0.0, 1.0 - (1.0 * currentSize)/getFullCrewSize());
int hits = (int)Math.floor(percent * 6);
if(percent > 0.0 && hits==0) {
//at least one hit if less than full staffed
hits = 1;
}
pilot.setHits(hits);
}
resetEngineer();
pilot.setToughness(commander.getToughness());
//TODO: game option to use tactics as command and ind init bonus
if(commander.hasSkill(SkillType.S_TACTICS)) {
pilot.setCommandBonus(commander.getSkill(SkillType.S_TACTICS).getFinalSkillValue());
}
entity.setCrew(pilot);
}
public void resetEngineer() {
if(!isSelfCrewed()) {
return;
}
int minutesLeft = 480;
int overtimeLeft = 240;
if(null != engineer) {
minutesLeft = engineer.getMinutesLeft();
overtimeLeft = engineer.getOvertimeLeft();
} else {
//then get the number based on the least amount of time available to crew members
for(Person p : getActiveCrew()) {
if(p.getMinutesLeft() < minutesLeft) {
minutesLeft = p.getMinutesLeft();
}
if(p.getOvertimeLeft() < overtimeLeft) {
overtimeLeft = p.getOvertimeLeft();
}
}
}
if(getEntity() instanceof Infantry) {
if(!isUnmanned()) {
engineer = new Person(getCommander().getName(), campaign);
engineer.setEngineer(true);
engineer.setMinutesLeft(minutesLeft);
engineer.setOvertimeLeft(overtimeLeft);
engineer.setId(getCommander().getId());
engineer.setPrimaryRole(Person.T_MECHANIC);
engineer.setRankNumeric(getCommander().getRankNumeric());
//will only be reloading ammo, so doesn't really matter what skill level we give them - set to regular
engineer.addSkill(SkillType.S_TECH_MECHANIC, SkillType.getType(SkillType.S_TECH_MECHANIC).getRegularLevel(), 0);
} else {
engineer = null;
}
} else {
if (vesselCrew.size() > 0) {
int nCrew = 0;
int sumSkill = 0;
int sumBonus = 0;
String engineerName = "Nobody";
int bestRank = -1;
for(UUID pid : vesselCrew) {
Person p = campaign.getPerson(pid);
if(null == p) {
continue;
}
if(p.hasSkill(SkillType.S_TECH_VESSEL)) {
sumSkill += p.getSkill(SkillType.S_TECH_VESSEL).getLevel();
sumBonus += p.getSkill(SkillType.S_TECH_VESSEL).getBonus();
nCrew++;
}
if(p.getRankNumeric() > bestRank) {
engineerName = p.getFullName();
bestRank = p.getRankNumeric();
}
}
if(nCrew > 0) {
engineer = new Person(engineerName, campaign);
engineer.setEngineer(true);
engineer.setMinutesLeft(minutesLeft);
engineer.setOvertimeLeft(overtimeLeft);
engineer.setId(getCommander().getId());
engineer.setPrimaryRole(Person.T_SPACE_CREW);
if(bestRank > -1) {
engineer.setRankNumeric(bestRank);
}
engineer.addSkill(SkillType.S_TECH_VESSEL, sumSkill/nCrew, sumBonus/nCrew);
engineer.setUnitId(this.getId());
} else {
engineer = null;
}
} else { // Needed to fix bug where removed crew doesn't remove engineer
engineer = null;
}
}
if(null != engineer) {
//change reference for any scheduled tasks
for(Part p : getParts()) {
if(p.isBeingWorkedOn()) {
p.setTeamId(engineer.getId());
}
}
} else {
//cancel any mothballing if this happens
if(isMothballing()) {
mothballTime = 0;
}
//cancel any scheduled tasks
for(Part p : getParts()) {
if(p.isBeingWorkedOn()) {
p.cancelAssignment();
}
}
}
}
public int getAeroCrewNeeds() {
return Compute.getAeroCrewNeeds(entity);
}
public int getFullCrewSize() {
return Compute.getFullCrewSize(entity);
}
public int getTotalDriverNeeds() {
return Compute.getTotalDriverNeeds(entity);
}
public int getTotalCrewNeeds() {
int nav = 0;
if(entity instanceof SmallCraft || entity instanceof Jumpship) {
if(entity instanceof Jumpship && !(entity instanceof SpaceStation)) {
nav = 1;
}
return getAeroCrewNeeds() - getTotalDriverNeeds() - nav;
}
return 0;
}
public boolean canTakeMoreDrivers() {
int nDrivers = drivers.size();
return nDrivers < getTotalDriverNeeds();
}
public boolean canTakeMoreVesselCrew() {
int nCrew = vesselCrew.size();
int nav = 0;
if(entity instanceof SmallCraft || entity instanceof Jumpship) {
if(entity instanceof Jumpship && !(entity instanceof SpaceStation)) {
nav = 1;
}
return nCrew < (getAeroCrewNeeds() - getTotalDriverNeeds() - nav);
}
return false;
}
public boolean canTakeNavigator() {
return entity instanceof Jumpship && !(entity instanceof SpaceStation) && navigator == null;
}
public boolean canTakeTech() {
return tech == null && requiresMaintenance() && !isSelfCrewed();
}
public boolean canTakeMoreGunners() {
int nGunners = gunners.size();
if(nGunners == 3) {
}
return nGunners < getTotalGunnerNeeds();
}
public int getTotalGunnerNeeds() {
return Compute.getTotalGunnerNeeds(entity);
}
public boolean usesSoloPilot() {
//return Compute.getFullCrewSize(entity) == 1;
//Taharqa: I dont think we should do it based on computed size, but whether the unit logically
//is the type of unit that has only one pilot. This is partly because there may be some vees
//that only have one pilot and this is also a problem for BA units with only one active suit
return (entity instanceof Mech)
|| (entity instanceof Protomech)
|| (entity instanceof Aero && !(entity instanceof SmallCraft) && !(entity instanceof Jumpship));
}
public boolean usesSoldiers() {
return entity instanceof Infantry;
}
public void addDriver(Person p) {
ensurePersonIsRegistered(p);
drivers.add(p.getId());
p.setUnitId(getId());
resetPilotAndEntity();
p.addLogEntry(campaign.getDate(), "Assigned to " + getName());
MekHQ.triggerEvent(new PersonCrewAssignmentEvent(p, this));
}
public void addDriver(Person p, boolean useTransfers) {
ensurePersonIsRegistered(p);
drivers.add(p.getId());
p.setUnitId(getId());
resetPilotAndEntity();
if (useTransfers) {
p.addLogEntry(campaign.getDate(), "Reassigned to " + getName());
} else {
p.addLogEntry(campaign.getDate(), "Assigned to " + getName());
}
MekHQ.triggerEvent(new PersonCrewAssignmentEvent(p, this));
}
public void addGunner(Person p) {
ensurePersonIsRegistered(p);
gunners.add(p.getId());
p.setUnitId(getId());
resetPilotAndEntity();
p.addLogEntry(campaign.getDate(), "Assigned to " + getName());
MekHQ.triggerEvent(new PersonCrewAssignmentEvent(p, this));
}
public void addGunner(Person p, boolean useTransfers) {
ensurePersonIsRegistered(p);
gunners.add(p.getId());
p.setUnitId(getId());
resetPilotAndEntity();
if (useTransfers) {
p.addLogEntry(campaign.getDate(), "Reassigned to " + getName());
} else {
p.addLogEntry(campaign.getDate(), "Assigned to " + getName());
}
MekHQ.triggerEvent(new PersonCrewAssignmentEvent(p, this));
}
public void addVesselCrew(Person p) {
ensurePersonIsRegistered(p);
vesselCrew.add(p.getId());
p.setUnitId(getId());
resetPilotAndEntity();
p.addLogEntry(campaign.getDate(), "Assigned to " + getName());
MekHQ.triggerEvent(new PersonCrewAssignmentEvent(p, this));
}
public void addVesselCrew(Person p, boolean useTransfers) {
ensurePersonIsRegistered(p);
vesselCrew.add(p.getId());
p.setUnitId(getId());
resetPilotAndEntity();
if (useTransfers) {
p.addLogEntry(campaign.getDate(), "Reassigned to " + getName());
} else {
p.addLogEntry(campaign.getDate(), "Assigned to " + getName());
}
MekHQ.triggerEvent(new PersonCrewAssignmentEvent(p, this));
}
public void setNavigator(Person p) {
ensurePersonIsRegistered(p);
navigator = p.getId();
p.setUnitId(getId());
resetPilotAndEntity();
p.addLogEntry(campaign.getDate(), "Assigned to " + getName());
MekHQ.triggerEvent(new PersonCrewAssignmentEvent(p, this));
}
public void setNavigator(Person p, boolean useTransfers) {
ensurePersonIsRegistered(p);
navigator = p.getId();
p.setUnitId(getId());
resetPilotAndEntity();
if (useTransfers) {
p.addLogEntry(campaign.getDate(), "Reassigned to " + getName());
} else {
p.addLogEntry(campaign.getDate(), "Assigned to " + getName());
}
MekHQ.triggerEvent(new PersonCrewAssignmentEvent(p, this));
}
public void setTech(Person p) {
ensurePersonIsRegistered(p);
tech = p.getId();
p.addTechUnitID(getId());
p.addLogEntry(campaign.getDate(), "Assigned to " + getName());
MekHQ.triggerEvent(new PersonTechAssignmentEvent(p, this));
}
public void setTech(UUID pid) {
tech = pid;
}
public void removeTech() {
if (tech != null) {
MekHQ.triggerEvent(new PersonTechAssignmentEvent(campaign.getPerson(tech), this));
tech = null;
}
}
private void ensurePersonIsRegistered(Person p) {
Objects.requireNonNull(p);
if(null == campaign.getPerson(p.getId())) {
campaign.addPersonWithoutId(p, false);
}
}
public void addPilotOrSoldier(Person p) {
ensurePersonIsRegistered(p);
drivers.add(p.getId());
gunners.add(p.getId());
p.setUnitId(getId());
resetPilotAndEntity();
p.addLogEntry(campaign.getDate(), "Assigned to " + getName());
MekHQ.triggerEvent(new PersonCrewAssignmentEvent(p, this));
}
public void addPilotOrSoldier(Person p, boolean useTransfers) {
ensurePersonIsRegistered(p);
drivers.add(p.getId());
gunners.add(p.getId());
p.setUnitId(getId());
resetPilotAndEntity();
if (useTransfers) {
p.addLogEntry(campaign.getDate(), "Reassigned to " + getName());
} else {
p.addLogEntry(campaign.getDate(), "Assigned to " + getName());
}
MekHQ.triggerEvent(new PersonCrewAssignmentEvent(p, this));
}
public void remove(Person p, boolean log) {
ensurePersonIsRegistered(p);
if(p.getId().equals(tech)) {
tech = null;
p.removeTechUnitId(getId());
MekHQ.triggerEvent(new PersonTechAssignmentEvent(p, this));
} else {
p.setUnitId(null);
drivers.remove(p.getId());
gunners.remove(p.getId());
vesselCrew.remove(p.getId());
if(p.getId().equals(navigator)) {
navigator = null;
}
if((null != engineer) && p.getId().equals(engineer.getId())) {
engineer = null;
}
resetPilotAndEntity();
MekHQ.triggerEvent(new PersonCrewAssignmentEvent(p, this));
}
if(log) {
p.addLogEntry(campaign.getDate(), "Removed from " + getName());
}
}
public boolean isUnmanned() {
return (null == getCommander());
}
public int getForceId() {
return forceId;
}
public void setForceId(int id) {
this.forceId = id;
}
public int getScenarioId() {
return scenarioId;
}
public void setScenarioId(int i) {
this.scenarioId = i;
}
public ArrayList<Person> getCrew() {
ArrayList<Person> crew = new ArrayList<Person>();
for(UUID id : drivers) {
Person p = campaign.getPerson(id);
if(null != p) {
crew.add(p);
}
}
if(!usesSoloPilot() && !usesSoldiers()) {
for(UUID id : gunners) {
Person p = campaign.getPerson(id);
if(null != p) {
crew.add(p);
}
}
}
for(UUID id : vesselCrew) {
Person p = campaign.getPerson(id);
if(null != p) {
crew.add(p);
}
}
if(navigator != null) {
Person p = campaign.getPerson(navigator);
if(null != p) {
crew.add(p);
}
}
return crew;
}
public Person getTech() {
if(null != engineer) {
return engineer;
}
if(null != tech) {
return campaign.getPerson(tech);
}
return null;
}
public boolean isMothballing() {
return mothballTime > 0;
}
public int getMothballTime() {
return mothballTime;
}
public void setMothballTime(int t) {
mothballTime = t;
}
public boolean isMothballed() {
return mothballed;
}
public void setMothballed(boolean b) {
this.mothballed = b;
// Tech gets removed either way bug [#488]
if(null != tech) {
remove(getTech(), true);
}
if(mothballed) {
//remove any other personnel
for(Person p : getCrew()) {
remove(p, true);
}
resetPilotAndEntity();
} else {
//start maintenance cycle over again
resetDaysSinceMaintenance();
}
}
public void startMothballing(UUID id) {
//set this person as tech
if(!isSelfCrewed() && null != tech && !tech.equals(id)) {
if(null != getTech()) {
remove(getTech(), true);
}
}
tech = id;
//dont remove personnel yet, because self crewed units need their crews to mothball
campaign.removeUnitFromForce(this);
//clear any assigned tasks
for(Part p : getParts()) {
p.cancelAssignment();
}
//set mothballing time
if(getEntity() instanceof Infantry) {
mothballTime = 480;
}
else if(getEntity() instanceof Dropship || getEntity() instanceof Jumpship) {
mothballTime = 480 * (int)Math.ceil(getEntity().getWeight()/500.0);
} else {
if(isMothballed()) {
mothballTime = 480;
} else {
mothballTime = 960;
}
}
campaign.mothball(this);
}
public ArrayList<Person> getActiveCrew() {
ArrayList<Person> crew = new ArrayList<Person>();
for(UUID id : drivers) {
Person p = campaign.getPerson(id);
if(null != p) {
if(p.getHits() > 0 && (entity instanceof Tank || entity instanceof Infantry)) {
continue;
}
crew.add(p);
}
}
if(!usesSoloPilot() && !usesSoldiers()) {
for(UUID id : gunners) {
Person p = campaign.getPerson(id);
if(null != p) {
if(p.getHits() > 0 && (entity instanceof Tank || entity instanceof Infantry)) {
continue;
}
crew.add(p);
}
}
}
for(UUID id : vesselCrew) {
Person p = campaign.getPerson(id);
if(null != p) {
crew.add(p);
}
}
if(navigator != null) {
Person p = campaign.getPerson(navigator);
if(null != p) {
crew.add(p);
}
}
return crew;
}
public boolean isDriver(Person person) {
for(UUID id : drivers) {
if(person.getId().equals(id)) {
return true;
}
}
return false;
}
public boolean isGunner(Person person) {
for(UUID id : gunners) {
if(person.getId().equals(id)) {
return true;
}
}
return false;
}
public boolean isCommander(Person person) {
return person.getId().equals(getCommander().getId());
}
public boolean isNavigator(Person person) {
return person.getId().equals(navigator);
}
public void setRefit(Refit r) {
refit = r;
}
public Refit getRefit() {
return refit;
}
public boolean isRefitting() {
return null != refit;
}
public String getName() {
if (getFluffName() != null && !getFluffName().equals("")) {
return entity.getShortName() + " - " + getFluffName();
}
return entity.getShortName();
}
public String getHyperlinkedName() {
return "<a href='UNIT:" + getId() + "'>" + entity.getShortName() + "</a>";
}
@Override
public boolean equals(Object obj) {
if(obj == this) {
return true;
}
if((null == obj) || (getClass() != obj.getClass())) {
return false;
}
final Unit other = (Unit) obj;
return Objects.equals(id, other.id) && Objects.equals(getName(), other.getName());
}
@Override
public int hashCode() {
return Objects.hash(id, getName());
}
public Person getEngineer() {
return engineer;
}
public UUID getTechId() {
return tech;
}
public int getOldId() {
return oldId;
}
public void fixIdReferences(Hashtable<Integer, UUID> uHash, Hashtable<Integer, UUID> peopleHash) {
for(int oid : oldDrivers) {
UUID nid = peopleHash.get(oid);
if(null != nid) {
drivers.add(peopleHash.get(oid));
}
}
for(int oid : oldGunners) {
UUID nid = peopleHash.get(oid);
if(null != nid) {
gunners.add(peopleHash.get(oid));
}
}
for(int oid : oldVesselCrew) {
UUID nid = peopleHash.get(oid);
if(null != nid) {
vesselCrew.add(peopleHash.get(oid));
}
}
navigator = peopleHash.get(oldNavigator);
if(null != refit) {
refit.fixIdReferences(uHash, peopleHash);
}
}
public Part getPartForEquipmentNum(int index, int loc) {
for(Part p : parts) {
if(p.isPartForEquipmentNum(index, loc)) {
return p;
}
}
return null;
}
public boolean isEntityCamo() {
if ((null != entity) && (null != entity.getCamoCategory()
&& entity.getCamoCategory() != IPlayer.NO_CAMO
&& !entity.getCamoCategory().isEmpty())
&& (null != entity.getCamoFileName())
&& (!Player.NO_CAMO.equals(entity.getCamoFileName()))
&& !entity.getCamoFileName().isEmpty()) {
return true;
}
return false;
}
public int getAvailability(int era) {
//take the highest availability of all parts
int availability = EquipmentType.RATING_A;
for(Part p : parts) {
int newAvailability = p.getAvailability(era);
//Taharqa: its not clear whether a unit should really be considered extinct
//when its parts are extinct as many probably outlive the production of parts
//it would be better to just use the unit extinction date itself, but given
//that there are no canon extinction/reintro dates for units, we will use this
//instead
if(p.isExtinctIn(campaign.getCalendar().get(Calendar.YEAR))) {
newAvailability = EquipmentType.RATING_X;
}
if(newAvailability > availability) {
availability = newAvailability;
}
}
return availability;
}
public void setDaysToArrival(int days) {
daysToArrival = days;
}
public int getDaysToArrival() {
return daysToArrival;
}
public boolean checkArrival() {
if(daysToArrival > 0) {
daysToArrival--;
if (daysToArrival == 0) {
MekHQ.triggerEvent(new UnitArrivedEvent(this));
return true;
}
}
return false;
}
public boolean isPresent() {
return daysToArrival == 0;
}
public int getMaintenanceTime() {
if(getEntity() instanceof Mech) {
switch(getEntity().getWeightClass()) {
case EntityWeightClass.WEIGHT_ULTRA_LIGHT:
return 30;
case EntityWeightClass.WEIGHT_LIGHT:
return 45;
case EntityWeightClass.WEIGHT_MEDIUM:
return 60;
case EntityWeightClass.WEIGHT_HEAVY:
return 75;
case EntityWeightClass.WEIGHT_ASSAULT:
default:
return 90;
}
}
if(getEntity() instanceof Protomech) {
return 20;
}
if(getEntity() instanceof BattleArmor) {
return 10;
}
if(getEntity() instanceof ConvFighter) {
return 45;
}
if(getEntity() instanceof SmallCraft && !(getEntity() instanceof Dropship)) {
return 90;
}
if(getEntity() instanceof Aero
&& !(getEntity() instanceof Dropship)
&& !(getEntity() instanceof Jumpship)) {
switch(getEntity().getWeightClass()) {
case EntityWeightClass.WEIGHT_LIGHT:
return 45;
case EntityWeightClass.WEIGHT_MEDIUM:
return 60;
case EntityWeightClass.WEIGHT_HEAVY:
default:
return 75;
}
}
if(getEntity() instanceof SupportTank) {
switch(getEntity().getWeightClass()) {
case EntityWeightClass.WEIGHT_SMALL_SUPPORT:
return 20;
case EntityWeightClass.WEIGHT_MEDIUM_SUPPORT:
return 35;
case EntityWeightClass.WEIGHT_LARGE_SUPPORT:
default:
return 100;
}
}
if(getEntity() instanceof Tank) {
switch(getEntity().getWeightClass()) {
case EntityWeightClass.WEIGHT_LIGHT:
return 30;
case EntityWeightClass.WEIGHT_MEDIUM:
return 50;
case EntityWeightClass.WEIGHT_HEAVY:
return 75;
case EntityWeightClass.WEIGHT_ASSAULT:
return 90;
case EntityWeightClass.WEIGHT_SUPER_HEAVY:
default:
return 120;
}
}
//the rest get support from crews, so zero
return 0;
}
public void incrementDaysSinceMaintenance(boolean maintained, int astechs) {
daysSinceMaintenance++;
astechDaysMaintained += astechs;
if(maintained) {
daysActivelyMaintained++;
}
}
public void resetDaysSinceMaintenance() {
daysSinceMaintenance = 0;
daysActivelyMaintained = 0;
astechDaysMaintained = 0;
}
public int getDaysSinceMaintenance() {
return daysSinceMaintenance;
}
//there are no official rules about partial maintenance
//lets say less than half is +2
//more than half is +1 penalty
//also we will take the average rounded down of the number of astechs to figure out
//shorthanded penalty
public double getMaintainedPct() {
return (daysActivelyMaintained/(double)daysSinceMaintenance);
}
public boolean isFullyMaintained() {
return daysActivelyMaintained == daysSinceMaintenance;
}
public int getAstechsMaintained() {
return (int)Math.floor((1.0 * astechDaysMaintained) / daysSinceMaintenance);
}
public int getQuality() {
int nParts = 0;
int sumQuality = 0;
for(Part p : getParts()) {
//no rules about this but lets assume missing parts are quality A
if(p instanceof MissingPart) {
nParts++;
}
else if(p.needsMaintenance()) {
nParts++;
sumQuality += p.getQuality();
}
}
if(nParts == 0) {
return Part.QUALITY_D;
}
return (int)Math.round((1.0 * sumQuality)/nParts);
}
public void setQuality(int q) {
for (Part p : getParts()) {
if (!(p instanceof MissingPart)) {
p.setQuality(q);
}
}
}
public String getQualityName() {
return Part.getQualityName(getQuality(), campaign.getCampaignOptions().reverseQualityNames());
}
public boolean requiresMaintenance() {
if(!isAvailable()) {
return false;
}
if(getEntity() instanceof Infantry && !(getEntity() instanceof BattleArmor)) {
return false;
}
return true;
}
public boolean isSelfCrewed() {
return (getEntity() instanceof Dropship || getEntity() instanceof Jumpship
|| getEntity() instanceof Infantry && !(getEntity() instanceof BattleArmor));
}
public boolean isUnderRepair() {
for(Part p : getParts()) {
if(null != p.getTeamId()) {
return true;
}
}
return false;
}
public String getLastMaintenanceReport() {
return lastMaintenanceReport;
}
public void setLastMaintenanceReport(String r) {
lastMaintenanceReport = r;
}
public static int getDamageState(Entity en) {
return en.getDamageLevel(false);
}
public void resetParts() {
parts = new ArrayList<Part>();
}
/**
* @return the name
*/
public String getFluffName() {
return this.fluffName;
}
/**
* @param name the name to set
*/
public void setFluffName(String name) {
this.fluffName = name;
}
/**
* Checks to see if a particular BA suit on BA is currently operable
* This requires the suit to not be destroyed and to have not missing equipment parts
*/
public boolean isBattleArmorSuitOperable(int trooper) {
if(null == getEntity() || !(getEntity() instanceof BattleArmor)) {
return false;
}
if(getEntity().getInternal(trooper) < 0) {
return false;
}
for(Part part : getParts()) {
if(part instanceof MissingBattleArmorEquipmentPart &&
((MissingBattleArmorEquipmentPart)part).getTrooper() == trooper) {
return false;
}
}
return true;
}
public boolean isIntroducedBy(int year) {
return null != entity && entity.getYear() <= year;
}
public boolean isExtinctIn(int year) {
//TODO: currently we do not track this in MM (and I don't think it really exists,
//but I am adding the code elsewhere to take advantage of this method if we do code it.
return false;
}
public String toString() {
String entName = "None";
if (getEntity() != null) {
entName = getEntity().getDisplayName();
}
return "Unit for Entity: " + entName;
}
}