/*
* ProtomekLocation.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 megamek.common.CriticalSlot;
import megamek.common.EquipmentType;
import megamek.common.IArmorState;
import megamek.common.ILocationExposureStatus;
import megamek.common.Mounted;
import megamek.common.Protomech;
import megamek.common.TargetRoll;
import megamek.common.TechConstants;
import mekhq.MekHqXmlUtil;
import mekhq.campaign.Campaign;
import mekhq.campaign.personnel.Person;
import mekhq.campaign.personnel.SkillType;
import mekhq.campaign.work.WorkTime;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
*
* @author Jay Lawson <jaylawson39 at yahoo.com>
*/
public class ProtomekLocation extends Part {
private static final long serialVersionUID = -122291037522319765L;
//some of these aren't used but may be later for advanced designs (i.e. WoR)
protected int loc;
protected int structureType;
protected boolean booster;
double percent;
boolean breached;
boolean blownOff;
boolean forQuad;
//system components for head
//protected boolean sensors;
//protected boolean lifeSupport;
public ProtomekLocation() {
this(0, 0, 0, false, false, null);
}
public ProtomekLocation(int loc, int tonnage, int structureType, boolean hasBooster, boolean quad, Campaign c) {
super(tonnage, c);
this.loc = loc;
this.structureType = structureType;
this.booster = hasBooster;
this.percent = 1.0;
this.forQuad = quad;
this.breached = false;
this.name = "Protomech Location";
switch(loc) {
case(Protomech.LOC_HEAD):
this.name = "Protomech Head";
break;
case(Protomech.LOC_TORSO):
this.name = "Protomech Torso";
break;
case(Protomech.LOC_LARM):
this.name = "Protomech Left Arm";
break;
case(Protomech.LOC_RARM):
this.name = "Protomech Right Arm";
break;
case(Protomech.LOC_LEG):
this.name = "Protomech Legs";
if(forQuad) {
this.name = "Protomech Legs (Quad)";
}
break;
case(Protomech.LOC_MAINGUN):
this.name = "Protomech Main Gun";
break;
}
if(booster) {
this.name += " (Myomer Booster)";
}
}
public ProtomekLocation clone() {
ProtomekLocation clone = new ProtomekLocation(loc, getUnitTonnage(), structureType, booster, forQuad, campaign);
clone.copyBaseData(this);
clone.percent = this.percent;
clone.breached = this.breached;
clone.blownOff = this.blownOff;
return clone;
}
public int getLoc() {
return loc;
}
public boolean hasBooster() {
return booster;
}
public int getStructureType() {
return structureType;
}
public double getTonnage() {
return 0;
}
@Override
public long getStickerPrice() {
double nloc = 7.0;
if(null != unit) {
nloc = unit.getEntity().locations();
}
double totalStructureCost = 2400 * getUnitTonnage();
if(booster) {
if(null != unit) {
totalStructureCost += Math.round(unit.getEntity().getEngine().getRating() * 1000 * unit.getEntity().getWeight() * 0.025f);
} else {
//FIXME: uggh different costs by engine rating and weight, use a fake rating
totalStructureCost += Math.round(75000 * getUnitTonnage() * 0.025f);
}
}
double cost = totalStructureCost/nloc;
if (loc == Protomech.LOC_TORSO) {
cost += 575000;
}
return (long) Math.round(cost);
}
public boolean forQuad() {
return forQuad;
}
@Override
public boolean isSamePartType(Part part) {
return part instanceof ProtomekLocation
&& getLoc() == ((ProtomekLocation)part).getLoc()
&& getUnitTonnage() == ((ProtomekLocation)part).getUnitTonnage()
&& hasBooster() == ((ProtomekLocation)part).hasBooster()
&& (!isLegs() || forQuad == ((ProtomekLocation)part).forQuad);
// && getStructureType() == ((ProtomekLocation) part).getStructureType();
}
private boolean isLegs() {
return loc == Protomech.LOC_LEG;
}
@Override
public boolean isSameStatus(Part part) {
return super.isSameStatus(part) && this.getPercent() == ((ProtomekLocation)part).getPercent();
}
public double getPercent() {
return percent;
}
@Override
public void writeToXml(PrintWriter pw1, int indent) {
writeToXmlBegin(pw1, indent);
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<loc>"
+loc
+"</loc>");
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<structureType>"
+structureType
+"</structureType>");
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<booster>"
+booster
+"</booster>");
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<percent>"
+percent
+"</percent>");
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<forQuad>"
+forQuad
+"</forQuad>");
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<breached>"
+breached
+"</breached>");
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("loc")) {
loc = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("structureType")) {
structureType = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("percent")) {
percent = Double.parseDouble(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("booster")) {
if (wn2.getTextContent().equalsIgnoreCase("true"))
booster = true;
else
booster = false;
} else if (wn2.getNodeName().equalsIgnoreCase("forQuad")) {
if (wn2.getTextContent().equalsIgnoreCase("true"))
forQuad = true;
else
forQuad = false;
} else if (wn2.getNodeName().equalsIgnoreCase("breached")) {
if (wn2.getTextContent().equalsIgnoreCase("true"))
breached = true;
else
breached = false;
}
}
}
@Override
public int getAvailability(int era) {
if(era == EquipmentType.ERA_CLAN) {
return EquipmentType.RATING_E;
} else {
return EquipmentType.RATING_X;
}
}
@Override
public int getTechRating() {
return EquipmentType.RATING_E;
}
@Override
public int getTechLevel() {
return TechConstants.T_CLAN_TW;
}
@Override
public int getTechBase() {
return T_CLAN;
}
@Override
public void fix() {
super.fix();
if(isBlownOff()) {
blownOff = false;
unit.getEntity().setLocationBlownOff(loc, false);
for (int i = 0; i < unit.getEntity().getNumberOfCriticals(loc); i++) {
CriticalSlot slot = unit.getEntity().getCritical(loc, i);
// ignore empty & non-hittable slots
if (slot == null) {
continue;
}
slot.setMissing(false);
Mounted m = slot.getMount();
if(null != m) {
m.setMissing(false);
}
}
} else if(isBreached()) {
breached = false;
unit.getEntity().setLocationStatus(loc, ILocationExposureStatus.NORMAL, true);
for (int i = 0; i < unit.getEntity().getNumberOfCriticals(loc); i++) {
CriticalSlot slot = unit.getEntity().getCritical(loc, i);
// ignore empty & non-hittable slots
if (slot == null) {
continue;
}
slot.setBreached(false);
Mounted m = slot.getMount();
if(null != m) {
m.setBreached(false);
}
}
} else {
percent = 1.0;
if(null != unit) {
unit.getEntity().setInternal(unit.getEntity().getOInternal(loc), loc);
}
}
}
@Override
public MissingPart getMissingPart() {
return new MissingProtomekLocation(loc, getUnitTonnage(), structureType, booster, forQuad, campaign);
}
@Override
public void remove(boolean salvage) {
blownOff = false;
if(null != unit) {
unit.getEntity().setInternal(IArmorState.ARMOR_DESTROYED, loc);
unit.getEntity().setLocationBlownOff(loc, false);
Part spare = campaign.checkForExistingSparePart(this);
if(!salvage) {
campaign.removePart(this);
} else if(null != spare) {
spare.incrementQuantity();
campaign.removePart(this);
}
unit.removePart(this);
if(loc != Protomech.LOC_TORSO) {
Part missing = getMissingPart();
unit.addPart(missing);
campaign.addPart(missing, 0);
}
//According to StratOps, this always destroys all equipment in that location as well
for (int i = 0; i < unit.getEntity().getNumberOfCriticals(loc); i++) {
final CriticalSlot cs = unit.getEntity().getCritical(loc, i);
if(null == cs || !cs.isEverHittable()) {
continue;
}
cs.setHit(true);
cs.setDestroyed(true);
cs.setRepairable(false);
Mounted m = cs.getMount();
if(null != m) {
m.setHit(true);
m.setDestroyed(true);
m.setRepairable(false);
}
}
for(Mounted m : unit.getEntity().getEquipment()) {
if(m.getLocation() == loc || m.getSecondLocation() == loc) {
m.setHit(true);
m.setDestroyed(true);
m.setRepairable(false);
}
}
}
setUnit(null);
updateConditionFromEntity(false);
}
@Override
public void updateConditionFromEntity(boolean checkForDestruction) {
if(null != unit) {
blownOff = unit.getEntity().isLocationBlownOff(loc);
breached = unit.isLocationBreached(loc);
percent = ((double) unit.getEntity().getInternalForReal(loc)) / ((double) unit.getEntity().getOInternal(loc));
if(percent <= 0.0) {
remove(false);
return;
}
}
}
@Override
public int getBaseTime() {
if(isSalvaging()) {
if(blownOff) {
return 0;
}
return 240;
}
if(blownOff) {
return 200;
}
if(breached) {
return 60;
}
if (percent < 0.25) {
return 270;
} else if (percent < 0.5) {
return 180;
} else if (percent < 0.75) {
return 135;
}
return 90;
}
@Override
public int getDifficulty() {
if(isSalvaging()) {
if(isBlownOff()) {
return 0;
}
return 3;
}
if(blownOff) {
return 1;
}
if(breached) {
return 0;
}
if (percent < 0.25) {
return 2;
} else if (percent < 0.5) {
return 1;
} else if (percent < 0.75) {
return 0;
}
return -1;
}
public boolean isBreached() {
return breached;
}
public boolean isBlownOff() {
return blownOff;
}
@Override
public boolean needsFixing() {
return percent < 1.0 || breached || blownOff;
}
@Override
public String getDetails() {
String toReturn = "";
if(null != unit) {
toReturn = unit.getEntity().getLocationName(loc);
if(isBlownOff()) {
toReturn += " (Blown Off)";
} else if(isBreached()) {
toReturn += " (Breached)";
} else {
toReturn += " (" + Math.round(100*percent) + "%)";
}
return toReturn;
}
toReturn += getUnitTonnage() + " tons" + " (" + Math.round(100*percent) + "%)";
return toReturn;
}
private int getAppropriateSystemIndex() {
switch(loc) {
case(Protomech.LOC_LEG):
return Protomech.SYSTEM_LEGCRIT;
case(Protomech.LOC_LARM):
case(Protomech.LOC_RARM):
return Protomech.SYSTEM_ARMCRIT;
case(Protomech.LOC_HEAD):
return Protomech.SYSTEM_HEADCRIT;
case(Protomech.LOC_TORSO):
return Protomech.SYSTEM_TORSOCRIT;
default:
return -1;
}
}
@Override
public void updateConditionFromPart() {
if(null != unit) {
unit.getEntity().setInternal((int)Math.round(percent * unit.getEntity().getOInternal(loc)), loc);
//if all the system crits are marked off on the entity in this location, then we need to
//fix one of them, because the last crit on protomechs is always location destruction
int systemIndx = getAppropriateSystemIndex();
if(loc != -1 && unit.getEntity().getGoodCriticals(CriticalSlot.TYPE_SYSTEM, systemIndx, loc) <= 0) {
//Because the last crit for protomechs is always location destruction we need to
//clear the first system crit we find
for (int i = 0; i < unit.getEntity().getNumberOfCriticals(loc); i++) {
CriticalSlot slot = unit.getEntity().getCritical(loc, i);
if ((slot != null) && slot.getType() == CriticalSlot.TYPE_SYSTEM) {
slot.setDestroyed(false);
slot.setHit(false);
slot.setRepairable(true);
slot.setMissing(false);
break;
}
}
}
}
}
@Override
public String checkFixable() {
if(null == unit) {
return null;
}
if(isSalvaging()) {
//check for armor
if(unit.getEntity().getArmorForReal(loc, false) > 0
|| (unit.getEntity().hasRearArmor(loc) && unit.getEntity().getArmorForReal(loc, true) > 0 )) {
return "must salvage armor in this location first";
}
//you can only salvage a location that has nothing left on it
int systemRepairable = 0;
for (int i = 0; i < unit.getEntity().getNumberOfCriticals(loc); i++) {
CriticalSlot slot = unit.getEntity().getCritical(loc, i);
// ignore empty & non-hittable slots
if ((slot == null) || !slot.isEverHittable()) {
continue;
}
//we don't care about the final critical hit to the system
//in locations because that just represents the location destruction
if(slot.getType() == CriticalSlot.TYPE_SYSTEM) {
if(slot.isRepairable()) {
if(systemRepairable > 0) {
return "Repairable parts in " + unit.getEntity().getLocationName(loc) + " must be salvaged or scrapped first.";
} else {
systemRepairable++;
}
}
}
else if (slot.isRepairable()) {
return "Repairable parts in " + unit.getEntity().getLocationName(loc) + " must be salvaged or scrapped first.";
}
}
//protomechs only have system stuff in the crits, so we need to also
//check for mounted equipment separately
for(Mounted m : unit.getEntity().getEquipment()) {
if(m.isRepairable() && (m.getLocation() == loc || m.getSecondLocation() == loc)) {
return "Repairable parts in " + unit.getEntity().getLocationName(loc) + " must be salvaged or scrapped first." + m.getName();
}
}
}
return null;
}
@Override
public boolean isSalvaging() {
//cant salvage a center torso
if(loc == Protomech.LOC_TORSO) {
return false;
}
return super.isSalvaging();
}
@Override
public String checkScrappable() {
//cant scrap a center torso
if(loc == Protomech.LOC_TORSO) {
return "Protomech's Torso cannot be scrapped";
}
//check for armor
if(unit.getEntity().getArmor(loc, false) > 0
|| (unit.getEntity().hasRearArmor(loc) && unit.getEntity().getArmor(loc, true) > 0 )) {
return "You must first remove the armor from this location before you scrap it";
}
//you can only salvage a location that has nothing left on it
int systemRepairable = 0;
for (int i = 0; i < unit.getEntity().getNumberOfCriticals(loc); i++) {
CriticalSlot slot = unit.getEntity().getCritical(loc, i);
// ignore empty & non-hittable slots
if ((slot == null) || !slot.isEverHittable()) {
continue;
}
//we don't care about the final critical hit to the system
//in locations because that just represents the location destruction
if(slot.getType() == CriticalSlot.TYPE_SYSTEM) {
if(slot.isRepairable()) {
if(systemRepairable > 0) {
return "Repairable parts in " + unit.getEntity().getLocationName(loc) + " must be salvaged or scrapped first.";
} else {
systemRepairable++;
}
}
}
else if (slot.isRepairable()) {
return "Repairable parts in " + unit.getEntity().getLocationName(loc) + " must be salvaged or scrapped first.";
}
}
//protomechs only have system stuff in the crits, so we need to also
//check for mounted equipment separately
for(Mounted m : unit.getEntity().getEquipment()) {
if(m.isRepairable() && (m.getLocation() == loc || m.getSecondLocation() == loc)) {
return "Repairable parts in " + unit.getEntity().getLocationName(loc) + " must be salvaged or scrapped first." + m.getName();
}
}
return null;
}
@Override
public TargetRoll getAllMods(Person tech) {
if(isBreached() && !isSalvaging()) {
return new TargetRoll(TargetRoll.AUTOMATIC_SUCCESS, "fixing breach");
}
if(isBlownOff() && isSalvaging()) {
return new TargetRoll(TargetRoll.AUTOMATIC_SUCCESS, "salvaging blown-off location");
}
return super.getAllMods(tech);
}
public String getDesc() {
if((!isBreached() && !isBlownOff()) || isSalvaging()) {
return super.getDesc();
}
String toReturn = "<html><font size='2'";
String scheduled = "";
if (getTeamId() != null) {
scheduled = " (scheduled) ";
}
toReturn += ">";
if(isBlownOff()) {
toReturn += "<b>Re-attach " + getName() + "</b><br/>";
} else {
toReturn += "<b>Seal " + getName() + "</b><br/>";
}
toReturn += getDetails() + "<br/>";
if(getSkillMin() > SkillType.EXP_ELITE) {
toReturn += "<font color='red'>Impossible</font>";
} else {
toReturn += "" + getTimeLeft() + " minutes" + scheduled;
if(isBlownOff()) {
String bonus = getAllMods(null).getValueAsString();
if (getAllMods(null).getValue() > -1) {
bonus = "+" + bonus;
}
bonus = "(" + bonus + ")";
if(!getCampaign().getCampaignOptions().isDestroyByMargin()) {
toReturn += ", " + SkillType.getExperienceLevelName(getSkillMin());
}
toReturn += " " + bonus;
if (getMode() != WorkTime.NORMAL) {
toReturn += "<br/><i>" + getCurrentModeName() + "</i>";
}
}
}
toReturn += "</font></html>";
return toReturn;
}
@Override
public boolean onBadHipOrShoulder() {
return false;
}
@Override
public boolean isRightTechType(String skillType) {
return skillType.equals(SkillType.S_TECH_MECH);
}
public void doMaintenanceDamage(int d) {
int points = unit.getEntity().getInternal(loc);
points = Math.max(points -d, 1);
unit.getEntity().setInternal(points, loc);
updateConditionFromEntity(false);
}
@Override
public String getLocationName() {
return unit.getEntity().getLocationName(loc);
}
@Override
public int getLocation() {
return loc;
}
@Override
public int getIntroDate() {
return 3055;
}
@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.GENERAL_LOCATION;
}
@Override
public int getRepairPartType() {
return Part.REPAIR_PART_TYPE.MEK_LOCATION;
}
}