/* * PersonnelMarket.java * * Copyright (c) 2013 Dylan Myers <dylan at dylanspcs.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.market; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Calendar; import java.util.GregorianCalendar; import java.util.Hashtable; import java.util.UUID; import megamek.common.Compute; import megamek.common.Entity; import megamek.common.EntityMovementMode; import megamek.common.EntityWeightClass; import megamek.common.MechFileParser; import megamek.common.MechSummary; import megamek.common.MechSummaryCache; import megamek.common.TargetRoll; import megamek.common.UnitType; import megamek.common.loaders.EntityLoadingException; import mekhq.MekHQ; import mekhq.MekHqXmlUtil; import mekhq.Utilities; import mekhq.Version; import mekhq.campaign.Campaign; import mekhq.campaign.finances.Transaction; import mekhq.campaign.personnel.Person; import mekhq.campaign.personnel.SkillType; import mekhq.campaign.rating.IUnitRating; import mekhq.campaign.universe.Faction; import mekhq.campaign.universe.RandomFactionGenerator; import org.w3c.dom.Node; import org.w3c.dom.NodeList; public class PersonnelMarket { private ArrayList<Person> personnel = new ArrayList<Person>(); private Hashtable<UUID, Person> personnelIds = new Hashtable<UUID, Person>(); private int daysSinceRolled; public static final int TYPE_RANDOM = 0; public static final int TYPE_DYLANS = 1; public static final int TYPE_FMMR = 2; public static final int TYPE_STRAT_OPS = 3; public static final int TYPE_ATB = 4; public static final int TYPE_NUM = 5; /* Used by AtB to track Units assigned to recruits; the key * is the person UUID. */ private Hashtable<UUID, Entity> attachedEntities = new Hashtable<UUID, Entity>(); /* Alternate types of rolls, set by PersonnelMarketDialog */ private boolean paidRecruitment = false; private int paidRecruitType; public PersonnelMarket() { } public PersonnelMarket(Campaign c) { daysSinceRolled = c.getCampaignOptions().getMaintenanceCycleDays(); generatePersonnelForDay(c); } /* * Generate new personnel to be added to the * market availability pool */ public void generatePersonnelForDay(Campaign c) { int roll; int q = 0; Person p; boolean updated = false; if (!personnel.isEmpty()) { removePersonnelForDay(c); } if (paidRecruitment && c.getCalendar().get(Calendar.DAY_OF_WEEK) == Calendar.MONDAY) { if (c.getFinances().debit(100000, Transaction.C_MISC, "Paid recruitment roll", c.getDate())) { doPaidRecruitment(c); updated = true; } else { c.addReport("<html><font color=\"red\">Insufficient funds for paid recruitment.</font></html>"); } } else { switch (c.getCampaignOptions().getPersonnelMarketType()) { case TYPE_DYLANS: // TODO: Add in extra infantry and vehicle crews q = generateRandomQuantity(); ArrayList<Long> mtf = new ArrayList<Long>(); long mostTypes = getUnitMainForceTypes(c); if ((mostTypes & Entity.ETYPE_MECH) != 0) { mtf.add(Entity.ETYPE_MECH); } else if ((mostTypes & Entity.ETYPE_TANK) != 0) { mtf.add(Entity.ETYPE_TANK); } else if ((mostTypes & Entity.ETYPE_AERO) != 0) { mtf.add(Entity.ETYPE_AERO); } else if ((mostTypes & Entity.ETYPE_BATTLEARMOR) != 0) { mtf.add(Entity.ETYPE_BATTLEARMOR); } else if ((mostTypes & Entity.ETYPE_INFANTRY) != 0) { mtf.add(Entity.ETYPE_INFANTRY); } else if ((mostTypes & Entity.ETYPE_PROTOMECH) != 0) { mtf.add(Entity.ETYPE_PROTOMECH); } else if ((mostTypes & Entity.ETYPE_CONV_FIGHTER) != 0) { mtf.add(Entity.ETYPE_CONV_FIGHTER); } else if ((mostTypes & Entity.ETYPE_SMALL_CRAFT) != 0) { mtf.add(Entity.ETYPE_SMALL_CRAFT); } else if ((mostTypes & Entity.ETYPE_DROPSHIP) != 0) { mtf.add(Entity.ETYPE_DROPSHIP); } else { mtf.add(Entity.ETYPE_MECH); } int weight = (int) (c.getCampaignOptions().getPersonnelMarketDylansWeight() * 100); for (int i = 0; i < q; i++) { long choice = mtf.get(Compute.randomInt(Math.max(mtf.size() - 1, 1))); if (Compute.randomInt(99) < weight) { if (choice == Entity.ETYPE_MECH) { p = c.newPerson(Person.T_MECHWARRIOR); } else if (choice == Entity.ETYPE_TANK) { if (Compute.d6() < 3) { p = c.newPerson(Person.T_GVEE_DRIVER); } else { p = c.newPerson(Person.T_VEE_GUNNER); } } else if (choice == Entity.ETYPE_AERO) { p = c.newPerson(Person.T_AERO_PILOT); } else if (choice == Entity.ETYPE_BATTLEARMOR) { p = c.newPerson(Person.T_BA); } else if (choice == Entity.ETYPE_INFANTRY) { p = c.newPerson(Person.T_INFANTRY); } else if (choice == Entity.ETYPE_PROTOMECH) { p = c.newPerson(Person.T_PROTO_PILOT); } else if (choice == Entity.ETYPE_CONV_FIGHTER) { p = c.newPerson(Person.T_CONV_PILOT); } else if (choice == Entity.ETYPE_SMALL_CRAFT) { p = c.newPerson(Person.T_SPACE_PILOT); } else if (choice == Entity.ETYPE_DROPSHIP) { int space = Compute.randomInt(Person.T_SPACE_GUNNER); while (space < Person.T_SPACE_PILOT) { space = Compute.randomInt(Person.T_SPACE_GUNNER); } p = c.newPerson(space); } else { p = c.newPerson(Person.T_NONE); } } else { roll = Compute.randomInt(Person.T_NUM - 1); while (roll == Person.T_NONE) { roll = Compute.randomInt(Person.T_NUM - 1); } p = c.newPerson(roll); } UUID id = UUID.randomUUID(); while (null != personnelIds.get(id)) { id = UUID.randomUUID(); } p.setId(id); personnel.add(p); personnelIds.put(id, p); if (c.getCampaignOptions().getUseAtB()) { addRecruitUnit(p, c); } } updated = true; break; case TYPE_FMMR: long mft = getUnitMainForceType(c); int mftMod = 0; if (mft == Entity.ETYPE_MECH || mft == Entity.ETYPE_TANK || mft == Entity.ETYPE_INFANTRY || mft == Entity.ETYPE_BATTLEARMOR) { mftMod = 1; } if (c.getCalendar().get(Calendar.DAY_OF_MONTH) == 1) { for (int i = Person.T_NONE + 1; i < Person.T_NUM; i++) { roll = Compute.d6(2); // TODO: Modifiers for hiring hall, but first needs to track the hiring hall if (c.getUnitRating().equalsIgnoreCase("A") || c.getUnitRating().equalsIgnoreCase("A*")) { roll += 3; } else if (c.getUnitRating().equalsIgnoreCase("B")) { roll += 2; } else if (c.getUnitRating().equalsIgnoreCase("C")) { roll += 1; } else if (c.getUnitRating().equalsIgnoreCase("E")) { roll -= 1; } else if (c.getUnitRating().equalsIgnoreCase("F")) { roll -= 2; } roll += mftMod; roll = Math.max(roll, 0); if (roll < 4) { q = 0; } else if (roll < 6) { q = 1; } else if (roll < 9) { q = 2; } else if (roll < 11) { q = 3; } else if (roll < 14) { q = 4; } else if (roll < 16) { q = 5; } else { q = 6; } for (int j = 0; j < q; j++) { p = c.newPerson(i); UUID id = UUID.randomUUID(); while (null != personnelIds.get(id)) { id = UUID.randomUUID(); } p.setId(id); personnel.add(p); personnelIds.put(id, p); if (c.getCampaignOptions().getUseAtB()) { addRecruitUnit(p, c); } } } updated = true; } break; case TYPE_STRAT_OPS: if (daysSinceRolled == c.getCampaignOptions().getMaintenanceCycleDays()) { roll = Compute.d6(2); if (roll == 2) { // Medical p = c.newPerson(Person.T_DOCTOR); } else if (roll == 3) { // ASF or Proto Pilot if (c.getFaction().isClan() && c.getCalendar().after(new GregorianCalendar(3059, 1, 1)) && Compute.d6(2) < 6) { p = c.newPerson(Person.T_PROTO_PILOT); } else { p = c.newPerson(Person.T_AERO_PILOT); } } else if (roll == 4 || roll == 10) { // MW p = c.newPerson(Person.T_MECHWARRIOR); } else if (roll == 5 || roll == 9) { // Vehicle Crews if (Compute.d6() < 3) { p = c.newPerson(Person.T_GVEE_DRIVER); } else { p = c.newPerson(Person.T_VEE_GUNNER); } } else if (roll == 6 || roll == 8) { // Infantry if (c.getFaction().isClan() && Compute.d6(2) > 3) { p = c.newPerson(Person.T_BA); } else { p = c.newPerson(Person.T_INFANTRY); } } else if (roll == 11) { // Tech int tr = Compute.randomInt(Person.T_ASTECH); while (tr < Person.T_MECH_TECH) { tr = Compute.randomInt(Person.T_ASTECH); } p = c.newPerson(tr); } else if (roll == 12) { // Vessel Crew int tr = Compute.randomInt(Person.T_SPACE_GUNNER); while (tr < Person.T_SPACE_PILOT) { tr = Compute.randomInt(Person.T_SPACE_GUNNER); } p = c.newPerson(tr); } else { p = c.newPerson(Person.T_NONE); } UUID id = UUID.randomUUID(); while (null != personnelIds.get(id)) { id = UUID.randomUUID(); } p.setId(id); personnel.add(p); personnelIds.put(id, p); if (c.getCampaignOptions().getUseAtB()) { addRecruitUnit(p, c); } updated = true; } else { incrementDaysSinceRolled(); } break; case TYPE_ATB: p = null; if (c.getCalendar().get(Calendar.DAY_OF_WEEK) == Calendar.MONDAY) { roll = Compute.d6(2); if (roll == 2) { switch (Compute.randomInt(4)) { case 0: p = c.newPerson(Person.T_ADMIN_COM); break; case 1: p = c.newPerson(Person.T_ADMIN_HR); break; case 2: p = c.newPerson(Person.T_ADMIN_LOG); break; case 3: p = c.newPerson(Person.T_ADMIN_TRA); break; } } else if (roll == 3 || roll == 11) { int r = Compute.d6(); if (r == 1 && c.getCalendar().get(Calendar.YEAR) > (c.getFaction().isClan()?2870:3050)) { p = c.newPerson(Person.T_BA_TECH); } else if (r < 4) { p = c.newPerson(Person.T_MECHANIC); } else if (r == 4 && c.getCampaignOptions().getUseAero()) { p = c.newPerson(Person.T_AERO_TECH); } else { p = c.newPerson(Person.T_MECH_TECH); } } else if (roll == 4 || roll == 10) { p = c.newPerson(Person.T_MECHWARRIOR); } else if (roll == 5 && c.getCampaignOptions().getUseAero()) { p = c.newPerson(Person.T_AERO_PILOT); } else if (roll == 5 && c.getFaction().isClan()) { p = c.newPerson(Person.T_MECHWARRIOR); } else if (roll == 5 || roll == 10) { int r = Compute.d6(2); if (r == 2) { p = c.newPerson(Person.T_VTOL_PILOT); //Frequency based on frequency of VTOLs in Xotl 3028 Merc/General } else if (r <= 5) { p = c.newPerson(Person.T_GVEE_DRIVER); } else { p = c.newPerson(Person.T_VEE_GUNNER); } } else if (roll == 6 || roll == 8) { if (c.getFaction().isClan() && c.getCalendar().get(Calendar.YEAR) > 2870 && Compute.d6(2) > 3) { p = c.newPerson(Person.T_BA); } else if (!c.getFaction().isClan() && c.getCalendar().get(Calendar.YEAR) > 3050 && Compute.d6(2) > 11) { p = c.newPerson(Person.T_BA); } else { p = c.newPerson(Person.T_INFANTRY); } } else if (roll == 12) { p = c.newPerson(Person.T_DOCTOR); } if (null != p) { UUID id = UUID.randomUUID(); while (null != personnelIds.get(id)) { id = UUID.randomUUID(); } p.setId(id); personnel.add(p); personnelIds.put(id, p); addRecruitUnit(p, c); if (p.getPrimaryRole() == Person.T_GVEE_DRIVER) { /* Replace driver with 1-6 crew with equal * chances of being drivers or gunners */ personnel.remove(p); for (int i = 0; i < Compute.d6(); i++) { if (Compute.d6() < 4) { p = c.newPerson(Person.T_GVEE_DRIVER); } else { p = c.newPerson(Person.T_VEE_GUNNER); } p = c.newPerson((Compute.d6() < 4)?Person.T_GVEE_DRIVER:Person.T_VEE_GUNNER); if (c.getCampaignOptions().useAbilities()) { int nabil = Math.max(0, p.getExperienceLevel(false) - SkillType.EXP_REGULAR); while (nabil > 0 && null != c.rollSPA(p.getPrimaryRole(), p)) { nabil--; } } id = UUID.randomUUID(); while (null != personnelIds.get(id)) { id = UUID.randomUUID(); } p.setId(id); personnel.add(p); personnelIds.put(id, p); } } Person adminHR = c.findBestInRole(Person.T_ADMIN_HR, SkillType.S_ADMIN); int adminHRExp = (adminHR == null)?SkillType.EXP_ULTRA_GREEN:adminHR.getSkill(SkillType.S_ADMIN).getExperienceLevel(); int gunneryMod = 0; int pilotingMod = 0; switch (adminHRExp) { case SkillType.EXP_ULTRA_GREEN: gunneryMod = -1; pilotingMod = -1; break; case SkillType.EXP_GREEN: if (Compute.d6() < 4) { gunneryMod = -1; } else { pilotingMod = -1; } break; case SkillType.EXP_VETERAN: if (Compute.d6() < 4) { gunneryMod = 1; } else { pilotingMod = 1; } break; case SkillType.EXP_ELITE: gunneryMod = 1; pilotingMod = 1; } switch (p.getPrimaryRole()) { case Person.T_MECHWARRIOR: adjustSkill(p, SkillType.S_GUN_MECH, gunneryMod); adjustSkill(p, SkillType.S_PILOT_MECH, pilotingMod); break; case Person.T_GVEE_DRIVER: adjustSkill(p, SkillType.S_PILOT_GVEE, pilotingMod); break; case Person.T_NVEE_DRIVER: adjustSkill(p, SkillType.S_PILOT_NVEE, pilotingMod); break; case Person.T_VTOL_PILOT: adjustSkill(p, SkillType.S_PILOT_VTOL, pilotingMod); break; case Person.T_VEE_GUNNER: adjustSkill(p, SkillType.S_GUN_VEE, gunneryMod); break; case Person.T_AERO_PILOT: adjustSkill(p, SkillType.S_GUN_AERO, gunneryMod); adjustSkill(p, SkillType.S_PILOT_AERO, pilotingMod); break; case Person.T_INFANTRY: adjustSkill(p, SkillType.S_SMALL_ARMS, gunneryMod); adjustSkill(p, SkillType.S_ANTI_MECH, pilotingMod); break; case Person.T_BA: adjustSkill(p, SkillType.S_GUN_BA, gunneryMod); adjustSkill(p, SkillType.S_ANTI_MECH, pilotingMod); break; case Person.T_PROTO_PILOT: adjustSkill(p, SkillType.S_GUN_PROTO, gunneryMod); break; } int nabil = Math.max(0, p.getExperienceLevel(false) - SkillType.EXP_REGULAR); while (nabil > 0 && null != c.rollSPA(p.getPrimaryRole(), p)) { nabil--; } } updated = true; } break; case TYPE_RANDOM: default: // default is random q = generateRandomQuantity(); for (int i = 0; i < q; i++) { roll = Compute.randomInt(Person.T_NUM - 1); while (roll == Person.T_NONE) { roll = Compute.randomInt(Person.T_NUM - 1); } p = c.newPerson(roll); UUID id = UUID.randomUUID(); while (null != personnelIds.get(id)) { id = UUID.randomUUID(); } p.setId(id); personnel.add(p); personnelIds.put(id, p); updated = true; if (c.getCampaignOptions().getUseAtB()) { addRecruitUnit(p, c); } } } } if (updated && c.getCampaignOptions().getPersonnelMarketReportRefresh()) { c.addReport("<a href='PERSONNEL_MARKET'>Personnel market updated</a>"); } } /* * Remove personnel from market on a new day * The better they are, the faster they disappear */ public void removePersonnelForDay(Campaign c) { ArrayList<Person> toRemove = new ArrayList<Person>(); int roll; switch (c.getCampaignOptions().getPersonnelMarketType()) { case TYPE_FMMR: if (c.getCalendar().get(Calendar.DAY_OF_MONTH) == 1) { personnel.clear(); attachedEntities.clear(); } break; case TYPE_STRAT_OPS: if (daysSinceRolled == c.getCampaignOptions().getMaintenanceCycleDays()) { personnel.clear(); attachedEntities.clear(); } break; case TYPE_ATB: if (c.getCalendar().get(Calendar.DAY_OF_WEEK) == Calendar.MONDAY) { personnel.clear(); attachedEntities.clear(); } break; case TYPE_DYLANS: case TYPE_RANDOM: default: // default is random for (Person p : personnel) { roll = Compute.d6(2); if (p.getExperienceLevel(false) == SkillType.EXP_ELITE && roll < c.getCampaignOptions().getPersonnelMarketRandomEliteRemoval()) { toRemove.add(p); } else if (p.getExperienceLevel(false) == SkillType.EXP_VETERAN && roll < c.getCampaignOptions().getPersonnelMarketRandomVeteranRemoval()) { toRemove.add(p); } else if (p.getExperienceLevel(false) == SkillType.EXP_REGULAR && roll < c.getCampaignOptions().getPersonnelMarketRandomRegularRemoval()) { toRemove.add(p); } else if (p.getExperienceLevel(false) == SkillType.EXP_GREEN && roll < c.getCampaignOptions().getPersonnelMarketRandomGreenRemoval()) { toRemove.add(p); } else if (p.getExperienceLevel(false) == SkillType.EXP_ULTRA_GREEN && roll < c.getCampaignOptions().getPersonnelMarketRandomUltraGreenRemoval()) { toRemove.add(p); } } } for (Person p : toRemove) { if (attachedEntities.contains(p.getId())) { attachedEntities.remove(p.getId()); } } personnel.removeAll(toRemove); } public void setPersonnel(ArrayList<Person> p) { personnel = p; } public ArrayList<Person> getPersonnel() { return personnel; } public void addPerson(Person p) { UUID id = UUID.randomUUID(); while (null != personnelIds.get(id)) { id = UUID.randomUUID(); } p.setId(id); personnel.add(p); } public void addPerson(Person p, Entity e) { addPerson(p); attachedEntities.put(p.getId(), e); } public void removePerson(Person p) { personnel.remove(p); if (attachedEntities.containsKey(p.getId())) { attachedEntities.remove(p.getId()); } } public Entity getAttachedEntity(Person p) { return attachedEntities.get(p.getId()); } public Entity getAttachedEntity(UUID pid) { return attachedEntities.get(pid); } public void removeAttachedEntity(UUID id) { attachedEntities.remove(id); } public boolean getPaidRecruitment() { return paidRecruitment; } public void setPaidRecruitment(boolean pr) { paidRecruitment = pr; } public int getPaidRecruitType() { return paidRecruitType; } public void setPaidRecruitType(int pr) { paidRecruitType = pr; } public void incrementDaysSinceRolled() { daysSinceRolled++; } public void writeToXml(PrintWriter pw1, int indent) { pw1.println(MekHqXmlUtil.indentStr(indent) + "<personnelMarket>"); for (Person p : personnel) { p.writeToXml(pw1, indent + 1); } if (paidRecruitment) { pw1.println(MekHqXmlUtil.indentStr(indent + 1) + "<paidRecruitment/>"); } MekHqXmlUtil.writeSimpleXmlTag(pw1, indent + 1, "paidRecruitType", paidRecruitType); for (UUID id : attachedEntities.keySet()) { pw1.println(MekHqXmlUtil.indentStr(indent + 1) + "<entity id=\"" + id.toString() + "\">" + attachedEntities.get(id).getShortNameRaw() + "</entity>"); } pw1.println(MekHqXmlUtil.indentStr(indent) + "</personnelMarket>"); } public static PersonnelMarket generateInstanceFromXML(Node wn, Campaign c, Version version) { PersonnelMarket retVal = null; try { // Instantiate the correct child class, and call its parsing function. retVal = new PersonnelMarket(); // Okay, now load Part-specific fields! NodeList nl = wn.getChildNodes(); // Loop through the nodes and load our personnel for (int x = 0; x < nl.getLength(); x++) { Node wn2 = nl.item(x); // If it's not an element node, we ignore it. if (wn2.getNodeType() != Node.ELEMENT_NODE) { continue; } if (wn2.getNodeName().equalsIgnoreCase("person")) { Person p = Person.generateInstanceFromXML(wn2, c, version); if (p != null) { retVal.personnel.add(p); } } else if (wn2.getNodeName().equalsIgnoreCase("entity")) { UUID id = UUID.fromString(wn2.getAttributes().getNamedItem("id").getTextContent()); MechSummary ms = MechSummaryCache.getInstance().getMech(wn2.getTextContent()); Entity en = null; try { en = new MechFileParser(ms.getSourceFile(), ms.getEntryName()).getEntity(); } catch (EntityLoadingException ex) { en = null; MekHQ.logError("Unable to load entity: " + ms.getSourceFile() + ": " + ms.getEntryName() + ": " + ex.getMessage()); MekHQ.logError(ex); } if (null != en) { retVal.attachedEntities.put(id, en); } } else if (wn2.getNodeName().equalsIgnoreCase("paidRecruitment")) { retVal.paidRecruitment = true; } else if (wn2.getNodeName().equalsIgnoreCase("paidRecruitType")) { retVal.paidRecruitType = Integer.parseInt(wn2.getTextContent()); } else { // Error condition of sorts! // Errr, what should we do here? MekHQ.logMessage("Unknown node type not loaded in Personnel nodes: " + wn2.getNodeName()); } } } catch (Exception ex) { // Errrr, apparently either the class name was invalid... // Or the listed name doesn't exist. // Doh! MekHQ.logError(ex); } // All personnel need the rank reference fixed for (int x = 0; x < retVal.personnel.size(); x++) { Person psn = retVal.personnel.get(x); // skill types might need resetting psn.resetSkillTypes(); } return retVal; } public static String getTypeName(int type) { switch (type) { case TYPE_RANDOM: return "Random"; case TYPE_DYLANS: return "Dylan's Method"; case TYPE_FMMR: return "FM: Mercenaries Revised"; case TYPE_STRAT_OPS: return "Strat Ops"; case TYPE_ATB: return "Against the Bot"; default: return "ERROR: Default case reached in PersonnelMarket.getTypeName()"; } } public long getUnitMainForceType(Campaign c) { long mostTypes = getUnitMainForceTypes(c); if ((mostTypes & Entity.ETYPE_MECH) != 0) { return Entity.ETYPE_MECH; } else if ((mostTypes & Entity.ETYPE_TANK) != 0) { return Entity.ETYPE_TANK; } else if ((mostTypes & Entity.ETYPE_AERO) != 0) { return Entity.ETYPE_AERO; } else if ((mostTypes & Entity.ETYPE_BATTLEARMOR) != 0) { return Entity.ETYPE_BATTLEARMOR; } else if ((mostTypes & Entity.ETYPE_INFANTRY) != 0) { return Entity.ETYPE_INFANTRY; } else if ((mostTypes & Entity.ETYPE_PROTOMECH) != 0) { return Entity.ETYPE_PROTOMECH; } else if ((mostTypes & Entity.ETYPE_CONV_FIGHTER) != 0) { return Entity.ETYPE_CONV_FIGHTER; } else if ((mostTypes & Entity.ETYPE_SMALL_CRAFT) != 0) { return Entity.ETYPE_SMALL_CRAFT; } else if ((mostTypes & Entity.ETYPE_DROPSHIP) != 0) { return Entity.ETYPE_DROPSHIP; } else { return Entity.ETYPE_MECH; } } public long getUnitMainForceTypes(Campaign c) { int mechs = c.getNumberOfUnitsByType(Entity.ETYPE_MECH); int ds = c.getNumberOfUnitsByType(Entity.ETYPE_DROPSHIP); int sc = c.getNumberOfUnitsByType(Entity.ETYPE_SMALL_CRAFT); int cf = c.getNumberOfUnitsByType(Entity.ETYPE_CONV_FIGHTER); int asf = c.getNumberOfUnitsByType(Entity.ETYPE_AERO); int vee = c.getNumberOfUnitsByType(Entity.ETYPE_TANK, true) + c.getNumberOfUnitsByType(Entity.ETYPE_TANK); int inf = c.getNumberOfUnitsByType(Entity.ETYPE_INFANTRY); int ba = c.getNumberOfUnitsByType(Entity.ETYPE_BATTLEARMOR); int proto = c.getNumberOfUnitsByType(Entity.ETYPE_PROTOMECH); int most = mechs; if (ds > most) { most = ds; } if (sc > most) { most = sc; } if (cf > most) { most = cf; } if (asf > most) { most = asf; } if (vee > most) { most = vee; } if (inf > most) { most = inf; } if (ba > most) { most = ba; } if (proto > most) { most = proto; } long retval = 0; if (most == mechs) { retval = retval | Entity.ETYPE_MECH; } if (most == ds) { retval = retval | Entity.ETYPE_DROPSHIP; } if (most == sc) { retval = retval | Entity.ETYPE_SMALL_CRAFT; } if (most == cf) { retval = retval | Entity.ETYPE_CONV_FIGHTER; } if (most == asf) { retval = retval | Entity.ETYPE_AERO; } if (most == vee) { retval = retval | Entity.ETYPE_TANK; } if (most == inf) { retval = retval | Entity.ETYPE_INFANTRY; } if (most == ba) { retval = retval | Entity.ETYPE_BATTLEARMOR; } if (most == proto) { retval = retval | Entity.ETYPE_PROTOMECH; } return retval; } public int generateRandomQuantity() { int roll = Compute.d6(2); int retval = 0; if (roll == 12) { retval = 6; } else if (roll < 12) { retval = 5; } else if (roll < 10) { retval = 4; } else if (roll < 8) { retval = 3; } else if (roll < 5) { retval = 2; } else if (roll < 3) { retval = 1; } else if (roll == 1) { retval = 0; } return retval; } private void doPaidRecruitment(Campaign c) { int mod; switch (paidRecruitType) { case Person.T_MECHWARRIOR: mod = -2; break; case Person.T_INFANTRY: mod = 2; break; case Person.T_MECH_TECH: case Person.T_AERO_TECH: case Person.T_MECHANIC: case Person.T_BA_TECH: case Person.T_DOCTOR: mod = 1; break; default: mod = 0; } mod += c.getUnitRatingMod() - IUnitRating.DRAGOON_C; if (c.getFinances().isInDebt()) { mod -= 3; } Person adminHR = c.findBestInRole(Person.T_ADMIN_HR, SkillType.S_ADMIN); int adminHRExp = (adminHR == null)?SkillType.EXP_ULTRA_GREEN:adminHR.getSkill(SkillType.S_ADMIN).getExperienceLevel(); mod += adminHRExp - 2; int q = 0; int r = Compute.d6(2) + mod; if (r > 15) { q = 6; } else if (r > 12) { q = 5; } else if (r > 10) { q = 4; } else if (r > 8) { q = 3; } else if (r > 5) { q = 2; } else if (r > 3) { q = 1; } for (int i = 0; i < q; i++) { Person p = c.newPerson(paidRecruitType); UUID id = UUID.randomUUID(); while (null != personnelIds.get(id)) { id = UUID.randomUUID(); } p.setId(id); personnel.add(p); personnelIds.put(id, p); if (c.getCampaignOptions().getUseAtB()) { addRecruitUnit(p, c); } } } public TargetRoll getShipSearchTarget(Campaign campaign, boolean jumpship) { TargetRoll target = new TargetRoll(jumpship?12:10, "Base"); Person adminLog = campaign.findBestInRole(Person.T_ADMIN_LOG, SkillType.S_ADMIN); int adminLogExp = (adminLog == null)?SkillType.EXP_ULTRA_GREEN:adminLog.getSkill(SkillType.S_ADMIN).getExperienceLevel(); for (Person p : campaign.getAdmins()) { if ((p.getPrimaryRole() == Person.T_ADMIN_LOG || p.getSecondaryRole() == Person.T_ADMIN_LOG) && p.getSkill(SkillType.S_ADMIN).getExperienceLevel() > adminLogExp) { adminLogExp = p.getSkill(SkillType.S_ADMIN).getExperienceLevel(); } } target.addModifier(SkillType.EXP_REGULAR - adminLogExp, "Admin/Logistics"); target.addModifier(IUnitRating.DRAGOON_C - campaign.getUnitRatingMod(), "Unit Rating"); return target; } private void addRecruitUnit(Person p, Campaign c) { int unitType; switch (p.getPrimaryRole()) { case Person.T_MECHWARRIOR: unitType = UnitType.MEK; break; case Person.T_GVEE_DRIVER: case Person.T_VEE_GUNNER: case Person.T_VTOL_PILOT: return; case Person.T_AERO_PILOT: if (!c.getCampaignOptions().getAeroRecruitsHaveUnits()) { return; } unitType = UnitType.AERO; break; case Person.T_INFANTRY: unitType = UnitType.INFANTRY; break; case Person.T_BA: unitType = UnitType.BATTLE_ARMOR; break; case Person.T_PROTO_PILOT: unitType = UnitType.PROTOMEK; break; default: return; } int weight = -1; if (unitType == UnitType.MEK || unitType == UnitType.TANK || unitType == UnitType.AERO) { int roll = Compute.d6(2); if (roll < 8) { return; } if (roll < 10) { weight = EntityWeightClass.WEIGHT_LIGHT; } else if (roll < 12) { weight = EntityWeightClass.WEIGHT_MEDIUM; } else { weight = EntityWeightClass.WEIGHT_HEAVY; } } Entity en = null; String faction = getRecruitFaction(c); MechSummary ms = c.getUnitGenerator().generate(faction, unitType, weight, c.getCalendar().get(Calendar.YEAR), IUnitRating.DRAGOON_F); if (null != ms) { if (Faction.getFaction(faction).isClan() && ms.getName().matches(".*Platoon.*")) { String name = "Clan " + ms.getName().replaceAll("Platoon", "Point"); ms = MechSummaryCache.getInstance().getMech(name); System.out.println("looking for Clan infantry " + name); } try { en = new MechFileParser(ms.getSourceFile(), ms.getEntryName()).getEntity(); } catch (EntityLoadingException ex) { en = null; MekHQ.logError("Unable to load entity: " + ms.getSourceFile() + ": " + ms.getEntryName() + ": " + ex.getMessage()); MekHQ.logError(ex); } } else { MekHQ.logError("Personnel market could not find " + UnitType.getTypeName(unitType) + " for recruit from faction " + faction); return; } if (null != en) { attachedEntities.put(p.getId(), en); /* adjust vehicle pilot roles according to the type of vehicle rolled */ if ((en.getEntityType() & Entity.ETYPE_TANK) != 0) { if (en.getMovementMode() == EntityMovementMode.TRACKED || en.getMovementMode() == EntityMovementMode.WHEELED || en.getMovementMode() == EntityMovementMode.HOVER || en.getMovementMode() == EntityMovementMode.WIGE) { if (p.getPrimaryRole() == Person.T_VTOL_PILOT) { swapSkills(p, SkillType.S_PILOT_VTOL, SkillType.S_PILOT_GVEE); p.setPrimaryRole(Person.T_GVEE_DRIVER); } if (p.getPrimaryRole() == Person.T_NVEE_DRIVER) { swapSkills(p, SkillType.S_PILOT_NVEE, SkillType.S_PILOT_GVEE); p.setPrimaryRole(Person.T_GVEE_DRIVER); } } else if (en.getMovementMode() == EntityMovementMode.VTOL) { if (p.getPrimaryRole() == Person.T_GVEE_DRIVER) { swapSkills(p, SkillType.S_PILOT_GVEE, SkillType.S_PILOT_VTOL); p.setPrimaryRole(Person.T_VTOL_PILOT); } if (p.getPrimaryRole() == Person.T_NVEE_DRIVER) { swapSkills(p, SkillType.S_PILOT_NVEE, SkillType.S_PILOT_VTOL); p.setPrimaryRole(Person.T_VTOL_PILOT); } } else if (en.getMovementMode() == EntityMovementMode.NAVAL || en.getMovementMode() == EntityMovementMode.HYDROFOIL || en.getMovementMode() == EntityMovementMode.SUBMARINE) { if (p.getPrimaryRole() == Person.T_GVEE_DRIVER) { swapSkills(p, SkillType.S_PILOT_GVEE, SkillType.S_PILOT_NVEE); p.setPrimaryRole(Person.T_NVEE_DRIVER); } if (p.getPrimaryRole() == Person.T_VTOL_PILOT) { swapSkills(p, SkillType.S_PILOT_VTOL, SkillType.S_PILOT_NVEE); p.setPrimaryRole(Person.T_NVEE_DRIVER); } } } } } private void swapSkills(Person p, String skill1, String skill2) { int s1 = p.hasSkill(skill1)?p.getSkill(skill1).getLevel():0; int b1 = p.hasSkill(skill1)?p.getSkill(skill1).getBonus():0; int s2 = p.hasSkill(skill2)?p.getSkill(skill2).getLevel():0; int b2 = p.hasSkill(skill2)?p.getSkill(skill2).getBonus():0; p.addSkill(skill1, s2, b2); p.addSkill(skill2, s1, b1); if (p.getSkill(skill1).getLevel() == 0) { p.removeSkill(skill1); } if (p.getSkill(skill2).getLevel() == 0) { p.removeSkill(skill2); } } public void adjustSkill (Person p, String skillName, int mod) { if (p.getSkill(skillName) == null) { return; } if (mod > 0) { p.improveSkill(skillName); } if (mod < 0) { int lvl = p.getSkill(skillName).getLevel() + mod; p.getSkill(skillName).setLevel(Math.max(lvl, 0)); } } public static String getRecruitFaction(Campaign c) { if (c.getFactionCode().equals("MERC")) { if (c.getCalendar().get(Calendar.YEAR) > 3055 && Compute.randomInt(20) == 0) { ArrayList<String> clans = new ArrayList<String>(); for (String f : RandomFactionGenerator.getInstance().getCurrentFactions()) { if (Faction.getFaction(f).isClan()) { clans.add(f); } } String clan = Utilities.getRandomItem(clans); if (clan != null) { return clan; } } else { String faction = RandomFactionGenerator.getInstance().getEmployer(); if (faction != null) { return faction; } } } return c.getFactionCode(); } }