/*
* ContractMarket.java
*
* Copyright (c) 2014 Carl Spain. 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.market;
import java.io.PrintWriter;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Set;
import org.joda.time.DateTime;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import megamek.client.RandomSkillsGenerator;
import megamek.common.Compute;
import mekhq.MekHQ;
import mekhq.MekHqXmlUtil;
import mekhq.Utilities;
import mekhq.Version;
import mekhq.campaign.Campaign;
import mekhq.campaign.JumpPath;
import mekhq.campaign.mission.AtBContract;
import mekhq.campaign.mission.Contract;
import mekhq.campaign.mission.Mission;
import mekhq.campaign.personnel.Person;
import mekhq.campaign.personnel.SkillType;
import mekhq.campaign.rating.IUnitRating;
import mekhq.campaign.universe.Faction;
import mekhq.campaign.universe.Planet;
import mekhq.campaign.universe.Planets;
import mekhq.campaign.universe.RandomFactionGenerator;
/**
* Contract offers that are generated monthly under AtB rules.
*
* Based on PersonnelMarket
*
* @author Neoancient
*
*/
public class ContractMarket implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1303462872220110093L;
public static int TYPE_ATBMONTHLY = 0;
//TODO: Implement a method that rolls each day to see whether a new contract appears or an offer disappears
public final static int CLAUSE_COMMAND = 0;
public final static int CLAUSE_SALVAGE = 1;
public final static int CLAUSE_SUPPORT = 2;
public final static int CLAUSE_TRANSPORT = 3;
public final static int CLAUSE_NUM = 4;
private int method = TYPE_ATBMONTHLY;
private ArrayList<Contract> contracts;
private int lastId = 0;
private HashMap<Integer, Contract> contractIds;
private HashMap<Integer, ClauseMods> clauseMods;
/* It is possible to call addFollowup more than once for the
* same contract by canceling the dialog and running it again;
* this is the easiest place to track it to prevent
* multiple followup contracts.
* key: followup id
* value: main contract id
*/
private HashMap<Integer, Integer> followupContracts;
public ContractMarket() {
contracts = new ArrayList<Contract>();
contractIds = new HashMap<Integer, Contract>();
clauseMods = new HashMap<Integer, ClauseMods>();
followupContracts = new HashMap<Integer, Integer>();
}
public ArrayList<Contract> getContracts() {
return contracts;
}
public void removeContract(Contract c) {
contracts.remove(c);
contractIds.remove(c.getId());
clauseMods.remove(c.getId());
followupContracts.remove(c.getId());
}
public AtBContract addAtBContract(Campaign campaign) {
AtBContract c = generateAtBContract(campaign, campaign.getUnitRatingMod());
if (c != null) {
contracts.add(c);
}
return c;
}
public int getRerollsUsed(Contract c, int clause) {
if (null != clauseMods.get(c.getId())) {
return clauseMods.get(c.getId()).rerollsUsed[clause];
}
return 0;
}
public void rerollClause(AtBContract c, int clause, Campaign campaign) {
if (null != clauseMods.get(c.getId())) {
switch (clause) {
case CLAUSE_COMMAND:
rollCommandClause(c, clauseMods.get(c.getId()).mods[clause]);
break;
case CLAUSE_SALVAGE:
rollSalvageClause(c, clauseMods.get(c.getId()).mods[clause]);
break;
case CLAUSE_TRANSPORT:
rollTransportClause(c, clauseMods.get(c.getId()).mods[clause]);
break;
case CLAUSE_SUPPORT:
rollSupportClause(c, clauseMods.get(c.getId()).mods[clause]);
break;
}
clauseMods.get(c.getId()).rerollsUsed[clause]++;
c.calculateContract(campaign);
}
}
public void generateContractOffers(Campaign campaign) {
generateContractOffers(campaign, false);
}
public void generateContractOffers(Campaign campaign, boolean newCampaign) {
if ((method == TYPE_ATBMONTHLY && campaign.getCalendar().get(Calendar.DAY_OF_MONTH) == 1) ||
newCampaign) {
Contract[] list = contracts.toArray(new Contract[contracts.size()]);
for (Contract c : list) {
removeContract(c);
}
int unitRatingMod = campaign.getUnitRatingMod();
for (Mission m : campaign.getMissions()) {
if (m instanceof AtBContract && m.isActive()) {
checkForSubcontracts(campaign, (AtBContract)m,
unitRatingMod);
}
}
int numContracts = Compute.d6() - 4 + unitRatingMod;
DateTime currentDate = Utilities.getDateTimeDay(campaign.getCalendar());
Set<Faction> currentFactions =
campaign.getCurrentPlanet().getFactionSet(currentDate);
boolean inMinorFaction = true;
for (Faction f : currentFactions) {
if (RandomFactionGenerator.getInstance().isMajorPower(f) ||
f.isClan()) {
inMinorFaction = false;
break;
}
}
if (inMinorFaction) {
numContracts--;
}
boolean inBackwater = true;
if( currentFactions.size() > 1 )
{
// More than one faction, if any is *not* periphery, we're not in backwater either
for( Faction f : currentFactions ) {
if( !f.isPeriphery() ) {
inBackwater = false;
}
}
} else {
// Just one faction. Are there any others nearby?
Faction onlyFaction = currentFactions.iterator().next();
if( !onlyFaction.isPeriphery() ) {
for (Planet key : Planets.getInstance().getNearbyPlanets(campaign.getCurrentPlanet(), 30)) {
for (Faction f : key.getFactionSet(currentDate)) {
if( !onlyFaction.equals(f) ) {
inBackwater = false;
break;
}
}
if (!inBackwater) {
break;
}
}
}
}
if (inBackwater) {
numContracts--;
}
if (campaign.getFactionCode().equals("MERC") || campaign.getFactionCode().equals("PIR")) {
if (campaign.getAtBConfig().isHiringHall(campaign.getCurrentPlanet().getId(), campaign.getDate())) {
numContracts++;
/* Though the rules do not state these modifiers are mutually exclusive, the fact that the
* distance of Galatea from a border means that it has no advantage for Mercs over border
* worlds. Common sense dictates that worlds with hiring halls should not be
* subject to the -1 for backwater/interior.
*/
if (inBackwater) {
numContracts++;
}
}
} else {
/* Per IOps Beta, government units determine number of contracts as on a system with a great hall */
numContracts++;
}
/* If located on a faction's capital (interpreted as the starting planet for that faction),
* generate one contract offer for that faction.
*/
for (Faction f : campaign.getCurrentPlanet().getFactionSet(currentDate)) {
try {
if (f.getStartingPlanet(campaign.getEra()).equals(campaign.getCurrentPlanet().getId())
&& RandomFactionGenerator.getInstance().getEmployerSet().contains(f.getShortName())) {
AtBContract c = generateAtBContract(campaign, f.getShortName(), unitRatingMod);
if (c != null) {
contracts.add(c);
break;
}
}
} catch (ArrayIndexOutOfBoundsException e) {
//no starting planet in current era; continue to next faction
}
}
if (newCampaign) {
numContracts = Math.max(numContracts, 2);
}
for (int i = 0; i < numContracts; i++) {
AtBContract c = generateAtBContract(campaign, unitRatingMod);
if (c != null) {
contracts.add(c);
}
}
if (campaign.getCampaignOptions().getContractMarketReportRefresh()) {
campaign.addReport("<a href='CONTRACT_MARKET'>Contract market updated</a>");
}
}
}
private void checkForSubcontracts(Campaign campaign,
AtBContract contract, int unitRatingMod) {
if (contract.getMissionType() == AtBContract.MT_GARRISONDUTY) {
int numSubcontracts = 0;
for (Mission m : campaign.getMissions()) {
if (m instanceof AtBContract &&
((AtBContract)m).getParentContract() == contract) {
numSubcontracts++;
}
}
for (int i = numSubcontracts; i < unitRatingMod - 1; i++) {
int roll = Compute.d6(2);
if (roll >= 10) {
AtBContract sub = generateAtBSubcontract(campaign, contract, unitRatingMod);
if (sub.getEndingDate().before(contract.getEndingDate())) {
contracts.add(sub);
}
}
}
}
}
/* If no suitable planet can be found or no jump path to the planet can be calculated after
* the indicated number of retries, this will return null.
*/
private AtBContract generateAtBContract(Campaign campaign, int unitRatingMod) {
if (campaign.getFactionCode().equals("MERC")) {
if (null == campaign.getRetainerEmployerCode()) {
int retries = 3;
AtBContract retVal = null;
while (retries > 0 && retVal == null) {
retVal = generateAtBContract(campaign,
RandomFactionGenerator.getInstance().getEmployer(),
unitRatingMod, 0);
retries--;
}
return retVal;
} else {
return generateAtBContract(campaign,
campaign.getRetainerEmployerCode(), unitRatingMod, 3);
}
} else {
return generateAtBContract(campaign,
campaign.getFactionCode(), unitRatingMod, 3);
}
}
private AtBContract generateAtBContract(Campaign campaign,
String employer, int unitRatingMod) {
return generateAtBContract(campaign, employer, unitRatingMod, 3);
}
private AtBContract generateAtBContract(Campaign campaign,
String employer, int unitRatingMod, int retries) {
AtBContract contract = new AtBContract("New Contract");
lastId++;
contract.setId(lastId);
contractIds.put(lastId, contract);
if (employer.equals("MERC")) {
contract.setMercSubcontract(true);
while (employer.equals("MERC")) {
employer = RandomFactionGenerator.getInstance().getEmployer();
}
}
contract.setEmployerCode(employer, campaign.getEra());
contract.setMissionType(findAtBMissionType(unitRatingMod,
RandomFactionGenerator.getInstance().isISMajorPower(contract.getEmployerCode())));
if (contract.getMissionType() == AtBContract.MT_PIRATEHUNTING)
contract.setEnemyCode("PIR");
else if (contract.getMissionType() == AtBContract.MT_RIOTDUTY)
contract.setEnemyCode("REB");
else {
boolean rebsAllowed = contract.getMissionType() <= AtBContract.MT_RIOTDUTY;
contract.setEnemyCode(RandomFactionGenerator.getInstance().getEnemy(contract.getEmployerCode(), rebsAllowed));
}
if (contract.getMissionType() == AtBContract.MT_GARRISONDUTY && contract.getEnemyCode().equals("REB")) {
contract.setMissionType(AtBContract.MT_RIOTDUTY);
}
/* Addition to AtB rules: factions which are generally neutral
* (ComStar, Mercs not under contract) are more likely to have garrison-type
* contracts and less likely to have battle-type contracts unless at war.
*/
if (RandomFactionGenerator.getInstance().isNeutral(employer) &&
!RandomFactionGenerator.getInstance().isAtWarWith(employer,
contract.getEnemyCode(), campaign.getDate())) {
if (contract.getMissionType() == AtBContract.MT_PLANETARYASSAULT) {
contract.setMissionType(AtBContract.MT_GARRISONDUTY);
} else if (contract.getMissionType() == AtBContract.MT_RELIEFDUTY) {
contract.setMissionType(AtBContract.MT_SECURITYDUTY);
}
}
boolean isAttacker = (contract.getMissionType() == AtBContract.MT_PLANETARYASSAULT ||
contract.getMissionType() >= AtBContract.MT_PLANETARYASSAULT ||
(contract.getMissionType() == AtBContract.MT_RELIEFDUTY && Compute.d6() < 4) ||
contract.getEnemyCode().equals("REB"));
if (isAttacker) {
contract.setPlanetName(RandomFactionGenerator.getInstance().getMissionTarget(contract.getEmployerCode(), contract.getEnemyCode(), campaign.getDate()));
} else {
contract.setPlanetName(RandomFactionGenerator.getInstance().getMissionTarget(contract.getEnemyCode(), contract.getEmployerCode(), campaign.getDate()));
}
if (contract.getPlanetName() == null) {
MekHQ.logError("Could not find contract location for " +
contract.getEmployerCode() + " vs. " + contract.getEnemyCode());
if (retries > 0) {
return generateAtBContract(campaign, employer, unitRatingMod, retries - 1);
} else {
return null;
}
}
JumpPath jp = null;
try {
jp = campaign.calculateJumpPath(campaign.getCurrentPlanet(), contract.getPlanet());
} catch (NullPointerException ex) {
// could not calculate jump path; leave jp null
}
if (jp == null) {
if (retries > 0) {
return generateAtBContract(campaign, employer, unitRatingMod, retries - 1);
} else {
return null;
}
}
setAllyRating(contract, isAttacker, campaign.getCalendar().get(Calendar.YEAR));
setEnemyRating(contract, isAttacker, campaign.getCalendar().get(Calendar.YEAR));
if (contract.getMissionType() == AtBContract.MT_CADREDUTY) {
contract.setAllySkill(RandomSkillsGenerator.L_GREEN);
contract.setAllyQuality(IUnitRating.DRAGOON_F);
}
contract.calculateLength(campaign.getCampaignOptions().getVariableContractLength());
setAtBContractClauses(contract, unitRatingMod, campaign);
contract.calculatePaymentMultiplier(campaign);
contract.calculatePartsAvailabilityLevel(campaign);
contract.initContractDetails(campaign);
contract.calculateContract(campaign);
return contract;
}
protected AtBContract generateAtBSubcontract(Campaign campaign,
AtBContract parent, int unitRatingMod) {
AtBContract contract = new AtBContract("New Subcontract");
contract.setEmployerCode(parent.getEmployerCode(), campaign.getEra());
contract.setMissionType(findAtBMissionType(unitRatingMod,
RandomFactionGenerator.getInstance().isISMajorPower(contract.getEmployerCode())));
if (contract.getMissionType() == AtBContract.MT_PIRATEHUNTING)
contract.setEnemyCode("PIR");
else if (contract.getMissionType() == AtBContract.MT_RIOTDUTY)
contract.setEnemyCode("REB");
else {
boolean rebsAllowed = contract.getMissionType() <= AtBContract.MT_RIOTDUTY;
contract.setEnemyCode(RandomFactionGenerator.getInstance().getEnemy(contract.getEmployerCode(), rebsAllowed));
}
if (contract.getMissionType() == AtBContract.MT_GARRISONDUTY && contract.getEnemyCode().equals("REB")) {
contract.setMissionType(AtBContract.MT_RIOTDUTY);
}
contract.setParentContract(parent);
contract.initContractDetails(campaign);
lastId++;
contract.setId(lastId);
contractIds.put(lastId, contract);
/* The AtB rules say to roll the enemy, but also that the subcontract
* takes place in the same planet/sector. Rebels and pirates can
* appear anywhere, but others should be limited to what's within a
* jump. */
/*TODO: When MekHQ gets the capability of splitting the unit to
* different locations, this restriction can be lessened or lifted.
*/
if (!contract.getEnemyCode().equals("REB") &&
!contract.getEnemyCode().equals("PIR")) {
boolean factionValid = false;
for (Planet p : Planets.getInstance().getNearbyPlanets(campaign.getCurrentPlanet(), 30)) {
if (factionValid) break;
for (Faction f : p.getFactionSet(Utilities.getDateTimeDay(campaign.getCalendar()))) {
if (f.getShortName().equals(contract.getEnemyCode())) {
factionValid = true;
break;
}
}
}
if (!factionValid) {
contract.setEnemyCode(parent.getEnemyCode());
}
}
boolean isAttacker = (contract.getMissionType() == AtBContract.MT_PLANETARYASSAULT ||
contract.getMissionType() >= AtBContract.MT_PLANETARYASSAULT ||
(contract.getMissionType() == AtBContract.MT_RELIEFDUTY && Compute.d6() < 4) ||
contract.getEnemyCode().equals("REB"));
contract.setPlanetName(parent.getPlanetName());
setAllyRating(contract, isAttacker, campaign.getCalendar().get(Calendar.YEAR));
setEnemyRating(contract, isAttacker, campaign.getCalendar().get(Calendar.YEAR));
if (contract.getMissionType() == AtBContract.MT_CADREDUTY) {
contract.setAllySkill(RandomSkillsGenerator.L_GREEN);
contract.setAllyQuality(IUnitRating.DRAGOON_F);
}
contract.calculateLength(campaign.getCampaignOptions().getVariableContractLength());
contract.setCommandRights(Math.max(parent.getCommandRights() - 1,
Contract.COM_INTEGRATED));
contract.setSalvageExchange(parent.isSalvageExchange());
contract.setSalvagePct(Math.max(parent.getSalvagePct() - 10, 0));
contract.setStraightSupport(Math.max(parent.getStraightSupport() - 20,
0));
if (parent.getBattleLossComp() <= 10) {
contract.setBattleLossComp(0);
} else if (parent.getBattleLossComp() <= 20) {
contract.setBattleLossComp(10);
} else {
contract.setBattleLossComp(parent.getBattleLossComp() - 20);
}
contract.setTransportComp(100);
contract.calculatePaymentMultiplier(campaign);
contract.calculatePartsAvailabilityLevel(campaign);
contract.calculateContract(campaign);
return contract;
}
public void addFollowup(Campaign campaign,
AtBContract contract) {
if (followupContracts.values().contains(contract.getId())) {
return;
}
AtBContract followup = new AtBContract("Followup Contract");
followup.setEmployerCode(contract.getEmployerCode(), campaign.getEra());
followup.setEnemyCode(contract.getEnemyCode());
followup.setPlanetName(contract.getPlanetName());
switch (contract.getMissionType()) {
case AtBContract.MT_DIVERSIONARYRAID:
followup.setMissionType(AtBContract.MT_OBJECTIVERAID);
break;
case AtBContract.MT_RECONRAID:
followup.setMissionType(AtBContract.MT_PLANETARYASSAULT);
break;
case AtBContract.MT_RIOTDUTY:
followup.setMissionType(AtBContract.MT_GARRISONDUTY);
break;
}
followup.setAllySkill(contract.getAllySkill());
followup.setAllyQuality(contract.getAllyQuality());
followup.setEnemySkill(contract.getEnemySkill());
followup.setEnemyQuality(contract.getEnemyQuality());
followup.calculateLength(campaign.getCampaignOptions().getVariableContractLength());
setAtBContractClauses(followup, campaign.getUnitRatingMod(), campaign);
followup.calculatePaymentMultiplier(campaign);
followup.calculatePartsAvailabilityLevel(campaign);
followup.initContractDetails(campaign);
followup.calculateContract(campaign);
lastId++;
followup.setId(lastId);
contractIds.put(lastId, followup);
contracts.add(followup);
followupContracts.put(followup.getId(), contract.getId());
}
protected int findAtBMissionType(int unitRatingMod, boolean majorPower) {
final int[][] table = {
//col 0: IS Houses
{AtBContract.MT_GUERRILLAWARFARE, AtBContract.MT_RECONRAID, AtBContract.MT_PIRATEHUNTING,
AtBContract.MT_PLANETARYASSAULT, AtBContract.MT_OBJECTIVERAID, AtBContract.MT_OBJECTIVERAID,
AtBContract.MT_EXTRACTIONRAID, AtBContract.MT_RECONRAID, AtBContract.MT_GARRISONDUTY,
AtBContract.MT_CADREDUTY, AtBContract.MT_RELIEFDUTY},
//col 1: Others
{AtBContract.MT_GUERRILLAWARFARE, AtBContract.MT_RECONRAID, AtBContract.MT_PLANETARYASSAULT,
AtBContract.MT_OBJECTIVERAID, AtBContract.MT_EXTRACTIONRAID, AtBContract.MT_PIRATEHUNTING,
AtBContract.MT_SECURITYDUTY, AtBContract.MT_OBJECTIVERAID, AtBContract.MT_GARRISONDUTY,
AtBContract.MT_CADREDUTY, AtBContract.MT_DIVERSIONARYRAID}
};
int roll = Compute.d6(2) + unitRatingMod - IUnitRating.DRAGOON_C;
if (roll > 12) {
roll = 12;
}
if (roll < 2) {
roll = 2;
}
return table[majorPower?0:1][roll - 2];
}
public void setAllyRating(AtBContract contract, boolean isAttacker, int year) {
int mod = 0;
if (contract.getEnemyCode().equals("REB") ||
contract.getEnemyCode().equals("PIR")) {
mod -= 1;
}
if (contract.getMissionType() == AtBContract.MT_GUERRILLAWARFARE ||
contract.getMissionType() == AtBContract.MT_CADREDUTY) {
mod -= 3;
}
if (contract.getMissionType() == AtBContract.MT_GARRISONDUTY ||
contract.getMissionType() == AtBContract.MT_SECURITYDUTY) {
mod -= 2;
}
if (AtBContract.isMinorPower(contract.getEmployerCode())) {
mod -= 1;
}
if (contract.getEnemyCode().equals("IND") ||
contract.getEnemyCode().equals("PIND")) {
mod -= 2;
}
if (contract.getMissionType() == AtBContract.MT_PLANETARYASSAULT) {
mod += 1;
}
if (Faction.getFaction(contract.getEmployerCode()).isClan() && !isAttacker) {
//facing front-line units
mod += 1;
}
contract.setAllySkill(getSkillRating(Compute.d6(2) + mod));
if (year > 2950 && year < 3039 &&
!Faction.getFaction(contract.getEmployerCode()).isClan()) {
mod -= 1;
}
contract.setAllyQuality(getQualityRating(Compute.d6(2) + mod));
}
public void setEnemyRating(AtBContract contract, boolean isAttacker, int year) {
int mod = 0;
if (contract.getEnemyCode().equals("REB") ||
contract.getEnemyCode().equals("PIR")) {
mod -= 2;
}
if (contract.getMissionType() == AtBContract.MT_GUERRILLAWARFARE) {
mod += 2;
}
if (contract.getMissionType() == AtBContract.MT_PLANETARYASSAULT) {
mod += 1;
}
if (AtBContract.isMinorPower(contract.getEmployerCode())) {
mod -= 1;
}
if (Faction.getFaction(contract.getEmployerCode()).isClan()) {
mod += isAttacker?2:4;
}
contract.setEnemySkill(getSkillRating(Compute.d6(2) + mod));
if (year > 2950 && year < 3039 &&
!Faction.getFaction(contract.getEnemyCode()).isClan()) {
mod -= 1;
}
contract.setEnemyQuality(getQualityRating(Compute.d6(2) + mod));
}
protected int getSkillRating(int roll) {
if (roll <= 5) return RandomSkillsGenerator.L_GREEN;
if (roll <= 9) return RandomSkillsGenerator.L_REG;
if (roll <= 11) return RandomSkillsGenerator.L_VET;
return RandomSkillsGenerator.L_ELITE;
}
protected int getQualityRating(int roll) {
if (roll <= 5) return IUnitRating.DRAGOON_F;
if (roll <= 8) return IUnitRating.DRAGOON_D;
if (roll <= 10) return IUnitRating.DRAGOON_C;
if (roll == 11) return IUnitRating.DRAGOON_B;
return IUnitRating.DRAGOON_A;
}
protected void setAtBContractClauses(AtBContract contract, int unitRatingMod, Campaign campaign) {
ClauseMods mods = new ClauseMods();
clauseMods.put(contract.getId(), mods);
/* AtB rules seem to indicate one admin in each role (though this
* is not explicitly stated that I have seen) but MekHQ allows
* assignment of multiple admins to each role. Therefore we go
* through all the admins and for each role select the one with
* the highest admin skill, or higher negotiation if the admin
* skills are equal.
*/
Person adminCommand = campaign.findBestInRole(Person.T_ADMIN_COM, SkillType.S_ADMIN, SkillType.S_NEG);
Person adminTransport = campaign.findBestInRole(Person.T_ADMIN_TRA, SkillType.S_ADMIN, SkillType.S_NEG);
Person adminLogistics = campaign.findBestInRole(Person.T_ADMIN_LOG, SkillType.S_ADMIN, SkillType.S_NEG);
int adminCommandExp = (adminCommand == null)?SkillType.EXP_ULTRA_GREEN:adminCommand.getSkill(SkillType.S_ADMIN).getExperienceLevel();
int adminTransportExp = (adminTransport == null)?SkillType.EXP_ULTRA_GREEN:adminTransport.getSkill(SkillType.S_ADMIN).getExperienceLevel();
int adminLogisticsExp = (adminLogistics == null)?SkillType.EXP_ULTRA_GREEN:adminLogistics.getSkill(SkillType.S_ADMIN).getExperienceLevel();
/* Treat government units like merc units that have a retainer contract */
if ((!campaign.getFactionCode().equals("MERC") &&
!campaign.getFactionCode().equals("PIR")) ||
null != campaign.getRetainerEmployerCode()) {
for (int i = 0; i < CLAUSE_NUM; i++) {
mods.mods[i]++;
}
}
if (campaign.getCampaignOptions().isMercSizeLimited() &&
campaign.getFactionCode().equals("MERC")) {
int max = (unitRatingMod + 1) * 12;
int numMods = (AtBContract.getEffectiveNumUnits(campaign) - max) / 2;
while (numMods > 0) {
mods.mods[Compute.randomInt(4)]--;
numMods--;
}
}
mods.mods[CLAUSE_COMMAND] = adminCommandExp - SkillType.EXP_REGULAR;
mods.mods[CLAUSE_SALVAGE] = 0;
mods.mods[CLAUSE_TRANSPORT] = adminTransportExp - SkillType.EXP_REGULAR;
mods.mods[CLAUSE_SUPPORT] = adminLogisticsExp - SkillType.EXP_REGULAR;
if (unitRatingMod >= IUnitRating.DRAGOON_A) {
mods.mods[Compute.randomInt(4)] += 2;
mods.mods[Compute.randomInt(4)] += 2;
} else if (unitRatingMod == IUnitRating.DRAGOON_B) {
mods.mods[Compute.randomInt(4)] += 1;
mods.mods[Compute.randomInt(4)] += 1;
} else if (unitRatingMod == IUnitRating.DRAGOON_C) {
mods.mods[Compute.randomInt(4)] += 1;
} else if (unitRatingMod <= IUnitRating.DRAGOON_F) {
mods.mods[Compute.randomInt(4)] -= 1;
}
if (Faction.getFaction(contract.getEnemyCode()).isClan() &&
!Faction.getFaction(contract.getEmployerCode()).isClan()) {
for (int i = 0; i < 4; i++)
if (i == CLAUSE_SALVAGE) mods.mods[i] -= 2;
else mods.mods[i] += 1;
} else {
if (contract.getEnemySkill() >= SkillType.EXP_VETERAN)
mods.mods[Compute.randomInt(4)] += 1;
if (contract.getEnemySkill() == SkillType.EXP_ELITE)
mods.mods[Compute.randomInt(4)] += 1;
}
int[][] missionMods = {
{1, 0, 1, 0}, {0, 1, -1, -3}, {-3, 0, 2, 1}, {-2, 1, -1, -1},
{-2, 0, 2, 3}, {-1, 1, 1, 1}, {-2, 3, -2, -1}, {2, 2, -1, -1},
{0, 2, 2, 1}, {-1, 0, 1, 2}, {-1, -2, 1, -1}, {-1, -1, 2, 1}
};
for (int i = 0; i < 4; i++) {
mods.mods[i] += missionMods[contract.getMissionType()][i];
}
if (RandomFactionGenerator.getInstance().isISMajorPower(contract.getEmployerCode())) {
mods.mods[CLAUSE_SALVAGE] += -1;
mods.mods[CLAUSE_TRANSPORT] += 1;
}
if (AtBContract.isMinorPower(contract.getEmployerCode())) {
mods.mods[CLAUSE_SALVAGE] += -2;
}
if (contract.getEmployerCode().equals("MERC")) {
mods.mods[CLAUSE_COMMAND] += -1;
mods.mods[CLAUSE_SALVAGE] += 2;
mods.mods[CLAUSE_SUPPORT] += 1;
mods.mods[CLAUSE_TRANSPORT] += 1;
}
if (contract.getEmployerCode().equals("IND")) {
mods.mods[CLAUSE_COMMAND] += 0;
mods.mods[CLAUSE_SALVAGE] += -1;
mods.mods[CLAUSE_SUPPORT] += -1;
mods.mods[CLAUSE_TRANSPORT] += 0;
}
if (campaign.getFactionCode().equals("MERC")) {
rollCommandClause(contract, mods.mods[CLAUSE_COMMAND]);
} else {
contract.setCommandRights(Contract.COM_INTEGRATED);
}
rollSalvageClause(contract, mods.mods[CLAUSE_SALVAGE]);
rollSupportClause(contract, mods.mods[CLAUSE_SUPPORT]);
rollTransportClause(contract, mods.mods[CLAUSE_TRANSPORT]);
}
private void rollCommandClause(AtBContract contract, int mod) {
int roll = Compute.d6(2) + mod;
if (roll < 3) contract.setCommandRights(Contract.COM_INTEGRATED);
else if (roll < 8) contract.setCommandRights(Contract.COM_HOUSE);
else if (roll < 12) contract.setCommandRights(Contract.COM_LIAISON);
else contract.setCommandRights(Contract.COM_INDEP);
}
private void rollSalvageClause(AtBContract contract, int mod) {
contract.setSalvageExchange(false);
int roll = Math.min(Compute.d6(2) + mod, 13);
if (roll < 2) {
contract.setSalvagePct(0);
} else if (roll < 4) {
contract.setSalvageExchange(true);
int r;
do {
r = Compute.d6(2);
} while (r < 4);
contract.setSalvagePct(Math.min((r - 3) * 10, 100));
} else {
contract.setSalvagePct(Math.min((roll - 3) * 10, 100));
}
}
private void rollSupportClause(AtBContract contract, int mod) {
int roll = Compute.d6(2) + mod;
contract.setStraightSupport(0);
contract.setBattleLossComp(0);
if (roll < 3) {
contract.setStraightSupport(0);
} else if (roll < 8) {
contract.setStraightSupport((roll - 2) * 20);
} else if (roll == 8) {
contract.setBattleLossComp(10);
} else {
contract.setBattleLossComp(Math.min((roll - 8) * 20, 100));
}
}
private void rollTransportClause(AtBContract contract, int mod) {
int roll = Compute.d6(2) + mod;
if (roll < 2) contract.setTransportComp(0);
else if (roll < 6) contract.setTransportComp((20 + (roll - 2) * 5));
else if (roll < 10) contract.setTransportComp((45 + (roll - 6) * 5));
else contract.setTransportComp(100);
}
public void writeToXml(PrintWriter pw1, int indent) {
pw1.println(MekHqXmlUtil.indentStr(indent) + "<contractMarket>");
MekHqXmlUtil.writeSimpleXmlTag(pw1, indent+1, "lastId", lastId);
for (Contract c : contracts) {
c.writeToXml(pw1, indent + 1);
}
for (Integer key : clauseMods.keySet()) {
if (!contractIds.containsKey(key)) {
continue;
}
pw1.println(MekHqXmlUtil.indentStr(indent+1)
+ "<clauseMods id=\"" + key + "\">");
String rerolls = "";
String mods = "";
for (int i = 0; i < CLAUSE_NUM; i++) {
rerolls += clauseMods.get(key).rerollsUsed[i] + ((i < CLAUSE_NUM - 1)?",":"");
mods += clauseMods.get(key).mods[i] + ((i < CLAUSE_NUM - 1)?",":"");
}
MekHqXmlUtil.writeSimpleXmlTag(pw1, indent+2, "mods", mods);
MekHqXmlUtil.writeSimpleXmlTag(pw1, indent+2, "rerollsUsed", rerolls);
pw1.println(MekHqXmlUtil.indentStr(indent+1) + "</clauseMods>");
}
pw1.println(MekHqXmlUtil.indentStr(indent) + "</contractMarket>");
}
public static ContractMarket generateInstanceFromXML(Node wn, Campaign c, Version version) {
ContractMarket retVal = null;
try {
// Instantiate the correct child class, and call its parsing function.
retVal = new ContractMarket();
// Okay, now load Part-specific fields!
NodeList nl = wn.getChildNodes();
// Loop through the nodes and load our contract offers
for (int x = 0; x < nl.getLength(); x++) {
Node wn2 = nl.item(x);
// If it's not an element node, we ignore it.
if (wn2.getNodeType() != Node.ELEMENT_NODE) {
continue;
}
if (wn2.getNodeName().equalsIgnoreCase("lastId")) {
retVal.lastId = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("mission")) {
Mission m = Mission.generateInstanceFromXML(wn2, c, version);
if (m != null && m instanceof Contract) {
retVal.contracts.add((Contract)m);
retVal.contractIds.put(m.getId(), (Contract)m);
}
} else if (wn2.getNodeName().equalsIgnoreCase("clauseMods")) {
int key = Integer.parseInt(wn2.getAttributes().getNamedItem("id").getTextContent());
ClauseMods cm = retVal.new ClauseMods();
NodeList nl2 = wn2.getChildNodes();
for (int i = 0; i < nl2.getLength(); i++) {
Node wn3 = nl2.item(i);
if (wn3.getNodeName().equalsIgnoreCase("mods")) {
String [] s = wn3.getTextContent().split(",");
for (int j = 0; j < s.length; j++) {
cm.mods[j] = Integer.parseInt(s[j]);
}
} else if (wn3.getNodeName().equalsIgnoreCase("rerollsUsed")) {
String [] s = wn3.getTextContent().split(",");
for (int j = 0; j < s.length; j++) {
cm.rerollsUsed[j] = Integer.parseInt(s[j]);
}
}
}
retVal.clauseMods.put(key, cm);
}
}
} catch (Exception ex) {
// Errrr, apparently either the class name was invalid...
// Or the listed name doesn't exist.
// Doh!
MekHQ.logError(ex);
}
return retVal;
}
/* Keep track of how many rerolls remain for each contract clause
* based on the admin's negotiation skill. Also track bonuses, as
* the random clause bonuses should be persistent.
*/
public class ClauseMods {
public int[] rerollsUsed = {0, 0, 0, 0};
public int[] mods = {0, 0, 0, 0};
}
}