/*
* Contract.java
*
* Copyright (c) 2011 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.mission;
import java.io.PrintWriter;
import java.io.Serializable;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import megamek.common.BattleArmor;
import megamek.common.Infantry;
import mekhq.MekHqXmlSerializable;
import mekhq.MekHqXmlUtil;
import mekhq.Utilities;
import mekhq.campaign.Campaign;
import mekhq.campaign.unit.Unit;
import mekhq.campaign.universe.Planets;
/**
* Contracts - we need to track static amounts here because changes in the underlying
* campaign don't change the figures once the ink is dry
*
*
* @author Jay Lawson <jaylawson39 at yahoo.com>
*/
public class Contract extends Mission implements Serializable, MekHqXmlSerializable {
/**
*
*/
private static final long serialVersionUID = 4606932545119410453L;
public final static int OH_NONE = 0;
public final static int OH_HALF = 1;
public final static int OH_FULL = 2;
public final static int OH_NUM = 3;
public final static int COM_INTEGRATED = 0;
public final static int COM_HOUSE = 1;
public final static int COM_LIAISON = 2;
public final static int COM_INDEP = 3;
public final static int COM_NUM = 4;
private Date startDate;
private Date endDate;
private int nMonths;
private String employer;
private double paymentMultiplier;
private int commandRights;
private int overheadComp;
private int straightSupport;
private int battleLossComp;
private int salvagePct;
private boolean salvageExchange;
private int transportComp;
private boolean mrbcFee;
private int advancePct;
private int signBonus;
//need to keep track of total value salvaged for salvage rights
private long salvagedByUnit = 0;
private long salvagedByEmployer = 0;
//actual amounts
private long advanceAmount;
private long signingAmount;
private long transportAmount;
private long overheadAmount;
private long supportAmount;
private long baseAmount;
private long feeAmount;
public Contract() {
this(null,null);
}
public Contract(String name, String employer) {
super(name);
this.employer = employer;
this.nMonths = 12;
this.paymentMultiplier = 2.0;
this.commandRights = COM_HOUSE;
this.overheadComp = OH_NONE;
this.straightSupport = 50;
this.battleLossComp = 50;
this.salvagePct = 50;
this.salvageExchange = false;
this.transportComp = 50;
this.mrbcFee = true;
this.advancePct = 25;
this.signBonus = 0;
}
public static String getOverheadCompName(int i) {
switch(i) {
case OH_NONE:
return "None";
case OH_HALF:
return "Half";
case OH_FULL:
return "Full";
default:
return "?";
}
}
public static String getCommandRightsName(int i) {
switch(i) {
case COM_INTEGRATED:
return "Integrated";
case COM_HOUSE:
return "House";
case COM_LIAISON:
return "Liaison";
case COM_INDEP:
return "Independent";
default:
return "?";
}
}
public String getEmployer() {
return employer;
}
public void setEmployer(String s) {
this.employer = s;
}
public int getLength() {
return nMonths;
}
public void setLength(int m) {
nMonths = m;
}
public Date getStartDate() {
return startDate;
}
public void setStartDate(Date d) {
startDate = d;
}
public Date getEndingDate() {
return endDate;
}
public double getMultiplier() {
return paymentMultiplier;
}
public void setMultiplier(double s) {
paymentMultiplier = s;
}
public int getTransportComp() {
return transportComp;
}
public void setTransportComp(int s) {
transportComp = s;
}
public int getStraightSupport() {
return straightSupport;
}
public void setStraightSupport(int s) {
straightSupport = Math.max(0, Math.min(100, s));
}
public int getOverheadComp() {
return overheadComp;
}
public void setOverheadComp(int s) {
overheadComp = s;
}
public int getCommandRights() {
return commandRights;
}
public void setCommandRights(int s) {
commandRights = s;
}
public int getBattleLossComp() {
return battleLossComp;
}
public void setBattleLossComp(int s) {
battleLossComp = Math.max(0, Math.min(100, s));
}
public int getSalvagePct() {
return salvagePct;
}
public void setSalvagePct(int s) {
salvagePct = s;
}
public boolean isSalvageExchange() {
return salvageExchange;
}
public void setSalvageExchange(boolean b) {
salvageExchange = b;
}
public boolean canSalvage() {
return salvagePct > 0;
}
public long getSalvagedByUnit() {
return salvagedByUnit;
}
public void setSalvagedByUnit(long l) {
this.salvagedByUnit = l;
}
public void addSalvageByUnit(long l) {
salvagedByUnit += l;
}
public long getSalvagedByEmployer() {
return salvagedByEmployer;
}
public void setSalvagedByEmployer(long l) {
this.salvagedByEmployer = l;
}
public void addSalvageByEmployer(long l) {
salvagedByEmployer += l;
}
public int getSigningBonusPct() {
return signBonus;
}
public void setSigningBonusPct(int s) {
signBonus = s;
}
public int getAdvancePct() {
return advancePct;
}
public void setAdvancePct(int s) {
advancePct = s;
}
public boolean payMRBCFee() {
return mrbcFee;
}
public void setMRBCFee(boolean b) {
mrbcFee = b;
}
public long getTotalAmountPlusFeesAndBonuses() {
return baseAmount + supportAmount + overheadAmount + transportAmount + signingAmount - feeAmount;
}
public long getTotalAmount() {
return baseAmount + supportAmount + overheadAmount + transportAmount;
}
public long getAdvanceAmount() {
return advanceAmount;
}
protected void setAdvanceAmount(long amount) {
advanceAmount = amount;
}
public long getFeeAmount() {
return feeAmount;
}
protected void setFeeAmount(long amount) {
feeAmount = amount;
}
public long getBaseAmount() {
return baseAmount;
}
protected void setBaseAmount(long amount) {
baseAmount = amount;
}
public long getOverheadAmount() {
return overheadAmount;
}
protected void setOverheadAmount(long amount) {
overheadAmount = amount;
}
public long getSupportAmount() {
return supportAmount;
}
protected void setSupportAmount(long amount) {
supportAmount = amount;
}
public long getTransportAmount() {
return transportAmount;
}
protected void setTransportAmount(long amount) {
transportAmount = amount;
}
public long getSigningBonusAmount() {
return signingAmount;
}
protected void setSigningBonusAmount (long amount) {
signingAmount = amount;
}
public long getMonthlyPayOut() {
return (getTotalAmountPlusFeesAndBonuses() - getTotalAdvanceMonies())/getLength();
}
public long getTotalAdvanceMonies() {
return getAdvanceAmount() + getSigningBonusAmount();
}
public long getEstimatedTotalProfit(Campaign c) {
long profit = getTotalAmountPlusFeesAndBonuses();
profit -= c.getOverheadExpenses() * getLength();
profit -= c.getMaintenanceCosts() * getLength();
profit -= c.getPayRoll() * getLength();
if(null != c.getPlanet(planetName) && c.getCampaignOptions().payForTransport()) {
profit -= 2 * c.calculateCostPerJump(true) * c.calculateJumpPath(c.getCurrentPlanet(), getPlanet()).getJumps();
}
return profit;
}
public int getMonthsLeft(Date date) {
GregorianCalendar cal = new GregorianCalendar();
cal.setTime(date);
cal.add(Calendar.MONTH, 1);
date = cal.getTime();
int monthsLeft = 0;
while(date.before(endDate) || date.equals(endDate)) {
monthsLeft++;
cal.add(Calendar.MONTH, 1);
date = cal.getTime();
}
return monthsLeft;
}
/**
* Only do this at the time the contract is set up, otherwise amounts may change after
* the ink is signed, which is a no-no.
* @param c
*/
public void calculateContract(Campaign c) {
//calculate base amount
baseAmount = (long)(paymentMultiplier * getLength() * c.getContractBase());
//calculate overhead
switch(overheadComp) {
case OH_HALF:
overheadAmount = (long)(0.5 * c.getOverheadExpenses() * getLength());
break;
case OH_FULL:
overheadAmount = (long)(1 * c.getOverheadExpenses() * getLength());
break;
default:
overheadAmount = 0;
}
//calculate support amount
long maintCosts = 0;
for (Unit u : c.getUnits()) {
if (u.getEntity() instanceof Infantry && !(u.getEntity() instanceof BattleArmor)) {
continue;
}
maintCosts += u.getWeeklyMaintenanceCost();
}
maintCosts *= 4;
supportAmount = (long)((straightSupport/100.0) * maintCosts * getLength());
//calculate transportation costs
if(null != Planets.getInstance().getPlanetById(planetName)) {
transportAmount = (long)((transportComp/100.0) * 2 * c.calculateCostPerJump(false) * c.calculateJumpPath(c.getCurrentPlanet(), getPlanet()).getJumps());
}
signingAmount = (long)((signBonus/100.0) * (baseAmount + overheadAmount + transportAmount + supportAmount));
advanceAmount = (long)((advancePct/100.0) * (baseAmount + overheadAmount + transportAmount + supportAmount));
if(mrbcFee) {
feeAmount = (long)(0.05 * (baseAmount + overheadAmount + transportAmount + supportAmount));
} else {
feeAmount = 0;
}
//only adjust the start date for travel if the start date is currently null
boolean adjustStartDate = false;
if(null == startDate) {
startDate = c.getCalendar().getTime();
adjustStartDate = true;
}
GregorianCalendar cal = new GregorianCalendar();
cal.setTime(startDate);
if(adjustStartDate && null != c.getPlanet(planetName)) {
int days = (int)Math.ceil(c.calculateJumpPath(c.getCurrentPlanet(), getPlanet()).getTotalTime(Utilities.getDateTimeDay(cal), c.getLocation().getTransitTime()));
while(days > 0) {
cal.add(Calendar.DAY_OF_YEAR, 1);
days--;
}
startDate = cal.getTime();
}
int months = getLength();
while(months > 0) {
cal.add(Calendar.MONTH, 1);
months--;
}
endDate = cal.getTime();
}
protected void writeToXmlBegin(PrintWriter pw1, int indent) {
super.writeToXmlBegin(pw1, indent);
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<nMonths>"
+nMonths
+"</nMonths>");
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<startDate>"
+df.format(startDate)
+"</startDate>");
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<endDate>"
+df.format(endDate)
+"</endDate>");
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<employer>"
+MekHqXmlUtil.escape(employer)
+"</employer>");
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<paymentMultiplier>"
+paymentMultiplier
+"</paymentMultiplier>");
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<commandRights>"
+commandRights
+"</commandRights>");
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<overheadComp>"
+overheadComp
+"</overheadComp>");
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<salvagePct>"
+salvagePct
+"</salvagePct>");
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<salvageExchange>"
+salvageExchange
+"</salvageExchange>");
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<straightSupport>"
+straightSupport
+"</straightSupport>");
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<battleLossComp>"
+battleLossComp
+"</battleLossComp>");
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<transportComp>"
+transportComp
+"</transportComp>");
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<mrbcFee>"
+mrbcFee
+"</mrbcFee>");
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<advancePct>"
+advancePct
+"</advancePct>");
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<signBonus>"
+signBonus
+"</signBonus>");
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<advanceAmount>"
+advanceAmount
+"</advanceAmount>");
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<signingAmount>"
+signingAmount
+"</signingAmount>");
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<transportAmount>"
+transportAmount
+"</transportAmount>");
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<overheadAmount>"
+overheadAmount
+"</overheadAmount>");
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<supportAmount>"
+supportAmount
+"</supportAmount>");
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<baseAmount>"
+baseAmount
+"</baseAmount>");
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<feeAmount>"
+feeAmount
+"</feeAmount>");
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<salvagedByUnit>"
+salvagedByUnit
+"</salvagedByUnit>");
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+"<salvagedByEmployer>"
+salvagedByEmployer
+"</salvagedByEmployer>");
}
public void loadFieldsFromXmlNode(Node wn) throws ParseException {
// Okay, now load mission-specific fields!
NodeList nl = wn.getChildNodes();
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
for (int x=0; x<nl.getLength(); x++) {
Node wn2 = nl.item(x);
if (wn2.getNodeName().equalsIgnoreCase("employer")) {
employer = wn2.getTextContent();
}
else if (wn2.getNodeName().equalsIgnoreCase("startDate")) {
startDate = df.parse(wn2.getTextContent().trim());
}
else if (wn2.getNodeName().equalsIgnoreCase("endDate")) {
endDate = df.parse(wn2.getTextContent().trim());
}
else if (wn2.getNodeName().equalsIgnoreCase("nMonths")) {
nMonths = Integer.parseInt(wn2.getTextContent().trim());
} else if (wn2.getNodeName().equalsIgnoreCase("paymentMultiplier")) {
paymentMultiplier = Double.parseDouble(wn2.getTextContent().trim());
} else if (wn2.getNodeName().equalsIgnoreCase("commandRights")) {
commandRights = Integer.parseInt(wn2.getTextContent().trim());
} else if (wn2.getNodeName().equalsIgnoreCase("overheadComp")) {
overheadComp = Integer.parseInt(wn2.getTextContent().trim());
} else if (wn2.getNodeName().equalsIgnoreCase("salvagePct")) {
salvagePct = Integer.parseInt(wn2.getTextContent().trim());
} else if (wn2.getNodeName().equalsIgnoreCase("salvageExchange")) {
if (wn2.getTextContent().trim().equals("true"))
salvageExchange = true;
else
salvageExchange = false;
} else if (wn2.getNodeName().equalsIgnoreCase("straightSupport")) {
straightSupport = Integer.parseInt(wn2.getTextContent().trim());
} else if (wn2.getNodeName().equalsIgnoreCase("battleLossComp")) {
battleLossComp = Integer.parseInt(wn2.getTextContent().trim());
} else if (wn2.getNodeName().equalsIgnoreCase("salvagePct")) {
salvagePct = Integer.parseInt(wn2.getTextContent().trim());
} else if (wn2.getNodeName().equalsIgnoreCase("transportComp")) {
transportComp = Integer.parseInt(wn2.getTextContent().trim());
} else if (wn2.getNodeName().equalsIgnoreCase("advancePct")) {
advancePct = Integer.parseInt(wn2.getTextContent().trim());
} else if (wn2.getNodeName().equalsIgnoreCase("signBonus")) {
signBonus = Integer.parseInt(wn2.getTextContent().trim());
} else if (wn2.getNodeName().equalsIgnoreCase("mrbcFee")) {
if (wn2.getTextContent().trim().equals("true"))
mrbcFee = true;
else
mrbcFee = false;
}
else if (wn2.getNodeName().equalsIgnoreCase("advanceAmount")) {
advanceAmount = Long.parseLong(wn2.getTextContent().trim());
}
else if (wn2.getNodeName().equalsIgnoreCase("signingAmount")) {
signingAmount = Long.parseLong(wn2.getTextContent().trim());
}
else if (wn2.getNodeName().equalsIgnoreCase("transportAmount")) {
transportAmount = Long.parseLong(wn2.getTextContent().trim());
}
else if (wn2.getNodeName().equalsIgnoreCase("overheadAmount")) {
overheadAmount = Long.parseLong(wn2.getTextContent().trim());
}
else if (wn2.getNodeName().equalsIgnoreCase("supportAmount")) {
supportAmount = Long.parseLong(wn2.getTextContent().trim());
}
else if (wn2.getNodeName().equalsIgnoreCase("baseAmount")) {
baseAmount = Long.parseLong(wn2.getTextContent().trim());
}
else if (wn2.getNodeName().equalsIgnoreCase("feeAmount")) {
feeAmount = Long.parseLong(wn2.getTextContent().trim());
}
else if (wn2.getNodeName().equalsIgnoreCase("salvagedByUnit")) {
salvagedByUnit = Long.parseLong(wn2.getTextContent().trim());
}
else if (wn2.getNodeName().equalsIgnoreCase("salvagedByEmployer")) {
salvagedByEmployer = Long.parseLong(wn2.getTextContent().trim());
}
}
}
}