/* * AbstractMrbcRating.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.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import megamek.common.ASFBay; import megamek.common.BattleArmor; import megamek.common.BattleArmorBay; import megamek.common.Bay; import megamek.common.Dropship; import megamek.common.Entity; import megamek.common.HeavyVehicleBay; import megamek.common.Infantry; import megamek.common.InfantryBay; import megamek.common.Jumpship; import megamek.common.LightVehicleBay; import megamek.common.MechBay; import megamek.common.SmallCraftBay; import megamek.common.UnitType; import mekhq.MekHQ; import mekhq.campaign.Campaign; import mekhq.campaign.mission.Mission; import mekhq.campaign.personnel.Person; import mekhq.campaign.personnel.Skill; import mekhq.campaign.unit.Unit; /** * @author Deric Page (deric (dot) page (at) usa.net) * @version %Id% * @since 3/15/2012 */ public abstract class AbstractUnitRating implements IUnitRating { static final int HEADER_LENGTH = 19; static final int SUBHEADER_LENGTH = 23; static final int CATEGORY_LENGTH = 26; static final int SUBCATEGORY_LENGTH = 31; static final BigDecimal HUNDRED = new BigDecimal(100); private Campaign campaign = null; static final BigDecimal greenThreshold = new BigDecimal("5.5"); static final BigDecimal regularThreshold = new BigDecimal("4.0"); static final BigDecimal veteranThreshold = new BigDecimal("2.5"); private List<Person> commanderList = new ArrayList<>(); private BigDecimal numberUnits = BigDecimal.ZERO; private BigDecimal totalSkillLevels = BigDecimal.ZERO; private final Map<String, Integer> skillRatingCounts = new HashMap<>(); private int mechCount = 0; private int protoCount = 0; private int fighterCount = 0; private int lightVeeCount = 0; private int heavyVeeCount = 0; private int superHeavyVeeCount = 0; private int battleArmorCount = 0; private int numberBaSquads = 0; private int infantryCount = 0; private int smallCraftCount = 0; private int dropshipCount = 0; private int warshipCount = 0; private int jumpshipCount = 0; private int mechBayCount = 0; private int protoBayCount = 0; private int fighterBayCount = 0; private int smallCraftBayCount = 0; private int lightVeeBayCount = 0; private int heavyVeeBayCount = 0; private int baBayCount = 0; private int infantryBayCount = 0; private int dockingCollarCount = 0; private boolean warhipWithDocsOwner = false; private boolean warshipOwner = false; private boolean jumpshipOwner = false; private Person commander = null; private int breachCount = 0; private int successCount = 0; private int failCount = 0; private BigDecimal supportPercent = BigDecimal.ZERO; private BigDecimal transportPercent = BigDecimal.ZERO; private static boolean initialized = false; /** * Default constructor. * * @param campaign The MekHQ {@code Campaign} */ public AbstractUnitRating(Campaign campaign) { this.setCampaign(campaign); setInitialized(false); } static boolean isInitialized() { return initialized; } private static void setInitialized(boolean initialized) { AbstractUnitRating.initialized = initialized; } public void reInitialize() { setInitialized(false); initValues(); } public String getAverageExperience() { return getExperienceLevelName(calcAverageExperience()); } public int getCombatRecordValue() { setSuccessCount(0); setFailCount(0); setBreachCount(0); for (Mission m : getCampaign().getMissions()) { //Skip ongoing missions. if (m.isActive()) { continue; } if (m.getStatus() == Mission.S_SUCCESS) { setSuccessCount(getSuccessCount() + 1); } else if (m.getStatus() == Mission.S_FAILED) { setFailCount(getFailCount() + 1); } else if (m.getStatus() == Mission.S_BREACH) { setBreachCount(getBreachCount() + 1); } } return (getSuccessCount() * 5) - (getFailCount() * 10) - (getBreachCount() * 25); } /** * Returns the average experience level for all combat personnel. */ protected BigDecimal calcAverageExperience() { if (getNumberUnits().compareTo(BigDecimal.ZERO) > 0) { return getTotalSkillLevels().divide(getNumberUnits(), PRECISION, HALF_EVEN); } return BigDecimal.ZERO; } /** * Returns the number of breached contracts. */ int getBreachCount() { return breachCount; } List<Person> getCommanderList() { return commanderList; } /** * Returns the commander (highest ranking person) for this force. */ public Person getCommander() { if ((commander == null)) { // First, check to see if a commander as been flagged. commander = getCampaign().getFlaggedCommander(); if (commander != null) { return commander; } // If we don't have a list of potential commanders, we cannot determine a commander. List<Person> commanderList = getCommanderList(); if (commanderList == null || commanderList.isEmpty()) { commander = null; return null; } //Sort the list of personnel by rank from highest to lowest. Whoever has the highest rank is the commander. Collections.sort(commanderList, (p1, p2) -> { // Active personnel outrank inactive personnel. if (p1.isActive() && !p2.isActive()) { return -1; } else if (!p1.isActive() && p2.isActive()) { return 1; } // Compare rank. int p1Rank = p1.getRankNumeric(); int p2Rank = p2.getRankNumeric(); if (p1Rank > p2Rank) { return -1; } else if (p1Rank < p2Rank) { return 1; } // Compare expreience. int p1ExperienceLevel = p1.getExperienceLevel(false); int p2ExperienceLevel = p2.getExperienceLevel(false); if (p1ExperienceLevel > p2ExperienceLevel) { return -1; } else if (p1ExperienceLevel < p2ExperienceLevel) { return 1; } return 0; }); commander = commanderList.get(0); } return commander; } int getCommanderSkill(String skillName) { Person commander = getCommander(); if (commander == null) { return 0; } Skill skill = commander.getSkill(skillName); if (skill == null) { return 0; } return skill.getLevel(); } /** * Returns the number of failed contracts. */ int getFailCount() { return failCount; } public int getTechValue() { return 0; } /** * Returns the number of successfully completed contracts. */ int getSuccessCount() { return successCount; } /** * Returns the overall percentage of fully supported units. */ public BigDecimal getSupportPercent() { return supportPercent; } public int getTransportValue() { int value = 0; //Find the percentage of units that are transported. setTransportPercent(getTransportPercent()); //Compute the score. BigDecimal scoredPercent = getTransportPercent().subtract(new BigDecimal(50)); if (scoredPercent.compareTo(BigDecimal.ZERO) < 0) { return value; } BigDecimal percentageScore = scoredPercent.divide(new BigDecimal(10), 0, RoundingMode.DOWN); value += percentageScore.multiply(new BigDecimal(5)).setScale(0, RoundingMode.DOWN).intValue(); value = Math.min(value, 25); //Only the highest of these values should be used, regardless of how many are actually owned. if (isWarhipWithDocsOwner()) { value += 30; } else if (isWarshipOwner()) { value += 20; } else if (isJumpshipOwner()) { value += 10; } return value; } public int getUnitRating(int score) { if (score < 0) { return DRAGOON_F; } else if (score < 46) { return DRAGOON_D; } else if (score < 86) { return DRAGOON_C; } else if (score < 121) { return DRAGOON_B; } else if (score < 151) { return DRAGOON_A; } else { return DRAGOON_ASTAR; } } public String getUnitRatingName(int rating) { switch (rating) { case DRAGOON_F: return "F"; case DRAGOON_D: return "D"; case DRAGOON_C: return "C"; case DRAGOON_B: return "B"; case DRAGOON_A: return "A"; case DRAGOON_ASTAR: return "A*"; default: return "Unrated"; } } public String getUnitRating() { int score = calculateUnitRatingScore(); return getUnitRatingName(getUnitRating(score)) + " (" + score + ")"; } public int getUnitRatingAsInteger() { return getUnitRating(calculateUnitRatingScore()); } public int getScore() { return calculateUnitRatingScore(); } public int getModifier() { return (calculateUnitRatingScore() / 10); } /** * Calculates the weighted value of the unit based on if it is Infantry, Battle Armor or something else. * * @param u The {@code Unit} to be evaluated. */ BigDecimal getUnitValue(Unit u) { BigDecimal value = BigDecimal.ONE; if (isConventionalInfanry(u) && (((Infantry) u.getEntity()).getSquadN() == 1)) { value = new BigDecimal("0.25"); } return value; } boolean isConventionalInfanry(Unit u) { return (u.getEntity() instanceof Infantry) && !(u.getEntity() instanceof BattleArmor); } /** * Returns the sum of all experience rating for all combat units. */ BigDecimal getTotalSkillLevels() { return getTotalSkillLevels(true); } /** * Returns the sum of all experience rating for all combat units. * * @param canInit Whether or not this method may initialize the values */ BigDecimal getTotalSkillLevels(boolean canInit) { if (canInit && !isInitialized()) { initValues(); } return totalSkillLevels; } /** * Returns the total number of combat units. */ protected BigDecimal getNumberUnits() { return numberUnits; } protected abstract String getExperienceLevelName(BigDecimal experience); /** * Calculates the unit's rating score. */ protected abstract int calculateUnitRatingScore(); /** * Recalculates the dragoons rating. If this has already been done, the initialized flag should already be set true * and this method will immediately exit. */ protected void initValues() { logMessage("Initializing unit rating."); setCommanderList(new ArrayList<>()); setNumberUnits(BigDecimal.ZERO); setTotalSkillLevels(BigDecimal.ZERO); setMechCount(0); setFighterCount(0); setSmallCraftCount(0); setProtoCount(0); setLightVeeCount(0); setHeavyVeeCount(0); setSuperHeavyVeeCount(0); setBattleArmorCount(0); setNumberBaSquads(0); setInfantryCount(0); setJumpshipCount(0); setWarshipCount(0); setMechBayCount(0); setFighterBayCount(0); setSmallCraftBayCount(0); setProtoBayCount(0); setBaBayCount(0); setLightVeeBayCount(0); setHeavyVeeBayCount(0); setInfantryBayCount(0); setDockingCollarCount(0); setWarhipWithDocsOwner(false); setWarshipOwner(false); setJumpshipOwner(false); setCommander(null); setBreachCount(0); setSuccessCount(0); setFailCount(0); setSupportPercent(BigDecimal.ZERO); setTransportPercent(BigDecimal.ZERO); setInitialized(true); clearSkillRatingCounts(); logMessage("Initialization of unit rating complete."); } private void updateBayCount(Dropship ds) { // ToDo Superheavy Bays. for (Bay bay : ds.getTransportBays()) { if (bay instanceof MechBay) { setMechBayCount(getMechBayCount() + (int)bay.getCapacity()); } else if (bay instanceof BattleArmorBay) { setBaBayCount(getBaBayCount() + (int)bay.getCapacity()); } else if (bay instanceof InfantryBay) { setInfantryBayCount(getInfantryBayCount() + (int)bay.getCapacity()); } else if (bay instanceof LightVehicleBay) { setLightVeeBayCount(getLightVeeBayCount() + (int)bay.getCapacity()); } else if (bay instanceof HeavyVehicleBay) { setHeavyVeeBayCount(getHeavyVeeBayCount() + (int)bay.getCapacity()); } else if (bay instanceof ASFBay) { setFighterBayCount(getFighterBayCount() + (int)bay.getCapacity()); } else if (bay instanceof SmallCraftBay) { setSmallCraftBayCount(getSmallCraftBayCount() + (int)bay.getCapacity()); } } } void updateBayCount(Entity e) { if (e instanceof Dropship) { updateBayCount((Dropship) e); } else if (e instanceof Jumpship) { updateBayCount((Jumpship) e); } } private void updateBayCount(Jumpship jumpship) { for (Bay bay : jumpship.getTransportBays()) { if (bay instanceof ASFBay) { setFighterBayCount(getFighterBayCount() + (int)bay.getCapacity()); } else if (bay instanceof SmallCraftBay) { setSmallCraftBayCount(getSmallCraftBayCount() + (int)bay.getCapacity()); } } } void updateDockingCollarCount(Jumpship jumpship) { setDockingCollarCount(getDockingCollarCount() + jumpship.getDockingCollars().size()); } protected Campaign getCampaign() { return campaign; } protected void setCampaign(Campaign campaign) { this.campaign = campaign; } private void setCommanderList(List<Person> commanderList) { this.commanderList = commanderList; } void setNumberUnits(BigDecimal numberUnits) { this.numberUnits = numberUnits; } void setTotalSkillLevels(BigDecimal totalSkillLevels) { this.totalSkillLevels = totalSkillLevels; } /** * Increments the count of the given skill rating by one. * * @param rating The skill rating to be incremented. */ void incrementSkillRatingCounts(final String rating) { int count = 1; if (skillRatingCounts.containsKey(rating)) { count += skillRatingCounts.get(rating); } skillRatingCounts.put(rating, count); } /** * Returns a map of skill ratings and their counts. */ Map<String, Integer> getSkillRatingCounts() { // defensive copy return new HashMap<>(skillRatingCounts); } private void clearSkillRatingCounts() { skillRatingCounts.clear(); } int getMechCount() { return mechCount; } void setMechCount(int mechCount) { this.mechCount = mechCount; } private void incrementMechCount() { mechCount++; } int getProtoCount() { return protoCount; } void setProtoCount(int protoCount) { this.protoCount = protoCount; } private void incrementProtoCount() { protoCount++; } int getFighterCount() { return fighterCount; } void setFighterCount(int fighterCount) { this.fighterCount = fighterCount; } private void incrementFighterCount() { fighterCount++; } int getLightVeeCount() { return lightVeeCount; } void setLightVeeCount(int lightVeeCount) { this.lightVeeCount = lightVeeCount; } private void incrementLightVeeCount() { lightVeeCount++; } int getHeavyVeeCount() { return heavyVeeCount; } private void setHeavyVeeCount(int heavyVeeCount) { this.heavyVeeCount = heavyVeeCount; } private void incrementHeavyVeeCount() { heavyVeeCount++; } int getSuperHeavyVeeCount() { return superHeavyVeeCount; } private void setSuperHeavyVeeCount(int superHeavyVeeCount) { this.superHeavyVeeCount = superHeavyVeeCount; } private void incrementSuperHeavyVeeCount() { superHeavyVeeCount++; } int getBattleArmorCount() { return battleArmorCount; } void setBattleArmorCount(int battleArmorCount) { this.battleArmorCount = battleArmorCount; } private void incrementBattleArmorCount(int amount) { battleArmorCount += amount; } int getNumberBaSquads() { return numberBaSquads; } private void setNumberBaSquads(int numberBaSquads) { this.numberBaSquads = numberBaSquads; } private void incrementNumberBaSquads() { numberBaSquads++; } int getInfantryCount() { return infantryCount; } void setInfantryCount(int infantryCount) { this.infantryCount = infantryCount; } private void incrementInfantryCount(int amount) { infantryCount += amount; } int calcInfantryPlatoons() { return (int) Math.ceil(getInfantryCount() / 28); } int getDropshipCount() { return dropshipCount; } void setDropshipCount(int dropshipCount) { this.dropshipCount = dropshipCount; } private void incrementDropshipCount() { dropshipCount++; } int getSmallCraftCount() { return smallCraftCount; } void setSmallCraftCount(int smallCraftCount) { this.smallCraftCount = smallCraftCount; } private void incrementSmallCraftCount() { smallCraftCount++; } int getWarshipCount() { return warshipCount; } private void setWarshipCount(int warshipCount) { this.warshipCount = warshipCount; } private void incrementWarshipCount() { warshipCount++; } int getJumpshipCount() { return jumpshipCount; } void setJumpshipCount(int jumpshipCount) { this.jumpshipCount = jumpshipCount; } private void incrementJumpshipCount() { jumpshipCount++; } int getMechBayCount() { return mechBayCount; } private void setMechBayCount(int mechBayCount) { this.mechBayCount = mechBayCount; } int getProtoBayCount() { return protoBayCount; } private void setProtoBayCount(int protoBayCount) { this.protoBayCount = protoBayCount; } int getFighterBayCount() { return fighterBayCount; } private void setFighterBayCount(int fighterBayCount) { this.fighterBayCount = fighterBayCount; } int getSmallCraftBayCount() { return smallCraftBayCount; } private void setSmallCraftBayCount(int smallCraftBayCount) { this.smallCraftBayCount = smallCraftBayCount; } int getLightVeeBayCount() { return lightVeeBayCount; } private void setLightVeeBayCount(int lightVeeBayCount) { this.lightVeeBayCount = lightVeeBayCount; } int getHeavyVeeBayCount() { return heavyVeeBayCount; } private void setHeavyVeeBayCount(int heavyVeeBayCount) { this.heavyVeeBayCount = heavyVeeBayCount; } int getBaBayCount() { return baBayCount; } private void setBaBayCount(int baBayCount) { this.baBayCount = baBayCount; } int getInfantryBayCount() { return infantryBayCount; } private void setInfantryBayCount(int infantryBayCount) { this.infantryBayCount = infantryBayCount; } int getDockingCollarCount() { return dockingCollarCount; } void setDockingCollarCount(int dockingCollarCount) { this.dockingCollarCount = dockingCollarCount; } boolean isWarhipWithDocsOwner() { return warhipWithDocsOwner; } void setWarhipWithDocsOwner(boolean warhipWithDocsOwner) { this.warhipWithDocsOwner = warhipWithDocsOwner; } boolean isWarshipOwner() { return warshipOwner; } void setWarshipOwner(boolean warshipOwner) { this.warshipOwner = warshipOwner; } boolean isJumpshipOwner() { return jumpshipOwner; } void setJumpshipOwner(boolean jumpshipOwner) { this.jumpshipOwner = jumpshipOwner; } protected void setCommander(Person commander) { this.commander = commander; } private void setBreachCount(int breachCount) { this.breachCount = breachCount; } private void setSuccessCount(int successCount) { this.successCount = successCount; } private void setFailCount(int failCount) { this.failCount = failCount; } void setSupportPercent(BigDecimal supportPercent) { this.supportPercent = supportPercent; } @Override public BigDecimal getTransportPercent() { return transportPercent; } void setTransportPercent(BigDecimal transportPercent) { this.transportPercent = transportPercent; } void updateUnitCounts(Unit u) { if (u.isMothballed()) { return; } logMessage("Adding " + u.getName() + " to unit counts."); Entity e = u.getEntity(); if (null == e) { logMessage("Unit " + u.getName() + " is not an Entity. Skipping."); return; } int unitType = UnitType.determineUnitTypeCode(e); logMessage("Unit " + u.getName() + " is a " + UnitType.getTypeDisplayableName(unitType)); //todo: Add Airship when Megamek supports it. switch (unitType) { case UnitType.MEK: incrementMechCount(); break; case UnitType.PROTOMEK: incrementProtoCount(); break; case UnitType.GUN_EMPLACEMENT: case UnitType.VTOL: case UnitType.TANK: logMessage("Unit " + u.getName() + " weight is " + e.getWeight()); if (e.getWeight() <= 50f) { incrementLightVeeCount(); } else if (e.getWeight() <= 100f) { incrementHeavyVeeCount(); } else { incrementSuperHeavyVeeCount(); } break; case UnitType.DROPSHIP: incrementDropshipCount(); break; case UnitType.SMALL_CRAFT: incrementSmallCraftCount(); break; case UnitType.WARSHIP: incrementWarshipCount(); break; case UnitType.JUMPSHIP: incrementJumpshipCount(); break; case UnitType.AERO: case UnitType.CONV_FIGHTER: incrementFighterCount(); break; case UnitType.BATTLE_ARMOR: incrementNumberBaSquads(); incrementBattleArmorCount(((BattleArmor) e).getSquadSize()); break; case UnitType.INFANTRY: Infantry i = (Infantry) e; incrementInfantryCount(i.getSquadSize() * i.getSquadN()); break; } } void logMessage(final String msg) { MekHQ.logMessage(msg, 5); //verbosity level debug/informational } }