/*
* Location.java
*
* 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.parts;
import java.io.PrintWriter;
import java.util.ArrayList;
import megamek.common.BattleArmor;
import megamek.common.Compute;
import megamek.common.Entity;
import megamek.common.EntityMovementMode;
import megamek.common.EntityWeightClass;
import megamek.common.EquipmentType;
import megamek.common.IArmorState;
import megamek.common.MechFileParser;
import megamek.common.MechSummary;
import megamek.common.MechSummaryCache;
import megamek.common.TargetRoll;
import megamek.common.TechConstants;
import megamek.common.loaders.EntityLoadingException;
import mekhq.MekHqXmlUtil;
import mekhq.campaign.Campaign;
import mekhq.campaign.parts.equipment.BattleArmorEquipmentPart;
import mekhq.campaign.personnel.Person;
import mekhq.campaign.personnel.SkillType;
import mekhq.campaign.unit.TestUnit;
import mekhq.campaign.unit.Unit;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* Battle Armor suits are crazy - you cant crit the equipment in them, so
* if we remove the suit we should remove all the equipment with the same trooper and
* track its value and tonnage in the suit object. As of 0.3.16, we are doing this differently. We are
* now using the linked child and parent part ids from the Part java to link the suit to all of its
* constituent equipment and armor. This stuff is then pulled off the unit and put back on with the
* BattleArmorSuit.remove and MissingBattleArmorSuit.fix methods. This allows us to adjust for the fact
* that modular equipment can now be removed separately. We still need to figure out how to acquire
* new suits that come pre-packaged with all of their equipment.
* @author Jay Lawson <jaylawson39 at yahoo.com>
*/
public class BattleArmorSuit extends Part {
private static final long serialVersionUID = -122291037522319765L;
protected String chassis;
protected String model;
protected boolean clan;
protected int trooper;
protected boolean quad;
protected int groundMP;
protected int jumpMP;
protected EntityMovementMode jumpType;
protected int weightClass;
private long alternateCost;
private double alternateTon;
private int introYear;
public BattleArmorSuit() {
super(0, null);
this.trooper = 0;
this.quad = false;
this.weightClass= 0;
this.groundMP = 0;
this.jumpMP = 0;
this.clan = false;
this.introYear = EquipmentType.DATE_NONE;
this.jumpType = EntityMovementMode.NONE;
this.name = "BattleArmor Suit";
}
public BattleArmorSuit(BattleArmor ba, int loc, Campaign c) {
super((int)ba.getWeight(), c);
this.trooper = loc;
this.quad = ba.getChassisType() == BattleArmor.CHASSIS_TYPE_QUAD;
this.weightClass= ba.getWeightClass();
this.groundMP = ba.getOriginalWalkMP();
this.jumpMP = ba.getOriginalJumpMP();
this.clan = ba.isClan();
this.chassis = ba.getChassis();
this.model = ba.getModel();
this.jumpType = ba.getMovementMode();
this.name = chassis + " " + model + " Suit";
initializeExtraCostsAndTons();
}
public BattleArmorSuit(String ch, String m, int ton, int t, int w, int gmp, int jmp, boolean q, boolean clan, EntityMovementMode mode, Campaign c) {
super(ton, c);
this.trooper = t;
this.quad = q;
this.weightClass= w;
this.groundMP = gmp;
this.jumpMP = jmp;
this.clan = clan;
this.chassis = ch;
this.model = m;
this.jumpType = mode;
this.name = chassis + " " + model + " Suit";
initializeExtraCostsAndTons();
}
public BattleArmorSuit clone() {
BattleArmorSuit clone = new BattleArmorSuit(chassis, model, getUnitTonnage(), trooper, weightClass, groundMP, jumpMP, quad, clan, jumpType, campaign);
clone.copyBaseData(this);
clone.alternateCost = this.alternateCost;
clone.alternateTon = this.alternateTon;
return clone;
}
public int getTrooper() {
return trooper;
}
public void setTrooper(int i) {
trooper = i;
}
public double getTonnage() {
//if there are no linked parts and the unit is null,
//then use the pre-recorded alternate costs
if(null == unit && childPartIds.size()==0) {
return alternateTon;
}
double tons = 0;
switch(weightClass) {
case EntityWeightClass.WEIGHT_ULTRA_LIGHT:
if(clan) {
tons += 0.13;
} else {
tons += 0.08;
}
tons += groundMP * .025;
if(jumpType == EntityMovementMode.INF_UMU) {
tons += jumpMP * .045;
}
else if(jumpType == EntityMovementMode.VTOL) {
tons += jumpMP * .03;
} else {
tons += jumpMP * .025;
}
break;
case EntityWeightClass.WEIGHT_LIGHT:
if(clan) {
tons += 0.15;
} else {
tons += 0.1;
}
tons += groundMP * .03;
if(jumpType == EntityMovementMode.INF_UMU) {
tons += jumpMP * .045;
}
else if(jumpType == EntityMovementMode.VTOL) {
tons += jumpMP * .04;
} else {
tons += jumpMP * .025;
}
break;
case EntityWeightClass.WEIGHT_MEDIUM:
if(clan) {
tons += 0.25;
} else {
tons += 0.175;
}
tons += groundMP * .04;
if(jumpType == EntityMovementMode.INF_UMU) {
tons += jumpMP * .085;
}
else if(jumpType == EntityMovementMode.VTOL) {
tons += jumpMP * .06;
} else {
tons += jumpMP * .05;
}
break;
case EntityWeightClass.WEIGHT_HEAVY:
if(clan) {
tons += 0.4;
} else {
tons += 0.3;
}
tons += groundMP * .08;
if(jumpType == EntityMovementMode.INF_UMU) {
tons += jumpMP * .16;
}
else {
tons += jumpMP * .125;
}
break;
case EntityWeightClass.WEIGHT_ASSAULT:
if(clan) {
tons += 0.7;
} else {
tons += 0.55;
}
tons += groundMP * .16;
tons += jumpMP * .25;
break;
}
//if there are no linked parts and the unit is null,
//then use the pre-recorded extra costs
if(null == unit && childPartIds.size()==0) {
tons += alternateTon;
}
for(int childId : childPartIds) {
Part p = campaign.getPart(childId);
if(null != p) {
tons += p.getTonnage();
}
}
return tons;
}
@Override
public long getStickerPrice() {
//if there are no linked parts and the unit is null,
//then use the pre-recorded alternate costs
if(null == unit && childPartIds.size()==0) {
return alternateCost;
}
long cost = 0;
switch(weightClass) {
case EntityWeightClass.WEIGHT_MEDIUM:
cost += 100000;
if(jumpType == EntityMovementMode.VTOL) {
cost += jumpMP * 100000;
} else {
cost += jumpMP * 75000;
}
break;
case EntityWeightClass.WEIGHT_HEAVY:
cost += 200000;
if(jumpType == EntityMovementMode.INF_UMU) {
cost += jumpMP * 100000;
} else {
cost += jumpMP * 150000;
}
break;
case EntityWeightClass.WEIGHT_ASSAULT:
cost += 400000;
if(jumpType == EntityMovementMode.INF_UMU) {
cost += jumpMP * 150000;
} else {
cost += jumpMP * 300000;
}
break;
default:
cost += 50000;
cost += 50000 * jumpMP;
}
cost += 25000 * (groundMP-1);
for(int childId : childPartIds) {
Part p = campaign.getPart(childId);
if(null != p) {
if(p instanceof BaArmor) {
cost += p.getCurrentValue();
} else {
if(p instanceof BattleArmorSuit) {
}
cost += p.getStickerPrice();
}
}
}
return cost;
}
private void initializeExtraCostsAndTons() {
alternateCost = 0;
alternateTon = 0;
//simplest way to do this is just get the full cost and tonnage of a new unit and divide by
//squad size
MechSummary summary = MechSummaryCache.getInstance().getMech(getChassis() + " " + getModel());
if(null != summary) {
int squadSize = summary.getArmorTypes().length - 1;
alternateCost = summary.getAlternateCost()/squadSize;
alternateTon = summary.getSuitWeight();
introYear = summary.getYear();
}
}
public boolean isQuad() {
return quad;
}
public int getWeightClass() {
return weightClass;
}
public int getGroundMP() {
return groundMP;
}
public int getJumpMP() {
return jumpMP;
}
public String getChassis() {
return chassis;
}
public String getModel() {
return model;
}
@Override
public boolean isSamePartType(Part part) {
//because of the linked children parts, we always need to consider these as different
//return false;
return part instanceof BattleArmorSuit
&& chassis.equals(((BattleArmorSuit)part).getChassis())
&& model.equals(((BattleArmorSuit)part).getModel())
&& this.getStickerPrice() == part.getStickerPrice();
}
@Override
public void writeToXml(PrintWriter pw1, int indent) {
writeToXmlBegin(pw1, indent);
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<chassis>"
+MekHqXmlUtil.escape(chassis)
+"</chassis>");
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<model>"
+MekHqXmlUtil.escape(model)
+"</model>");
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<clan>"
+clan
+"</clan>");
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<trooper>"
+trooper
+"</trooper>");
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<quad>"
+quad
+"</quad>");
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<groundMP>"
+groundMP
+"</groundMP>");
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<jumpMP>"
+jumpMP
+"</jumpMP>");
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<weightClass>"
+weightClass
+"</weightClass>");
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<jumpType>"
+MekHqXmlUtil.escape(EntityMovementMode.token(jumpType))
+"</jumpType>");
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<alternateCost>"
+alternateCost
+"</alternateCost>");
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<alternateTon>"
+alternateTon
+"</alternateTon>");
writeToXmlEnd(pw1, indent);
}
@Override
protected void loadFieldsFromXmlNode(Node wn) {
NodeList nl = wn.getChildNodes();
for (int x=0; x<nl.getLength(); x++) {
Node wn2 = nl.item(x);
if (wn2.getNodeName().equalsIgnoreCase("trooper")) {
trooper = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("groundMP")) {
groundMP = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("jumpMP")) {
jumpMP = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("weightClass")) {
weightClass = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("quad")) {
quad = Boolean.parseBoolean(wn2.getTextContent().trim());
} else if (wn2.getNodeName().equalsIgnoreCase("clan")) {
clan = Boolean.parseBoolean(wn2.getTextContent().trim());
} else if (wn2.getNodeName().equalsIgnoreCase("chassis")) {
chassis = MekHqXmlUtil.unEscape(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("model")) {
model = MekHqXmlUtil.unEscape(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("jumpType")) {
jumpType = EntityMovementMode.type(MekHqXmlUtil.unEscape(wn2.getTextContent()));
} else if (wn2.getNodeName().equalsIgnoreCase("alternateCost")) {
alternateCost = Long.parseLong(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("alternateTon")) {
alternateTon = Double.parseDouble(wn2.getTextContent());
}
}
}
@Override
public int getAvailability(int era) {
int chassisAvail = EquipmentType.RATING_E;
if(weightClass > EntityWeightClass.WEIGHT_ULTRA_LIGHT) {
if(era == EquipmentType.ERA_SW) {
chassisAvail = EquipmentType.RATING_F;
}
}
else if(era < EquipmentType.ERA_CLAN) {
chassisAvail = EquipmentType.RATING_X;
}
if(jumpType == EntityMovementMode.INF_UMU || jumpType == EntityMovementMode.VTOL) {
chassisAvail = EquipmentType.RATING_F;
}
return chassisAvail;
}
@Override
public int getTechRating() {
int rating = EquipmentType.RATING_E;
if(weightClass < EntityWeightClass.WEIGHT_LIGHT) {
rating = EquipmentType.RATING_D;
}
return rating;
}
@Override
public int getTechLevel() {
if(clan) {
return TechConstants.T_CLAN_TW;
} else {
return TechConstants.T_IS_TW_NON_BOX;
}
}
@Override
public int getTechBase() {
if(clan) {
return T_CLAN;
} else {
return T_IS;
}
}
@Override
public void fix() {
super.fix();
if(null != unit) {
unit.getEntity().setInternal(unit.getEntity().getOInternal(trooper), trooper);
}
}
@Override
public MissingPart getMissingPart() {
return new MissingBattleArmorSuit(chassis, model, getUnitTonnage(), trooper, weightClass, groundMP, jumpMP, quad, clan, jumpType, campaign);
}
@Override
public void remove(boolean salvage) {
ArrayList<Part> trooperParts = new ArrayList<Part>();
if(null != unit) {
Person trooperToRemove = null;
if(unit.getEntity().getInternal(trooper) > 0) {
//then there is a trooper here, so remove a crewmember
if(unit.getCrew().size() > 0) {
trooperToRemove = unit.getCrew().get(unit.getCrew().size()-1);
//dont remove yet - we need to first set the internal to
//destroyed so, this slot gets skipped over when we reset the pilot
}
}
for(Part part : unit.getParts()) {
if(part instanceof BattleArmorEquipmentPart && ((BattleArmorEquipmentPart)part).getTrooper() == trooper) {
trooperParts.add(part);
addChildPart(part);
}
if(part instanceof BaArmor && ((BaArmor)part).getLocation() == trooper) {
BaArmor armorClone = (BaArmor)part.clone();
armorClone.setAmount(((BaArmor)part).getAmount());
armorClone.setParentPartId(getId());
campaign.addPart(armorClone, 0);
addChildPart(armorClone);
}
}
unit.getEntity().setInternal(IArmorState.ARMOR_DESTROYED, trooper);
if(null != trooperToRemove) {
unit.remove(trooperToRemove, true);
}
unit.getEntity().setArmor(IArmorState.ARMOR_DESTROYED, trooper);
unit.getEntity().setLocationBlownOff(trooper, false);
Part missing = getMissingPart();
unit.addPart(missing);
campaign.addPart(missing, 0);
trooper = 0;
unit.removePart(this);
//Taharqa: I am not sure why this runDiagnostic is here and I think its problematic
//I know for certain it causes problems when we are trying to figure out damage
//to salvage unit because it can sometimes update parts before it checks for destruction
//so that they then appear to be the same and aren't checked. In general it seems
//bad form. Looking through the code, I couldnt see any obvious reason for its
//existence. I am going to remove it and see if it causes problems.
//unit.runDiagnostic(false);
}
for(Part p : trooperParts) {
p.remove(salvage);
}
Part spare = campaign.checkForExistingSparePart(this);
if(!salvage) {
campaign.removePart(this);
} else if(null != spare) {
spare.incrementQuantity();
campaign.removePart(this);
}
setUnit(null);
updateConditionFromEntity(false);
}
@Override
public void updateConditionFromEntity(boolean checkForDestruction) {
if(null != unit) {
if(trooper < 0) {
System.err.println("Trooper location -1 found on BattleArmorSuit attached to unit");
return;
}
if(unit.getEntity().getInternal(trooper) == IArmorState.ARMOR_DESTROYED) {
if(!checkForDestruction) {
remove(false);
return;
} else {
if(Compute.d6(2) < campaign.getCampaignOptions().getDestroyPartTarget()) {
remove(false);
return;
} else {
//it seems a little weird to change the entity here, but no other
//way to guarantee this happens
unit.getEntity().setInternal(0, trooper);
}
}
}
}
}
@Override
public int getBaseTime() {
return 0;
}
@Override
public int getDifficulty() {
return 0;
}
@Override
public String getDetails() {
if(null != unit) {
return "Trooper " + trooper;
} else {
int nEquip = 0;
int armor = 0;
if(getChildPartIds().size() > 0) {
for(int childId : getChildPartIds()) {
Part p = campaign.getPart(childId);
if(null != p) {
if(p instanceof BaArmor) {
armor = ((BaArmor)p).getAmount();
} else {
nEquip++;
}
}
}
return nEquip + " pieces of equipment; " + armor + " armor points";
}
}
return super.getDetails();
}
@Override
public void updateConditionFromPart() {
//According to BT Forums, if a suit survives the 10+ roll, then it is fine
//and does not need to be repaired
//http://bg.battletech.com/forums/index.php/topic,33650.new.html#new
//so we will never damage the part
}
@Override
public TargetRoll getAllMods(Person tech) {
if(isSalvaging()) {
return new TargetRoll(TargetRoll.AUTOMATIC_SUCCESS, "BA suit removal");
}
return super.getAllMods(tech);
}
@Override
public boolean isRightTechType(String skillType) {
return skillType.equals(SkillType.S_TECH_BA);
}
@Override
public boolean needsFixing() {
return false;
}
@Override
public String checkFixable() {
return null;
}
@Override
public void doMaintenanceDamage(int d) {
//not sure what the best policy is here, because we have no way to repair suits
//and no guidance from the rules as written, but I think we should just destroy
//the suit as the maintenance damage roll for BA in StratOps destroys suits
remove(false);
}
@Override
public String getLocationName() {
// TODO Auto-generated method stub
return null;
}
@Override
public int getLocation() {
return trooper;
}
public boolean needsMaintenance() {
return false;
}
/*
* This method will load up a TestUnit in order to identify the parts that need to be
* added to the suit
*/
private void addSubParts() {
//first get a copy of the entity so we can create a test unit
MechSummary summary = MechSummaryCache.getInstance().getMech(getChassis() + " " + getModel());
if(null == summary) {
return;
}
Entity newEntity = null;
try {
newEntity = new MechFileParser(summary.getSourceFile(), summary.getEntryName()).getEntity();
} catch (EntityLoadingException e) {
e.printStackTrace();
}
Unit newUnit = null;
if (null != newEntity) {
newUnit = new TestUnit(newEntity, campaign, false);
}
if(null != newUnit) {
//This now works, except when GM Mode is used to procure which must not be using the
//find method
for(Part part : newUnit.getParts()) {
if(part instanceof BattleArmorEquipmentPart && ((BattleArmorEquipmentPart)part).getTrooper() == BattleArmor.LOC_TROOPER_1) {
Part newEquip = part.clone();
newEquip.setParentPartId(getId());
campaign.addPart(newEquip, 0);
addChildPart(newEquip);
}
else if(part instanceof BaArmor && ((BaArmor)part).getLocation() == BattleArmor.LOC_TROOPER_1) {
BaArmor armorClone = (BaArmor)part.clone();
armorClone.setAmount(newUnit.getEntity().getOArmor(BattleArmor.LOC_TROOPER_1));
armorClone.setParentPartId(getId());
campaign.addPart(armorClone, 0);
addChildPart(armorClone);
}
}
}
}
@Override
public void postProcessCampaignAddition() {
if(getChildPartIds().isEmpty()) {
addSubParts();
}
}
@Override
public int getIntroDate() {
return introYear;
}
@Override
public int getExtinctDate() {
return EquipmentType.DATE_NONE;
}
@Override
public int getReIntroDate() {
return EquipmentType.DATE_NONE;
}
@Override
public int getMassRepairOptionType() {
return Part.REPAIR_PART_TYPE.ARMOR;
}
}