/* * ResolveScenarioTracker.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; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.Hashtable; import java.util.List; import java.util.Random; import java.util.UUID; import java.util.stream.Collectors; import javax.swing.JFileChooser; import javax.swing.filechooser.FileFilter; import megamek.client.Client; import megamek.common.Aero; import megamek.common.Compute; import megamek.common.Crew; import megamek.common.CriticalSlot; import megamek.common.EjectedCrew; import megamek.common.Entity; import megamek.common.IArmorState; import megamek.common.IEntityRemovalConditions; import megamek.common.Infantry; import megamek.common.Jumpship; import megamek.common.MULParser; import megamek.common.Mech; import megamek.common.MechFileParser; import megamek.common.MechSummary; import megamek.common.MechSummaryCache; import megamek.common.MechWarrior; import megamek.common.Mounted; import megamek.common.Protomech; import megamek.common.SmallCraft; import megamek.common.Tank; import megamek.common.event.GameVictoryEvent; import megamek.common.loaders.EntityLoadingException; import mekhq.MekHQ; import mekhq.Utilities; import mekhq.campaign.event.PersonBattleFinishedEvent; import mekhq.campaign.finances.Transaction; import mekhq.campaign.mission.AtBContract; import mekhq.campaign.mission.AtBScenario; import mekhq.campaign.mission.Contract; import mekhq.campaign.mission.Loot; import mekhq.campaign.mission.Mission; import mekhq.campaign.mission.Scenario; import mekhq.campaign.parts.Part; import mekhq.campaign.personnel.Person; import mekhq.campaign.unit.TestUnit; import mekhq.campaign.unit.Unit; /** * This object will be the main workhorse for the scenario * resolution wizard. It will keep track of information and be * fed back and forth between the various wizards * @author Jay Lawson <jaylawson39 at yahoo.com> */ public class ResolveScenarioTracker { //Hashtable<UUID, Entity> entities; Hashtable<UUID, UnitStatus> unitsStatus; Hashtable<UUID, UnitStatus> salvageStatus; Hashtable<UUID, Crew> pilots; Hashtable<UUID, Crew> mia; ArrayList<TestUnit> potentialSalvage; ArrayList<TestUnit> alliedUnits; ArrayList<TestUnit> actualSalvage; ArrayList<TestUnit> leftoverSalvage; ArrayList<Unit> units; ArrayList<Loot> potentialLoot; ArrayList<Loot> actualLoot; Hashtable<UUID, PersonStatus> peopleStatus; Hashtable<UUID, PrisonerStatus> prisonerStatus; Hashtable<String, String> killCredits; Hashtable<UUID, EjectedCrew> ejections; Hashtable<UUID, EjectedCrew> enemyEjections; /* AtB */ int contractBreaches = 0; int bonusRolls = 0; Campaign campaign; Scenario scenario; JFileChooser unitList; Client client; Boolean control; private GameVictoryEvent victoryEvent; public ResolveScenarioTracker(Scenario s, Campaign c, boolean ctrl) { this.scenario = s; this.campaign = c; this.control = ctrl; unitsStatus = new Hashtable<UUID, UnitStatus>(); salvageStatus = new Hashtable<UUID, UnitStatus>(); potentialSalvage = new ArrayList<TestUnit>(); alliedUnits = new ArrayList<TestUnit>(); // TODO: Make some use of this? actualSalvage = new ArrayList<TestUnit>(); leftoverSalvage = new ArrayList<TestUnit>(); pilots = new Hashtable<UUID, Crew>(); mia = new Hashtable<UUID, Crew>(); units = new ArrayList<Unit>(); potentialLoot = scenario.getLoot(); actualLoot = new ArrayList<Loot>(); peopleStatus = new Hashtable<UUID, PersonStatus>(); prisonerStatus = new Hashtable<UUID, PrisonerStatus>(); killCredits = new Hashtable<String, String>(); ejections = new Hashtable<UUID, EjectedCrew>(); enemyEjections = new Hashtable<UUID, EjectedCrew>(); for(UUID uid : scenario.getForces(campaign).getAllUnits()) { Unit u = campaign.getUnit(uid); if(null != u && null == u.checkDeployment()) { units.add(u); //assume its missing until we can confirm otherwise unitsStatus.put(uid, new UnitStatus(u)); } } unitList = new JFileChooser("."); unitList.setDialogTitle("Load Units"); unitList.setFileFilter(new FileFilter() { @Override public boolean accept(File dir) { if (dir.isDirectory()) { return true; } return dir.getName().endsWith(".mul"); } @Override public String getDescription() { return "MUL file"; } }); } public void findUnitFile() { unitList.showOpenDialog(null); } public String getUnitFilePath() { File unitFile = unitList.getSelectedFile(); if(null == unitFile) { return "No file selected"; } else { return unitFile.getAbsolutePath(); } } public void setClient(Client c) { client = c; } public void processMulFiles() { File unitFile = unitList.getSelectedFile(); //File salvageFile = salvageList.getSelectedFile(); if(null != unitFile) { try { loadUnitsAndPilots(unitFile); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } else { initUnitsAndPilotsWithoutBattle(); } checkStatusOfPersonnel(); } private TestUnit generateNewTestUnit(Entity e) { // Do some hoops here so that the new mech gets it's old individual paint job! String cat = e.getCamoCategory(); String fn = e.getCamoFileName(); TestUnit nu = new TestUnit(e, campaign, true); nu.getEntity().setCamoCategory(cat); nu.getEntity().setCamoFileName(fn); /* AtB uses id to track status of allied units */ if (e.getExternalIdAsString().equals("-1")) { UUID id = UUID.randomUUID(); nu.getEntity().setExternalIdAsString(id.toString()); nu.setId(id); } else { nu.setId(UUID.fromString(e.getExternalIdAsString())); } for (Part part : nu.getParts()) { part.setBrandNew(false); } return nu; } public void processGame() { int pid = client.getLocalPlayer().getId(); int team = client.getLocalPlayer().getTeam(); for (Enumeration<Entity> iter = victoryEvent.getEntities(); iter.hasMoreElements();) { Entity e = iter.nextElement(); if(e.getSubEntities().isPresent()) { // Sub-entities have their own entry in the VictoryEvent data continue; } checkForLostLimbs(e, control); if(e.getOwnerId() == pid) { if(!e.getExternalIdAsString().equals("-1")) { UnitStatus status = unitsStatus.get(UUID.fromString(e.getExternalIdAsString())); if (null == status && scenario instanceof AtBScenario) { TestUnit nu = generateNewTestUnit(e); status = new UnitStatus(nu); unitsStatus.put(nu.getId(), status); alliedUnits.add(nu); } if(null != status) { boolean lost = (!e.canEscape() && !control) || e.getRemovalCondition() == IEntityRemovalConditions.REMOVE_DEVASTATED; status.assignFoundEntity(e, lost); } } if(null != e.getCrew()) { if(!e.getCrew().getExternalIdAsString().equals("-1")) { if(!e.getCrew().isEjected() || e instanceof EjectedCrew) { pilots.put(UUID.fromString(e.getCrew().getExternalIdAsString()), e.getCrew()); } if(e instanceof EjectedCrew) { ejections.put(UUID.fromString(e.getCrew().getExternalIdAsString()), (EjectedCrew)e); } } } } else if(e.getOwner().getTeam() == team) { TestUnit nu = generateNewTestUnit(e); UnitStatus status = new UnitStatus(nu); unitsStatus.put(nu.getId(), status); alliedUnits.add(nu); boolean lost = (!e.canEscape() && !control) || e.getRemovalCondition() == IEntityRemovalConditions.REMOVE_DEVASTATED; status.assignFoundEntity(e, lost); } else if(e.getOwner().isEnemyOf(client.getLocalPlayer())) { if(control) { // Kill credit automatically assigned only if they can't escape if (!e.canEscape()) { Entity killer = victoryEvent.getEntity(e.getKillerId()); if(null != killer && killer.getOwnerId() == pid) { //the killer is one of your units, congrats! killCredits.put(e.getDisplayName(), killer.getExternalIdAsString()); } else { killCredits.put(e.getDisplayName(), "None"); } } if(e instanceof EjectedCrew) { enemyEjections.put(UUID.fromString(e.getCrew().getExternalIdAsString()), (EjectedCrew)e); continue; } TestUnit nu = generateNewTestUnit(e); UnitStatus us = new UnitStatus(nu); us.setTotalLoss(false); salvageStatus.put(nu.getId(), us); potentialSalvage.add(nu); } } } // Utterly destroyed entities for (Enumeration<Entity> iter = victoryEvent.getDevastatedEntities(); iter.hasMoreElements();) { Entity e = iter.nextElement(); if(e.getSubEntities().isPresent()) { // Sub-entities have their own entry in the VictoryEvent data continue; } if(e.getOwnerId() == pid) { if(!e.getExternalIdAsString().equals("-1")) { UnitStatus status = unitsStatus.get(UUID.fromString(e.getExternalIdAsString())); if(null != status) { status.assignFoundEntity(e, true); } } } else if(e.getOwner().getTeam() == team) { TestUnit nu = generateNewTestUnit(e); UnitStatus us = new UnitStatus(nu); unitsStatus.put(nu.getId(), us); alliedUnits.add(nu); } else { Entity killer = victoryEvent.getEntity(e.getKillerId()); if(null != killer && killer.getOwnerId() == pid) { //the killer is one of your units, congrats! killCredits.put(e.getDisplayName(), killer.getExternalIdAsString()); } else { killCredits.put(e.getDisplayName(), "None"); } //why are we doing this, aren't they utterly destroyed? //Taharqa: I am commenting this out /*TestUnit nu = generateNewTestUnit(e); UnitStatus us = new UnitStatus(nu); salvageStatus.put(nu.getId(), us); potentialSalvage.add(nu);*/ } } //add retreated units for (Enumeration<Entity> iter = victoryEvent.getRetreatedEntities(); iter.hasMoreElements();) { Entity e = iter.nextElement(); if(e.getSubEntities().isPresent()) { // Sub-entities have their own entry in the VictoryEvent data continue; } checkForLostLimbs(e, control); if(e.getOwnerId() == pid) { if(!e.getExternalIdAsString().equals("-1")) { UnitStatus status = unitsStatus.get(UUID.fromString(e.getExternalIdAsString())); if (null == status && scenario instanceof AtBScenario) { TestUnit nu = generateNewTestUnit(e); status = new UnitStatus(nu); unitsStatus.put(nu.getId(), status); alliedUnits.add(nu); } if(null != status) { status.assignFoundEntity(e, false); } } if(null != e.getCrew()) { if(!e.getCrew().getExternalIdAsString().equals("-1")) { pilots.put(UUID.fromString(e.getCrew().getExternalIdAsString()), e.getCrew()); if(e instanceof EjectedCrew) { ejections.put(UUID.fromString(e.getCrew().getExternalIdAsString()), (EjectedCrew)e); } } } } else if(e.getOwner().getTeam() == team) { TestUnit nu = generateNewTestUnit(e); UnitStatus us = new UnitStatus(nu); unitsStatus.put(nu.getId(), us); alliedUnits.add(nu); } } Enumeration<Entity> wrecks = victoryEvent.getGraveyardEntities(); while (wrecks.hasMoreElements()) { Entity e = wrecks.nextElement(); if(e.getSubEntities().isPresent()) { // Sub-entities have their own entry in the VictoryEvent data continue; } checkForLostLimbs(e, control); if(e.getOwnerId() == pid) { if(!e.getExternalIdAsString().equals("-1")) { UnitStatus status = unitsStatus.get(UUID.fromString(e.getExternalIdAsString())); if(null != status) { status.assignFoundEntity(e, !control); if(e instanceof EjectedCrew) { ejections.put(UUID.fromString(e.getExternalIdAsString()), (EjectedCrew)e); } } } if(null != e.getCrew()) { if(!e.getCrew().getExternalIdAsString().equals("-1")) { if(e instanceof EjectedCrew) { ejections.put(UUID.fromString(e.getCrew().getExternalIdAsString()), (EjectedCrew)e); } if(!e.getCrew().isEjected() || e instanceof EjectedCrew) { if(control) { pilots.put(UUID.fromString(e.getCrew().getExternalIdAsString()), e.getCrew()); } else { mia.put(UUID.fromString(e.getCrew().getExternalIdAsString()), e.getCrew()); } } } } } else if(e.getOwner().getTeam() == team) { TestUnit nu = generateNewTestUnit(e); UnitStatus us = new UnitStatus(nu); unitsStatus.put(nu.getId(), us); alliedUnits.add(nu); } else if(e.getOwner().isEnemyOf(client.getLocalPlayer())) { Entity killer = victoryEvent.getEntity(e.getKillerId()); if(null != killer && killer.getOwnerId() == pid) { //the killer is one of your units, congrats! killCredits.put(e.getDisplayName(), killer.getExternalIdAsString()); } else { killCredits.put(e.getDisplayName(), "None"); } if(e instanceof EjectedCrew) { enemyEjections.put(UUID.fromString(e.getCrew().getExternalIdAsString()), (EjectedCrew)e); continue; } if(control) { TestUnit nu = generateNewTestUnit(e); UnitStatus us = new UnitStatus(nu); us.setTotalLoss(false); salvageStatus.put(nu.getId(), us); potentialSalvage.add(nu); } } } checkStatusOfPersonnel(); } /** * This checks whether an entity has any blown off limbs. If the battlefield * was not controlled it marks the limb as destroyed. if the battlefield was * controlled it clears the missing status from any equipment. * * This method should be run the first time an entity is loaded into the tracker, * either from the game or from a MUL file. * @param en * @param controlsField */ private void checkForLostLimbs(Entity en, boolean controlsField) { for(int loc = 0; loc < en.locations(); loc++) { if(en.isLocationBlownOff(loc) && !controlsField) { //sorry dude, we cant find your arm en.setLocationBlownOff(loc, false); en.setArmor(IArmorState.ARMOR_DESTROYED, loc); en.setInternal(IArmorState.ARMOR_DESTROYED, loc); } //check for mounted and critical slot missingness as well for (int i = 0; i < en.getNumberOfCriticals(loc); i++) { final CriticalSlot cs = en.getCritical(loc, i); if(null == cs || !cs.isEverHittable()) { continue; } Mounted m = cs.getMount(); if(cs.isMissing()) { if(controlsField) { cs.setMissing(false); if(null != m) { m.setMissing(false); } } else { if(null != m) { m.setMissing(true); } } } } } } private List<Person> shuffleCrew(List<Person> source) { List<Person> sortedList = new ArrayList<Person>(); Random generator = new Random(); while (source.size() > 0) { int position = generator.nextInt(source.size()); sortedList.add(source.get(position)); source.remove(position); } return sortedList; } public void assignKills() { for(Unit u : units) { for(String killed : killCredits.keySet()) { if(killCredits.get(killed).equalsIgnoreCase("None")) { continue; } if(u.getId().toString().equals(killCredits.get(killed))) { for(Person p : u.getActiveCrew()) { PersonStatus status = peopleStatus.get(p.getId()); if(null == status) { //this shouldnt happen so report MekHQ.logError("A null person status was found for person id " + p.getId().toString() + " when trying to assign kills"); continue; } status.addKill(new Kill(p.getId(), killed, u.getEntity().getShortNameRaw(), campaign.getCalendar().getTime())); } } } } } public void checkStatusOfPersonnel() { //lets cycle through units and get their crew for(Unit u : units) { //shuffling the crew ensures that casualties are randomly assigned in multi-crew units List<Person> crew = shuffleCrew(u.getActiveCrew()); Entity en = null; UnitStatus ustatus = unitsStatus.get(u.getId()); if(null != ustatus) { en = ustatus.getEntity(); } if(null == en) { continue; } //check for an ejected entity and if we find one then assign it instead to switch vees //over to infantry checks for casualties Entity ejected = ejections.get(u.getCommander().getId()); //determine total casualties for infantry and large craft int casualties = 0; int casualtiesAssigned = 0; Infantry infantry = null; if (en instanceof Infantry) { infantry = (Infantry)en; } else if (ejected != null && ejected instanceof Infantry) { infantry = (Infantry)ejected; } if(infantry != null) { infantry.applyDamage(); // If reading from a MUL, the shooting strength is set to Integer.MAX_VALUE if there is no damage. int strength = Math.min(((Infantry)infantry).getShootingStrength(), crew.size()); casualties = crew.size() - strength; if (ustatus.isTotalLoss()) { casualties = crew.size(); } // If a tank has already taken hits to the commander or driver, do not assign them again. if (en instanceof Tank) { if (((Tank)en).isDriverHit()) { casualtiesAssigned++; } if (((Tank)en).isCommanderHit()) { casualtiesAssigned++; } } } if(en instanceof SmallCraft || en instanceof Jumpship) { //need to check for existing hits because you can fly aeros with less than full //crew int existingHits = 0; int currentHits = 0; if(null != u.getEntity().getCrew()) { existingHits = u.getEntity().getCrew().getHits(); } if(null != en && null != en.getCrew()) { currentHits = en.getCrew().getHits(); } int newHits = Math.max(0,currentHits - existingHits); casualties = (int)Math.ceil(Compute.getFullCrewSize(en) * (newHits/6.0)); } //try to find the crew in our pilot and mia vectors Crew pilot = pilots.get(u.getCommander().getId()); boolean missingCrew = false; if(null == pilot) { pilot = mia.get(u.getCommander().getId()); missingCrew = true; } for(Person p : crew) { PersonStatus status = new PersonStatus(p.getFullName(), u.getEntity().getDisplayName(), p.getHits(), p.getId()); status.setMissing(missingCrew); //if the pilot was not found in either the pilot or mia vector //then the unit was devastated and no one ejected, so they should be dead, really dead if(null == pilot) { status.setHits(6); status.setDead(true); } //cant do the following by u.usesSoloPilot because entity may be different if ejected else if(en instanceof Mech || en instanceof Protomech || (en instanceof Aero && !(en instanceof SmallCraft || en instanceof Jumpship))) { status.setHits(pilot.getHits()); } else { //we have a multi-crewed vee/Aero/Infantry boolean wounded = false; //tanks need to be handled specially because of the special crits and because //tank destruction should "kill" the crew if(en instanceof Tank) { boolean destroyed = false; for(int loc = 0; loc < en.locations(); loc++) { if(loc == Tank.LOC_TURRET || loc == Tank.LOC_TURRET_2 || loc == Tank.LOC_BODY) { continue; } if(en.getInternal(loc) <= 0) { destroyed = true; break; } } if(destroyed || null == en.getCrew() || en.getCrew().isDead()) { if(Compute.d6(2) >= 7) { wounded = true; } else { status.setHits(6); status.setDead(true); } } else if(((Tank)en).isDriverHit() && u.isDriver(p)) { if(Compute.d6(2) >= 7) { wounded = true; } else { status.setHits(6); status.setDead(true); } } else if(((Tank)en).isCommanderHit() && u.isCommander(p)) { if(Compute.d6(2) >= 7) { wounded = true; } else { status.setHits(6); status.setDead(true); } } } if(casualtiesAssigned < casualties) { casualtiesAssigned++; if(Compute.d6(2) >= 7) { wounded = true; } else { status.setHits(6); status.setDead(true); } } if(wounded) { int hits = campaign.getCampaignOptions().getMinimumHitsForVees(); if (campaign.getCampaignOptions().useAdvancedMedical() || campaign.getCampaignOptions().useRandomHitsForVees()) { int range = 6 - hits; hits = hits + Compute.randomInt(range); } status.setHits(hits); } } status.setXP(campaign.getCampaignOptions().getScenarioXP()); status.setDeployed(!en.wasNeverDeployed()); peopleStatus.put(p.getId(), status); } } // And now we have potential prisoners that are crewing a unit... if(campaign.getCampaignOptions().capturePrisoners()) { for(Unit u : potentialSalvage) { if (null == u) { continue; // Shouldn't happen... but well... ya know } Entity en = null; UnitStatus ustatus = salvageStatus.get(u.getId()); if(null != ustatus) { en = ustatus.getEntity(); } if(null == en) { continue; } //check for an ejected entity and if we find one then assign it instead to switch vees //over to infantry checks for casualties Entity ejected = null; if (!en.getCrew().getExternalIdAsString().equals("-1")) { ejected = enemyEjections.get(UUID.fromString(en.getCrew().getExternalIdAsString())); } if(null != ejected) { en = ejected; } //check if this ejection was picked up by a player's unit boolean pickedUp = en instanceof MechWarrior && !((MechWarrior)en).getPickedUpByExternalIdAsString().equals("-1") && null != unitsStatus.get(UUID.fromString(((MechWarrior)en).getPickedUpByExternalIdAsString())); //if the crew ejected from this unit, then skip it because we should find them elsewhere //if they are alive if(!(en instanceof EjectedCrew) && null != en.getCrew() && en.getCrew().isEjected()) { continue; } //shuffling the crew ensures that casualties are randomly assigned in multi-crew units List<Person> crew = Utilities.genRandomCrewWithCombinedSkill(campaign, u).values().stream().flatMap(c -> c.stream()).collect(Collectors.toList()); crew = shuffleCrew(crew); //For vees we may need to know the commander or driver, which aren't assigned for TestUnit. Person commander = null; Person driver = null; if (en instanceof Tank) { //Prefer gunner over driver, as in Unit::getCommander for (Person p : crew) { if (p.getPrimaryRole() == Person.T_VEE_GUNNER) { commander = p; } else if (p.getPrimaryRole() == Person.T_GVEE_DRIVER || p.getPrimaryRole() == Person.T_VTOL_PILOT || p.getPrimaryRole() == Person.T_NVEE_DRIVER) { driver = p; } } } if (commander == null && crew.size() > 0) { commander = crew.get(0); } int casualties = 0; int casualtiesAssigned = 0; if(en instanceof Infantry) { en.applyDamage(); int strength = ((Infantry)en).getShootingStrength(); casualties = crew.size() - strength; } if(en instanceof Aero && !u.usesSoloPilot()) { //need to check for existing hits because you can fly aeros with less than full //crew int existingHits = 0; int currentHits = 0; if(null != u.getEntity().getCrew()) { existingHits = u.getEntity().getCrew().getHits(); } if(null != en && null != en.getCrew()) { currentHits = en.getCrew().getHits(); } int newHits = Math.max(0,currentHits - existingHits); casualties = (int)Math.ceil(Compute.getFullCrewSize(en) * (newHits/6.0)); } for(Person p : crew) { // Give them a UUID. We won't actually use this for the campaign, but to //identify them in the prisonerStatus hash UUID id = UUID.randomUUID(); while (prisonerStatus.get(id) != null) { id = UUID.randomUUID(); } p.setId(id); PrisonerStatus status = new PrisonerStatus(p.getFullName(), u.getEntity().getDisplayName(), p); if(en instanceof Mech || en instanceof Protomech || (en instanceof Aero && !(en instanceof SmallCraft || en instanceof Jumpship))) { Crew pilot = en.getCrew(); if(null == pilot) { continue; } status.setHits(pilot.getHits()); } else { //we have a multi-crewed vee boolean wounded = false; if(en instanceof Tank) { boolean destroyed = false; for(int loc = 0; loc < en.locations(); loc++) { if(loc == Tank.LOC_TURRET || loc == Tank.LOC_TURRET_2 || loc == Tank.LOC_BODY) { continue; } if(en.getInternal(loc) <= 0) { destroyed = true; break; } } if(destroyed || null == en.getCrew() || en.getCrew().isDead()) { if(Compute.d6(2) >= 7) { wounded = true; } else { status.setHits(6); } } else if(((Tank)en).isDriverHit() && driver != null && driver.getId() == p.getId()) { if(Compute.d6(2) >= 7) { wounded = true; } else { status.setHits(6); status.setDead(true); } } else if(((Tank)en).isCommanderHit() && commander != null && commander.getId() == p.getId()) { if(Compute.d6(2) >= 7) { wounded = true; } else { status.setHits(6); status.setDead(true); } } } else if(en instanceof Infantry || en instanceof Aero) { if(casualtiesAssigned < casualties) { casualtiesAssigned++; if(Compute.d6(2) >= 7) { wounded = true; } else { status.setHits(6); status.setDead(true); } } } if(wounded) { int hits = campaign.getCampaignOptions().getMinimumHitsForVees(); if (campaign.getCampaignOptions().useAdvancedMedical() || campaign.getCampaignOptions().useRandomHitsForVees()) { int range = 6 - hits; hits = hits + Compute.randomInt(range); } status.setHits(hits); } } status.setCaptured(Utilities.isLikelyCapture(en) || pickedUp); status.setXP(campaign.getCampaignOptions().getScenarioXP()); prisonerStatus.put(id, status); } } } } private void loadUnitsAndPilots(File unitFile) throws IOException { if (unitFile != null) { // I need to get the parser myself, because I want to pull both // entities and pilots from it // Create an empty parser. MULParser parser = new MULParser(); // Open up the file. InputStream listStream = new FileInputStream(unitFile); // Read a Vector from the file. try { parser.parse(listStream); listStream.close(); } catch (Exception excep) { excep.printStackTrace(System.err); // throw new IOException("Unable to read from: " + // unitFile.getName()); } // Was there any error in parsing? if (parser.hasWarningMessage()) { MekHQ.logMessage(parser.getWarningMessage()); } killCredits = parser.getKills(); for (Entity e : parser.getSurvivors()) { checkForLostLimbs(e, control); if(!e.getExternalIdAsString().equals("-1")) { UnitStatus status = unitsStatus.get(UUID.fromString(e.getExternalIdAsString())); if (null == status && scenario instanceof AtBScenario && !(e instanceof EjectedCrew)) { TestUnit nu = generateNewTestUnit(e); status = new UnitStatus(nu); unitsStatus.put(nu.getId(), status); alliedUnits.add(nu); } if(null != status) { boolean lost = (!e.canEscape() && !control) || e.getRemovalCondition() == IEntityRemovalConditions.REMOVE_DEVASTATED; status.assignFoundEntity(e, lost); } } if(null != e.getCrew()) { if(!e.getCrew().getExternalIdAsString().equals("-1")) { if(!e.getCrew().isEjected() || e instanceof EjectedCrew) { pilots.put(UUID.fromString(e.getCrew().getExternalIdAsString()), e.getCrew()); } if(e instanceof EjectedCrew) { ejections.put(UUID.fromString(e.getCrew().getExternalIdAsString()), (EjectedCrew)e); } } } } for (Entity e : parser.getAllies()) { checkForLostLimbs(e, control); if(!e.getExternalIdAsString().equals("-1")) { UnitStatus status = unitsStatus.get(UUID.fromString(e.getExternalIdAsString())); if (null == status) { TestUnit nu = generateNewTestUnit(e); status = new UnitStatus(nu); unitsStatus.put(nu.getId(), status); alliedUnits.add(nu); } if(null != status) { boolean lost = (!e.canEscape() && !control) || e.getRemovalCondition() == IEntityRemovalConditions.REMOVE_DEVASTATED; status.assignFoundEntity(e, lost); } } if(null != e.getCrew()) { if(!e.getCrew().getExternalIdAsString().equals("-1")) { if(!e.getCrew().isEjected() || e instanceof EjectedCrew) { pilots.put(UUID.fromString(e.getCrew().getExternalIdAsString()), e.getCrew()); } if(e instanceof EjectedCrew) { ejections.put(UUID.fromString(e.getCrew().getExternalIdAsString()), (EjectedCrew)e); } } } } // Utterly destroyed entities for (Entity e : parser.getDevastated()) { UnitStatus status = null; if(!e.getExternalIdAsString().equals("-1")) { status = unitsStatus.get(UUID.fromString(e.getExternalIdAsString())); } if(null != status) { status.assignFoundEntity(e, true); } else { //why are we doing this, aren't they utterly destroyed? //Taharqa: I am commenting this out /*TestUnit nu = generateNewTestUnit(e); UnitStatus us = new UnitStatus(nu); salvageStatus.put(nu.getId(), us); potentialSalvage.add(nu);*/ } } for(Entity e : parser.getSalvage()) { checkForLostLimbs(e, control); UnitStatus status = null; if(!e.getExternalIdAsString().equals("-1") && e.isSalvage()) { status = unitsStatus.get(UUID.fromString(e.getExternalIdAsString())); } if(null != status) { status.assignFoundEntity(e, !control); if(null != e.getCrew()) { if(!e.getCrew().getExternalIdAsString().equals("-1")) { if(e instanceof EjectedCrew) { ejections.put(UUID.fromString(e.getCrew().getExternalIdAsString()), (EjectedCrew)e); } if(!e.getCrew().isEjected() || e instanceof EjectedCrew) { if(control) { pilots.put(UUID.fromString(e.getCrew().getExternalIdAsString()), e.getCrew()); } else { mia.put(UUID.fromString(e.getCrew().getExternalIdAsString()), e.getCrew()); } } } } } else { if(e instanceof EjectedCrew & null != e.getCrew() && !e.getCrew().getExternalIdAsString().equals("-1")) { enemyEjections.put(UUID.fromString(e.getCrew().getExternalIdAsString()), (EjectedCrew)e); continue; } if(control) { TestUnit nu = generateNewTestUnit(e); UnitStatus us = new UnitStatus(nu); us.setTotalLoss(false); salvageStatus.put(nu.getId(), us); potentialSalvage.add(nu); } } } } } /** * When resolving the battle manually without a resolution file (such as MekHQ + tabletop), * set initial status of units and crew as pre-battle. */ private void initUnitsAndPilotsWithoutBattle() { for (Unit u : units) { UnitStatus status = unitsStatus.get(u.getId()); status.assignFoundEntity(u.getEntity(), false); u.getEntity().setDeployed(true); Crew crew = u.getEntity().getCrew(); if(null != crew && !crew.getExternalIdAsString().equals("-1")) { pilots.put(UUID.fromString(crew.getExternalIdAsString()), crew); } } } public ArrayList<TestUnit> getAlliedUnits() { return alliedUnits; } public ArrayList<TestUnit> getPotentialSalvage() { return potentialSalvage; } public ArrayList<TestUnit> getActualSalvage() { return actualSalvage; } public void salvageUnit(int i) { TestUnit salvageUnit = potentialSalvage.get(i); actualSalvage.add(salvageUnit); } public void dontSalvageUnit(int i) { leftoverSalvage.add(potentialSalvage.get(i)); } public void setContractBreaches(int i) { contractBreaches = i; } public void setBonusRolls(int i) { bonusRolls = i; } public Campaign getCampaign() { return campaign; } public Scenario getScenario() { return scenario; } public Mission getMission() { return campaign.getMission(scenario.getMissionId()); } public Hashtable<String, String> getKillCredits() { return killCredits; } public ArrayList<Unit> getUnits() { return units; } public void resolveScenario(int resolution, String report) { //lets start by generating a stub file for our records scenario.generateStub(campaign); //ok lets do the whole enchilada and go ahead and update campaign //first figure out if we need any battle loss comp double blc = 0; Mission m = campaign.getMission(scenario.getMissionId()); if(m instanceof Contract) { blc = ((Contract)m).getBattleLossComp()/100.0; } //now lets update personnel for(UUID pid : peopleStatus.keySet()) { Person person = campaign.getPerson(pid); PersonStatus status = peopleStatus.get(pid); if(null == person || null == status) { continue; } MekHQ.triggerEvent(new PersonBattleFinishedEvent(person, status)); if(status.getHits() > person.getHits()) { person.setHits(status.getHits()); } if(status.wasDeployed()) { person.setXp(person.getXp() + status.getXP()); person.addLogEntry(campaign.getDate(), "Participated in " + scenario.getName() + " during mission " + m.getName()); } for(Kill k : status.getKills()) { campaign.addKill(k); } if(status.isMissing()) { campaign.changeStatus(person, Person.S_MIA); } if(status.isDead()) { campaign.changeStatus(person, Person.S_KIA); if (campaign.getCampaignOptions().getUseAtB() && m instanceof AtBContract) { campaign.getRetirementDefectionTracker().removeFromCampaign(person, true, campaign.getCampaignOptions().getUseShareSystem()?person.getNumShares(campaign.getCampaignOptions().getSharesForAll()):0, campaign, (AtBContract)m); } } if (campaign.getCampaignOptions().useAdvancedMedical()) { person.diagnose(status.getHits()); } if (status.toRemove()) { campaign.removePerson(pid, false); } } // update prisoners for(UUID pid : prisonerStatus.keySet()) { PrisonerStatus status = prisonerStatus.get(pid); Person person = status.getPerson(); if(null == person || null == status) { continue; } MekHQ.triggerEvent(new PersonBattleFinishedEvent(person, status)); if(status.isDead()) { continue; } if (status.isCaptured()) { getCampaign().recruitPerson(person, true, true); if (getCampaign().getCampaignOptions().getUseAtB() && getCampaign().getCampaignOptions().getUseAtBCapture() && m instanceof AtBContract && status.isCaptured()) { getCampaign().getFinances().credit(50000, Transaction.C_MISC, "Bonus for prisoner capture", getCampaign().getDate()); //$NON-NLS-1$ if (Compute.d6(2) >= 10 + ((AtBContract)m).getEnemySkill() - getCampaign().getUnitRatingMod()) { getCampaign().addReport(String.format( "You have convinced %s to defect.", person.getHyperlinkedName())); //$NON-NLS-1$ person.setWillingToDefect(true); } } } else { continue; } person.setXp(person.getXp() + status.getXP()); if(status.getHits() > person.getHits()) { person.setHits(status.getHits()); } person.addLogEntry(campaign.getDate(), "Participated in " + scenario.getName() + " during mission " + m.getName()); for(Kill k : status.getKills()) { campaign.addKill(k); } //if(status.isMissing()) { // campaign.changeStatus(person, Person.S_MIA); //} if(status.isDead()) { campaign.changeStatus(person, Person.S_KIA); if (campaign.getCampaignOptions().getUseAtB() && m instanceof AtBContract) { campaign.getRetirementDefectionTracker().removeFromCampaign(person, true, campaign.getCampaignOptions().getUseShareSystem()?person.getNumShares(campaign.getCampaignOptions().getSharesForAll()):0, campaign, (AtBContract)m); } } if (campaign.getCampaignOptions().useAdvancedMedical()) { person.diagnose(status.getHits()); } } //now lets update all units for(Unit unit : units) { UnitStatus ustatus = unitsStatus.get(unit.getId()); if(null == ustatus) { //shouldnt happen continue; } Entity en = ustatus.getEntity(); long unitValue = unit.getBuyCost(); if(campaign.getCampaignOptions().useBLCSaleValue()) { unitValue = unit.getSellValue(); } if(ustatus.isTotalLoss()) { //missing unit if(blc > 0) { long value = (long)(blc * unitValue); campaign.getFinances().credit(value, Transaction.C_BLC, "Battle loss compensation for " + unit.getName(), campaign.getCalendar().getTime()); DecimalFormat formatter = new DecimalFormat(); campaign.addReport(formatter.format(value) + " in battle loss compensation for " + unit.getName() + " has been credited to your account."); } campaign.removeUnit(unit.getId()); } else { long currentValue = unit.getValueOfAllMissingParts(); campaign.clearGameData(en); // FIXME: Need to implement a "fuel" part just like the "armor" part if (en instanceof Aero) { ((Aero)en).setFuelTonnage(((Aero)ustatus.getBaseEntity()).getFuelTonnage()); } unit.setEntity(en); unit.runDiagnostic(true); unit.resetPilotAndEntity(); if(!unit.isRepairable()) { unit.setSalvage(true); } campaign.addReport(unit.getHyperlinkedName() + " has been recovered."); //check for BLC long newValue = unit.getValueOfAllMissingParts(); long blcValue = newValue - currentValue; String blcString = "attle loss compensation (parts) for " + unit.getName(); if(!unit.isRepairable()) { //if the unit is not repairable, you should get BLC for it but we should subtract //the value of salvageable parts blcValue = unitValue - unit.getSellValue(); blcString = "attle loss compensation for " + unit.getName(); } if(blc > 0 && blcValue > 0) { long finalValue = (long)(blc * blcValue); campaign.getFinances().credit(finalValue, Transaction.C_BLC, "B" + blcString, campaign.getCalendar().getTime()); DecimalFormat formatter = new DecimalFormat(); campaign.addReport(formatter.format(finalValue) + " in b" + blcString + " has been credited to your account."); } } } //now lets take care of salvage for(TestUnit salvageUnit : actualSalvage) { UnitStatus salstatus = new UnitStatus(salvageUnit); // FIXME: Need to implement a "fuel" part just like the "armor" part if (salvageUnit.getEntity() instanceof Aero) { ((Aero)salvageUnit.getEntity()).setFuelTonnage(((Aero)salstatus.getBaseEntity()).getFuelTonnage()); } campaign.clearGameData(salvageUnit.getEntity()); campaign.addTestUnit(salvageUnit); //if this is a contract, add to the salvaged value if(getMission() instanceof Contract) { ((Contract)getMission()).addSalvageByUnit(salvageUnit.getSellValue()); } } if(getMission() instanceof Contract) { long value = 0; for(Unit salvageUnit : leftoverSalvage) { value += salvageUnit.getSellValue(); } if(((Contract)getMission()).isSalvageExchange()) { value = (long)((value) * (((Contract)getMission()).getSalvagePct()/100.0)); campaign.getFinances().credit(value, Transaction.C_SALVAGE, "salvage exchange for " + scenario.getName(), campaign.getCalendar().getTime()); DecimalFormat formatter = new DecimalFormat(); campaign.addReport(formatter.format(value) + " C-Bills have been credited to your account for salvage exchange."); } else { ((Contract)getMission()).addSalvageByEmployer(value); } } if (campaign.getCampaignOptions().getUseAtB() && getMission() instanceof AtBContract) { int unitRatingMod = campaign.getUnitRatingMod(); for (Unit unit : units) { unit.setSite(((AtBContract)getMission()).getRepairLocation(unitRatingMod)); } for (Unit unit : actualSalvage) { unit.setSite(((AtBContract)getMission()).getRepairLocation(unitRatingMod)); } } for(Loot loot : actualLoot) { loot.get(campaign, scenario); } scenario.setStatus(resolution); scenario.setReport(report); scenario.clearAllForcesAndPersonnel(campaign); //lets reset the network ids from the c3UUIDs campaign.reloadGameEntities(); campaign.refreshNetworks(); scenario.setDate(campaign.getCalendar().getTime()); if (campaign.getCampaignOptions().getUseAtB() && scenario instanceof AtBScenario) { ((AtBScenario)scenario).doPostResolution(campaign, contractBreaches, bonusRolls); } client = null; } public ArrayList<Person> getMissingPersonnel() { ArrayList<Person> mia = new ArrayList<Person>(); for(UUID pid : peopleStatus.keySet()) { PersonStatus status = peopleStatus.get(pid); if(status.isMissing()) { Person p = campaign.getPerson(pid); if(null != p) { mia.add(p); } } } return mia; } public ArrayList<Person> getDeadPersonnel() { ArrayList<Person> kia = new ArrayList<Person>(); for(UUID pid : peopleStatus.keySet()) { PersonStatus status = peopleStatus.get(pid); if(status.isDead()) { Person p = campaign.getPerson(pid); if(null != p) { kia.add(p); } } } return kia; } public ArrayList<Person> getRecoveredPersonnel() { ArrayList<Person> recovered = new ArrayList<Person>(); for(UUID pid : peopleStatus.keySet()) { PersonStatus status = peopleStatus.get(pid); if(!status.isDead() && !status.isMissing()) { Person p = campaign.getPerson(pid); if(null != p) { recovered.add(p); } } } return recovered; } public Hashtable<UUID, PersonStatus> getPeopleStatus() { return peopleStatus; } public Hashtable<UUID, PrisonerStatus> getPrisonerStatus() { return prisonerStatus; } public Hashtable<UUID, UnitStatus> getUnitsStatus() { return unitsStatus; } public Hashtable<UUID, UnitStatus> getSalvageStatus() { return salvageStatus; } public ArrayList<Loot> getPotentialLoot() { return potentialLoot; } public void addLoot(Loot loot) { actualLoot.add(loot); } public ArrayList<PersonStatus> getSortedPeople() { //put all the PersonStatuses in an ArrayList and sort by the unit name ArrayList<PersonStatus> toReturn = new ArrayList<PersonStatus>(); for(UUID id : getPeopleStatus().keySet()) { PersonStatus status = peopleStatus.get(id); if(null != status) { toReturn.add(status); } } //now sort Collections.sort(toReturn); return toReturn; } public ArrayList<PrisonerStatus> getSortedPrisoners() { //put all the PersonStatuses in an ArrayList and sort by the unit name ArrayList<PrisonerStatus> toReturn = new ArrayList<PrisonerStatus>(); for(UUID id : getPrisonerStatus().keySet()) { PrisonerStatus status = prisonerStatus.get(id); if(null != status) { toReturn.add(status); } } //now sort Collections.sort(toReturn); return toReturn; } public boolean usesSalvageExchange() { return (getMission() instanceof Contract) && ((Contract)getMission()).isSalvageExchange(); } /** * This object is used to track the status of a particular personnel. At the present, * we track the person's missing status, hits, and XP * @author Jay Lawson * */ public class PersonStatus implements Comparable<PersonStatus> { private String name; private String unitName; private int hits; private boolean missing; private int xp; private ArrayList<Kill> kills; private boolean remove; private boolean pickedUp; private UUID personId; private boolean deployed; private boolean forceDead; public PersonStatus(String n, String u, int h, UUID id) { name = n; unitName = u; hits = h; missing = false; xp = 0; kills = new ArrayList<Kill>(); remove = false; pickedUp = false; personId = id; deployed = true; forceDead = false; } public UUID getId() { return personId; } public void setRemove(UUID set) { personId = set; } public boolean toRemove() { return remove; } public void setRemove(boolean set) { remove = set; } public String getName() { return name; } public String getUnitName() { return unitName; } public int getHits() { return hits; } public void setHits(int h) { hits = h; } public boolean isDead() { return forceDead || (hits >= 6); } public void setDead(boolean dead) { this.forceDead = dead; } public boolean isMissing() { return missing && !isDead(); } public void setMissing(boolean b) { missing = b; } public boolean wasPickedUp() { return pickedUp; } public void setPickedUp(boolean set) { pickedUp = set; } public int getXP() { if(isDead()) { return 0; } return xp; } public void setXP(int x) { xp = x; } public void addKill(Kill k) { kills.add(k); } public ArrayList<Kill> getKills() { return kills; } public void setDeployed(boolean b) { deployed = b; } public boolean wasDeployed() { return deployed; } @Override public String toString() { return unitName; } @Override public int compareTo(PersonStatus ostatus) { return unitName.compareTo(ostatus.getUnitName()); } } /** * This object is used to track the status of a prisoners. We need to actually put the whole * person object here because we are not already tracking it on the campaign * @author Jay Lawson * */ public class PrisonerStatus extends PersonStatus { //for prisoners we have to track a whole person Person person; private boolean captured; public PrisonerStatus(String n, String u, Person p) { super(n, u, 0, p.getId()); person = p; } public Person getPerson() { return person; } public boolean isCaptured() { return captured; } public void setCaptured(boolean set) { captured = set; } } /** * This object is used to track the status of a particular unit. * @author Jay Lawson * */ public class UnitStatus { private String name; private String chassis; private String model; private boolean totalLoss; private Entity entity; private Entity baseEntity; Unit unit; public UnitStatus(Unit unit) { this.unit = unit; this.name = unit.getName(); chassis = unit.getEntity().getChassis(); model = unit.getEntity().getModel(); //assume its a total loss until we find something that says otherwise totalLoss = true; //create a brand new entity until we find one MechSummary summary = MechSummaryCache.getInstance().getMech(getLookupName()); if(null == summary) { } else { try { entity = unit.getEntity() == null ? new MechFileParser(summary.getSourceFile(), summary.getEntryName()).getEntity() : unit.getEntity(); baseEntity = new MechFileParser(summary.getSourceFile(), summary.getEntryName()).getEntity(); } catch (EntityLoadingException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } public String toString() { return "Unit status for: " + getName() + ", loss: " + isTotalLoss(); } public String getName() { return name; } public String getLookupName() { String s = chassis + " " + model; s = s.trim(); return s; } public Entity getEntity() { return entity; } public void assignFoundEntity(Entity e, boolean loss) { totalLoss = loss; entity = e; } public Entity getBaseEntity() { return baseEntity; } public void setBaseEntity(Entity baseEntity) { this.baseEntity = baseEntity; } public boolean isTotalLoss() { return totalLoss; } public void setTotalLoss(boolean b) { totalLoss = b; } public String getDesc() { return getDesc(null); } public String getDesc(DecimalFormat formatter) { if(null == entity) { return "Whoops, No Entity"; } String color = "black"; String status = Unit.getDamageStateName(entity.getDamageLevel(false)); if (!Unit.isRepairable(entity)) { color = "rgb(190, 150, 55)"; status = "Salvage"; } else if (!Unit.isFunctional(entity)) { color = "rgb(205, 92, 92)"; status = "Inoperable"; } else { switch(entity.getDamageLevel(false)) { case Entity.DMG_LIGHT: color = "green"; break; case Entity.DMG_MODERATE: color = "yellow"; break; case Entity.DMG_HEAVY: color = "orange"; break; case Entity.DMG_CRIPPLED: color = "red"; break; } } String s = "<html><b>" + getName() + "</b><br><font color='" + color + "'>"+ status + "</font></html>"; if(null != formatter) { s = "<html><b>" + getName() + "</b><br> (" + formatter.format(unit.getSellValue()) + "C-bills) <font color='" + color + "'>"+ status + "</font></html>"; } return s; } public boolean isLikelyCaptured() { if(null == entity) { return false; } return Utilities.isLikelyCapture(entity); } } public void setEvent(GameVictoryEvent gve) { victoryEvent = gve; } }