/* * FieldManualMercRevMrbcRating.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.rating; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.Map; import megamek.common.Aero; import megamek.common.BattleArmor; import megamek.common.ConvFighter; import megamek.common.Crew; import megamek.common.Dropship; import megamek.common.Entity; import megamek.common.Infantry; import megamek.common.Jumpship; import megamek.common.Mech; import megamek.common.Protomech; import megamek.common.SmallCraft; import megamek.common.Tank; import megamek.common.TechConstants; import megamek.common.UnitType; import megamek.common.VTOL; import megamek.common.Warship; import mekhq.campaign.Campaign; import mekhq.campaign.personnel.Person; import mekhq.campaign.personnel.Skill; import mekhq.campaign.personnel.SkillType; import mekhq.campaign.unit.Unit; /** * @author Deric Page (deric (dot) page (at) usa.net) * @version %Id% * @since 3/12/2012 */ public class FieldManualMercRevDragoonsRating extends AbstractUnitRating { private BigDecimal highTechPercent = BigDecimal.ZERO; private BigDecimal numberIS2 = BigDecimal.ZERO; private BigDecimal numberClan = BigDecimal.ZERO; private int countIS2 = 0; private int countClan = 0; private int mechSupportNeeded = 0; private int tankSupportNeeded = 0; private int vtolSupportNeeded = 0; private int baSupportNeeded = 0; private int convFighterSupportNeeded = 0; private int aeroFighterSupportNeeded = 0; private int smallCraftSupportNeeded = 0; private int medSupportNeeded = 0; private int adminSupportNeeded = 0; private int dropJumpShipSupportNeeded = 0; private int techSupportHours = 0; private int medSupportHours = 0; private int adminSupportHours; public FieldManualMercRevDragoonsRating(Campaign campaign) { super(campaign); } @Override protected void initValues() { if (isInitialized()) { return; } logMessage("Initializing Dragoons rating."); super.initValues(); setHighTechPercent(BigDecimal.ZERO); setNumberIS2(BigDecimal.ZERO); setNumberClan(BigDecimal.ZERO); setCountClan(0); setCountIS2(0); for (Unit u : getCampaign().getCopyOfUnits()) { if (null == u) { continue; } logMessage("Processing unit " + u.getName()); if (u.isMothballed()) { logMessage("Unit " + u.getName() + " is mothballed. Skipping."); continue; } updateUnitCounts(u); BigDecimal value = getUnitValue(u); logMessage("Unit " + u.getName() + " -- Value = " + value.toPlainString()); setNumberUnits(getNumberUnits().add(value)); Person p = u.getCommander(); if (null != p) { logMessage("Unit " + u.getName() + " -- Adding commander (" + p.getName() + ") to commander list."); getCommanderList().add(p); } updateAdvanceTechCount(u, value); updateSkillLevel(u, value); updateBayCount(u.getEntity()); updateJumpships(u.getEntity()); updateTechSupportNeeds(u); } updateAvailableSupport(); calcMedicalSupportHoursNeeded(); calcAdminSupportHoursNeeded(); } void updateAvailableSupport() { logMessage("Updating available support."); for (Person p : getCampaign().getPersonnel()) { logMessage("Checking " + p.getName()); if (!p.isActive()) { logMessage("Person, " + p.getName() + ", is not active."); continue; } if (p.isTech()) { updateTechSupportHours(p); } else if (p.isDoctor()) { updateMedicalSupportHours(p); } else if (p.isAdmin()) { updateAdministrativeSupportHours(p); } } } private void updateJumpships(Entity en) { if (en instanceof Warship) { if (en.getDocks() > 0) { setWarhipWithDocsOwner(true); } else { setWarshipOwner(true); } } else if (en instanceof Jumpship) { setJumpshipOwner(true); } } private void updateTechSupportNeeds(Unit u) { if (u.isMothballed()) { return; } Entity en = u.getEntity(); if (null == en) { return; } logMessage("Unit " + u.getName() + " updating tech support needs."); double timeMult = 1; int needed = 0; if (getCampaign().getCampaignOptions().useQuirks()) { if (en.hasQuirk("easy_maintain")) { logMessage("Unit " + u.getName() + " is easy to maintain."); timeMult = 0.8; } else if (en.hasQuirk("difficult_maintain")) { logMessage("Unit " + u.getName() + " is difficult to maintain."); timeMult = 1.2; } } if (en instanceof Mech) { needed += (Math.floor(en.getWeight() / 5) + 40) * timeMult; logMessage("Unit " + u.getName() + " needs " + needed + " mech tech hours."); mechSupportNeeded += needed; } else if (en instanceof Warship || en instanceof Jumpship || en instanceof Dropship) { // according to FMMR, this should be tracked separately because it only applies to admin support but not // technical support. updateDropJumpShipSupportNeeds(en); } else if ((en instanceof SmallCraft)) { if (en.getWeight() >= 50000) { needed += (Math.floor(en.getWeight() / 50) + 20) * timeMult; } else if (en.getWeight() >= 16000) { needed += (Math.floor(en.getWeight() / 25) + 40) * timeMult; } else { needed += (Math.floor(en.getWeight() / 10) + 80) * timeMult; } logMessage("Unit " + u.getName() + " needs " + needed + " small craft tech hours."); smallCraftSupportNeeded += needed; } else if (en instanceof ConvFighter) { needed += (Math.floor(en.getWeight() / 2.5) + 20) * timeMult; logMessage("Unit " + u.getName() + " needs " + needed + " conv. fighter tech hours."); convFighterSupportNeeded += needed; } else if (en instanceof Aero) { needed += (Math.floor(en.getWeight() / 2.5) + 40) * timeMult; logMessage("Unit " + u.getName() + " needs " + needed + " aero tech hours."); aeroFighterSupportNeeded += needed; } else if (en instanceof VTOL) { needed += (Math.floor(en.getWeight() / 5) + 30) * timeMult; logMessage("Unit " + u.getName() + " needs " + needed + " VTOL tech hours."); vtolSupportNeeded += needed; } else if (en instanceof Tank) { needed += (Math.floor(en.getWeight() / 5) + 20) * timeMult; logMessage("Unit " + u.getName() + " needs " + needed + " tank tech hours."); tankSupportNeeded += needed; } else if (en instanceof BattleArmor) { needed += ((en.getTotalArmor() * 2) + 5) * timeMult; logMessage("Unit " + u.getName() + " needs " + needed + " BA tech hours."); baSupportNeeded += needed; } // todo Decide if this should be an additional campaign option or if this is implicit in having Faction & // todo Era mods turned on for the campiagn in the first place. // if (campaign.getCampaignOptions().useFactionModifiers() && en.isClan()) { // hoursNeeded *= 2; // } else if (campaign.getCampaignOptions().useEraMods() && (en.getTechLevel() > TechConstants.T_INTRO_BOXSET)) { // hoursNeeded *= 1.5; // } } private void updateDropJumpShipSupportNeeds(Entity en) { double hours = 0; if (en instanceof Warship) { hours += 5000; } else if (en instanceof Jumpship) { hours += 800; } else if (en instanceof Dropship) { if (en.getWeight() >= 50000) { hours = Math.floor(en.getWeight() / 50) + 20; } else if (en.getWeight() >= 16000) { hours = Math.floor(en.getWeight() / 25) + 40; } else { hours = Math.floor(en.getWeight() / 10) + 80; } } if (getCampaign().getCampaignOptions().useQuirks()) { if (en.hasQuirk("easy_maintain")) { hours -= hours * 0.2; } else if (en.hasQuirk("difficult_maintain")) { hours += hours * 0.2; } } logMessage("Unit " + en.getId() + " needs " + hours + " ship tech hours."); dropJumpShipSupportNeeded += hours; } // The wording on this in FM:Mr is rather confusing. Near as I can parse it out, you divide your total personnel // into 7-man "squads". These each require 4 hours of medical support (3 + (7/5) = 3 + 1.4 = 4.4 rounds to 4). // The left over personnel form a new "squad" which requires 3 hours + (# left over / 5). So, if you have 25 // personnel that would be: // 25 / 7 = 3 squads of 7 and 1 squad of 4. // 3 * (3 + 7/5) = 3 * (3 + 1.4) = 3 * 4 = 12 hours // 3 + (4/5) = 3 + 0.8 = 3.8 = 4 hours. // total = 16 hours. private void calcMedicalSupportHoursNeeded() { int numSquads = new BigDecimal(getCampaign().getPersonnel().size()) .divide(new BigDecimal(7), 0, RoundingMode.DOWN).intValue(); int leftOver = getCampaign().getPersonnel().size() - (numSquads * 7); medSupportNeeded = (numSquads * 4) + (3 + (new BigDecimal(leftOver).divide(new BigDecimal(5), 0, RoundingMode.HALF_EVEN).intValue())); } private int getMedicalSupportHoursNeeded() { return medSupportNeeded; } private void calcAdminSupportHoursNeeded() { int personnelCount = 0; for (Person p : getCampaign().getPersonnel()) { if ((p.getPrimaryRole() == Person.T_ADMIN_TRA) || (p.getPrimaryRole() == Person.T_ADMIN_COM) || (p.getPrimaryRole() == Person.T_ADMIN_LOG) || (p.getPrimaryRole() == Person.T_ADMIN_HR) || (p.getSecondaryRole() == Person.T_ADMIN_HR) || (p.getSecondaryRole() == Person.T_ADMIN_TRA) || (p.getSecondaryRole() == Person.T_ADMIN_COM) || (p.getSecondaryRole() == Person.T_ADMIN_LOG)) { continue; } personnelCount++; } int totalSupport = personnelCount + getTechSupportNeeded() + dropJumpShipSupportNeeded; adminSupportNeeded = new BigDecimal(totalSupport).divide(new BigDecimal(30), 0, RoundingMode.HALF_EVEN).intValue(); } private static int getSupportHours(int skillLevel) { switch (skillLevel) { case (SkillType.EXP_ULTRA_GREEN): return 20; case (SkillType.EXP_GREEN): return 30; case (SkillType.EXP_REGULAR): return 40; case (SkillType.EXP_VETERAN): return 45; default: return 50; } } private void updateTechSupportHours(Person p) { String[] techSkills = new String[]{SkillType.S_TECH_MECH, SkillType.S_TECH_AERO, SkillType.S_TECH_BA, SkillType.S_TECH_VESSEL, SkillType.S_TECH_MECHANIC}; // Get the highest tech skill this person has. int highestSkill = SkillType.EXP_ULTRA_GREEN; for (String s : techSkills) { if (p.hasSkill(s)) { int rank = p.getSkill(s).getExperienceLevel(); if (rank > highestSkill) { highestSkill = rank; } } } // Get the number of support hours this person contributes. int hours = getSupportHours(highestSkill); if (p.isTechSecondary()) { hours = (int) Math.floor(hours / 2.0); } logMessage("Person, " + p.getName() + ", provides " + hours + " tech support hours."); techSupportHours += hours; } private void updateMedicalSupportHours(Person p) { Skill doctorSkill = p.getSkill(SkillType.S_DOCTOR); if (doctorSkill == null) { return; } int hours = getSupportHours(doctorSkill.getExperienceLevel()); if (p.getSecondaryRole() == Person.T_DOCTOR) { hours = (int) Math.floor(hours / 2.0); } logMessage("Person, " + p.getName() + " provides " + hours + " medical support hours."); medSupportHours += hours; } private void updateAdministrativeSupportHours(Person p) { Skill adminSkill = p.getSkill(SkillType.S_ADMIN); if (adminSkill == null) { return; } int hours = getSupportHours(adminSkill.getExperienceLevel()); if (p.isAdminSecondary()) { hours = (int) Math.floor(hours / 2.0); } logMessage("Person, " + p.getName() + ", provides " + hours + " admin support hours."); adminSupportHours += hours; } private void updateSkillLevel(Unit u, BigDecimal value) { //Make sure this is a combat unit. if ((null == u.getEntity()) || (null == u.getEntity().getCrew())) { return; } logMessage("Unit " + u.getName() + " updating unit skill rating."); //Calculate the unit's average combat skill. Crew p = u.getEntity().getCrew(); BigDecimal combatSkillAverage; //Infantry and Protos do not have a piloting skill. if ((u.getEntity() instanceof Infantry) || (u.getEntity() instanceof Protomech)) { combatSkillAverage = new BigDecimal(p.getGunnery()); //All other units use an average of piloting and gunnery. } else { combatSkillAverage = BigDecimal.valueOf(p.getGunnery() + p.getPiloting()).divide(BigDecimal.valueOf(2), PRECISION, HALF_EVEN); } String experience = getExperienceLevelName(combatSkillAverage); logMessage("Unit " + u.getName() + " combat skill average = " + combatSkillAverage.toPlainString() + "(" + experience + ")"); //Add to the running total. setTotalSkillLevels(getTotalSkillLevels().add(value.multiply(combatSkillAverage))); incrementSkillRatingCounts(experience); } @Override protected int calculateUnitRatingScore() { initValues(); int score = 0; score += getExperienceValue(); score += getCommanderValue(); score += getCombatRecordValue(); score += getSupportValue(); score += getTransportValue(); score += getTechValue(); score += getFinancialValue(); return score; } public int getExperienceValue() { BigDecimal averageExperience = calcAverageExperience(); if (averageExperience.compareTo(greenThreshold) >= 0) { return 5; } else if (averageExperience.compareTo(regularThreshold) >= 0) { return 10; } else if (averageExperience.compareTo(veteranThreshold) >= 0) { return 20; } return 40; } public int getCommanderValue() { if (getCommander() == null) { return 0; } int value = 0; Skill test = getCommander().getSkill(SkillType.S_LEADER); if (test != null) { value += test.getLevel(); } test = getCommander().getSkill(SkillType.S_NEG); if (test != null) { value += test.getLevel(); } test = getCommander().getSkill(SkillType.S_STRATEGY); if (test != null) { value += test.getLevel(); } test = getCommander().getSkill(SkillType.S_TACTICS); if (test != null) { value += test.getLevel(); } /** * todo consider adding rpg traits in MekHQ (they would have no impact on megamek). * value += (total positive - total negative) * See FM: Mercs (rev) pg 154 for a full list. */ return value; } private int getMedicPoolHours() { return (getCampaign().getMedicPool() + getCampaign().getNumberMedics()) * 20; } int getMedicalSupportAvailable() { return medSupportHours + getMedicPoolHours(); } private int getAstechPoolHours() { return getCampaign().getNumberAstechs() * 20; } int getTechSupportHours() { return techSupportHours + getAstechPoolHours(); } private BigDecimal getMedicalSupportPercentage() { if (getMedicalSupportAvailable() <= 0) { return BigDecimal.ZERO; } if (getMedicalSupportHoursNeeded() <= 0) { return HUNDRED; } BigDecimal percent = new BigDecimal(getMedicalSupportAvailable()) .divide(new BigDecimal(getMedicalSupportHoursNeeded()), PRECISION, HALF_EVEN) .multiply(HUNDRED).setScale(0, RoundingMode.DOWN); return (percent.compareTo(HUNDRED) > 0 ? HUNDRED : percent); } private int getMedicalSupportValue() { BigDecimal percent = getMedicalSupportPercentage(); BigDecimal threshold = new BigDecimal(75); if (percent.compareTo(threshold) < 0) { return 0; } percent = percent.subtract(threshold).divide(new BigDecimal(5), PRECISION, HALF_EVEN); return percent.setScale(0, RoundingMode.DOWN).intValue() * 2; } private BigDecimal getAdminSupportPercentage() { if (adminSupportHours <= 0) { return BigDecimal.ZERO; } if (adminSupportNeeded <= 0) { return BigDecimal.ZERO; } BigDecimal percent = new BigDecimal(adminSupportHours).divide(new BigDecimal(adminSupportNeeded), PRECISION, HALF_EVEN) .multiply(HUNDRED) .setScale(0, RoundingMode.DOWN); return (percent.compareTo(HUNDRED) > 0 ? HUNDRED : percent); } private int getAdminValue() { BigDecimal percent = getAdminSupportPercentage(); BigDecimal threshold = new BigDecimal(60); if (percent.compareTo(threshold) < 0) { return 0; } percent = percent.subtract(threshold).divide(new BigDecimal(10), PRECISION, HALF_EVEN); return percent.setScale(0, RoundingMode.DOWN).intValue(); } private int getTechSupportNeeded() { return mechSupportNeeded + tankSupportNeeded + vtolSupportNeeded + baSupportNeeded + convFighterSupportNeeded + aeroFighterSupportNeeded + smallCraftSupportNeeded; } private BigDecimal getTechSupportPercentage() { if (getTechSupportHours() <= 0) { return BigDecimal.ZERO; } int techSupportNeeded = getTechSupportNeeded(); if (techSupportNeeded <= 0) { return HUNDRED; } BigDecimal percent = BigDecimal.valueOf(getTechSupportHours()) .divide(BigDecimal.valueOf(techSupportNeeded), PRECISION, HALF_EVEN) .multiply(HUNDRED) .setScale(0, RoundingMode.DOWN); return (percent.compareTo(HUNDRED) > 0 ? HUNDRED : percent); } private int getTechSupportValue() { BigDecimal percent = getTechSupportPercentage(); BigDecimal threshold = new BigDecimal(60); if (percent.compareTo(threshold) < 0) { return 0; } percent = percent.subtract(threshold).divide(new BigDecimal(10), PRECISION, HALF_EVEN); return percent.setScale(0, RoundingMode.DOWN).intValue() * 5; } public int getSupportValue() { return getTechSupportValue() + getMedicalSupportValue() + getAdminValue(); } private int getYearsInDebt() { int yearsInDebt = getCampaign().getFinances().getFullYearsInDebt(getCampaign().getCalendar()); yearsInDebt += getCampaign().getFinances().getPartialYearsInDebt(getCampaign().getCalendar()); return yearsInDebt; } /* public int getYearsInDebt(boolean recalculate) { if (!recalculate) { return yearsInDebt; } //If we're not in debt, no penalty. if (!campaign.getFinances().isInDebt()) { return 0; } //Sort the transactions in reverse date order. List<Transaction> transactions = campaign.getFinances().getAllTransactions(); Comparator<Transaction> transactionDateCompare = new Comparator<Transaction>() { @Override public int compare(Transaction t1, Transaction t2) { return (t2.getDate()).compareTo(t1.getDate()); } }; Collections.sort(transactions, Collections.reverseOrder(transactionDateCompare)); //Loop through the transaction list, counting all consecutive years in debt. int years = 1; for (Transaction t : transactions) { //Only count yearly carryovers. if (!"Carryover from previous year".equalsIgnoreCase(t.getDescription())) { continue; } //If the carryover was negative, count it. If not, end the cycle. We only care about the number //of years since we were last in the black. if (t.getAmount() < 0) { years++; } else { break; } } yearsInDebt = years; return years; } */ public int getFinancialValue() { int score = getYearsInDebt() * -10; score -= 25 * getCampaign().getFinances().getLoanDefaults(); score -= 10 * getCampaign().getFinances().getFailedCollateral(); return score; } private String getQualityDetails() { StringBuilder out = new StringBuilder(); out.append(String.format("%-" + HEADER_LENGTH + "s %3d", "Quality:", getExperienceValue())).append("\n"); out.append(String.format(" %-" + SUBHEADER_LENGTH + "s %s", "Average Skill Rating:", getAverageExperience())).append("\n"); final String TEMPLATE = " #%-" + CATEGORY_LENGTH + "s %3d"; Map<String, Integer> skillRatingCounts = getSkillRatingCounts(); boolean first = true; for (String nm : SkillType.SKILL_LEVEL_NAMES) { if (skillRatingCounts.containsKey(nm)) { if (!first) { out.append("\n"); } out.append(String.format(TEMPLATE, nm + ":", skillRatingCounts.get(nm))); first = false; } } return out.toString(); } private String getCommandDetails() { StringBuilder out = new StringBuilder(); Person commander = getCommander(); String commanderName = (null == commander) ? "" : " (" + commander.getName() + ")"; out.append(String.format("%-" + HEADER_LENGTH + "s %3d %s", "Command:", getCommanderValue(), commanderName)).append("\n"); final String TEMPLATE = " %-" + SUBHEADER_LENGTH + "s %3d"; out.append(String.format(TEMPLATE, "Leadership:", getCommanderSkill(SkillType.S_LEADER))).append("\n"); out.append(String.format(TEMPLATE, "Negotiation:", getCommanderSkill(SkillType.S_NEG))).append("\n"); out.append(String.format(TEMPLATE, "Strategy:", getCommanderSkill(SkillType.S_STRATEGY))).append("\n"); out.append(String.format(TEMPLATE, "Tactics:", getCommanderSkill(SkillType.S_TACTICS))); return out.toString(); } private String getCombatRecordDetails() { StringBuilder out = new StringBuilder(); out.append(String.format("%-" + HEADER_LENGTH + "s %3d", "Combat Record:", getCombatRecordValue())).append("\n"); final String TEMPLATE = " %-" + SUBHEADER_LENGTH + "s %3d"; out.append(String.format(TEMPLATE, "Successful Missions:", getSuccessCount())).append("\n"); out.append(String.format(TEMPLATE, "Failed Missions:", getFailCount())).append("\n"); out.append(String.format(TEMPLATE, "Contract Breaches:", getBreachCount())); return out.toString(); } String getTransportationDetails() { StringBuilder out = new StringBuilder(); out.append(String.format("%-" + HEADER_LENGTH + "s %3d", "Transportation", getTransportValue())).append("\n"); final String TEMPLATE = " %-" + SUBHEADER_LENGTH + "s %3s"; out.append(String.format(TEMPLATE, "Dropship Capacity:", getTransportPercent().toPlainString() + "%")).append("\n"); final String TEMPLATE_TWO = " #%-" + CATEGORY_LENGTH + "s %3d needed / %3d available"; out.append(String.format(TEMPLATE_TWO, "Mech Bays:", getMechCount(), getMechBayCount())); out.append("\n").append(String.format(TEMPLATE_TWO, "Fighter Bays:", getFighterCount(), getFighterBayCount())); out.append("\n").append(String.format(TEMPLATE_TWO, "Small Craft Bays:", getSmallCraftCount(), getSmallCraftBayCount())); out.append("\n").append(String.format(TEMPLATE_TWO, "Protomech Bays:", getProtoCount(), getProtoBayCount())); out.append("\n").append(String.format(TEMPLATE_TWO, "Heavy Vehicle Bays:", getHeavyVeeCount(), getHeavyVeeBayCount())); int excessHeavyVeeBays = Math.max(0, getHeavyVeeBayCount() - getHeavyVeeCount()); out.append("\n").append(String.format(TEMPLATE_TWO, "Light Vehicle Bays:", getLightVeeCount(), getLightVeeBayCount())) .append(" (plus ").append(excessHeavyVeeBays).append(" excess heavy)"); out.append("\n").append(String.format(TEMPLATE_TWO, "BA Bays:", getNumberBaSquads(), getBaBayCount())); out.append("\n").append(String.format(TEMPLATE_TWO, "Infantry Bays:", calcInfantryPlatoons(), getInfantryBayCount())); out.append("\n").append(String.format(TEMPLATE, "Jumpship?", (isJumpshipOwner() ? "Yes" : "No"))); out.append("\n").append(String.format(TEMPLATE, "Warship w/out Collar?", (isWarshipOwner() ? "Yes" : "No"))); out.append("\n").append(String.format(TEMPLATE, "Warship w/ Collar?", (isWarhipWithDocsOwner() ? "Yes" : "No"))); return out.toString(); } private String getTechnologyDetails() { StringBuilder out = new StringBuilder(); out.append(String.format("%-" + HEADER_LENGTH + "s %3d", "Technology:", getTechValue())); int totalUnits = getTechRatedUnits(); final String TEMPLATE = " %-" + SUBHEADER_LENGTH + "s %3d"; out.append("\n").append(String.format(TEMPLATE, "#Clan Units:", getCountClan())); out.append("\n").append(String.format(TEMPLATE, "#IS2 Units:", getCountIS2())); out.append("\n").append(String.format(TEMPLATE, "Total # Units:", totalUnits)); return out.toString(); } private String getSupportDetails() { StringBuilder out = new StringBuilder(); out.append(String.format("%-" + HEADER_LENGTH + "s %3d", "Support:", getSupportValue())); final String TEMPLATE_SUB = " %-" + SUBHEADER_LENGTH + "s %3s"; final String TEMPLATE_CAT = " %-" + CATEGORY_LENGTH + "s %8s"; final String TEMPLATE_SUBCAT = " %-" + (SUBCATEGORY_LENGTH) + "s %4s"; out.append("\n").append(String.format(TEMPLATE_SUB, "Tech Support:", getTechSupportPercentage().toPlainString())).append("%"); out.append("\n").append(String.format(TEMPLATE_CAT, "Total Hours Needed:", getTechSupportNeeded())); out.append("\n").append(String.format(TEMPLATE_SUBCAT, "Mech:", mechSupportNeeded)); out.append("\n").append(String.format(TEMPLATE_SUBCAT, "Tank:", tankSupportNeeded)); out.append("\n").append(String.format(TEMPLATE_SUBCAT, "VTOL:", vtolSupportNeeded)); out.append("\n").append(String.format(TEMPLATE_SUBCAT, "BA:", baSupportNeeded)); out.append("\n").append(String.format(TEMPLATE_SUBCAT, "Conv. Fighter:", convFighterSupportNeeded)); out.append("\n").append(String.format(TEMPLATE_SUBCAT, "Aero. Fighter:", aeroFighterSupportNeeded)); out.append("\n").append(String.format(TEMPLATE_SUBCAT, "Small Craft:", smallCraftSupportNeeded)); out.append("\n").append(String.format(TEMPLATE_CAT, "Available:", getTechSupportHours())); out.append("\n").append(String.format(TEMPLATE_SUBCAT, "Techs:", techSupportHours)); out.append("\n").append(String.format(TEMPLATE_SUBCAT, "Astechs:", getAstechPoolHours())); out.append("\n").append(String.format(TEMPLATE_SUB, "Medical Support:", getMedicalSupportPercentage().toPlainString())).append("%"); out.append("\n").append(String.format(TEMPLATE_CAT, "Total Hours Needed:", getMedicalSupportHoursNeeded())); out.append("\n").append(String.format(TEMPLATE_CAT, "Available:", getMedicalSupportAvailable())); out.append("\n").append(String.format(TEMPLATE_SUBCAT, "Doctors:", medSupportHours)); out.append("\n").append(String.format(TEMPLATE_SUBCAT, "Medics:", getMedicPoolHours())); out.append("\n").append(String.format(TEMPLATE_SUB, "HR Support:", getAdminSupportPercentage().toPlainString())).append("%"); out.append("\n").append(String.format(TEMPLATE_CAT, "Total Hours Needed:", adminSupportNeeded)); out.append("\n").append(String.format(TEMPLATE_CAT, "Available:", adminSupportHours)); return out.toString(); } private String getFinancialDetails() { StringBuilder out = new StringBuilder(); out.append(String.format("%-" + HEADER_LENGTH + "s %3d", "Financial:", getFinancialValue())); final String TEMPLATE = " %-" + SUBHEADER_LENGTH + "s %3s"; out.append("\n").append(String.format(TEMPLATE, "Years in Debt:", getYearsInDebt())); out.append("\n").append(String.format(TEMPLATE, "Loan Defaults:", getCampaign().getFinances().getLoanDefaults())); out.append("\n").append(String.format(TEMPLATE, "No Collateral Payment:", getCampaign().getFinances().getFailedCollateral())); return out.toString(); } public String getDetails() { return String.format("%-" + HEADER_LENGTH + "s %s", "Dragoons Rating:", getUnitRating()) + "\n" + " Method: FM: Mercenaries (rev)\n\n" + getQualityDetails() + "\n\n" + getCommandDetails() + "\n\n" + getCombatRecordDetails() + "\n\n" + getTransportationDetails() + "\n\n" + getTechnologyDetails() + "\n\n" + getSupportDetails() + "\n\n" + getFinancialDetails(); } public String getHelpText() { return "Method: FM: Mercenaries (rev)\n" + "An attempt to match the FM: Mercenaries (rev) method for calculating the Dragoon's rating as closely as possible.\n" + "Known differences include the following:\n" + "+ Command: Does not incorporate any positive or negative traits from AToW or BRPG3." + "+ Transportation: This is computed by individual unit rather than by lance/star/squadron.\n" + " Auxiliary vessels are not accounted for.\n" + "+ Support: Artillery weapons & Naval vessels are not accounted for.\n" + "Note: The Dragoons Rating, RAW, does not account for Protomechs at all an Infantry only require admin & medical support, not tech support."; } public BigDecimal getTransportPercent() { // todo Superheavys. //Find out how short of transport bays we are. int heavyVeeBays = getHeavyVeeBayCount(); int numberWithoutTransport = Math.max((getMechCount() - getMechBayCount()), 0); numberWithoutTransport += Math.max(getProtoCount() - getProtoBayCount(), 0); numberWithoutTransport += Math.max(getHeavyVeeCount() - heavyVeeBays, 0); heavyVeeBays -= getHeavyVeeCount(); numberWithoutTransport += Math.max((getLightVeeCount() - (getLightVeeBayCount() + heavyVeeBays)), 0); numberWithoutTransport += Math.max((getFighterCount() - getFighterBayCount()), 0); numberWithoutTransport += Math.max((getNumberBaSquads() - getBaBayCount()), 0); numberWithoutTransport += Math.max((calcInfantryPlatoons() - getInfantryBayCount()), 0); numberWithoutTransport += Math.max((getSmallCraftCount() - getSmallCraftBayCount()), 0); BigDecimal transportNeeded = new BigDecimal(numberWithoutTransport); //Find the percentage of units that are transported. BigDecimal totalUnits = new BigDecimal(getMechCount() + getLightVeeCount() + getHeavyVeeCount() + getFighterCount() + getNumberBaSquads() + calcInfantryPlatoons()); if (totalUnits.compareTo(BigDecimal.ZERO) == 0) { return BigDecimal.ZERO; } BigDecimal percentUntransported = transportNeeded.divide(totalUnits, PRECISION, HALF_EVEN); setTransportPercent(BigDecimal.ONE.subtract(percentUntransported).multiply(HUNDRED).setScale(0, HALF_EVEN)); return super.getTransportPercent(); } @Override protected String getExperienceLevelName(BigDecimal experience) { if (experience.compareTo(greenThreshold) >= 0) { return SkillType.getExperienceLevelName(SkillType.EXP_GREEN); } if (experience.compareTo(regularThreshold) >= 0) { return SkillType.getExperienceLevelName(SkillType.EXP_REGULAR); } if (experience.compareTo(veteranThreshold) >= 0) { return SkillType.getExperienceLevelName(SkillType.EXP_VETERAN); } return SkillType.getExperienceLevelName(SkillType.EXP_ELITE); } private int getTechRatedUnits() { return getMechCount() + getProtoCount() + getFighterCount() + getLightVeeCount() + getHeavyVeeCount() + getSuperHeavyVeeCount() + getNumberBaSquads() + getSmallCraftCount() + getDropshipCount() + getWarshipCount() + getJumpshipCount(); } @Override public int getTechValue() { //Make sure we have units. if (getNumberUnits().compareTo(BigDecimal.ZERO) == 0) { return 0; } //Number of high-tech units is equal to the number of IS2 units plus twice the number of Clan units. BigDecimal highTechNumber = new BigDecimal(getCountIS2() + (getCountClan() * 2)); //Conventional infantry does not count. int numberUnits = getTechRatedUnits(); if (numberUnits <= 0) { return 0; } //Calculate the percentage of high-tech units. setHighTechPercent(highTechNumber.divide(new BigDecimal(numberUnits), PRECISION, HALF_EVEN)); setHighTechPercent(getHighTechPercent().multiply(ONE_HUNDRED)); //Cannot go above 100 percent. if (getHighTechPercent().compareTo(ONE_HUNDRED) > 0) { setHighTechPercent(ONE_HUNDRED); } //Score is calculated from percentage above 30%. BigDecimal scoredPercent = getHighTechPercent().subtract(new BigDecimal(30)); //If we have a negative value (hi-tech percent was < 30%) return a value of zero. if (scoredPercent.compareTo(BigDecimal.ZERO) <= 0) { return 0; } //Round down to the nearest whole percentage. scoredPercent = scoredPercent.setScale(0, RoundingMode.DOWN); //Add +5 points for every 10% remaining. BigDecimal oneTenth = scoredPercent.divide(new BigDecimal(10), PRECISION, HALF_EVEN); BigDecimal score = oneTenth.multiply(new BigDecimal(5)); return score.intValue(); } private void setHighTechPercent(BigDecimal highTechPercent) { this.highTechPercent = highTechPercent; } private BigDecimal getHighTechPercent() { return highTechPercent; } /** * Adds the tech level of the passed unit to the number of Clan or IS Advanced units in the list (as appropriate). * * @param u The {@code Unit} to be evaluated. * @param value The unit's value. Most have a value of '1' but infantry and battle armor are less. */ private void updateAdvanceTechCount(Unit u, BigDecimal value) { if (u.isMothballed()) { return; } logMessage("Unit " + u.getName() + " updating advanced tech count."); int unitType = UnitType.determineUnitTypeCode(u.getEntity()); switch (unitType) { case UnitType.MEK: case UnitType.PROTOMEK: case UnitType.CONV_FIGHTER: case UnitType.AERO: case UnitType.TANK: case UnitType.VTOL: case UnitType.BATTLE_ARMOR: case UnitType.SMALL_CRAFT: case UnitType.DROPSHIP: case UnitType.WARSHIP: case UnitType.JUMPSHIP: int techLevel = u.getEntity().getTechLevel(); logMessage("Unit " + u.getName() + " TL = " + TechConstants.getLevelDisplayableName(techLevel)); if (techLevel > TechConstants.T_INTRO_BOXSET) { if (TechConstants.isClan(techLevel)) { setNumberClan(getNumberClan().add(value)); if (!isConventionalInfanry(u)) { setCountClan(getCountClan() + 1); } } else { setNumberIS2(getNumberIS2().add(value)); if (!isConventionalInfanry(u)) { setCountIS2(getCountIS2() + 1); } } } break; default: // not counted for tech level purposes. logMessage("Unit " + u.getName() + " not counted for tech level."); } } private void setNumberClan(BigDecimal numberClan) { this.numberClan = numberClan; } private BigDecimal getNumberClan() { return numberClan; } private void setCountClan(int countClan) { this.countClan = countClan; } private void setNumberIS2(BigDecimal numberIS2) { this.numberIS2 = numberIS2; } private BigDecimal getNumberIS2() { return numberIS2; } private void setCountIS2(int countIS2) { this.countIS2 = countIS2; } private int getCountIS2() { return countIS2; } private int getCountClan() { return countClan; } }