/*
* Person.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.personnel;
import java.io.PrintWriter;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.EnumMap;
import java.util.Enumeration;
import java.util.GregorianCalendar;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.UUID;
import java.util.Vector;
import java.util.function.IntSupplier;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.joda.time.DateTime;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import megamek.common.Aero;
import megamek.common.BattleArmor;
import megamek.common.Compute;
import megamek.common.ConvFighter;
import megamek.common.Crew;
import megamek.common.Dropship;
import megamek.common.Entity;
import megamek.common.EntityMovementMode;
import megamek.common.Infantry;
import megamek.common.Jumpship;
import megamek.common.Mech;
import megamek.common.Protomech;
import megamek.common.SmallCraft;
import megamek.common.Tank;
import megamek.common.TargetRoll;
import megamek.common.VTOL;
import megamek.common.annotations.Nullable;
import megamek.common.options.IOption;
import megamek.common.options.IOptionGroup;
import megamek.common.options.PilotOptions;
import mekhq.MekHQ;
import mekhq.MekHqXmlSerializable;
import mekhq.MekHqXmlUtil;
import mekhq.Utilities;
import mekhq.Version;
import mekhq.campaign.Campaign;
import mekhq.campaign.CampaignOptions;
import mekhq.campaign.ExtraData;
import mekhq.campaign.LogEntry;
import mekhq.campaign.event.PersonChangedEvent;
import mekhq.campaign.mod.am.InjuryUtil;
import mekhq.campaign.unit.Unit;
import mekhq.campaign.work.IAcquisitionWork;
import mekhq.campaign.work.IPartWork;
/**
* @author Jay Lawson <jaylawson39 at yahoo.com>
*/
public class Person implements Serializable, MekHqXmlSerializable {
private static final long serialVersionUID = -847642980395311152L;
public static final int G_MALE = 0;
public static final int G_FEMALE = 1;
public static final int T_NONE = 0;
public static final int T_MECHWARRIOR = 1;
public static final int T_AERO_PILOT = 2;
public static final int T_GVEE_DRIVER = 3;
public static final int T_NVEE_DRIVER = 4;
public static final int T_VTOL_PILOT = 5;
public static final int T_VEE_GUNNER = 6;
public static final int T_BA = 7;
public static final int T_INFANTRY = 8;
public static final int T_PROTO_PILOT = 9;
public static final int T_CONV_PILOT = 10;
public static final int T_SPACE_PILOT = 11;
public static final int T_SPACE_CREW = 12;
public static final int T_SPACE_GUNNER = 13;
public static final int T_NAVIGATOR = 14;
public static final int T_MECH_TECH = 15;
public static final int T_MECHANIC = 16;
public static final int T_AERO_TECH = 17;
public static final int T_BA_TECH = 18;
public static final int T_ASTECH = 19;
public static final int T_DOCTOR = 20;
public static final int T_MEDIC = 21;
public static final int T_ADMIN_COM = 22;
public static final int T_ADMIN_LOG = 23;
public static final int T_ADMIN_TRA = 24;
public static final int T_ADMIN_HR = 25;
public static final int T_NUM = 26;
public static final int S_ACTIVE = 0;
public static final int S_RETIRED = 1;
public static final int S_KIA = 2;
public static final int S_MIA = 3;
public static final int S_NUM = 4;
public static final int PRONOUN_HESHE = 0;
public static final int PRONOUN_HIMHER = 1;
public static final int PRONOUN_HISHER = 2;
public static final int PRONOUN_HISHERS = 3;
// Prisoners, Bondsmen, and Normal Personnel
public static final int PRISONER_NOT = 0;
public static final int PRISONER_YES = 1;
public static final int PRISONER_BONDSMAN = 2;
public static final int PRISONER_NUM = 3;
// Phenotypes
public static final int PHENOTYPE_NONE = 0;
public static final int PHENOTYPE_MW = 1;
public static final int PHENOTYPE_BA = 2;
public static final int PHENOTYPE_AERO = 3;
public static final int PHENOTYPE_VEE = 4;
public static final int PHENOTYPE_NUM = 5;
// ROM Designations
public static final int DESIG_NONE = 0;
public static final int DESIG_EPSILON = 1;
public static final int DESIG_PI = 2;
public static final int DESIG_IOTA = 3;
public static final int DESIG_XI = 4;
public static final int DESIG_THETA = 5;
public static final int DESIG_ZETA = 6;
public static final int DESIG_MU = 7;
public static final int DESIG_RHO = 8;
public static final int DESIG_LAMBDA = 9;
public static final int DESIG_PSI = 10;
public static final int DESIG_OMICRON = 11;
public static final int DESIG_CHI = 12;
public static final int DESIG_GAMMA = 13;
public static final int DESIG_NUM = 14;
public static final String LOGTYPE_MEDICAL = "med";
private static final IntSupplier PREGNANCY_DURATION = () -> {
double gaussian = Math.sqrt(-2 * Math.log(Math.nextUp(Math.random())))
* Math.cos(2.0 * Math.PI * Math.random());
// To not get weird results, we limit the values to +/- 4.0 (almost 6 weeks)
gaussian = Math.max(-4.0, Math.min(4.0, gaussian));
return (int) Math.round(gaussian * 10 + 38 * 7);
};
private static final IntSupplier PREGNANCY_SIZE = () -> {
int children = 1;
// Hellin's law says it's 1:89 chance, to not make it appear too seldom, we use 1:50
while(Compute.randomInt(50) == 0) {
++ children;
}
return Math.min(children, 8); // Limit to octuplets, for the sake of sanity
};
private static final String[] PREGNANCY_MULTIPLE_NAMES = {null, null,
"twins", "triplets", "quadruplets", "quintuplets",
"sextuplets", "septuplets", "octuplets", "nonuplets", "decuplets"
};
public static final ExtraData.IntKey PREGNANCY_CHILDREN_DATA
= new ExtraData.IntKey("procreation:children");
public static final ExtraData.StringKey PREGNANCY_FATHER_DATA
= new ExtraData.StringKey("procreation:father");
protected UUID id;
protected int oldId;
// Lineage & Procreation
protected UUID ancestorsID;
protected UUID spouse;
protected GregorianCalendar dueDate;
private String name;
private String maidenname;
private String callsign;
private int gender;
private int primaryRole;
private int secondaryRole;
private int primaryDesignator;
private int secondaryDesignator;
protected String biography;
protected GregorianCalendar birthday;
protected GregorianCalendar deathday;
protected ArrayList<LogEntry> personnelLog;
private Hashtable<String, Skill> skills;
private PilotOptions options = new PilotOptions();
private Hashtable<String, SpecialAbility> spas = new Hashtable<String, SpecialAbility>();
private int toughness;
private int status;
protected int xp;
protected int acquisitions;
protected int salary;
private int hits;
private int prisonerStatus;
// Is this person willing to defect? Only for prisoners ...
private boolean willingToDefect;
boolean dependent;
boolean commander;
//phenotype and background
private int phenotype;
private boolean clan;
private String bloodname;
//assignments
private UUID unitId;
protected UUID doctorId;
private ArrayList<UUID> techUnitIds;
//for reverse compatability v0.1.8 and earlier
protected int teamId = -1;
//for reverse compatability
private int oldUnitId;
private int oldDoctorId;
//days of rest
protected int idleMonths;
protected int daysToWaitForHealing;
//portrait
protected String portraitCategory;
protected String portraitFile;
// runtime override (not saved)
protected transient String portraitCategoryOverride;
protected transient String portraitFileOverride;
// Our rank
private int rank;
private int rankLevel = 0;
// If this Person uses a custom rank system (-1 for no)
private int rankSystem = -1;
private Ranks ranks;
// Manei Domini "Classes"
public static final int MD_NONE = 0;
public static final int MD_GHOST = 1;
public static final int MD_WRAITH = 2;
public static final int MD_BANSHEE = 3;
public static final int MD_ZOMBIE = 4;
public static final int MD_PHANTOM = 5;
public static final int MD_SPECTER = 6;
public static final int MD_POLTERGEIST = 7;
public static final int MD_NUM = 8;
private int maneiDominiClass = MD_NONE;
private int maneiDominiRank = Rank.MD_RANK_NONE;
//stuff to track for support teams
protected int minutesLeft;
protected int overtimeLeft;
protected int nTasks;
protected boolean engineer;
/**
* * Start Advanced Medical ***
*/
private ArrayList<Injury> injuries = new ArrayList<Injury>();
private Map<BodyLocation, Integer> hitsPerLocation = new EnumMap<>(BodyLocation.class);
/**
* * End Advanced Medical ***
*/
/* Against the Bot */
private boolean founder; // +1 share if using shares system
private int originalUnitWeight; // uses EntityWeightClass; 0 (Extra-Light) for no original unit
private int originalUnitTech; // 0 = IS1, 1 = IS2, 2 = Clan
private UUID originalUnitId;
// Generic extra data, for use with plugins and mods
private ExtraData extraData = new ExtraData();
//lets just go ahead and pass in the campaign - to hell with OOP
private Campaign campaign;
//default constructor
public Person(Campaign c) {
this("Biff the Understudy", c);
}
public Person(String name, Campaign c) {
this.name = name;
callsign = "";
portraitCategory = Crew.ROOT_PORTRAIT;
portraitFile = Crew.PORTRAIT_NONE;
portraitCategoryOverride = null;
portraitFileOverride = null;
xp = 0;
acquisitions = 0;
gender = G_MALE;
birthday = new GregorianCalendar(3042, Calendar.JANUARY, 1);
rank = 0;
rankLevel = 0;
status = S_ACTIVE;
hits = 0;
skills = new Hashtable<String, Skill>();
salary = -1;
campaign = c;
doctorId = null;
unitId = null;
oldDoctorId = -1;
oldUnitId = -1;
toughness = 0;
biography = "";
nTasks = 0;
personnelLog = new ArrayList<LogEntry>();
idleMonths = -1;
daysToWaitForHealing = 15;
resetMinutesLeft();
prisonerStatus = PRISONER_NOT;
dependent = false;
commander = false;
techUnitIds = new ArrayList<UUID>();
salary = -1;
phenotype = PHENOTYPE_NONE;
clan = campaign.getFaction() != null && campaign.getFaction().isClan();
bloodname = "";
primaryDesignator = DESIG_NONE;
secondaryDesignator = DESIG_NONE;
}
public int getPhenotype() {
return phenotype;
}
public void setPhenotype(int i) {
phenotype = i;
}
public boolean isClanner() {
return clan;
}
public void setClanner(boolean b) {
clan = b;
}
public String getBackgroundName() {
if (isClanner()) {
return getPhenotypeName();
} else {
return "Inner Sphere";
}
}
public String getBloodname() {
return bloodname;
}
public void setBloodname(String bn) {
bloodname = bn;
}
public boolean isCommander() {
return commander;
}
public void setCommander(boolean tf) {
commander = tf;
}
public boolean isDependent() {
return dependent;
}
public void setDependent(boolean tf) {
dependent = tf;
}
public boolean isPrisoner() {
if (prisonerStatus == PRISONER_YES) {
return true;
}
return false;
}
public void setPrisoner() {
prisonerStatus = PRISONER_YES;
setRankNumeric(Ranks.RANK_PRISONER);
}
public boolean isBondsman() {
if (prisonerStatus == PRISONER_BONDSMAN) {
return true;
}
return false;
}
public void setBondsman() {
prisonerStatus = PRISONER_BONDSMAN;
willingToDefect = false;
setRankNumeric(Ranks.RANK_BONDSMAN);
}
public boolean isFree() {
return (!isPrisoner() && !isBondsman());
}
public void setFreeMan() {
prisonerStatus = PRISONER_NOT;
willingToDefect = false;
}
public void setPrisonerStatus(int status) {
prisonerStatus = status;
if( prisonerStatus != PRISONER_YES ) {
willingToDefect = false;
}
}
public int getPrisonerStatus() {
return prisonerStatus;
}
public boolean isWillingToDefect() {
return willingToDefect;
}
public void setWillingToDefect(boolean willingToDefect) {
this.willingToDefect = willingToDefect && (prisonerStatus == PRISONER_YES);
}
public String getGenderName() {
return getGenderName(gender);
}
public static String getGenderName(int gender) {
switch (gender) {
case G_MALE:
return "Male";
case G_FEMALE:
return "Female";
default:
return "?";
}
}
public String getGenderPronoun(int variant) {
return getGenderPronoun(gender, variant);
}
public static String getGenderPronoun(int gender, int variant) {
if (variant == PRONOUN_HESHE) {
switch (gender) {
case G_MALE:
return "he";
case G_FEMALE:
return "she";
default:
return "?";
}
} else if (variant == PRONOUN_HIMHER) {
switch (gender) {
case G_MALE:
return "him";
case G_FEMALE:
return "her";
default:
return "?";
}
} else if (variant == PRONOUN_HISHER) {
switch (gender) {
case G_MALE:
return "his";
case G_FEMALE:
return "her";
default:
return "?";
}
} else if (variant == PRONOUN_HISHERS) {
switch (gender) {
case G_MALE:
return "his";
case G_FEMALE:
return "hers";
default:
return "?";
}
} else {
return "UNKNOWN ERROR IN GENDER PRONOUN";
}
}
public static String getStatusName(int status) {
switch (status) {
case S_ACTIVE:
return "Active";
case S_RETIRED:
return "Retired";
case S_KIA:
return "Killed in Action";
case S_MIA:
return "Missing in Action";
default:
return "?";
}
}
public String pregnancyStatus() {
return isPregnant() ? " (Pregnant)" : "";
}
public String getStatusName() {
return getStatusName(status);
}
public static String getPhenotypeName(int pheno) {
switch (pheno) {
case PHENOTYPE_NONE:
return "Freeborn";
case PHENOTYPE_MW:
return "Trueborn Mechwarrior";
case PHENOTYPE_AERO:
return "Trueborn Pilot";
case PHENOTYPE_VEE:
return "Trueborn Vehicle Crew";
case PHENOTYPE_BA:
return "Trueborn Elemental";
default:
return "?";
}
}
public static String getPhenotypeShortName(int pheno) {
switch (pheno) {
case PHENOTYPE_NONE:
return "Freeborn";
case PHENOTYPE_MW:
case PHENOTYPE_AERO:
case PHENOTYPE_VEE:
case PHENOTYPE_BA:
return "Trueborn";
default:
return "?";
}
}
public String getPhenotypeName() {
return getPhenotypeName(phenotype);
}
public String getPhenotypeShortName() {
return getPhenotypeShortName(phenotype);
}
public static String getPrisonerStatusName(int status) {
switch (status) {
case PRISONER_NOT:
return "Free";
case PRISONER_YES:
return "Prisoner";
case PRISONER_BONDSMAN:
return "Bondsman";
default:
return "?";
}
}
public String getPrisonerStatusName() {
return getPrisonerStatusName(prisonerStatus);
}
public String getName() {
return name;
}
public void setName(String n) {
this.name = n;
}
public String getMaidenName() {
return maidenname;
}
public void setMaidenName(String n) {
this.maidenname = n;
}
public String getFullName() {
if (bloodname.length() > 0) {
return name + " " + bloodname;
}
return name;
}
public String getHyperlinkedName() {
return "<a href='PERSON:" + getId() + "'>" + getFullName() + "</a>";
}
public String getCallsign() {
return callsign;
}
public void setCallsign(String n) {
this.callsign = n;
}
public String getPortraitCategory() {
return Utilities.nonNull(portraitCategoryOverride, portraitCategory);
}
public String getPortraitFileName() {
return Utilities.nonNull(portraitFileOverride, portraitFile);
}
public void setPortraitCategory(String s) {
this.portraitCategory = s;
}
public void setPortraitFileName(String s) {
this.portraitFile = s;
}
public void setPortraitCategoryOverride(String s) {
this.portraitCategoryOverride = s;
}
public void setPortraitFileNameOverride(String s) {
this.portraitFileOverride = s;
}
public int getPrimaryRole() {
return primaryRole;
}
public void setPrimaryRole(int t) {
this.primaryRole = t;
//you cant be primary tech and a secondary astech
//you cant be a primary astech and a secondary tech
if ((isTechPrimary() && secondaryRole == T_ASTECH)
|| (isTechSecondary() && primaryRole == T_ASTECH)) {
secondaryRole = T_NONE;
}
if ((primaryRole == T_DOCTOR && secondaryRole == T_MEDIC)
|| (secondaryRole == T_DOCTOR && primaryRole == T_MEDIC)) {
secondaryRole = T_NONE;
}
MekHQ.triggerEvent(new PersonChangedEvent(this));
}
public int getSecondaryRole() {
return secondaryRole;
}
public void setSecondaryRole(int t) {
this.secondaryRole = t;
MekHQ.triggerEvent(new PersonChangedEvent(this));
}
public int getStatus() {
return status;
}
public void setStatus(int s) {
this.status = s;
}
public int getIdleMonths() {
return idleMonths;
}
public void setIdleMonths(int m) {
this.idleMonths = m;
}
public int getDaysToWaitForHealing() {
return daysToWaitForHealing;
}
public void setDaysToWaitForHealing(int d) {
this.daysToWaitForHealing = d;
}
public static String getRoleDesc(int type, boolean clan) {
switch (type) {
case (T_NONE):
return "None";
case (T_MECHWARRIOR):
return "Mechwarrior";
case (T_GVEE_DRIVER):
return "Vehicle Driver";
case (T_NVEE_DRIVER):
return "Naval Driver";
case (T_VTOL_PILOT):
return "VTOL Pilot";
case (T_VEE_GUNNER):
return "Vehicle Gunner";
case (T_CONV_PILOT):
return "Conventional Aircraft Pilot";
case (T_AERO_PILOT):
return "Aero Pilot";
case (T_PROTO_PILOT):
return "Proto Pilot";
case (T_BA):
if (clan) {
return "Elemental";
} else {
return "Battle Armor Pilot";
}
case (T_INFANTRY):
return "Soldier";
case (T_SPACE_PILOT):
return "Vessel Pilot";
case (T_SPACE_CREW):
return "Vessel Crewmember";
case (T_SPACE_GUNNER):
return "Vessel Gunner";
case (T_NAVIGATOR):
return "Hyperspace Navigator";
case (T_MECH_TECH):
return "Mech Tech";
case (T_MECHANIC):
return "Mechanic";
case (T_AERO_TECH):
return "Aero Tech";
case (T_BA_TECH):
return "Battle Armor Tech";
case (T_ASTECH):
return "Astech";
case (T_DOCTOR):
return "Doctor";
case (T_MEDIC):
return "Medic";
case (T_ADMIN_COM):
return "Admin/Command";
case (T_ADMIN_LOG):
return "Admin/Logistical";
case (T_ADMIN_TRA):
return "Admin/Transport";
case (T_ADMIN_HR):
return "Admin/HR";
default:
return "??";
}
}
public String getRoleDesc() {
String role = getPrimaryRoleDesc();
if (secondaryRole != T_NONE && secondaryRole != -1) {
role += "/" + getSecondaryRoleDesc();
}
return role;
}
public String getPrimaryRoleDesc() {
String bgPrefix = "";
if (isClanner()) {
bgPrefix = getPhenotypeShortName() + " ";
}
return bgPrefix + getRoleDesc(primaryRole, campaign.getFaction().isClan());
}
public String getSecondaryRoleDesc() {
return getRoleDesc(secondaryRole, campaign.getFaction().isClan());
}
public boolean canPerformRole(int role) {
switch (role) {
case (T_NONE):
return true;
case (T_MECHWARRIOR):
return hasSkill(SkillType.S_GUN_MECH) && hasSkill(SkillType.S_PILOT_MECH);
case (T_GVEE_DRIVER):
return hasSkill(SkillType.S_PILOT_GVEE);
case (T_NVEE_DRIVER):
return hasSkill(SkillType.S_PILOT_NVEE);
case (T_VTOL_PILOT):
return hasSkill(SkillType.S_PILOT_VTOL);
case (T_VEE_GUNNER):
return hasSkill(SkillType.S_GUN_VEE);
case (T_AERO_PILOT):
return hasSkill(SkillType.S_GUN_AERO) && hasSkill(SkillType.S_PILOT_AERO);
case (T_CONV_PILOT):
return hasSkill(SkillType.S_GUN_JET) && hasSkill(SkillType.S_PILOT_JET);
case (T_PROTO_PILOT):
return hasSkill(SkillType.S_GUN_PROTO);
case (T_BA):
return hasSkill(SkillType.S_GUN_BA);
case (T_INFANTRY):
return hasSkill(SkillType.S_SMALL_ARMS);
case (T_SPACE_PILOT):
return hasSkill(SkillType.S_PILOT_SPACE);
case (T_SPACE_CREW):
return hasSkill(SkillType.S_TECH_VESSEL);
case (T_SPACE_GUNNER):
return hasSkill(SkillType.S_GUN_SPACE);
case (T_NAVIGATOR):
return hasSkill(SkillType.S_NAV);
case (T_MECH_TECH):
return hasSkill(SkillType.S_TECH_MECH) && getSkill(SkillType.S_TECH_MECH).getExperienceLevel() > SkillType.EXP_ULTRA_GREEN;
case (T_MECHANIC):
return hasSkill(SkillType.S_TECH_MECHANIC) && getSkill(SkillType.S_TECH_MECHANIC).getExperienceLevel() > SkillType.EXP_ULTRA_GREEN;
case (T_AERO_TECH):
return hasSkill(SkillType.S_TECH_AERO) && getSkill(SkillType.S_TECH_AERO).getExperienceLevel() > SkillType.EXP_ULTRA_GREEN;
case (T_BA_TECH):
return hasSkill(SkillType.S_TECH_BA) && getSkill(SkillType.S_TECH_BA).getExperienceLevel() > SkillType.EXP_ULTRA_GREEN;
case (T_ASTECH):
return hasSkill(SkillType.S_ASTECH);
case (T_DOCTOR):
return hasSkill(SkillType.S_DOCTOR) && getSkill(SkillType.S_DOCTOR).getExperienceLevel() > SkillType.EXP_ULTRA_GREEN;
case (T_MEDIC):
return hasSkill(SkillType.S_MEDTECH);
case (T_ADMIN_COM):
case (T_ADMIN_LOG):
case (T_ADMIN_TRA):
case (T_ADMIN_HR):
return hasSkill(SkillType.S_ADMIN);
default:
return false;
}
}
public void setGender(int g) {
this.gender = g;
}
public int getGender() {
return gender;
}
public void setBirthday(GregorianCalendar date) {
this.birthday = date;
}
public GregorianCalendar getBirthday() {
return birthday;
}
public GregorianCalendar getDeathday() {
return deathday;
}
public void setDeathday(GregorianCalendar date) {
this.deathday = date;
}
public int getAge(GregorianCalendar today) {
// Get age based on year
if (null != deathday) {
//use deathday instead of birthdate
today = deathday;
}
int age = today.get(Calendar.YEAR) - birthday.get(Calendar.YEAR);
// Add the tentative age to the date of birth to get this year's birthday
GregorianCalendar tmpDate = (GregorianCalendar) birthday.clone();
tmpDate.add(Calendar.YEAR, age);
// If this year's birthday has not happened yet, subtract one from age
if (today.before(tmpDate)) {
age--;
}
return age;
}
public void setId(UUID id) {
this.id = id;
}
public UUID getId() {
return id;
}
public UUID getSpouseID() {
return spouse;
}
public void setSpouseID(UUID spouse) {
this.spouse = spouse;
}
public Person getSpouse() {
return campaign.getPerson(spouse);
}
public GregorianCalendar getDueDate() {
return dueDate;
}
public void setDueDate(GregorianCalendar dueDate) {
this.dueDate = dueDate;
}
public boolean isPregnant() {
return dueDate != null;
}
public UUID getAncestorsID() {
return ancestorsID;
}
public void setAncestorsID(UUID id) {
ancestorsID = id;
}
public Ancestors getAncestors() {
return campaign.getAncestors(ancestorsID);
}
public Person getMother() {
return campaign.getPerson(getAncestors().getMotherID());
}
public Person getFather() {
return campaign.getPerson(getAncestors().getFatherID());
}
public Collection<Person> birth() {
int size = extraData.get(PREGNANCY_CHILDREN_DATA, 1);
String fatherIDString = extraData.get(PREGNANCY_FATHER_DATA);
UUID fatherID = (null != fatherIDString) ? UUID.fromString(fatherIDString) : getSpouseID();
Ancestors anc = campaign.getAncestors(fatherID, id);
if(null == anc) {
anc = campaign.createAncestors(fatherID, id);
}
final UUID ancId = anc.getId();
final String surname = getName().contains(" ") ? getName().split(" ", 2)[1] : "";
// Cleanup
setDueDate(null);
extraData.set(PREGNANCY_CHILDREN_DATA, 0);
extraData.set(PREGNANCY_FATHER_DATA, null);
return IntStream.range(0, size).mapToObj(i -> {
Person baby = campaign.newPerson(T_NONE);
baby.setDependent(true);
baby.setName(baby.getName().split(" ", 2)[0] + " " + surname);
baby.setBirthday((GregorianCalendar) campaign.getCalendar().clone());
UUID babyId = UUID.randomUUID();
while (null != campaign.getPerson(babyId)) {
babyId = UUID.randomUUID();
}
baby.setId(babyId);
baby.setAncestorsID(ancId);
campaign.addReport(getHyperlinkedName() + " has given birth to " + baby.getHyperlinkedName() + ", a baby " + (baby.getGender() == G_MALE ? "boy!" : "girl!"));
if (campaign.getCampaignOptions().logConception()) {
addLogEntry(campaign.getDate(), "Delivered a healthy baby " + (baby.getGender() == G_MALE ? "boy!" : "girl!"));
}
return baby;
}).collect(Collectors.toList());
}
public void procreate() {
if(!isFemale() || isPregnant()) {
return;
}
// Spouse NULL protection...
if((getSpouseID() != null) && (getSpouse() == null)) {
setSpouseID(null);
}
if (!isDeployed()) {
// Age limitations...
if (getAge(campaign.getCalendar()) > 13 && getAge(campaign.getCalendar()) < 51) {
boolean concieved = false;
if (getSpouse() == null && campaign.getCampaignOptions().useUnofficialProcreationNoRelationship()) {
// 0.005% chance that this procreation attempt will create a child
concieved = (Compute.randomInt(100000) < 2);
} else if (getSpouse() != null) {
if (getSpouse().isActive() && !getSpouse().isDeployed() && getSpouse().getAge(campaign.getCalendar()) > 13) {
// 0.05% chance that this procreation attempt will create a child
concieved = (Compute.randomInt(10000) < 2);
}
}
if(concieved) {
GregorianCalendar tCal = (GregorianCalendar) campaign.getCalendar().clone();
tCal.add(GregorianCalendar.DAY_OF_YEAR, PREGNANCY_DURATION.getAsInt());
setDueDate(tCal);
int size = PREGNANCY_SIZE.getAsInt();
extraData.set(PREGNANCY_CHILDREN_DATA, size);
extraData.set(PREGNANCY_FATHER_DATA,
(null != getSpouseID()) ? getSpouseID().toString() : null);
String sizeString = (size < PREGNANCY_MULTIPLE_NAMES.length) ? PREGNANCY_MULTIPLE_NAMES[size] : null;
if(null == sizeString) {
campaign.addReport(getHyperlinkedName()+" has conceived");
if (campaign.getCampaignOptions().logConception()) {
addLogEntry(campaign.getDate(), "Has conceived");
}
} else {
campaign.addReport(getHyperlinkedName()+" has conceived " + sizeString);
if (campaign.getCampaignOptions().logConception()) {
addLogEntry(campaign.getDate(), "Has conceived " + sizeString);
}
}
}
}
}
}
public void addPregnancy() {
GregorianCalendar tCal = (GregorianCalendar) campaign.getCalendar().clone();
tCal.add(GregorianCalendar.DAY_OF_YEAR, PREGNANCY_DURATION.getAsInt());
setDueDate(tCal);
int size = PREGNANCY_SIZE.getAsInt();
extraData.set(PREGNANCY_CHILDREN_DATA, size);
extraData.set(PREGNANCY_FATHER_DATA,
(null != getSpouseID()) ? getSpouseID().toString() : null);
String sizeString = (size < PREGNANCY_MULTIPLE_NAMES.length) ? PREGNANCY_MULTIPLE_NAMES[size] : null;
if(null == sizeString) {
campaign.addReport(getHyperlinkedName()+" has conceived");
if (campaign.getCampaignOptions().logConception()) {
addLogEntry(campaign.getDate(), "Has conceived");
}
} else {
campaign.addReport(getHyperlinkedName()+" has conceived " + sizeString);
if (campaign.getCampaignOptions().logConception()) {
addLogEntry(campaign.getDate(), "Has conceived " + sizeString);
}
}
}
public void removePregnancy() {
setDueDate(null);
extraData.set(PREGNANCY_CHILDREN_DATA, 0);
extraData.set(PREGNANCY_FATHER_DATA, null);
}
public boolean safeSpouse(Person p) {
// Huge convoluted return statement
return (
!this.equals(p)
&& (getAncestorsID() == null
|| campaign.getAncestors(getAncestorsID()).checkMutualAncestors(campaign.getAncestors(p.getAncestorsID())))
&& p.getSpouseID() == null
&& getGender() != p.getGender()
&& p.getAge(campaign.getCalendar()) > 13
);
}
public boolean isFemale() {
return gender == G_FEMALE;
}
public boolean isMale() {
return gender == G_MALE;
}
// Currently this isn't used
public boolean isNeuter() {
return !isMale() && !isFemale();
}
public int getXp() {
return xp;
}
public void setXp(int xp) {
this.xp = xp;
}
public void awardXP(int xp) {
this.xp += xp;
}
public int getAcquisitions() {
return acquisitions;
}
public void setAcquisition(int a) {
acquisitions = a;
}
public void incrementAcquisition() {
acquisitions++;
}
public void setDoctorId(UUID t, int daysToWait) {
this.doctorId = t;
this.daysToWaitForHealing = daysToWait;
}
public boolean checkNaturalHealing(int daysToWait) {
if (needsFixing() && daysToWaitForHealing <= 0 && doctorId == null) {
heal();
daysToWaitForHealing = daysToWait;
return true;
}
return false;
}
public void decrementDaysToWaitForHealing() {
if (daysToWaitForHealing > 0) {
daysToWaitForHealing--;
}
}
public boolean isDeployed() {
Unit u = campaign.getUnit(unitId);
if (null != u) {
return u.getScenarioId() != -1;
}
return false;
}
public String getBiography() {
return biography;
}
public void setBiography(String s) {
this.biography = s;
}
public boolean isActive() {
return getStatus() == S_ACTIVE;
}
public boolean isInActive() {
return getStatus() != S_ACTIVE;
}
public ExtraData getExtraData() {
return extraData;
}
@Override
public void writeToXml(PrintWriter pw1, int indent) {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
pw1.println(MekHqXmlUtil.indentStr(indent) + "<person id=\""
+ id.toString()
+ "\" type=\""
+ this.getClass().getName()
+ "\">");
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<name>"
+ MekHqXmlUtil.escape(name)
+ "</name>");
if (maidenname != null) {
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<maidenname>"
+ MekHqXmlUtil.escape(maidenname)
+ "</maidenname>");
}
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<callsign>"
+ MekHqXmlUtil.escape(callsign)
+ "</callsign>");
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<primaryRole>"
+ primaryRole
+ "</primaryRole>");
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<secondaryRole>"
+ secondaryRole
+ "</secondaryRole>");
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<primaryDesignator>"
+ primaryDesignator
+ "</primaryDesignator>");
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<secondaryDesignator>"
+ secondaryDesignator
+ "</secondaryDesignator>");
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<commander>"
+ MekHqXmlUtil.escape(Boolean.toString(commander))
+ "</commander>");
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<dependent>"
+ MekHqXmlUtil.escape(Boolean.toString(dependent))
+ "</dependent>");
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<clan>"
+ clan
+ "</clan>");
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<phenotype>"
+ phenotype
+ "</phenotype>");
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<bloodname>"
+ bloodname
+ "</bloodname>");
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<biography>"
+ MekHqXmlUtil.escape(biography)
+ "</biography>");
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<idleMonths>"
+ idleMonths
+ "</idleMonths>");
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<id>"
+ this.id.toString()
+ "</id>");
if (ancestorsID != null) {
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<ancestors>"
+ this.ancestorsID.toString()
+ "</ancestors>");
}
if (spouse != null) {
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<spouse>"
+ this.spouse.toString()
+ "</spouse>");
}
if (dueDate != null) {
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<dueDate>"
+ df.format(dueDate.getTime())
+ "</dueDate>");
}
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<portraitCategory>"
+ MekHqXmlUtil.escape(portraitCategory)
+ "</portraitCategory>");
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<portraitFile>"
+ MekHqXmlUtil.escape(portraitFile)
+ "</portraitFile>");
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<xp>"
+ xp
+ "</xp>");
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<daysToWaitForHealing>"
+ daysToWaitForHealing
+ "</daysToWaitForHealing>");
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<gender>"
+ gender
+ "</gender>");
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<rank>"
+ rank
+ "</rank>");
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<rankLevel>"
+ rankLevel
+ "</rankLevel>");
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<rankSystem>"
+ rankSystem
+ "</rankSystem>");
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<maneiDominiRank>"
+ maneiDominiRank
+ "</maneiDominiRank>");
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<maneiDominiClass>"
+ maneiDominiClass
+ "</maneiDominiClass>");
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<nTasks>"
+ nTasks
+ "</nTasks>");
if (null != doctorId) {
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<doctorId>"
+ doctorId.toString()
+ "</doctorId>");
}
if (null != unitId) {
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<unitId>"
+ unitId.toString()
+ "</unitId>");
}
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<salary>"
+ salary
+ "</salary>");
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<status>"
+ status
+ "</status>");
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<prisonerstatus>"
+ prisonerStatus
+ "</prisonerstatus>");
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<willingToDefect>"
+ willingToDefect
+ "</willingToDefect>");
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<hits>"
+ hits
+ "</hits>");
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<toughness>"
+ toughness
+ "</toughness>");
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<minutesLeft>"
+ minutesLeft
+ "</minutesLeft>");
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<overtimeLeft>"
+ overtimeLeft
+ "</overtimeLeft>");
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<birthday>"
+ df.format(birthday.getTime())
+ "</birthday>");
if (null != deathday) {
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<deathday>"
+ df.format(deathday.getTime())
+ "</deathday>");
}
for (String skName : skills.keySet()) {
Skill skill = skills.get(skName);
skill.writeToXml(pw1, indent + 1);
}
if (countOptions(PilotOptions.LVL3_ADVANTAGES) > 0) {
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<advantages>"
+ String.valueOf(getOptionList("::", PilotOptions.LVL3_ADVANTAGES))
+ "</advantages>");
}
if (countOptions(PilotOptions.EDGE_ADVANTAGES) > 0) {
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<edge>"
+ String.valueOf(getOptionList("::", PilotOptions.EDGE_ADVANTAGES))
+ "</edge>");
}
if (countOptions(PilotOptions.MD_ADVANTAGES) > 0) {
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<implants>"
+ String.valueOf(getOptionList("::", PilotOptions.MD_ADVANTAGES))
+ "</implants>");
}
if (!techUnitIds.isEmpty()) {
pw1.println(MekHqXmlUtil.indentStr(indent + 1) + "<techUnitIds>");
for (UUID id : techUnitIds) {
pw1.println(MekHqXmlUtil.indentStr(indent + 2)
+ "<id>"
+ id.toString()
+ "</id>");
}
pw1.println(MekHqXmlUtil.indentStr(indent + 1) + "</techUnitIds>");
}
if (!personnelLog.isEmpty()) {
pw1.println(MekHqXmlUtil.indentStr(indent + 1) + "<personnelLog>");
for (LogEntry entry : personnelLog) {
entry.writeToXml(pw1, indent + 2);
}
pw1.println(MekHqXmlUtil.indentStr(indent + 1) + "</personnelLog>");
}
if (injuries.size() > 0) {
pw1.println(MekHqXmlUtil.indentStr(indent + 1) + "<injuries>");
for (Injury injury : injuries) {
injury.writeToXml(pw1, indent + 2);
}
pw1.println(MekHqXmlUtil.indentStr(indent + 1) + "</injuries>");
}
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<founder>"
+ founder
+ "</founder>");
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<originalUnitWeight>"
+ originalUnitWeight
+ "</originalUnitWeight>");
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<originalUnitTech>"
+ originalUnitTech
+ "</originalUnitTech>");
if (originalUnitId != null) {
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<originalUnitId>"
+ originalUnitId.toString()
+ "</originalUnitId>");
}
pw1.println(MekHqXmlUtil.indentStr(indent + 1)
+ "<acquisitions>"
+ acquisitions
+ "</acquisitions>");
if(null != extraData) {
extraData.writeToXml(pw1);
}
pw1.println(MekHqXmlUtil.indentStr(indent) + "</person>");
}
public static Person generateInstanceFromXML(Node wn, Campaign c, Version version) {
Person retVal = null;
try {
// Instantiate the correct child class, and call its parsing function.
retVal = new Person(c);
// Okay, now load Person-specific fields!
NodeList nl = wn.getChildNodes();
String advantages = null;
String edge = null;
String implants = null;
//backwards compatability
String pilotName = null;
String pilotNickname = null;
int pilotGunnery = -1;
int pilotPiloting = -1;
int pilotCommandBonus = -1;
int type = 0;
for (int x = 0; x < nl.getLength(); x++) {
Node wn2 = nl.item(x);
if (wn2.getNodeName().equalsIgnoreCase("name")) {
retVal.name = wn2.getTextContent();
} else if (wn2.getNodeName().equalsIgnoreCase("maidenname")) {
retVal.maidenname = wn2.getTextContent();
} else if (wn2.getNodeName().equalsIgnoreCase("callsign")) {
retVal.callsign = wn2.getTextContent();
} else if (wn2.getNodeName().equalsIgnoreCase("commander")) {
retVal.commander = Boolean.parseBoolean(wn2.getTextContent().trim());
} else if (wn2.getNodeName().equalsIgnoreCase("dependent")) {
retVal.dependent = Boolean.parseBoolean(wn2.getTextContent().trim());
} else if (wn2.getNodeName().equalsIgnoreCase("isClanTech")
|| wn2.getNodeName().equalsIgnoreCase("clan")) {
retVal.clan = Boolean.parseBoolean(wn2.getTextContent().trim());
} else if (wn2.getNodeName().equalsIgnoreCase("phenotype")) {
retVal.phenotype = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("bloodname")) {
retVal.bloodname = wn2.getTextContent();
} else if (wn2.getNodeName().equalsIgnoreCase("biography")) {
retVal.biography = wn2.getTextContent();
} else if (wn2.getNodeName().equalsIgnoreCase("primaryRole")) {
retVal.primaryRole = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("secondaryRole")) {
retVal.secondaryRole = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("acquisitions")) {
retVal.acquisitions = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("primaryDesignator")) {
retVal.primaryDesignator = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("secondaryDesignator")) {
retVal.secondaryDesignator = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("daysToWaitForHealing")) {
retVal.daysToWaitForHealing = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("idleMonths")) {
retVal.idleMonths = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("id")) {
if (version.getMajorVersion() == 0 && version.getMinorVersion() < 2 && version.getSnapshot() < 14) {
retVal.oldId = Integer.parseInt(wn2.getTextContent());
} else {
retVal.id = UUID.fromString(wn2.getTextContent());
}
} else if (wn2.getNodeName().equalsIgnoreCase("ancestors")) {
retVal.ancestorsID = UUID.fromString(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("spouse")) {
retVal.spouse = UUID.fromString(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("duedate")) {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
retVal.dueDate = (GregorianCalendar) GregorianCalendar.getInstance();
retVal.dueDate.setTime(df.parse(wn2.getTextContent().trim()));
} else if (wn2.getNodeName().equalsIgnoreCase("teamId")) {
retVal.teamId = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("portraitCategory")) {
retVal.setPortraitCategory(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("portraitFile")) {
retVal.setPortraitFileName(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("xp")) {
retVal.xp = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("nTasks")) {
retVal.nTasks = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("hits")) {
retVal.hits = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("gender")) {
retVal.gender = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("rank")) {
if (Version.versionCompare(version, "0.3.4-r1782")) {
RankTranslator rt = new RankTranslator(c);
try {
retVal.rank = rt.getNewRank(c.getRanks().getOldRankSystem(), Integer.parseInt(wn2.getTextContent()));
} catch (ArrayIndexOutOfBoundsException e) {
// Do nothing
}
} else {
retVal.rank = Integer.parseInt(wn2.getTextContent());
}
} else if (wn2.getNodeName().equalsIgnoreCase("rankLevel")) {
retVal.rankLevel = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("rankSystem")) {
retVal.setRankSystem(Integer.parseInt(wn2.getTextContent()));
} else if (wn2.getNodeName().equalsIgnoreCase("maneiDominiRank")) {
retVal.maneiDominiRank = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("maneiDominiClass")) {
retVal.maneiDominiClass = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("doctorId")) {
if (version.getMajorVersion() == 0 && version.getMinorVersion() < 2 && version.getSnapshot() < 14) {
retVal.oldDoctorId = Integer.parseInt(wn2.getTextContent());
} else {
if (!wn2.getTextContent().equals("null")) {
retVal.doctorId = UUID.fromString(wn2.getTextContent());
}
}
} else if (wn2.getNodeName().equalsIgnoreCase("unitId")) {
if (version.getMajorVersion() == 0 && version.getMinorVersion() < 2 && version.getSnapshot() < 14) {
retVal.oldUnitId = Integer.parseInt(wn2.getTextContent());
} else {
if (!wn2.getTextContent().equals("null")) {
retVal.unitId = UUID.fromString(wn2.getTextContent());
}
}
} else if (wn2.getNodeName().equalsIgnoreCase("status")) {
retVal.status = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("prisonerstatus")) {
retVal.prisonerStatus = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("willingToDefect")) {
retVal.willingToDefect = Boolean.parseBoolean(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("salary")) {
retVal.salary = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("minutesLeft")) {
retVal.minutesLeft = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("overtimeLeft")) {
retVal.overtimeLeft = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("birthday")) {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
retVal.birthday = (GregorianCalendar) GregorianCalendar.getInstance();
retVal.birthday.setTime(df.parse(wn2.getTextContent().trim()));
} else if (wn2.getNodeName().equalsIgnoreCase("deathday")) {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
retVal.deathday = (GregorianCalendar) GregorianCalendar.getInstance();
retVal.deathday.setTime(df.parse(wn2.getTextContent().trim()));
} else if (wn2.getNodeName().equalsIgnoreCase("advantages")) {
advantages = wn2.getTextContent();
} else if (wn2.getNodeName().equalsIgnoreCase("edge")) {
edge = wn2.getTextContent();
} else if (wn2.getNodeName().equalsIgnoreCase("implants")) {
implants = wn2.getTextContent();
} else if (wn2.getNodeName().equalsIgnoreCase("toughness")) {
retVal.toughness = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("pilotGunnery")) {
pilotGunnery = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("pilotPiloting")) {
pilotPiloting = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("pilotHits")) {
retVal.hits = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("pilotCommandBonus")) {
pilotCommandBonus = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("pilotName")) {
pilotName = wn2.getTextContent();
} else if (wn2.getNodeName().equalsIgnoreCase("pilotNickname")) {
pilotNickname = wn2.getTextContent();
} else if (wn2.getNodeName().equalsIgnoreCase("type")) {
type = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("skill")) {
Skill s = Skill.generateInstanceFromXML(wn2);
if (null != s && null != s.getType()) {
retVal.skills.put(s.getType().getName(), s);
}
} else if (wn2.getNodeName().equalsIgnoreCase("techUnitIds")) {
NodeList nl2 = wn2.getChildNodes();
for (int y = 0; y < nl2.getLength(); y++) {
Node wn3 = nl2.item(y);
// If it's not an element node, we ignore it.
if (wn3.getNodeType() != Node.ELEMENT_NODE) {
continue;
}
if (!wn3.getNodeName().equalsIgnoreCase("id")) {
// Error condition of sorts!
// Errr, what should we do here?
MekHQ.logMessage("Unknown node type not loaded in techUnitIds nodes: " + wn3.getNodeName());
continue;
}
retVal.techUnitIds.add(UUID.fromString(wn3.getTextContent()));
}
} else if (wn2.getNodeName().equalsIgnoreCase("personnelLog")) {
NodeList nl2 = wn2.getChildNodes();
for (int y = 0; y < nl2.getLength(); y++) {
Node wn3 = nl2.item(y);
// If it's not an element node, we ignore it.
if (wn3.getNodeType() != Node.ELEMENT_NODE) {
continue;
}
if (!wn3.getNodeName().equalsIgnoreCase("logEntry")) {
// Error condition of sorts!
// Errr, what should we do here?
MekHQ.logMessage("Unknown node type not loaded in personnel log nodes: " + wn3.getNodeName());
continue;
}
retVal.addLogEntry(LogEntry.generateInstanceFromXML(wn3));
}
} else if (wn2.getNodeName().equalsIgnoreCase("injuries")) {
NodeList nl2 = wn2.getChildNodes();
for (int y = 0; y < nl2.getLength(); y++) {
Node wn3 = nl2.item(y);
// If it's not an element node, we ignore it.
if (wn3.getNodeType() != Node.ELEMENT_NODE) {
continue;
}
if (!wn3.getNodeName().equalsIgnoreCase("injury")) {
// Error condition of sorts!
// Errr, what should we do here?
MekHQ.logMessage("Unknown node type not loaded in injury nodes: " + wn3.getNodeName());
continue;
}
retVal.injuries.add(Injury.generateInstanceFromXML(wn3));
}
DateTime now = new DateTime(c.getCalendar());
retVal.injuries.stream().filter(inj -> (null == inj.getStart()))
.forEach(inj -> inj.setStart(now.minusDays(inj.getOriginalTime() - inj.getTime())));
} else if (wn2.getNodeName().equalsIgnoreCase("founder")) {
retVal.founder = Boolean.parseBoolean(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("originalUnitWeight")) {
retVal.originalUnitWeight = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("originalUnitTech")) {
retVal.originalUnitTech = Integer.parseInt(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("originalUnitId")) {
retVal.originalUnitId = UUID.fromString(wn2.getTextContent());
} else if (wn2.getNodeName().equalsIgnoreCase("extraData")) {
retVal.extraData = ExtraData.createFromXml(wn2);
}
}
if (version.getMajorVersion() == 0 && version.getMinorVersion() < 2 && version.getSnapshot() < 13) {
if (retVal.primaryRole > T_INFANTRY) {
retVal.primaryRole += 4;
}
if (retVal.secondaryRole > T_INFANTRY) {
retVal.secondaryRole += 4;
}
}
if (version.getMajorVersion() == 0 && version.getMinorVersion() < 3 && version.getMinorVersion() > 1) {
//adjust for conventional fighter pilots
if (retVal.primaryRole >= T_CONV_PILOT) {
retVal.primaryRole += 1;
}
if (retVal.secondaryRole >= T_CONV_PILOT) {
retVal.secondaryRole += 1;
}
}
if (version.getMajorVersion() == 0 && version.getMinorVersion() == 3 && version.getSnapshot() < 1) {
//adjust for conventional fighter pilots
if (retVal.primaryRole == T_CONV_PILOT && retVal.hasSkill(SkillType.S_PILOT_SPACE) && !retVal.hasSkill(SkillType.S_PILOT_JET)) {
retVal.primaryRole += 1;
}
if (retVal.secondaryRole == T_CONV_PILOT && retVal.hasSkill(SkillType.S_PILOT_SPACE) && !retVal.hasSkill(SkillType.S_PILOT_JET)) {
retVal.secondaryRole += 1;
}
if (retVal.primaryRole == T_AERO_PILOT && !retVal.hasSkill(SkillType.S_PILOT_SPACE) && retVal.hasSkill(SkillType.S_PILOT_JET)) {
retVal.primaryRole += 8;
}
if (retVal.secondaryRole == T_AERO_PILOT && !retVal.hasSkill(SkillType.S_PILOT_SPACE) && retVal.hasSkill(SkillType.S_PILOT_JET)) {
retVal.secondaryRole += 8;
}
}
if ((null != advantages) && (advantages.trim().length() > 0)) {
StringTokenizer st = new StringTokenizer(advantages, "::");
while (st.hasMoreTokens()) {
String adv = st.nextToken();
String advName = Crew.parseAdvantageName(adv);
Object value = Crew.parseAdvantageValue(adv);
try {
retVal.getOptions().getOption(advName).setValue(value);
} catch (Exception e) {
MekHQ.logMessage("Error restoring advantage: " + adv);
}
}
}
if ((null != edge) && (edge.trim().length() > 0)) {
StringTokenizer st = new StringTokenizer(edge, "::");
while (st.hasMoreTokens()) {
String adv = st.nextToken();
String advName = Crew.parseAdvantageName(adv);
Object value = Crew.parseAdvantageValue(adv);
try {
retVal.getOptions().getOption(advName).setValue(value);
} catch (Exception e) {
MekHQ.logMessage("Error restoring edge: " + adv);
}
}
}
if ((null != implants) && (implants.trim().length() > 0)) {
StringTokenizer st = new StringTokenizer(implants, "::");
while (st.hasMoreTokens()) {
String adv = st.nextToken();
String advName = Crew.parseAdvantageName(adv);
Object value = Crew.parseAdvantageValue(adv);
try {
retVal.getOptions().getOption(advName).setValue(value);
} catch (Exception e) {
MekHQ.logMessage("Error restoring implants: " + adv);
}
}
}
//check to see if we are dealing with a PilotPerson from 0.1.8 or earlier
if (pilotGunnery != -1) {
switch (type) {
case 0:
retVal.addSkill(SkillType.S_GUN_MECH, 7 - pilotGunnery, 0);
retVal.addSkill(SkillType.S_PILOT_MECH, 8 - pilotPiloting, 0);
retVal.primaryRole = T_MECHWARRIOR;
break;
case 1:
retVal.addSkill(SkillType.S_GUN_VEE, 7 - pilotGunnery, 0);
retVal.addSkill(SkillType.S_PILOT_GVEE, 8 - pilotPiloting, 0);
retVal.primaryRole = T_GVEE_DRIVER;
break;
case 2:
retVal.addSkill(SkillType.S_GUN_AERO, 7 - pilotGunnery, 0);
retVal.addSkill(SkillType.S_PILOT_AERO, 8 - pilotPiloting, 0);
retVal.primaryRole = T_AERO_PILOT;
break;
case 4:
retVal.addSkill(SkillType.S_GUN_BA, 7 - pilotGunnery, 0);
retVal.addSkill(SkillType.S_ANTI_MECH, 8 - pilotPiloting, 0);
retVal.primaryRole = T_BA;
break;
}
retVal.addSkill(SkillType.S_TACTICS, pilotCommandBonus, 0);
}
if (null != pilotName) {
retVal.setName(pilotName);
}
if (null != pilotNickname) {
retVal.setCallsign(pilotNickname);
}
} catch (Exception ex) {
// Errrr, apparently either the class name was invalid...
// Or the listed name doesn't exist.
// Doh!
MekHQ.logError(ex);
}
if (retVal.id == null) {
MekHQ.logMessage("ID not pre-defined; generating person's ID.", 5);
retVal.id = UUID.randomUUID();
}
// Prisoner and Bondsman updating
if (retVal.prisonerStatus != PRISONER_NOT && retVal.rank == 0) {
if (retVal.prisonerStatus == PRISONER_BONDSMAN) {
retVal.setRankNumeric(Ranks.RANK_BONDSMAN);
} else {
retVal.setRankNumeric(Ranks.RANK_PRISONER);
}
}
return retVal;
}
public void setSalary(int s) {
salary = s;
}
public int getRawSalary() {
return salary;
}
public int getSalary() {
if (isPrisoner() || isBondsman()) {
return 0;
}
if (salary > -1) {
return salary;
}
//if salary is -1, then use the standard amounts
int primaryBase = campaign.getCampaignOptions().getBaseSalary(getPrimaryRole());
primaryBase *= campaign.getCampaignOptions().getSalaryXpMultiplier(getExperienceLevel(false));
if (hasSkill(SkillType.S_ANTI_MECH) && (getPrimaryRole() == T_INFANTRY || getPrimaryRole() == T_BA)) {
primaryBase *= campaign.getCampaignOptions().getSalaryAntiMekMultiplier();
}
int secondaryBase = campaign.getCampaignOptions().getBaseSalary(getSecondaryRole()) / 2;
secondaryBase *= campaign.getCampaignOptions().getSalaryXpMultiplier(getExperienceLevel(true));
if (hasSkill(SkillType.S_ANTI_MECH) && (getSecondaryRole() == T_INFANTRY || getSecondaryRole() == T_BA)) {
secondaryBase *= campaign.getCampaignOptions().getSalaryAntiMekMultiplier();
}
int totalBase = primaryBase + secondaryBase;
if (getRank().isOfficer()) {
totalBase *= campaign.getCampaignOptions().getSalaryCommissionMultiplier();
} else {
totalBase *= campaign.getCampaignOptions().getSalaryEnlistedMultiplier();
}
totalBase *= getRank().getPayMultiplier();
return totalBase;
//TODO: distinguish dropship, jumpship, and warship crew
//TODO: Add era mod to salary calc..
}
public int getRankNumeric() {
return rank;
}
public void setRankNumeric(int r) {
rank = r;
rankLevel = 0; // Always reset to 0 so that a call to setRankLevel() isn't mandatory.
}
public int getRankLevel() {
// If we're somehow above the max level for this rank, drop to that level
int profession = getProfession();
while (profession != Ranks.RPROF_MW && getRanks().isEmptyProfession(profession)) {
profession = getRanks().getAlternateProfession(profession);
}
if (rankLevel > getRank().getRankLevels(profession)) {
rankLevel = getRank().getRankLevels(profession);
}
return rankLevel;
}
public void setRankLevel(int level) {
rankLevel = level;
}
public int getRankSystem() {
if (rankSystem == -1) {
return campaign.getRanks().getRankSystem();
}
return rankSystem;
}
public void setRankSystem(int system) {
rankSystem = system;
if (system == campaign.getRanks().getRankSystem()) {
rankSystem = -1;
}
// Set the ranks too
if (rankSystem == -1) {
ranks = null;
} else {
ranks = new Ranks(rankSystem);
}
MekHQ.triggerEvent(new PersonChangedEvent(this));
}
public Ranks getRanks() {
if (rankSystem != -1) {
// Null protection
if (ranks == null) {
ranks = new Ranks(rankSystem);
}
return ranks;
}
return campaign.getRanks();
}
public Rank getRank() {
if (rankSystem != -1) {
return Ranks.getRanksFromSystem(rankSystem).getRank(rank);
}
return campaign.getRanks().getRank(rank);
}
public String getRankName() {
String rankName;
int profession = getProfession();
/* Track number of times the profession has been redirected so we don't get caught
* in a loop by self-reference or loops due to bad configuration */
int redirects = 0;
// If we're using an "empty" profession, default to MechWarrior
while (getRanks().isEmptyProfession(profession) && redirects < Ranks.RPROF_NUM) {
profession = campaign.getRanks().getAlternateProfession(profession);
redirects++;
}
// If we're set to a rank that no longer exists, demote ourself
while (getRank().getName(profession).equals("-")) {
setRankNumeric(--rank);
}
redirects = 0;
// re-route through any profession redirections
while (getRank().getName(profession).startsWith("--") && profession != Ranks.RPROF_MW
&& redirects < Ranks.RPROF_NUM) {
// We've hit a rank that defaults to the MechWarrior table, so grab the equivalent name from there
if (getRank().getName(profession).equals("--")) {
profession = getRanks().getAlternateProfession(profession);
} else if (getRank().getName(profession).startsWith("--")) {
profession = getRanks().getAlternateProfession(getRank().getName(profession));
}
redirects++;
}
if (getRank().getName(profession).startsWith("--")) {
profession = Ranks.RPROF_MW;
}
rankName = getRank().getName(profession);
// Manei Domini Additions
if (getRankSystem() != Ranks.RS_WOB) {
// Oops, clear our MD variables
maneiDominiClass = MD_NONE;
maneiDominiRank = Rank.MD_RANK_NONE;
}
if (maneiDominiClass != MD_NONE) {
rankName = getManeiDominiClassNames() + " " + rankName;
}
if (maneiDominiRank != Rank.MD_RANK_NONE) {
rankName += " " + Rank.getManeiDominiRankName(maneiDominiRank);
}
if (getRankSystem() == Ranks.RS_COM || getRankSystem() == Ranks.RS_WOB) {
rankName += getComstarBranchDesignation();
}
// If we have a rankLevel, add it
if (rankLevel > 0) {
if (getRank().getRankLevels(profession) > 0)
rankName += Utilities.getRomanNumeralsFromArabicNumber(rankLevel, true);
else // Oops! Our rankLevel didn't get correctly cleared, they's remedy that.
rankLevel = 0;
}
// We have our name, return it
return rankName;
}
public int getManeiDominiClass() {
return maneiDominiClass;
}
public void setManeiDominiClass(int maneiDominiClass) {
this.maneiDominiClass = maneiDominiClass;
MekHQ.triggerEvent(new PersonChangedEvent(this));
}
public int getManeiDominiRank() {
return maneiDominiRank;
}
public void setManeiDominiRank(int maneiDominiRank) {
this.maneiDominiRank = maneiDominiRank;
MekHQ.triggerEvent(new PersonChangedEvent(this));
}
public String getManeiDominiClassNames() {
return getManeiDominiClassNames(maneiDominiClass, getRankSystem());
}
public static String getManeiDominiClassNames(int maneiDominiClass, int rankSystem) {
// Only WoB
if (rankSystem != Ranks.RS_WOB)
return "";
switch (maneiDominiClass) {
case MD_NONE: return "";
case MD_GHOST: return "Ghost";
case MD_WRAITH: return "Wraith";
case MD_BANSHEE: return "Banshee";
case MD_ZOMBIE: return "Zombie";
case MD_PHANTOM: return "Phantom";
case MD_SPECTER: return "Specter";
case MD_POLTERGEIST: return "Poltergeist";
default: return "";
}
}
public String getSkillSummary() {
return SkillType.getExperienceLevelName(getExperienceLevel(false));
}
@Override
public String toString() {
return getFullName();
}
public int getExperienceLevel(boolean secondary) {
int role = primaryRole;
if (secondary) {
role = secondaryRole;
}
switch (role) {
case T_MECHWARRIOR:
if (hasSkill(SkillType.S_GUN_MECH) && hasSkill(SkillType.S_PILOT_MECH)) {
/* Attempt to use higher precision averaging, but if it doesn't provide a clear result
due to non-standard experience thresholds then fall back on lower precision averaging
See Bug #140 */
if(campaign.getCampaignOptions().useAltQualityAveraging()) {
int rawScore = (int) Math.floor(
(getSkill(SkillType.S_GUN_MECH).getLevel() + getSkill(SkillType.S_PILOT_MECH).getLevel()) / 2.0
);
if(getSkill(SkillType.S_GUN_MECH).getType().getExperienceLevel(rawScore) ==
getSkill(SkillType.S_PILOT_MECH).getType().getExperienceLevel(rawScore)) {
return getSkill(SkillType.S_GUN_MECH).getType().getExperienceLevel(rawScore);
}
}
return (int) Math.floor((getSkill(SkillType.S_GUN_MECH).getExperienceLevel()
+ getSkill(SkillType.S_PILOT_MECH).getExperienceLevel()) / 2.0);
} else {
return -1;
}
case T_GVEE_DRIVER:
if (hasSkill(SkillType.S_PILOT_GVEE)) {
return getSkill(SkillType.S_PILOT_GVEE).getExperienceLevel();
} else {
return -1;
}
case T_NVEE_DRIVER:
if (hasSkill(SkillType.S_PILOT_NVEE)) {
return getSkill(SkillType.S_PILOT_NVEE).getExperienceLevel();
} else {
return -1;
}
case T_VTOL_PILOT:
if (hasSkill(SkillType.S_PILOT_VTOL)) {
return getSkill(SkillType.S_PILOT_VTOL).getExperienceLevel();
} else {
return -1;
}
case T_VEE_GUNNER:
if (hasSkill(SkillType.S_GUN_VEE)) {
return getSkill(SkillType.S_GUN_VEE).getExperienceLevel();
} else {
return -1;
}
case T_AERO_PILOT:
if (hasSkill(SkillType.S_GUN_AERO) && hasSkill(SkillType.S_PILOT_AERO)) {
if(campaign.getCampaignOptions().useAltQualityAveraging()) {
int rawScore = (int) Math.floor(
(getSkill(SkillType.S_GUN_AERO).getLevel() + getSkill(SkillType.S_PILOT_AERO).getLevel()) / 2.0
);
if(getSkill(SkillType.S_GUN_AERO).getType().getExperienceLevel(rawScore) ==
getSkill(SkillType.S_PILOT_AERO).getType().getExperienceLevel(rawScore)) {
return getSkill(SkillType.S_GUN_AERO).getType().getExperienceLevel(rawScore);
}
}
return (int) Math.floor((getSkill(SkillType.S_GUN_AERO).getExperienceLevel()
+ getSkill(SkillType.S_PILOT_AERO).getExperienceLevel()) / 2.0);
} else {
return -1;
}
case T_CONV_PILOT:
if (hasSkill(SkillType.S_GUN_JET) && hasSkill(SkillType.S_PILOT_JET)) {
if(campaign.getCampaignOptions().useAltQualityAveraging()) {
int rawScore = (int) Math.floor(
(getSkill(SkillType.S_GUN_JET).getLevel() + getSkill(SkillType.S_PILOT_JET).getLevel()) / 2.0
);
if(getSkill(SkillType.S_GUN_JET).getType().getExperienceLevel(rawScore) ==
getSkill(SkillType.S_PILOT_JET).getType().getExperienceLevel(rawScore)) {
return getSkill(SkillType.S_GUN_JET).getType().getExperienceLevel(rawScore);
}
}
return (int) Math.floor((getSkill(SkillType.S_GUN_JET).getExperienceLevel()
+ getSkill(SkillType.S_PILOT_JET).getExperienceLevel()) / 2.0);
} else {
return -1;
}
case T_BA:
if (hasSkill(SkillType.S_GUN_BA) && hasSkill(SkillType.S_ANTI_MECH)) {
if(campaign.getCampaignOptions().useAltQualityAveraging()) {
int rawScore = (int) Math.floor(
(getSkill(SkillType.S_GUN_BA).getLevel() + getSkill(SkillType.S_ANTI_MECH).getLevel()) / 2.0
);
if(getSkill(SkillType.S_GUN_BA).getType().getExperienceLevel(rawScore) ==
getSkill(SkillType.S_ANTI_MECH).getType().getExperienceLevel(rawScore)) {
return getSkill(SkillType.S_GUN_BA).getType().getExperienceLevel(rawScore);
}
}
return (int) Math.floor((getSkill(SkillType.S_GUN_BA).getExperienceLevel()
+ getSkill(SkillType.S_ANTI_MECH).getExperienceLevel()) / 2.0);
} else {
return -1;
}
case T_PROTO_PILOT:
if (hasSkill(SkillType.S_GUN_PROTO)) {
return getSkill(SkillType.S_GUN_PROTO).getExperienceLevel();
} else {
return -1;
}
case T_INFANTRY:
if (hasSkill(SkillType.S_SMALL_ARMS)) {
return getSkill(SkillType.S_SMALL_ARMS).getExperienceLevel();
} else {
return -1;
}
case T_SPACE_PILOT:
if (hasSkill(SkillType.S_PILOT_SPACE)) {
return getSkill(SkillType.S_PILOT_SPACE).getExperienceLevel();
} else {
return -1;
}
case T_SPACE_CREW:
if (hasSkill(SkillType.S_TECH_VESSEL)) {
return getSkill(SkillType.S_TECH_VESSEL).getExperienceLevel();
} else {
return -1;
}
case T_SPACE_GUNNER:
if (hasSkill(SkillType.S_GUN_SPACE)) {
return getSkill(SkillType.S_GUN_SPACE).getExperienceLevel();
} else {
return -1;
}
case T_NAVIGATOR:
if (hasSkill(SkillType.S_NAV)) {
return getSkill(SkillType.S_NAV).getExperienceLevel();
} else {
return -1;
}
case T_MECH_TECH:
if (hasSkill(SkillType.S_TECH_MECH)) {
return getSkill(SkillType.S_TECH_MECH).getExperienceLevel();
} else {
return -1;
}
case T_MECHANIC:
if (hasSkill(SkillType.S_TECH_MECHANIC)) {
return getSkill(SkillType.S_TECH_MECHANIC).getExperienceLevel();
} else {
return -1;
}
case T_AERO_TECH:
if (hasSkill(SkillType.S_TECH_AERO)) {
return getSkill(SkillType.S_TECH_AERO).getExperienceLevel();
} else {
return -1;
}
case T_BA_TECH:
if (hasSkill(SkillType.S_TECH_BA)) {
return getSkill(SkillType.S_TECH_BA).getExperienceLevel();
} else {
return -1;
}
case T_ASTECH:
if (hasSkill(SkillType.S_ASTECH)) {
return getSkill(SkillType.S_ASTECH).getExperienceLevel();
} else {
return -1;
}
case T_DOCTOR:
if (hasSkill(SkillType.S_DOCTOR)) {
return getSkill(SkillType.S_DOCTOR).getExperienceLevel();
} else {
return -1;
}
case T_MEDIC:
if (hasSkill(SkillType.S_MEDTECH)) {
return getSkill(SkillType.S_MEDTECH).getExperienceLevel();
} else {
return -1;
}
case T_ADMIN_COM:
case T_ADMIN_LOG:
case T_ADMIN_TRA:
case T_ADMIN_HR:
if (hasSkill(SkillType.S_ADMIN)) {
return getSkill(SkillType.S_ADMIN).getExperienceLevel();
} else {
return -1;
}
default:
return -1;
}
}
public String getPatientDesc() {
String toReturn = "<html><font size='2'><b>" + getFullTitle() + "</b><br/>";
toReturn += getHits() + " hit(s)<br>[next check in " + getDaysToWaitForHealing() + " days]";
toReturn += "</font></html>";
return toReturn;
}
/**
* returns a full description in html format that will be used for the graphical display in the personnel table
*
* @return
*/
public String getFullDesc() {
String toReturn = "<b>" + getFullTitle(true) + "</b><br/>";
toReturn += getSkillSummary() + " " + getRoleDesc();
return toReturn;
}
public String getFullTitle() {
return getFullTitle(false);
}
public String getFullTitle(boolean html) {
String rank = getRankName();
// Do prisoner checks
if (rank.equalsIgnoreCase("None")) {
if (isPrisoner()) {
return "Prisoner " + getFullName();
}
if (isBondsman()) {
return "Bondsman " + getFullName();
}
return getFullName();
}
// This is used for the rank sorter. If you have a better way to accomplish it, by all means...
// Of course, nothing that uses Full Title actually uses the rank sorter yet I guess...
// Still, I've turned it back on and I don't see it messing anything up anywhere.
// - Dylan
// If we need it in html for any reason, make it so.
if (html)
rank = makeHTMLRankDiv();
return rank + " " + getFullName();
}
public String makeHTMLRank() {
return "<html>"+makeHTMLRankDiv()+"</html>";
}
public String makeHTMLRankDiv() {
return "<div id=\""+getId()+"\">"+getRankName()+ (isPrisoner() && isWillingToDefect() ? "*" : "") + "</div>";
}
public String getHyperlinkedFullTitle() {
return "<a href='PERSON:" + getId() + "'>" + getFullTitle() + "</a>";
}
public String getComstarBranchDesignation() {
StringBuilder sb = new StringBuilder(" ");
// Primary
if (getPrimaryDesignator() != DESIG_NONE) {
sb.append(parseDesignator(getPrimaryDesignator()));
} else if (isTechPrimary()) {
sb.append("Zeta");
} else if (isAdminPrimary()) {
sb.append("Chi");
} else {
parseRoleForDesignation(getPrimaryRole(), sb);
}
// Secondary
if (getSecondaryDesignator() != DESIG_NONE) {
sb.append(" ");
sb.append(parseDesignator(getSecondaryDesignator()));
} else if (isTechSecondary()) {
sb.append(" Zeta");
} else if (isAdminSecondary()) {
sb.append(" Chi");
} else if (getSecondaryRole() != T_NONE) {
sb.append(" ");
parseRoleForDesignation(getSecondaryRole(), sb);
}
return sb.toString();
}
private void parseRoleForDesignation(int role, StringBuilder sb) {
switch (role) {
case T_MECHWARRIOR:
sb.append("Epsilon");
break;
case T_AERO_PILOT:
sb.append("Pi");
break;
case T_BA:
case T_INFANTRY:
sb.append("Iota");
break;
case T_SPACE_CREW:
case T_SPACE_GUNNER:
case T_SPACE_PILOT:
case T_NAVIGATOR:
Unit u = campaign.getUnit(getUnitId());
if (u != null) {
Entity en = u.getEntity();
if (en instanceof Dropship) {
sb.append("Xi");
}
if (en instanceof Jumpship) {
sb.append("Theta");
}
}
break;
case T_DOCTOR:
case T_MEDIC:
sb.append("Kappa");
break;
case T_GVEE_DRIVER:
case T_NVEE_DRIVER:
case T_VTOL_PILOT:
case T_VEE_GUNNER:
case T_CONV_PILOT:
sb.append("Lambda");
break;
default: break;
}
}
public static String parseDesignator(int designator) {
switch (designator) {
case DESIG_NONE:
return "None (Auto-Set)";
case DESIG_EPSILON:
return "Epsilon";
case DESIG_PI:
return "Pi";
case DESIG_IOTA:
return "Iota";
case DESIG_XI:
return "Xi";
case DESIG_THETA:
return "Theta";
case DESIG_ZETA:
return "Zeta";
case DESIG_MU:
return "Mu";
case DESIG_RHO:
return "Rho";
case DESIG_LAMBDA:
return "Lambda";
case DESIG_PSI:
return "Psi";
case DESIG_OMICRON:
return "Omicron";
case DESIG_CHI:
return "Chi";
case DESIG_GAMMA:
return "Gamma";
default:
return "";
}
}
/**
* @return the primaryDesignator
*/
public int getPrimaryDesignator() {
return primaryDesignator;
}
/**
* @param primaryDesignator the primaryDesignator to set
*/
public void setPrimaryDesignator(int primaryDesignator) {
this.primaryDesignator = primaryDesignator;
MekHQ.triggerEvent(new PersonChangedEvent(this));
}
/**
* @return the secondaryDesignator
*/
public int getSecondaryDesignator() {
return secondaryDesignator;
}
/**
* @param secondaryDesignator the secondaryDesignator to set
*/
public void setSecondaryDesignator(int secondaryDesignator) {
this.secondaryDesignator = secondaryDesignator;
MekHQ.triggerEvent(new PersonChangedEvent(this));
}
public int getHealingDifficulty() {
if (campaign.getCampaignOptions().useTougherHealing()) {
return Math.max(0, getHits() - 2);
}
return 0;
}
public TargetRoll getHealingMods(Person doctor) {
TargetRoll mods = new TargetRoll(getHealingDifficulty(), "difficulty");
return mods;
}
public String fail(int rating) {
return " <font color='red'><b>Failed to heal.</b></font>";
}
public boolean hasSkill(String skillName) {
return null != skills.get(skillName);
}
@Nullable
public Skill getSkill(String skillName) {
return skills.get(skillName);
}
public void addSkill(String skillName, int lvl, int bonus) {
skills.put(skillName, new Skill(skillName, lvl, bonus));
}
public void addSkill(String skillName, int xpLvl, boolean random, int bonus) {
skills.put(skillName, new Skill(skillName, xpLvl, random, bonus));
}
public void removeSkill(String skillName) {
if (hasSkill(skillName)) {
skills.remove(skillName);
}
}
/**
* Remove all skills
*/
public void removeAllSkills() {
skills.clear();
}
/**
* Limit skills to the maximum of the given level
*/
public void limitSkills(int maxLvl) {
for(Map.Entry<String, Skill> skill : skills.entrySet()) {
if(skill.getValue().getLevel() > maxLvl) {
skill.getValue().setLevel(maxLvl);
}
}
}
public void improveSkill(String skillName) {
if (hasSkill(skillName)) {
getSkill(skillName).improve();
} else {
addSkill(skillName, 0, 0);
}
MekHQ.triggerEvent(new PersonChangedEvent(this));
}
public int getCostToImprove(String skillName) {
if (hasSkill(skillName)) {
return getSkill(skillName).getCostToImprove();
} else {
return -1;
}
}
public int getHits() {
return hits;
}
public void setHits(int h) {
this.hits = h;
}
/**
* @return <tt>true</tt> if the location (or any of its parent locations) has an injury
* which implies that the location (most likely a limb) is severed.
*/
public boolean isLocationMissing(BodyLocation loc) {
if(null == loc) {
return false;
}
for(Injury i : getInjuriesByLocation(loc)) {
if(i.getType().impliesMissingLocation(loc)) {
return true;
}
}
// Check parent locations as well (a hand can be missing if the corresponding arm is)
return isLocationMissing(loc.parent);
}
public void heal() {
hits = Math.max(hits - 1, 0);
if (!needsFixing()) {
doctorId = null;
}
}
public boolean needsFixing() {
return (hits > 0 || needsAMFixing()) && status != S_KIA && status == S_ACTIVE;
}
public String succeed() {
heal();
return " <font color='green'><b>Successfully healed one hit.</b></font>";
}
/**
* @return the spas
*/
public Hashtable<String, SpecialAbility> getSpas() {
return spas;
}
/**
* @param spas the spas to set
*/
public void setSpas(Hashtable<String, SpecialAbility> spas) {
this.spas = spas;
}
public int countSPAs() {
return spas.size();
}
public ArrayList<SpecialAbility> getEligibleSPAs(boolean generation) {
ArrayList<SpecialAbility> eligible = new ArrayList<SpecialAbility>();
for (Enumeration<IOption> i = getOptions(PilotOptions.LVL3_ADVANTAGES); i.hasMoreElements(); ) {
IOption ability = i.nextElement();
if (!ability.booleanValue()) {
SpecialAbility spa = SpecialAbility.getAbility(ability.getName());
if(null == spa) {
continue;
}
if(!spa.isEligible(this)) {
continue;
}
if(generation & spa.getWeight() <= 0) {
continue;
}
eligible.add(spa);
}
}
return eligible;
}
/**
* This function returns an html-coded list that says what abilities are enabled for this pilot
*
* @return
*/
public String getAbilityList(String type) {
String abilityString = "";
// Add in SPAs
for (SpecialAbility spa : spas.values()) {
abilityString = abilityString + spa.getDisplayName() + "<br />";
}
// Filter through pilot options, and add any that are needed
for (Enumeration<IOption> i = getOptions(type); i.hasMoreElements(); ) {
IOption ability = i.nextElement();
if (ability.booleanValue() && !spas.containsKey(ability.getName())) {
abilityString = abilityString + Utilities.getOptionDisplayName(ability) + "<br>";
}
}
if (abilityString.equals("")) {
return null;
}
return "<html>" + abilityString + "</html>";
}
public void acquireAbility(String type, String name, Object value) {
//we might also need to remove some prior abilities
SpecialAbility spa = SpecialAbility.getAbility(name);
Vector<String> toRemove = new Vector<String>();
if(null != spa) {
toRemove = spa.getRemovedAbilities();
}
// Handle our hashmap of SPAs
spas.put(name, spa);
for (String remove : toRemove) {
spas.remove(remove);
}
// Handle any options that are SPAs
for (Enumeration<IOption> i = getOptions(type); i.hasMoreElements(); ) {
IOption ability = i.nextElement();
if (ability.getName().equals(name)) {
ability.setValue(value);
} else {
for(String remove : toRemove) {
if (ability.getName().equals(remove)) {
ability.setValue(ability.getDefault());
}
}
}
}
}
public PilotOptions getOptions() {
return options;
}
/**
* Returns the options of the given category that this pilot has
*/
public Enumeration<IOption> getOptions(String grpKey) {
for (Enumeration<IOptionGroup> i = options.getGroups(); i.hasMoreElements(); ) {
IOptionGroup group = i.nextElement();
if (group.getKey().equalsIgnoreCase(grpKey)) {
return group.getOptions();
}
}
// no pilot advantages -- return an empty Enumeration
return new Vector<IOption>().elements();
}
public int countOptions() {
int count = 0;
for (Enumeration<IOptionGroup> i = options.getGroups(); i.hasMoreElements(); ) {
IOptionGroup group = i.nextElement();
for (Enumeration<IOption> j = group.getOptions(); j.hasMoreElements(); ) {
IOption option = j.nextElement();
if (option.booleanValue()) {
count++;
}
}
}
return count;
}
public int countOptions(String grpKey) {
int count = 0;
for (Enumeration<IOptionGroup> i = options.getGroups(); i.hasMoreElements(); ) {
IOptionGroup group = i.nextElement();
if (!group.getKey().equalsIgnoreCase(grpKey)) {
continue;
}
for (Enumeration<IOption> j = group.getOptions(); j.hasMoreElements(); ) {
IOption option = j.nextElement();
if (option.booleanValue()) {
count++;
}
}
}
return count;
}
/**
* Returns a string of all the option "codes" for this pilot, for a given group, using sep as the separator
*/
public String getOptionList(String sep, String grpKey) {
StringBuffer adv = new StringBuffer();
if (null == sep) {
sep = "";
}
for (Enumeration<IOptionGroup> i = options.getGroups(); i.hasMoreElements(); ) {
IOptionGroup group = i.nextElement();
if (!group.getKey().equalsIgnoreCase(grpKey)) {
continue;
}
for (Enumeration<IOption> j = group.getOptions(); j.hasMoreElements(); ) {
IOption option = j.nextElement();
if (option.booleanValue()) {
if (adv.length() > 0) {
adv.append(sep);
}
adv.append(option.getName());
if ((option.getType() == IOption.STRING) || (option.getType() == IOption.CHOICE) || (option.getType() == IOption.INTEGER)) {
adv.append(" ").append(option.stringValue());
}
}
}
}
return adv.toString();
}
public int getEdge() {
return getOptions().intOption("edge");
}
public void setEdge(int e) {
for (Enumeration<IOption> i = getOptions(PilotOptions.EDGE_ADVANTAGES); i.hasMoreElements(); ) {
IOption ability = i.nextElement();
if (ability.getName().equals("edge")) {
ability.setValue(e);
}
}
}
/*
* This will set a specific edge trigger, regardless of the current status
*/
public void setEdgeTrigger(String name, boolean status) {
for (Enumeration<IOption> i = getOptions(PilotOptions.EDGE_ADVANTAGES); i.hasMoreElements(); ) {
IOption ability = i.nextElement();
if (ability.getName().equals(name)) {
ability.setValue(status);
}
}
MekHQ.triggerEvent(new PersonChangedEvent(this));
}
/**
* This will flip the boolean status of the current edge trigger
*
* @param name
*/
public void changeEdgeTrigger(String name) {
for (Enumeration<IOption> i = getOptions(PilotOptions.EDGE_ADVANTAGES); i.hasMoreElements(); ) {
IOption ability = i.nextElement();
if (ability.getName().equals(name)) {
ability.setValue(!ability.booleanValue());
}
}
MekHQ.triggerEvent(new PersonChangedEvent(this));
}
/**
* This function returns an html-coded tooltip that says what edge will be used
*
* @return
*/
public String getEdgeTooltip() {
String edgett = "";
for (Enumeration<IOption> i = getOptions(PilotOptions.EDGE_ADVANTAGES); i.hasMoreElements(); ) {
IOption ability = i.nextElement();
//yuck, it would be nice to have a more fool-proof way of identifying edge triggers
if (ability.getName().contains("edge_when") && ability.booleanValue()) {
edgett = edgett + ability.getDescription() + "<br>";
}
}
if (edgett.equals("")) {
return "No triggers set";
}
return "<html>" + edgett + "</html>";
}
public boolean isSupport() {
return primaryRole >= T_MECH_TECH || secondaryRole >= T_MECH_TECH;
}
public boolean canDrive(Entity ent) {
if (ent instanceof Mech) {
return hasSkill(SkillType.S_PILOT_MECH);
} else if (ent instanceof VTOL) {
return hasSkill(SkillType.S_PILOT_VTOL);
} else if (ent instanceof Tank) {
if (ent.getMovementMode() == EntityMovementMode.NAVAL
|| ent.getMovementMode() == EntityMovementMode.HYDROFOIL
|| ent.getMovementMode() == EntityMovementMode.SUBMARINE) {
return hasSkill(SkillType.S_PILOT_NVEE);
} else {
return hasSkill(SkillType.S_PILOT_GVEE);
}
} else if (ent instanceof ConvFighter) {
return hasSkill(SkillType.S_PILOT_JET) || hasSkill(SkillType.S_PILOT_AERO);
} else if (ent instanceof SmallCraft || ent instanceof Jumpship) {
return hasSkill(SkillType.S_PILOT_SPACE);
} else if (ent instanceof Aero) {
return hasSkill(SkillType.S_PILOT_AERO);
} else if (ent instanceof BattleArmor) {
return hasSkill(SkillType.S_GUN_BA);
} else if (ent instanceof Infantry) {
return hasSkill(SkillType.S_SMALL_ARMS);
} else if (ent instanceof Protomech) {
return hasSkill(SkillType.S_GUN_PROTO);
}
return false;
}
public boolean canGun(Entity ent) {
if (ent instanceof Mech) {
return hasSkill(SkillType.S_GUN_MECH);
} else if (ent instanceof Tank) {
return hasSkill(SkillType.S_GUN_VEE);
} else if (ent instanceof ConvFighter) {
return hasSkill(SkillType.S_GUN_JET) || hasSkill(SkillType.S_GUN_AERO);
} else if (ent instanceof SmallCraft || ent instanceof Jumpship) {
return hasSkill(SkillType.S_GUN_SPACE);
} else if (ent instanceof Aero) {
return hasSkill(SkillType.S_GUN_AERO);
} else if (ent instanceof BattleArmor) {
return hasSkill(SkillType.S_GUN_BA);
} else if (ent instanceof Infantry) {
return hasSkill(SkillType.S_SMALL_ARMS);
} else if (ent instanceof Protomech) {
return hasSkill(SkillType.S_GUN_PROTO);
}
return false;
}
public boolean canTech(Entity ent) {
if (ent instanceof Mech || ent instanceof Protomech) {
return hasSkill(SkillType.S_TECH_MECH);
} else if (ent instanceof Aero) {
return hasSkill(SkillType.S_TECH_AERO);
} else if (ent instanceof BattleArmor) {
return hasSkill(SkillType.S_TECH_BA);
} else if (ent instanceof Tank) {
return hasSkill(SkillType.S_TECH_MECHANIC);
}
return false;
}
public int getMaintenanceTimeUsing() {
int time = 0;
for (UUID id : getTechUnitIDs()) {
Unit u = campaign.getUnit(id);
if (null != u) {
time += u.getMaintenanceTime();
}
}
return time;
}
public boolean isMothballing() {
if (!isTech()) {
return false;
}
for (Unit u : campaign.getUnits()) {
if (u.isMothballing() && u.getTechId().equals(id)) {
return true;
}
}
return false;
}
public UUID getUnitId() {
return unitId;
}
public void setUnitId(UUID i) {
unitId = i;
}
public void removeTechUnitId(UUID i) {
techUnitIds.remove(i);
}
public void addTechUnitID(UUID i) {
techUnitIds.add(i);
}
public void clearTechUnitIDs() {
techUnitIds.clear();
}
public ArrayList<UUID> getTechUnitIDs() {
return techUnitIds;
}
public int getOldSupportTeamId() {
return teamId;
}
public int getMinutesLeft() {
return minutesLeft;
}
public void setMinutesLeft(int m) {
this.minutesLeft = m;
if(engineer && null != getUnitId()) {
//set minutes for all crewmembers
Unit u = campaign.getUnit(getUnitId());
if(null != u) {
for(Person p : u.getActiveCrew()) {
p.setMinutesLeft(m);
}
}
}
}
public int getOvertimeLeft() {
return overtimeLeft;
}
public void setOvertimeLeft(int m) {
this.overtimeLeft = m;
if(engineer && null != getUnitId()) {
//set minutes for all crewmembers
Unit u = campaign.getUnit(getUnitId());
if(null != u) {
for(Person p : u.getActiveCrew()) {
p.setMinutesLeft(m);
}
}
}
}
public void resetMinutesLeft() {
if (isTechPrimary() || getPrimaryRole() == T_DOCTOR) {
this.minutesLeft = 480;
this.overtimeLeft = 240;
}
if (isTechSecondary() || getSecondaryRole() == T_DOCTOR) {
this.minutesLeft = 240;
this.overtimeLeft = 240;
}
}
public boolean isAdmin() {
return (isAdminPrimary() || isAdminSecondary());
}
public boolean isMedic() {
return (T_MEDIC == primaryRole) || (T_MEDIC == secondaryRole);
}
public boolean isAdminPrimary() {
return primaryRole == T_ADMIN_HR || primaryRole == T_ADMIN_COM || primaryRole == T_ADMIN_LOG || primaryRole == T_ADMIN_TRA;
}
public boolean isAdminSecondary() {
return secondaryRole == T_ADMIN_HR || secondaryRole == T_ADMIN_COM || secondaryRole == T_ADMIN_LOG || secondaryRole == T_ADMIN_TRA;
}
public Skill getBestTechSkill() {
Skill skill = null;
int lvl = -1;
if (hasSkill(SkillType.S_TECH_MECH) && getSkill(SkillType.S_TECH_MECH).getExperienceLevel() > lvl) {
skill = getSkill(SkillType.S_TECH_MECH);
lvl = getSkill(SkillType.S_TECH_MECH).getExperienceLevel();
}
if (hasSkill(SkillType.S_TECH_AERO) && getSkill(SkillType.S_TECH_AERO).getExperienceLevel() > lvl) {
skill = getSkill(SkillType.S_TECH_AERO);
lvl = getSkill(SkillType.S_TECH_AERO).getExperienceLevel();
}
if (hasSkill(SkillType.S_TECH_MECHANIC) && getSkill(SkillType.S_TECH_MECHANIC).getExperienceLevel() > lvl) {
skill = getSkill(SkillType.S_TECH_MECHANIC);
lvl = getSkill(SkillType.S_TECH_MECHANIC).getExperienceLevel();
}
if (hasSkill(SkillType.S_TECH_BA) && getSkill(SkillType.S_TECH_BA).getExperienceLevel() > lvl) {
skill = getSkill(SkillType.S_TECH_BA);
lvl = getSkill(SkillType.S_TECH_BA).getExperienceLevel();
}
return skill;
}
public boolean isTech() {
//type must be correct and you must be more than ultra-green in the skill
boolean isMechTech = hasSkill(SkillType.S_TECH_MECH) && getSkill(SkillType.S_TECH_MECH).getExperienceLevel() > SkillType.EXP_ULTRA_GREEN;
boolean isAeroTech = hasSkill(SkillType.S_TECH_AERO) && getSkill(SkillType.S_TECH_AERO).getExperienceLevel() > SkillType.EXP_ULTRA_GREEN;
boolean isMechanic = hasSkill(SkillType.S_TECH_MECHANIC) && getSkill(SkillType.S_TECH_MECHANIC).getExperienceLevel() > SkillType.EXP_ULTRA_GREEN;
boolean isBATech = hasSkill(SkillType.S_TECH_BA) && getSkill(SkillType.S_TECH_BA).getExperienceLevel() > SkillType.EXP_ULTRA_GREEN;
return (isTechPrimary() || isTechSecondary()) && (isMechTech || isAeroTech || isMechanic || isBATech);
}
public boolean isTechPrimary() {
return primaryRole == T_MECH_TECH || primaryRole == T_AERO_TECH || primaryRole == T_MECHANIC || primaryRole == T_BA_TECH || primaryRole == T_SPACE_CREW;
}
public boolean isTechSecondary() {
return secondaryRole == T_MECH_TECH || secondaryRole == T_AERO_TECH || secondaryRole == T_MECHANIC || secondaryRole == T_BA_TECH;
}
public boolean isTaskOvertime(IPartWork partWork) {
return partWork.getTimeLeft() > getMinutesLeft()
&& (partWork.getTimeLeft() - getMinutesLeft()) <= getOvertimeLeft();
}
public Skill getSkillForWorkingOn(IPartWork part) {
Unit unit = part.getUnit();
Skill skill = getSkillForWorkingOn(unit);
if (null != skill) {
return skill;
}
//check spare parts
//return the best one
if (part.isRightTechType(SkillType.S_TECH_MECH) && hasSkill(SkillType.S_TECH_MECH)) {
skill = getSkill(SkillType.S_TECH_MECH);
}
if (part.isRightTechType(SkillType.S_TECH_BA) && hasSkill(SkillType.S_TECH_BA)) {
if (null == skill || skill.getFinalSkillValue() > getSkill(SkillType.S_TECH_BA).getFinalSkillValue()) {
skill = getSkill(SkillType.S_TECH_BA);
}
}
if (part.isRightTechType(SkillType.S_TECH_AERO) && hasSkill(SkillType.S_TECH_AERO)) {
if (null == skill || skill.getFinalSkillValue() > getSkill(SkillType.S_TECH_AERO).getFinalSkillValue()) {
skill = getSkill(SkillType.S_TECH_AERO);
}
}
if (part.isRightTechType(SkillType.S_TECH_MECHANIC) && hasSkill(SkillType.S_TECH_MECHANIC)) {
if (null == skill || skill.getFinalSkillValue() > getSkill(SkillType.S_TECH_MECHANIC).getFinalSkillValue()) {
skill = getSkill(SkillType.S_TECH_MECHANIC);
}
}
if (part.isRightTechType(SkillType.S_TECH_VESSEL) && hasSkill(SkillType.S_TECH_VESSEL)) {
if (null == skill || skill.getFinalSkillValue() > getSkill(SkillType.S_TECH_VESSEL).getFinalSkillValue()) {
skill = getSkill(SkillType.S_TECH_VESSEL);
}
}
if (null != skill) {
return skill;
}
//if we are still here then we didn't have the right tech skill, so return the highest
//of any tech skills that we do have
if (hasSkill(SkillType.S_TECH_MECH)) {
skill = getSkill(SkillType.S_TECH_MECH);
}
if (hasSkill(SkillType.S_TECH_BA)) {
if (null == skill || skill.getFinalSkillValue() > getSkill(SkillType.S_TECH_BA).getFinalSkillValue()) {
skill = getSkill(SkillType.S_TECH_BA);
}
}
if (hasSkill(SkillType.S_TECH_MECHANIC)) {
if (null == skill || skill.getFinalSkillValue() > getSkill(SkillType.S_TECH_MECHANIC).getFinalSkillValue()) {
skill = getSkill(SkillType.S_TECH_MECHANIC);
}
}
if (hasSkill(SkillType.S_TECH_AERO)) {
if (null == skill || skill.getFinalSkillValue() > getSkill(SkillType.S_TECH_AERO).getFinalSkillValue()) {
skill = getSkill(SkillType.S_TECH_AERO);
}
}
return skill;
}
public Skill getSkillForWorkingOn(Unit unit) {
if (null != unit && unit.getEntity() instanceof Mech && hasSkill(SkillType.S_TECH_MECH)) {
return getSkill(SkillType.S_TECH_MECH);
}
if (null != unit && unit.getEntity() instanceof BattleArmor && hasSkill(SkillType.S_TECH_BA)) {
return getSkill(SkillType.S_TECH_BA);
}
if (null != unit && unit.getEntity() instanceof Tank && hasSkill(SkillType.S_TECH_MECHANIC)) {
return getSkill(SkillType.S_TECH_MECHANIC);
}
if (null != unit && (unit.getEntity() instanceof Dropship || unit.getEntity() instanceof Jumpship)
&& hasSkill(SkillType.S_TECH_VESSEL)) {
return getSkill(SkillType.S_TECH_VESSEL);
}
if (null != unit && unit.getEntity() instanceof Aero
&& !(unit.getEntity() instanceof Dropship)
&& !(unit.getEntity() instanceof Jumpship)
&& hasSkill(SkillType.S_TECH_AERO)) {
return getSkill(SkillType.S_TECH_AERO);
}
return null;
}
public Skill getSkillForWorkingOn(IAcquisitionWork acquisition, String skillName) {
if (skillName.equals(CampaignOptions.S_TECH)) {
return getBestTechSkill();
}
if (hasSkill(skillName)) {
return getSkill(skillName);
}
return null;
}
public String getDocDesc() {
String toReturn = "<html><font size='2'><b>" + getFullName() + "</b><br/>";
Skill skill = getSkill(SkillType.S_DOCTOR);
if (null != skill) {
toReturn += SkillType.getExperienceLevelName(skill.getExperienceLevel()) + " " + SkillType.S_DOCTOR;
}
if (campaign.getMedicsPerDoctor() < 4) {
toReturn += "</font><font size='2' color='red'>" + ", " + campaign.getMedicsPerDoctor() + " medics</font><font size='2'><br>";
} else {
toReturn += ", " + campaign.getMedicsPerDoctor() + " medics<br>";
}
toReturn += campaign.getPatientsFor(this) + " patient(s)";
toReturn += "</font></html>";
return toReturn;
}
public int getBestTechLevel() {
int lvl = -1;
Skill mechSkill = getSkill(SkillType.S_TECH_MECH);
Skill mechanicSkill = getSkill(SkillType.S_TECH_MECHANIC);
Skill baSkill = getSkill(SkillType.S_TECH_BA);
Skill aeroSkill = getSkill(SkillType.S_TECH_AERO);
if (null != mechSkill && mechSkill.getLevel() > lvl) {
lvl = mechSkill.getLevel();
}
if (null != mechanicSkill && mechanicSkill.getLevel() > lvl) {
lvl = mechanicSkill.getLevel();
}
if (null != baSkill && baSkill.getLevel() > lvl) {
lvl = baSkill.getLevel();
}
if (null != aeroSkill && aeroSkill.getLevel() > lvl) {
lvl = aeroSkill.getLevel();
}
return lvl;
}
public String getTechDesc(boolean overtimeAllowed) {
return getTechDesc(overtimeAllowed, null);
}
public String getTechDesc(boolean overtimeAllowed, IPartWork part) {
String toReturn = "<html><font size='2'><b>" + getFullName() + "</b><br/>";
if (null != part && null != part.getUnit() && getTechUnitIDs().contains(part.getUnit().getId())) {
toReturn = "<html><font size='2' color='green'><b>@" + getFullName() + "</b><br/>";
}
Skill mechSkill = getSkill(SkillType.S_TECH_MECH);
Skill mechanicSkill = getSkill(SkillType.S_TECH_MECHANIC);
Skill baSkill = getSkill(SkillType.S_TECH_BA);
Skill aeroSkill = getSkill(SkillType.S_TECH_AERO);
Skill vesselSkill = getSkill(SkillType.S_TECH_VESSEL);
boolean first = true;
if (null != mechSkill) {
if (!first) {
toReturn += "; ";
}
toReturn += SkillType.getExperienceLevelName(mechSkill.getExperienceLevel()) + " " + SkillType.S_TECH_MECH;
first = false;
}
if (null != mechanicSkill) {
if (!first) {
toReturn += "; ";
}
toReturn += SkillType.getExperienceLevelName(mechanicSkill.getExperienceLevel()) + " " + SkillType.S_TECH_MECHANIC;
first = false;
}
if (null != baSkill) {
if (!first) {
toReturn += "; ";
}
toReturn += SkillType.getExperienceLevelName(baSkill.getExperienceLevel()) + " " + SkillType.S_TECH_BA;
first = false;
}
if (null != aeroSkill) {
if (!first) {
toReturn += "; ";
}
toReturn += SkillType.getExperienceLevelName(aeroSkill.getExperienceLevel()) + " " + SkillType.S_TECH_AERO;
first = false;
}
if (null != vesselSkill) {
if (!first) {
toReturn += "; ";
}
toReturn += SkillType.getExperienceLevelName(vesselSkill.getExperienceLevel()) + " " + SkillType.S_TECH_VESSEL;
first = false;
}
toReturn += "<br/>";
toReturn += getMinutesLeft() + " minutes left";
if (overtimeAllowed) {
toReturn += " + (" + getOvertimeLeft() + " overtime)";
}
toReturn += "</font></html>";
return toReturn;
}
public boolean isRightTechTypeFor(IPartWork part) {
Unit unit = part.getUnit();
if (null == unit) {
if ((hasSkill(SkillType.S_TECH_MECH) && part.isRightTechType(SkillType.S_TECH_MECH))
|| (hasSkill(SkillType.S_TECH_AERO) && part.isRightTechType(SkillType.S_TECH_AERO))
|| (hasSkill(SkillType.S_TECH_MECHANIC) && part.isRightTechType(SkillType.S_TECH_MECHANIC))
|| (hasSkill(SkillType.S_TECH_BA) && part.isRightTechType(SkillType.S_TECH_BA))
|| (hasSkill(SkillType.S_TECH_VESSEL) && part.isRightTechType(SkillType.S_TECH_VESSEL))) {
return true;
}
return false;
}
if (unit.getEntity() instanceof Mech || unit.getEntity() instanceof Protomech) {
return hasSkill(SkillType.S_TECH_MECH);
}
if (unit.getEntity() instanceof BattleArmor) {
return hasSkill(SkillType.S_TECH_BA);
}
if (unit.getEntity() instanceof Tank || unit.getEntity() instanceof Infantry) {
return hasSkill(SkillType.S_TECH_MECHANIC);
}
if (unit.getEntity() instanceof Dropship || unit.getEntity() instanceof Jumpship) {
return hasSkill(SkillType.S_TECH_VESSEL);
}
if (unit.getEntity() instanceof Aero) {
return hasSkill(SkillType.S_TECH_AERO);
}
return false;
}
public UUID getDoctorId() {
return doctorId;
}
public boolean isDoctor() {
return hasSkill(SkillType.S_DOCTOR) && (primaryRole == T_DOCTOR || secondaryRole == T_DOCTOR);
}
public int getToughness() {
return toughness;
}
public void setToughness(int t) {
toughness = t;
}
public void resetSkillTypes() {
for (String skillName : skills.keySet()) {
Skill s = skills.get(skillName);
s.updateType();
}
}
public int getOldId() {
return oldId;
}
public void fixIdReferences(Hashtable<Integer, UUID> uHash, Hashtable<Integer, UUID> pHash) {
unitId = uHash.get(oldUnitId);
doctorId = pHash.get(oldDoctorId);
}
public int getNTasks() {
return nTasks;
}
public void setNTasks(int n) {
nTasks = n;
}
public ArrayList<LogEntry> getPersonnelLog() {
Collections.sort(personnelLog, new Comparator<LogEntry>() {
@Override
public int compare(final LogEntry u1, final LogEntry u2) {
return u1.getDate().compareTo(u2.getDate());
}
});
return personnelLog;
}
public void addLogEntry(Date d, String desc, String type) {
personnelLog.add(new LogEntry(d, desc, type));
}
public void addLogEntry(Date d, String desc) {
personnelLog.add(new LogEntry(d, desc));
}
public void addLogEntry(LogEntry entry) {
personnelLog.add(entry);
}
/**
* All methods below are for the Advanced Medical option **
*/
public ArrayList<Injury> getInjuries() {
return new ArrayList<>(injuries);
}
public void clearInjuries() {
injuries.clear();
// Clear the doctor if there is one
doctorId = null;
MekHQ.triggerEvent(new PersonChangedEvent(this));
}
public void removeInjury(Injury i) {
injuries.remove(i);
MekHQ.triggerEvent(new PersonChangedEvent(this));
}
public String getInjuriesDesc() {
String toReturn = "<html><font size='2'><b>" + getFullTitle() + "</b><br/>";
toReturn += " Injuries:";
String sep = "<br/> ";
for (Injury injury : injuries) {
toReturn += sep + injury.getFluff();
if (sep.contains("<br/>")) {
sep = ", ";
} else {
sep = ",<br/> ";
}
}
toReturn += "</font></html>";
return toReturn;
}
private int getHitsInLocation(BodyLocation loc) {
return ((null != loc) && hitsPerLocation.containsKey(loc)) ? hitsPerLocation.get(loc).intValue() : 0;
}
public void diagnose(int hits) {
InjuryUtil.resolveAfterCombat(campaign, this, hits);
InjuryUtil.resolveCombatDamage(campaign, this, hits);
setHits(0);
}
public void changeStatus(int status) {
if (status == getStatus()) {
return;
}
Unit u = campaign.getUnit(getUnitId());
if (status == Person.S_KIA) {
addLogEntry(campaign.getDate(), "Died from " + getGenderPronoun(PRONOUN_HISHER) + " wounds");
//set the deathday
setDeathday((GregorianCalendar) campaign.calendar.clone());
}
if (status == Person.S_RETIRED) {
addLogEntry(campaign.getDate(), "Retired from active duty due to " + getGenderPronoun(PRONOUN_HISHER) + " wounds");
}
setStatus(status);
if (status != Person.S_ACTIVE) {
setDoctorId(null, campaign.getCampaignOptions().getNaturalHealingWaitingPeriod());
// If we're assigned to a unit, remove us from it
if (null != u) {
u.remove(this, true);
}
// If we're assigned as a tech for any unit, remove us from it/them
if (!techUnitIds.isEmpty()) {
for (UUID tuuid : techUnitIds) {
Unit t = campaign.getUnit(tuuid);
t.remove(this, true);
}
}
}
}
public int getAbilityTimeModifier() {
int modifier = 100;
if (campaign.getCampaignOptions().useToughness()) {
if (getToughness() == 1) {
modifier -= 10;
}
if (getToughness() > 1) {
modifier -= 15;
}
} // TODO: Fully implement this for advanced healing
if (getOptions().booleanOption("pain_resistance")) {
modifier -= 15;
} else if (getOptions().booleanOption("iron_man")) {
modifier -= 10;
}
return modifier;
}
public void applyBodyHit(BodyLocation location) {
hitsPerLocation.put(location, getHitsInLocation(location) + 1);
}
public boolean hasInjury(BodyLocation loc) {
return (null != getInjuryByLocation(loc));
}
public boolean hasInjury(BodyLocation loc, InjuryType type) {
return (null != getInjuryByLocationAndType(loc, type));
}
public boolean needsAMFixing() {
boolean retVal = false;
if (injuries.size() > 0) {
for (Injury i : injuries) {
if (i.getTime() > 0 || !(i.isPermanent())) {
retVal = true;
break;
}
}
}
return retVal;
}
public int getPilotingInjuryMod() {
return Modifier.calcTotalModifier(injuries.stream().flatMap(i -> i.getModifiers().stream()), Modifier.Value.PILOTING);
}
public int getGunneryInjuryMod() {
return Modifier.calcTotalModifier(injuries.stream().flatMap(i -> i.getModifiers().stream()), Modifier.Value.GUNNERY);
}
/**
* @return an HTML encoded string of effects
*/
public String getEffectString() {
StringBuilder sb = new StringBuilder("<html>");
final int pilotingMod = getPilotingInjuryMod();
final int gunneryMod = getGunneryInjuryMod();
if((pilotingMod != 0) && (pilotingMod < Integer.MAX_VALUE)) {
sb.append(String.format(" Piloting %+d <br>", pilotingMod));
} else if(pilotingMod == Integer.MAX_VALUE) {
sb.append(" Piloting: <i>Impossible</i> <br>");
}
if((gunneryMod != 0) && (gunneryMod < Integer.MAX_VALUE)) {
sb.append(String.format(" Gunnery: %+d <br>", gunneryMod));
} else if(gunneryMod == Integer.MAX_VALUE) {
sb.append(" Gunnery: <i>Impossible</i> <br>");
}
return sb.append("</html>").toString();
}
public String getInjuriesText() {
String nl = System.getProperty("line.separator");
String buffer = "";
for (Injury injury : injuries) {
buffer = buffer + injury.getFluff() + nl;
}
return buffer + getEffectString();
}
public boolean hasInjuryModifiers() {
return injuries.stream().flatMap(i -> i.getModifiers().stream())
.filter(mod -> mod.tags.contains(InjuryType.MODTAG_INJURY)).findFirst().isPresent();
}
public boolean hasInjuries(boolean permCheck) {
boolean tf = false;
if (injuries.size() > 0) {
if (permCheck) {
for (Injury injury : injuries) {
if (!injury.isPermanent() || injury.getTime() > 0) {
tf = true;
break;
}
}
} else {
tf = true;
}
}
return tf;
}
public boolean hasOnlyHealedPermanentInjuries() {
if (injuries.size() == 0) {
return false;
}
for (Injury injury : injuries) {
if (!injury.isPermanent() || injury.getTime() > 0) {
return false;
}
}
return true;
}
public List<Injury> getInjuriesByLocation(BodyLocation loc) {
return injuries.stream()
.filter((i) -> (i.getLocation() == loc)).collect(Collectors.toList());
}
// Returns only the first injury in a location
public Injury getInjuryByLocation(BodyLocation loc) {
return injuries.stream()
.filter((i) -> (i.getLocation() == loc)).findFirst().orElse(null);
}
public Injury getInjuryByType(InjuryType t) {
return injuries.stream()
.filter((i) -> (i.getType() == t)).findFirst().orElse(null);
}
public Injury getInjuryByLocationAndType(BodyLocation loc, InjuryType t) {
return injuries.stream()
.filter((i) -> (i.getLocation() == loc) && (i.getType() == t)).findFirst().orElse(null);
}
public void addInjury(Injury i) {
injuries.add(i);
if (null != getUnitId()) {
campaign.getUnit(getUnitId()).resetPilotAndEntity();
}
}
public int getProfession() {
return getProfessionFromPrimaryRole(primaryRole);
}
public static int getProfessionFromPrimaryRole(int role) {
switch (role) {
case T_MECHWARRIOR:
case T_PROTO_PILOT:
case T_DOCTOR:
case T_MEDIC:
return Ranks.RPROF_MW;
case T_AERO_PILOT:
case T_CONV_PILOT:
return Ranks.RPROF_ASF;
case T_GVEE_DRIVER:
case T_NVEE_DRIVER:
case T_VTOL_PILOT:
case T_VEE_GUNNER:
return Ranks.RPROF_VEE;
case T_BA:
case T_INFANTRY:
return Ranks.RPROF_INF;
case T_SPACE_PILOT:
case T_SPACE_CREW:
case T_SPACE_GUNNER:
case T_NAVIGATOR:
return Ranks.RPROF_NAVAL;
case T_MECH_TECH:
case T_MECHANIC:
case T_AERO_TECH:
case T_BA_TECH:
case T_ASTECH:
case T_ADMIN_COM:
case T_ADMIN_LOG:
case T_ADMIN_TRA:
case T_ADMIN_HR:
return Ranks.RPROF_TECH;
default:
return Ranks.RPROF_MW;
}
}
/* For use by Against the Bot retirement/defection rolls */
public boolean isFounder() {
return founder;
}
public void setFounder(boolean founder) {
this.founder = founder;
}
public int getOriginalUnitWeight() {
return originalUnitWeight;
}
public void setOriginalUnitWeight(int weight) {
originalUnitWeight = weight;
}
public int getOriginalUnitTech() {
return originalUnitTech;
}
public void setOriginalUnitTech(int tech) {
originalUnitTech = tech;
}
public UUID getOriginalUnitId() {
return originalUnitId;
}
public void setOriginalUnitId(UUID id) {
originalUnitId = id;
}
public void setOriginalUnit(Unit unit) {
originalUnitId = unit.getId();
originalUnitTech = 0;
if (unit.getEntity().isClan()) {
originalUnitTech += 2;
} else if (unit.getEntity().getTechLevel() > megamek.common.TechConstants.T_INTRO_BOXSET) {
originalUnitTech++;
}
originalUnitWeight = unit.getEntity().getWeightClass();
}
public int getNumShares(boolean sharesForAll) {
if (isPrisoner() || isBondsman()) {
return 0;
}
if (!sharesForAll && primaryRole != T_MECHWARRIOR &&
secondaryRole != T_MECHWARRIOR) {
return 0;
}
int shares = 1;
if (founder) {
shares++;
}
shares += Math.max(-1, getExperienceLevel(false) - 2);
if (getRank().isOfficer()) {
Ranks ranks = getRanks();
int rankOrder = ranks.getOfficerCut();
while (rankOrder <= rank && rankOrder < Ranks.RC_NUM) {
Rank rank = ranks.getAllRanks().get(rankOrder);
if (!rank.getName(getProfession()).equals("-")) {
shares++;;
}
rankOrder++;
}
}
if (originalUnitWeight >= 1) {
shares++;
}
if (originalUnitWeight >= 3) {
shares++;
}
shares += originalUnitTech;
return shares;
}
public boolean isDeadOrMIA() {
return (status == S_KIA) || (status == S_MIA);
}
public boolean isEngineer() {
return engineer;
}
public void setEngineer(boolean b) {
engineer = b;
}
public String getChildList() {
List<UUID> ancestors = new ArrayList<>();
for(Ancestors a : campaign.getAncestors()) {
if((null != a)
&& getId().equals(a.getMotherID()) || getId().equals(a.getFatherID())) {
ancestors.add(a.getId());
}
}
List<String> children = new ArrayList<>();
for (Person p : campaign.getPersonnel()) {
if(ancestors.contains(p.getAncestorsID())) {
children.add(p.getFullName());
}
}
return "<html>" + Utilities.combineString(children, "<br/>") + "</html>";
}
public boolean hasChildren() {
boolean hasKids = false;
if (getId() != null) {
for (Ancestors a : campaign.getAncestors()) {
if (getId().equals(a.getMotherID()) || getId().equals(a.getFatherID())) {
hasKids = true;
break;
}
}
}
return hasKids;
}
}