mekhq.campaign.mission.Contract.java Source code

Java tutorial

Introduction

Here is the source code for mekhq.campaign.mission.Contract.java

Source

/*
 * 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.apache.commons.text.CharacterPredicate;
import org.apache.commons.text.RandomStringGenerator;
import org.joda.time.DateTime;
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.JumpPath;
import mekhq.campaign.unit.Unit;

/**
 * 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 transitAmount;
    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 + transitAmount + signingAmount
                - feeAmount;
    }

    public long getTotalAmount() {
        return baseAmount + supportAmount + overheadAmount + transportAmount + transitAmount;
    }

    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 getTransitAmount() {
        return transitAmount;
    }

    protected void setTransitAmount(long amount) {
        transitAmount = 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 static String generateRandomContractName() {
        RandomStringGenerator generator = new RandomStringGenerator.Builder().withinRange('0', 'Z')
                .filteredBy(UpperCaseAndDigits.UPPERANDDIGITS).build();
        return generator.generate(15);
    }

    public static enum UpperCaseAndDigits implements CharacterPredicate {
        UPPERANDDIGITS {
            @Override
            public boolean test(int codePoint) {
                return (Character.isDigit(codePoint) || Character.isUpperCase(codePoint));
            }
        }
    }

    private int getTravelDays(Campaign c) {
        if (null != this.getPlanet()) {
            DateTime currentDate = Utilities.getDateTimeDay(c.getCalendar());
            JumpPath jumpPath = c.calculateJumpPath(c.getCurrentPlanet(), getPlanet());
            double days = Math.round(jumpPath.getTotalTime(currentDate, c.getLocation().getTransitTime()) * 100.0)
                    / 100.0;
            return (int) Math.round(days);
        }
        return 0;
    }

    private int getLengthPlusTravel(Campaign c) {
        int travelMonths = (int) Math.ceil(getTravelDays(c) / 30.0) * 2;
        return getLength() + travelMonths;
    }

    /**
     * Get the estimated total profit for this contract. The total profit is the total contract
     * payment including fees and bonuses, minus overhead, maintenance, payroll, spare parts,
     * and other monthly expenses. The duration used for monthly expenses is the contract duration
     * plus the travel time from the unit's current world to the contract world and back.
     *
     * @param c The campaign with which this contract is associated.
     * @return The estimated profit in C-bills.
     */
    public long getEstimatedTotalProfit(Campaign c) {
        long profit = getTotalAmountPlusFeesAndBonuses();
        profit -= c.getOverheadExpenses() * getLengthPlusTravel(c);
        profit -= c.getMaintenanceCosts() * getLengthPlusTravel(c);
        if (c.getCampaignOptions().usePeacetimeCost()) {
            profit -= c.getPeacetimeCost() * getLengthPlusTravel(c);
        } else {
            profit -= c.getPayRoll() * getLengthPlusTravel(c);
        }
        if (null != c.getPlanet(planetId) && c.getCampaignOptions().payForTransport()) {
            JumpPath jumpPath = c.calculateJumpPath(c.getCurrentPlanet(), getPlanet());

            boolean campaignOps = c.getCampaignOptions().useEquipmentContractBase();
            profit -= 2 * c.calculateCostPerJump(campaignOps, campaignOps) * jumpPath.getJumps();
        }
        return profit;
    }

    /**
     * Get the number of months left in this contract after the given date. Partial months are counted as
     * full months.
     *
     * @param date
     * @return
     */
    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 = 1 * c.getOverheadExpenses() * getLength();
            break;
        default:
            overheadAmount = 0;
        }

        //calculate support amount
        if (c.getCampaignOptions().usePeacetimeCost() && c.getCampaignOptions().getUnitRatingMethod()
                .equals(mekhq.campaign.rating.UnitRatingMethod.CAMPAIGN_OPS)) {
            supportAmount = (long) ((straightSupport / 100.0) * c.getPeacetimeCost() * getLength());
        } else {
            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 != getPlanet()) {
            JumpPath jumpPath = c.calculateJumpPath(c.getCurrentPlanet(), getPlanet());

            // FM:Mercs transport payments take into account owned transports and do not use CampaignOps dropship costs.
            // CampaignOps doesn't care about owned transports and does use its own dropship costs.
            boolean campaignOps = c.getCampaignOptions().useEquipmentContractBase();
            transportAmount = (long) ((transportComp / 100.0) * 2 * c.calculateCostPerJump(campaignOps, campaignOps)
                    * jumpPath.getJumps());
        }

        //calculate transit amount for CO
        if (c.getCampaignOptions().usePeacetimeCost() && c.getCampaignOptions().getUnitRatingMethod()
                .equals(mekhq.campaign.rating.UnitRatingMethod.CAMPAIGN_OPS)) {
            //contract base * transport period * reputation * employer modifier
            transitAmount = (long) (c.getContractBase()
                    * (((c.calculateJumpPath(c.getCurrentPlanet(), getPlanet()).getJumps()) * 2) / 4)
                    * (c.getUnitRatingMod() * .2 + .5) * 1.2);
        } else {
            transitAmount = 0;
        }

        signingAmount = (long) ((signBonus / 100.0)
                * (baseAmount + overheadAmount + transportAmount + transitAmount + supportAmount));

        advanceAmount = (long) ((advancePct / 100.0)
                * (baseAmount + overheadAmount + transportAmount + transitAmount + supportAmount));

        if (mrbcFee) {
            feeAmount = (long) (0.05
                    * (baseAmount + overheadAmount + transportAmount + transitAmount + 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(planetId)) {
            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();
    }

    @Override
    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) + "<transitAmount>" + transitAmount + "</transitAmount>");
        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>");
    }

    @Override
    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("transitAmount")) {
                transitAmount = 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());
            }
        }
    }
}