/*
* MissingPart.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.io.Serializable;
import java.util.Calendar;
import java.util.UUID;
import megamek.common.EquipmentType;
import megamek.common.TargetRoll;
import mekhq.MekHqXmlSerializable;
import mekhq.MekHqXmlUtil;
import mekhq.Utilities;
import mekhq.campaign.Campaign;
import mekhq.campaign.personnel.SkillType;
import mekhq.campaign.work.IAcquisitionWork;
import mekhq.campaign.work.IPartWork;
import mekhq.campaign.work.WorkTime;
/**
* A missing part is a placeholder on a unit to indicate that a replacement
* task needs to be performed
* @author Jay Lawson <jaylawson39 at yahoo.com>
*/
public abstract class MissingPart extends Part implements Serializable, MekHqXmlSerializable, IPartWork, IAcquisitionWork {
/**
*
*/
private static final long serialVersionUID = 300672661487966982L;
public MissingPart(int tonnage, Campaign c) {
super(tonnage, false, c);
}
public MissingPart(int tonnage, boolean isOmniPodded, Campaign c) {
super(tonnage, isOmniPodded, c);
}
public MissingPart clone() {
//should never be called
return null;
}
@Override
public long getStickerPrice() {
//missing parts aren't worth a thing
return 0;
}
@Override
public long getBuyCost() {
return getNewPart().getStickerPrice();
}
@Override
public boolean isSalvaging() {
return false;
}
@Override
public String getStatus() {
return "Destroyed";
}
@Override
public boolean isSamePartType(Part part) {
//missing parts should always return false
return false;
}
public String getDesc() {
String bonus = getAllMods(null).getValueAsString();
if (getAllMods(null).getValue() > -1) {
bonus = "+" + bonus;
}
bonus = "(" + bonus + ")";
String toReturn = "<html><font size='2'";
String scheduled = "";
if (getTeamId() != null) {
scheduled = " (scheduled) ";
}
//if (this instanceof ReplacementItem
// && !((ReplacementItem) this).hasPart()) {
// toReturn += " color='white'";
//}
toReturn += ">";
toReturn += "<b>Replace " + getName() + "</b><br/>";
toReturn += getDetails() + "<br/>";
if(getSkillMin() > SkillType.EXP_ELITE) {
toReturn += "<font color='red'>Impossible</font>";
} else {
toReturn += "" + getTimeLeft() + " minutes" + scheduled;
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 String succeed() {
fix();
return " <font color='green'><b> replaced.</b></font>";
}
@Override
public void fix() {
Part replacement = findReplacement(false);
if(null != replacement) {
Part actualReplacement = replacement.clone();
unit.addPart(actualReplacement);
campaign.addPart(actualReplacement, 0);
replacement.decrementQuantity();
remove(false);
//assign the replacement part to the unit
actualReplacement.updateConditionFromPart();
}
}
@Override
public void remove(boolean salvage) {
campaign.removePart(this);
if(null != unit) {
unit.removePart(this);
}
setUnit(null);
}
public abstract boolean isAcceptableReplacement(Part part, boolean refit);
public Part findReplacement(boolean refit) {
Part bestPart = null;
//check to see if we already have a replacement assigned
if(replacementId > -1) {
bestPart = campaign.getPart(replacementId);
if(null != bestPart) {
return bestPart;
}
}
// don't just return with the first part if it is damaged
for(Part part : campaign.getSpareParts()) {
if(part.isReservedForRefit() || part.isBeingWorkedOn() || part.isReservedForReplacement() || !part.isPresent() || part.hasParentPart()) {
continue;
}
if(isAcceptableReplacement(part, refit)) {
if(null == bestPart) {
bestPart = part;
} else if(bestPart.needsFixing() && !part.needsFixing()) {
bestPart = part;
}
}
}
return bestPart;
}
public boolean isReplacementAvailable() {
return null != findReplacement(false);
}
@Override
public String getDetails() {
String[] inventories = campaign.getPartInventory(getNewPart());
if(isReplacementAvailable()) {
return "Replacement part available";
} else {
return "<font color='red'>No replacement (" + inventories[1] + " in transit, " + inventories[2] + " on order)</font>";
}
}
@Override
public boolean needsFixing() {
//missing parts always need fixing
if(null != unit) {
return (!unit.isSalvage() || null != getTeamId()) && unit.isRepairable();
}
return false;
}
@Override
public MissingPart getMissingPart() {
//do nothing - this should never be accessed
return null;
}
@Override
public void updateConditionFromEntity(boolean checkForDestruction) {
//do nothing
}
@Override
public String fail(int rating) {
skillMin = ++rating;
timeSpent = 0;
shorthandedMod = 0;
if(skillMin > SkillType.EXP_ELITE) {
Part part = findReplacement(false);
if(null != part) {
part.decrementQuantity();
skillMin = SkillType.EXP_GREEN;
}
return " <font color='red'><b> failed and part destroyed.</b></font>";
} else {
return " <font color='red'><b> failed.</b></font>";
}
}
@Override
public boolean canChangeWorkMode() {
return !isOmniPodded();
}
@Override
public TargetRoll getAllAcquisitionMods() {
TargetRoll target = new TargetRoll();
if(getTechBase() == T_CLAN && campaign.getCampaignOptions().getClanAcquisitionPenalty() > 0) {
target.addModifier(campaign.getCampaignOptions().getClanAcquisitionPenalty(), "clan-tech");
}
else if(getTechBase() == T_IS && campaign.getCampaignOptions().getIsAcquisitionPenalty() > 0) {
target.addModifier(campaign.getCampaignOptions().getIsAcquisitionPenalty(), "Inner Sphere tech");
}
else if(getTechBase() == T_BOTH) {
int penalty = Math.min(campaign.getCampaignOptions().getClanAcquisitionPenalty(), campaign.getCampaignOptions().getIsAcquisitionPenalty());
if(penalty > 0) {
target.addModifier(penalty, "tech limit");
}
}
//availability mod
int avail = getAvailability(campaign.getEra());
if(this.isExtinctIn(campaign.getCalendar().get(Calendar.YEAR))) {
avail = EquipmentType.RATING_X;
}
int availabilityMod = Availability.getAvailabilityModifier(avail);
target.addModifier(availabilityMod, "availability (" + EquipmentType.getRatingName(avail) + ")");
return target;
}
@Override
public String getAcquisitionDesc() {
String toReturn = "<html><font size='2'";
toReturn += ">";
toReturn += "<b>" + getAcquisitionDisplayName() + "</b> " + getAcquisitionBonus() + "<br/>";
String[] inventories = campaign.getPartInventory(getNewPart());
toReturn += inventories[1] + " in transit, " + inventories[2] + " on order";
if (!isOmniPodded()) {
Part newPart = getAcquisitionPart();
newPart.setOmniPodded(true);
inventories = campaign.getPartInventory(newPart);
if (Integer.parseInt(inventories[0]) > 0) {
toReturn += ", " + inventories[0] + " OmniPod";
}
}
toReturn += "<br/>";
toReturn += Utilities.getCurrencyString(getBuyCost()) + "<br/>";
toReturn += "</font></html>";
return toReturn;
}
@Override
public String getAcquisitionDisplayName() {
return getAcquisitionName();
}
@Override
public String getAcquisitionExtraDesc() {
return "";
}
@Override
public String getAcquisitionBonus() {
String bonus = getAllAcquisitionMods().getValueAsString();
if(getAllAcquisitionMods().getValue() > -1) {
bonus = "+" + bonus;
}
return "(" + bonus + ")";
}
@Override
public Part getAcquisitionPart() {
return getNewPart();
}
@Override
public String find(int transitDays) {
Part newPart = getNewPart();
newPart.setBrandNew(true);
newPart.setDaysToArrival(transitDays);
if(campaign.buyPart(newPart, transitDays)) {
return "<font color='green'><b> part found</b>.</font> It will be delivered in " + transitDays + " days.";
} else {
return "<font color='red'><b> You cannot afford this part. Transaction cancelled</b>.</font>";
}
}
@Override
public Object getNewEquipment() {
return getNewPart();
}
public abstract Part getNewPart();
@Override
public String failToFind() {
resetDaysToWait();
return "<font color='red'><b> part not found</b>.</font>";
}
@Override
public void writeToXmlBegin(PrintWriter pw1, int indent) {
super.writeToXmlBegin(pw1, indent);
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<daysToWait>"
+daysToWait
+"</daysToWait>");
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<replacementId>"
+replacementId
+"</replacementId>");
}
@Override
public void writeToXml(PrintWriter pw1, int indent) {
writeToXmlBegin(pw1, indent);
writeToXmlEnd(pw1, indent);
}
@Override
public String checkScrappable() {
if(!isReplacementAvailable()) {
return "Nothing to scrap";
}
return null;
}
@Override
public String scrap() {
Part replace = findReplacement(false);
if(null != replace) {
replace.decrementQuantity();
}
skillMin = SkillType.EXP_GREEN;
return replace.getName() + " scrapped.";
}
@Override
public String getAcquisitionName() {
String details = getNewPart().getDetails();
details = details.replaceFirst("\\d+\\shit\\(s\\)", "");
return getPartName() + " " + details;
}
@Override
public int getTechLevel() {
return getNewPart().getTechLevel();
}
@Override
public void reservePart() {
//this is being set as an overnight repair, so
//we also need to reserve the replacement. If the
//quantity of the replacement is more than one, we will
//also need to split off a separate one
//shouldn't be null, but it never hurts to check
Part replacement = findReplacement(false);
UUID teamId = getTeamId();
if(null != replacement && null != teamId) {
if(replacement.getQuantity() > 1) {
Part actualReplacement = replacement.clone();
actualReplacement.setReserveId(teamId);
campaign.addPart(actualReplacement, 0);
replacementId = actualReplacement.getId();
replacement.decrementQuantity();
} else {
replacement.setReserveId(teamId);
replacementId = replacement.getId();
}
}
}
@Override
public void cancelReservation() {
Part replacement = findReplacement(false);
if(replacementId > -1 && null != replacement) {
replacementId = -1;
replacement.setReserveId(null);
if(replacement.isSpare()) {
Part spare = campaign.checkForExistingSparePart(replacement);
if(null != spare) {
spare.incrementQuantity();
campaign.removePart(replacement);
}
}
}
}
@Override
public boolean needsMaintenance() {
return false;
}
}