PlayerCharacter.java

Index Score
pcgen.core
PCGen

View: Reasons, Metrics, Source Code

These are the metrics that contribute to the Enerjy Score for this file, ranked by impact. So the metrics listed at the top influence the score to a greater extent that the metrics listed at the bottom.

MetricDescription
LINE_COMMENTNumber of line comments
EXEC_COMMENTSComments in executable code
LINESNumber of lines in the source file
LOCLines of code
BLOCKSNumber of blocks
CYCLOMATICCyclomatic complexity
COMPARISONSNumber of comparison operators
SIZESize of the file in bytes
OPERATORSNumber of operators
COMMENTSComment lines
PROGRAM_LENGTHHalstead program length
ELOCEffective lines of code
EXITSProcedure exits
OPERANDSNumber of operands
DECL_COMMENTSComments in declarations
LOGICAL_LINESNumber of statements
JAVA0145JAVA0145 Tab character used in source file
RETURNSNumber of return points from functions
INTERFACE_COMPLEXITYInterface complexity
FUNCTIONSNumber of function declarations
DOC_COMMENTNumber of javadoc comment lines
UNIQUE_OPERANDSNumber of unique operands
PARAMSNumber of formal parameter declarations
PROGRAM_VOCABHalstead program vocabulary
WHITESPACENumber of whitespace lines
LOOPSNumber of loops
JAVA0117JAVA0117 Missing javadoc: method 'method'
JAVA0177JAVA0177 Variable declaration missing initializer
JAVA0049JAVA0049 Nested block at depth N (maximum: M)
JAVA0179JAVA0179 Local variable hides visible field
BLOCK_COMMENTNumber of block comment lines
JAVA0259JAVA0259 Return of collection/array field
JAVA0076JAVA0076 Use of magic number
JAVA0254JAVA0254 Use enhanced for loop construct instead of Iterator
JAVA0130JAVA0130 Non-static method does not use instance fields
JAVA0173JAVA0173 Unused method parameter
JAVA0285JAVA0285 Dereference of potentially null variable
JAVA0064JAVA0064 N variations of identifier name (maximum: M)
JAVA0143JAVA0143 Synchronized method
JAVA0031JAVA0031 Case statement not properly closed
JAVA0034JAVA0034 Missing braces in if statement
JAVA0123JAVA0123 Use all three components of for loop
JAVA0108JAVA0108 Incorrect javadoc: no @param tag for 'parameter'
JAVA0276JAVA0276 Unnecessary use of String constructor
UNIQUE_OPERATORSNumber of unique operators
JAVA0039JAVA0039 Break statement with label
NEST_DEPTHMaximum nesting depth
JAVA0109JAVA0109 Incorrect javadoc: no parameter 'parameter'
JAVA0125JAVA0125 Continue statement with label
JAVA0110JAVA0110 Incorrect javadoc: no @return tag
JAVA0144JAVA0144 Line exceeds maximum M characters
JAVA0128JAVA0128 Public constructor in non-public class
JAVA0119JAVA0119 Control variable changed within body of for loop
PROGRAM_VOLUMEHalstead program volume
JAVA0136JAVA0136 N methods defined in class (maximum: M)
JAVA0032JAVA0032 Switch statement missing default
JAVA0174JAVA0174 Assigned local variable never used
JAVA0256JAVA0256 Assignment of external collection/array to field
JAVA0138JAVA0138 N parameters defined for method (maximum: M)
JAVA0126JAVA0126 Method declares unchecked exception in throws
JAVA0043JAVA0043 Inner class does not use outer class
JAVA0029JAVA0029 Private method not used
JAVA0100JAVA0100 Class contains N non-final fields (maximum: M)
JAVA0075JAVA0075 Method parameter hides field
JAVA0270JAVA0270 Use Java 5.0 enhanced for loop construct to iterate over all elements in an array
/* * PlayerCharacter.java * Copyright 2001 (C) Bryan McRoberts <merton_monk@yahoo.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Created on April 21, 2001, 2:15 PM * * Current Ver: $Revision: 7459 $ * Last Editor: $Author: nuance $ * Last Edited: $Date: 2008-08-09 21:17:14 -0400 (Sat, 09 Aug 2008) $ * */ package pcgen.core; import java.awt.geom.Point2D; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStreamReader; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.IdentityHashMap; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Observable; import java.util.Set; import java.util.SortedSet; import java.util.StringTokenizer; import java.util.TreeMap; import java.util.TreeSet; import pcgen.base.formula.Formula; import pcgen.base.lang.StringUtil; import pcgen.base.util.DoubleKeyMap; import pcgen.base.util.HashMapToList; import pcgen.base.util.MapToList; import pcgen.base.util.TreeMapToList; import pcgen.cdom.base.AssociatedPrereqObject; import pcgen.cdom.base.CDOMObject; import pcgen.cdom.base.CDOMObjectUtilities; import pcgen.cdom.base.CDOMReference; import pcgen.cdom.base.Constants; import pcgen.cdom.base.TransitionChoice; import pcgen.cdom.content.ChallengeRating; import pcgen.cdom.content.HitDie; import pcgen.cdom.content.LevelCommandFactory; import pcgen.cdom.content.Modifier; import pcgen.cdom.enumeration.AssociationKey; import pcgen.cdom.enumeration.FormulaKey; import pcgen.cdom.enumeration.Gender; import pcgen.cdom.enumeration.IntegerKey; import pcgen.cdom.enumeration.ListKey; import pcgen.cdom.enumeration.ObjectKey; import pcgen.cdom.enumeration.RaceSubType; import pcgen.cdom.enumeration.RaceType; import pcgen.cdom.enumeration.SkillCost; import pcgen.cdom.enumeration.StringKey; import pcgen.cdom.enumeration.VariableKey; import pcgen.cdom.helper.Qualifier; import pcgen.cdom.helper.StatLock; import pcgen.cdom.inst.EquipmentHead; import pcgen.cdom.inst.ObjectCache; import pcgen.cdom.inst.PCClassLevel; import pcgen.cdom.reference.CDOMSingleRef; import pcgen.core.Ability.Nature; import pcgen.core.analysis.RaceStat; import pcgen.core.analysis.TemplateSR; import pcgen.core.analysis.TemplateSelect; import pcgen.core.analysis.TemplateStat; import pcgen.core.bonus.Bonus; import pcgen.core.bonus.BonusObj; import pcgen.core.bonus.BonusObj.BonusPair; import pcgen.core.bonus.util.MissingObject; import pcgen.core.character.CharacterSpell; import pcgen.core.character.CompanionMod; import pcgen.core.character.EquipSet; import pcgen.core.character.EquipSlot; import pcgen.core.character.Follower; import pcgen.core.character.SpellBook; import pcgen.core.character.SpellInfo; import pcgen.core.levelability.LevelAbility; import pcgen.core.pclevelinfo.PCLevelInfo; import pcgen.core.prereq.PrereqHandler; import pcgen.core.prereq.Prerequisite; import pcgen.core.prereq.PrerequisiteOperator; import pcgen.core.spell.PCSpellTracker; import pcgen.core.spell.Spell; import pcgen.core.system.GameModeRollMethod; import pcgen.core.utils.CoreUtility; import pcgen.core.utils.MessageType; import pcgen.core.utils.ShowMessageDelegate; import pcgen.gui.GuiConstants; import pcgen.io.PCGFile; import pcgen.io.exporttoken.BonusToken; import pcgen.persistence.PersistenceLayerException; import pcgen.persistence.lst.prereq.PreParserFactory; import pcgen.rules.context.LoadContext; import pcgen.util.Delta; import pcgen.util.Logging; import pcgen.util.PropertyFactory; import pcgen.util.chooser.ChooserFactory; import pcgen.util.chooser.ChooserInterface; import pcgen.util.enumeration.AttackType; import pcgen.util.enumeration.Load; import pcgen.util.enumeration.Visibility; /** * <code>PlayerCharacter</code>. * * @author Bryan McRoberts <merton_monk@users.sourceforge.net> * @version $Revision: 7459 $ */ public final class PlayerCharacter extends Observable implements Cloneable, VariableContainer, AssociationStore { // Constants for use in getBonus /** ATTACKBONUS = 0 */ public static final int ATTACKBONUS = 0; /** MONKBONUS = 4 */ public static final int MONKBONUS = 4; private static String lastVariable = null; private ObjectCache cache = new ObjectCache(); private AssociationSupport assocSupt = new AssociationSupport(); // List of Armor Proficiencies private final List<String> armorProfList = new ArrayList<String>(); // List of misc items (Assets, Magic items, etc) private final ArrayList<String> miscList = new ArrayList<String>(3); // List of Note objects private final ArrayList<NoteItem> notesList = new ArrayList<NoteItem>(); // This may be different from file name private final ArrayList<Equipment> primaryWeapons = new ArrayList<Equipment>(); private final ArrayList<Equipment> secondaryWeapons = new ArrayList<Equipment>(); private final ArrayList<String> shieldProfList = new ArrayList<String>(); // List of Skills private final ArrayList<Skill> skillList = new ArrayList<Skill>(); // Collections of String (probably should be full objects) private final ArrayList<SpecialAbility> specialAbilityList = new ArrayList<SpecialAbility>(); // List of Template objects private final ArrayList<PCTemplate> templateList = new ArrayList<PCTemplate>(); // of // Template // List of VARs private final ArrayList<String> variableList = new ArrayList<String>(); private BigDecimal gold = new BigDecimal(0); private Deity deity = null; // source of granted domains private HashMap<String, String> domainSourceMap = new HashMap<String, String>(); private List<BonusObj> activeBonusList = new ArrayList<BonusObj>(); private final List<CharacterDomain> characterDomainList = new ArrayList<CharacterDomain>(); // List of Classes private ArrayList<PCClass> classList = new ArrayList<PCClass>(); // List of CompanionMods private final ArrayList<CompanionMod> companionModList = new ArrayList<CompanionMod>(); /** This character's list of followers */ private final List<Follower> followerList = new ArrayList<Follower>(); private HashMapToList<Class, String> qualifyArrayMap = new HashMapToList<Class, String>(); private Follower followerMaster = null; // Who is the master now? // List of Equip Sets private final List<EquipSet> equipSetList = new ArrayList<EquipSet>(); // List of Equipment private List<Equipment> equipmentList = new ArrayList<Equipment>(); private List<Equipment> equipmentMasterList = new ArrayList<Equipment>(); private Map<String, Integer> autoEquipOutputOrderCache = new HashMap<String, Integer>(); private List<PCLevelInfo> pcLevelInfo = new ArrayList<PCLevelInfo>(); // TODO This probably should not be a member but should be passed around private List<BonusObj> processedBonusList = new ArrayList<BonusObj>(); private final List<String> spellBooks = new ArrayList<String>(); private Map<String, SpellBook> spellBookMap = new HashMap<String, SpellBook>(); // Temporary Bonuses private List<Equipment> tempBonusItemList = new ArrayList<Equipment>(); private List<BonusObj> tempBonusList = new ArrayList<BonusObj>(); private Set<String> tempBonusFilters = new TreeSet<String>(); private Map<String, String> activeBonusMap = new TreeMap<String, String>(); private Race race = null; private PCClass selectedFavoredClass = null; private final StatList statList = new StatList(this); // List of Kit objects private List<Kit> kitList = null; // Spells private PCSpellTracker spellTracker = null; // // We don't want this list sorted until after it has been added // to the character, The reason is that sorting prevents // .CLEAR-TEMPLATES from clearing the OLDER template languages. private final List<Language> templateAutoLanguages = new ArrayList<Language>(); private final SortedSet<Language> templateLanguages = new TreeSet<Language>(); private final SortedSet<Language> languages = new TreeSet<Language>(); private Map<StringKey, String> stringChar = new HashMap<StringKey, String>(); private String calcEquipSetId = "0.1"; //$NON-NLS-1$ private String descriptionLst = "EMPTY"; //$NON-NLS-1$ private String tabName = Constants.EMPTY_STRING; private String gender = Globals.getAllGenders().get(0); private TreeSet<String> variableSet = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER); // Weapon, Armor and Shield proficiencies // private final TreeSet<WeaponProf> weaponProfList = new // TreeSet<WeaponProf>(); private Double[] movementMult = Globals.EMPTY_DOUBLE_ARRAY; private String[] movementMultOp = Globals.EMPTY_STRING_ARRAY; private String[] movementTypes = Globals.EMPTY_STRING_ARRAY; // Movement lists private Double[] movements = Globals.EMPTY_DOUBLE_ARRAY; private boolean armorProfListStable = false; // whether to add auto known spells each level private boolean autoKnownSpells = true; // whether higher level spell slots should be used for lower levels private boolean useHigherKnownSlots = SettingsHandler.isUseHigherLevelSlotsDefault(); private boolean useHigherPreppedSlots = SettingsHandler.isUseHigherLevelSlotsDefault(); // should we also load companions on master load? private boolean autoLoadCompanion = false; // Should we sort the gear automatically? private boolean autoSortGear = true; // Should we ignore cost for gear? private boolean ignoreCost = SettingsHandler.getGearTab_IgnoreCost(); // Should we allow the character to go into debt? private boolean allowDebt = SettingsHandler.getGearTab_AllowDebt(); // Should we resize the gear automatically? private boolean autoResize = SettingsHandler.getGearTab_AutoResize(); // output sheet locations private String outputSheetHTML = Constants.EMPTY_STRING; private String outputSheetPDF = Constants.EMPTY_STRING; private boolean[] ageSetKitSelections = new boolean[10]; private boolean dirtyFlag = false; private int serial = 0; private boolean displayUpdate = false; private boolean importing = false; // Should temp mods/bonuses be used/saved? private boolean useTempMods = true; private int age = 0; // 0 = LG to 8 = CE and 9 is <none selected> private int alignment = 9; private int costPool = 0; private int currentEquipSetNumber = 0; private int earnedXP = 0; // order in which the equipment will be output. private int equipOutputOrder = GuiConstants.INFOSKILLS_OUTPUT_BY_NAME_ASC; private int freeLangs = 0; private int heightInInches = 0; // in inches // pool of stats remaining to distribute private int poolAmount = 0; // order in which the skills will be output. private int skillsOutputOrder = GuiConstants.INFOSKILLS_OUTPUT_BY_NAME_ASC; private int spellLevelTemp = 0; private int weightInPounds = 0; // in pounds private VariableProcessor variableProcessor; // used by point buy. Total number of points for method, not points // remaining private int pointBuyPoints = -1; private boolean processLevelAbilities = true; /** * Aggregate abilities stored as a double key map. The keys are the Category * and nature (Normal, Virtual, or Automatic). The value is a list of abilities * that match the keys. When populated the list will contain a copy of all * abilities the character possesses via any of its classes, feats, * templates, equipoment etc. */ private DoubleKeyMap<AbilityCategory, Ability.Nature, List<Ability>> theAbilities = new DoubleKeyMap<AbilityCategory, Ability.Nature, List<Ability>>(); /** * List of all directly assigned normal nature abilities split by category. * These are abilities that are added directly to the character rather than * being added to a class, template etc that the character possesses. */ private Map<AbilityCategory, List<Ability>> realAbilities = new HashMap<AbilityCategory, List<Ability>>(); /** * List of all directly assigned virtual nature abilities split by category. * These are abilities that are added directly to the character rather than * being added to a class, template etc that the character possesses. */ private Map<AbilityCategory, List<Ability>> virtualAbilities = new HashMap<AbilityCategory, List<Ability>>(); /** * This map stores any user bonuses (entered through the GUI) to the * corrisponding ability pool. */ private Map<AbilityCategory, BigDecimal> theUserPoolBonuses = null; private Set<WeaponProf> theWeaponProfs = null; private Map<String, WeaponProf> cachedWeaponProfs = null; // private Map<String, List<TypedBonus>> theBonusMap = new HashMap<String, // List<TypedBonus>>(); // A cache outside of the variable cache to hold the values that will not alter after 20th level. Integer epicBAB = null; HashMap<Integer, Integer> epicCheckMap = new HashMap<Integer, Integer>(); private IdentityHashMap<PCTemplate, String> chosenFeatStrings = null; private HashMapToList<CDOMObject, PCTemplate> templatesAdded = new HashMapToList<CDOMObject, PCTemplate>(); // ///////////////////////////////////// // operations /** * Constructor */ public PlayerCharacter() { variableProcessor = new VariableProcessorPC(this); for (int i = 0; i < 10; i++) { ageSetKitSelections[i] = false; } Globals.setCurrentPC(this); for (int i = 0, x = SettingsHandler.getGame().s_ATTRIBLONG.length; i < x; ++i) { final PCStat stat = SettingsHandler.getGame().getUnmodifiableStatList().get(i); statList.addStat(stat.clone()); } setRace(Globals.s_EMPTYRACE); setName(Constants.EMPTY_STRING); setFeats(0); rollStats(SettingsHandler.getGame().getRollMethod()); miscList.add(Constants.EMPTY_STRING); miscList.add(Constants.EMPTY_STRING); miscList.add(Constants.EMPTY_STRING); addSpellBook(new SpellBook(Globals.getDefaultSpellBook(), SpellBook.TYPE_KNOWN_SPELLS)); addSpellBook(new SpellBook(Globals.INNATE_SPELL_BOOK_NAME, SpellBook.TYPE_INNATE_SPELLS)); populateSkills(SettingsHandler.getSkillsTab_IncludeSkills()); spellTracker = new PCSpellTracker(this); setStringFor(StringKey.HANDED, PropertyFactory.getString("in_right")); //$NON-NLS-1$ } /** * Get the active bonus map * * @return active bonus map */ public Map<String, String> getActiveBonusMap() { return activeBonusMap; } /** * Set the age * * @param i The PC's age */ public void setAge(final int i) { age = i; setDirty(true); calcActiveBonuses(); if (!isImporting()) { Globals.getBioSet().makeKitSelectionFor(this); } } /** * Get the age * * @return age */ public int getAge() { return age; } /** * Alignment of this PC * * @return alignment */ public int getAlignment() { return alignment; } /** * if checkBonus is true, then search for all skills with a SKILLRANK bonus * to include in list as well TODO This is bogus. Not only does it return * skills with a bonus but it modifies the PC's skill list to include them. * * @param checkBonus Whether to return all skills (true) (including those with * a bonus but no ranks) or just the skills that are actually * in the skillList. * @return ArrayList */ public ArrayList<Skill> getAllSkillList(final boolean checkBonus) { if (!checkBonus) { return skillList; } for (final Skill skill : Globals.getContext().ref.getConstructedCDOMObjects(Skill.class)) { if (!hasSkill(skill.getKeyName())) { if (!CoreUtility.doublesEqual(skill.getSkillRankBonusTo(this), 0.0)) { addSkill(skill); } } } return skillList; } /** * Retrieve those skills in the character's skill list that match the * supplied visibility level. * * @param vis * What level of visibility skills are desired. * * @return A list of the character's skills matching the visibility * criteria. */ public ArrayList<Skill> getPartialSkillList(Visibility vis) { // Now select the required set of skills, based on their visibility. ArrayList<Skill> aList = new ArrayList<Skill>(); for (Skill aSkill : skillList) { final Visibility skillVis = aSkill.getSafe(ObjectKey.VISIBILITY); if ((vis == Visibility.DEFAULT) || (skillVis == Visibility.DEFAULT) || (skillVis == vis)) { aList.add(aSkill); } } return aList; } /** * Get the armor proficiency list * * @return armor proficiency list */ public List<String> getArmorProfList() { if (armorProfListStable) { return armorProfList; } final List<String> autoArmorProfList = getAutoArmorProfList(); addArmorProfs(autoArmorProfList); final List<String> selectedProfList = getSelectedArmorProfList(); addArmorProfs(selectedProfList); armorProfListStable = true; return armorProfList; } /** * Sets a 'stable' list of armor profs * * @param isStable set the armour profs stable (when true) or * not stable (when false) and also make the * PC dirty. */ public void setArmorProfListStable(final boolean isStable) { armorProfListStable = isStable; setDirty(true); } /** * Returns the Spell Stat bonus for a class * * @param aClass * @return base spell stat bonus */ public int getBaseSpellStatBonus(final PCClass aClass) { if (aClass == null) { return 0; } int baseSpellStat = 0; PCStat ss = aClass.get(ObjectKey.SPELL_STAT); if (ss != null) { String statString = ss.getAbb(); final int statIndex = getStatList().getIndexOfStatFor(statString); if (statIndex >= 0) { baseSpellStat = getStatList().getTotalStatFor(statString); // final List<TypedBonus> bonuses = getBonusesTo("STAT", // "BASESPELLSTAT"); // bonuses.addAll( getBonusesTo("STAT", // "BASESPELLSTAT;CLASS."+aClass.getKeyName()) ); // bonuses.addAll( getBonusesTo("STAT", "CAST." + statString) ); // baseSpellStat += TypedBonus.totalBonuses(bonuses); baseSpellStat += (int) getTotalBonusTo("STAT", "BASESPELLSTAT"); baseSpellStat += (int) getTotalBonusTo("STAT", "BASESPELLSTAT;CLASS." + aClass.getKeyName()); baseSpellStat += (int) getTotalBonusTo("STAT", "CAST." + statString); baseSpellStat = getStatList().getModForNumber(baseSpellStat, statIndex); } } return baseSpellStat; } /** * Set BIO * * @param aString */ public void setBio(final String aString) { setStringFor(StringKey.BIO, aString); } /** * Get the BIO * * @return the BIO */ public String getBio() { return getSafeStringFor(StringKey.BIO); } /** * Set the birthday * * @param aString */ public void setBirthday(final String aString) { setStringFor(StringKey.BIRTHDAY, aString); } /** * Get the birthday * * @return birthday */ public String getBirthday() { return getSafeStringFor(StringKey.BIRTHDAY); } /** * Set the birthplace * * @param aString */ public void setBirthplace(final String aString) { setStringFor(StringKey.BIRTHPLACE, aString); } /** * Get the birthplace * * @return birthplace */ public String getBirthplace() { return getSafeStringFor(StringKey.BIRTHPLACE); } /** * Set the current EquipSet that is used to Bonus/Equip calculations * * @param eqSetId */ public void setCalcEquipSetId(final String eqSetId) { calcEquipSetId = eqSetId; setDirty(true); } /** * Get the id for the equipment set being used for calculation * * @return id */ public String getCalcEquipSetId() { if (equipSetList.isEmpty()) { return calcEquipSetId; } if (getEquipSetByIdPath(calcEquipSetId) == null) { // PC does not have that equipset ID // so we need to find one they do have for (EquipSet eSet : equipSetList) { if (eSet.getParentIdPath().equals(EquipSet.ROOT_ID)) { calcEquipSetId = eSet.getIdPath(); return calcEquipSetId; } } } return calcEquipSetId; } /** * Set's current equipmentList to selected output EquipSet then loops * through all the equipment and sets the correct status of each (equipped, * carried, etc) */ public void setCalcEquipmentList() { setCalcEquipmentList(false); } /** * Set's current equipmentList to selected output EquipSet then loops * through all the equipment and sets the correct status of each (equipped, * carried, etc) * * @param useTempBonuses */ public void setCalcEquipmentList(final boolean useTempBonuses) { // First we get the EquipSet that is going to be used // to calculate everything from final String calcId = getCalcEquipSetId(); final EquipSet eSet = getEquipSetByIdPath(calcId); if (eSet == null) { Logging .debugPrint("No EquipSet has been selected for calculations yet."); //$NON-NLS-1$ return; } // new equipment list final List<Equipment> eqList = new ArrayList<Equipment>(); // set PC's equipmentList to new one setEquipmentList(eqList); // get all the PC's EquipSet's final List<EquipSet> pcEquipSetList = getEquipSet(); if (pcEquipSetList.isEmpty()) { return; } // make sure EquipSet's are in sorted order // (important for Containers contents) Collections.sort(pcEquipSetList); // loop through all the EquipSet's and create equipment // then set status to equipped and add to PC's equipment list for (EquipSet es : pcEquipSetList) { final String abCalcId = calcId + EquipSet.PATH_SEPARATOR; final String abParentId = es.getParentIdPath() + EquipSet.PATH_SEPARATOR; // calcId = 0.1. // parentIdPath = 0.10. // OR // calcId = 0.10. // parentIdPath = 0.1. if (!abParentId.startsWith(abCalcId)) { continue; } final Equipment eqI = es.getItem(); if (eqI == null) { continue; } final Equipment eq = es.getItem(); final String aLoc = es.getName(); final String aNote = es.getNote(); Float num = es.getQty(); final StringTokenizer aTok = new StringTokenizer(es.getIdPath(), EquipSet.PATH_SEPARATOR); // if the eSet.getIdPath() is longer than 3 // it's inside a container, don't try to equip if (aTok.countTokens() > 3) { eq.setLocation(Equipment.CONTAINED); eq.setIsEquipped(false, this); eq.setNumberCarried(num); eq.setQty(num); } else if (aLoc.startsWith(Constants.S_CARRIED)) { eq.setLocation(Equipment.CARRIED_NEITHER); eq.setIsEquipped(false, this); eq.setNumberCarried(num); eq.setQty(num); } else if (aLoc.startsWith(Constants.S_NOTCARRIED)) { eq.setLocation(Equipment.NOT_CARRIED); eq.setIsEquipped(false, this); eq.setNumberCarried(Float.valueOf(0)); eq.setQty(num); } else if (eq.isWeapon()) { if (aLoc.equals(Constants.S_PRIMARY) || aLoc.equals(Constants.S_NATURAL_PRIMARY)) { eq.setQty(num); eq.setNumberCarried(num); eq.setNumberEquipped(num.intValue()); eq.setLocation(Equipment.EQUIPPED_PRIMARY); eq.setIsEquipped(true, this); } else if (aLoc.startsWith(Constants.S_SECONDARY) || aLoc.equals(Constants.S_NATURAL_SECONDARY)) { eq.setQty(num); eq.setNumberCarried(num); eq.setNumberEquipped(num.intValue()); eq.setLocation(Equipment.EQUIPPED_SECONDARY); eq.setIsEquipped(true, this); } else if (aLoc.equals(Constants.S_BOTH)) { eq.setQty(num); eq.setNumberCarried(num); eq.setNumberEquipped(num.intValue()); eq.setLocation(Equipment.EQUIPPED_BOTH); eq.setIsEquipped(true, this); } else if (aLoc.equals(Constants.S_DOUBLE)) { eq.setQty(num); eq.setNumberCarried(num); eq.setNumberEquipped(2); eq.setLocation(Equipment.EQUIPPED_TWO_HANDS); eq.setIsEquipped(true, this); } else if (aLoc.equals(Constants.S_UNARMED)) { eq.setLocation(Equipment.EQUIPPED_NEITHER); eq.setNumberEquipped(num.intValue()); } else if (aLoc.equals(Constants.S_TWOWEAPONS)) { if (num.doubleValue() < 2.0) { num = new Float(2.0); } es.setQty(num); eq.setQty(num); eq.setNumberCarried(num); eq.setNumberEquipped(2); eq.setLocation(Equipment.EQUIPPED_TWO_HANDS); eq.setIsEquipped(true, this); } else if (aLoc.equals(Constants.S_SHIELD)) { eq.setLocation(Equipment.EQUIPPED_NEITHER); eq.setNumberEquipped(num.intValue()); } } else { eq.setLocation(Equipment.EQUIPPED_NEITHER); eq.setIsEquipped(true, this); eq.setNumberCarried(num); eq.setQty(num); } if ((aNote != null) && (aNote.length() > 0)) { eq.setNote(aNote); } addLocalEquipment(eq); } // loop through all equipment and make sure that // containers contents are updated for (Equipment eq : getEquipmentList()) { if (eq.isContainer()) { eq.updateContainerContentsString(this); } // also make sure the masterList output order is // preserved as this equipmentList is a modified // clone of the original final Equipment anEquip = getEquipmentNamed(eq.getName()); if (anEquip != null) { eq.setOutputIndex(anEquip.getOutputIndex()); } } // if temporary bonuses, read the bonus equipList if (useTempBonuses) { for (Equipment eq : getTempBonusItemList()) { // make sure that this EquipSet is the one // this temporary bonus item comes from // to make sure we keep them together final Equipment anEquip = getEquipmentNamed(eq.getName(), getEquipmentList()); if (anEquip != null) { eq.setQty(anEquip.getQty()); eq.setNumberCarried(anEquip.getCarried()); if (anEquip.isEquipped()) { if (eq.isWeapon()) { eq.put(IntegerKey.SLOTS, 0); eq.put(ObjectKey.CURRENT_COST, BigDecimal.ZERO); eq.put(ObjectKey.WEIGHT, BigDecimal.ZERO); eq.setLocation(anEquip.getLocation()); } else { // replace the orig item // with the bonus item eq.setLocation(anEquip.getLocation()); removeLocalEquipment(anEquip); anEquip.setIsEquipped(false, this); anEquip.setLocation(Equipment.NOT_CARRIED); anEquip.setNumberCarried(Float.valueOf(0)); } eq.setIsEquipped(true, this); eq.setNumberEquipped(1); } else { eq.put(ObjectKey.CURRENT_COST, BigDecimal.ZERO); eq.put(ObjectKey.WEIGHT, BigDecimal.ZERO); eq.setLocation(Equipment.EQUIPPED_TEMPBONUS); eq.setIsEquipped(false, this); } // Adding this type to be // correctly treated by Merge eq.setTypeInfo("TEMPORARY"); addLocalEquipment(eq); } } } // all done! } /** * * @param aPC */ public void setCalcFollowerBonus(final PlayerCharacter aPC) { setDirty(true); for (Follower aF : getFollowerList()) { final String rType = aF.getType().toUpperCase(); final String rName = aF.getRace().toUpperCase(); for (CompanionMod cm : Globals.getCompanionMods(rType)) { final String aType = cm.getType().toUpperCase(); final int iRace = cm.getLevel(rName); if (aType.equals(rType) && (iRace == 1)) { // Found race and type of follower // so add bonus to the master addCompanionMod(cm); cm.activateBonuses(aPC); } } } } /** * Adds a <tt>CompanionMod</tt> to the character. * * @param aMod * The <tt>CompanionMod</tt> to add. */ public void addCompanionMod(final CompanionMod aMod) { companionModList.add(aMod); } /** * Set the catchphrase * * @param aString */ public void setCatchPhrase(final String aString) { setStringFor(StringKey.CATCH_PHRASE, aString); } /** * Get the catchphrase * * @return catchphrase */ public String getCatchPhrase() { return getSafeStringFor(StringKey.CATCH_PHRASE); } /** * Get the class given a key * * @param aString * @return PCClass */ public PCClass getClassKeyed(final String aString) { for (PCClass aClass : classList) { if (aClass.getKeyName().equalsIgnoreCase(aString)) { return aClass; } } return null; } /** * Get the class list * * @return classList */ public ArrayList<PCClass> getClassList() { return classList; } /** * Set the cost pool * * @param i */ public void setCostPool(final int i) { costPool = i; } /** * Get the cost pool * * @return costPool */ public int getCostPool() { return costPool; } /** * Get the spell tracker * * @return spellTracker */ public PCSpellTracker getSpellTracker() { return spellTracker; } /** * Get a list of types that apply to this character * * @return a List of Strings where each String is a type that the character * has. The list returned will never be null * * @deprecated Use getRaceType() and getRacialSubTypes() instead */ @Deprecated public List<String> getTypes() { final List<String> list = new ArrayList<String>(); if (race != null) { list.add(race.getType()); } else { list.add("Humanoid"); } for (PCTemplate t : templateList) { list.add(t.getType()); } return list; } @Deprecated public String getCritterType() { final StringBuffer critterType = new StringBuffer(); // Not too sure about this if, but that's what the previous code // implied... if (race != null) { critterType.append(race.getType()); } else { critterType.append("Humanoid"); } if (!templateList.isEmpty()) { for (PCTemplate t : templateList) { final String aType = t.getType(); if (!"".equals(aType)) { critterType.append('|').append(aType); } } } return critterType.toString(); } /** * Returns a String with the characters Race Type (e.g. Humanoid). * * @return The character's race type or &quot;None&quot; */ public String getRaceType() { String raceType = Constants.s_NONE; if (race != null) { RaceType rt = race.get(ObjectKey.RACETYPE); if (rt != null) { raceType = rt.toString(); } } if (!companionModList.isEmpty()) { for (CompanionMod cm : companionModList) { final String aType = cm.getRaceType(); if (!Constants.EMPTY_STRING.equals(aType)) { raceType = aType; } } } if (!templateList.isEmpty()) { for (PCTemplate t : templateList) { RaceType rt = t.get(ObjectKey.RACETYPE); if (rt != null) { raceType = rt.toString(); } } } return raceType; } /** * Gets a <tt>List</tt> of racial subtypes for the character (e.g. Good). * * @return A unmodifiable <tt>List</tt> of subtypes. */ public Collection<String> getRacialSubTypes() { final ArrayList<String> racialSubTypes = new ArrayList<String>(); for (RaceSubType st : race.getSafeListFor(ListKey.RACESUBTYPE)) { racialSubTypes.add(st.toString()); } if (!templateList.isEmpty()) { List<RaceSubType> added = new ArrayList<RaceSubType>(); List<RaceSubType> removed = new ArrayList<RaceSubType>(); for (PCTemplate aTemplate : templateList) { added.addAll(aTemplate.getSafeListFor(ListKey.RACESUBTYPE)); removed.addAll(aTemplate.getSafeListFor(ListKey.REMOVED_RACESUBTYPE)); } for (RaceSubType st : added) { racialSubTypes.add(st.toString()); } for (RaceSubType st : removed) { racialSubTypes.remove(st.toString()); } } return Collections.unmodifiableList(racialSubTypes); } /** * Set the current equipment set name * * @param aName the name of the new current equipment set */ public void setCurrentEquipSetName(final String aName) { setStringFor(StringKey.CURRENT_EQUIP_SET_NAME, aName); } /** * Get the current equipment set name * * @return equipment set name */ public String getCurrentEquipSetName() { return getSafeStringFor(StringKey.CURRENT_EQUIP_SET_NAME); } /** * Get the deity * * @return deity */ public Deity getDeity() { return deity; } /** * Set the description * * @param aString */ public void setDescription(final String aString) { setStringFor(StringKey.DESCRIPTION, aString); } /** * Get the description * * @return description */ public String getDescription() { return getSafeStringFor(StringKey.DESCRIPTION); } /** * Selector * * @return description lst */ public String getDescriptionLst() { return descriptionLst; } /** * Sets the character changed since last save. * * @param dirtyState */ public void setDirty(final boolean dirtyState) { if (dirtyState) { cachedWeaponProfs = null; serial++; cache = new ObjectCache(); getVariableProcessor().setSerial(serial); setAggregateAbilitiesStable(null, false); } // TODO - This is kind of strange. We probably either only want to // notify our observers if we have gone from not dirty to dirty and not // the reverse case. At a minimum we should probably tell them the // state anyway. if (dirtyFlag != dirtyState) { dirtyFlag = dirtyState; setChanged(); notifyObservers(); } } /** * Gets whether the character has been changed since last saved. * * @return true if dirty */ public boolean isDirty() { return dirtyFlag; } /** * Returns the serial for the instance - every time something changes the * serial is incremented. Use to detect change in PlayerCharacter. * * @return serial */ public int getSerial() { return serial; } /** * @return display name */ public String getDisplayName() { final String custom = getTabName(); if (!Constants.EMPTY_STRING.equals(custom)) { return custom; } final StringBuffer displayName = new StringBuffer().append(getName()); // TODO - i18n switch (SettingsHandler.getNameDisplayStyle()) { case Constants.DISPLAY_STYLE_NAME: break; case Constants.DISPLAY_STYLE_NAME_CLASS: displayName.append(" the ").append(getDisplayClassName()); break; case Constants.DISPLAY_STYLE_NAME_RACE: displayName.append(" the ").append(getDisplayRaceName()); break; case Constants.DISPLAY_STYLE_NAME_RACE_CLASS: displayName.append(" the ").append(getDisplayRaceName()) .append(' ').append(getDisplayClassName()); break; case Constants.DISPLAY_STYLE_NAME_FULL: return getFullDisplayName(); default: break; // custom broken } return displayName.toString(); } /** * set display update TODO - This probably doesn't belong here. It seems to * only be used by InfoSkills. * * @param aDisplayUpdate */ public void setDisplayUpdate(final boolean aDisplayUpdate) { this.displayUpdate = aDisplayUpdate; } /** * is display update * * @return True if display update */ public boolean isDisplayUpdate() { return displayUpdate; } /** * Get the list of equipment sets * * @return List */ public List<EquipSet> getEquipSet() { return equipSetList; } /** * Get the equipment set given id * * @param id * @return EquipSet */ public EquipSet getEquipSetByIdPath(final String id) { if (equipSetList.isEmpty()) { return null; } for (EquipSet eSet : equipSetList) { if (eSet.getIdPath().equals(id)) { return eSet; } } return null; } /** * Get the equipment set by name * * @param aName * @return Equip set */ public EquipSet getEquipSetByName(final String aName) { if (equipSetList.isEmpty()) { return null; } for (EquipSet eSet : equipSetList) { if (eSet.getName().equals(aName)) { return eSet; } } return null; } /** * Set the number of the current equipset when exporting * * @param anInt */ public void setEquipSetNumber(final int anInt) { currentEquipSetNumber = anInt; setDirty(true); } /** * Get the equipment set number * * @return equipset number */ public int getEquipSetNumber() { return currentEquipSetNumber; } /** * gets the total weight in an EquipSet * * @param idPath * @return equipment set weight */ public double getEquipSetWeightDouble(final String idPath) { if (equipSetList.isEmpty()) { return 0.0; } double totalWeight = 0.0; for (EquipSet es : equipSetList) { final String abIdPath = idPath + EquipSet.PATH_SEPARATOR; final String esIdPath = es.getIdPath() + EquipSet.PATH_SEPARATOR; if (!esIdPath.startsWith(abIdPath)) { continue; } final Equipment eqI = es.getItem(); if (eqI != null) { if ((eqI.getCarried().floatValue() > 0.0f) && (eqI.getParent() == null)) { if (eqI.getChildCount() > 0) { totalWeight += (eqI.getWeightAsDouble(this) + eqI .getContainedWeight(this).floatValue()); } else { totalWeight += (eqI.getWeightAsDouble(this) * eqI.getCarried() .floatValue()); } } } } return totalWeight; } /** * Count the total number of items of aName within EquipSet idPath * * @param idPath * @param aName * @return equipment set count */ public Float getEquipSetCount(final String idPath, final String aName) { float count = 0; for (EquipSet eSet : getEquipSet()) { final String esID = eSet.getIdPath() + EquipSet.PATH_SEPARATOR; final String abID = idPath + EquipSet.PATH_SEPARATOR; if (esID.startsWith(abID)) { if (eSet.getValue().equals(aName)) { count += eSet.getQty().floatValue(); } } } return Float.valueOf(count); } /** * List of Equipment objects * * @param eqList */ public void setEquipmentList(final List<Equipment> eqList) { equipmentList = eqList; } /** * Get equipment list * * @return equipment list */ public List<Equipment> getEquipmentList() { return equipmentList; } /** * Retrieves a list of the character's equipment in output order. This is in * ascending order of the equipment's outputIndex field. If multiple items * of equipment have the same outputIndex they will be ordered by name. Note * hidden items (outputIndex = -1) are not included in this list. * * @return An ArrayList of the equipment objects in output order. */ public List<Equipment> getEquipmentListInOutputOrder() { return sortEquipmentList(getEquipmentList()); } /** * Retrieves a list of the character's equipment in output order. This is in * ascending order of the equipment's outputIndex field. If multiple items * of equipment have the same outputIndex they will be ordered by name. Note * hidden items (outputIndex = -1) are not included in this list. * * Deals with merge as well * * @param merge * * @return An ArrayList of the equipment objects in output order. */ public List<Equipment> getEquipmentListInOutputOrder(final int merge) { return sortEquipmentList(getEquipmentList(), merge); } /** * Get equipment master list * * @return equipment master list */ public List<Equipment> getEquipmentMasterList() { final List<Equipment> aList = new ArrayList<Equipment>(equipmentMasterList); // Try all possible POBjects for (final PObject aPObj : getPObjectList()) { if (aPObj != null) { aPObj.addAutoTagsToList("EQUIP", aList, this, true); } } return aList; } /** * Get equipment master list in output order * * @return equipment master list in output order */ public List<Equipment> getEquipmentMasterListInOutputOrder() { final List<Equipment> l = getEquipmentMasterList(); Collections.sort(l, CoreUtility.equipmentComparator); return l; } /** * Search for a piece of equipment in the specified list by name. * * TODO - This does not belong in PlayerCharacter. Move to Equipment if * needed. * * TODO - This probably won't work with i18n. Should always search by key. * * @param aString * The name of the equipment. * @param aList * The list of equipment to search in. * * @return The <tt>Equipment</tt> object or <tt>null</tt> */ public Equipment getEquipmentNamed(final String aString, final List<Equipment> aList) { Equipment match = null; for (Equipment eq : aList) { if (aString.equalsIgnoreCase(eq.getName())) { match = eq; } } return match; } /** * Set the characters eye colour * * @param aString * the colour of their eyes */ public void setEyeColor(final String aString) { setStringFor(StringKey.EYE_COLOR, aString); } /** * Get the characters eye colour * * @return the colour of their eyes */ public String getEyeColor() { return getSafeStringFor(StringKey.EYE_COLOR); } /** * Get a number that represents the number of feats added to this character * by BONUS statements. * * @return the number of feats added by bonus statements */ private double getBonusFeatPool() { String aString = Globals.getBonusFeatString(); final StringTokenizer aTok = new StringTokenizer(aString, Constants.PIPE, false); final int startLevel = Integer.parseInt(aTok.nextToken()); final int rangeLevel = Integer.parseInt(aTok.nextToken()); // TODO - Should this stuff follow stacking rules? // TODO - Does it even matter anymore. // double pool = getBonusValue("FEAT", "POOL"); // double pcpool = getBonusValue("FEAT", "PCPOOL"); // double mpool = getBonusValue("FEAT", "MONSTERPOOL"); double pool = getTotalBonusTo("FEAT", "POOL"); double pcpool = getTotalBonusTo("FEAT", "PCPOOL"); double mpool = getTotalBonusTo("FEAT", "MONSTERPOOL"); double bonus = getTotalBonusTo("ABILITYPOOL", "FEAT"); Logging.debugPrint(""); //$NON-NLS-1$ Logging.debugPrint("=============="); //$NON-NLS-1$ Logging.debugPrint("level " + this.getTotalPlayerLevels()); //$NON-NLS-1$ Logging.debugPrint("POOL: " + pool); //$NON-NLS-1$ Logging.debugPrint("PCPOOL: " + pcpool); //$NON-NLS-1$ Logging.debugPrint("MPOOL: " + mpool); //$NON-NLS-1$ Logging.debugPrint("APOOL: " + bonus); //$NON-NLS-1$ double startAdjust = rangeLevel == 0 ? 0 : startLevel / rangeLevel; pool += Math.floor((this.getTotalCharacterLevel() >= startLevel) ? 1.0d + pcpool - startAdjust + 0.0001 : pcpool + 0.0001); pool += Math.floor(mpool + 0.0001); pool += Math.floor(bonus + 0.0001); Logging.debugPrint(""); //$NON-NLS-1$ Logging.debugPrint("Total Bonus: " + pool); //$NON-NLS-1$ Logging.debugPrint("=============="); //$NON-NLS-1$ Logging.debugPrint(""); //$NON-NLS-1$ return pool; } /** * Checks whether a PC is allowed to level up. A PC is not allowed to level * up if the "Enforce Spending" option is set and he still has unallocated * skill points and/or feat slots remaining. This can be used to enforce * correct spending of these resources when creating high-level multiclass * characters. * * @return true if the PC can level up */ public boolean canLevelUp() { if (SettingsHandler.getEnforceSpendingBeforeLevelUp() && (getSkillPoints() > 0 || getFeats() > 0)) { return false; } return true; } /** * Sets the filename of the character. * * @param newFileName */ public void setFileName(final String newFileName) { setStringFor(StringKey.FILE_NAME, newFileName); } /** * Gets the filename of the character. * * @return file name of character */ public String getFileName() { return getSafeStringFor(StringKey.FILE_NAME); } /** * Returns the followers associated with this character. * * @return A <tt>List</tt> of <tt>Follower</tt> objects. */ public List<Follower> getFollowerList() { return followerList; } /** * Returns a very descriptive name for the character. * * <p> * The format is [name] the [level]th level [race name] [classes] * * @return A descriptive string name for the character. */ public String getFullDisplayName() { final int levels = getTotalLevels(); // If you aren't multi-classed, don't display redundant class level // information in addition to the total PC level return new StringBuffer().append(getName()).append(" the ").append( levels).append(getOrdinal(levels)).append(" level ").append( getDisplayRaceName()).append(' ').append( (classList.size() < 2) ? getDisplayClassName() : getFullDisplayClassName()).toString(); } /** * Returns a region (including subregion) string for the character. * * <p/> Build on-the-fly so removing templates won't mess up region * * @return character region */ public String getFullRegion() { final String sub = getSubRegion(); final StringBuffer tempRegName = new StringBuffer().append(getRegion()); if (!sub.equals(Constants.s_NONE)) { tempRegName.append(" (").append(sub).append(')'); } return tempRegName.toString(); } /** * Sets the character's gender. * * <p> * The gender will only be set if the character does not have a template * that locks the character's gender. * * <p> * <b>WARNING:</b> This method has a side effect that it will actually set * the gender to the locked template gender. * * @param argGender * A gender to try and set. */ public void setGender(final String argGender) { final Gender g = findTemplateGender(); if (g == null) { gender = argGender; } else { gender = g.toString(); } setDirty(true); } /** * Returns a string for the character's gender. * * <p> * This method will return the stored gender or the template locked gender * if there is one. This means the <tt>setGender()</tt> side effect is not * really required. * * @return A <tt>String</tt> version of the character's gender. TODO - * Gender should be an object so it can be i18n. */ public String getGender() { final Gender tGender = findTemplateGender(); return tGender == null ? gender : tGender.toString(); } /** * Checks if the user is allowed to change the character's gender. * * <p> * That is, if no template with a gender lock has been specified. * * @return <tt>true</tt> if the user can freely set the character's * gender. */ public boolean canSetGender() { return findTemplateGender() == null; } /** * Sets the character's wealth. * * <p> * Gold here is used as a character's total purchase power not actual gold * pieces. * * @param aString * A String gold amount. TODO - Do this parsing elsewhere. */ public void setGold(final String aString) { gold = new BigDecimal(aString); setDirty(true); } /** * Returns the character's total wealth. * * @see pcgen.core.PlayerCharacter#setGold(String) * * @return A <tt>BigDecimal</tt> value for the character's wealth. */ public BigDecimal getGold() { return gold; } /** * Sets the character's hair color as a string. * * @param aString * The hair color to set. */ public void setHairColor(final String aString) { setStringFor(StringKey.HAIR_COLOR, aString); } /** * Gets the character's hair color. * * @return A hair color string. */ public String getHairColor() { return getSafeStringFor(StringKey.HAIR_COLOR); } /** * Sets the character's hair style. * * @param aString * A hair style. */ public void setHairStyle(final String aString) { setStringFor(StringKey.HAIR_STYLE, aString); } /** * Gets the character's hair style. * * @return The character's hair style. */ public String getHairStyle() { return getSafeStringFor(StringKey.HAIR_STYLE); } /** * Sets the character's handedness. * * @param aString * A String to use as a handedness. * * TODO - This should probably be an object as some systems may use the * information. */ public void setHanded(final String aString) { setStringFor(StringKey.HANDED, aString); } /** * Returns the character's handedness string. * * @return A String for handedness. */ public String getHanded() { return getSafeStringFor(StringKey.HANDED); } /** * Sets the character's height in inches. * * @param i * A height in inches. * * TODO - This should be a double value stored in CM */ public void setHeight(final int i) { heightInInches = i; setDirty(true); } /** * Gets the character's height in inches. * * @return The character's height in inches. */ public int getHeight() { return heightInInches; } /** * Marks the character as being in the process of being loaded. * * <p> * This information is used to prevent the system from trying to calculate * values on partial information or values that should be set from the saved * character. * * <p> * TODO - This is pretty dangerous. * * @param newIsImporting * <tt>true</tt> to mark the character as being imported. */ public void setImporting(final boolean newIsImporting) { this.importing = newIsImporting; } /** * Sets the character's interests. * * @param aString * A string of interests for the character. */ public void setInterests(final String aString) { setStringFor(StringKey.INTERESTS, aString); } /** * Gets a string of interests for the character. * * @return A String of interests or an empty string. */ public String getInterests() { return getSafeStringFor(StringKey.INTERESTS); } /** * Gets the character's list of languages. * * @return An unmodifiable language set. */ public SortedSet<Language> getLanguagesList() { return Collections.unmodifiableSortedSet(languages); } /** * Removes all the character's languages. */ public void clearLanguages() { languages.clear(); } /** * Adds a <tt>Collection</tt> of languages to the character. * * @param aList * A <tt>Collection</tt> of <tt>Language</tt> objects. */ public void addLanguages(final Collection<Language> aList) { languages.addAll(aList); } /** * TODO - This doesn't need to be a PlayerCharacter method. * * @return */ public String getLanguagesListNames() { final StringBuffer b = new StringBuffer(); for (final Language l : languages) { if (b.length() > 0) { b.append(", "); } b.append(l.toString()); } return b.toString(); } /** * Sets the character's location. * * @param aString * A location. */ public void setLocation(final String aString) { setStringFor(StringKey.LOCATION, aString); } /** * Gets the character's location. * * @return The character's location. */ public String getLocation() { return getSafeStringFor(StringKey.LOCATION); } /** * This method returns the effective level of this character for purposes of * applying companion mods to a companion of the specified type. * <p> * <b>Note</b>: This whole structure is kind of messed up since nothing * enforces that a companion mod of a given type always looks at the same * variable (either Class or Variable). * * @param compType * A type of companion to get level for * @return The effective level for this companion type */ public int getEffectiveCompanionLevel(final String compType) { final Collection<CompanionMod> mods = Globals.getCompanionMods(compType); for (CompanionMod cMod : mods) { for (Iterator<String> iType = cMod.getVarMap().keySet().iterator(); iType .hasNext();) { final String varName = iType.next(); final int lvl = this.getVariableValue(varName, Constants.EMPTY_STRING) .intValue(); if (lvl > 0) { return lvl; } } for (final String classKey : cMod.getClassMap().keySet()) { final int lvl = this.getClassKeyed(classKey).getLevel(); if (lvl > 0) { return lvl; } } } return 0; } /** * Removes all <tt>CompanionMod</tt>s from the character. */ public void clearCompanionMods() { companionModList.clear(); } /** * Set the master for this object also set the level dependent stats based * on the masters level and info contained in the companionModList Array * such as HitDie, SR, BONUS, SA, etc * * @param aM * The master to be set. */ public void setMaster(final Follower aM) { followerMaster = aM; final PlayerCharacter mPC = getMasterPC(); if (mPC == null) { return; } // make sure masters Name and fileName are correct if (!aM.getFileName().equals(mPC.getFileName())) { aM.setFileName(mPC.getFileName()); setDirty(true); } if (!aM.getName().equals(mPC.getName())) { aM.setName(mPC.getName()); setDirty(true); } // Get total wizard + sorcerer levels as they stack like a mother int mTotalLevel = 0; int addHD = 0; for (PCClass mClass : mPC.getClassList()) { boolean found = false; for (CompanionMod cMod : Globals.getCompanionMods(aM.getType())) { if ((cMod.getLevel(mClass.getKeyName()) > 0) && !found) { mTotalLevel += mClass.getLevel(); found = true; } } } List<CompanionMod> oldCompanionMods = new ArrayList<CompanionMod>(companionModList); List<CompanionMod> newCompanionMods = new ArrayList<CompanionMod>(); // Clear the companionModList so we can add everything to it clearCompanionMods(); for (CompanionMod cMod : Globals.getCompanionMods(aM.getType())) { // Check all the masters classes for (PCClass mClass : mPC.getClassList()) { final int mLev = mClass.getLevel() + aM.getAdjustment(); final int compLev = cMod.getLevel(mClass.getKeyName()); if (compLev < 0) { continue; } // This CompanionMod must be for this Class // and for the correct level or lower if ((compLev <= mLev) || (compLev <= mTotalLevel)) { if (PrereqHandler.passesAll(cMod.getPrerequisiteList(), this, cMod)) { if (!oldCompanionMods.contains(cMod)) { newCompanionMods.add(cMod); } addCompanionMod(cMod); addHD += cMod.getSafe(IntegerKey.HIT_DIE); } } } for (String varName : cMod.getVarMap().keySet()) { final int mLev = mPC.getVariableValue(varName, Constants.EMPTY_STRING) .intValue() + aM.getAdjustment(); if (mLev >= cMod.getLevel(varName)) { if (PrereqHandler.passesAll(cMod.getPrerequisiteList(), this, cMod)) { if (!oldCompanionMods.contains(cMod)) { newCompanionMods.add(cMod); } addCompanionMod(cMod); addHD += cMod.getSafe(IntegerKey.HIT_DIE); } } } } // Add additional HD if required LevelCommandFactory lcf = race.get(ObjectKey.MONSTER_CLASS); final int usedHD = followerMaster.getUsedHD(); addHD -= usedHD; // if ((newClass != null) && (addHD != 0)) if ((lcf != null) && (addHD != 0)) { // set the new HD (but only do it once!) incrementClassLevel(addHD, lcf.getPCClass(), true); followerMaster.setUsedHD(addHD + usedHD); setDirty(true); } // If it's a familiar, we need to change it's Skills if (getUseMasterSkill()) { final List<Skill> mList = mPC.getSkillList(); final List<String> sKeyList = new ArrayList<String>(); // now we have to merge the two lists together and // take the higher rank of each skill for the Familiar for (Skill fSkill : getAllSkillList(true)) { for (Skill mSkill : mList) { // first check to see if familiar // already has ranks in the skill if (mSkill.getKeyName().equals(fSkill.getKeyName())) { // need higher rank of the two if (mSkill.getRank().intValue() > fSkill.getRank() .intValue()) { // first zero current fSkill.setZeroRanks(lcf == null ? null : lcf.getPCClass(), this); // We don't pass in a class here so that the real // skills can be distinguished from the ones from // the master. fSkill.modRanks(mSkill.getRank().doubleValue(), null, true, this); } } // build a list of all skills a master // Possesses, but the familiar does not if (!hasSkill(mSkill.getKeyName()) && !sKeyList.contains(mSkill.getKeyName())) { sKeyList.add(mSkill.getKeyName()); } } } // now add all the skills only the master has for (String skillKey : sKeyList) { // familiar doesn't have skill, // but master does, so add it final Skill newSkill = Globals.getContext().ref.silentlyGetConstructedCDOMObject(Skill.class, skillKey).clone(); final double sr = mPC.getSkillKeyed(skillKey).getRank().doubleValue(); if ((newSkill.getChoiceString() != null) && (newSkill.getChoiceString().length() > 0)) { continue; } // We don't pass in a class here so that the real skills can be // distinguished from the ones form the master. newSkill.modRanks(sr, null, true, this); getSkillList().add(newSkill); } } // Setup the default EquipSet if not already present if (getEquipSet().size() == 0) { String id = getNewIdPath(null); EquipSet eSet = new EquipSet(id, PropertyFactory.getString("in_ieDefault")); addEquipSet(eSet); } oldCompanionMods.removeAll(companionModList); for (CompanionMod cMod : oldCompanionMods) { cMod.subAddsForLevel(-9, this); } for (CompanionMod cMod : newCompanionMods) { cMod.addAddsForLevel(-9, this, null); cMod.addAdds(this); for (CDOMReference<PCTemplate> ref : cMod.getSafeListFor(ListKey.TEMPLATE)) { for (PCTemplate pct : ref.getContainedObjects()) { addTemplate(pct); } } for (CDOMReference<PCTemplate> ref : cMod.getSafeListFor(ListKey.REMOVE_TEMPLATES)) { for (PCTemplate pct : ref.getContainedObjects()) { removeTemplate(pct); } } for (TransitionChoice<Kit> kit : cMod.getSafeListFor(ListKey.KIT_CHOICE)) { kit.act(kit.driveChoice(this), this); } } setDirty(true); } /** * Returns the maximum number of followers of the specified type this * character can have. This method does not adjust for any followers already * selected by the character. * * @param aType * The follower type to check e.g. Familiar * @return The max number of followers -1 for any number */ public int getMaxFollowers(final String aType) { int ret = -1; List<? extends PObject> pobjList = getPObjectList(); for (PObject pobj : pobjList) { if (pobj == null) { continue; } final List<String> formulas = pobj.getNumFollowers(aType); if (formulas == null) { continue; } for (String formula : formulas) { final int val = this.getVariableValue(formula, Constants.EMPTY_STRING, this).intValue(); ret = Math.max(ret, val); } } if (ret != -1) { // ret += (int)getBonusValue("FOLLOWERS", aType.toUpperCase()); ret += this.getTotalBonusTo("FOLLOWERS", aType.toUpperCase()); } else { // Old way of handling this // If the character qualifies for any companion mod of this type // they can take unlimited number of them. for (CompanionMod cMod : Globals.getCompanionMods(aType)) { for (String varName : cMod.getVarMap().keySet()) { if (this.getVariableValue(varName, Constants.EMPTY_STRING) .intValue() > 0) { return -1; } } for (String key : cMod.getClassMap().keySet()) { for (PCClass pcClass : getClassList()) { if (pcClass.getKeyName().equals(key)) { return -1; } } } } return 0; } return ret; } /** * Gets the list of potential followers of a given type. * * @param aType * Type of follower to retrieve list for e.g. Familiar * @return A List of FollowerOption objects representing the possible list * of follower choices. */ public List<FollowerOption> getAvailableFollowers(final String aType) { final List<FollowerOption> ret = new ArrayList<FollowerOption>(); final List<? extends PObject> pobjList = getPObjectList(); for (PObject pobj : pobjList) { if (pobj == null) { continue; } final List<FollowerOption> followers = pobj.getPotentialFollowers(aType); if (followers != null) { ret.addAll(followers); } } return ret; } /** * Get the Follower object that is the "master" for this object * * @return follower master */ public Follower getMaster() { return followerMaster; } /** * Get the PlayerCharacter that is the "master" for this object * * @return master PC */ public PlayerCharacter getMasterPC() { if (followerMaster == null) { return null; } for (PlayerCharacter nPC : Globals.getPCList()) { if (followerMaster.getFileName().equals(nPC.getFileName())) { return nPC; } } // could not find a filename match, let's try the Name for (PlayerCharacter nPC : Globals.getPCList()) { if (followerMaster.getName().equals(nPC.getName())) { return nPC; } } // no Name and no FileName match, so must not be loaded return null; } /** * Sets the character's name. * * @param aString * A name to set. */ public void setName(final String aString) { setStringFor(StringKey.NAME, aString); } /** * Gets the character's name. * * @return The name */ public String getName() { return getSafeStringFor(StringKey.NAME); } /** * Takes all the Temporary Bonuses and Merges them into just the unique * named bonuses. * * @return List of Strings */ public List<String> getNamedTempBonusList() { final List<String> aList = new ArrayList<String>(); for (BonusObj aBonus : getTempBonusList()) { if (aBonus == null) { continue; } if (!aBonus.isApplied()) { continue; } final PObject aCreator = (PObject) aBonus.getCreatorObject(); if (aCreator == null) { continue; } final String aName = aCreator.getKeyName(); if (!aList.contains(aName)) { aList.add(aName); } } return aList; } /** * Takes all the Temporary Bonuses and Merges them into just the unique * named bonuses. * * @return List of Strings */ public List<String> getNamedTempBonusDescList() { final List<String> aList = new ArrayList<String>(); for (BonusObj aBonus : getTempBonusList()) { if (aBonus == null) { continue; } if (!aBonus.isApplied()) { continue; } final PObject aCreator = (PObject) aBonus.getCreatorObject(); if (aCreator == null) { continue; } final String aDesc = aCreator.getDescription(); if (!aList.contains(aDesc)) { aList.add(aDesc); } } return aList; } /** * @return nonProficiencyPenalty. Searches templates first. */ public int getNonProficiencyPenalty() { int npp = Globals.getGameModeNonProfPenalty(); for (PCTemplate t : templateList) { Integer temp = t.get(IntegerKey.NONPP); if (temp != null) { npp = temp; } } return npp; } /** * Gets a list of notes associated with the character. * * @return A list of <tt>NoteItem</tt> objects. */ public ArrayList<NoteItem> getNotesList() { return notesList; } /** * Sets a string of phobias for the character. * * @param aString * A string to set. */ public void setPhobias(final String aString) { setStringFor(StringKey.PHOBIAS, aString); } /** * Gets the phobia string for the character. * * @return A phobia string. */ public String getPhobias() { return getSafeStringFor(StringKey.PHOBIAS); } /** * Sets the name of the player for this character. * * @param aString * A name to set. */ public void setPlayersName(final String aString) { setStringFor(StringKey.PLAYERS_NAME, aString); } /** * Gets the name of the player for this character. * * @return The player's name. */ public String getPlayersName() { return getSafeStringFor(StringKey.PLAYERS_NAME); } public void setPoolAmount(final int anInt) { poolAmount = anInt; } public int getPoolAmount() { return poolAmount; } /** * Selector Sets the path to the portrait of the character. * * @param newPortraitPath * the path to the portrait file */ public void setPortraitPath(final String newPortraitPath) { setStringFor(StringKey.PORTRAIT_PATH, newPortraitPath); } /** * Selector Gets the path to the portrait of the character. * * @return the path to the portrait file */ public String getPortraitPath() { return getSafeStringFor(StringKey.PORTRAIT_PATH); } /** * Selector * * @return primary weapons */ public List<Equipment> getPrimaryWeapons() { return primaryWeapons; } /** * Get race * * @return race */ public Race getRace() { return race; } /** * Set region * * @param arg */ public void setRegion(final String arg) { setStringFor(StringKey.REGION, arg); } /** * Set sub region * * @param aString */ public void setSubRegion(final String aString) { setStringFor(StringKey.SUB_REGION, aString); } /** * Selector <p/> Build on-the-fly so removing templates won't mess up region * * @return character region */ public String getRegion() { return getRegion(true); } /** * Get region * * @param useTemplates * @return region */ public String getRegion(final boolean useTemplates) { String pcRegion = getStringFor(StringKey.REGION); if ((pcRegion != null) || !useTemplates) { return pcRegion; // character's region trumps any from templates } String r = Constants.s_NONE; for (int i = 0, x = templateList.size(); i < x; ++i) { final PCTemplate template = templateList.get(i); final String tempRegion = template.getRegion(); if (!tempRegion.equals(Constants.s_NONE)) { r = tempRegion; } } return r; } /** * Set residence * * @param aString */ public void setResidence(final String aString) { setStringFor(StringKey.RESIDENCE, aString); } /** * Get residence * * @return residence */ public String getResidence() { return getSafeStringFor(StringKey.RESIDENCE); } /** * Selector * * @return secondary weapons */ public List<Equipment> getSecondaryWeapons() { return secondaryWeapons; } /** * Get HTML sheet for selected character * * @param aString */ public void setSelectedCharacterHTMLOutputSheet(final String aString) { outputSheetHTML = aString; } /** * Location of HTML Output Sheet * * @return HTML output sheet */ public String getSelectedCharacterHTMLOutputSheet() { return outputSheetHTML; } /** * Set selected PDF character sheet for character * * @param aString */ public void setSelectedCharacterPDFOutputSheet(final String aString) { outputSheetPDF = aString; } /** * Location of PDF Output Sheet * * @return pdf output sheet */ public String getSelectedCharacterPDFOutputSheet() { return outputSheetPDF; } /** * Get list of shield proficiencies * * @return shield prof list */ public List<String> getShieldProfList() { final List<String> autoShieldProfList = getAutoShieldProfList(); addShieldProfs(autoShieldProfList); return shieldProfList; } /** * Get size * * @return size */ public String getSize() { final SizeAdjustment sa = getSizeAdjustment(); if (sa != null) { return sa.getAbbreviation(); } return " "; } /** * Get skill list * * @return list of skills */ public ArrayList<Skill> getSkillList() { return getAllSkillList(false); } /** * Retrieves a list of the character's skills in output order. This is in * ascending order of the skill's outputIndex field. If skills have the same * outputIndex they will be ordered by name. Note hidden skills (outputIndex = * -1) are not included in this list. * * @return An ArrayList of the skill objects in output order. */ public ArrayList<Skill> getSkillListInOutputOrder() { return getSkillListInOutputOrder(new ArrayList<Skill>(getSkillList())); } /** * Retrieves a list of the character's skills in output order. This is in * ascending order of the skill's outputIndex field. If skills have the same * outputIndex they will be ordered by name. Note hidden skills (outputIndex = * -1) are not included in this list. * * Deals with sorted list * * @param sortedList * * @return An ArrayList of the skill objects in output order. */ public ArrayList<Skill> getSkillListInOutputOrder( final ArrayList<Skill> sortedList) { Collections.sort(sortedList, new Comparator<Skill>() { /** * Comparator will be specific to Skill objects */ public int compare(final Skill skill1, final Skill skill2) { int obj1Index = skill1.getOutputIndex(); int obj2Index = skill2.getOutputIndex(); // Force unset items (index of 0) to appear at the end if (obj1Index == 0) { obj1Index = 999; } if (obj2Index == 0) { obj2Index = 999; } if (obj1Index > obj2Index) { return 1; } else if (obj1Index < obj2Index) { return -1; } else { return skill1.getOutputName().compareToIgnoreCase( skill2.getOutputName()); } } }); // Remove the hidden skills from the list for (Iterator<Skill> i = sortedList.iterator(); i.hasNext();) { final Skill bSkill = i.next(); Visibility skVis = bSkill.getSafe(ObjectKey.VISIBILITY); if (bSkill.getOutputIndex() == -1 || skVis.equals(Visibility.HIDDEN) || skVis.equals(Visibility.DISPLAY_ONLY)) { i.remove(); } } return sortedList; } /** * Set skill points * * @param anInt */ public void setSkillPoints(final int anInt) { setDirty(true); } /** * Get skill points * * @return skill points */ public int getSkillPoints() { int returnValue = 0; // First compute gained points, and then remove the already spent ones. // We can't use Remaining points because the level may be removed, and // then we have // to display this as -x on the "Total Skill Points" field for (PCLevelInfo li : getLevelInfo()) { returnValue += li.getSkillPointsGained(); } final List<Skill> skillList = new ArrayList<Skill>(getSkillList()); for (Skill aSkill : skillList) { for (String bSkill : aSkill.getRankList()) { final int iOffs = bSkill.indexOf(':'); final double curRank = Double.parseDouble(bSkill.substring(iOffs + 1)); final PCClass pcClass = getClassKeyed(bSkill.substring(0, iOffs)); if (pcClass != null) { // Only add the cost for skills associated with a class. // Skill ranks from feats etc are free. final int cost = this.getSkillCostForClass(aSkill, pcClass).getCost(); returnValue -= (int) (cost * curRank); } } } if (Globals.getGameModeHasPointPool()) { returnValue += (int) getRawFeats(false); // DO NOT CALL // getFeats() here! It // will set up a // recursive loop and // result in a stack // overflow! } return returnValue; } /** * Set skin colour * * @param aString */ public void setSkinColor(final String aString) { setStringFor(StringKey.SKIN_COLOR, aString); } /** * Get skin colour * * @return skin colour */ public String getSkinColor() { return getSafeStringFor(StringKey.SKIN_COLOR); } /** * Get list of special abilities * * @return List of special abilities */ public List<SpecialAbility> getSpecialAbilityList() { // aList will contain a list of SpecialAbility objects List<SpecialAbility> aList = new ArrayList<SpecialAbility>(specialAbilityList); // Try all possible POBjects for (PObject aPObj : getPObjectList()) { if (aPObj == null) { continue; } aPObj.addSpecialAbilitiesToList(aList, this); aPObj.addSABToList(aList, this); } // for (CDOMObject cdo : getCDOMObjectList()) // { // //TODO this is for once SAB: is converted to new token style // } for (PObject po : getConditionalTemplateObjects()) { po.addSABToList(aList, this); } Collections.sort(aList); return aList; } /** * Get list of special abilities as Strings * * @return List of special abilities as Strings */ public List<String> getSpecialAbilityListStrings() { final ArrayList<String> bList = new ArrayList<String>(); for (SpecialAbility sa : getSpecialAbilityList()) { if (!PrereqHandler.passesAll(sa.getPrerequisiteList(), this, sa)) { continue; } final String saText = sa.getParsedText(this, this); if (saText != null && !saText.equals("")) { bList.add(saText); } } return bList; } /** * same as getSpecialAbilityList except if if you have the same ability * twice, it only lists it once with (2) at the end. * * @return List */ public ArrayList<String> getSpecialAbilityTimesList() { final List<String> abilityList = getSpecialAbilityListStrings(); final List<String> sortList = new ArrayList<String>(); final int[] numTimes = new int[abilityList.size()]; for (int i = 0; i < abilityList.size(); i++) { final String ability = abilityList.get(i); if (!sortList.contains(ability)) { sortList.add(ability); numTimes[i] = 1; } else { for (int j = 0; j < sortList.size(); j++) { final String testAbility = sortList.get(j); if (testAbility.equals(ability)) { numTimes[j]++; } } } } final ArrayList<String> retList = new ArrayList<String>(); for (int i = 0; i < sortList.size(); i++) { String ability = sortList.get(i); if (numTimes[i] > 1) { ability = ability + " (" + numTimes[i] + ")"; } retList.add(ability); } return retList; } /** * Set speech tendency * * @param aString */ public void setSpeechTendency(final String aString) { setStringFor(StringKey.SPEECH_TENDENCY, aString); } /** * Get speech tendency * * @return speech tendency */ public String getSpeechTendency() { return getSafeStringFor(StringKey.SPEECH_TENDENCY); } /** * Set the name of the spellbook to auto add new known spells to. * * @param aString * The new spellbook name. */ public void setSpellBookNameToAutoAddKnown(final String aString) { setStringFor(StringKey.SPELLBOOK_AUTO_ADD_KNOWN, aString); } /** * Get the name of the spellbook to auto add new known spells to. * * @return spellbook name */ public String getSpellBookNameToAutoAddKnown() { return getSafeStringFor(StringKey.SPELLBOOK_AUTO_ADD_KNOWN); } /** * Retrieve a spell book object given the name of the spell book. * * @param name * The name of the spell book to be retrieved. * @return The spellbook (or null if not present). */ public SpellBook getSpellBookByName(final String name) { return spellBookMap.get(name); } /** * Get spell books * * @return spellBooks */ public List<String> getSpellBooks() { return spellBooks; } /** * Get spell class given an index * * @param ix * @return spell class */ public PObject getSpellClassAtIndex(final int ix) { final List<? extends PObject> aList = getSpellClassList(); if ((ix >= 0) && (ix < aList.size())) { return aList.get(ix); } return null; } /** * a temporary placeholder used for computing the DC of a spell Set from * within Spell.java before the getVariableValue() call * * @param i */ public void setSpellLevelTemp(final int i) { // Explicitly should *not* set the dirty flag to true. spellLevelTemp = i; } /** * Get spell level temp * * @return temp spell level */ public int getSpellLevelTemp() { return spellLevelTemp; } /** * Get the stat list * * @return stat list */ public StatList getStatList() { return statList; } /** * Selector <p/> Build on-the-fly so removing templates won't mess up * subrace * * @return character subrace */ public String getSubRace() { String subRace = Constants.s_NONE; for (int i = 0, x = templateList.size(); i < x; ++i) { final PCTemplate template = templateList.get(i); final String tempSubRace = template.getSubRace(); if (!tempSubRace.equals(Constants.s_NONE)) { subRace = tempSubRace; } } return subRace; } /** * Selector <p/> Build on-the-fly so removing templates won't mess up sub * region * * @return character sub region */ public String getSubRegion() { return getSubRegion(true); } /** * Set the name on the tab * * @param aString */ public void setTabName(final String aString) { tabName = aString; setDirty(true); setChanged(); notifyObservers("TabName"); } /** * Get tab name * * @return name on tab */ public String getTabName() { return tabName; } /** * Temporary Bonuses */ /** * List if Items which have Temp Bonuses applied to them * * @return List */ private List<Equipment> getTempBonusItemList() { return tempBonusItemList; } /** * Set temp bonus list * * @param aList */ public void setTempBonusList(final List<BonusObj> aList) { tempBonusList = aList; setDirty(true); } /** * Temp Bonus list * * @return List */ public List<BonusObj> getTempBonusList() { return tempBonusList; } /** * get filtered temp bonus list * * @return filtered temp bonus list */ public List<BonusObj> getFilteredTempBonusList() { final List<BonusObj> ret = new ArrayList<BonusObj>(); for (BonusObj bonus : getTempBonusList()) { if (!tempBonusFilters.contains(bonus.getName())) { ret.add(bonus); } } return ret; } /** * get temp bonus filters * * @return temp bonus filters */ public Set<String> getTempBonusFilters() { return tempBonusFilters; } /** * Clear temp bonus filters * */ public void clearTempBonusFilters() { tempBonusFilters.clear(); } /** * set temp bonus filter * * @param aBonusStr */ public void setTempBonusFilter(final String aBonusStr) { tempBonusFilters.add(aBonusStr); calcActiveBonuses(); } /** * unset temp bonus filter * * @param aBonusStr */ public void unsetTempBonusFilter(final String aBonusStr) { tempBonusFilters.remove(aBonusStr); calcActiveBonuses(); } /** * Given a Source and a Target object, get a list of BonusObj's * * @param aCreator * @param aTarget * * @return List of BonusObj */ public List<BonusObj> getTempBonusList(final String aCreator, final String aTarget) { final List<BonusObj> aList = new ArrayList<BonusObj>(); for (BonusObj bonus : getTempBonusList()) { final Object aTO = bonus.getTargetObject(); final Object aCO = bonus.getCreatorObject(); String targetName = Constants.EMPTY_STRING; String creatorName = Constants.EMPTY_STRING; if (aCO instanceof PObject) { creatorName = ((PObject) aCO).getKeyName(); } if (aTO instanceof PlayerCharacter) { targetName = getName(); } else if (aTO instanceof PObject) { targetName = ((PObject) aTO).getKeyName(); } if (creatorName.equals(aCreator) && targetName.equals(aTarget)) { aList.add(bonus); } } return aList; } /** * Get the list of Templates applied to this PC * * @return List of templates */ public ArrayList<PCTemplate> getTemplateList() { return templateList; } /** * Retrieve a list of the templates applied to this PC that should be * visible on output. * * @return The list of templates visible on output sheets. */ public List<PCTemplate> getOutputVisibleTemplateList() { List<PCTemplate> tl = new ArrayList<PCTemplate>(); for (PCTemplate template : getTemplateList()) { if ((template.getSafe(ObjectKey.VISIBILITY) == Visibility.DEFAULT) || (template.getSafe(ObjectKey.VISIBILITY) == Visibility.OUTPUT_ONLY)) { tl.add(template); } } return tl; } /** * Get the template keyed aKey from this PC * * @param aKey * * @return PC template or null if not found */ public PCTemplate getTemplateKeyed(final String aKey) { for (PCTemplate template : templateList) { if (template.getKeyName().equalsIgnoreCase(aKey)) { return template; } } return null; } /** * Set trait 1 * * @param aString */ public void setTrait1(final String aString) { setStringFor(StringKey.TRAIT1, aString); } /** * Get trait 1 * * @return trait 1 */ public String getTrait1() { return getSafeStringFor(StringKey.TRAIT1); } /** * Set trait 2 * * @param aString */ public void setTrait2(final String aString) { setStringFor(StringKey.TRAIT2, aString); } /** * Get trait 2 * * @return trait 2 */ public String getTrait2() { return getSafeStringFor(StringKey.TRAIT2); } /** * Most of the time when you're looking up a PC variable you want the * standard behaviour (i.e. you don't care about the source and you want * the search to recurse and you want the result to include any bonuses * that have been defined for this variable). If that's what you want, * this is the routine to call. * * note: most of the code was calling the method this delegates to and * passing the exact same five constant values. * * @param variableString The variable to lookup and return the value of. * @return Float */ public Float getVariable(final String variableString) { return getVariable(variableString, true, true, "", "", true); } public Float getVariable( final String variableString, final boolean isMax, final String matchSrc, final String matchSubSrc) { return getVariable(variableString, isMax, true, matchSrc, matchSubSrc, true); } private double getMinMaxFirstValue(final boolean isNewValue, final boolean isMax, final double oldValue, final double newValue) { if (!isNewValue) return newValue; if (isMax) return Math.max(oldValue, newValue); return Math.min(oldValue, newValue); } /** * Should probably be refactored to return a String instead. TODO This * should call getPObjectList() to get a list of PObjects to test against. I * don't want to change the behaviour for now however. * * @param variableString * @param isMax * @param includeBonus * Should bonus tokens be added to this variables value * @param matchSrc * @param matchSubSrc * @param recurse * @return Float */ public Float getVariable( final String variableString, final boolean isMax, boolean includeBonus, final String matchSrc, final String matchSubSrc, final boolean recurse) { double value = 0.0; boolean found = false; if (lastVariable != null) { if (lastVariable.equals(variableString)) { StringBuffer sb = new StringBuffer(256); sb .append("This is a deliberate warning message, not an error - "); sb .append("Avoiding infinite loop in getVariable: repeated lookup "); sb.append("of \"").append(lastVariable).append("\" at ") .append(value); Logging.debugPrint(sb.toString()); lastVariable = null; return new Float(value); } } for (String vString : variableList) { final StringTokenizer aTok = new StringTokenizer(vString, Constants.PIPE); final String src = aTok.nextToken(); if ((matchSrc.length() > 0) && !src.equals(matchSrc)) { continue; } final String subSrc = aTok.nextToken(); if ((matchSubSrc.length() > 0) && !subSrc.equals(matchSubSrc)) { continue; } final String nString = aTok.nextToken(); if (nString.equalsIgnoreCase(variableString)) { final String sString = aTok.nextToken(); final double newValue = getVariableValue(sString, src).doubleValue(); value = getMinMaxFirstValue(found, isMax, value, newValue); found = true; } } // Now check Abilities to see if they modify the variable Set<Ability> abilitySet = getFullAbilitySet(); for (Ability obj : abilitySet) { final String varInList = checkForVariableInList(obj, variableString, isMax, found, value); if (varInList.length() > 0) { value = getMinMaxFirstValue(found, isMax, value, Float .parseFloat(varInList)); found = true; } } final List<Skill> skillList = new ArrayList<Skill>(getSkillList()); for (Skill obj : skillList) { final String varInList = checkForVariableInList(obj, variableString, isMax, found, value); if (varInList.length() > 0) { value = getMinMaxFirstValue(found, isMax, value, Float .parseFloat(varInList)); found = true; } } for (Equipment obj : equipmentList) { final String eS = checkForVariableInList(obj, variableString, isMax, found, value); if (eS.length() > 0) { value = getMinMaxFirstValue(found, isMax, value, Float .parseFloat(eS)); found = true; } for (EquipmentModifier em : (obj.getEqModifierList(true))) { final String varInList = checkForVariableInList(em, variableString, isMax, found, value); if (varInList.length() > 0) { value = getMinMaxFirstValue(found, isMax, value, Float .parseFloat(varInList)); found = true; } } for (EquipmentModifier em : (obj.getEqModifierList(false))) { final String varInList = checkForVariableInList(em, variableString, isMax, found, value); if (varInList.length() > 0) { value = getMinMaxFirstValue(found, isMax, value, Float .parseFloat(varInList)); found = true; } } } for (PCTemplate obj : templateList) { final String aString = checkForVariableInList(obj, variableString, isMax, found, value); if (aString.length() > 0) { value = getMinMaxFirstValue(found, isMax, value, Float .parseFloat(aString)); found = true; } } for (CompanionMod obj : companionModList) { final String aString = checkForVariableInList(obj, variableString, isMax, found, value); if (aString.length() > 0) { value = getMinMaxFirstValue(found, isMax, value, Float .parseFloat(aString)); found = true; } } if (race != null) { final String aString = checkForVariableInList(race, variableString, isMax, found, value); if (aString.length() > 0) { value = getMinMaxFirstValue(found, isMax, value, Float .parseFloat(aString)); found = true; } } if (deity != null) { final String aString = checkForVariableInList(deity, variableString, isMax, found, value); if (aString.length() > 0) { value = getMinMaxFirstValue(found, isMax, value, Float .parseFloat(aString)); found = true; } } for (CharacterDomain obj : characterDomainList) { if (obj.getDomain() == null) { continue; } final String aString = checkForVariableInList(obj.getDomain(), variableString, isMax, found, value); if (aString.length() > 0) { value = getMinMaxFirstValue(found, isMax, value, Float .parseFloat(aString)); found = true; } } // for ( final WeaponProf obj : getWeaponProfs() ) // { // if (obj == null) // { // continue; // } // // final String aString = checkForVariableInList(obj, variableString, // isMax, Constants.EMPTY_STRING, // Constants.EMPTY_STRING, found, // value, decrement); // // if (aString.length() > 0) // { // value = getMinMaxFirstValue(found, isMax, value, // Float.parseFloat(aString)); // found = true; // } // } for (PCStat obj : statList) { final String aString = checkForVariableInList(obj, variableString, isMax, found, value); if (aString.length() > 0) { value = getMinMaxFirstValue(found, isMax, value, Float .parseFloat(aString)); found = true; } } for (PCAlignment obj : SettingsHandler.getGame() .getUnmodifiableAlignmentList()) { final String aString = checkForVariableInList(obj, variableString, isMax, found, value); if (aString.length() > 0) { value = getMinMaxFirstValue(found, isMax, value, Float .parseFloat(aString)); found = true; } } if (!found) { if (recurse) { lastVariable = variableString; value = getVariableValue(variableString, Constants.EMPTY_STRING) .floatValue(); includeBonus = false; found = true; lastVariable = null; } else { return null; } } if (found && includeBonus) { value += getTotalBonusTo("VAR", variableString); } return new Float(value); } // /** // * Returns the <tt>Set</tt> of <tt>WeaponProf</tt> objects for the // character. // * // * @return A sorted <tt>Set</tt> of weapon proficiencies. // */ // public TreeSet<WeaponProf> getWeaponProfList() // { // final TreeSet<WeaponProf> wp = new TreeSet<WeaponProf>(weaponProfList); // // // Try all possible PObjects // for (PObject pobj : getPObjectList()) // { // if (pobj != null) // { // final List<String> profKeyList = // pobj.getSafeListFor(ListKey.SELECTED_WEAPON_PROF_BONUS); // for (String profKey : profKeyList) // { // final WeaponProf prof = Globals.getWeaponProfKeyed(profKey); // if (prof != null) // { // wp.add(prof); // } // } // } // } // // return wp; // } private Map<String, WeaponProf> buildWeaponProfCache() { final Map<String, WeaponProf> ret = new HashMap<String, WeaponProf>(); if (theWeaponProfs != null) { for (final WeaponProf wp : this.theWeaponProfs) { ret.put(wp.getKeyName(), wp); } } // Try all possible PObjects for (final PObject pobj : getPObjectList()) { if (pobj != null) { // results = addWeaponProfsLists(aRace.getWeaponProfAutos(), // results, aFeatList, true); // // for (String aString : // aRace.getSafeListFor(weaponProfBonusKey)) // { // results.add(aString); // addWeaponProfToList(aFeatList, aString, true); // } final Set<String> profKeyList = new TreeSet<String>(pobj .getSafeListFor(ListKey.SELECTED_WEAPON_PROF_BONUS)); // TODO - Need to handle more crap here. pobj.addAutoTagsToList("WEAPONPROF", profKeyList, this, true); // TODO: Selected bonus weapon prof is stored in the associated // list for (final String profKey : profKeyList) { final WeaponProf prof = Globals.getContext().ref.silentlyGetConstructedCDOMObject(WeaponProf.class, profKey); if (prof != null) { ret.put(prof.getKeyName(), prof); } } } } return ret; } public SortedSet<WeaponProf> getWeaponProfs() { if (this.cachedWeaponProfs == null) { cachedWeaponProfs = buildWeaponProfCache(); } return Collections.unmodifiableSortedSet(new TreeSet<WeaponProf>( cachedWeaponProfs.values())); } /** * Sets the character's weight in pounds. * * @param i * A weight to set. */ public void setWeight(final int i) { weightInPounds = i; setDirty(true); } /** * Gets the character's weight in pounds. * * @return The character's weight. */ public int getWeight() { return weightInPounds; } public void setPointBuyPoints(final int argPointBuyPoints) { pointBuyPoints = argPointBuyPoints; } public int getPointBuyPoints() { return pointBuyPoints + (int) getTotalBonusTo("POINTBUY", "POINTS"); } public void setXP(final int xp) { // Remove the effect of LEVELADJ when storing our // internal notion of experience int realXP = xp - getLAXP(); if (realXP < 0) { Logging.errorPrint("ERROR: too little experience: " + realXP); realXP = 0; } setEarnedXP(realXP); } public int getXP() { // Add the effect of LEVELADJ when // showing our external notion of XP. return earnedXP + getLAXP(); } public void addArmorProf(final String aProf) { if (!armorProfList.contains(aProf)) { // // Insert all types at the head of the list // if (aProf.startsWith("ARMORTYPE=") || aProf.startsWith("ARMORTYPE.")) { armorProfList.add(0, aProf); } else if (aProf.startsWith("TYPE=") || aProf.startsWith("TYPE.")) { armorProfList.add(0, aProf); } else { armorProfList.add(aProf); } } // setDirty(true); } public void addArmorProfs(final List<String> aList) { for (String prof : aList) { addArmorProf(prof); } } public void addEquipSet(final EquipSet set) { equipSetList.add(set); setDirty(true); } /** * Add an item of equipment to the character. * * @param eq * The equipment to be added. */ public void addEquipment(final Equipment eq) { equipmentList.add(eq); if (!equipmentMasterList.contains(eq)) { equipmentMasterList.add(eq); } if (eq.isType(Constants.s_TYPE_SPELLBOOK)) { String baseBookname = eq.getName(); String bookName = eq.getName(); int qty = (int) eq.qty(); for (int i = 0; i < qty; i++) { if (i > 0) { bookName = baseBookname + " #" + (i + 1); } SpellBook book = new SpellBook(bookName, SpellBook.TYPE_SPELL_BOOK); book.setEquip(eq); addSpellBook(book); } } setDirty(true); } /** * Cache the output index of an automatic equipment item. * @param key The key of the equipment item. * @param index The output index. */ public void cacheOutputIndex(String key, int index) { Logging.debugPrint("Caching " + key + " - " + index + " direct"); autoEquipOutputOrderCache.put(key, index); } /** * Cache the output index of an automatic equipment item. * @param item The equipment item. */ public void cacheOutputIndex(Equipment item) { if (item.isAutomatic()) { Logging.debugPrint("Caching " + item.getKeyName() + " - " + item.getOutputIndex() + " item"); autoEquipOutputOrderCache.put(item.getKeyName(), item .getOutputIndex()); } } /** * Retrieve the cached output idex of the automatic equipment item * @param key The key of the equipment item. * @return The output index. */ public int getCachedOutputIndex(String key) { Integer order = autoEquipOutputOrderCache.get(key); return order != null ? order : -1; } /** * Update the number of a particular equipment item the character possesses. * Mostly concerned with ensuring that the spellbook objects remain in sync * with the number of equipment spellbooks. * * @param eq * The Equipment being updated. * @param oldQty * The original number of items. * @param newQty * The new number of items. */ public void updateEquipmentQty(final Equipment eq, double oldQty, double newQty) { if (eq.isType(Constants.s_TYPE_SPELLBOOK)) { String baseBookname = eq.getName(); String bookName = eq.getName(); int old = (int) oldQty; int newQ = (int) newQty; // Add any new items for (int i = old; i < newQ; i++) { if (i > 0) { bookName = baseBookname + " #" + (i + 1); } SpellBook book = new SpellBook(bookName, SpellBook.TYPE_SPELL_BOOK); book.setEquip(eq); addSpellBook(book); } // Remove any old items for (int i = old; i > newQ; i--) { if (i > 0) { bookName = baseBookname + " #" + i; } delSpellBook(bookName); } } setDirty(true); } public void addFollower(final Follower aFollower) { followerList.add(aFollower); setDirty(true); } public void addLocalEquipment(final Equipment eq) { equipmentList.add(eq); } public void addNotesItem(final NoteItem item) { notesList.add(item); setDirty(true); } /** * Adds a "temporary" bonus * * @param aBonus */ public void addTempBonus(final BonusObj aBonus) { getTempBonusList().add(aBonus); setDirty(true); } public void addTempBonusItemList(final Equipment aEq) { getTempBonusItemList().add(aEq); setDirty(true); } /** * Compute total bonus from a List of BonusObj's * * @param aList * @return bonus from list */ public double calcBonusFromList(final List<BonusObj> aList) { double iBonus = 0; for (BonusObj bonus : aList) { iBonus += bonus.resolve(this, "").doubleValue(); } return iBonus; } public boolean checkQualifyList(CDOMObject testQualObj) { /* * The use of Object.class here is the "universalizer" to account * for the 5.10.* format of Qualify - which is "allow anything all at once" * - Tom Parker 1/17/07 */ // Try all possible CDOMOjects for (CDOMObject cdo : getCDOMObjectList()) { if (grantsQualify(cdo, testQualObj)) { return true; } } return false; } public final boolean grantsQualify(CDOMObject owner, CDOMObject qualTestObject) { List<Qualifier> qualList = owner.getListFor(ListKey.QUALIFY); if (qualList == null) { return false; } Class<? extends CDOMObject> cl = qualTestObject.getClass(); for (Qualifier qual : qualList) { if (cl.equals(qual.getQualifiedClass())) { CDOMReference qRef = qual.getQualifiedReference(); if (checkRef(qRef, qualTestObject)) { return true; } } } return false; } private <T extends CDOMObject> boolean checkRef(CDOMReference<T> ref, T qualTestObject) { return ref.contains(qualTestObject); } /** * Checks to see if this PC has the weapon proficiency key aKey * * @param aKey * @return boolean */ public boolean hasWeaponProfKeyed(final String aKey) { if (cachedWeaponProfs == null) { cachedWeaponProfs = buildWeaponProfCache(); } for (WeaponProf wp : cachedWeaponProfs.values()) { if (wp != null && wp.getKeyName().equalsIgnoreCase(aKey)) { return true; } } return false; } public boolean hasWeaponProf(final WeaponProf wp) { // return hasWeaponProfKeyed( wp.getKeyName() ); if (cachedWeaponProfs == null) { cachedWeaponProfs = buildWeaponProfCache(); } return cachedWeaponProfs.get(wp.getKeyName()) != null; } public Equipment getEquipmentNamed(final String aString) { return getEquipmentNamed(aString, getEquipmentMasterList()); } public List<String> getMiscList() { return miscList; } public void buildVariableSet() { // Building the PObject list relies on variables for evaluating prereqs, // so we have to grab it before clearing out the variables. List<? extends CDOMObject> pObjList = getCDOMObjectList(); variableSet.clear(); // Go through all objects that could add a VAR // and build the HashSet // Try all possible POBjects for (CDOMObject aPObj : pObjList) { for (VariableKey vk : aPObj.getVariableKeys()) { variableSet.add(vk.toString()); } } // Some virtual feats rely on variables as prereqs, hence the need to // Recalculate them after we get all vars. setVirtualFeatsStable(false); } public boolean delEquipSet(final EquipSet eSet) { if (equipSetList.isEmpty()) { return false; } boolean found = false; final String pid = eSet.getIdPath(); // first remove this EquipSet equipSetList.remove(eSet); // now find and remove all it's children for (Iterator<EquipSet> e = equipSetList.iterator(); e.hasNext();) { final EquipSet es = e.next(); final String abParentId = es.getParentIdPath() + EquipSet.PATH_SEPARATOR; final String abPid = pid + EquipSet.PATH_SEPARATOR; if (abParentId.startsWith(abPid)) { e.remove(); found = true; } } setDirty(true); return found; } public void delEquipSetItem(final Equipment eq) { if (equipSetList.isEmpty()) { return; } final List<EquipSet> tmpList = new ArrayList<EquipSet>(); // now find and remove equipment from all EquipSet's for (EquipSet es : equipSetList) { final Equipment eqI = es.getItem(); if ((eqI != null) && eq.equals(eqI)) { tmpList.add(es); } } for (EquipSet es : tmpList) { delEquipSet(es); } setDirty(true); } public void delFollower(final Follower aFollower) { followerList.remove(aFollower); setDirty(true); } public void equipmentListAddAll(final List<Equipment> aList) { if (aList.isEmpty()) { return; } equipmentList.addAll(aList); equipmentMasterList.addAll(aList); setDirty(true); } public boolean hasVariable(final String variableString) { for (String var : variableList) { final StringTokenizer aTok = new StringTokenizer(var, Constants.PIPE); aTok.nextToken(); // source aTok.nextToken(); // subSource if ((aTok.nextToken()).equalsIgnoreCase(variableString)) // nString { return true; } } // if (Globals.hasWeaponProfVariableNamed(getWeaponProfs(), // variableString)) // { // return true; // } return variableSet.contains(variableString.toUpperCase()); } /** * Put the provided bonus key and value into the supplied bonus map. Some * sanity checking is done on the key. * * @param aKey * The bonus key * @param aVal * The value of the bonus * @param bonusMap * The map of bonuses being built. */ private void putActiveBonusMap(final String aKey, final String aVal, Map<String, String> bonusMap) { // // This is a bad idea...will add whatever the bonus is to ALL skills // if (aKey.equalsIgnoreCase("SKILL.LIST")) { displayUpdate = true; return; } bonusMap.put(aKey, aVal); // setDirty(true); } public int racialSizeInt() { int iSize = 0; if (race != null) { // get the base size for the race iSize = race.getSafe(FormulaKey.SIZE).resolve(this, "").intValue(); // now check and see if a template has set the // size of the character in question // with something like SIZE:L for (PCTemplate template : getTemplateList()) { Formula sizeFormula = template.get(FormulaKey.SIZE); if (sizeFormula != null) { iSize = sizeFormula.resolve(this, template.getKeyName()) .intValue(); } } } return iSize; } /** * @param aBonus * This will be used when I expand the functionality of the * TempBonus tab. Please leave -- JSC 08/08/03 */ public void removeActiveBonus(final BonusObj aBonus) { activeBonusList.remove(aBonus); } public void removeEquipment(final Equipment eq) { if (eq.isType(Constants.s_TYPE_SPELLBOOK)) { delSpellBook(eq.getName()); } equipmentList.remove(eq); equipmentMasterList.remove(eq); setDirty(true); } public void removeLocalEquipment(final Equipment eq) { equipmentList.remove(eq); setDirty(true); } /** * Now we use the ACTYPE tag on misc info to determine the formula * * @return ac total */ public int getACTotal() { return calcACOfType("Total"); } public void setAlignment(final int index, final boolean bLoading) { setAlignment(index, bLoading, false); } public void setAlignment(final int index, final boolean bLoading, final boolean bForce) { // Anyone every heard of constants!? // 0 = LG, 3 = NG, 6 = CG // 1 = LN, 4 = TN, 7 = CN // 2 = LE, 5 = NE, 8 = CE if (bForce || this.race.canBeAlignment(Integer.toString(index))) { alignment = index; } else { if ((bLoading) && (index != SettingsHandler.getGame().getIndexOfAlignment( Constants.s_NONE))) { ShowMessageDelegate.showMessageDialog( "Invalid alignment. Setting to <none selected>", Constants.s_APPNAME, MessageType.INFORMATION); alignment = SettingsHandler.getGame().getIndexOfAlignment( Constants.s_NONE); } throw new IllegalArgumentException("Invalid alignment"); } setDirty(true); } /** * @return the allowDebt */ public boolean isAllowDebt() { return allowDebt; } /** * @param allowDebt the allowDebt to set */ public void setAllowDebt(boolean allowDebt) { this.allowDebt = allowDebt; } public String getAttackString(AttackType at) { return getAttackString(at, 0); } public String getAttackString(AttackType at, final int bonus) { return getAttackString(at, bonus, 0); } /** * Calculates and returns an attack string for one of Melee, Ranged or * Unarmed damage. This will be returned in attack string format i.e. * +11/+6/+1. The attack string returned by this function normally only * includes the attacks generated by the characters Base Attack Bonus. There * are two bonuses to TOHIT that may be applied to the attack string * returned by this function. The first bonus increases only the size of the * attacks generated. The second increases both the size and number of * attacks * * @param at * The type of attack. Takes an AttackType (an enumeration) * * @param TOHITBonus * A bonus that will be added to the TOHIT numbers. This bonus * affects only the numbers produced, not the number of attacks * * @param BABBonus * This bonus will be added to BAB before the number of attacks * has been determined. * @return The attack string for this character */ public String getAttackString(AttackType at, final int TOHITBonus, int BABBonus) { final String cacheLookup = "AttackString:" + at.getIdentifier() + "," + TOHITBonus + "," + BABBonus; final String cached = getVariableProcessor().getCachedString(cacheLookup); if (cached != null) { return cached; } // index: 0 = melee; 1 = ranged; 2 = unarmed // now we see if this PC is a Familiar // Initialise to some large negative number int masterBAB = -9999; int masterTotal = -9999; final PlayerCharacter nPC = getMasterPC(); // check for Epic /* final int totalClassLevels = getTotalCharacterLevel(); Map<String, String> totalLvlMap = null; final Map<String, String> classLvlMap; if (totalClassLevels > SettingsHandler.getGame().getBabMaxLvl()) { String epicAttack = epicAttackMap.get(cacheLookup); totalLvlMap = getTotalLevelHashMap(); classLvlMap = getCharacterLevelHashMap(SettingsHandler.getGame() .getBabMaxLvl()); // insure class-levels total is below some value (20) getVariableProcessor().pauseCache(); setClassLevelsBrazenlyTo(classLvlMap); } */ if ((nPC != null) && (getCopyMasterBAB().length() > 0)) { masterBAB = nPC.baseAttackBonus(); final String copyMasterBAB = replaceMasterString(getCopyMasterBAB(), masterBAB); masterBAB = getVariableValue(copyMasterBAB, Constants.EMPTY_STRING) .intValue(); masterTotal = masterBAB + TOHITBonus; } final int BAB = baseAttackBonus(); int attackCycle = 1; int workingBAB = BAB + TOHITBonus; int subTotal = BAB; int raceBAB = 0; final List<Integer> ab = new ArrayList<Integer>(10); final StringBuffer attackString = new StringBuffer(); // Assume a max of 10 attack cycles for (int total = 0; total < 10; ++total) { ab.add(Integer.valueOf(0)); } // Some classes (like the Monk or Ranged Sniper) use // a different attack cycle than the standard classes // So compute the base attack for this type (BAB, RAB, UAB) for (PCClass pcClass : classList) { // Get the attack bonus final int b = pcClass.baseAttackBonus(this); // Get the attack cycle final int c = pcClass.attackCycle(at); // add to all other classes if (c < ab.size()) { final int d = ab.get(c).intValue() + b; // set new value for iteration ab.set(c, Integer.valueOf(d)); } if (c != 3) { raceBAB += b; } } // Iterate through all the possible attack cycle values // and find the one with the highest attack value for (int i = 2; i < 10; ++i) { final int newAttack = ab.get(i).intValue(); final int oldAttack = ab.get(attackCycle).intValue(); if ((newAttack / i) > (oldAttack / attackCycle)) { attackCycle = i; } } /* // restore class levels to original value if altered if (totalLvlMap != null) { setClassLevelsBrazenlyTo(totalLvlMap); getVariableProcessor().restartCache(); } */ // total Number of Attacks for this PC int attackTotal = ab.get(attackCycle).intValue(); // Default cut-off before multiple attacks (e.g. 5) final int defaultAttackCycle = SettingsHandler.getGame().getBabAttCyc(); if (attackTotal == 0) { attackCycle = defaultAttackCycle; } // FAMILIAR: check to see if the masters BAB is better workingBAB = Math.max(workingBAB, masterTotal); subTotal = Math.max(subTotal, masterBAB); raceBAB = Math.max(raceBAB, masterBAB); if (attackCycle != defaultAttackCycle) { if ((attackTotal / attackCycle) < (subTotal / defaultAttackCycle)) { attackCycle = defaultAttackCycle; attackTotal = subTotal; } else { workingBAB -= raceBAB; subTotal -= raceBAB; } } int maxAttacks = SettingsHandler.getGame().getBabMaxAtt(); final int minMultiBab = SettingsHandler.getGame().getBabMinVal(); // If there is a bonus to BAB, it needs to be added to ALL of // the variables used to determine the number of attacks attackTotal += BABBonus; workingBAB += BABBonus; subTotal += BABBonus; do { if (attackString.length() > 0) { attackString.append('/'); } attackString.append(Delta.toString(workingBAB)); workingBAB -= attackCycle; attackTotal -= attackCycle; subTotal -= attackCycle; maxAttacks--; } while (((attackTotal >= minMultiBab) || (subTotal >= minMultiBab)) && (maxAttacks > 0)); getVariableProcessor().addCachedString(cacheLookup, attackString.toString()); return attackString.toString(); } public SortedSet<Language> getAutoLanguages() { // find list of all possible languages boolean clearRacials = false; final SortedSet<Language> autoLangs = new TreeSet<Language>(); // Search for a CLEAR in the list and // if found clear all BEFORE but not AFTER it. // ---arcady June 1, 2002 for (Language lang : templateAutoLanguages) { final String aString = lang.toString(); if (".CLEARRACIAL".equals(aString)) { clearRacials = true; languages.removeAll(getRace().getSafeListFor( ListKey.AUTO_LANGUAGES)); } else if (".CLEARALL".equals(aString) || ".CLEAR".equals(aString)) { clearRacials = true; autoLangs.clear(); languages.clear(); } else if (".CLEARTEMPLATES".equals(aString)) { autoLangs.clear(); languages.removeAll(templateAutoLanguages); } else { autoLangs.add(lang); } } for (PObject pObj : getPObjectList()) { if (clearRacials && pObj instanceof Race) { clearRacials = false; continue; } for (CDOMReference<Language> ref : pObj .getSafeListFor(ListKey.AUTO_LANGUAGES)) { autoLangs.addAll(ref.getContainedObjects()); } } languages.addAll(autoLangs); return autoLangs; } /** * @return the autoResize */ public boolean isAutoResize() { return autoResize; } /** * @param autoResize the autoResize to set */ public void setAutoResize(boolean autoResize) { this.autoResize = autoResize; } /** * Sets the autoSortGear. * * @param autoSortGear * The autoSortGear to set */ public void setAutoSortGear(final boolean autoSortGear) { this.autoSortGear = autoSortGear; setDirty(true); } /** * Returns the autoSortGear. * * @return boolean */ public boolean isAutoSortGear() { return autoSortGear; } /** * whether we should add auto known spells at level up * * @param aBool */ public void setAutoSpells(final boolean aBool) { autoKnownSpells = aBool; setDirty(true); } public boolean getAutoSpells() { return autoKnownSpells; } /** * @return the ignoreCost */ public boolean isIgnoreCost() { return ignoreCost; } /** * @param ignoreCost the ignoreCost to set */ public void setIgnoreCost(boolean ignoreCost) { this.ignoreCost = ignoreCost; } /** * Determine whether higher level known spell slots can be used for lower * level spells, or if known spells are restricted to their own level only. * * @return Returns the useHigherKnownSlots. */ public boolean getUseHigherKnownSlots() { return useHigherKnownSlots; } /** * Set whether higher level known spell slots can be used for lower level * spells, or if known spells are restricted to their own level only. * * @param useHigher * Can higher level known spell slots be used? */ public void setUseHigherKnownSlots(boolean useHigher) { this.useHigherKnownSlots = useHigher; } /** * Determine whether higher level prepared spell slots can be used for lower * level spells, or if prepared spells are restricted to their own level * only. * * @return Returns the useHigherPreppedSlots. */ public boolean getUseHigherPreppedSlots() { return useHigherPreppedSlots; } /** * Set whether higher level prepared spell slots can be used for lower level * spells, or if prepared spells are restricted to their own level only. * * @param useHigher * Can higher level prepared spell slots be used? */ public void setUseHigherPreppedSlots(boolean useHigher) { this.useHigherPreppedSlots = useHigher; } /** * Returns the &quot;Base&quot; check value for the check at the index * specified. * * <p> * This method caps the base check based on the game mode setting for * {@link pcgen.core.GameMode#getChecksMaxLvl() checks max level}. * * @param checkInd * The index of the check to get * * @return The base check value. */ public int getBaseCheck(final int checkInd) { final String cacheLookup = "getBaseCheck:" + checkInd; //$NON-NLS-1$ Float total = null; if (epicCheckMap.containsKey(checkInd)) { total = epicCheckMap.get(checkInd).floatValue(); } else { total = getVariableProcessor().getCachedVariable(cacheLookup); } if (total != null) { return total.intValue(); } double bonus = 0; boolean isEpic = false; final int totalClassLevels; Map<String, String> totalLvlMap = null; final Map<String, String> classLvlMap; if (checkInd >= 0 && checkInd < SettingsHandler.getGame().getUnmodifiableCheckList() .size()) { totalClassLevels = getTotalCharacterLevel(); if (totalClassLevels > SettingsHandler.getGame().getChecksMaxLvl()) { isEpic = true; Integer epicCheck = epicCheckMap.get(checkInd); if (epicCheck == null) { totalLvlMap = getTotalLevelHashMap(); classLvlMap = getCharacterLevelHashMap(SettingsHandler.getGame() .getChecksMaxLvl()); getVariableProcessor().pauseCache(); setClassLevelsBrazenlyTo(classLvlMap); // insure class-levels // total is below some // value (e.g. 20) } else { //Logging.errorPrint("getBaseCheck(): '" + cacheLookup + "' = epic='" + epicCheck + "'"); //$NON-NLS-1$ return epicCheck; } } final String checkName = SettingsHandler.getGame().getUnmodifiableCheckList().get( checkInd).toString(); bonus = getTotalBonusTo("CHECKS", "BASE." + checkName); // // now we see if this PC is a Familiar/Mount final PlayerCharacter nPC = getMasterPC(); if ((nPC != null) && (getCopyMasterCheck().length() > 0)) { int masterBonus = nPC.getBaseCheck(checkInd); final String copyMasterCheck = replaceMasterString(getCopyMasterCheck(), masterBonus); masterBonus = getVariableValue(copyMasterCheck, Constants.EMPTY_STRING).intValue(); // use masters save if better bonus = Math.max(bonus, masterBonus); } if (isEpic) { epicCheckMap.put(checkInd, (int) bonus); } if (totalLvlMap != null) { setClassLevelsBrazenlyTo(totalLvlMap); getVariableProcessor().restartCache(); } } return (int) bonus; } /** * Returns the total check value for the check index specified for the * character. * * <p> * This total includes all check bonuses the character has. * * @param aCheck * The index of the check to get. * * @return A check value. */ public int getTotalCheck(final int aCheck) { int bonus = getBaseCheck(aCheck); return bonus + (int) getTotalBonusTo("CHECKS", SettingsHandler.getGame() .getCheckKey(aCheck)); } // /** // * type 0 = attack bonus; 1 = check1; 2 = check2; 3 = check3; etc, last // one is = Unarmed // * @param type // * @param addBonuses // * @return bonus // */ // public double getBonus(final int type, final boolean addBonuses) // { // final String cacheLookup = "getBonus:" + type + "," + addBonuses; // final Float total = // getVariableProcessor().getCachedVariable(cacheLookup); // // if (total != null) // { // return total.doubleValue(); // } // // double bonus = 0; // final int totalClassLevels; // Map<String, String> totalLvlMap = null; // final Map<String, String> classLvlMap; // // if (type == 0) // { // // bonus = race.getBAB(this); // } // else if (type <= // SettingsHandler.getGame().getUnmodifiableCheckList().size()) // { // totalClassLevels = getTotalCharacterLevel(); // if (totalClassLevels > SettingsHandler.getGame().getChecksMaxLvl()) // { // totalLvlMap = getTotalLevelHashMap(); // classLvlMap = // getCharacterLevelHashMap(SettingsHandler.getGame().getChecksMaxLvl()); // getVariableProcessor().pauseCache(); // setClassLevelsBrazenlyTo(classLvlMap); // insure class-levels total is // below some value (e.g. 20) // } // // bonus = getTotalBonusTo("CHECKS", // "BASE." + SettingsHandler.getGame().getUnmodifiableCheckList().get(type - // 1).toString()); // // // // // now we see if this PC is a Familiar/Mount // final PlayerCharacter nPC = getMasterPC(); // // if ((nPC != null) && (getCopyMasterCheck().length() > 0)) // { // int masterBonus; // final PlayerCharacter curPC = this; // Globals.setCurrentPC(nPC); // // // calculate the Masters Save Bonus // masterBonus = nPC.calculateSaveBonus(type, // SettingsHandler.getGame().getUnmodifiableCheckList().get(type - // 1).toString(), "BASE"); // Globals.setCurrentPC(curPC); // // final String copyMasterCheck = replaceMasterString(getCopyMasterCheck(), // masterBonus); // masterBonus = getVariableValue(copyMasterCheck, "").intValue(); // // // use masters save if better // bonus = Math.max(bonus, masterBonus); // } // // if (totalLvlMap != null) // { // setClassLevelsBrazenlyTo(totalLvlMap); // getVariableProcessor().restartCache(); // } // } // // if (addBonuses) // { // if (type == 0) // { // bonus += getTotalBonusTo("TOHIT", "TOHIT"); // bonus += getSizeAdjustmentBonusTo("TOHIT", "TOHIT"); // } // else if (type <= // SettingsHandler.getGame().getUnmodifiableCheckList().size()) // { // bonus += getTotalBonusTo("CHECKS", // SettingsHandler.getGame().getUnmodifiableCheckList().get(type - // 1).toString()); // } // else // { // bonus += getSizeAdjustmentBonusTo("TOHIT", "TOHIT"); // } // } // // int cBonus = 0; // // for ( PCClass pcClass : classList ) // { // if ((type == 0) || (type > // SettingsHandler.getGame().getUnmodifiableCheckList().size())) // { // cBonus += pcClass.baseAttackBonus(this); // } // } // // bonus += cBonus; // // getVariableProcessor().addCachedVariable(cacheLookup, new Float(bonus)); // return bonus; // } /** * return bonus total for a specific bonusType e.g: * getBonusDueToType("COMBAT","AC","Armor") to get armor bonuses * * @param mainType * @param subType * @param bonusType * @return bonus due to type */ public double getBonusDueToType(final String mainType, final String subType, final String bonusType) { final String typeString = mainType + "." + subType + ":" + bonusType; return sumActiveBonusMap(typeString); } /** * Get the list of WeaponName and Proficiency types from the changeProfMap * of each granting object * * @return List */ public MapToList<String, WeaponProf> getChangeProfList() { MapToList<String, WeaponProf> mtl = new TreeMapToList<String, WeaponProf>( String.CASE_INSENSITIVE_ORDER); for (PObject pObj : getPObjectList()) { Map<WeaponProf, String> cp = pObj.getChangeProfList(this); for (Map.Entry<WeaponProf, String> me : cp.entrySet()) { mtl.addToListFor(me.getValue(), me.getKey()); } } return mtl; } public CharacterDomain getCharacterDomainForDomain(final String domainKey) { for (CharacterDomain cd : characterDomainList) { final Domain aDomain = cd.getDomain(); if ((aDomain != null) && aDomain.getKeyName().equalsIgnoreCase(domainKey)) { return cd; } } return null; } public List<CharacterDomain> getCharacterDomainList() { return Collections.unmodifiableList(characterDomainList); } public boolean hasCharacterDomainList() { return characterDomainList != null && !characterDomainList.isEmpty(); } public Domain getCharacterDomainKeyed(final String domainKey) { for (CharacterDomain cd : characterDomainList) { final Domain aDomain = cd.getDomain(); if ((aDomain != null) && aDomain.getKeyName().equalsIgnoreCase(domainKey)) { return cd.getDomain(); } } return null; } /** * @return the number of Character Domains used */ public int getCharacterDomainUsed() { return characterDomainList.size(); } public List<CompanionMod> getCompanionModList() { return Collections.unmodifiableList(companionModList); } public String getCopyMasterBAB() { for (CompanionMod cMod : companionModList) { if (cMod.getType().equalsIgnoreCase(getMaster().getType())) { if (cMod.getCopyMasterBAB() != null) { return cMod.getCopyMasterBAB(); } } } return Constants.EMPTY_STRING; } public String getCopyMasterCheck() { for (CompanionMod cMod : companionModList) { if (cMod.getType().equalsIgnoreCase(getMaster().getType())) { if (cMod.getCopyMasterCheck() != null) { return cMod.getCopyMasterCheck(); } } } return Constants.EMPTY_STRING; } public String getCopyMasterHP() { for (CompanionMod cMod : companionModList) { if (cMod.getType().equalsIgnoreCase(getMaster().getType())) { if (cMod.getCopyMasterHP() != null) { return cMod.getCopyMasterHP(); } } } return Constants.EMPTY_STRING; } public void setCurrentHP(final int currentHP) { setDirty(true); } public boolean setDeity(final Deity aDeity) { if (!canSelectDeity(aDeity)) { return false; } deity = aDeity; if (!isImporting()) { getSpellList(); deity.globalChecks(this); } setDirty(true); calcActiveBonuses(); return true; } /** * return the first source that matches className * * @param className * @return domain source */ public String getDomainSource(final String className) { for (String aKey : domainSourceMap.keySet()) { final String aVal = domainSourceMap.get(aKey); final int aNum = Integer.parseInt(aVal); if ((className == null) && (aNum > 0)) { return aKey; } else if (aKey.indexOf(className) >= 0) { return aKey; } } return Constants.EMPTY_STRING; } /** * Returns the character's Effective Character Level. * * <p> * The level is calculated by adding total non-monster levels, total * hitdice, and level adjustment. * * @return The ECL of the character. */ public int getECL() { int totalLevels = 0; totalLevels += totalNonMonsterLevels(); totalLevels += totalHitDice(); totalLevels += getLevelAdjustment(this); return totalLevels; } /** * Set the order in which equipment should be sorted for output. * * @param i * The new output order */ public void setEquipOutputOrder(final int i) { equipOutputOrder = i; setDirty(true); } /** * @return The selected Output Order for equipment. */ public int getEquipOutputOrder() { return equipOutputOrder; } /** * Retrieves an unsorted list of the character's equipment matching the * supplied type and status criteria. * * @param typeName * The type of equipment to be selected * @param status * The required status: 1 (equipped) 2 (not equipped) 3 (don't * care) * @return An ArrayList of the matching equipment objects. */ public List<Equipment> getEquipmentOfType(final String typeName, final int status) { return getEquipmentOfType(typeName, Constants.EMPTY_STRING, status); } /** * Retrieves an unsorted list of the character's equipment matching the * supplied type, sub type and status criteria. * * @param typeName * The type of equipment to be selected * @param subtypeName * The subtype of equipment to be selected (empty string for no * subtype) * @param status * The required status: 1 (equipped) 2 (not equipped) 3 (don't * care) * @return An ArrayList of the matching equipment objects. */ public List<Equipment> getEquipmentOfType(final String typeName, final String subtypeName, final int status) { final List<Equipment> aArrayList = new ArrayList<Equipment>(); for (Equipment eq : equipmentList) { if (eq.typeStringContains(typeName) && (Constants.EMPTY_STRING.equals(subtypeName) || eq .typeStringContains(subtypeName)) && ((status == 3) || ((status == 2) && !eq.isEquipped()) || ((status == 1) && eq .isEquipped()))) { aArrayList.add(eq); } } return aArrayList; } /** * Retrieves a list, sorted in output order, of the character's equipment * matching the supplied type and status criteria. This list is in ascending * order of the equipment's outputIndex field. If multiple items of * equipment have the same outputIndex they will be ordered by name. Note * hidden items (outputIndex = -1) are not included in this list. * * @param typeName * The type of equipment to be selected * @param status * The required status: 1 (equipped) 2 (not equipped) 3 (don't * care) * @return An ArrayList of the matching equipment objects in output order. */ public List<Equipment> getEquipmentOfTypeInOutputOrder( final String typeName, final int status) { return sortEquipmentList(getEquipmentOfType(typeName, status), Constants.MERGE_ALL); } /** * @param typeName * The type of equipment to be selected * @param status * The required status * @param merge * What type of merge for like equipment * @return An ArrayList of equipment objects */ public List<Equipment> getEquipmentOfTypeInOutputOrder( final String typeName, final int status, final int merge) { return sortEquipmentList(getEquipmentOfType(typeName, status), merge); } /** * @param typeName * The type of equipment to be selected * @param subtypeName * The subtype of equipment to be selected * @param status * The required status * @param merge * What sort of merging should occur * @return An ArrayList of equipment objects */ public List<Equipment> getEquipmentOfTypeInOutputOrder( final String typeName, final String subtypeName, final int status, final int merge) { return sortEquipmentList(getEquipmentOfType(typeName, subtypeName, status), Constants.MERGE_ALL); } /** * Retrieve the expanded list of weapons Expanded weapons include: double * weapons and melee+ranged weapons Output order is assumed Merge of like * equipment depends on the passed in int * * @param merge * * @return the sorted list of weapons. */ public List<Equipment> getExpandedWeapons(final int merge) { final List<Equipment> weapList = sortEquipmentList(getEquipmentOfType("Weapon", 3), merge); // // If any weapon is both Melee and Ranged, then make 2 weapons // for list, one Melee only, the other Ranged and Thrown. // For double weapons, if wielded in two hands show attacks // for both heads, head 1 and head 2 else // if wielded in 1 hand, just show damage by head // for (int idx = 0; idx < weapList.size(); ++idx) { final Equipment equip = weapList.get(idx); if (equip.isDouble() && (equip.getLocation() == Equipment.EQUIPPED_TWO_HANDS)) { Equipment eqm = equip.clone(); eqm.removeType("Double"); eqm.setTypeInfo("Head1"); // Add "Head 1 only" to the name of the weapon eqm.setWholeItemName(eqm.getName()); eqm.setName(EquipmentUtilities.appendToName(eqm.getName(), "Head 1 only")); if (eqm.getOutputName().indexOf("Head 1 only") < 0) { eqm.put(StringKey.OUTPUT_NAME, EquipmentUtilities.appendToName(eqm .getOutputName(), "Head 1 only")); } PlayerCharacterUtilities.setProf(equip, eqm); weapList.add(idx + 1, eqm); eqm = equip.clone(); final String altType = eqm.getType(false); if (altType.length() != 0) { eqm.setTypeInfo(".CLEAR." + altType); } eqm.removeType("Double"); eqm.setTypeInfo("Head2"); EquipmentHead head = eqm.getEquipmentHead(1); String altDamage = eqm.getAltDamage(this); if (altDamage.length() != 0) { head.put(StringKey.DAMAGE, altDamage); } head.put(IntegerKey.CRIT_MULT, eqm.getAltCritMultiplier()); head.put(IntegerKey.CRIT_RANGE, eqm.getRawCritRange(false)); eqm.getEqModifierList(true).clear(); eqm.getEqModifierList(true) .addAll(eqm.getEqModifierList(false)); // Add "Head 2 only" to the name of the weapon eqm.setWholeItemName(eqm.getName()); eqm.setName(EquipmentUtilities.appendToName(eqm.getName(), "Head 2 only")); if (eqm.getOutputName().indexOf("Head 2 only") < 0) { eqm.put(StringKey.OUTPUT_NAME, EquipmentUtilities.appendToName(eqm .getOutputName(), "Head 2 only")); } PlayerCharacterUtilities.setProf(equip, eqm); weapList.add(idx + 2, eqm); } // // Leave else here, as otherwise will show attacks // for both heads for thrown double weapons when // it should only show one // else if (equip.isMelee() && equip.isRanged() && (equip.getRange(this).intValue() != 0)) { // // Strip off the Ranged portion, set range to 0 // Equipment eqm = equip.clone(); eqm.setTypeInfo("Both"); eqm.removeType("Ranged.Thrown"); eqm.put(IntegerKey.RANGE, 0); PlayerCharacterUtilities.setProf(equip, eqm); weapList.set(idx, eqm); // // Replace any primary weapons int iPrimary; for (iPrimary = getPrimaryWeapons().size() - 1; iPrimary >= 0; --iPrimary) { final Equipment teq = getPrimaryWeapons().get(iPrimary); if (teq == equip) { break; } } if (iPrimary >= 0) { getPrimaryWeapons().set(iPrimary, eqm); } // // Replace any secondary weapons int iSecondary; for (iSecondary = getSecondaryWeapons().size() - 1; iSecondary >= 0; --iSecondary) { final Equipment teq = getSecondaryWeapons().get(iSecondary); if (teq == equip) { break; } } if (iSecondary >= 0) { getSecondaryWeapons().set(iSecondary, eqm); } // // Add thrown portion, strip Melee // eqm = equip.clone(); eqm.setTypeInfo("Ranged.Thrown.Both"); eqm.removeType("Melee"); // Add "Thrown" to the name of the weapon eqm.setName(EquipmentUtilities.appendToName(eqm.getName(), "Thrown")); if (eqm.getOutputName().indexOf("Thrown") < 0) { eqm.put(StringKey.OUTPUT_NAME, EquipmentUtilities.appendToName(eqm .getOutputName(), "Thrown")); } PlayerCharacterUtilities.setProf(equip, eqm); weapList.add(++idx, eqm); if (iPrimary >= 0) { getPrimaryWeapons().add(++iPrimary, eqm); } else if (iSecondary >= 0) { getSecondaryWeapons().add(++iSecondary, eqm); } } } return weapList; } /** * renaming to standard convention due to refactoring of export * * Build on-the-fly so removing templates doesn't mess up favoured list * * @author Thomas Behr 08-03-02 */ public SortedSet<PCClass> getFavoredClasses() { /* * CONSIDER Can this be cached? */ SortedSet<PCClass> favored = new TreeSet<PCClass>(CDOMObjectUtilities.CDOM_SORTER); if (selectedFavoredClass != null) { favored.add(selectedFavoredClass); } List<CDOMReference<? extends PCClass>> favClass = getRace().getListFor( ListKey.FAVORED_CLASS); if (favClass != null) { for (CDOMReference<? extends PCClass> ref : favClass) { favored.addAll(ref.getContainedObjects()); } } for (PCTemplate template : templateList) { List<CDOMReference<? extends PCClass>> fc = template .getListFor(ListKey.FAVORED_CLASS); if (fc != null) { for (CDOMReference<? extends PCClass> ref : fc) { favored.addAll(ref.getContainedObjects()); } } } return favored; } /** * Calculates the level of the character's favored class * * @return level */ public int getFavoredClassLevel() { final SortedSet<PCClass> aList = getFavoredClasses(); int level = 0; int max = 0; boolean isAny = getRace().getSafe(ObjectKey.ANY_FAVORED_CLASS); for (PCClass cl : aList) { for (PCClass pcClass : classList) { if (isAny) { max = Math.max(max, pcClass.getLevel()); } if (cl.getKeyName().equals(pcClass.getKeyName())) { level += pcClass.getLevel(); break; } } } return Math.max(level, max); } /** * Calculates total bonus from Feats * * @param aType * @param aName * @return feat bonus to */ public double getFeatBonusTo(String aType, String aName) { return getPObjectWithCostBonusTo(aggregateFeatList(), aType .toUpperCase(), aName.toUpperCase()); } /** * Returns the Feat definition of a feat possessed by the character. * * @param featName * String name of the feat to check for. * @return the Feat (not the CharacterFeat) searched for, <code>null</code> * if not found. */ public Ability getFeatNamed(final String featName) { return AbilityUtilities.getAbilityFromList(AbilityUtilities .getAggregateAbilitiesListForKey(Constants.FEAT_CATEGORY, this), Constants.FEAT_CATEGORY, featName, Ability.Nature.ANY); } public Ability getFeatNamed(final String featName, final Ability.Nature featType) { return AbilityUtilities.getAbilityFromList(AbilityUtilities .getAggregateAbilitiesListForKey(Constants.FEAT_CATEGORY, this), Constants.FEAT_CATEGORY, featName, featType); } /** * Searches the characters feats for an Ability object which is a clone of * the same Base ability as the Ability passed in * * @param anAbility * @return the Ability if found, otherwise null */ public Ability getAbilityMatching(final Ability anAbility) { return AbilityUtilities.getAbilityFromList(new ArrayList<Ability>( getFullAbilitySet()), anAbility); } public int getFirstSpellLevel(final Spell aSpell) { int anInt = 0; for (PCClass pcClass : getClassList()) { final String aKey = pcClass.getSpellKey(); final int temp = aSpell.getFirstLevelForKey(aKey, this); anInt = Math.min(anInt, temp); } return anInt; } public void setHasMadeKitSelectionForAgeSet(final int index, final boolean arg) { if ((index >= 0) && (index < 10)) { ageSetKitSelections[index] = arg; } setDirty(true); } public List<Kit> getKitInfo() { List<Kit> returnList; if (kitList != null) { returnList = kitList; } else { returnList = Collections.emptyList(); } return returnList; } public int getLevelAdjustment(final PlayerCharacter aPC) { int levelAdj = race.getSafe(FormulaKey.LEVEL_ADJUSTMENT).resolve(aPC, "").intValue(); for (PCTemplate template : templateList) { levelAdj += template.getSafe(FormulaKey.LEVEL_ADJUSTMENT).resolve( aPC, "").intValue(); } return levelAdj; } public List<PCLevelInfo> getLevelInfo() { return pcLevelInfo; } public String getLevelInfoClassKeyName(final int idx) { if ((idx >= 0) && (idx < getLevelInfoSize())) { return pcLevelInfo.get(idx).getClassKeyName(); } return Constants.EMPTY_STRING; } public int getLevelInfoClassLevel(final int idx) { if ((idx >= 0) && (idx < getLevelInfoSize())) { return pcLevelInfo.get(idx).getLevel(); } return 0; } public PCLevelInfo getLevelInfoFor(final String classKey, int level) { for (PCLevelInfo pcl : pcLevelInfo) { if (pcl.getClassKeyName().equals(classKey)) { level--; } if (level <= 0) { return pcl; } } return null; } public int getLevelInfoSize() { return pcLevelInfo.size(); } /** * whether we should load companions on master load * * @param aBool */ public void setLoadCompanion(final boolean aBool) { autoLoadCompanion = aBool; setDirty(true); } public boolean getLoadCompanion() { return autoLoadCompanion; } /** * @return the number of Character Domains possible */ public int getMaxCharacterDomains() { return (int) getTotalBonusTo("DOMAIN", "NUMBER"); } /** * @param source * @param aPC * @return the number of Character Domains possible and check the level of * the source class if the result is 0. */ public int getMaxCharacterDomains(final PCClass source, final PlayerCharacter aPC) { int i = getMaxCharacterDomains(); if (i == 0 && domainSourceMap.size() == 0) i = (int) source.getBonusTo("DOMAIN", "NUMBER", source .getLevel(), aPC); return i; } /** * Calculate the maximum number of ranks the character is allowed to have in * the specified skill. * * @param skillKey * The key of the skill being checked. * @param aClass * The name of the current class in which points are being spent - * only used to check cross-class skill cost. * @return max rank */ public Float getMaxRank(final String skillKey, final PCClass aClass) { int levelForSkillPurposes = getTotalLevels(); final BigDecimal maxRanks; final Skill aSkill = Globals.getContext().ref.silentlyGetConstructedCDOMObject(Skill.class, skillKey); if (aSkill == null) { return 0.0f; } if (aSkill.getSafe(ObjectKey.EXCLUSIVE)) { // Exclusive skills only count levels in classes which give access // to the skill levelForSkillPurposes = 0; for (PCClass bClass : classList) { if (this.isClassSkill(aSkill, bClass)) { levelForSkillPurposes += bClass.getLevel(); } } if (levelForSkillPurposes == 0) { // No classes qualify for this exclusive skill, so treat it as a // cross-class skill // This does not seem right to me! JD levelForSkillPurposes = (getTotalLevels()); maxRanks = SkillUtilities.maxCrossClassSkillForLevel( levelForSkillPurposes, this); } else { maxRanks = SkillUtilities.maxClassSkillForLevel( levelForSkillPurposes, this); } } else if (!this.isClassSkill(aSkill) && (this.getSkillCostForClass(aSkill, aClass).equals(SkillCost.CLASS))) { // Cross class skill - but as cost is 1 only return a whole number maxRanks = new BigDecimal(SkillUtilities.maxCrossClassSkillForLevel( levelForSkillPurposes, this).intValue()); // This was (int) (i/2.0) previously } else if (!this.isClassSkill(aSkill)) { // Cross class skill maxRanks = SkillUtilities.maxCrossClassSkillForLevel( levelForSkillPurposes, this); } else { // Class skill maxRanks = SkillUtilities.maxClassSkillForLevel(levelForSkillPurposes, this); } return new Float(maxRanks.floatValue()); } /** * @param moveIdx * @return the integer movement speed for Index */ public Double getMovement(final int moveIdx) { if ((getMovements() != null) && (moveIdx < movements.length)) { return movements[moveIdx]; } return Double.valueOf(0); } public String getMovementType(final int moveIdx) { if ((movementTypes != null) && (moveIdx < movementTypes.length)) { return movementTypes[moveIdx]; } return Constants.EMPTY_STRING; } public double movementOfType(final String moveType) { if (movementTypes == null) return 0.0; for (int moveIdx = 0; moveIdx < movementTypes.length; moveIdx++) { if (movementTypes[moveIdx].equalsIgnoreCase(moveType)) return movement(moveIdx); } return 0.0; } /** * gets first domain with remaining slots, creates an CharacterDomain * object, sets all the correct info and returns it * * @return Character Domain */ public CharacterDomain getNewCharacterDomain() { return getNewCharacterDomain(null); } public CharacterDomain getNewCharacterDomain(final String className) { final String sDom = getDomainSource(className); if (sDom.length() > 0) { final StringTokenizer aTok = new StringTokenizer(sDom, Constants.PIPE); final String aType = aTok.nextToken(); final String aName = aTok.nextToken(); final int aLevel = Integer.parseInt(aTok.nextToken()); final CharacterDomain aCD = new CharacterDomain(); if (aType.equalsIgnoreCase("PCClass")) { aCD.setFromPCClass(true); } else { aCD.setFromPCClass(false); } aCD.setObjectName(aName); aCD.setLevel(aLevel); return aCD; } return null; } /** * Checks if the stat is a non ability. * * @param i the index of the stat * * @return true, if is non ability */ public boolean isNonAbility(final int i) { //Unlocked overrides any lock to a non ability so check for it first if (RaceStat.isUnlocked(i, race)) { return false; } for (PCTemplate template : templateList) { if (TemplateStat.isUnlocked(template, i)) { return false; } } if (RaceStat.isNonAbility(i, race)) { return true; } for (PCTemplate template : templateList) { if (TemplateStat.isNonAbility(template, i)) { return true; } } return false; } public int getNumberOfMovements() { return (movements != null) ? movements.length : 0; } public int getOffHandLightBonus() { final int div = getVariableValue("OFFHANDLIGHTBONUS", Constants.EMPTY_STRING) .intValue(); return div; } /* * returns true if Equipment is in the primary weapon list */ public boolean isPrimaryWeapon(final Equipment eq) { if (eq == null) { return false; } for (Equipment eqI : primaryWeapons) { if (eqI.getName().equalsIgnoreCase(eq.getName()) && (eqI.getLocation() == eq.getLocation())) { return true; } } return false; } public boolean isProficientWith(final Equipment eq) { if (eq.isShield()) { final List<String> aList = getShieldProfList(); return isProficientWithShield(eq, aList); } else if (eq.isArmor()) { final List<String> aList = getArmorProfList(); return isProficientWithArmor(eq, aList); } else if (eq.isWeapon()) { return isProficientWithWeapon(eq); } return false; } /** * Changes the race of the character. First it removes the current Race, and * any bonus attributes (e.g. feats), then add the new Race. * * @param aRace */ public void setRace(final Race aRace) { final Race oldRace = getRace(); final boolean raceIsNull = (oldRace == null); // needed because race // is set to null later // remove current race attributes if (!raceIsNull) { oldRace.getSpellSupport().clearCharacterSpells(); languages.removeAll(oldRace.getSafeListFor(ListKey.AUTO_LANGUAGES)); cachedWeaponProfs = null; removeNaturalWeapons(race); removeTemplatesFrom(race); selectedFavoredClass = null; LevelCommandFactory lcf = race.get(ObjectKey.MONSTER_CLASS); if (lcf != null) { final PCClass mclass = lcf.getPCClass(); incrementClassLevel(lcf.getLevelCount().resolve(this, "").intValue() * -1, mclass, true); } } // add new race attributes race = null; if (aRace != null) { race = aRace.clone(); } if (race != null) { race.activateBonuses(this); if (!isImporting()) { Globals.getBioSet().randomize("AGE.HT.WT", this); } // Get existing classes final List<PCClass> existingClasses = new ArrayList<PCClass>(classList); classList.clear(); // // Remove all saved monster level information // for (int i = getLevelInfoSize() - 1; i >= 0; --i) { final String classKeyName = getLevelInfoClassKeyName(i); final PCClass aClass = Globals.getContext().ref.silentlyGetConstructedCDOMObject(PCClass.class, classKeyName); if ((aClass == null) || aClass.isMonster()) { removeLevelInfo(i); } } final List<PCLevelInfo> existingLevelInfo = new ArrayList<PCLevelInfo>(pcLevelInfo); pcLevelInfo.clear(); // Make sure monster classes are added first if (!isImporting()) { LevelCommandFactory lcf = race.get(ObjectKey.MONSTER_CLASS); if (lcf != null) { PCClass mclass = lcf.getPCClass(); incrementClassLevel(lcf.getLevelCount().resolve(this, "") .intValue(), mclass, true); } } pcLevelInfo.addAll(existingLevelInfo); // // If user has chosen a class before choosing a race, // we need to tweak the number of skill points and feats // if (!isImporting() && existingClasses.size() != 0) { int totalLevels = this.getTotalLevels(); // final Integer zero = Integer.valueOf(0); for (PCClass pcClass : existingClasses) { // // Don't add monster classes back in. This will possibly // mess up feats earned by level // ?Possibly convert to mclass if not null? // if (!pcClass.isMonster()) { classList.add(pcClass); final int cLevels = pcClass.getLevel(); // aClass.setLevel(0); pcClass.setSkillPool(0); int cMod = 0; for (int j = 0; j < cLevels; ++j) { cMod += pcClass.recalcSkillPointMod(this, ++totalLevels); } pcClass.setSkillPool(cMod); } } } addNaturalWeapons(race.getNaturalWeapons()); getAutoLanguages(); if (!isImporting()) { selectRacialFavoredClass(); } selectTemplates(race, isImporting()); // gets and adds templates race.chooseLanguageAutos(isImporting(), this); } // TODO - Change this back // setAggregateAbilitiesStable(null, false); setAggregateFeatsStable(false); setAutomaticFeatsStable(false); setVirtualFeatsStable(false); if (!isImporting()) { getSpellList(); race.globalChecks(this); adjustMoveRates(); calcActiveBonuses(); } setDirty(true); } /** * return bonus from a Race * * @param aType * @param aName * @return race bonus to */ public double getRaceBonusTo(String aType, String aName) { if (getRace() == null) { return 0; } final List<BonusObj> tempList = getRace().getBonusListOfType(aType.toUpperCase(), aName.toUpperCase()); return calcBonusFromList(tempList); } public int getSR() { return calcSR(true); } /* * returns true if Equipment is in the secondary weapon list */ public boolean isSecondaryWeapon(final Equipment eq) { if (eq == null) { return false; } for (Equipment eqI : secondaryWeapons) { if (eqI.getName().equalsIgnoreCase(eq.getName()) && (eqI.getLocation() == eq.getLocation())) { return true; } } return false; } /** * Calculates total bonus from Size adjustments * * @param aType * @param aName * @return size adjustment bonus to */ public double getSizeAdjustmentBonusTo(String aType, String aName) { return getBonusDueToType(aType.toUpperCase(), aName.toUpperCase(), "SIZE"); } public Skill getSkillKeyed(final String skillKey) { final List<Skill> skillList = new ArrayList<Skill>(getSkillList()); for (Skill skill : skillList) { if (skill.getKeyName().equalsIgnoreCase(skillKey)) { return skill; } } return null; } /** * Set the order in which skills should be sorted for output. * * @param i * The new output order */ public void setSkillsOutputOrder(final int i) { skillsOutputOrder = i; setDirty(true); } /** * @return The selected Output Order for skills. */ public int getSkillsOutputOrder() { return skillsOutputOrder; } /** * Method will go through the list of classes that the player character has * and see if they are a spell caster and of the desired caster level. * * @param minLevel * @return boolean */ public boolean isSpellCaster(final int minLevel) { return isSpellCaster(minLevel, false) > 0; } /** * Method will go through the list of classes that the player character has * and see if they are a spell caster and of the total of all of their * spellcasting levels is at least the desired caster level. * * @param minLevel * The desired caster level * @param sumOfLevels * True if all of the character caster levels should be added * together before the comparison. * @return boolean */ public int isSpellCaster(final int minLevel, final boolean sumOfLevels) { return isSpellCaster(null, minLevel, sumOfLevels); } /** * Method will go through the list of classes that the player character has * and see if they are a spell caster of the desired type and of the desired * caster level. * * @param spellType * The type of spellcaster (i.e. "ARCANE" or "Divine") * @param minLevel * The desired caster level * @return boolean */ public boolean isSpellCaster(final String spellType, final int minLevel) { return isSpellCaster(spellType, minLevel, false) > 0; } /** * Method will go through the list of classes that the player character has * and see if they are a spell caster of the desired type and of the desired * caster level. * * @param spellType * The type of spellcaster (i.e. "Arcane" or "Divine") * @param minLevel * The desired caster level * @param sumLevels * True if all of the character caster levels should be added * together before the comparison. * @return boolean */ public int isSpellCaster(final String spellType, final int minLevel, final boolean sumLevels) { int classTotal = 0; int runningTotal = 0; for (PCClass pcClass : classList) { if (spellType == null || spellType.equalsIgnoreCase(pcClass.getSpellType())) { int classLevels = (int) getTotalBonusTo("CASTERLEVEL", pcClass .getKeyName()); if ((classLevels == 0) && (canCastSpellTypeLevel(pcClass.getSpellType(), 0, 1) || canCastSpellTypeLevel( pcClass.getSpellType(), 1, 1))) { // missing CASTERLEVEL hack classLevels = pcClass.getLevel(); } classLevels += (int) getTotalBonusTo("PCLEVEL", pcClass.getKeyName()); if (sumLevels) { runningTotal += classLevels; } else { if (classLevels >= minLevel) { classTotal++; } } } } if (sumLevels) { return runningTotal >= minLevel ? 1 : 0; } return classTotal; } public boolean isSpellCastermax(final int maxLevel) { for (PCClass pcClass : classList) { if (pcClass.get(StringKey.SPELLTYPE) != null && (pcClass.getLevel() <= maxLevel)) { return true; } } return false; } public Map<String, Integer> getSpellInfoMap(final String key1, final String key2) { return spellTracker.getSpellInfoMap(key1, key2); } public boolean isSpellLevelforKey(final String key, final int levelMatch) { return spellTracker.isSpellLevelforKey(key, levelMatch); } public int getSpellLevelforKey(final String key, final int levelMatch) { return spellTracker.getSpellLevelforKey(key, levelMatch); } public void getSpellList() { // all non-spellcaster spells are added to race // so return if it's null if (race == null) { return; } race.getSpellSupport().clearCharacterSpells(); addSpells(race); if (deity != null) { addSpells(deity); } for (CharacterDomain cd : characterDomainList) { addSpells(cd.getDomain()); } for (PCClass pcClass : classList) { addSpells(pcClass); } for (Ability ability : getFullAbilitySet()) { addSpells(ability); } final List<Skill> skillList = new ArrayList<Skill>(getSkillList()); for (Skill skill : skillList) { addSpells(skill); } // Domains are skipped - it's assumed that their spells are added to the // first divine spellcasting for (Equipment eq : equipmentList) { if (eq.isEquipped()) { addSpells(eq); for (EquipmentModifier eqMod : eq.getEqModifierList(true)) { addSpells(eqMod); } for (EquipmentModifier eqMod : eq.getEqModifierList(false)) { addSpells(eqMod); } } } for (PCTemplate template : templateList) { addSpells(template); } } /** * Parses a spells range (short, medium or long) into an Integer based on * the spell and spell casters level * * @param aSpell * The spell being output. * @param owner * The class providing the spell. * @param si * The info about conditions applied to the spell * @return spell range */ public String getSpellRange(final Spell aSpell, final PObject owner, final SpellInfo si) { String aRange = aSpell.getRange(); final String aSpellClass = "CLASS:" + (owner != null ? owner.getKeyName() : ""); int rangeInFeet = 0; String aString = Globals.getGameModeSpellRangeFormula(aRange.toUpperCase()); if (aRange.equalsIgnoreCase("CLOSE") && (aString == null)) { aString = "((CASTERLEVEL/2).TRUNC*5)+25"; //$NON-NLS-1$ } else if (aRange.equalsIgnoreCase("MEDIUM") && (aString == null)) { aString = "(CASTERLEVEL*10)+100"; //$NON-NLS-1$ } else if (aRange.equalsIgnoreCase("LONG") && (aString == null)) { aString = "(CASTERLEVEL*40)+400"; //$NON-NLS-1$ } if (aString != null) { List<Ability> metaFeats = null; if (si != null) { metaFeats = si.getFeatList(); rangeInFeet = getVariableValue(aSpell, aString, aSpellClass) .intValue(); } if ((metaFeats != null) && !metaFeats.isEmpty()) { for (Ability feat : metaFeats) { rangeInFeet += (int) feat.bonusTo("SPELL", "RANGE", this, this); final int iMult = (int) feat .bonusTo("SPELL", "RANGEMULT", this, this); if (iMult > 0) { rangeInFeet = (rangeInFeet * iMult); } } } aRange += (" (" + Globals.getGameModeUnitSet() .displayDistanceInUnitSet(rangeInFeet) + Globals.getGameModeUnitSet().getDistanceUnit() + ")"); } else { aRange = parseSpellString(aSpell, aRange, owner); } return aRange; } /** * Computes the Caster Level for a Class * * @param aSpell * @param aName * @return caster level for spell */ public int getCasterLevelForSpell(final Spell aSpell, final String aName) { final String aSpellClass = "CLASS:" + aName; int casterLevel = getVariableValue(aSpell, "CASTERLEVEL", aSpellClass).intValue(); return casterLevel; } /** * Computes the Caster Level for a Class * * @param aClass * @return class caster level */ public int getClassCasterLevel(final PCClass aClass) { final int casterLevel = getVariableValue("CASTERLEVEL", "CLASS:" + aClass.getKeyName()) .intValue(); return casterLevel; } /** * Computes the Caster Level for a race * * @param aRace * @return race caster level */ public int getRaceCasterLevel(final Race aRace) { final int casterLevel = getVariableValue("CASTERLEVEL", "RACE:" + aRace.getKeyName()) .intValue(); return casterLevel; } /** * Calculates total bonus from all stats * * @param aType * @param aName * @return stat bonus to */ public double getStatBonusTo(String aType, String aName) { final List<BonusObj> aList = statList.getBonusListOfType(aType.toUpperCase(), aName .toUpperCase()); return calcBonusFromList(aList); } /** * return bonus from Temporary Bonuses * * @param aType * @param aName * @return temp bonus to */ public double getTempBonusTo(String aType, String aName) { double bonus = 0; if (!getUseTempMods()) { return bonus; } aType = aType.toUpperCase(); aName = aName.toUpperCase(); for (BonusObj bonusObj : getTempBonusList()) { final String bString = bonusObj.toString(); if ((bString.indexOf(aType) < 0) || (bString.indexOf(aName) < 0)) { continue; } final Object tarObj = bonusObj.getTargetObject(); final Object creObj = bonusObj.getCreatorObject(); if ((creObj == null) || (tarObj == null)) { continue; } if (!(creObj instanceof PObject) || !(tarObj instanceof PlayerCharacter)) { continue; } final PlayerCharacter bPC = (PlayerCharacter) tarObj; if (bPC != this) { continue; } bonus += bonusObj.resolve(this, "").doubleValue(); } return bonus; } /** * Parses through all templates to calculate total bonus * * @param aType * @param aName * @return template bonus to */ public double getTemplateBonusTo(String aType, String aName) { return getPObjectWithCostBonusTo(templateList, aType.toUpperCase(), aName.toUpperCase()); } /** * Get the total bonus from Stats, Size, Age, Alignment, Classes, * companions, Equipment, Feats, Templates, Domains, Races, etc This value * is taken from an already populated HashMap for speed * * @param bonusType * Type of bonus ("COMBAT" or "SKILL") * @param bonusName * Name of bonus ("AC" or "Hide"); * @return total bonus to */ public double getTotalBonusTo(final String bonusType, final String bonusName) { final String prefix = new StringBuffer(bonusType).append('.').append(bonusName) .toString(); return sumActiveBonusMap(prefix); } // public List<TypedBonus> getBonusesTo(final String bonusType, final String // bonusName) // { // final String prefix = new // StringBuffer(bonusType).append('.').append(bonusName).toString(); // // final List<TypedBonus> ret = theBonusMap.get(prefix); // if ( ret == null ) // { // return Collections.emptyList(); // } // // return ret; // } public int getTotalLevels() { int totalLevels = 0; totalLevels += totalNonMonsterLevels(); // Monster hit dice count towards total levels -- was // totalMonsterLevels() // sage_sam changed 03 Dec 2002 for Bug #646816 totalLevels += totalHitDice(); return totalLevels; } public int getTotalPlayerLevels() { int totalLevels = 0; totalLevels += totalNonMonsterLevels(); return totalLevels; } /** * Get the value of the desired stat at the point just before the character * was raised to the next level. * * @param statAbb * The short name of the stat to calculate the value of. * @param level * The level we want to see the stat at. * @param includePost * Should stat mods that occurred after levelling be included? * @return The stat as it was at the level */ public int getTotalStatAtLevel(final String statAbb, final int level, final boolean includePost) { int curStat = getStatList().getTotalStatFor(statAbb); for (int idx = getLevelInfoSize() - 1; idx >= level; --idx) { final int statLvlAdjust = pcLevelInfo.get(idx).getTotalStatMod(statAbb, true); curStat -= statLvlAdjust; } // If the user doesn't want POST changes, we remove any made in the // target level only if (!includePost && level > 0) { int statLvlAdjust = pcLevelInfo.get(level - 1).getTotalStatMod(statAbb, true); statLvlAdjust -= pcLevelInfo.get(level - 1).getTotalStatMod(statAbb, false); curStat -= statLvlAdjust; } return curStat; } public int getTwoHandDamageDivisor() { int div = getVariableValue("TWOHANDDAMAGEDIVISOR", Constants.EMPTY_STRING) .intValue(); if (div == 0) { div = 2; } return div; } /** * Get the unarmed damage string for this PC as adjusted by the booleans * passed in. * * @param includeCrit * @param includeStrBonus * @param adjustForPCSize * @return the unarmed damage string */ public String getUnarmedDamageString(final boolean includeCrit, final boolean includeStrBonus, final boolean adjustForPCSize) { String retString = "2|1d2"; for (PCClass pcClass : classList) { retString = PlayerCharacterUtilities.getBestUDamString(retString, pcClass .getUdamForLevel(pcClass.getLevel(), includeCrit, includeStrBonus, this, adjustForPCSize)); } for (PObject pObj : getPObjectList()) { if (pObj == null || pObj instanceof PCClass) { continue; } retString = PlayerCharacterUtilities.getBestUDamString(retString, pObj .getUdamFor(includeCrit, includeStrBonus, this)); } // string is in form sides|damage, just return damage portion return retString.substring(retString.indexOf('|') + 1); } public boolean getUseMasterSkill() { for (CompanionMod cMod : companionModList) { if (cMod.getType().equalsIgnoreCase(getMaster().getType())) { if (cMod.getUseMasterSkill()) { return true; } } } return false; } /** * whether we should use/save Temporary bonuses * * @param aBool */ public void setUseTempMods(final boolean aBool) { useTempMods = aBool; // commented out setDirty because this causes a re-load of all tabs // every time any tab is viewed! merton_monk // setDirty(true); } public boolean getUseTempMods() { return useTempMods; } /** * Evaluates a variable for this character e.g: * getVariableValue("3+CHA","CLASS:Cleric") for Turn Undead * * @param aString * The variable to be evaluated * @param src * The source within which the variable is evaluated * @return The value of the variable */ public Float getVariableValue(final String aString, final String src) { return getVariableValue(null, aString, src); } public Float getVariableValue(final String varName, final String src, final PlayerCharacter aPC) { return getVariableValue(null, varName, src); } /** * Evaluates a variable for this character e.g: * getVariableValue("3+CHA","CLASS:Cleric") for Turn Undead * * @param aSpell * This is specifically to compute bonuses to CASTERLEVEL for a * specific spell. * @param aString * The variable to be evaluated * @param src * The source within which the variable is evaluated * @return The value of the variable */ private Float getVariableValue(final Spell aSpell, String aString, String src) { VariableProcessor vp = getVariableProcessor(); return vp.getVariableValue(aSpell, aString, src, getSpellLevelTemp()); } /** * @return VariableProcessor */ public VariableProcessor getVariableProcessor() { return variableProcessor; } public int getTotalCasterLevelWithSpellBonus(final Spell aSpell, final String spellType, final String classOrRace, final int casterLev) { if (aSpell != null && aSpell.getFixedCasterLevel() != null) { return getVariableValue(aSpell.getFixedCasterLevel(), Constants.EMPTY_STRING).intValue(); } int tBonus = casterLev; boolean replaceCasterLevel = false; String tType; String tStr; // final List<TypedBonus> bonuses = new ArrayList<TypedBonus>(); final List<CasterLevelSpellBonus> bonuses = new ArrayList<CasterLevelSpellBonus>(); if (classOrRace != null) { // bonuses.addAll(getBonusesTo("CASTERLEVEL", classOrRace)); tBonus = (int) getTotalBonusTo("CASTERLEVEL", classOrRace); if (tBonus > 0) { tType = getSpellBonusType("CASTERLEVEL", classOrRace); bonuses.add(new CasterLevelSpellBonus(tBonus, tType)); } // Support both types of syntax for CLASS: // BONUS:CASTERLEVEL|Sorcerer|1 and // BONUS:CASTERLEVEL|CLASS.Sorcerer|1 if (!classOrRace.startsWith("RACE.")) { tStr = "CLASS." + classOrRace; // bonuses.addAll( getBonusesTo("CASTERLEVEL", tStr) ); tBonus = (int) getTotalBonusTo("CASTERLEVEL", tStr); if (tBonus > 0) { tType = getSpellBonusType("CASTERLEVEL", tStr); bonuses.add(new CasterLevelSpellBonus(tBonus, tType)); } } } if (aSpell == null) { return (tBonus); } if (!spellType.equals(Constants.s_NONE)) { tStr = "TYPE." + spellType; // bonuses.addAll( getBonusesTo("CASTERLEVEL", tStr) ); tBonus = (int) getTotalBonusTo("CASTERLEVEL", tStr); if (tBonus > 0) { tType = getSpellBonusType("CASTERLEVEL", tStr); bonuses.add(new CasterLevelSpellBonus(tBonus, tType)); } tStr += ".RESET"; // final List<TypedBonus> reset = getBonusesTo("CASTERLEVEL", tStr); // if ( reset.size() > 0 ) // { // bonuses.addAll(reset); // replaceCasterLevel = true; // } tBonus = (int) getTotalBonusTo("CASTERLEVEL", tStr); if (tBonus > 0) { replaceCasterLevel = true; tType = getSpellBonusType("CASTERLEVEL", tStr); bonuses.add(new CasterLevelSpellBonus(tBonus, tType)); } } tStr = "SPELL." + aSpell.getKeyName(); // bonuses.addAll( getBonusesTo("CASTERLEVEL", tStr) ); tBonus = (int) getTotalBonusTo("CASTERLEVEL", tStr); if (tBonus > 0) { tType = getSpellBonusType("CASTERLEVEL", tStr); bonuses.add(new CasterLevelSpellBonus(tBonus, tType)); } tStr += ".RESET"; // final List<TypedBonus> reset = getBonusesTo("CASTERLEVEL", tStr); // if ( reset.size() > 0 ) // { // bonuses.addAll(reset); // replaceCasterLevel = true; // } tBonus = (int) getTotalBonusTo("CASTERLEVEL", tStr); if (tBonus > 0) { replaceCasterLevel = true; tType = getSpellBonusType("CASTERLEVEL", tStr); bonuses.add(new CasterLevelSpellBonus(tBonus, tType)); } /* * This wraps in TreeSet because it looks to me like this is ordered * (given .RESET) */ for (String school : new TreeSet<String>(aSpell.getSafeListFor(ListKey.SPELL_SCHOOL))) { tStr = "SCHOOL." + school; // bonuses.addAll( getBonusesTo("CASTERLEVEL", tStr) ); tBonus = (int) getTotalBonusTo("CASTERLEVEL", tStr); if (tBonus != 0) // Allow negative bonus to casterlevel { tType = getSpellBonusType("CASTERLEVEL", tStr); bonuses.add(new CasterLevelSpellBonus(tBonus, tType)); } tStr += ".RESET"; // final List<TypedBonus> reset1 = getBonusesTo("CASTERLEVEL", // tStr); // if ( reset.size() > 0 ) // { // bonuses.addAll(reset1); // replaceCasterLevel = true; // } tBonus = (int) getTotalBonusTo("CASTERLEVEL", tStr); if (tBonus > 0) { replaceCasterLevel = true; tType = getSpellBonusType("CASTERLEVEL", tStr); bonuses.add(new CasterLevelSpellBonus(tBonus, tType)); } } for (String subschool : new TreeSet<String>(aSpell.getSafeListFor(ListKey.SPELL_SUBSCHOOL))) { tStr = "SUBSCHOOL." + subschool; // bonuses.addAll( getBonusesTo("CASTERLEVEL", tStr) ); tBonus = (int) getTotalBonusTo("CASTERLEVEL", tStr); if (tBonus > 0) { tType = getSpellBonusType("CASTERLEVEL", tStr); bonuses.add(new CasterLevelSpellBonus(tBonus, tType)); } tStr += ".RESET"; // final List<TypedBonus> reset1 = getBonusesTo("CASTERLEVEL", // tStr); // if ( reset.size() > 0 ) // { // bonuses.addAll(reset1); // replaceCasterLevel = true; // } tBonus = (int) getTotalBonusTo("CASTERLEVEL", tStr); if (tBonus > 0) { replaceCasterLevel = true; tType = getSpellBonusType("CASTERLEVEL", tStr); bonuses.add(new CasterLevelSpellBonus(tBonus, tType)); } } //Not wrapped because it wasn't in 5.14 for (String desc : aSpell.getSafeListFor(ListKey.SPELL_DESCRIPTOR)) { tStr = "DESCRIPTOR." + desc; // bonuses.addAll( getBonusesTo("CASTERLEVEL", tStr) ); tBonus = (int) getTotalBonusTo("CASTERLEVEL", tStr); if (tBonus > 0) { tType = getSpellBonusType("CASTERLEVEL", tStr); bonuses.add(new CasterLevelSpellBonus(tBonus, tType)); } tStr += ".RESET"; // final List<TypedBonus> reset1 = getBonusesTo("CASTERLEVEL", // tStr); // if ( reset.size() > 0 ) // { // bonuses.addAll(reset1); // replaceCasterLevel = true; // } tBonus = (int) getTotalBonusTo("CASTERLEVEL", tStr); if (tBonus > 0) { replaceCasterLevel = true; tType = getSpellBonusType("CASTERLEVEL", tStr); bonuses.add(new CasterLevelSpellBonus(tBonus, tType)); } } final Map<String, Integer> domainMap = aSpell.getLevelInfo(this); if (domainMap != null) { for (String mKey : domainMap.keySet()) { if (mKey.startsWith("DOMAIN|")) { tStr = "DOMAIN." + mKey.substring(7); // bonuses.addAll( getBonusesTo("CASTERLEVEL", tStr) ); tBonus = (int) getTotalBonusTo("CASTERLEVEL", tStr); if (tBonus > 0) { tType = getSpellBonusType("CASTERLEVEL", tStr); bonuses.add(new CasterLevelSpellBonus(tBonus, tType)); } tStr += ".RESET"; // final List<TypedBonus> reset1 = // getBonusesTo("CASTERLEVEL", tStr); // if ( reset.size() > 0 ) // { // bonuses.addAll(reset1); // replaceCasterLevel = true; // } tBonus = (int) getTotalBonusTo("CASTERLEVEL", tStr); if (tBonus > 0) { replaceCasterLevel = true; tType = getSpellBonusType("CASTERLEVEL", tStr); bonuses.add(new CasterLevelSpellBonus(tBonus, tType)); } } } } // now go through all bonuses, checking types to see what should add // together for (int z = 0; z < bonuses.size() - 1; z++) { final CasterLevelSpellBonus zBonus = bonuses.get(z); String zType = zBonus.getType(); if ((zBonus.getBonus() == 0) || zType.equals("")) { continue; } boolean zReplace = false; boolean zStack = false; if (zType.endsWith(".REPLACE")) { zType = zType.substring(0, zType.length() - 8); zReplace = true; } else { if (zType.endsWith(".STACK")) { zType = zType.substring(0, zType.length() - 6); zStack = true; } } for (int k = z + 1; k < bonuses.size(); k++) { final CasterLevelSpellBonus kBonus = bonuses.get(k); String kType = kBonus.getType(); if ((kBonus.getBonus() == 0) || kType.equals("")) { continue; } boolean kReplace = false; boolean kStack = false; if (kType.endsWith(".REPLACE")) { kType = kType.substring(0, kType.length() - 8); kReplace = true; } else { if (kType.endsWith(".STACK")) { kType = kType.substring(0, kType.length() - 6); kStack = true; } } if (!zType.equals(kType)) { continue; } // if both end in ".REPLACE", add together and save for later // comparison if (zReplace && kReplace) { kBonus.setBonus(zBonus.getBonus() + kBonus.getBonus()); zBonus.setBonus(0); continue; } // if either ends in ".STACK", then they will add if (zStack || kStack) { continue; } // otherwise, only keep max if (zBonus.getBonus() > kBonus.getBonus()) { kBonus.setBonus(0); } else { zBonus.setBonus(0); } } } int result = 0; if (!replaceCasterLevel) { result += casterLev; } // result += TypedBonus.totalBonuses(bonuses); // Now go through bonuses and add it up for (CasterLevelSpellBonus resultBonus : bonuses) { result += resultBonus.getBonus(); } if (result <= 0) { result = 1; // Casterlevel must be at least 1 } return (result); } private String getSpellBonusType(final String bonusType, final String bonusName) { String prefix = new StringBuffer(bonusType).append('.').append(bonusName) .toString(); prefix = prefix.toUpperCase(); for (String aKey : getActiveBonusMap().keySet()) { String aString = aKey; // rString could be something like: // COMBAT.AC:Armor.REPLACE // So need to remove the .STACK or .REPLACE // to get a match for prefix like: COMBAT.AC:Armor if (aKey.endsWith(".STACK")) { aString = aKey.substring(0, aKey.length() - 6); } else if (aKey.endsWith(".REPLACE")) { aString = aKey.substring(0, aKey.length() - 8); } // if prefix is of the form: // COMBAT.AC // then it must match // COMBAT.AC // COMBAT.AC:Luck // COMBAT.AC:Armor.REPLACE // However, it must not match // COMBAT.ACCHECK if ((aString.length() > prefix.length()) && !aString.startsWith(prefix + ":")) { continue; } if (aString.startsWith(prefix)) { final int typeIndex = aString.indexOf(":"); if (typeIndex > 0) { return (aKey.substring(typeIndex + 1)); // use aKey to get // .REPLACE or // .STACK } return Constants.EMPTY_STRING; // no type; } } return Constants.EMPTY_STRING; // just return no type } public List<Vision> getVisionList() { /* * TODO This is a temporary hack until a better cache dirty method * is established - the problem is that initializeVisionCache triggers * cache reset, which is a problem for getting back the right value * from this method unless the cache is maintained :P */ ObjectCache myCache = cache; if (!myCache.containsListFor(ListKey.VISION_CACHE)) { myCache.initializeVisionCache(this); } return myCache.getListFor(ListKey.VISION_CACHE); } public String getVision() { final StringBuffer visionString = new StringBuffer(); final List<Vision> visionList = getVisionList(); for (Vision vision : visionList) { if (visionString.length() > 0) { visionString.append(", "); } visionString.append(vision); } return visionString.toString(); } public int abilityAC() { return calcACOfType("Ability"); } /** * adds CharacterDomain to list * * @param aCD */ public void addCharacterDomain(final CharacterDomain aCD) { if ((aCD != null) && !characterDomainList.contains(aCD) && (aCD.getDomain() != null)) { characterDomainList.add(aCD); final PCClass domainClass = getClassKeyed(aCD.getObjectName()); if (domainClass != null) { final int _maxLevel = domainClass.getMaxCastLevel(); aCD.getDomain().addSpellsToClassForLevels(domainClass, 0, _maxLevel); } setDirty(true); } } /** * Sets the source of granted domains * * @param aType * @param aName * @param aLevel * @param dNum */ public void addDomainSource(final String aType, final String aName, final int aLevel, final int dNum) { final String aString = aType + Constants.PIPE + aName + Constants.PIPE + aLevel; final String sNum = Integer.toString(dNum); domainSourceMap.put(aString, sNum); setDirty(true); } /** * returns all equipment (from the equipmentList) of type aString * * @param aList * @param aType * @return List */ public List<Equipment> addEqType(final List<Equipment> aList, final String aType) { for (Equipment eq : getEquipmentList()) { if (eq.typeStringContains(aType)) { aList.add(eq); setDirty(true); } else if (aType.equalsIgnoreCase("CONTAINED") && (eq.getParent() != null)) { aList.add(eq); setDirty(true); } } return aList; } /** * Adds a <tt>Kit</tt> to the applied list of kits for the character. * * @param aKit * The <tt>Kit</tt> to add. */ public void addKit(final Kit aKit) { if (kitList == null) { kitList = new ArrayList<Kit>(); } kitList.add(aKit); setDirty(true); } public void addLanguage(final Language aLang) { languages.add(aLang); setDirty(true); } public void addLanguageKeyed(final String aKey) { final Language aLang = Globals.getContext().ref.silentlyGetConstructedCDOMObject(Language.class, aKey); if (aLang != null) { addLanguage(aLang); } } public void addNaturalWeapons(final List<Equipment> weapons) { equipmentListAddAll(weapons); EquipSet eSet = getEquipSetByIdPath("0.1"); if (eSet != null) { for (Equipment eq : weapons) { EquipSet es = addEquipToTarget(eSet, null, "", eq, null); if (es == null) { addEquipToTarget(eSet, null, Constants.S_CARRIED, eq, null); } } } setDirty(true); } public void addShieldProf(final String aProf) { if (!shieldProfList.contains(aProf)) { // // Insert all types at the head of the list // if (aProf.startsWith("SHIELDTYPE=") || aProf.startsWith("SHIELDTYPE.")) { shieldProfList.add(0, aProf); } else if (aProf.startsWith("TYPE=") || aProf.startsWith("TYPE.")) { shieldProfList.add(0, aProf); } else { shieldProfList.add(aProf); } } } public void addShieldProfs(final List<String> aList) { for (String prof : aList) { addShieldProf(prof); } } public Skill addSkill(final Skill addSkill) { Skill retSkill; // // First, check to see if skill is already in list // final List<Skill> skillList = new ArrayList<Skill>(getSkillList()); for (Skill skill : skillList) { if (skill.getKeyName().equals(addSkill.getKeyName())) { return skill; } } // // Skill not found, add to list // retSkill = addSkill.clone(); getSkillList().add(retSkill); setDirty(true); if (!isImporting()) { retSkill.globalChecks(this); calcActiveBonuses(); } return retSkill; } /** * @param acs * is the CharacterSpell object containing the spell which is to * be modified * @param aFeatList * is the list of feats to be added to the SpellInfo object * @param classKey * is the name of the class whose list of character spells will * be modified * @param bookName * is the name of the book for the SpellInfo object * @param spellLevel * is the original (unadjusted) level of the spell not including * feat adjustments * @param adjSpellLevel * is the adjustedLevel (including feat adjustments) of this * spell, it may be higher if the user chooses a higher level. * * @return an empty string on successful completion, otherwise the return * value indicates the reason the add function failed. */ public String addSpell(CharacterSpell acs, final List<Ability> aFeatList, final String classKey, final String bookName, final int adjSpellLevel, final int spellLevel) { if (acs == null) { return "Invalid parameter to add spell"; } PCClass aClass = null; final Spell aSpell = acs.getSpell(); if ((bookName == null) || (bookName.length() == 0)) { return "Invalid spell list/book name."; } SpellBook spellBook = getSpellBookByName(bookName); if (spellBook == null) { return "Could not find spell list/book " + bookName; } if (classKey != null) { aClass = getClassKeyed(classKey); if ((aClass == null) && (classKey.lastIndexOf('(') >= 0)) { aClass = getClassKeyed(classKey.substring(0, classKey.lastIndexOf('(')).trim()); } } // If this is a spellbook, the class doesn't have to be one the PC has // already. if (aClass == null && spellBook.getType() == SpellBook.TYPE_SPELL_BOOK) { aClass = Globals.getContext().ref.silentlyGetConstructedCDOMObject(PCClass.class, classKey); if ((aClass == null) && (classKey.lastIndexOf('(') >= 0)) { aClass = Globals.getContext().ref.silentlyGetConstructedCDOMObject(PCClass.class, classKey.substring(0, classKey.lastIndexOf('(')).trim()); } } if (aClass == null) { return "No class keyed " + classKey; } if (!aClass.getSafe(ObjectKey.MEMORIZE_SPELLS) && !bookName.equals(Globals.getDefaultSpellBook())) { return aClass.getDisplayName() + " can only add to " + Globals.getDefaultSpellBook(); } // Divine spellcasters get no bonus spells at level 0 // TODO: allow classes to define how many bonus spells they get each // level! // int numSpellsFromSpecialty = aClass.getNumSpellsFromSpecialty(); // if (spellLevel == 0 && // "Divine".equalsIgnoreCase(aClass.getSpellType())) // { // numSpellsFromSpecialty = 0; // } // all the exists checks are done. // don't allow adding spells which are not qualified for. if (!PrereqHandler.passesAll(aSpell.getPrerequisiteList(), this, aSpell)) { return "You do not qualify for " + acs.getSpell().getDisplayName() + "."; } // don't allow adding spells which are prohibited to known // or prepared lists // But if a spell is both prohibited and in a speciality // which can be the case for some spells, then allow it. if (spellBook.getType() != SpellBook.TYPE_SPELL_BOOK && !acs.isSpecialtySpell() && aClass.isProhibited(aSpell, this)) { return acs.getSpell().getDisplayName() + " is prohibited."; } // Now let's see if they should be able to add this spell // first check for known/cast/threshold final int known = aClass.getKnownForLevel(spellLevel, this); int specialKnown = 0; final int cast = aClass.getCastForLevel(adjSpellLevel, bookName, true, true, this); aClass.memorizedSpellForLevelBook(adjSpellLevel, bookName); final boolean isDefault = bookName.equals(Globals.getDefaultSpellBook()); if (isDefault) { specialKnown = aClass.getSpecialtyKnownForLevel(spellLevel, this); } int numPages = 0; // known is the maximum spells that can be known this level // listNum is the current spells already memorized this level // cast is the number of spells that can be cast at this level // Modified this to use new availableSpells() method so you can "blow" // higher-level slots on // lower-level spells // in re BUG [569517] // sk4p 13 Dec 2002 if (spellBook.getType() == SpellBook.TYPE_SPELL_BOOK) { // If this is a spellbook rather than known spells // or prepared spells, then let them add spells up to // the page limit of the book. setSpellLevelTemp(spellLevel); /* * TODO Need to understand more about this context of formula * resolution (in context of a spell??) in order to understand how * to put this method into the Formula interface */ numPages = getVariableValue(acs.getSpell(), spellBook.getPageFormula().toString(), "").intValue(); // Check number of pages remaining in the book if (numPages + spellBook.getNumPagesUsed() > spellBook .getNumPages()) { return "There are not enough pages left to add this spell to the spell book."; } spellBook.setNumPagesUsed(numPages + spellBook.getNumPagesUsed()); spellBook.setNumSpells(spellBook.getNumSpells() + 1); } else if (!aClass.getSafe(ObjectKey.MEMORIZE_SPELLS) && !availableSpells(adjSpellLevel, aClass, bookName, true, acs .isSpecialtySpell())) { String ret; int maxAllowed; // If this were a specialty spell, would there be room? if (!acs.isSpecialtySpell() && availableSpells(adjSpellLevel, aClass, bookName, true, true)) { ret = "Your remaining slot(s) must be filled with your speciality."; maxAllowed = known; } else { ret = "You can only learn " + (known + specialKnown) + " spells for level " + adjSpellLevel + "\nand there are no higher-level slots available."; maxAllowed = known + specialKnown; } int memTot = aClass.memorizedSpellForLevelBook(adjSpellLevel, bookName); int spellDifference = maxAllowed - memTot; if (spellDifference > 0) { ret += "\n" + spellDifference + " spells from lower levels are using slots for this level."; } return ret; } else if (aClass.getSafe(ObjectKey.MEMORIZE_SPELLS) && !isDefault && !availableSpells(adjSpellLevel, aClass, bookName, false, acs .isSpecialtySpell())) { String ret; int maxAllowed; if (!acs.isSpecialtySpell() && availableSpells(adjSpellLevel, aClass, bookName, false, true)) { ret = "Your remaining slot(s) must be filled with your speciality or domain."; maxAllowed = aClass.getCastForLevel(adjSpellLevel, bookName, false, true, this); } else { ret = "You can only prepare " + cast + " spells for level " + adjSpellLevel + "\nand there are no higher-level slots available."; maxAllowed = cast; } int memTot = aClass.memorizedSpellForLevelBook(adjSpellLevel, bookName); int spellDifference = maxAllowed - memTot; if (spellDifference > 0) { ret += "\n" + spellDifference + " spells from lower levels are using slots for this level."; } return ret; } // determine if this spell already exists // for this character in this book at this level SpellInfo si = null; final List<CharacterSpell> acsList = aClass.getSpellSupport().getCharacterSpell(acs.getSpell(), bookName, adjSpellLevel); if (!acsList.isEmpty()) { for (int x = acsList.size() - 1; x >= 0; x--) { final CharacterSpell c = acsList.get(x); if (!c.equals(acs)) { acsList.remove(x); } } } final boolean isEmpty = acsList.isEmpty(); if (!isEmpty) { // I am not sure why this code is set up like this but it is // bogus. I am trying to break as little as possible so if // I have one matching spell I will use it otherwise I will // use the passed in spell. if (acsList.size() == 1) { final CharacterSpell tcs = acsList.get(0); si = tcs.getSpellInfoFor(bookName, adjSpellLevel, -1, aFeatList); } else { si = acs.getSpellInfoFor(bookName, adjSpellLevel, -1, aFeatList); } } if (si != null) { // ok, we already known this spell, so if they are // trying to add it to the default spellBook, barf // otherwise increment the number of times memorised if (isDefault) { return "The Known Spells spellbook contains all spells of this level that you know. You cannot place spells in multiple times."; } si.setTimes(si.getTimes() + 1); } else { if (isEmpty && !aClass.getSpellSupport().containsCharacterSpell(acs)) { aClass.getSpellSupport().addCharacterSpell(acs); } else if (isEmpty) { // Make sure that we are working on the same spell object, not just the same spell for (CharacterSpell characterSpell : aClass.getSpellSupport() .getCharacterSpellList()) { if (characterSpell.equals(acs)) { acs = characterSpell; } } } si = acs.addInfo(adjSpellLevel, 1, bookName, aFeatList); // // if (Globals.hasSpellPPCost()) { final Spell theSpell = acs.getSpell(); int ppCost = theSpell.getSafe(IntegerKey.PP_COST); for (Ability feat : aFeatList) { ppCost += (int) feat.bonusTo("PPCOST", theSpell.getKeyName(), this, this); } si.setActualPPCost(ppCost); } if (Spell.hasSpellPointCost()) { final Spell theSpell = acs.getSpell(); int spellPointCost = theSpell.getSpellPointCostActual(); for (Ability feat : aFeatList) { spellPointCost += (int) feat.bonusTo("SPELLPOINTCOST", theSpell .getKeyName(), this, this); } si.setActualSpellPointCost(spellPointCost); } } // Set number of pages on the spell si.setNumPages(si.getNumPages() + numPages); setDirty(true); return ""; } /** * return value indicates if book was actually added or not * * @param aName * @return TRUE or FALSE */ public boolean addSpellBook(final String aName) { if (aName != null && (aName.length() > 0) && !spellBooks.contains(aName)) { return addSpellBook(new SpellBook(aName, SpellBook.TYPE_PREPARED_LIST)); } return false; } /** * return value indicates if book was actually added or not * * @param book * @return TRUE or FALSE */ public boolean addSpellBook(final SpellBook book) { if (book != null) { String aName = book.getName(); if (!spellBooks.contains(aName)) { spellBooks.add(aName); spellBookMap.put(aName, book); setDirty(true); return true; } } return false; } public PCTemplate addTemplate(final PCTemplate inTemplate) { return addTemplate(inTemplate, true); } public PCTemplate addTemplate(final PCTemplate inTemplate, boolean doChoose) { if (inTemplate == null) { return null; } // Don't allow multiple copies of template. // Given that we clone everything will this ever // evaluate to true ? if (templateList.contains(inTemplate)) { return null; } // Search for a template with this name already // assigned to this character for (PCTemplate template : templateList) { if (template.getKeyName().equals(inTemplate.getKeyName())) { return null; // template with duplicate name } } // Add a clone of the template passed in int lockMonsterSkillPoints = 0; // this is what this value was before // adding this template for (PCClass pcClass : classList) { if (pcClass.isMonster()) { lockMonsterSkillPoints = (int) getTotalBonusTo("MONSKILLPTS", "LOCKNUMBER"); break; } } final PCTemplate inTmpl; try { inTmpl = inTemplate.clone(); templateList.add(inTmpl); } catch (CloneNotSupportedException e) { return null; } // If we are importing these levels will have been saved with the // character so don't apply them again. if (!isImporting()) { for (LevelCommandFactory lcf : inTemplate .getSafeListFor(ListKey.ADD_LEVEL)) { lcf.add(this); } } this.setArmorProfListStable(false); calcActiveBonuses(); for (CDOMReference<Language> ref : inTmpl .getSafeListFor(ListKey.AUTO_LANGUAGES)) { templateAutoLanguages.addAll(ref.getContainedObjects()); } addStartingLanguages(inTmpl, templateLanguages); getAutoLanguages(); addNaturalWeapons(inTmpl.getNaturalWeapons()); inTmpl.chooseLanguageAutos(isImporting(), this); if (PlayerCharacterUtilities.canReassignTemplateFeats()) { final List<String> templateFeats = feats(inTmpl, getTotalLevels(), totalHitDice(), true); for (int i = 0, x = templateFeats.size(); i < x; ++i) { AbilityUtilities.modFeatsFromList(this, null, templateFeats .get(i), true, false); } } else { setAutomaticAbilitiesStable(null, false); // setAutomaticFeatsStable(false); } if (doChoose) { selectTemplates(inTmpl, isImporting()); } else { Collection<PCTemplate> list = getTemplatesAdded(inTmpl); for (PCTemplate pct : list) { addTemplate(pct); } } if (!isImporting()) { getSpellList(); inTmpl.globalChecks(this); inTmpl.checkRemovals(this); } setAggregateAbilitiesStable(null, false); // setAutomaticFeatsStable(false); // setAggregateFeatsStable(false); getAutomaticAbilityList(AbilityCategory.FEAT); // rebuildFeatAggreagateList(); calcActiveBonuses(); int postLockMonsterSkillPoints; // this is what this value was before // adding this template boolean first = true; for (PCClass pcClass : classList) { if (pcClass.isMonster()) { postLockMonsterSkillPoints = (int) getTotalBonusTo("MONSKILLPTS", "LOCKNUMBER"); if (postLockMonsterSkillPoints != lockMonsterSkillPoints && postLockMonsterSkillPoints > 0) { for (PCLevelInfo pi : getLevelInfo()) { final int newSkillPointsGained = pcClass .recalcSkillPointMod(this, pi.getLevel()); if (pi.getClassKeyName().equals(pcClass.getKeyName())) { final int formerGained = pi.getSkillPointsGained(); pi.setSkillPointsGained(newSkillPointsGained); pi.setSkillPointsRemaining(pi .getSkillPointsRemaining() + newSkillPointsGained - formerGained); pcClass.setSkillPool(pcClass.getSkillPool(this) + newSkillPointsGained - formerGained); setSkillPoints(getSkillPoints() + newSkillPointsGained - formerGained); } } } } // // Recalculate HPs in case HD have changed. // if (!isImporting()) { Modifier<HitDie> dieLock = inTemplate.get(ObjectKey.HITDIE); if (dieLock != null) { for (int level = 1; level <= pcClass.getLevel(); level++) { HitDie baseHD = pcClass.getSafe(ObjectKey.LEVEL_HITDIE); if (!baseHD.equals(pcClass.getLevelHitDie(this, level))) { // If the HD has changed from base reroll pcClass.rollHP(this, level, first); } } } } first = false; } // karianna bug 1184888 adjustMoveRates(); setDirty(true); return inTmpl; } public void addWeaponProf(final String aProfKey) { final WeaponProf wp = Globals.getContext().ref.silentlyGetConstructedCDOMObject(WeaponProf.class, aProfKey); if (wp != null) { // weaponProfList.add(wp); if (theWeaponProfs == null) { theWeaponProfs = new TreeSet<WeaponProf>(); } theWeaponProfs.add(wp); setDirty(true); } } public void adjustGold(final double delta) { // I don't really like this hack, but setScale just won't work right... gold = new BigDecimal(gold.doubleValue() + delta).divide( BigDecimal.ONE, 2, BigDecimal.ROUND_HALF_EVEN); setDirty(true); } /** * recalculate all the move rates and modifiers */ public void adjustMoveRates() { movements = null; movementTypes = null; movementMult = null; movementMultOp = null; if (getRace() == null) { return; } List<Movement> mms = getRace().getListFor(ListKey.MOVEMENT); if (mms == null || mms.isEmpty() || (!mms.get(0).isInitialized())) { return; } Movement movement = mms.get(0); movements = movement.getMovements(); movementTypes = movement.getMovementTypes(); movementMult = movement.getMovementMult(); movementMultOp = movement.getMovementMultOp(); setMoveFromList(getCDOMObjectList()); // temp mods // TODO This would never do anything since setMoveFromList only // handles PObjects // if (!getTempBonusList().isEmpty() && getUseTempMods()) // { // setMoveFromList(getTempBonusList()); // } // Need to create movement entries if there is a BONUS:MOVEADD // associated with that type of movement for (final BonusObj bonus : getActiveBonusList()) // for ( final BonusObj bonus : getAllActiveBonuses() ) { if (bonus.getTypeOfBonus().equals("MOVEADD")) { String moveType = bonus.getBonusInfo(); if (moveType.startsWith("TYPE")) { moveType = moveType.substring(5); } moveType = CoreUtility.capitalizeFirstLetter(moveType); boolean found = false; for (int i = 0; i < movements.length; i++) { if (moveType.equals(movementTypes[i])) { found = true; } } if (!found) { setMyMoveRates(moveType, 0.0, Double.valueOf(0.0), "", 1); } } } setDirty(true); } public List<Spell> aggregateSpellList(final String school, final String subschool, final String descriptor, final int minLevel, final int maxLevel) { final List<Spell> retList = new ArrayList<Spell>(); for (PObject pObj : getSpellClassList()) { for (int a = minLevel; a <= maxLevel; a++) { final List<CharacterSpell> aList = pObj.getSpellSupport().getCharacterSpell(null, "", a); for (CharacterSpell cs : aList) { final Spell aSpell = cs.getSpell(); if ((school.length() == 0) || aSpell.containsInList(ListKey.SPELL_SCHOOL, school) || (subschool.length() == 0) || aSpell.containsInList(ListKey.SPELL_SUBSCHOOL, subschool) || (descriptor.length() == 0) || aSpell.containsInList(ListKey.SPELL_DESCRIPTOR, descriptor)) { retList.add(aSpell); } } } } return retList; } public int altHP() { final int i = (int) getTotalBonusTo("HP", "ALTHP"); return i; } public int baseAC() { return calcACOfType("Base"); } /** * @return Total base attack bonus as an int */ public int baseAttackBonus() { // check for cached version final String cacheLookup = "BaseAttackBonus"; Float total; if (epicBAB != null) { total = epicBAB.floatValue(); } else { total = getVariableProcessor().getCachedVariable(cacheLookup); } if (total != null) { return total.intValue(); } // get Master's BAB final PlayerCharacter nPC = getMasterPC(); if ((nPC != null) && (getCopyMasterBAB().length() > 0)) { int masterBAB = nPC.baseAttackBonus(); final String copyMasterBAB = replaceMasterString(getCopyMasterBAB(), masterBAB); masterBAB = getVariableValue(copyMasterBAB, "").intValue(); getVariableProcessor().addCachedVariable(cacheLookup, Float.valueOf(masterBAB)); return masterBAB; } // Check for Epic final int totalClassLevels = getTotalCharacterLevel(); Map<String, String> totalLvlMap = null; final Map<String, String> classLvlMap; boolean isEpic = false; if (totalClassLevels > SettingsHandler.getGame().getBabMaxLvl()) { isEpic = true; if (epicBAB == null) { totalLvlMap = getTotalLevelHashMap(); classLvlMap = getCharacterLevelHashMap(SettingsHandler.getGame() .getBabMaxLvl()); // ensure total class-levels below some value (e.g. 20) getVariableProcessor().pauseCache(); setClassLevelsBrazenlyTo(classLvlMap); } else { //Logging.errorPrint("baseAttackBonus(): '" + cacheLookup + "' = epic:'" + epicBAB + "'"); //$NON-NLS-1$ return epicBAB; } } final int bab = (int) getTotalBonusTo("COMBAT", "BAB"); if (isEpic) { epicBAB = bab; } if (totalLvlMap != null) { setClassLevelsBrazenlyTo(totalLvlMap); getVariableProcessor().restartCache(); } getVariableProcessor().addCachedVariable(cacheLookup, Float.valueOf(bab)); return bab; } /** * get the base MOVE: plus any bonuses from BONUS:MOVE additions does not * take into account Armor penalties to movement does not take into account * penalties due to load carried * * @param moveIdx * @param load * @return base movement */ public int basemovement(final int moveIdx, final Load load) { // get base movement final int move = getMovement(moveIdx).intValue(); return move; } // TODO - Fix this to do 90% of the parsing work up front. public int calcACOfType(final String ACType) { final List<String> addList = SettingsHandler.getGame().getACTypeAddString(ACType); final List<String> removeList = SettingsHandler.getGame().getACTypeRemoveString(ACType); if ((addList == null) && (removeList == null)) { Logging.errorPrint("Invalid ACType: " + ACType); return 0; } int AC = 0; if (addList != null) { // final List<TypedBonus> bonuses = new ArrayList<TypedBonus>(); for (String aString : addList) { final PObject aPObj = new PObject(); getPreReqFromACType(aString, aPObj); if (PrereqHandler.passesAll(aPObj.getPrerequisiteList(), this, aPObj)) { final StringTokenizer aTok = new StringTokenizer(aString, "|"); AC += subCalcACOfType(aTok); // while ( aTok.hasMoreTokens() ) // { // bonuses.addAll( // TypedBonus.getBonusesOfType(getBonusesTo("COMBAT", "AC"), // aTok.nextToken()) ); // // } // AC += TypedBonus.totalBonuses(bonuses); } } } if (removeList != null) { // final List<TypedBonus> bonuses = new ArrayList<TypedBonus>(); for (String rString : removeList) { final PObject aPObj = new PObject(); getPreReqFromACType(rString, aPObj); if (PrereqHandler.passesAll(aPObj.getPrerequisiteList(), this, aPObj)) { final StringTokenizer aTok = new StringTokenizer(rString, "|"); AC -= subCalcACOfType(aTok); // while ( aTok.hasMoreTokens() ) // { // bonuses.addAll( // TypedBonus.getBonusesOfType(getBonusesTo("COMBAT", "AC"), // aTok.nextToken()) ); // // } // AC -= TypedBonus.totalBonuses(bonuses); } } } return AC; } /** * Creates the activeBonusList which is used to calculate all the bonuses to * a PC */ public void calcActiveBonuses() { if (isImporting() || (race == null) || rebuildingAbilities) { return; } // build the Variable HashSet buildVariableSet(); // Keep rebuilding the active bonus map until the // contents do not change. This is to cope with the // situation where we have a variable A that has a prereq // that depends on variable B that will not be the correct // value until after the map has been completely created. // Get the original value of the map. // String origMapVal = theBonusMap.toString(); String origMapVal = activeBonusMap.toString(); // ensure that the values for the looked up variables are the most up to // date setDirty(true); calcActiveBonusLoop(); // Get the new contents of the map // String mapVal = theBonusMap.toString(); String mapVal = activeBonusMap.toString(); // As the map is a TreeMap we know that the contents will be in // alphabetical order, so doing a straight string compare is // the easiest way to compare the whole tree. while (!mapVal.equals(origMapVal)) { // If the newly calculated bonus map is different to the old one // loop again until they are the same. setDirty(true); calcActiveBonusLoop(); origMapVal = mapVal; // mapVal = theBonusMap.toString(); mapVal = activeBonusMap.toString(); } } private List<BonusObj> getAllActiveBonuses() { List<BonusObj> ret = new ArrayList<BonusObj>(); for (final PObject pobj : getPObjectList()) { // We exclude equipmods here as their bonuses are already counted in // the equipment they belong to. if (pobj != null && !(pobj instanceof EquipmentModifier)) { // Class bonuses are only included if the level is greater than 0 // This is because 0 levels of a class can be added to access spell casting etc if (!(pobj instanceof PCClass) || ((PCClass) pobj).getLevel() > 0) { pobj.activateBonuses(this); ret.addAll(pobj.getActiveBonuses(this)); } } } ret.addAll(getPurchaseModeBonuses()); if (getUseTempMods()) { ret.addAll(getTempBonuses()); } ret = Bonus.sortBonusList(ret); return ret; } /* * These are designed to catch a re-entrant bonus loop, which can occur * when a BONUS contains a level limited item in a Formula, such as BAB */ private int cablInt = 1; private int lastCablInt = 0; private void calcActiveBonusLoop() { if (cablInt == lastCablInt) { return; } lastCablInt = cablInt; final List<BonusObj> bonuses = getAllActiveBonuses(); activeBonusList = bonuses; // buildBonusMap(bonuses); buildActiveBonusMap(); cablInt++; } /** * Calculate the Challenge Rating * * @return CR */ public float calcCR() { float CR = 0; // Calculate and add the CR from the PC Classes for (PCClass pcClass : classList) { CR += pcClass.calcCR(this); } // Calculate and add the CR from the templates for (PCTemplate template : templateList) { CR += template.getCR(getTotalLevels(), totalHitDice()); } // Calculate and add the CR from race ChallengeRating cr = race.getSafe(ObjectKey.CHALLENGE_RATING); final float raceCR = cr.getRating().resolve(this, "").floatValue(); // If the total CR to date is 0 then add race CR, e.g. A Lizard has CR of 1/2 if (CR == 0) { CR += raceCR; } // Else if the total CR so far is 1 or greater and the race CR is greater // than or equal to 1 then add the race CR else if (CR >= 1 && raceCR >= 1) { CR += raceCR; } // Calculate and add in the MISC bonus to CR CR += (float) getTotalBonusTo("MISC", "CR"); return CR; } /** * Gets a list of all sources of DRs. * * @return List of DRs */ public List<DamageReduction> getDRList() { List<DamageReduction> drList = new ArrayList<DamageReduction>(); for (CDOMObject obj : getCDOMObjectList()) { List<DamageReduction> objList = obj .getListFor(ListKey.DAMAGE_REDUCTION); if (objList != null) { drList.addAll(objList); } } return DamageReduction.getDRList(this, drList); } /** * Get all possible sources of Damage Resistance and calculate * * @return DR */ public String calcDR() { return DamageReduction.getDRString(this, getDRList()); } public double calcMoveMult(final double move, final int index) { double iMove = 0; if (movementMultOp[index].charAt(0) == '*') { iMove = move * movementMult[index].doubleValue(); } else if (movementMultOp[index].charAt(0) == '/') { iMove = move / movementMult[index].doubleValue(); } if (iMove > 0) { return iMove; } return move; } public int calcSR(final boolean includeEquipment) { int SR = race.getSR(this); if (deity != null) { SR = Math.max(SR, deity.getSR(this)); } for (CompanionMod cMod : companionModList) { SR = Math.max(SR, cMod.getSR(this)); } for (PCClass pcClass : classList) { SR = Math.max(SR, pcClass.getSR(this)); } for (Ability aFeat : getFullAbilitySet()) { SR = Math.max(SR, aFeat.getSR(this)); } final List<Skill> skillList = new ArrayList<Skill>(getSkillList()); for (Skill skill : skillList) { SR = Math.max(SR, skill.getSR(this)); } for (CharacterDomain cd : characterDomainList) { if (cd.getDomain() != null) { SR = Math.max(cd.getDomain().getSR(this), SR); } } if (includeEquipment) { for (Equipment eq : equipmentList) { if (eq.isEquipped()) { SR = Math.max(SR, eq.getSR(this)); for (EquipmentModifier eqMod : eq.getEqModifierList(true)) { SR = Math.max(SR, eqMod.getSR(this)); } for (EquipmentModifier eqMod : eq.getEqModifierList(false)) { SR = Math.max(SR, eqMod.getSR(this)); } } } } final int atl = getTotalLevels(); final int thd = totalHitDice(); for (PCTemplate template : templateList) { SR = Math.max(SR, TemplateSR.getSR(template, atl, thd, this)); } SR += (int) getTotalBonusTo("MISC", "SR"); // SR += (int) getBonusValue("MISC", "SR"); // // This would make more sense to just not add in the first place... // if (!includeEquipment) { SR -= (int) getEquipmentBonusTo("MISC", "SR"); } return SR; } /** * Method will go through the list of classes that the PC has and see if * they can cast spells of desired type at desired <b>spell level</b>. * * @param spellType * Spell type to check for * @param spellLevel * Desired spell level * @param minNumSpells * Minimum number of spells at the desired spell level * @return boolean <p/> author David Wilson * <eldiosyeldiablo@users.sourceforge.net> */ public boolean canCastSpellTypeLevel(final String spellType, final int spellLevel, final int minNumSpells) { for (PCClass aClass : classList) { String classSpellType = aClass.get(StringKey.SPELLTYPE); if (classSpellType != null && ("Any".equalsIgnoreCase(spellType) || classSpellType .equalsIgnoreCase(spellType))) { // Get the number of known spells for the level int knownForLevel = aClass.getKnownForLevel(spellLevel, this); knownForLevel += aClass.getSpecialtyKnownForLevel(spellLevel, this); if (knownForLevel >= minNumSpells) { return true; } // See if the character can cast // at the required spell level if (aClass.getCastForLevel(spellLevel, this) >= minNumSpells) { return true; } // If they don't memorise spells and don't have // a CastList then they use something funky // like Power Points (psionic) if (!aClass.getSafe(ObjectKey.MEMORIZE_SPELLS) && !aClass.hasKnownList() && aClass.zeroCastSpells()) { return true; } } } return false; } /** * Check whether a deity can be selected by this character * * @return <code>true</code> means the deity can be a selected by a * character with the given properties; <code>false</code> means * the character cannot. */ public boolean canSelectDeity(final Deity aDeity) { if (aDeity == null) { return false; } boolean result; if (classList.isEmpty()) { result = true; } else { result = false; CLASS: for (PCClass aClass : classList) { List<CDOMReference<Deity>> deityList = aClass .getListFor(ListKey.DEITY); if (deityList == null) { result = true; break; } else { for (CDOMReference<Deity> deity : deityList) { if (deity.contains(aDeity)) { result = true; break CLASS; } } } } } return result && PrereqHandler.passesAll(aDeity.getPrerequisiteList(), this, aDeity); } public int classAC() { return calcACOfType("ClassDefense"); } /** * Return value indicates whether or not a spell was deleted. * * @param si * @param aClass * @param bookName * @return String */ public String delSpell(SpellInfo si, final PCClass aClass, final String bookName) { if ((bookName == null) || (bookName.length() == 0)) { return "Invalid spell book name."; } if (aClass == null) { return "Error: Class is null"; } final CharacterSpell acs = si.getOwner(); final boolean isDefault = bookName.equals(Globals.getDefaultSpellBook()); // yes, you can remove spells from the default spellbook, // but they will just get added back in when the character // is re-loaded. But, allow them to do it anyway, just in case // there is some weird spell that keeps getting loaded by // accident (or is saved in the .pcg file) if (isDefault && aClass.isAutoKnownSpell(acs.getSpell().getKeyName(), si .getActualLevel(), this)) { Logging.errorPrint("Notice: removing " + acs.getSpell().getDisplayName() + " even though it is an auto known spell"); } SpellBook spellBook = getSpellBookByName(bookName); if (spellBook.getType() == SpellBook.TYPE_SPELL_BOOK) { int pagesPerSpell = si.getNumPages() / si.getTimes(); spellBook.setNumPagesUsed(spellBook.getNumPagesUsed() - pagesPerSpell); spellBook.setNumSpells(spellBook.getNumSpells() - 1); si.setNumPages(si.getNumPages() - pagesPerSpell); } si.setTimes(si.getTimes() - 1); if (si.getTimes() <= 0) { acs.removeSpellInfo(si); } // Remove the spell form the character's class instance if it // is no longer present in any book aClass.getSpellSupport().removeSpellIfUnused(acs); return ""; } /** * Calculate different kinds of bonuses to saves. possible tokens are * <ul> * <li>save</li> * <li>save.TOTAL</li> * <li>save.BASE</li> * <li>save.MISC</li> * <li>save.list</li> * <li>save.TOTAL.list</li> * <li>save.BASE.list</li> * <li>save.MISC.list</li> * </ul> * where<br /> * save := "CHECK1"|"CHECK2"|"CHECK3"<br /> * list := ((include|exclude)del)*(include|exclude)<br /> * include := "FEATS"|"MAGIC"|"RACE"<br /> * exclude := "NOFEATS"|"NOMAGIC"|"NORACE"|"NOSTAT" <br /> * del := "." <br /> * given as regular expression. <p/> "include"-s will add the appropriate * modifier "exclude"-s will subtract the appropriate modifier <p/> (This * means <tt>save.MAGIC.NOMAGIC</tt> equals 0, whereas * <tt>save.RACE.RACE</tt> equals 2 times the racial bonus) <p/> If you * use unrecognised terminals, their value will amount to 0 This means * <tt>save.BLABLA</tt> equals 0 whereas <tt>save.MAGIC.BLABLA</tt> * equals <tt>save.MAGIC</tt> <p/> <br> * author: Thomas Behr 09-03-02 * * @param saveIndex * See the appropriate gamemode file * @param saveType * "CHECK1", "CHECK2", or "CHECK3"; may not differ from * saveIndex! * @param tokenString * tokenString to parse * @return the calculated save bonus */ public int calculateSaveBonus(final int saveIndex, final String saveType, final String tokenString) { final StringTokenizer aTok = new StringTokenizer(tokenString, "."); final String[] tokens = new String[aTok.countTokens()]; final int checkIndex = SettingsHandler.getGame().getIndexOfCheck(saveType); int save = 0; for (int i = 0; aTok.hasMoreTokens(); ++i) { tokens[i] = aTok.nextToken(); if ("TOTAL".equals(tokens[i])) { save += getTotalCheck(checkIndex); } else if ("BASE".equals(tokens[i])) { save += getBaseCheck(checkIndex); } else if ("MISC".equals(tokens[i])) { save += (int) getTotalBonusTo("CHECKS", saveType); } if ("EPIC".equals(tokens[i])) { save += (int) getBonusDueToType("CHECKS", saveType, "EPIC"); } if ("MAGIC".equals(tokens[i])) { save += (int) getEquipmentBonusTo("CHECKS", saveType); } if ("RACE".equals(tokens[i])) { save += calculateSaveBonusRace(saveIndex); } if ("FEATS".equals(tokens[i])) { save += (int) getFeatBonusTo("CHECKS", saveType); } if ("STATMOD".equals(tokens[i])) { save += (int) getCheckBonusTo("CHECKS", saveType); } /** * exclude stuff */ if ("NOEPIC".equals(tokens[i])) { save -= (int) getBonusDueToType("CHECKS", saveType, "EPIC"); } if ("NOMAGIC".equals(tokens[i])) { save -= (int) getEquipmentBonusTo("CHECKS", saveType); } if ("NORACE".equals(tokens[i])) { save -= calculateSaveBonusRace(saveIndex); } if ("NOFEATS".equals(tokens[i])) { save -= (int) getFeatBonusTo("CHECKS", saveType); } if ("NOSTAT".equals(tokens[i]) || "NOSTATMOD".equals(tokens[i])) { save -= (int) getCheckBonusTo("CHECKS", saveType); } } return save; } /** * return value indicates whether or not a book was actually removed * * @param aName * @return true or false */ public boolean delSpellBook(final String aName) { if ((aName.length() > 0) && !aName.equals(Globals.getDefaultSpellBook()) && spellBooks.contains(aName)) { return delSpellBook(spellBookMap.get(aName)); } return false; } /** * return value indicates whether or not a book was actually removed * * @param book * @return true or false */ public boolean delSpellBook(final SpellBook book) { if (book != null) { String aName = book.getName(); if (!aName.equals(Globals.getDefaultSpellBook()) && spellBooks.contains(aName)) { spellBooks.remove(aName); spellBookMap.remove(aName); setDirty(true); for (PCClass pcClass : classList) { final List<CharacterSpell> aList = pcClass.getSpellSupport().getCharacterSpell(null, aName, -1); for (int j = aList.size() - 1; j >= 0; --j) { final CharacterSpell cs = aList.get(j); cs.removeSpellInfo(cs.getSpellInfoFor(aName, -1, -1)); } } return true; } } return false; } public void determinePrimaryOffWeapon() { primaryWeapons.clear(); secondaryWeapons.clear(); if (equipmentList.isEmpty()) { return; } final List<Equipment> unequippedPrimary = new ArrayList<Equipment>(); final List<Equipment> unequippedSecondary = new ArrayList<Equipment>(); for (Equipment eq : equipmentList) { if (!eq.isWeapon() || (eq.getSlots(this) < 1)) { continue; } final boolean isEquipped = eq.isEquipped(); if ((eq.getLocation() == Equipment.EQUIPPED_PRIMARY) || ((eq.getLocation() == Equipment.EQUIPPED_BOTH) && primaryWeapons .isEmpty()) || (eq.getLocation() == Equipment.EQUIPPED_TWO_HANDS)) { if (isEquipped) { primaryWeapons.add(eq); } else { unequippedPrimary.add(eq); } } else if ((eq.getLocation() == Equipment.EQUIPPED_BOTH) && !primaryWeapons.isEmpty()) { if (isEquipped) { secondaryWeapons.add(eq); } else { unequippedSecondary.add(eq); } } if (eq.getLocation() == Equipment.EQUIPPED_SECONDARY) { if (isEquipped) { secondaryWeapons.add(eq); } else { unequippedSecondary.add(eq); } } if (eq.getLocation() == Equipment.EQUIPPED_TWO_HANDS) { for (int y = 0; y < (eq.getNumberEquipped() - 1); ++y) { if (isEquipped) { secondaryWeapons.add(eq); } else { unequippedSecondary.add(eq); } } } } if (Globals.checkRule(RuleConstants.EQUIPATTACK)) { if (unequippedPrimary.size() != 0) { primaryWeapons.addAll(unequippedPrimary); } if (unequippedSecondary.size() != 0) { secondaryWeapons.addAll(unequippedSecondary); } } } public int dodgeAC() { return calcACOfType("Dodge"); } public int equipmentAC() { return calcACOfType("Equipment") + calcACOfType("Armor"); } public int flatfootedAC() { return calcACOfType("Flatfooted"); } /** * Checks for existence of source in domainSourceMap * * @param aType * @param aName * @param aLevel * @return TRUE if it has a domain source */ public boolean hasDomainSource(final String aType, final String aName, final int aLevel) { final String aKey = aType + "|" + aName + "|" + aLevel; return domainSourceMap.containsKey(aKey); } /** * Check if the character has the feat 'automatically' * * @param featName * String name of the feat to check for. * @return <code>true</code> if the character has the feat, * <code>false</code> otherwise. */ public boolean hasFeatAutomatic(final String featName) { return AbilityUtilities.getAbilityFromList(featAutoList(), Constants.FEAT_CATEGORY, featName, Ability.Nature.ANY) != null; } /** * Check if the character has the feat 'virtually' * * @param featName * String name of the feat to check for. * @return <code>true</code> if the character has the feat, * <code>false</code> otherwise. */ public boolean hasFeatVirtual(final String featName) { return AbilityUtilities.getAbilityFromList(getVirtualFeatList(), Constants.FEAT_CATEGORY, featName, Ability.Nature.ANY) != null; } /** * Does the character have this ability as an auto ability. * * @param aCategory * The ability category to check. * @param anAbility * The Ability object to check * * @return <tt>true</tt> if the character has the ability */ public boolean hasAutomaticAbility(final AbilityCategory aCategory, final Ability anAbility) { if (aCategory == AbilityCategory.FEAT) { return hasFeatAutomatic(anAbility.getKeyName()); } final List<Ability> abilities = theAbilities.get(aCategory, Ability.Nature.AUTOMATIC); if (abilities == null) { return false; } return abilities.contains(anAbility); } /** * Does the character have this ability as a virtual ability. * * @param aCategory * The ability category to check. * @param anAbility * The Ability object to check * * @return <tt>true</tt> if the character has the ability */ public boolean hasVirtualAbility(final AbilityCategory aCategory, final Ability anAbility) { if (aCategory == AbilityCategory.FEAT) { return hasFeatVirtual(anAbility.getKeyName()); } final List<Ability> abilities = theAbilities.get(aCategory, Ability.Nature.VIRTUAL); if (abilities == null) { return false; } return abilities.contains(anAbility); } public boolean hasMadeKitSelectionForAgeSet(final int index) { return ((index >= 0) && (index < 10) && ageSetKitSelections[index]); } public boolean hasSpecialAbility(final String abilityKey) { for (SpecialAbility sa : getSpecialAbilityList()) { if (sa.getKeyName().equalsIgnoreCase(abilityKey)) { return true; } } return false; } public int hitPoints() { int total = 0; String aString = Globals.getGameModeHPFormula(); if (aString.length() != 0) { for (;;) { int startIdx = aString.indexOf("$$"); if (startIdx < 0) { break; } int endIdx = aString.indexOf("$$", startIdx + 2); if (endIdx < 0) { break; } String lookupString = aString.substring(startIdx + 2, endIdx); lookupString = pcgen.io.ExportHandler.getTokenString(this, lookupString); aString = aString.substring(0, startIdx) + lookupString + aString.substring(endIdx + 2); } total = getVariableValue(aString, "").intValue(); } else { final double iConMod = getStatBonusTo("HP", "BONUS"); for (PCClass pcClass : classList) { total += pcClass.hitPoints((int) iConMod); } } total += (int) getTotalBonusTo("HP", "CURRENTMAX"); // // now we see if this PC is a Familiar final PlayerCharacter nPC = getMasterPC(); if (nPC == null) { return total; } if (getCopyMasterHP().length() == 0) { return total; } // // In order for the BONUS's to work, the PC we want // to get the hit points for must be the "current" one. // final PlayerCharacter curPC = this; Globals.setCurrentPC(nPC); int masterHP = nPC.hitPoints(); Globals.setCurrentPC(curPC); final String copyMasterHP = replaceMasterString(getCopyMasterHP(), masterHP); masterHP = getVariableValue(copyMasterHP, "").intValue(); return masterHP; } /** * Check to see if this PC should ignore Encumbrance for a specified armor * (Constants.HEAVY_LOAD, etc) If the check is more than the testing type, * return true * * @param armor * @return true or false */ public boolean ignoreEncumberedArmorMove(final Load armor) { return compareLoad(armor, ObjectKey.UNENCUMBERED_ARMOR); } /** * Check to see if this PC should ignore Encumbrance for a specified load * (Constants.HEAVY_LOAD, etc) If the check is more than the testing type, * return true * * @param load * @return true or false */ public boolean ignoreEncumberedLoadMove(final Load load) { return compareLoad(load, ObjectKey.UNENCUMBERED_LOAD); } private boolean compareLoad(final Load load, ObjectKey<Load> loadKey) { Load pcload = cache.get(loadKey); if (pcload == null) { pcload = Load.LIGHT; /* * Can't use getCDOMObjectList here due to override in Class LST file :P */ for (PObject po : getPObjectList()) { if (po != null && !(po instanceof PCClass)) { Load poLoad = po.get(loadKey); if (poLoad != null && pcload.compareTo(poLoad) < 0) { pcload = poLoad; } } } for (CDOMObject po : getConditionalTemplateObjects()) { Load poLoad = po.get(loadKey); if (poLoad != null && pcload.compareTo(poLoad) < 0) { pcload = poLoad; } } for (PCClass cl : classList) { Load active = cl.getSafe(loadKey); for (int i = 0; i < cl.getLevel(); i++) { PCClassLevel classLevel = cl.getClassLevel(i); Load override = classLevel.get(loadKey); if (override != null) { active = override; } } if (pcload.compareTo(active) < 0) { pcload = active; } } cache.put(loadKey, pcload); } return pcload.compareTo(load) >= 0; } /** * Change the number of levels a character has in a particular class. Note: * It is assumed that this method is not used as part of loading a * previously saved character. there is no way to bypass the prerequisites * with this method, also this method does not print warning messages see: * incrementClassLevel(int, PCClass, boolean, boolean); * * @param mod * the number of levels to add/remove * @param aClass * the class to adjust */ public void incrementClassLevel(final int mod, final PCClass aClass) { incrementClassLevel(mod, aClass, false); setDirty(true); } /** * TODO Why are we doing this, just add the domain * * @return index */ public int indexOfFirstEmptyCharacterDomain() { for (int i = 0; i < characterDomainList.size(); ++i) { final CharacterDomain aCD = characterDomainList.get(i); if (aCD.getDomain() == null) { return i; } } return -1; } /** * Initiative Modifier * * @return initiative modifier */ public int initiativeMod() { final int initmod = (int) getTotalBonusTo("COMBAT", "Initiative") + getVariableValue("INITCOMP", "").intValue(); return initmod; } public int languageNum(final boolean includeSpeakLanguage) { int i = (int) getStatBonusTo("LANG", "BONUS"); final Race pcRace = getRace(); final List<Skill> skillList = new ArrayList<Skill>(getSkillList()); if (i < 0) { i = 0; } if (includeSpeakLanguage) { for (Skill skill : skillList) { if (skill.getChoiceString().indexOf("Language") >= 0) { i += skill.getTotalRank(this).intValue(); } } } if (pcRace != null) { i += ((int) getTotalBonusTo("LANGUAGES", "NUMBER")); } // // Check all classes for ADD:LANGUAGE // for (PCClass pcClass : classList) { final int classLevel = pcClass.getLevel(); final List<LevelAbility> laList = pcClass.getLevelAbilityList(); if (laList != null) { for (int x = laList.size() - 1; x >= 0; --x) { final LevelAbility la = laList.get(x); if (la.isLanguage() && classLevel >= la.level()) { ++i; } } } } i += freeLangs; return i; } /** * Lists all the tokens that match prefix with associated values * * @param prefix * @return String TODO - Not sure what this is trying to do. */ public String listBonusesFor(final String prefix) { final StringBuffer buf = new StringBuffer(); final List<String> aList = new ArrayList<String>(); // final List<TypedBonus> bonuses = theBonusMap.get(prefix); // if ( bonuses == null ) // { // return Constants.EMPTY_STRING; // } // final List<String> bonusStrings = // TypedBonus.totalBonusesByType(bonuses); // return CoreUtility.commaDelimit(bonusStrings); for (String aKey : getActiveBonusMap().keySet()) { if (aKey.startsWith(prefix)) { // make a list of keys that end with .REPLACE if (aKey.endsWith(".REPLACE")) { aList.add(aKey); } else { String reason = ""; if (aKey.length() > prefix.length()) { reason = aKey.substring(prefix.length() + 1); } final int b = (int) getActiveBonusForMapKey(aKey, 0); if (b == 0) { continue; } if (!"NULL".equals(reason) && (reason.length() > 0)) { if (buf.length() > 0) { buf.append(", "); } buf.append(reason).append(' '); } buf.append(Delta.toString(b)); } } } // Now adjust the bonus if the .REPLACE value // replaces the value without .REPLACE for (String replaceKey : aList) { if (replaceKey.length() > 7) { final String aKey = replaceKey.substring(0, replaceKey.length() - 8); final double replaceBonus = getActiveBonusForMapKey(replaceKey, 0); double aBonus = getActiveBonusForMapKey(aKey, 0); aBonus += getActiveBonusForMapKey(aKey + ".STACK", 0); final int b = (int) Math.max(aBonus, replaceBonus); if (b == 0) { continue; } if (buf.length() > 0) { buf.append(", "); } final String reason = aKey.substring(prefix.length() + 1); if (!"NULL".equals(reason)) { buf.append(reason).append(' '); } buf.append(Delta.toString(b)); } } return buf.toString(); } /* */ public boolean loadDescriptionFilesInDirectory(final String aDirectory) { new File(aDirectory).list(new FilenameFilter() { public boolean accept(final File parentDir, final String fileName) { final File descriptionFile = new File(parentDir, fileName); if (PCGFile.isPCGenListFile(descriptionFile)) { BufferedReader descriptionReader = null; try { if (descriptionFile.exists()) { final char[] inputLine; // final BufferedReader descriptionReader = new // BufferedReader(new FileReader(descriptionFile)); descriptionReader = new BufferedReader(new InputStreamReader( new FileInputStream(descriptionFile), "UTF-8")); final int length = (int) descriptionFile.length(); inputLine = new char[length]; descriptionReader.read(inputLine, 0, length); setDescriptionLst(getDescriptionLst() + new String(inputLine)); } } catch (IOException exception) { Logging .errorPrint( "IOException in PlayerCharacter.loadDescriptionFilesInDirectory", exception); } finally { if (descriptionReader != null) { try { descriptionReader.close(); } catch (IOException e) { Logging .errorPrint( "Couldn't close descriptionReader in PlayerCharacter.loadDescriptionFilesInDirectory", e); // Not much to do... } } } } else if (parentDir.isDirectory()) { loadDescriptionFilesInDirectory(parentDir.getPath() + File.separator + fileName); } return false; } }); return false; } public void makeIntoExClass(final PCClass aClass) { CDOMSingleRef<PCClass> exc = aClass.get(ObjectKey.EX_CLASS); try { PCClass cl = exc.resolvesTo(); PCClass bClass = getClassKeyed(cl.getKeyName()); if (bClass == null) { bClass = cl.clone(); rebuildLists(bClass, aClass, aClass.getLevel(), this); bClass.setLevel(aClass.getLevel(), this); bClass.setHitPointMap(aClass.getHitPointMap()); final int idx = classList.indexOf(aClass); classList.set(idx, bClass); // thePObjectList.remove(aClass); // thePObjectList.add(bClass); } else { rebuildLists(bClass, aClass, aClass.getLevel(), this); bClass.setLevel(bClass.getLevel() + aClass.getLevel(), this); for (int i = 0; i < aClass.getLevel(); ++i) { bClass.setHitPoint(bClass.getLevel() + i + 1, aClass .getHitPoint(i + 1)); } classList.remove(aClass); // thePObjectList.remove(aClass); } // // change all the levelling info to the ex-class as well // for (int idx = pcLevelInfo.size() - 1; idx >= 0; --idx) { final PCLevelInfo li = pcLevelInfo.get(idx); if (li.getClassKeyName().equals(aClass.getKeyName())) { li.setClassKeyName(bClass.getKeyName()); } } // // Find all skills associated with old class and link them to new // class // for (Skill skill : getSkillList()) { skill.replaceClassRank(aClass.getKeyName(), cl.getKeyName()); } bClass.setSkillPool(aClass.getSkillPool(this)); } catch (NumberFormatException nfe) { ShowMessageDelegate.showMessageDialog(nfe.getMessage(), Constants.s_APPNAME, MessageType.INFORMATION); } } public int minXPForECL() { return PlayerCharacterUtilities.minXPForLevel(getECL(), this); } public int minXPForNextECL() { return PlayerCharacterUtilities.minXPForLevel(getECL() + 1, this); } public int miscAC() { return calcACOfType("Misc"); } /* */ public int modFromArmorOnWeaponRolls() { int bonus = 0; /* * Equipped some armor that we're not proficient in? acCheck penalty to * attack rolls */ for (Equipment eq : getEquipmentOfType("Armor", 1)) { if ((eq != null) && (!isProficientWith(eq))) { bonus += eq.acCheck(this).intValue(); } } /* * Equipped a shield that we're not proficient in? acCheck penalty to * attack rolls */ for (Equipment eq : getEquipmentOfType("Shield", 1)) { if ((eq != null) && (!isProficientWith(eq))) { bonus += eq.acCheck(this).intValue(); } } return bonus; } /** * Figure out if Load should affect AC and Skills, if so, set the load * appropriately, otherwise set a light load to eliminate the effects of * heavier loads * * @return a loadType appropriate for this Pc */ private Load getLoadType() { if (Globals.checkRule(RuleConstants.SYS_LDPACSK)) { final int loadScore = getVariableValue("LOADSCORE", "").intValue(); return Globals.loadTypeForLoadScore(loadScore, totalWeight(), this); } return Load.LIGHT; } /** * Calculate the AC bonus from equipped items. Extracted from * modToFromEquipment. * * @return PC's AC bonus from equipment */ private int modToACFromEquipment() { int bonus = 0; for (Equipment eq : equipmentList) { if (eq.isEquipped()) { bonus += eq.getACMod(this).intValue(); } } return bonus; } /** * Calculate the ACCHECK bonus from equipped items. Extracted from * modToFromEquipment. * * @return PC's ACCHECK bonus from equipment */ private int modToACCHECKFromEquipment() { Load load = getLoadType(); int bonus = 0; int penaltyForLoad = (Load.MEDIUM == load) ? -3 : (Load.HEAVY == load) ? -6 : 0; for (Equipment eq : equipmentList) { if (eq.isEquipped()) { bonus += eq.acCheck(this).intValue(); } } bonus = Math.min(bonus, penaltyForLoad); bonus += (int) getTotalBonusTo("MISC", "ACCHECK"); return bonus; } /** * Calculate the SpellFailure bonus from equipped items. Extracted from * modToFromEquipment. * * @return PC's SpellFailure bonus from equipment */ private int modToSpellFailureFromEquipment() { int bonus = 0; for (Equipment eq : equipmentList) { if (eq.isEquipped()) { bonus += eq.spellFailure(this).intValue(); } } bonus += (int) getTotalBonusTo("MISC", "SPELLFAILURE"); return bonus; } /** * Calculate the MAXDEX bonus taking account of equipped items. Extracted * from modToFromEquipment. * * @return MAXDEX bonus */ private int modToMaxDexFromEquipment() { final int statBonus = (int) getStatBonusTo("MISC", "MAXDEX"); final Load load = getLoadType(); int bonus = (load == Load.MEDIUM) ? 3 : (load == Load.HEAVY) ? 1 : (load == Load.OVERLOAD) ? 0 : statBonus; // If this is still true after all the equipment has been // examined, then we should use the Maximum - Maximum Dex modifier. boolean useMax = (load == Load.LIGHT); for (Equipment eq : equipmentList) { if (eq.isEquipped()) { final int potentialMax = eq.getMaxDex(this).intValue(); if (potentialMax != Constants.MAX_MAXDEX) { if (useMax || bonus > potentialMax) { bonus = potentialMax; } useMax = false; } } } if (useMax) { bonus = Constants.MAX_MAXDEX; } bonus += ((int) getTotalBonusTo("MISC", "MAXDEX") - statBonus); if (bonus < 0) { bonus = 0; } else if (bonus > Constants.MAX_MAXDEX) { bonus = Constants.MAX_MAXDEX; } return bonus; } /* * Figure the: MAXDEX ACCHECK SPELLFAILURE AC bonus from all currently * equipped items */ public int modToFromEquipment(final String typeName) { if (typeName.equals("AC")) { return modToACFromEquipment(); } if (typeName.equals("ACCHECK")) { return modToACCHECKFromEquipment(); } if (typeName.equals("MAXDEX")) { return modToMaxDexFromEquipment(); } if (typeName.equals("SPELLFAILURE")) { return modToSpellFailureFromEquipment(); } return 0; } /** * get the base MOVE: plus any bonuses from BONUS:MOVE additions takes into * account Armor restrictions to movement and load carried * * @param moveIdx * @return movement */ public double movement(final int moveIdx) { // get base movement double moveInFeet = getMovement(moveIdx).doubleValue(); // First get the MOVEADD bonus moveInFeet += getTotalBonusTo("MOVEADD", "TYPE." + getMovementType(moveIdx).toUpperCase()); // also check for special case of TYPE=ALL moveInFeet += getTotalBonusTo("MOVEADD", "TYPE.ALL"); double calcMove = moveInFeet; // now we apply any multipliers to the BASE move + MOVEADD move // First we get possible multipliers/divisors from the MOVE: // MOVEA: and MOVECLONE: tags if (getMovementMult(moveIdx).doubleValue() > 0) { calcMove = calcMoveMult(moveInFeet, moveIdx); } // Now we get the BONUS:MOVEMULT multipliers double moveMult = getTotalBonusTo("MOVEMULT", "TYPE." + getMovementType(moveIdx).toUpperCase()); // also check for special case of TYPE=ALL moveMult += getTotalBonusTo("MOVEMULT", "TYPE.ALL"); if (moveMult > 0) { calcMove = (int) (calcMove * moveMult); } double postMove = calcMove; // now add on any POSTMOVE bonuses postMove += getTotalBonusTo("POSTMOVEADD", "TYPE." + getMovementType(moveIdx).toUpperCase()); // also check for special case of TYPE=ALL postMove += getTotalBonusTo("POSTMOVEADD", "TYPE.ALL"); // because POSTMOVE is magical movement which should not be // multiplied by magical items, etc, we now see which is larger, // (baseMove + postMove) or (baseMove * moveMultiplier) // and keep the larger one, discarding the other moveInFeet = Math.max(calcMove, postMove); // get a list of all equipped Armor Load armorLoad = Load.LIGHT; for (Equipment armor : getEquipmentOfType("Armor", 1)) { if (armor.isShield()) { continue; } if (armor.isHeavy() && !ignoreEncumberedArmorMove(Load.HEAVY)) { armorLoad = armorLoad.max(Load.HEAVY); } else if (armor.isMedium() && !ignoreEncumberedArmorMove(Load.MEDIUM)) { armorLoad = armorLoad.max(Load.MEDIUM); } } final double armorMove = Globals.calcEncumberedMove(armorLoad, moveInFeet, true, null); final Load pcLoad = Globals.loadTypeForLoadScore(getVariableValue("LOADSCORE", "") .intValue(), totalWeight(), this); final double loadMove = Globals.calcEncumberedMove(pcLoad, moveInFeet, true, this); // It is possible to have a PC that is not encumbered by Armor // But is encumbered by Weight carried (and visa-versa) // So do two calcs and take the slowest moveInFeet = Math.min(armorMove, loadMove); return moveInFeet; } public double multiclassXPMultiplier() { final HashSet<PCClass> unfavoredClasses = new HashSet<PCClass>(); final SortedSet<PCClass> aList = getFavoredClasses(); boolean hasAny = hasAnyFavoredClass(); PCClass maxClass = null; PCClass secondClass = null; int maxClassLevel = 0; int secondClassLevel = 0; int xpPenalty = 0; double xpMultiplier = 1.0; for (PCClass pcClass : classList) { if (!pcClass.hasXPPenalty()) { continue; } String subClassKey = pcClass.getSubClassKey(); PCClass evalClass = pcClass; if (!subClassKey.equals("None")) { evalClass = pcClass.getSubClassKeyed(subClassKey); } if (!aList.contains(evalClass)) { unfavoredClasses.add(pcClass); if (pcClass.getLevel() > maxClassLevel) { if (hasAny) { secondClassLevel = maxClassLevel; secondClass = maxClass; } maxClassLevel = pcClass.getLevel(); maxClass = pcClass; } else if ((pcClass.getLevel() > secondClassLevel) && (hasAny)) { secondClassLevel = pcClass.getLevel(); secondClass = pcClass; } } } if ((hasAny) && (secondClassLevel > 0)) { maxClassLevel = secondClassLevel; unfavoredClasses.remove(maxClass); maxClass = secondClass; } if (maxClassLevel > 0) { unfavoredClasses.remove(maxClass); for (PCClass aClass : unfavoredClasses) { if ((maxClassLevel - (aClass.getLevel())) > 1) { ++xpPenalty; } } xpMultiplier = 1.0 - (xpPenalty * 0.2); if (xpMultiplier < 0) { xpMultiplier = 0; } } return xpMultiplier; } public boolean hasAnyFavoredClass() { if (getRace().getSafe(ObjectKey.ANY_FAVORED_CLASS)) { return true; } for (PCTemplate template : templateList) { if (template.getSafe(ObjectKey.ANY_FAVORED_CLASS)) { return true; } } return false; } public int naturalAC() { return calcACOfType("NaturalArmor"); } /** * Takes a String and a Class name and computes spell based variable such as * Class level * * @param aSpell * @param aString * @param anObj * @return String */ public String parseSpellString(final Spell aSpell, String aString, final PObject anObj) { String aSpellClass = null; if (anObj instanceof Domain) { final CharacterDomain aCD = getCharacterDomainForDomain(anObj.getKeyName()); if ((aCD != null) && aCD.isFromPCClass()) { aSpellClass = "CLASS:" + getClassKeyed(aCD.getObjectName()); } } else if (anObj instanceof PCClass) { aSpellClass = "CLASS:" + anObj.getKeyName(); } else if (anObj instanceof Race) // could be innate spell for race { aSpellClass = "RACE:" + anObj.getKeyName(); } if (aSpellClass == null) { return aString; } // Only want to replace items between ()'s while (aString.lastIndexOf('(') >= 0) { boolean found = false; final int start = aString.indexOf('('); int end = 0; int level = 0; for (int i = start; i < aString.length(); i++) { if (aString.charAt(i) == '(') { level++; } else if (aString.charAt(i) == ')') { level--; if (level == 0) { end = i; break; } } } /* * int x = CoreUtility.innerMostStringStart(aString); int y = * CoreUtility.innerMostStringEnd(aString); // bounds checking if * ((start > end) || (start >= aString.length())) { break; } if * ((end <= 0) || (end >= aString.length())) { break; } */ final String inCalc = aString.substring(start + 1, end); String replacement = "0"; final Float fVal = getVariableValue(aSpell, inCalc, aSpellClass); if (!CoreUtility.doublesEqual(fVal.floatValue(), 0.0f)) { found = true; replacement = fVal.intValue() + ""; } else if ((inCalc.indexOf("MIN") >= 0) || (inCalc.indexOf("MAX") >= 0)) { found = true; replacement = fVal.intValue() + ""; } else if (inCalc.toUpperCase().indexOf("MIN(") >= 0 || inCalc.toUpperCase().indexOf("MAX(") >= 0) { found = true; replacement = fVal.intValue() + ""; } if (found) { aString = aString.substring(0, start) + replacement + aString.substring(end + 1); } else { aString = aString.substring(0, start) + "[" + inCalc + "]" + aString.substring(end + 1); } } return aString; } /** * Populate the characters skills list with skill that the character does * not have ranks in according to the required level. The levels are defined * in constants in the Skill class, but are None, Untrained or All. * * @param level * The level of extra skills to be added. */ public void populateSkills(final int level) { Globals.sortPObjectListByName(getSkillList()); removeExcessSkills(level); addNewSkills(level); // Now regenerate the output order final int sort; final boolean sortOrder; switch (getSkillsOutputOrder()) { case GuiConstants.INFOSKILLS_OUTPUT_BY_NAME_ASC: sort = SkillComparator.RESORT_NAME; sortOrder = SkillComparator.RESORT_ASCENDING; break; case GuiConstants.INFOSKILLS_OUTPUT_BY_NAME_DSC: sort = SkillComparator.RESORT_NAME; sortOrder = SkillComparator.RESORT_DESCENDING; break; case GuiConstants.INFOSKILLS_OUTPUT_BY_TRAINED_ASC: sort = SkillComparator.RESORT_TRAINED; sortOrder = SkillComparator.RESORT_ASCENDING; break; case GuiConstants.INFOSKILLS_OUTPUT_BY_TRAINED_DSC: sort = SkillComparator.RESORT_TRAINED; sortOrder = SkillComparator.RESORT_DESCENDING; break; default: // Manual sort, or unrecognised, so do no sorting. return; } final List<Skill> localSkillList = getSkillList(); final SkillComparator comparator = new SkillComparator(sort, sortOrder); int nextOutputIndex = 1; Collections.sort(localSkillList, comparator); for (Skill skill : localSkillList) { if (skill.getOutputIndex() >= 0) { skill.setOutputIndex(nextOutputIndex++); } } } /** * Removes a CharacterDomain * * @param aCD */ public void removeCharacterDomain(final CharacterDomain aCD) { if (!characterDomainList.isEmpty()) { characterDomainList.remove(aCD); // thePObjectList.remove(aCD); setDirty(true); } } public void removeCharacterDomain(final String aDomainKey) { final CharacterDomain cd = getCharacterDomainForDomain(aDomainKey); characterDomainList.remove(cd); // thePObjectList.remove(cd); } public void removeNaturalWeapons(final PObject obj) { for (Equipment weapon : obj.getNaturalWeapons()) { // Need to make sure weapons are removed from // equip sets as well, or they will get added back // to the character. sage_sam 20 March 2003 removeEquipment(weapon); delEquipSetItem(weapon); setDirty(true); } } /** * Removes a "temporary" bonus * * @param aBonus */ public void removeTempBonus(final BonusObj aBonus) { getTempBonusList().remove(aBonus); setDirty(true); } public void removeTempBonusItemList(final Equipment aEq) { getTempBonusItemList().remove(aEq); setDirty(true); } public void removeTemplate(final PCTemplate inTmpl) { if (inTmpl == null) { return; } cachedWeaponProfs = null; languages.removeAll(inTmpl.getSafeListFor(ListKey.AUTO_LANGUAGES)); // remove // template // languages. templateAutoLanguages.removeAll(inTmpl .getSafeListFor(ListKey.AUTO_LANGUAGES)); // remove them from the // local listing. Don't // clear though in case // of multiple // templates. Collection<CDOMReference<Language>> langCollection = inTmpl .getListMods(Language.STARTING_LIST); if (langCollection != null) { for (CDOMReference<Language> ref : langCollection) { templateLanguages.removeAll(ref.getContainedObjects()); } } removeNaturalWeapons(inTmpl); PCTemplate t = this.getTemplateKeyed(inTmpl.getKeyName()); List<LevelCommandFactory> lcfList = t.getSafeListFor(ListKey.ADD_LEVEL); for (ListIterator<LevelCommandFactory> it = lcfList .listIterator(lcfList.size()); it.hasPrevious();) { it.previous().remove(this); } removeTemplatesFrom(inTmpl); for (PCTemplate template : templateList) { if (template.getKeyName().equals(inTmpl.getKeyName())) { templateList.remove(template); break; } } if (!PlayerCharacterUtilities.canReassignTemplateFeats()) { // TODO - ABILITYOBJECT // setAutomaticAbilitiesStable( null, false ); setAutomaticFeatsStable(false); } // karianna 1184888 adjustMoveRates(); // re-evaluate non-spellcaster spell lists getSpellList(); calcActiveBonuses(); setDirty(true); } public String replaceMasterString(String aString, final int aNum) { while (true) { final int x = aString.indexOf("MASTER"); if (x == -1) { break; } final String leftString = aString.substring(0, x); final String rightString = aString.substring(x + 6); aString = leftString + Integer.toString(aNum) + rightString; } return aString; } public PCLevelInfo saveLevelInfo(final String classKeyName) { final PCLevelInfo li = new PCLevelInfo(this, classKeyName); pcLevelInfo.add(li); return li; } public void saveStatIncrease(final String statAbb, final int mod, final boolean isPreMod) { final int idx = getLevelInfoSize() - 1; if (idx >= 0) { pcLevelInfo.get(idx).addModifiedStat(statAbb, mod, isPreMod); } setDirty(true); } public int getStatIncrease(final String statAbb, final boolean includePost) { final int idx = getLevelInfoSize() - 1; if (idx >= 0) { return pcLevelInfo.get(idx).getTotalStatMod(statAbb, includePost); } return 0; } public int sizeAC() { return calcACOfType("Size"); } public int sizeInt() { int iSize = racialSizeInt(); if (race != null) { // Now check and see if a class has modified // the size of the character with something like: // BONUS:SIZEMOD|NUMBER|+1 iSize += (int) getTotalBonusTo("SIZEMOD", "NUMBER"); // Now see if there is a HD advancement in size // (Such as for Dragons) for (int i = 0; i < race.sizesAdvanced(totalHitDice()); ++i) { ++iSize; } // // Must still be between 0 and 8 // if (iSize < 0) { iSize = 0; } if (iSize >= SettingsHandler.getGame().getSizeAdjustmentListSize()) { iSize = SettingsHandler.getGame().getSizeAdjustmentListSize() - 1; } } return iSize; } public int totalHitDice() { return totalMonsterLevels(); } public int totalNonMonsterLevels() { int totalLevels = 0; for (PCClass pcClass : classList) { if (!pcClass.isMonster()) { totalLevels += pcClass.getLevel(); } } return totalLevels; } public BigDecimal totalValue() { BigDecimal totalValue = BigDecimal.ZERO; for (Equipment eq : getEquipmentMasterList()) { totalValue = totalValue.add(eq.getCost(this).multiply( new BigDecimal(eq.qty()))); } return totalValue; } public Float totalWeight() { float totalWeight = 0; final Float floatZero = Float.valueOf(0); boolean firstClothing = true; if (equipmentList.isEmpty()) { return floatZero; } for (Equipment eq : equipmentList) { // Loop through the list of top if ((eq.getCarried().compareTo(floatZero) > 0) && (eq.getParent() == null)) { if (eq.getChildCount() > 0) { totalWeight += (eq.getWeightAsDouble(this) + eq .getContainedWeight(this).floatValue()); } else { if (firstClothing && eq.isEquipped() && eq.isType("CLOTHING")) { // The first equipped set of clothing should have a // weight of 0. Feature #437410 firstClothing = false; totalWeight += (eq.getWeightAsDouble(this) * Math.max(eq .getCarried().floatValue() - 1, 0)); } else { totalWeight += (eq.getWeightAsDouble(this) * eq.getCarried() .floatValue()); } } } } return Float.valueOf(totalWeight); } public int touchAC() { return calcACOfType("Touch"); } /** * replaces oldItem with newItem in all EquipSets * * @param oldItem * @param newItem */ public void updateEquipSetItem(final Equipment oldItem, final Equipment newItem) { if (equipSetList.isEmpty()) { return; } final List<EquipSet> tmpList = new ArrayList<EquipSet>(); // find all oldItem EquipSet's for (EquipSet es : equipSetList) { final Equipment eqI = es.getItem(); if ((eqI != null) && oldItem.equals(eqI)) { tmpList.add(es); } } for (EquipSet es : tmpList) { es.setValue(newItem.getName()); es.setItem(newItem); } setDirty(true); } /** * Gets whether the character has been changed since last saved. * * @return true or false */ public boolean wasEverSaved() { return !Constants.EMPTY_STRING.equals(getFileName()); } /** * Figures out if a bonus should stack based on type, then adds it to the * supplied map. * * @param bonus * The value of the bonus. * @param bonusType * The type of the bonus e.g. STAT.DEX:LUCK * @param bonusMap * The bonus map being built up. */ void setActiveBonusStack(double bonus, String bonusType, Map<String, String> bonusMap) { if (bonusType != null) { bonusType = bonusType.toUpperCase(); // only specific bonuses can actually be fractional // -> TODO should define this in external file if (!bonusType.startsWith("ITEMWEIGHT") && !bonusType.startsWith("ITEMCOST") && !bonusType.startsWith("ACVALUE") && !bonusType.startsWith("ITEMCAPACITY") && !bonusType.startsWith("LOADMULT") && !bonusType.startsWith("FEAT") && (bonusType.indexOf("DAMAGEMULT") < 0)) { bonus = ((int) bonus); // TODO: never used } } else { return; } // default to non-stacking bonuses int index = -1; // bonusType is either of form: // COMBAT.AC // or // COMBAT.AC:Luck // or // COMBAT.AC:Armor.REPLACE // final StringTokenizer aTok = new StringTokenizer(bonusType, ":"); if (aTok.countTokens() == 2) { // need 2nd token to see if it should stack final String aString; aTok.nextToken(); aString = aTok.nextToken(); if (aString != null) { index = SettingsHandler.getGame() .getUnmodifiableBonusStackList().indexOf(aString); // e.g. // Dodge } } else { // un-named (or un-TYPE'd) bonuses stack index = 1; } // .STACK means stack with everything // .REPLACE means stack with other .REPLACE if (bonusType.endsWith(".STACK") || bonusType.endsWith(".REPLACE")) { index = 1; } // If it's a negative bonus, it always needs to be added if (bonus < 0) { index = 1; } if (index == -1) // a non-stacking bonus { final String aVal = bonusMap.get(bonusType); if (aVal == null) { putActiveBonusMap(bonusType, String.valueOf(bonus), bonusMap); } else { putActiveBonusMap(bonusType, String.valueOf(Math.max(bonus, Float.parseFloat(aVal))), bonusMap); } } else // a stacking bonus { final String aVal = bonusMap.get(bonusType); if (aVal == null) { putActiveBonusMap(bonusType, String.valueOf(bonus), bonusMap); } else { putActiveBonusMap(bonusType, String.valueOf(bonus + Float.parseFloat(aVal)), bonusMap); } } } /** * Returns a list of Ability Objects of the given Category from the global * list, which 1) match the given abilityType, 2) the character qualifies * for, and 3) the character does not already have. * * @param category * of ability to return * @param abilityType * String type of ability to return. * @param autoQualify * assume PC qualifies for feat. Used for virtual feats * * @return List of Ability Objects. */ public List<Ability> getAvailableAbilities(final String category, final String abilityType, final boolean autoQualify) { final List<Ability> anAbilityList = new ArrayList<Ability>(); final Iterator<? extends Categorisable> it = Globals.getAbilityKeyIterator(category); while (it.hasNext()) { final Ability anAbility = (Ability) it.next(); if (anAbility.matchesType(abilityType) && canSelectAbility(anAbility, autoQualify)) { anAbilityList.add(anAbility); } } return anAbilityList; } /** * Returns the list of names of available feats of given type. That is, all * feats from the global list, which match the given featType, the character * qualifies for, and the character does not already have. * * @param featType * String category of feat to list. * @return List of Feats. */ public List<String> getAvailableFeatNames(final String featType) { return (getAvailableFeatNames(featType, false)); } /** * Returns the list of names of available feats of given type. That is, all * feats from the global list, which match the given featType, the character * qualifies for, and the character does not already have. * * @param featType * String category of feat to list. * @param autoQualify * assume PC qualifies for feat. Used for virtual feats * @return List of Feats. */ public List<String> getAvailableFeatNames(final String featType, final boolean autoQualify) { final List<String> anAbilityList = new ArrayList<String>(); final Iterator<? extends Categorisable> it = Globals.getAbilityKeyIterator("FEAT"); for (; it.hasNext();) { final Ability anAbility = (Ability) it.next(); if (anAbility.matchesType(featType) && canSelectAbility(anAbility, autoQualify)) { anAbilityList.add(anAbility.getKeyName()); } } return anAbilityList; } /** * @return true if character is not currently being read from file. */ public boolean isNotImporting() { return !importing; } /** * @return true if character is currently being read from file. */ public boolean isImporting() { return importing; } /** * @param moveIdx * @return the integer movement speed multiplier for Index */ Double getMovementMult(final int moveIdx) { if ((getMovements() != null) && (moveIdx < movementMult.length)) { return movementMult[moveIdx]; } return Double.valueOf(0); } void addVariable(final String variableString) { variableList.add(variableString); setDirty(true); } void giveClassesAway(final PCClass toClass, final PCClass fromClass, int iCount) { if ((toClass == null) || (fromClass == null)) { return; } // Will take destination class over maximum? if (toClass.hasMaxLevel() && (toClass.getLevel() + iCount) > toClass.getSafe(IntegerKey.LEVEL_LIMIT)) { iCount = toClass.getSafe(IntegerKey.LEVEL_LIMIT) - toClass.getLevel(); } // Enough levels to move? if ((fromClass.getLevel() <= iCount) || (iCount < 1)) { return; } final int iFromLevel = fromClass.getLevel() - iCount; final int iToLevel = toClass.getLevel(); toClass.setLevel(iToLevel + iCount, this); for (int i = 0; i < iCount; ++i) { toClass.setHitPoint(iToLevel + i, fromClass.getHitPoint(iFromLevel + i)); fromClass.setHitPoint(iFromLevel + i, Integer.valueOf(0)); } rebuildLists(toClass, fromClass, iCount, this); fromClass.setLevel(iFromLevel, this); // first, change the toClass current PCLevelInfo level for (PCLevelInfo pcl : pcLevelInfo) { if (pcl.getClassKeyName().equals(toClass.getKeyName())) { final int iTo = (pcl.getLevel() + toClass.getLevel()) - iToLevel; pcl.setLevel(iTo); } } // change old class PCLevelInfo to the new class for (PCLevelInfo pcl : pcLevelInfo) { if (pcl.getClassKeyName().equals(fromClass.getKeyName()) && (pcl.getLevel() > iFromLevel)) { final int iFrom = pcl.getLevel() - iFromLevel; pcl.setClassKeyName(toClass.getKeyName()); pcl.setLevel(iFrom); } } /* * // get skills associated with old class and link to new class for * (Iterator e = getSkillList().iterator(); e.hasNext();) { Skill aSkill = * (Skill) e.next(); aSkill.replaceClassRank(fromClass.getName(), * toClass.getName()); } toClass.setSkillPool(fromClass.getSkillPool()); */ } // boolean qualifiesForFeat(final String featName) // { // final Ability anAbility = Globals.getAbilityNamed("FEAT", featName); // // if (anAbility != null) // { // return qualifiesForFeat(anAbility); // } // // return false; // } /** * Returns true if this PlayerCharacter contains a Domain with a key that * matches the given Domain */ public boolean containsCharacterDomain(String aDomainKey) { for (CharacterDomain cd : characterDomainList) { Domain d = cd.getDomain(); if (d.getKeyName().equalsIgnoreCase(aDomainKey)) { return true; } } return false; } /** * Returns true if this PlayerCharacter contains a Domain with a key that * matches the given Domain * * @param aClassKey The key of the class granting the domain. * @param aDomainKey The key of the domain. * @return true if the doain is present. */ public boolean containsCharacterDomain(String aClassKey, String aDomainKey) { for (CharacterDomain cd : characterDomainList) { if (cd.isFromPCClass(aClassKey)) { Domain d = cd.getDomain(); if (d.getKeyName().equalsIgnoreCase(aDomainKey)) { return true; } } } return false; } /** * return the index of CharacterDomain matching domainName else return -1 * * @param domainKey * @return character domain index * @deprecated 10/21/06 thpr as part of PCClass rebuilding */ @Deprecated public int getCharacterDomainIndex(final String domainKey) { for (int i = 0; i < characterDomainList.size(); ++i) { final CharacterDomain aCD = characterDomainList.get(i); final Domain aDomain = aCD.getDomain(); if ((aDomain != null) && aDomain.getKeyName().equalsIgnoreCase(domainKey)) { return i; } } return -1; } void addFreeLanguage(final Language aLang) { this.languages.add(aLang); ++freeLangs; setDirty(true); } void removeVariable(final String variableString) { for (Iterator<String> e = variableList.iterator(); e.hasNext();) { final String aString = e.next(); if (aString.startsWith(variableString)) { e.remove(); setDirty(true); } } } /** * Scan through the list of domains the character has to ensure that they * are all still valid. Any invalid domains will be removed from the * character. */ void validateCharacterDomains() { if (!isImporting()) { getSpellList(); } for (CharacterDomain cd : characterDomainList) { if (!cd.isDomainValidFor(this)) { removeCharacterDomain(cd); } } } /** * Searches the activeBonus HashMap for aKey * * @param aKey * @param defaultValue * * @return defaultValue if aKey not found */ private double getActiveBonusForMapKey(String aKey, final double defaultValue) { aKey = aKey.toUpperCase(); final String regVal = getActiveBonusMap().get(aKey); if (regVal != null) { return Double.parseDouble(regVal); } return defaultValue; } /** * Active BonusObj's * * @return List */ public List<BonusObj> getActiveBonusList() { return activeBonusList; } private List<String> getAutoArmorProfList() { final ArrayList<String> aList = new ArrayList<String>(); // Try all possible PObjects for (PObject pObj : getPObjectList()) { if (pObj != null) { pObj.addAutoTagsToList("ARMORPROF", aList, this, true); } } return aList; } /** * Calculates total bonus from Checks * * @param aType * @param aName * @return check bonus to */ private double getCheckBonusTo(String aType, String aName) { double bonus = 0; aType = aType.toUpperCase(); aName = aName.toUpperCase(); final List<PObject> aList = SettingsHandler.getGame().getUnmodifiableCheckList(); for (PObject obj : aList) { final List<BonusObj> tempList = obj.getBonusListOfType(aType, aName); if (!tempList.isEmpty()) { bonus += calcBonusFromList(tempList); } } return bonus; } private synchronized void setClassLevelsBrazenlyTo( final Map<String, String> lvlMap) { // set class levels to class name,level pair for (PCClass pcClass : classList) { String lvl = lvlMap.get(pcClass.getKeyName()); if (lvl == null) { lvl = "0"; } pcClass.setLevelWithoutConsequence(Integer.parseInt(lvl)); } // Recalculate bonuses, based on new level calcActiveBonuses(); // setDirty(true); } private String getDisplayClassName() { return (classList.isEmpty() ? "Nobody" : classList.get( classList.size() - 1).getDisplayClassName()); } private String getDisplayRaceName() { final String raceName = getRace().toString(); return (raceName.equals(Constants.s_NONESELECTED) ? "Nothing" : raceName); } // /** // * Get AUTO weapon proficiencies from all granting objects // * @param aFeatList // * @return Sorted Set // */ // SortedSet<String> getAutoWeaponProfs(final List<Ability> aFeatList) // { // SortedSet<String> results = new TreeSet<String>(); // final Race aRace = getRace(); // // ListKey<String> weaponProfBonusKey = ListKey.SELECTED_WEAPON_PROF_BONUS; // // // // // Add race-grantedweapon proficiencies // // // if (aRace != null) // { // results = addWeaponProfsLists(aRace.getWeaponProfAutos(), results, // aFeatList, true); // // for (String aString : aRace.getSafeListFor(weaponProfBonusKey)) // { // results.add(aString); // addWeaponProfToList(aFeatList, aString, true); // } // // aRace.addAutoTagsToList("WEAPONPROF", (TreeSet) results, this, true); // } // // // // // Add template-granted weapon proficiencies // // // for ( PCTemplate template : getTemplateList() ) // { // results = addWeaponProfsLists(template.getWeaponProfAutos(), results, // aFeatList, true); // // for (String aString : template.getSafeListFor(weaponProfBonusKey)) // { // results.add(aString); // addWeaponProfToList(aFeatList, aString, true); // } // // template.addAutoTagsToList("WEAPONPROF", (TreeSet) results, this, true); // } // // // // // Add class-granted weapon proficiencies // // // for ( PCClass pcClass : classList ) // { // results = addWeaponProfsLists(pcClass.getWeaponProfAutos(), results, // aFeatList, true); // // for (String aString : pcClass.getSafeListFor(weaponProfBonusKey)) // { // results.add(aString); // addWeaponProfToList(aFeatList, aString, true); // } // // pcClass.addAutoTagsToList("WEAPONPROF", (TreeSet) results, this, true); // } // // // // // Add feat-granted weapon proficiencies // // // setAggregateFeatsStable(false); // // TODO - ABILITYOBJECT // // setAggregateAbilitiesStable(null, false); // // for ( Ability feat : aggregateFeatList() ) // { // results = addWeaponProfsLists(feat.getWeaponProfAutos(), results, // aFeatList, true); // // List<String> staticProfList = new ArrayList<String>(); // staticProfList.addAll(feat.getSafeListFor(weaponProfBonusKey)); // for (String aString : staticProfList) // { // results.add(aString); // addWeaponProfToList(aFeatList, aString, true); // } // // feat.addAutoTagsToList("WEAPONPROF", (TreeSet) results, this, true); // } // // // // // Add skill-granted weapon proficiencies // // // for ( Skill skill : getSkillList() ) // { // results = addWeaponProfsLists(skill.getWeaponProfAutos(), results, // aFeatList, true); // // TODO Should skills grant BONUS profs? // skill.addAutoTagsToList("WEAPONPROF", (TreeSet) results, this, true); // } // // // // // Add equipment-granted weapon proficiencies // // // for ( Equipment eq : equipmentList ) // { // if (eq.isEquipped()) // { // results = addWeaponProfsLists(eq.getWeaponProfAutos(), results, // aFeatList, true); // eq.addAutoTagsToList("WEAPONPROF", (TreeSet) results, this, true); // // // TODO Should eqMods add to Auto list or BONUS profs? // for ( EquipmentModifier eqMod : eq.getEqModifierList(true) ) // { // results = addWeaponProfsLists(eqMod.getWeaponProfAutos(), results, // aFeatList, true); // } // // for ( EquipmentModifier eqMod : eq.getEqModifierList(false) ) // { // results = addWeaponProfsLists(eqMod.getWeaponProfAutos(), results, // aFeatList, true); // } // } // } // // // // // Add deity-granted weapon proficiencies // // // if (deity != null) // { // results = addWeaponProfsLists(deity.getWeaponProfAutos(), results, // aFeatList, true); // deity.addAutoTagsToList("WEAPONPROF", (TreeSet) results, this, true); // // TODO Should deity add BONUS profs // } // // // // // Add domain-granted weapon proficiencies // // // for ( CharacterDomain cd : characterDomainList ) // { // final Domain aDomain = cd.getDomain(); // // if (aDomain != null) // { // results = addWeaponProfsLists(aDomain.getWeaponProfAutos(), results, // aFeatList, true); // // for (String aString : aDomain.getSafeListFor(weaponProfBonusKey)) // { // results.add(aString); // addWeaponProfToList(aFeatList, aString, true); // } // // aDomain.addAutoTagsToList("WEAPONPROF", (TreeSet) results, this, true); // } // } // // // // // Parse though aggregate feat list, looking for any feats that grant // weapon proficiencies // // // //addFeatProfs(getStableAggregateFeatList(), aFeatList, results); // //addFeatProfs(getStableAutomaticFeatList(), aFeatList, results); // // // Why do we clear the list and then add it all again? // weaponProfList.clear(); // TheForken // for ( String profKey : results ) // { // final WeaponProf wp = Globals.getWeaponProfKeyed(profKey); // if (wp != null) // { // weaponProfList.add(wp); // } // } // // return results; // } /** * Parses through all Equipment items and calculates total Bonus * * @param aType * @param aName * @return equipment bonus to */ public double getEquipmentBonusTo(String aType, String aName) { double bonus = 0; if (equipmentList.isEmpty()) { return bonus; } aType = aType.toUpperCase(); aName = aName.toUpperCase(); for (Equipment eq : equipmentList) { if (eq.isEquipped()) { final List<BonusObj> tempList = eq.getBonusListOfType(aType, aName, true); if (eq.isWeapon() && eq.isDouble()) { tempList.addAll(eq.getBonusListOfType(aType, aName, false)); } bonus += calcBonusFromList(tempList); } } return bonus; } private String getFullDisplayClassName() { if (classList.isEmpty()) { return "Nobody"; } final StringBuffer buf = new StringBuffer(); boolean first = true; for (PCClass c : classList) { if (!first) { buf.append('/'); first = false; } buf.append(c.getFullDisplayClassName()); } return buf.toString(); } /** * Return a hashmap of the first maxCharacterLevel character levels that a * character has taken This will be a hash of "Class name"=>"number of * levels as a string". For example, {"Fighter"=>"2", "Cleric":"16"} * * @param maxCharacterLevel * the maximum character level that we can include in this map * @return character level map */ private Map<String, String> getCharacterLevelHashMap( final int maxCharacterLevel) { final Map<String, String> lvlMap = new HashMap<String, String>(); int characterLevels = 0; for (int i = 0; i < getLevelInfoSize(); ++i) { final String classKeyName = getLevelInfoClassKeyName(i); final PCClass aClass = Globals.getContext().ref.silentlyGetConstructedCDOMObject(PCClass.class, classKeyName); if (aClass.isMonster() || characterLevels < maxCharacterLevel) { // we can use this class level if it is a monster level, or if // we have not yet hit our maximum number of characterLevels String val = lvlMap.get(classKeyName); if (val == null) { val = "0"; } val = String.valueOf(Integer.parseInt(val) + 1); lvlMap.put(classKeyName, val); } if (!aClass.isMonster()) { // If the class level was not a monster level then it counts // towards the total number of character levels characterLevels++; } } return lvlMap; } private void setMoveFromList(final List<? extends CDOMObject> aList) { for (CDOMObject pObj : aList) { List<Movement> ml = pObj.getListFor(ListKey.MOVEMENT); if (ml == null || ml.isEmpty()) { continue; } for (Movement movement : ml) { if (movement == null || movement.getNumberOfMovements() < 1) { continue; } for (int i = 0; i < movement.getNumberOfMovements(); i++) { setMyMoveRates(movement.getMovementType(i), movement .getMovement(i).doubleValue(), movement .getMovementMult(i), movement.getMovementMultOp(i), movement.getMoveRatesFlag()); } } } // setDirty(true); } /** * an array of movement speeds * * @return array of Integer movement speeds */ public Double[] getMovements() { return movements; } /** * sets up the movement arrays creates them if they do not exist * * @param moveType * @param anDouble * @param moveMult * @param multOp * @param moveFlag */ private void setMyMoveRates(final String moveType, final double anDouble, final Double moveMult, final String multOp, final int moveFlag) { // // NOTE: can not use getMovements() accessor as it calls // this function, so use the variable: movements // Double moveRate; // The ALL type can only be applied to existing movement // so just loop and add or set as appropriate if ("ALL".equals(moveType)) { if (moveFlag == 0) { // set all types of movement to moveRate for (int i = 0; i < movements.length; i++) { moveRate = new Double(anDouble); movements[i] = moveRate; } } else { // add moveRate to all types of movement. for (int i = 0; i < movements.length; i++) { moveRate = new Double(anDouble + movements[i].doubleValue()); movements[i] = moveRate; } } } else { if (moveFlag == 0) { // set movement to moveRate moveRate = new Double(anDouble); for (int i = 0; i < movements.length; i++) { if (moveType.equals(movementTypes[i])) { movements[i] = moveRate; movementMult[i] = moveMult; movementMultOp[i] = multOp; return; } } increaseMoveArray(moveRate, moveType, moveMult, multOp); } else { // get base movement, then add moveRate moveRate = new Double(anDouble + movements[0].doubleValue()); // for existing types of movement: for (int i = 0; i < movements.length; i++) { if (moveType.equals(movementTypes[i])) { movements[i] = moveRate; movementMult[i] = moveMult; movementMultOp[i] = multOp; return; } } increaseMoveArray(moveRate, moveType, moveMult, multOp); } } setDirty(true); } public int getNumAttacks() { return Math.min(Math.max(baseAttackBonus() / 5, 4), 1); } private String getOrdinal(final int cardinal) { switch (cardinal) { case 1: return "st"; case 2: return "nd"; case 3: return "rd"; default: return "th"; } } /** * Returns a bonus. * * @param aList * @param aType * @param aName * @return double */ private double getPObjectWithCostBonusTo( final List<? extends PObject> aList, final String aType, final String aName) { double iBonus = 0; if (aList.isEmpty()) { return iBonus; } for (PObject anObj : aList) { final List<BonusObj> tempList = anObj.getBonusListOfType(aType, aName); iBonus += calcBonusWithCostFromList(tempList); } return iBonus; } private boolean isProficientWithShield(final Equipment eq, final List<String> aList) { // First, check to see if fits into any TYPE granted for (int i = 0; i < aList.size(); ++i) { final String aString = aList.get(i); StringTokenizer tok; if (aString.startsWith("SHIELDTYPE=") || aString.startsWith("SHIELDTYPE.")) { tok = new StringTokenizer(aString.substring(11), "."); } else { // All TYPE profs are at the beginning of the list break; } int matches = 0; final int minMatches = tok.countTokens(); while (tok.hasMoreTokens()) { final String aType = tok.nextToken(); if (eq.isType(aType)) { matches++; } } // We have to match all the tokens. if (matches == minMatches) { return true; } } return aList.contains(eq.getShieldProf().getKeyName()); } private boolean isProficientWithArmor(final Equipment eq, final List<String> aList) { // First, check to see if fits into any TYPE granted for (int i = 0; i < aList.size(); ++i) { final String aString = aList.get(i); StringTokenizer tok; if (aString.startsWith("ARMORTYPE=") || aString.startsWith("ARMORTYPE.")) { tok = new StringTokenizer(aString.substring(10), "."); } else { // All TYPE profs are at the beginning of the list break; } int matches = 0; final int minMatches = tok.countTokens(); while (tok.hasMoreTokens()) { final String aType = tok.nextToken(); if (eq.isType(aType)) { matches++; } } // We have to match all the tokens. if (matches == minMatches) { return true; } } return aList.contains(eq.getArmorProf().getKeyName()); } private boolean isProficientWithWeapon(final Equipment eq) { if (eq.isNatural()) { return true; } CDOMSingleRef<WeaponProf> ref = eq.get(ObjectKey.WEAPON_PROF); if (ref == null) { return false; } WeaponProf wp = ref.resolvesTo(); return hasWeaponProfKeyed(wp.getKeyName()); } private void selectRacialFavoredClass() { TransitionChoice<PCClass> fcChoice = getRace().get( ObjectKey.FAVCLASS_CHOICE); if (fcChoice != null) { selectedFavoredClass = fcChoice.driveChoice(this).iterator().next(); } } private List<String> getSelectedArmorProfList() { final ArrayList<String> aList = new ArrayList<String>(); // Try all possible PObjects for (PObject pObj : getPObjectList()) { if (pObj == null) { continue; } List<String> l = pObj.getListFor(ListKey.SELECTED_ARMOR_PROF); if (l != null) { aList.addAll(l); } } return aList; } private String getSubRegion(final boolean useTemplates) { String pcSubRegion = getStringFor(StringKey.SUB_REGION); if ((pcSubRegion != null) || !useTemplates) { return pcSubRegion; // character's subregion trumps any from // templates } String s = Constants.s_NONE; for (PCTemplate template : templateList) { final String tempSubRegion = template.getSubRegion(); if (!tempSubRegion.equals(Constants.s_NONE)) { s = tempSubRegion; } } return s; } private int getTotalClassLevels() { int total = 0; for (PCClass pcClass : classList) { total += pcClass.getLevel(); } return total; } /** * get the total number of character levels a character has. A character * level is any class level that is not a monster level * * @return total character level */ private int getTotalCharacterLevel() { int total = 0; for (PCClass pcClass : classList) { if (!pcClass.isMonster()) { total += pcClass.getLevel(); } } return total; } private HashMap<String, String> getTotalLevelHashMap() { final HashMap<String, String> lvlMap = new HashMap<String, String>(); for (PCClass aClass : classList) { lvlMap.put(aClass.getKeyName(), String.valueOf(aClass.getLevel())); } return lvlMap; } private void addSpells(final PObject obj) { if ((race == null) || (obj == null) || (obj.getSpellList() == null) || obj.getSpellList().isEmpty()) { return; } PObject owner; List<PCSpell> spellList = obj.getSpellList(); for (PCSpell pcSpell : spellList) { final String spellKey = pcSpell.getKeyName(); final Spell aSpell = Globals.getSpellKeyed(spellKey); if (aSpell == null) { return; } final String castCount = pcSpell.getTimesPerDay(); int spellLevel = -1; int times = 1; int slotLevel = 0; owner = race; TimeUnit timeUnit = pcSpell.getTimeUnit(); if (castCount == null || castCount.equals("")) { times = 1; } else if (castCount.startsWith("LEVEL=") || castCount.startsWith("LEVEL.")) { spellLevel = Integer.parseInt(castCount.substring(6)); slotLevel = spellLevel; if (obj instanceof PCClass) { owner = obj; } } else { times = getVariableValue(castCount, "").intValue(); } final String book = pcSpell.getSpellbook(); final String dcFormula = pcSpell.getDcFormula(); if (dcFormula != null && !dcFormula.equals("")) { getVariableValue(dcFormula, "").intValue(); // TODO: value never // used } if (PrereqHandler.passesAll(pcSpell.getPrerequisiteList(), this, pcSpell)) { final Spell newSpell = aSpell.clone(); aSpell.setFixedCasterLevel(pcSpell.getCasterLevelFormula()); aSpell.setFixedDC(pcSpell.getDcFormula()); final List<CharacterSpell> sList = owner.getSpellSupport().getCharacterSpell(newSpell, book, spellLevel); if (!sList.isEmpty()) { continue; } final CharacterSpell cs = new CharacterSpell(owner, aSpell); SpellInfo si = cs.addInfo(slotLevel, times, book); si.setTimeUnit(timeUnit); addSpellBook(new SpellBook(book, SpellBook.TYPE_INNATE_SPELLS)); owner.getSpellSupport().addCharacterSpell(cs); } } setDirty(true); } private void setStableAggregateFeatList(final List<Ability> aFeatList) { stableAggregateFeatList = aFeatList; setAggregateFeatsStable(aFeatList != null); } /** * Not sure what this does yet. * * @param aFeatList * @param aString * @param isAuto */ private void addWeaponProfToList(final List<Ability> aFeatList, final String aString, final boolean isAuto) { LoadContext context = Globals.getContext(); if (aString.startsWith("WEAPONTYPE=") || aString.startsWith("WEAPONTYPE.")) { for (Equipment weap : EquipmentList.getEquipmentOfType("WEAPON." + aString.substring(11), "")) { CDOMSingleRef<WeaponProf> ref = weap.get(ObjectKey.WEAPON_PROF); if (ref != null) { addWeaponProfToList(aFeatList, ref.resolvesTo().getKeyName(), isAuto); } } return; } // Add all weapons of type aString // (e.g.: Simple, Martial, Exotic, Ranged, etc.) else if (context.containsType(WeaponProf.class, aString)) { for (WeaponProf weaponProf : Globals.getPObjectsOfType(context.ref .getConstructedCDOMObjects(WeaponProf.class), aString)) { addWeaponProfToList(aFeatList, weaponProf.getKeyName(), isAuto); } return; } final WeaponProf wp = context.ref.silentlyGetConstructedCDOMObject(WeaponProf.class, aString); if (wp != null) { final StringTokenizer aTok = new StringTokenizer(wp.getType(), "."); String featKey = aTok.nextToken() + " Weapon Proficiency"; while (aTok.hasMoreTokens() || (featKey.length() > 0)) { if ("".equals(featKey)) { if (aTok.hasMoreTokens()) { featKey = aTok.nextToken() + " Weapon Proficiency"; } else { break; } } Ability anAbility = AbilityUtilities.getAbilityFromList(aFeatList, "FEAT", featKey, Ability.Nature.ANY); if (anAbility != null) { if (anAbility.getSafe(ObjectKey.MULTIPLE_ALLOWED) && !anAbility.containsAssociated(aString)) { anAbility.addAssociated(aString); anAbility.sortAssociated(); } } else { anAbility = Globals.getAbilityKeyed("FEAT", featKey); if (anAbility != null) { if (isAuto && !anAbility.getSafe(ObjectKey.MULTIPLE_ALLOWED) && !Constants.s_INTERNAL_WEAPON_PROF .equalsIgnoreCase(featKey)) { // // Only use catch-all if haven't taken feat that // supersedes it // if (hasRealFeat(anAbility)) { featKey = Constants.s_INTERNAL_WEAPON_PROF; continue; } featKey = ""; continue; // Don't add auto-feat } anAbility = anAbility.clone(); anAbility.addAssociated(aString); if (isAuto) { anAbility.setFeatType(Ability.Nature.AUTOMATIC); } aFeatList.add(anAbility); } /* * else { if (!wp.isType("NATURAL")) { * Logging.errorPrint("Weaponprof feat not found: " + * featName + ":" + aString); } } */ } if (anAbility != null) { // TheForken 20050124 adds bonus to feat anAbility.addSelectedWeaponProfBonus(aString); } featKey = ""; } } if (wp != null && cachedWeaponProfs != null) { cachedWeaponProfs.put(wp.getKeyName(), wp); } // if (wp != null && !weaponProfList.contains(wp)) // { // weaponProfList.add(wp); // } } /** * Gets SHIELDPROF strings from all possible PObjects * * @return List */ private List<String> getAutoShieldProfList() { final ArrayList<String> aList = new ArrayList<String>(); for (PObject aPObj : getPObjectList()) { if (aPObj != null) { // TODO this is going to just add an empty list aPObj.addAutoTagsToList("SHIELDPROF", aList, this, true); } } return aList; } /** * Get the class level as a String * * @param aClassKey * @param doReplace * @return class level as String */ public String getClassLevelString(String aClassKey, final boolean doReplace) { int lvl = 0; int idx = aClassKey.indexOf(";BEFORELEVEL="); if (idx < 0) { idx = aClassKey.indexOf(";BEFORELEVEL."); } if (idx > 0) { lvl = Integer.parseInt(aClassKey.substring(idx + 13)); aClassKey = aClassKey.substring(0, idx); } if (doReplace) { aClassKey = aClassKey.replace('{', '(').replace('}', ')'); } if (aClassKey.startsWith("TYPE=")||aClassKey.startsWith("TYPE.")) { int totalLevels = 0; String[] classTypes = aClassKey.substring(5).split("\\."); CLASSFOR: for (PCClass cl : getClassList()) { for (String type : classTypes) { if (!cl.isType(type)) { continue CLASSFOR; } if (lvl > 0) { totalLevels += getLevelBefore(cl.getKeyName(), lvl); } totalLevels += cl.getLevel(); } } return Integer.toString(totalLevels); } else { final PCClass aClass = getClassKeyed(aClassKey); if (aClass != null) { if (lvl > 0) { return Integer.toString(getLevelBefore(aClass.getKeyName(), lvl)); } return Integer.toString(aClass.getLevel()); } return "0"; } } public int getLevelBefore(final String classKey, final int charLevel) { String thisClassKey; int lvl = 0; for (int idx = 0; idx < charLevel; ++idx) { thisClassKey = getLevelInfoClassKeyName(idx); if (thisClassKey.length() == 0) { break; } if (thisClassKey.equals(classKey)) { ++lvl; } } return lvl; } public List<? extends CDOMObject> getCDOMObjectList() { List<CDOMObject> list = new ArrayList<CDOMObject>(); for (PObject po : getPObjectList()) { if (po != null) { list.add(po); } } for (PCClass cl : classList) { for (int i = 1; i <= cl.getLevel(); i++) { PCClassLevel classLevel = cl.getClassLevel(i); list.add(classLevel); } } list.addAll(getConditionalTemplateObjects()); return list; } private List<PObject> getConditionalTemplateObjects() { List<PObject> list = new ArrayList<PObject>(); int totalLevels = getTotalLevels(); int totalHitDice = totalHitDice(); for (PCTemplate templ : getTemplateList()) { templ.getConditionalTemplates(totalLevels, totalHitDice, list); } return list; } private List<? extends PObject> getPObjectList() { // Possible object types include: // Campaigns // Alignment (PCAlignment) // BioSet (ageSet) // Check (PObject) // Class (PCClass) // CompanionMod // Deity // Domain (CharacterDomain) // Equipment (includes EqMods) // Feat (virtual feats, auto feats) // Race // SizeAdjustment // Skill // Stat (PCStat) // Template (PCTemplate) // final ArrayList<PObject> results = new ArrayList<PObject>(); // Loaded campaigns final List<Campaign> campaigns = Globals.getCampaignList(); for (final Campaign campaign : campaigns) { if (campaign != null && campaign.isLoaded()) { results.add(campaign); results.addAll(campaign.getSubCampaigns()); } } // Alignment PCAlignment align = SettingsHandler.getGame().getAlignmentAtIndex(getAlignment()); if (align != null) { results.add(align); } // armorProfList is still just a list of Strings // results.addAll(getArmorProfList()); // BioSet results.add(Globals.getBioSet()); results.addAll(SettingsHandler.getGame().getUnmodifiableCheckList()); // Class results.addAll(classList); // CompanionMod results.addAll(companionModList); // Deity if (deity != null) { results.add(deity); } // Domain (CharacterDomain) for (CharacterDomain aCD : characterDomainList) { final Domain aDomain = aCD.getDomain(); if (aDomain != null) { results.add(aDomain); } } // Equipment final List<Equipment> eqList = new ArrayList<Equipment>(getEquipmentList()); for (Equipment eq : eqList) { // Include natural weapons by default as they have an effect even if // not equipped. if (eq.isEquipped() || eq.isNatural()) { results.add(eq); for (EquipmentModifier eqMod : eq.getEqModifierList(true)) { results.add(eqMod); } for (EquipmentModifier eqMod : eq.getEqModifierList(false)) { results.add(eqMod); } } } // Feats and abilities (virtual feats, auto feats) List<Ability> abilities = getFullAbilityList(); results.addAll(abilities); // Race if (getRace() != null) { results.add(getRace()); } // SizeAdjustment if (getSizeAdjustment() != null) { results.add(getSizeAdjustment()); } // Skill results.addAll(getSkillList()); // Stat (PCStat) results.addAll(statList.getStatList()); // Template (PCTemplate) results.addAll(getTemplateList()); // weaponProfList is still just a list of Strings // results.addAll(getWeaponProfList()); return results; } private void getPreReqFromACType(String aString, final PObject aPObj) { final StringTokenizer aTok = new StringTokenizer(aString, Constants.PIPE); String outputString = Constants.PIPE; while (aTok.hasMoreTokens()) { final String bString = aTok.nextToken(); if (PreParserFactory.isPreReqString(bString)) { try { Logging .debugPrint("Why is this prerequisite '" + bString + "' parsed in '" + getClass().getName() + ".getPreReqFromACType()' rather than the persistence layer?"); final PreParserFactory factory = PreParserFactory.getInstance(); final Prerequisite prereq = factory.parse(bString); aPObj.addPrerequisite(prereq); } catch (PersistenceLayerException ple) { Logging.errorPrint(ple.getMessage(), ple); } } else { outputString += (bString + Constants.PIPE); } } aString = outputString.substring(1); // TODO: value never used } /** * @param level */ private void addNewSkills(final int level) { final List<Skill> addItems = new ArrayList<Skill>(); final List<Skill> skillList = new ArrayList<Skill>(getSkillList()); for (Skill aSkill : Globals.getContext().ref.getConstructedCDOMObjects(Skill.class)) { if (includeSkill(aSkill, level)) { /* * Must do brute force search - no guarantee it's sorted */ boolean found = false; for (Skill sk : skillList) { if (sk.getKeyName().equals(aSkill.getKeyName())) { found = true; break; } } if (!found) { addItems.add((aSkill.clone())); } } } getSkillList().addAll(addItems); // setDirty(true); } /** * availableSpells sk4p 13 Dec 2002 * * For learning or preparing a spell: Are there slots available at this * level or higher Fixes BUG [569517] * * @param level * the level being checked for availability * @param aClass * the class under consideration * @param bookName * the name of the spellbook * @param knownLearned * "true" if this is learning a spell, "false" if prepping * @param isSpecialtySpell * "true" if this is a speciality for the given class * @return true or false, a new spell can be added */ public boolean availableSpells(final int level, final PCClass aClass, final String bookName, final boolean knownLearned, final boolean isSpecialtySpell) { boolean available = false; final boolean isDivine = ("Divine".equalsIgnoreCase(aClass.get(StringKey.SPELLTYPE))); final boolean canUseHigher = knownLearned ? getUseHigherKnownSlots() : getUseHigherPreppedSlots(); int knownTot; int knownNon; int knownSpec; int memTot; int memNon; int memSpec; // int excTot int excNon; // int excTot int excSpec; int lowExcSpec = 0; int lowExcNon = 0; int goodExcSpec = 0; int goodExcNon = 0; for (int i = 0; i < level; ++i) { // Get the number of castable slots if (knownLearned) { knownNon = aClass.getKnownForLevel(i, bookName, this); knownSpec = aClass.getSpecialtyKnownForLevel(i, this); knownTot = knownNon + knownSpec; // TODO: : value never used } else { // Get the number of castable slots knownTot = aClass.getCastForLevel(i, bookName, true, true, this); knownNon = aClass.getCastForLevel(i, bookName, false, true, this); knownSpec = knownTot - knownNon; } // Now get the number of spells memorised, total and specialities memTot = aClass.memorizedSpellForLevelBook(i, bookName); memSpec = aClass.memorizedSpecialtiesForLevelBook(i, bookName); memNon = memTot - memSpec; // Excess castings excSpec = knownSpec - memSpec; excNon = knownNon - memNon; // Now we spend these slots making up any deficits in lower levels // while ((excNon > 0) && (lowExcNon < 0)) { --excNon; ++lowExcNon; } while ((excSpec > 0) && (lowExcSpec < 0)) { --excSpec; ++lowExcSpec; } if (!isDivine || knownLearned) { // If I'm not divine, I can use non-specialty slots of this // level // to take up the slack of my excess speciality spells from // lower levels. while ((excNon > 0) && (lowExcSpec < 0)) { --excNon; ++lowExcSpec; } // And I can use non-specialty slots of this level to take // up the slack of my excess speciality spells of this level. // while ((excNon > 0) && (excSpec < 0)) { --excNon; ++excSpec; } } // Now, if there are slots left over, I don't add them to the // running totals. // Spell slots of this level won't help me at the next level. // Deficits, however, will have to be made up at the next level. // if (excSpec < 0) { lowExcSpec += excSpec; } if (excNon < 0) { lowExcNon += excNon; } } for (int i = level; i <= Constants.MAX_SPELL_LEVEL; ++i) { if (knownLearned) { knownNon = aClass.getKnownForLevel(i, bookName, this); knownSpec = aClass.getSpecialtyKnownForLevel(i, this); knownTot = knownNon + knownSpec; // for completeness } else { // Get the number of castable slots knownTot = aClass.getCastForLevel(i, bookName, true, true, this); knownNon = aClass.getCastForLevel(i, bookName, false, true, this); knownSpec = knownTot - knownNon; } // At the level currently being looped through, if the number of // casts // is zero, that means we have reached a level beyond which no // higher-level // casts are possible. Therefore, it's time to break. // Likewise if we aren't allowed to use higher level slots, no sense // in // going higher than the spell's level. // if (!canUseHigher && i > level) { break; } // Now get the number of spells memorised, total and specialities memTot = aClass.memorizedSpellForLevelBook(i, bookName); memSpec = aClass.memorizedSpecialtiesForLevelBook(i, bookName); memNon = memTot - memSpec; // Excess castings excSpec = knownSpec - memSpec; excNon = knownNon - memNon; // Now we spend these slots making up any deficits in lower levels // while ((excNon > 0) && (lowExcNon < 0)) { --excNon; ++lowExcNon; } while ((excNon > 0) && (goodExcNon < 0)) { --excNon; ++goodExcNon; } while ((excSpec > 0) && (lowExcSpec < 0)) { --excSpec; ++lowExcSpec; } while ((excSpec > 0) && (goodExcSpec < 0)) { --excSpec; ++goodExcSpec; } if (!isDivine) { // If I'm not divine, I can use non-specialty slots of this // level // to take up the slack of my excess speciality spells from // lower levels. while ((excNon > 0) && (lowExcSpec < 0)) { --excNon; ++lowExcSpec; } // And also for levels sufficiently high for the spell that got // me // into this mess, but of lower level than the level currently // being calculated. while ((excNon > 0) && (goodExcSpec < 0)) { --excNon; ++goodExcSpec; } // And finally use non-specialty slots of this level to take // up the slack of excess speciality spells of this level. // while ((excNon > 0) && (excSpec < 0)) { --excNon; ++excSpec; } } // Right now, if there are slots left over at this level, // it means that there are slots left to add the spell that started // all of this. if (isDivine) { if (isSpecialtySpell && (excSpec > 0)) { available = true; } if (!isSpecialtySpell && (excNon > 0)) { available = true; } } else { if (!isSpecialtySpell && (excNon > 0)) { available = true; } if (isSpecialtySpell && ((excNon > 0) || (excSpec > 0))) { available = true; } } // If we found a slot, we need look no further. if (available) { break; } // Now, if there are slots left over, I don't add them to the // running totals. // Spell slots of this level won't help me at the next level. // Deficits, however, will have to be made up at the next level. // if (excSpec < 0) { goodExcSpec += excSpec; } if (excNon < 0) { goodExcNon += excNon; } } return available; } // private Map<String, List<TypedBonus>> buildBonusMap( final List<BonusObj> // aBonusList ) // { // final Map<String, List<TypedBonus>> ret = new HashMap<String, // List<TypedBonus>>(); // // final List<BonusObj> processedList = new ArrayList<BonusObj>(); // // for ( final BonusObj bonus : aBonusList ) // { // // TODO - The list should be sorted so that static bonuses come first // // we could break out of the loop at this point. // if (!bonus.isValueStatic()) // { // continue; // } // // final PObject anObj = (PObject) bonus.getCreatorObject(); // // if (anObj == null) // { // continue; // } // // // Keep track of which bonuses have been calculated // processedList.add(bonus); // // ret.putAll(bonus.getTypedBonuses(this)); // } // // // TODO - I don't understand this. The list is sorted with static // // bonuses coming first, why not just process the list once? // for ( final BonusObj bonus : aBonusList ) // { // if ( processedList.contains(bonus) ) // { // continue; // } // // final PObject anObj = (PObject) bonus.getCreatorObject(); // // if (anObj == null) // { // continue; // } // processBonus(bonus, ret, aBonusList, new ArrayList<BonusObj>(), // processedList); // } // return ret; // } /** * Build the bonus HashMap from all active BonusObj's */ private void buildActiveBonusMap() { clearActiveBonusMap(); processedBonusList.clear(); // // We do a first pass of just the "static" bonuses // as they require less computation and no recursion List<BonusObj> bonusListCopy = new ArrayList<BonusObj>(); bonusListCopy.addAll(getActiveBonusList()); for (BonusObj bonus : bonusListCopy) { if (!bonus.isValueStatic()) { continue; } final PObject anObj = (PObject) bonus.getCreatorObject(); if (anObj == null) { continue; } // Keep track of which bonuses have been calculated processedBonusList.add(bonus); for (BonusPair bp : bonus.getStringListFromBonus()) { final double iBonus = bp.resolve(this).doubleValue(); setActiveBonusStack(iBonus, bp.bonusKey, getActiveBonusMap()); Logging.debugPrint("BONUS: " + anObj.getDisplayName() + " : " + iBonus + " : " + bp.bonusKey); } } // // Now we do all the BonusObj's that require calculations bonusListCopy = new ArrayList<BonusObj>(); bonusListCopy.addAll(getActiveBonusList()); for (BonusObj bonus : getActiveBonusList()) { if (processedBonusList.contains(bonus)) { continue; } final PObject anObj = (PObject) bonus.getCreatorObject(); if (anObj == null) { continue; } processBonus(bonus, new ArrayList<BonusObj>()); } } /** * Compute total bonus from a List of BonusObj's Use cost of bonus to adjust * total bonus up or down This method takes a list of bonus objects. * * For each object in the list, it gets the creating object and queries it * for its "COST". It then multiplies the value of the bonus by this cost * and adds it to the cumulative total so far. If subSearch is true, the * choices made in the object that the bonus originated in are searched, the * effective bonus is multiplied by the number of times this bonus appears * in the list. * * Note: This COST seems to be used for several different things in the code * base, in feats for instance, it is used to modify the feat pool by * amounts other than 1 when selecting a given feat. Here it is used as a * multiplier to say how effective a given bonus is i.e. a bonus with a COST * of 0.5 counts for half its normal value. The COST is limited to a max of * 1, so it can only make bonuses less effective. * * @param aList * a list of bonus objects * @return the calculated cumulative bonus */ private double calcBonusWithCostFromList(final List<BonusObj> aList) { double totalBonus = 0; for (BonusObj aBonus : aList) { final PObject anObj = (PObject) aBonus.getCreatorObject(); if (anObj == null) { continue; } double iBonus = 0; if (aBonus.qualifies(this)) { iBonus = aBonus.resolve(this, anObj.getQualifiedKey()).doubleValue(); } int k = Math.max(1, (int) (anObj.getAssociatedCount() * anObj.getSafe(ObjectKey.SELECTION_COST).doubleValue())); if (anObj.getAssociatedCount() > 0) { k = 0; for (int f = 0; f < anObj.getAssociatedCount(); ++f) { final String aString = anObj.getAssociated(f); if (aString.equalsIgnoreCase(aBonus.getBonusInfo())) { ++k; } } } if ((k == 0) && !CoreUtility.doublesEqual(iBonus, 0)) { totalBonus += iBonus; } else { totalBonus += (iBonus * k); } } return totalBonus; } // private void calcPurchaseModeBonuses() private List<BonusObj> getPurchaseModeBonuses() { final GameMode gm = SettingsHandler.getGame(); final String purchaseMethodName = gm.getPurchaseModeMethodName(); if (gm.isPurchaseStatMode()) { final PointBuyMethod pbm = gm.getPurchaseMethodByName(purchaseMethodName); pbm.activateBonuses(this); // final List<BonusObj> tempList = pbm.getActiveBonuses(); // addListToActiveBonuses(tempList); return pbm.getActiveBonuses(); } return Collections.emptyList(); } // private void calcTempBonuses() private List<BonusObj> getTempBonuses() { final List<BonusObj> tempList = getFilteredTempBonusList(); if (tempList.isEmpty()) { return Collections.emptyList(); // return; } for (final Iterator<BonusObj> tempIter = tempList.iterator(); tempIter .hasNext();) { final BonusObj bonus = tempIter.next(); bonus.setApplied(false); if (bonus.qualifies(this)) { bonus.setApplied(true); } if (!bonus.isApplied()) { tempIter.remove(); } } // addListToActiveBonuses(tempList); return tempList; } /** * calculate the total racial modifier to save: racial bonuses like the * standard halfling's +1 on all saves template bonuses like the Lightfoot * halfling's +1 on all saves racial base modifiers for certain monsters * * @param saveIndex * @return int */ private int calculateSaveBonusRace(final int saveIndex) { int save; if (((saveIndex - 1) < 0) || ((saveIndex - 1) >= SettingsHandler.getGame() .getUnmodifiableCheckList().size())) { return 0; } final String sString = SettingsHandler.getGame().getUnmodifiableCheckList().get( saveIndex - 1).toString(); save = (int) race.bonusTo("CHECKS", "BASE." + sString, this, this); save += (int) race.bonusTo("CHECKS", sString, this, this); return save; } private void clearActiveBonusMap() { activeBonusMap.clear(); } /** * returns the level of the highest spell in a given spellbook Yes, divine * casters can have a "spellbook" * * @param aString * @return spell levels in book */ int countSpellLevelsInBook(final String aString) { int levelNum = 0; final StringTokenizer aTok = new StringTokenizer(aString, "."); final int classNum = Integer.parseInt(aTok.nextToken()); final int sbookNum = Integer.parseInt(aTok.nextToken()); String bookName = Globals.getDefaultSpellBook(); if (sbookNum > 0) { bookName = getSpellBooks().get(sbookNum); } final PObject aObject = getSpellClassAtIndex(classNum); if (aObject != null) { for (levelNum = 0; levelNum >= 0; ++levelNum) { final List<CharacterSpell> aList = aObject.getSpellSupport().getCharacterSpell(null, bookName, levelNum); if (aList.size() < 1) { break; } } } return levelNum; } /** * returns the number of spells based on class, level and spellbook * * @param aString * @return int */ int countSpellListBook(final String aString) { final int dot = aString.lastIndexOf('.'); int spellCount = 0; if (dot < 0) { for (PCClass pcClass : classList) { spellCount += pcClass.getSpellSupport().getCharacterSpellCount(); } } else { final int classNum = Integer.parseInt(aString.substring(17, dot)); final int levelNum = Integer.parseInt(aString.substring(dot + 1, aString .length() - 1)); final PObject aObject = getSpellClassAtIndex(classNum); if (aObject != null) { final List<CharacterSpell> aList = aObject.getSpellSupport().getCharacterSpell(null, Globals.getDefaultSpellBook(), levelNum); spellCount = aList.size(); } } return spellCount; } /** * returns the number of times a spell is memorised Tag looks like: * (SPELLTIMES%class&period;%book&period;%level&period;%spell) aString looks * like: SPELLTIMES2&period;-1&period;4&period;15 * * where &period; is a full stop (or period if you are from USA ;p) * * heavily stolen from replaceTokenSpellMem in ExportHandler.java * * @param aString * @return spell times */ int countSpellTimes(final String aString) { boolean found = false; final StringTokenizer aTok = new StringTokenizer(aString.substring(10), "."); final int classNum = Integer.parseInt(aTok.nextToken()); final int bookNum = Integer.parseInt(aTok.nextToken()); final int spellLevel = Integer.parseInt(aTok.nextToken()); final int spellNumber = Integer.parseInt(aTok.nextToken()); final PObject aObject = getSpellClassAtIndex(classNum); String bookName = Globals.getDefaultSpellBook(); if (bookNum > 0) { bookName = getSpellBooks().get(bookNum); } if ((aObject != null) || (classNum == -1)) { if (classNum == -1) { bookName = Globals.getDefaultSpellBook(); } if (!"".equals(bookName)) { SpellInfo si = null; if (classNum == -1) { final List<CharacterSpell> charSpellList = new ArrayList<CharacterSpell>(); for (PCClass pcClass : getClassList()) { final List<CharacterSpell> bList = pcClass.getSpellSupport().getCharacterSpell( null, bookName, -1); for (CharacterSpell cs : bList) { if (!charSpellList.contains(cs)) { charSpellList.add(cs); } } } Collections.sort(charSpellList); if (spellNumber < charSpellList.size()) { final CharacterSpell cs = charSpellList.get(spellNumber); si = cs.getSpellInfoFor(bookName, -1, -1); found = true; } } else if (aObject != null) { final List<CharacterSpell> charSpells = aObject.getSpellSupport().getCharacterSpell(null, bookName, spellLevel); if (spellNumber < charSpells.size()) { final CharacterSpell cs = charSpells.get(spellNumber); si = cs.getSpellInfoFor(bookName, spellLevel, -1); found = true; } } if (found && (si != null)) { return si.getTimes(); } } } return 0; } /** * Counts the number of spells inside a spellbook Yes, divine casters can * have a "spellbook" * * @param aString * @return spells in a book */ public int countSpellsInBook(final String aString) { final StringTokenizer aTok = new StringTokenizer(aString, "."); final int classNum = Integer.parseInt(aTok.nextToken()); final int sbookNum = Integer.parseInt(aTok.nextToken()); final int levelNum; if (sbookNum >= getSpellBooks().size()) { return 0; } if (aTok.hasMoreTokens()) { levelNum = Integer.parseInt(aTok.nextToken()); } else { levelNum = -1; } /* * // Class 0 is special Abilities. Obvious isn't it. if ( classNum == 0 ) { * int count = 0; final Collection<SpellLikeAbility> slas = * this.getSpellLikeAbilities(); * * int categoryCount = -1; String currentCategory = * Constants.EMPTY_STRING; for ( final SpellLikeAbility sla : slas ) { * if ( !currentCategory.equals(sla.getCategory()) ) { categoryCount++; * currentCategory = sla.getCategory(); } if ( categoryCount > sbookNum ) { * break; } if ( categoryCount == sbookNum ) { // This is the * "spellbook" we are looking for count++; } } return count; } */ String bookName = Globals.getDefaultSpellBook(); if (sbookNum > 0) { bookName = getSpellBooks().get(sbookNum); } final PObject aObject = getSpellClassAtIndex(classNum); if (aObject != null) { final List<CharacterSpell> aList = aObject.getSpellSupport().getCharacterSpell(null, bookName, levelNum); return aList.size(); } return 0; } private Gender findTemplateGender() { Gender g = null; for (PCTemplate template : templateList) { Gender lock = template.get(ObjectKey.GENDER_LOCK); if (lock != null) { g = lock; } } return g; } private void setEarnedXP(final int argEarnedXP) { earnedXP = argEarnedXP; setDirty(true); } private int getLAXP() { // Why +1? Adjustments are deltas, not absolute // levels, so are not subject to the "back off one" // element of the * algorithm in minXPForLevel. This // still means that levelAdjustment of 0 gives you 0 // XP, but we need LA of 1 to give us 1,000 XP. return PlayerCharacterUtilities.minXPForLevel( getLevelAdjustment(this) + 1, this); } private SizeAdjustment getSizeAdjustment() { final SizeAdjustment sa = SettingsHandler.getGame().getSizeAdjustmentAtIndex(sizeInt()); return sa; } public int getSpellClassCount() { return getSpellClassList().size(); } /** * Get the spell class list * * @return List */ public List<? extends PObject> getSpellClassList() { final ArrayList<PObject> aList = new ArrayList<PObject>(); if (!race.getSpellSupport().getCharacterSpell(null, Constants.EMPTY_STRING, -1).isEmpty()) { aList.add(race); } for (PCClass pcClass : classList) { if (pcClass.get(StringKey.SPELLTYPE) != null) { aList.add(pcClass); } } return aList; } private String checkForVariableInList( final PObject obj, final String variableString, final boolean isMax, boolean found, double value) { boolean flag = false; String src = obj.getSpellKey(); for (VariableKey vk : obj.getVariableKeys()) { String nString = vk.toString(); if (nString.equalsIgnoreCase(variableString)) { Float newValue = getVariableValue(obj.get(vk).toString(), src); if (!found) { value = newValue.floatValue(); } else if (isMax) { value = Math.max(value, newValue.doubleValue()); } else { value = Math.min(value, newValue.doubleValue()); } found = true; flag = true; } } if (flag) { return Double.toString(value); } return Constants.EMPTY_STRING; // signifies that the variable was found // in this list } /** * Check if the character has the named Deity. * * @param deityName * String name of the deity to check for. * @return <code>true</code> if the character has the Deity, * <code>false</code> otherwise. */ public boolean hasDeity(final String deityName) { final Prerequisite prereq = new Prerequisite(); prereq.setKind("DEITY"); prereq.setOperand(deityName); prereq.setOperator(PrerequisiteOperator.EQ); return PrereqHandler.passes(prereq, this, null); } private boolean includeSkill(final Skill skill, final int level) { if (level == 2 || skill.getTotalRank(this).floatValue() > 0) { return true; } if (level != 1 || !skill.getSafe(ObjectKey.USE_UNTRAINED)) { return false; } if (skill.getSafe(ObjectKey.EXCLUSIVE)) { return this.isClassSkill(skill) || this.isCrossClassSkill(skill); } else { return PrereqHandler.passesAll((skill).getPrerequisiteList(), this, skill); } } private void increaseMoveArray(final Double moveRate, final String moveType, final Double moveMult, final String multOp) { // could not find an existing one so // need to add new item to array // final Double[] tempMove = movements; final String[] tempType = movementTypes; final Double[] tempMult = movementMult; final String[] tempMultOp = movementMultOp; // now increase the size of the array by one movements = new Double[tempMove.length + 1]; movementTypes = new String[tempMove.length + 1]; movementMult = new Double[tempMove.length + 1]; movementMultOp = new String[tempMove.length + 1]; System.arraycopy(tempMove, 0, movements, 0, tempMove.length); System.arraycopy(tempType, 0, movementTypes, 0, tempMove.length); System.arraycopy(tempMult, 0, movementMult, 0, tempMove.length); System.arraycopy(tempMultOp, 0, movementMultOp, 0, tempMove.length); // the size is larger, but arrays start at 0 // so an array length=3 would have 0, 1, 2 as the targets movements[tempMove.length] = moveRate; movementTypes[tempMove.length] = moveType; movementMult[tempMove.length] = moveMult; movementMultOp[tempMove.length] = multOp; } /** * Change the number of levels a character has in a particular class. Note: * It is assumed that this method is not used as part of loading a * previously saved character. there is no way to bypass the prerequisites * with this method, see: incrementClassLevel(int, PCClass, boolean, * boolean); * * * @param numberOfLevels * number of levels to add * @param globalClass * the class to add the levels to * @param bSilent * whether or not to display warning messages */ public void incrementClassLevel(final int numberOfLevels, final PCClass globalClass, final boolean bSilent) { incrementClassLevel(numberOfLevels, globalClass, bSilent, false); } /** * Change the number of levels a character has in a particular class. Note: * It is assumed that this method is not used as part of loading a * previously saved character. * * @param numberOfLevels * The number of levels to add or remove. If a positive number is * passed in then that many levels will be added. If the number * of levels passed in is negative then that many levels will be * removed from the specified class. * @param globalClass * The global class from the data store. The class as stored in * the character will be compared to this one using the * getClassNamed() method * @param bSilent * If true do not display any warning messages about adding or * removing too many levels * @param bypassPrereqs * Whether we should bypass the checks as to whether or not the * PC qualifies to take this class. If true, the checks will be * bypassed */ public void incrementClassLevel(final int numberOfLevels, final PCClass globalClass, final boolean bSilent, final boolean bypassPrereqs) { // If not importing, load the spell list if (!isImporting()) { getSpellList(); } // Make sure the character qualifies for the class if adding it if (numberOfLevels > 0) { if (!bypassPrereqs && !globalClass.isQualified(this)) { return; } if (globalClass.isMonster() && !SettingsHandler.isIgnoreMonsterHDCap() && !race.isAdvancementUnlimited() && ((totalHitDice() + numberOfLevels) > race .maxHitDiceAdvancement()) && !bSilent) { ShowMessageDelegate .showMessageDialog( "Cannot increase Monster Hit Dice for this character beyond " + race.maxHitDiceAdvancement() + ". This character's current number of Monster Hit Dice is " + totalHitDice(), Constants.s_APPNAME, MessageType.INFORMATION); return; } } // Check if the character already has the class. PCClass pcClassClone = getClassKeyed(globalClass.getKeyName()); // If the character did not already have the class... if (pcClassClone == null) { // add the class even if setting to level 0 if (numberOfLevels >= 0) { // Get a clone of the class so we don't modify the globals! pcClassClone = globalClass.clone(); // Make sure the clone was successful if (pcClassClone == null) { Logging .errorPrint("PlayerCharacter::incrementClassLevel => " + "Clone of class " + globalClass.getKeyName() + " failed!"); return; } // If not importing, add extra feats if (!isImporting() && classList.isEmpty()) { adjustFeats(pcClassClone.getSafe(IntegerKey.START_FEATS)); } // Add the class to the character classes as level 0 classList.add(pcClassClone); // do the following only if adding a level of a class for the // first time if (numberOfLevels > 0) { for (CDOMReference<Language> ref : pcClassClone .getSafeListFor(ListKey.AUTO_LANGUAGES)) { languages.addAll(ref.getContainedObjects()); } } } else { // mod is < 0 and character does not have class. Return. return; } } // Add or remove levels as needed if (numberOfLevels > 0) { for (int i = 0; i < numberOfLevels; ++i) { final PCLevelInfo playerCharacterLevelInfo = saveLevelInfo(pcClassClone.getKeyName()); // if we fail to add the level, remove and return if (!pcClassClone.addLevel(playerCharacterLevelInfo, false, bSilent, this, bypassPrereqs)) { removeLevelInfo(pcClassClone.getKeyName()); return; } } } else if (numberOfLevels < 0) { for (int i = 0; i < -numberOfLevels; ++i) { pcClassClone.subLevel(bSilent, this); removeLevelInfo(pcClassClone.getKeyName()); } } // Handle any feat changes as a result of level changes if (!PlayerCharacterUtilities.canReassignTemplateFeats()) { for (PCTemplate template : templateList) { final List<String> templateFeats = feats(template, getTotalLevels(), totalHitDice(), true); for (int j = 0, y = templateFeats.size(); j < y; ++j) { AbilityUtilities.modFeatsFromList(this, null, templateFeats .get(j), true, false); } } } setAggregateAbilitiesStable(null, false); // setAggregateFeatsStable(false); // setAutomaticFeatsStable(false); // setVirtualFeatsStable(false); calcActiveBonuses(); // getAutoWeaponProfs(featAutoList()); // setDirty(true); } // private void processBonus( final BonusObj aBonus, // Map<String, List<TypedBonus>> aBonusMap, // final List<BonusObj> aBonusList, // final List<BonusObj> prevProcessed, // final List<BonusObj> processed ) // { // // Make sure we don't get into an infinite loop - can occur due to LST // coding or best guess dependancy mapping // if (prevProcessed.contains(aBonus)) // { // Logging.debugPrint("Ignoring bonus loop for " + aBonus + " as it was // already processed. Bonuses already processed: " + prevProcessed); // //$NON-NLS-1$//$NON-NLS-2$ // return; // } // prevProcessed.add(aBonus); // // final List<BonusObj> dependantBonuses = new ArrayList<BonusObj>(); // // // Go through all bonuses and check to see if they add to // // aBonus's dependencies and have not already been processed // for ( final BonusObj newBonus : aBonusList ) // { // if (processed.contains(newBonus)) // { // continue; // } // // if (aBonus.getDependsOn(newBonus.getBonusInfo())) // { // dependantBonuses.add(newBonus); // } // } // // // go through all the BonusObj's that aBonus depends on // // and process them first // for ( final BonusObj newBonus : dependantBonuses ) // { // // Recursively call itself // processBonus(newBonus, aBonusMap, aBonusList, prevProcessed, processed); // } // // // Double check that it hasn't been processed yet // if (processed.contains(aBonus)) // { // return; // } // // // Add to processed list // processed.add(aBonus); // // final PObject anObj = (PObject) aBonus.getCreatorObject(); // // if (anObj == null) // { // prevProcessed.remove(aBonus); // return; // } // // aBonusMap.putAll(aBonus.getTypedBonuses(this)); // // prevProcessed.remove(aBonus); // } /** * - Get's a list of dependencies from aBonus - Finds all active bonuses * that add to those dependencies and have not been processed and * recursively calls itself - Once recursed in, it adds the computed bonus * to activeBonusMap * * @param aBonus * The bonus to be processed. * @param prevProcessed * The list of bonuses which have already been processed in this * run. */ private void processBonus(final BonusObj aBonus, final ArrayList<BonusObj> prevProcessed) { // Make sure we don't get into an infinite loop - can occur due to LST // coding or best guess dependancy mapping if (prevProcessed.contains(aBonus)) { Logging .debugPrint("Ignoring bonus loop for " + aBonus + " as it was already processed. Bonuses already processed: " + prevProcessed); //$NON-NLS-1$//$NON-NLS-2$ return; } prevProcessed.add(aBonus); final List<BonusObj> aList = new ArrayList<BonusObj>(); // Go through all bonuses and check to see if they add to // aBonus's dependencies and have not already been processed for (BonusObj newBonus : getActiveBonusList()) { if (processedBonusList.contains(newBonus)) { continue; } if (aBonus.getDependsOn(newBonus.getUnparsedBonusInfoList())) { aList.add(newBonus); } } // go through all the BonusObj's that aBonus depends on // and process them first for (BonusObj newBonus : aList) { // Recursively call itself processBonus(newBonus, prevProcessed); } // Double check that it hasn't been processed yet if (processedBonusList.contains(aBonus)) { return; } // Add to processed list processedBonusList.add(aBonus); final PObject anObj = (PObject) aBonus.getCreatorObject(); if (anObj == null) { prevProcessed.remove(aBonus); return; } // calculate bonus and add to activeBonusMap for (BonusPair bp : aBonus.getStringListFromBonus()) { final double iBonus = bp.resolve(this).doubleValue(); setActiveBonusStack(iBonus, bp.bonusKey, getActiveBonusMap()); Logging.debugPrint("vBONUS: " + anObj.getDisplayName() + " : " + iBonus + " : " + bp.bonusKey); } prevProcessed.remove(aBonus); } private boolean qualifiesForFeat(final Ability aFeat) { return aFeat.canBeSelectedBy(this); } private boolean hasSkill(final String aSkillKey) { return (getSkillKeyed(aSkillKey) != null); } private void rebuildLists(final PCClass toClass, final PCClass fromClass, final int iCount, final PlayerCharacter aPC) { final int fromLevel = fromClass.getLevel(); final int toLevel = toClass.getLevel(); for (int i = 0; i < iCount; ++i) { fromClass.doMinusLevelMods(this, fromLevel - i); final PCLevelInfo playerCharacterLevelInfo = aPC.getLevelInfoFor(toClass.getKeyName(), toLevel + i + 1); toClass.doPlusLevelMods(toLevel + i + 1, aPC, playerCharacterLevelInfo); } } private void removeExcessSkills(final int level) { // Elaborate code here is in order to avoid a // ConcurrentModificationException List<Skill> skills = getSkillList(); List<Skill> skillIndexList = new ArrayList<Skill>(); skillIndexList.addAll(skills); final Iterator<Skill> skillIter = skillIndexList.iterator(); boolean modified = false; while (skillIter.hasNext()) { Skill skill = skillIter.next(); if (!includeSkill(skill, level)) { skills.remove(skill); modified = true; } } if (modified) { setDirty(true); } } private boolean removeLevelInfo(final String classKeyName) { for (int idx = pcLevelInfo.size() - 1; idx >= 0; --idx) { final PCLevelInfo li = pcLevelInfo.get(idx); if (li.getClassKeyName().equals(classKeyName)) { removeObjectsForLevelInfo(li); removeLevelInfo(idx); setDirty(true); return true; } } return false; } /** * @param li */ private void removeObjectsForLevelInfo(final PCLevelInfo li) { for (PObject object : li.getObjects()) { // remove this object from the feats lists for (Iterator<Ability> iterator = getRealAbilitiesList(AbilityCategory.FEAT).iterator(); iterator .hasNext();) { final Ability feat = iterator.next(); if (object == feat) { iterator.remove(); } } // remove this object from the feats lists for (Iterator<Ability> iterator = theAbilities.get(AbilityCategory.FEAT, Ability.Nature.VIRTUAL).iterator(); iterator .hasNext();) { final Ability feat = iterator.next(); if (object == feat) { iterator.remove(); } } } } private void removeLevelInfo(final int idx) { pcLevelInfo.remove(idx); setDirty(true); } /** * <code>rollStats</code> roll Globals.s_ATTRIBLONG.length random stats * Method: 1: 4d6 Drop Lowest 2: 3d6 3: 5d6 Drop 2 Lowest 4: 4d6 reroll 1's * drop lowest 5: 4d6 reroll 1's and 2's drop lowest 6: 3d6 +5 7: 5d6 Drop * lowest and middle as per FREQ #458917 * * @param method * the method to be used for rolling. */ public void rollStats(final int method) { int aMethod = method; if (SettingsHandler.getGame().isPurchaseStatMode()) { aMethod = Constants.CHARACTERSTATMETHOD_PURCHASE; } rollStats(aMethod, statList.getStatList(), SettingsHandler.getGame() .getCurrentRollingMethod(), false); } public void rollStats(final int method, final List<PCStat> aStatList, final GameModeRollMethod rollMethod, boolean aSortedFlag) { int[] rolls = new int[aStatList.size()]; for (int i = 0; i < rolls.length; i++) { switch (method) { case Constants.CHARACTERSTATMETHOD_PURCHASE: rolls[i] = SettingsHandler.getGame() .getPurchaseModeBaseStatScore(this); break; case Constants.CHARACTERSTATMETHOD_ALLSAME: rolls[i] = SettingsHandler.getGame().getAllStatsValue(); break; case Constants.CHARACTERSTATMETHOD_ROLLED: final String diceExpression = rollMethod.getMethodRoll(); rolls[i] = RollingMethods.roll(diceExpression); break; default: rolls[i] = 0; break; } } if (aSortedFlag) { Arrays.sort(rolls); } for (int stat = aStatList.size() - 1, i = 0; stat >= 0; stat--, i++) { PCStat currentStat = aStatList.get(stat); currentStat.setBaseScore(0); if (!currentStat.isRolled()) { continue; } int roll = rolls[i] + currentStat.getBaseScore(); if (roll < currentStat.getMinValue()) { roll = currentStat.getMinValue(); } if (roll > currentStat.getMaxValue()) { roll = currentStat.getMaxValue(); } currentStat.setBaseScore(roll); } this.setPoolAmount(0); this.costPool = 0; languages.clear(); getAutoLanguages(); setPoolAmount(0); } /** * Sorts the provided list of equipment in output order. This is in * ascending order of the equipment's outputIndex field. If multiple items * of equipment have the same outputIndex they will be ordered by name. Note * hidden items (outputIndex = -1) are not included in list. * * @param unsortedEquipList * An ArrayList of the equipment to be sorted. * @return An ArrayList of the equipment objects in output order. */ private List<Equipment> sortEquipmentList( final List<Equipment> unsortedEquipList) { return sortEquipmentList(unsortedEquipList, Constants.MERGE_ALL); } /** * Sorts the provided list of equipment in output order. This is in * ascending order of the equipment's outputIndex field. If multiple items * of equipment have the same outputIndex they will be ordered by name. Note * hidden items (outputIndex = -1) are not included in list. * * @param unsortedEquipList * An ArrayList of the equipment to be sorted. * @param merge * How to merge. * @return An ArrayList of the equipment objects in output order. */ private List<Equipment> sortEquipmentList( final List<Equipment> unsortedEquipList, final int merge) { if (unsortedEquipList.isEmpty()) { return unsortedEquipList; } // Merge list for duplicates // The sorting is done during the Merge final List<Equipment> sortedList = CoreUtility.mergeEquipmentList(unsortedEquipList, merge); // Remove the hidden items from the list for (Iterator<Equipment> i = sortedList.iterator(); i.hasNext();) { final Equipment item = i.next(); if (item.getOutputIndex() == -1) { i.remove(); } } return sortedList; } private int subCalcACOfType(final StringTokenizer aTok) { int total = 0; while (aTok.hasMoreTokens()) { final String aString = aTok.nextToken(); total += Integer.parseInt(BonusToken.getBonusToken( "BONUS.COMBAT.AC." + aString, this)); } return total; } /** * @param prefix * @return Total bonus for prefix from the activeBonus HashMap */ private double sumActiveBonusMap(String prefix) { double bonus = 0; prefix = prefix.toUpperCase(); final List<String> aList = new ArrayList<String>(); // There is a risk that the active bonus map may be modified by other // threads, so we use a for loop rather than an iterator so that we // still get an answer. Object[] keys = getActiveBonusMap().keySet().toArray(); for (int i = 0; i < keys.length; i++) { final String aKey = (String) keys[i]; // aKey is either of the form: // COMBAT.AC // or // COMBAT.AC:Luck // or // COMBAT.AC:Armor.REPLACE if (aList.contains(aKey)) { continue; } String rString = aKey; // rString could be something like: // COMBAT.AC:Armor.REPLACE // So need to remove the .STACK or .REPLACE // to get a match for prefix like: COMBAT.AC:Armor if (rString.endsWith(".STACK")) { rString = rString.substring(0, rString.length() - 6); } else if (rString.endsWith(".REPLACE")) { rString = rString.substring(0, rString.length() - 8); } // if prefix is of the form: // COMBAT.AC // then is must match rstring: // COMBAT.AC // COMBAT.AC:Luck // COMBAT.AC:Armor.REPLACE // However, it must not match // COMBAT.ACCHECK if ((rString.length() > prefix.length()) && !rString.startsWith(prefix + ":")) { continue; } if (rString.startsWith(prefix)) { aList.add(rString); aList.add(rString + ".STACK"); aList.add(rString + ".REPLACE"); final double aBonus = getActiveBonusForMapKey(rString, Double.NaN); final double replaceBonus = getActiveBonusForMapKey(rString + ".REPLACE", Double.NaN); final double stackBonus = getActiveBonusForMapKey(rString + ".STACK", 0); // // Using NaNs in order to be able to get the max // between an undefined bonus and a negative // if (Double.isNaN(aBonus)) // no bonusKey { if (!Double.isNaN(replaceBonus)) { // no bonusKey, but there // is a replaceKey bonus += replaceBonus; } } else if (Double.isNaN(replaceBonus)) { // is a bonusKey and no replaceKey bonus += aBonus; } else { // is a bonusKey and a replaceKey bonus += Math.max(aBonus, replaceBonus); } // always add stackBonus bonus += stackBonus; } } return bonus; } private int totalMonsterLevels() { int totalLevels = 0; for (PCClass pcClass : classList) { if (pcClass.isMonster()) { totalLevels += pcClass.getLevel(); } } // This is already accounted for in the monster levels above // for (Iterator e = companionModList.iterator(); e.hasNext();) // { // final CompanionMod cMod = (CompanionMod) e.next(); // totalLevels += cMod.getHitDie(); // } return totalLevels; } /** * @param descriptionLst * The descriptionLst to set. */ private void setDescriptionLst(final String descriptionLst) { this.descriptionLst = descriptionLst; } /* * Prepares this PC object for output by ensuring that all its * info is up to date. */ public void preparePCForOutput() { // Get the EquipSet used for output and calculations // possibly include equipment from temporary bonuses setCalcEquipmentList(getUseTempMods()); // Make sure spell lists are setup getSpellList(); getAllSkillList(true); //force refresh of skills int includeSkills = SettingsHandler.getIncludeSkills(); // TODO Reference a constant if (includeSkills == SettingsHandler.INCLUDE_SKILLS_SKILLS_TAB) { includeSkills = SettingsHandler.getSkillsTab_IncludeSkills(); } populateSkills(includeSkills); for (PObject pcClass : getClassList()) { pcClass.getSpellSupport().sortCharacterSpellList(); } determinePrimaryOffWeapon(); modFromArmorOnWeaponRolls(); adjustMoveRates(); calcActiveBonuses(); } private class CasterLevelSpellBonus { private int bonus; private String type; /** * Constructor * * @param b * @param t */ public CasterLevelSpellBonus(final int b, final String t) { bonus = b; type = t; } /** * Get bonus * * @return bonus */ public int getBonus() { return (bonus); } /** * Get type * * @return type */ public String getType() { return (type); } /** * Set bonus * * @param newBonus */ public void setBonus(final int newBonus) { bonus = newBonus; } @Override public String toString() { return ("bonus: " + bonus + " type: " + type); } } /** * @param info * @return character level */ public int getCharacterLevel(final PCLevelInfo info) { int i = 1; for (PCLevelInfo element : pcLevelInfo) { if (info == element) { return i; } i++; } return -1; } /** * Return a list of bonus languages which the character may select from. * This function is not efficient, but is sufficient for it's current use of * only being called when the user requests the bonus language selection * list. Note: A check will be made for the ALL language and it will be * replaced with the current list of languages in globals. These should be * further restricted by the prerequisites of the languages to ensure that * 'secret' languages are not offered. * * @return List of bonus languages for the character. */ public Set<Language> getLanguageBonusSelectionList() { Set<Language> languageList = new HashSet<Language>(); addStartingLanguages(race, languageList); // Templates for (PCTemplate template : templateList) { addStartingLanguages(template, languageList); } // Classes for (PCClass pcClass : classList) { addStartingLanguages(pcClass, languageList); } return languageList; } private void addStartingLanguages(CDOMObject cdo, Set<Language> languageList) { Collection<CDOMReference<Language>> racemods = cdo .getListMods(Language.STARTING_LIST); if (racemods != null) { for (CDOMReference<Language> ref : racemods) { languageList.addAll(ref.getContainedObjects()); } } } /** * Retrieve the bonus for the stat excluding either temporary bonuses, * equipment bonuses or both. This method ensure stacking rules are applied * to all included bonuses. If not excluding either, it is quicker to use * getTotalBonusTo. * * @param statAbbr * The short name of the stat to calculate the bonus for. * @param useTemp * Should temp bonuses be included? * @param useEquip * Should equipment bonuses be included? * @return The bonus to the stat. */ public int getPartialStatBonusFor(String statAbbr, boolean useTemp, boolean useEquip) { // List<BonusObj> abl = getAllActiveBonuses(); List<BonusObj> abl = getActiveBonusList(); final String prefix = "STAT." + statAbbr; Map<String, String> bonusMap = new HashMap<String, String>(); for (BonusObj bonus : abl) { if (bonus.isApplied() && bonus.getBonusName().equals("STAT")) { boolean found = false; for (Object element : bonus.getBonusInfoList()) { if (element instanceof PCStat && ((PCStat) element).getAbb().equals(statAbbr)) { found = true; break; } //TODO: This should be put into a proper object when parisng. if (element instanceof MissingObject) { String name = ((MissingObject) element).getObjectName(); if (("%LIST".equals(name) || "LIST".equals(name)) && bonus.getCreatorObject() instanceof PObject) { PObject creator = (PObject) bonus.getCreatorObject(); for (AssociatedChoice<String> assoc : creator.getAssociatedList()) { if (assoc.getChoices().contains(statAbbr)) { found = true; break; } } } } } if (!found) { continue; } // The bonus has been applied to the target stat // Should it be included? boolean addIt = false; if (bonus.getCreatorObject() instanceof Equipment || bonus.getCreatorObject() instanceof EquipmentModifier) { addIt = useEquip; } else if (tempBonusList.contains(bonus)) { addIt = useTemp; } else { addIt = true; } if (addIt) { // Grab the list of relevant types so that we can build up // the // bonuses with the stacking rules applied. for (BonusPair bp : bonus.getStringListFromBonus()) { if (bp.bonusKey.startsWith(prefix)) { setActiveBonusStack(bp.resolve(this).doubleValue(), bp.bonusKey, bonusMap); } } } } } // Sum the included bonuses to the stat to get our result. int total = 0; for (String bKey : bonusMap.keySet()) { total += Float.parseFloat(bonusMap.get(bKey)); } return total; } /** * Retrieve the stat as it was at a particular level excluding either * temporary bonuses, equipment bonuses or both. This method ensures * stacking rules are applied to all included bonuses. If not excluding * either, it is quicker to use getTotalStatAtLevel. * * @param statAbb * The short name of the stat to calculate the value of. * @param level * The level we want to see the stat at. * @param usePost * Should stat mods that occurred after levelling be included? * @param useTemp * Should temp bonuses be included? * @param useEquip * Should equipment bonuses be included? * @return The stat as it was at the level */ public int getPartialStatAtLevel(String statAbb, int level, boolean usePost, boolean useTemp, boolean useEquip) { int curStat = getStatList().getPartialStatFor(statAbb, useTemp, useEquip); for (int idx = getLevelInfoSize() - 1; idx >= level; --idx) { final int statLvlAdjust = pcLevelInfo.get(idx).getTotalStatMod(statAbb, usePost); curStat -= statLvlAdjust; } return curStat; } /** * Returns a deep copy of the PlayerCharacter. Note: This method does a * shallow copy of many lists in here that seem to point to "system" * objects. These copies should be validated before using this method. * * @return a new deep copy of the <code>PlayerCharacter</code> */ @Override public Object clone() { PlayerCharacter aClone = null; // calling super.clone won't work because it will not create // new data instances for all the final variables and I won't // be able to reset them. Need to call new PlayerCharacter() // aClone = (PlayerCharacter)super.clone(); aClone = new PlayerCharacter(); aClone.addArmorProfs(getArmorProfList()); for (final Ability a : this.getRealAbilitiesList(AbilityCategory.FEAT)) { aClone.addRealAbility(AbilityCategory.FEAT, (a.clone())); } for (final AbilityCategory cat : theAbilities.getKeySet()) { for (final Ability a : getRealAbilityList(cat)) { aClone.addRealAbility(cat, a.clone()); } } aClone.miscList.addAll(getMiscList()); for (NoteItem n : getNotesList()) { aClone.addNotesItem(n); } aClone.primaryWeapons.addAll(getPrimaryWeapons()); aClone.secondaryWeapons.addAll(getSecondaryWeapons()); aClone.shieldProfList.addAll(getShieldProfList()); final List<Skill> skillList = new ArrayList<Skill>(getSkillList()); for (Skill skill : skillList) { aClone.skillList.add((skill.clone())); } aClone.specialAbilityList.addAll(getSpecialAbilityList()); aClone.templateList.addAll(getTemplateList()); for (String s : this.variableList) { aClone.variableList.add(s); } aClone.gold = new BigDecimal(gold.toString()); // Points to a global deity object so it doesn't need to be cloned. aClone.deity = deity; aClone.domainSourceMap = new HashMap<String, String>(domainSourceMap); aClone.characterDomainList.addAll(characterDomainList); for (PCClass pcClass : classList) { aClone.classList.add((pcClass.clone())); } aClone.companionModList.addAll(companionModList); aClone.qualifyArrayMap.addAllLists(qualifyArrayMap); if (followerMaster != null) { aClone.followerMaster = followerMaster.clone(); } else { aClone.followerMaster = null; } for (EquipSet eqSet : equipSetList) { aClone.addEquipSet((EquipSet) eqSet.clone()); } aClone.equipmentList.addAll(equipmentList); aClone.equipmentMasterList.addAll(equipmentMasterList); for (PCLevelInfo info : pcLevelInfo) { aClone.pcLevelInfo.add(info.clone()); } for (String book : spellBooks) { aClone.addSpellBook(new String(book)); } aClone.tempBonusItemList.addAll(tempBonusItemList); aClone.tempBonusList.addAll(tempBonusList); aClone.tempBonusFilters.addAll(tempBonusFilters); aClone.race = race; aClone.selectedFavoredClass = selectedFavoredClass; aClone.statList.clear(); for (PCStat stat : statList) { aClone.statList.addStat(stat.clone()); } if (kitList != null) { aClone.kitList = new ArrayList<Kit>(); aClone.kitList.addAll(kitList); } // Not sure what this is. It may need to be cloned. aClone.spellTracker = spellTracker; aClone.templateAutoLanguages.addAll(templateAutoLanguages); aClone.templateLanguages.addAll(templateLanguages); aClone.setBio(new String(getBio())); aClone.setBirthday(new String(getBirthday())); aClone.setBirthplace(new String(getBirthplace())); aClone.setCatchPhrase(new String(getCatchPhrase())); aClone.setCurrentEquipSetName(new String(getCurrentEquipSetName())); aClone.setDescription(new String(getDescription())); aClone.setDescriptionLst(new String(getDescriptionLst())); aClone.setEyeColor(new String(getEyeColor())); aClone.setFileName(new String(getFileName())); aClone.setGender(new String(getGender())); aClone.setHairColor(new String(getHairColor())); aClone.setHairStyle(new String(getHairStyle())); aClone.setHanded(new String(getHanded())); aClone.setInterests(new String(getInterests())); aClone.setLocation(new String(getLocation())); aClone.setName(new String(getName())); aClone.setPhobias(new String(getPhobias())); aClone.setPlayersName(new String(getPlayersName())); aClone.setPortraitPath(new String(getPortraitPath())); if (getRegion() != null) { aClone.setRegion(new String(getRegion())); } aClone.setResidence(new String(getResidence())); aClone.setSkinColor(new String(getSkinColor())); aClone.setSpeechTendency(new String(getSpeechTendency())); if (getSubRegion() != null) { aClone.setSubRegion(new String(getSubRegion())); } aClone.tabName = new String(tabName); aClone.setTrait1(new String(getTrait1())); aClone.setTrait2(new String(getTrait2())); aClone.languages.addAll(languages); if (theWeaponProfs != null) { aClone.theWeaponProfs = new TreeSet<WeaponProf>(); aClone.theWeaponProfs.addAll(theWeaponProfs); } aClone.autoKnownSpells = autoKnownSpells; aClone.autoLoadCompanion = autoLoadCompanion; aClone.autoSortGear = autoSortGear; aClone.outputSheetHTML = new String(outputSheetHTML); aClone.outputSheetPDF = new String(outputSheetPDF); aClone.ageSetKitSelections = new boolean[10]; System.arraycopy(ageSetKitSelections, 0, aClone.ageSetKitSelections, 0, ageSetKitSelections.length); aClone.serial = serial; // Not sure what this is for aClone.displayUpdate = displayUpdate; aClone.setImporting(false); aClone.useTempMods = useTempMods; aClone.setFeats(feats); aClone.age = age; aClone.alignment = alignment; aClone.costPool = costPool; aClone.currentEquipSetNumber = currentEquipSetNumber; aClone.earnedXP = earnedXP; aClone.equipOutputOrder = equipOutputOrder; aClone.freeLangs = freeLangs; aClone.heightInInches = heightInInches; aClone.poolAmount = poolAmount; // order in which the skills will be output. aClone.skillsOutputOrder = skillsOutputOrder; aClone.spellLevelTemp = spellLevelTemp; aClone.weightInPounds = weightInPounds; // Is this OK? aClone.variableProcessor = new VariableProcessorPC(aClone); aClone.pointBuyPoints = pointBuyPoints; aClone.setDirty(true); // TODO - ABILITYOBJECT // aClone.setAggregateAbilitiesStable(null, false); aClone.setAutomaticFeatsStable(false); aClone.setVirtualFeatsStable(false); aClone.adjustMoveRates(); aClone.calcActiveBonuses(); return aClone; } /** * Set the string for the characteristic * * @param key * @param s */ public void setStringFor(StringKey key, String s) { stringChar.put(key, s); setDirty(true); } /** * Remove the string for the characteristic * * @param key * @return string removed */ public String removeStringFor(StringKey key) { return stringChar.remove(key); } private Float getEquippedQty(EquipSet eSet, Equipment eqI) { final String rPath = eSet.getIdPath(); for (EquipSet es : getEquipSet()) { String esIdPath = es.getIdPath() + EquipSet.PATH_SEPARATOR; String rIdPath = rPath + EquipSet.PATH_SEPARATOR; if (!esIdPath.startsWith(rIdPath)) { continue; } if (eqI.getName().equals(es.getValue())) { return es.getQty(); } } return Float.valueOf(0); } /** * This method gets a list of locations for a weapon * * @param hands * @param multiHand * @return weapon location choices */ private static List<String> getWeaponLocationChoices(final int hands, final String multiHand) { final List<String> result = new ArrayList<String>(hands + 2); if (hands > 0) { result.add(Constants.S_PRIMARY); for (int i = 1; i < hands; ++i) { if (i > 1) { result.add(Constants.S_SECONDARY + ' ' + i); } else { result.add(Constants.S_SECONDARY); } } if (multiHand.length() > 0) { result.add(multiHand); } } return result; } /** * If an item can only go in one location, return the name of that location * to add to an EquipSet * * @param eqI * @return single location */ private String getSingleLocation(Equipment eqI) { // Handle natural weapons if (eqI.isNatural()) { if (eqI.getSlots(this) == 0) { // TODO - Yuck. This should not look at the name!! if (eqI.modifiedName().endsWith("Primary")) { return Constants.S_NATURAL_PRIMARY; } return Constants.S_NATURAL_SECONDARY; } } // Always force weapons to go through the chooser dialog // unless they are also armor (ie: with Armor Spikes) if ((eqI.isWeapon()) && !(eqI.isArmor())) { return Constants.EMPTY_STRING; } List<EquipSlot> eqSlotList = SystemCollections.getUnmodifiableEquipSlotList(); if ((eqSlotList == null) || eqSlotList.isEmpty()) { return Constants.EMPTY_STRING; } for (EquipSlot es : eqSlotList) { // see if this EquipSlot can contain this item TYPE if (es.canContainType(eqI.getType())) { return es.getSlotName(); } } return Constants.EMPTY_STRING; } /** * Returns a list of String locations the sepcified Equipment can be * equipped to. * * @param eqSet * @param eqI * @param containers * @return */ public List<String> getEquippableLocations(EquipSet eqSet, Equipment eqI, List<String> containers) { // Some Equipment locations are based on the number of hands int hands = getHands(); List<String> aList = new ArrayList<String>(); if (eqI.isWeapon()) { if (eqI.isUnarmed()) { aList.add(Constants.S_UNARMED); } else if (eqI.isShield()) { aList.add(Constants.S_SHIELD); } else if (eqI.isWeaponOutsizedForPC(this)) { // do nothing for outsized weapons } else { if (eqI.isWeaponOneHanded(this)) { aList = getWeaponLocationChoices(hands, Constants.S_BOTH); } else { aList.add(Constants.S_BOTH); } if (eqI.isMelee() && eqI.isDouble()) { aList.add(Constants.S_DOUBLE); } } } else { String locName = getSingleLocation(eqI); if (locName.length() != 0) { aList.add(locName); } else { aList.add(Constants.S_EQUIPPED); } } if (!eqI.isUnarmed()) { aList.add(Constants.S_CARRIED); aList.add(Constants.S_NOTCARRIED); } // // Generate a list of containers // if (containers != null) { if (eqSet != null) { final String idPath = eqSet.getIdPath(); // process all EquipSet Items for (EquipSet es : getEquipSet()) { String esID = es.getParentIdPath() + EquipSet.PATH_SEPARATOR; String abID = idPath + EquipSet.PATH_SEPARATOR; if (esID.startsWith(abID)) { Equipment eq = es.getItem(); if ((eq != null) && eq.isContainer()) { containers.add(eq.getName()); } } } } } return aList; } /** * returns true if you can put Equipment into a location in EquipSet * * @param eSet * @param locName * @param eqI * @param eqTarget * @return true if equipment can be added */ public boolean canEquipItem(EquipSet eSet, String locName, Equipment eqI, Equipment eqTarget) { final String idPath = eSet.getIdPath(); // If target is a container, allow it if ((eqTarget != null) && eqTarget.isContainer()) { // TODO - Should make sure eqI can be contained by eqTarget return true; } // If Carried/Equipped/Not Carried slot // allow as many as they would like if (locName.startsWith(Constants.S_CARRIED) || locName.startsWith(Constants.S_EQUIPPED) || locName.startsWith(Constants.S_NOTCARRIED)) { return true; } // allow as many unarmed items as you'd like if (eqI.isUnarmed()) { return true; } // allow many Secondary Natural weapons if (locName.equals(Constants.S_NATURAL_SECONDARY)) { return true; } // Don't allow weapons that are too large for PC if (eqI.isWeapon() && eqI.isWeaponOutsizedForPC(this) && !eqI.isNatural()) { return false; } // make a HashMap to keep track of the number of each // item that is already equipped to a slot Map<String, String> slotMap = new HashMap<String, String>(); for (EquipSet es : getEquipSet()) { String esID = es.getParentIdPath() + EquipSet.PATH_SEPARATOR; String abID = idPath + EquipSet.PATH_SEPARATOR; if (!esID.startsWith(abID)) { continue; } // check to see if we already have // an item in that particular location if (es.getName().equals(locName)) { final Equipment eItem = es.getItem(); final String nString = slotMap.get(locName); int existNum = 0; if (nString != null) { existNum = Integer.parseInt(nString); } if (eItem != null) { existNum += eItem.getSlots(this); } slotMap.put(locName, String.valueOf(existNum)); } } for (EquipSet es : getEquipSet()) { String esID = es.getParentIdPath() + EquipSet.PATH_SEPARATOR; String abID = idPath + EquipSet.PATH_SEPARATOR; if (!esID.startsWith(abID)) { continue; } // if it's a weapon we have to do some // checks for hands already in use if (eqI.isWeapon()) { // weapons can never occupy the same slot if (es.getName().equals(locName)) { return false; } // if Double Weapon or Both Hands, then no // other weapon slots can be occupied if ((locName.equals(Constants.S_BOTH) || locName .equals(Constants.S_DOUBLE)) && (es.getName().equals(Constants.S_PRIMARY) || es.getName().equals(Constants.S_SECONDARY) || es.getName().equals(Constants.S_BOTH) || es .getName().equals(Constants.S_DOUBLE))) { return false; } // inverse of above case if ((locName.equals(Constants.S_PRIMARY) || locName .equals(Constants.S_SECONDARY)) && (es.getName().equals(Constants.S_BOTH) || es.getName() .equals(Constants.S_DOUBLE))) { return false; } } // If we already have an item in that location // check to see how many are allowed in that slot if (es.getName().equals(locName)) { final String nString = slotMap.get(locName); int existNum = 0; if (nString != null) { existNum = Integer.parseInt(nString); } existNum += eqI.getSlots(this); EquipSlot eSlot = Globals.getEquipSlotByName(locName); if (eSlot == null) { return true; } // if the item takes more slots, return false if (existNum > (eSlot.getSlotCount() + (int) getTotalBonusTo( "SLOTS", eSlot.getContainType()))) // if (existNum > (eSlot.getSlotCount() + (int) // getBonusValue("SLOTS", eSlot.getContainType()))) { return false; } return true; } } return true; } /** * Checks to see if Equipment exists in selected EquipSet and if so, then * return the EquipSet containing eqI * * @param eSet * @param eqI * @return EquipSet */ public EquipSet getEquipSetForItem(EquipSet eSet, Equipment eqI) { final String rPath = eSet.getIdPath(); for (EquipSet es : getEquipSet()) { String esIdPath = es.getIdPath() + EquipSet.PATH_SEPARATOR; String rIdPath = rPath + EquipSet.PATH_SEPARATOR; if (!esIdPath.startsWith(rIdPath)) { continue; } if (eqI.getName().equals(es.getValue())) { return es; } } return null; } /** * returns new id_Path with the last id one higher than the current highest * id for EquipSets with the same ParentIdPath * * @param eSet * @return new id path */ private String getNewIdPath(EquipSet eSet) { String pid = EquipSet.ROOT_ID; int newID = 0; if (eSet != null) { pid = eSet.getIdPath(); } for (EquipSet es : getEquipSet()) { if (es.getParentIdPath().equals(pid) && (es.getId() > newID)) { newID = es.getId(); } } ++newID; return pid + EquipSet.PATH_SEPARATOR + newID; } public EquipSet addEquipToTarget(final EquipSet eSet, final Equipment eqTarget, String locName, final Equipment eqI, Float newQty) { float tempQty = 1.0f; if (newQty != null) { tempQty = newQty.floatValue(); } else { newQty = Float.valueOf(tempQty); } boolean addAll = false; boolean mergeItem = false; Equipment masterEq = getEquipmentNamed(eqI.getName()); if (masterEq == null) { return null; } float diffQty = masterEq.getQty().floatValue() - getEquippedQty(eSet, eqI).floatValue(); // if newQty is less than zero, we want to // add all of this item to the EquipSet // or all remaining items that havn't already // been added to the EquipSet if (newQty.floatValue() < 0.0f) { tempQty = diffQty; newQty = new Float(tempQty + getEquippedQty(eSet, eqI).floatValue()); addAll = true; } // Check to make sure this EquipSet does not exceed // the PC's equipmentList number for this item if (tempQty > diffQty) { return null; } // check to see if the target item is a container if ((eqTarget != null) && eqTarget.isContainer()) { // set these to newQty just for testing eqI.setQty(newQty); eqI.setNumberCarried(newQty); // Make sure the container accepts items // of this type and is not full if (eqTarget.canContain(this, eqI) == 1) { locName = eqTarget.getName(); addAll = true; mergeItem = true; } else { return null; } } // If locName is empty equip this item to its default location. // If there is more than one option return with an error. if (locName == null || locName.length() == 0) { locName = getSingleLocation(eqI); if (locName.length() == 0) { return null; } } // make sure we can add item to that slot in this EquipSet if (!canEquipItem(eSet, locName, eqI, eqTarget)) { return null; } if (eqI.isContainer()) { // don't merge containers mergeItem = false; } EquipSet existingSet = getEquipSetForItem(eSet, eqI); if (addAll && mergeItem && (existingSet != null)) { newQty = new Float(tempQty + getEquippedQty(eSet, eqI).floatValue()); existingSet.setQty(newQty); eqI.setQty(newQty); eqI.setNumberCarried(newQty); setDirty(true); if ((eqTarget != null) && eqTarget.isContainer()) { eqTarget.updateContainerContentsString(this); } return existingSet; } if ((eqTarget != null) && eqTarget.isContainer()) { eqTarget.insertChild(this, eqI); eqI.setParent(eqTarget); } // construct the new IdPath // new id is one larger than any // other id at this path level String id = getNewIdPath(eSet); // now create a new EquipSet to add // this Equipment item to EquipSet newSet = new EquipSet(id, locName, eqI.getName(), eqI); // set the Quantity of equipment eqI.setQty(newQty); newSet.setQty(newQty); addEquipSet(newSet); setDirty(true); return newSet; } /** * Get the String for a characteristic * * @param key * @return String */ public String getStringFor(StringKey key) { return stringChar.get(key); } /** * Gets a 'safe' String representation * * @param key * @return a 'safe' String */ public String getSafeStringFor(StringKey key) { String s = stringChar.get(key); if (s == null) { s = Constants.EMPTY_STRING; } return s; } /** * Sets if ADD: level abilities should be processed when incrementing a * level. * * <p> * <b>Note</b>: This is kind of a hack used by the Kit code to allow a kit * to specify what the level abilities are. * * @param yesNo * Yes if level increases should process ADD: level abilities. */ public void setDoLevelAbilities(boolean yesNo) { processLevelAbilities = yesNo; } /** * Returns if level increases will process ADD: level abilities. * * @return <tt>true</tt> if ADD: level abilites will be processed. */ public boolean doLevelAbilities() { return processLevelAbilities; } /** * Whether to allow adjustment of the Global Feat pool * * @param allow */ public void setAllowFeatPoolAdjustment(boolean allow) { this.allowFeatPoolAdjustment = allow; } // /** // * Returns a list of Spell-Like Abilities for the character. // * // * <p>The list is sorted by Category, Frequency (most to least), and // * Spell name. // * // * @return A Sorted Set of Spell-Like Abilities. // */ // public Collection<SpellLikeAbility> getSpellLikeAbilities() // { // final SortedSet<SpellLikeAbility> ret = new TreeSet<SpellLikeAbility>(new // Comparator<SpellLikeAbility>() { // // public int compare(final SpellLikeAbility anO1, final SpellLikeAbility // anO2) // { // final Collator collator = Collator.getInstance(); // // Sort order is Category, Frequency (most -> least), Spell Name // int iRet = collator.compare( anO1.getCategory(), anO2.getCategory() ); // if ( iRet == 0 ) // { // final int freq1 = getVariableValue(anO1.getNumUses(), // this.getClass().getName()).intValue(); // final int freq2 = getVariableValue(anO2.getNumUses(), // this.getClass().getName()).intValue(); // // TODO - Handle Units // if ( freq1 == freq2 ) // { // final Spell spell1 = Globals.getSpellKeyed(anO1.getSpellKey()); // final Spell spell2 = Globals.getSpellKeyed(anO2.getSpellKey()); // if ( spell1 != null ) // { // if ( spell2 != null ) // { // return collator.compare( spell1.getDisplayName(), spell2.getDisplayName() // ); // } // else // { // return 1; // } // } // else if ( spell2 != null ) // { // return -1; // } // else // { // return 0; // } // } // return freq1 > freq2 ? 1 : -1; // } // // return iRet; // } // // }); // for ( final PObject pobj : getPObjectList() ) // { // ret.addAll( pobj.getSpellLikeAbilities() ); // } // // return ret; // } // // /** // * Gets a <tt>Collection</tt> of category names for the Spell-Like // Abilities // * for the character. // * // * @return List of category names. // */ // public List<String> getSpellLikeAbilityCategories() // { // final Collection<SpellLikeAbility> slas = this.getSpellLikeAbilities(); // final List<String> ret = new ArrayList<String>(); // // String currentCategory = Constants.EMPTY_STRING; // for ( final SpellLikeAbility sla : slas ) // { // if ( !currentCategory.equals(sla.getCategory()) ) // { // currentCategory = sla.getCategory(); // ret.add(currentCategory); // } // } // // return ret; // } // // /** // * Returns a <tt>List</tt> of Spell-Like Abilities for the specified // * category (spellbook). // * // * @param aCategory A category (spellbook) to return SLAs for. // * // * @return An unmodifialbe <tt>List</tt> of <tt>SpellLikeAbility</tt> // * objects. // */ // public List<SpellLikeAbility> getSpellLikeAbilities( final String // aCategory ) // { // final List<SpellLikeAbility> slas = new ArrayList<SpellLikeAbility>(); // // for ( final SpellLikeAbility sla : slas ) // { // if ( sla.getCategory().equals( aCategory ) ) // { // slas.add( sla ); // } // } // return Collections.unmodifiableList( slas ); // } /* * For debugging purposes Dumps contents of spell books to System.err * * static public void dumpSpells(final PlayerCharacter pc) { final List * bookList = pc.getSpellBooks(); for(int bookIdx = 0; bookIdx < * bookList.size(); ++bookIdx) { final String bookName = (String) * pc.getSpellBooks().get(bookIdx); * * System.err.println("=========="); System.err.println("Book:" + bookName); * final List casterList = pc.getSpellClassList(); for(int casterIdx = 0; * casterIdx < casterList.size(); ++casterIdx) { final PObject aCaster = * (PObject) casterList.get(casterIdx); final List spellList = * aCaster.getCharacterSpellList(); if (spellList == null) { continue; } * System.err.println("Class/Race:" + aCaster.getName()); * * for (Iterator i = spellList.iterator(); i.hasNext();) { final * CharacterSpell cs = (CharacterSpell) i.next(); * * for (Iterator csi = cs.getInfoListIterator(); csi.hasNext();) { final * SpellInfo sInfo = (SpellInfo) csi.next(); if * (bookName.equals(sInfo.getBook())) { * System.err.println(cs.getSpell().getOutputName() + sInfo.toString() + " * level:" + Integer.toString(sInfo.getActualLevel())); } } } } } } */ // -------------------------------------------------- // Feat/Ability stuff // -------------------------------------------------- // List of Feats private List<Ability> stableAggregateFeatList = null; // Whether one can trust the most recently calculated aggregateFeatList private boolean aggregateFeatsStable = false; // Whether one can trust the most recently calculated automaticFeatList // private boolean automaticFeatsStable = false; // Whether one can trust the most recently calculated virtualFeatList private boolean virtualFeatsStable = false; // whether to adjust the feat pool when requested private boolean allowFeatPoolAdjustment = true; // pool of feats remaining to distribute private double feats = 0; /** Status flag so that ability lists aren't cleared mid way through being rebuilt. */ private boolean rebuildingAbilities = false; /** * Set aggregate Feats stable * * @param stable */ private void setAggregateFeatsStable(final boolean stable) { setAggregateAbilitiesStable(AbilityCategory.FEAT, stable); } public void setAggregateAbilitiesStable(final AbilityCategory aCategory, final boolean stable) { if (!stable && rebuildingAbilities) { return; } if (!stable) { cachedWeaponProfs = null; } if (aCategory == AbilityCategory.FEAT) { aggregateFeatsStable = stable; setVirtualFeatsStable(stable); setAutomaticFeatsStable(stable); if (!stable) { cachedWeaponProfs = null; } } if (aCategory == null) { if (!stable) { // Clear all the categories for (final AbilityCategory cat : theAbilities.getKeySet()) { // Avoid an infinite loop if there is a faulty entry in the key set if (cat == null) { Logging .errorPrint("Null category entry in character's abilities key set " + String.valueOf(theAbilities.getKeySet())); } else { setAggregateAbilitiesStable(cat, stable); } } } setAggregateFeatsStable(stable); return; } if (!stable) { theAbilities.put(aCategory, Ability.Nature.AUTOMATIC, null); // TODO - Deal with non-aggregate virtual abilities (i.e. from ADD:) theAbilities.put(aCategory, Ability.Nature.VIRTUAL, null); theAbilities.put(aCategory, Ability.Nature.NORMAL, null); } } /** * Returns TRUE if all types (automatic, virtual and aggregate) of feats are * stable * * @return TRUE or FALSE */ private boolean isAggregateFeatsStable() { return (theAbilities .get(AbilityCategory.FEAT, Ability.Nature.AUTOMATIC) != null) && virtualFeatsStable && aggregateFeatsStable; } public boolean isAggregateAbilitiesStable(final AbilityCategory aCategory) { if (aCategory == AbilityCategory.FEAT) { return isAggregateFeatsStable(); } return theAbilities.get(aCategory, Ability.Nature.NORMAL) != null && theAbilities.get(aCategory, Ability.Nature.AUTOMATIC) != null && theAbilities.get(aCategory, Ability.Nature.VIRTUAL) != null; } /** * Sets a 'stable' list of automatic feats * * @param stable */ private void setAutomaticFeatsStable(final boolean stable) { setAutomaticAbilitiesStable(AbilityCategory.FEAT, stable); } public void setAutomaticAbilitiesStable(final AbilityCategory aCategory, final boolean stable) { if (aCategory == null) { if (!stable) { for (final AbilityCategory cat : theAbilities.getKeySet()) { theAbilities.put(cat, Ability.Nature.AUTOMATIC, null); } } setAutomaticFeatsStable(stable); return; } if (!stable) { theAbilities.put(aCategory, Ability.Nature.AUTOMATIC, null); } } public boolean addRealAbility(final AbilityCategory aCategory, final Ability anAbility) { if (anAbility == null) { return false; } anAbility.setFeatType(Ability.Nature.NORMAL); List<Ability> abilities = realAbilities.get(aCategory); if (abilities == null) { abilities = new ArrayList<Ability>(); realAbilities.put(aCategory, abilities); } abilities.add(anAbility); return true; } public void clearRealAbilities(final AbilityCategory aCategory) { if (aCategory == null) { for (final AbilityCategory cat : theAbilities.getKeySet()) { theAbilities.put(cat, Ability.Nature.NORMAL, null); } return; } theAbilities.put(aCategory, Ability.Nature.NORMAL, null); } public int getNumberOfRealAbilities(final AbilityCategory aCategory) { final List<Ability> abilities = theAbilities.get(aCategory, Ability.Nature.NORMAL); if (abilities == null) { return 0; } return abilities.size(); } public HashMap<Ability.Nature, Set<Ability>> getAbilitiesSet() { final Set<AbilityCategory> abCats = theAbilities.getKeySet(); HashMap<Ability.Nature, Set<Ability>> st = new HashMap<Ability.Nature, Set<Ability>>(); st.put(Ability.Nature.AUTOMATIC, new HashSet<Ability>()); st.put(Ability.Nature.NORMAL, new HashSet<Ability>()); st.put(Ability.Nature.VIRTUAL, new HashSet<Ability>()); st.put(Ability.Nature.ANY, new HashSet<Ability>()); if (abCats == null) { return st; } st.get(Ability.Nature.VIRTUAL).addAll( getAbilitySetByNature(Ability.Nature.VIRTUAL)); st.get(Ability.Nature.AUTOMATIC).addAll( getAbilitySetByNature(Ability.Nature.AUTOMATIC)); st.get(Ability.Nature.NORMAL).addAll( getAbilitySetByNature(Ability.Nature.NORMAL)); st.get(Ability.Nature.ANY).addAll(st.get(Ability.Nature.NORMAL)); st.get(Ability.Nature.ANY).addAll(st.get(Ability.Nature.AUTOMATIC)); st.get(Ability.Nature.ANY).addAll(st.get(Ability.Nature.VIRTUAL)); return st; } public List<Ability> getAllAbilities() { Set<AbilityCategory> abCats = theAbilities.getKeySet(); List<Ability> list = new ArrayList<Ability>(); if (abCats == null) { return list; } for (AbilityCategory ac : abCats) { list.addAll(getAutomaticAbilityList(ac)); list.addAll(getRealAbilitiesList(ac)); list.addAll(getVirtualAbilityList(ac)); } return list; } public List<Ability> getRealAbilitiesList(final AbilityCategory aCategory) { List<Ability> abilities = theAbilities.get(aCategory, Ability.Nature.NORMAL); if (abilities != null) { return new ArrayList<Ability>(abilities); } // If the list is null it means it needs to be recalculated. rebuildAggregateAbilityList(); abilities = theAbilities.get(aCategory, Ability.Nature.NORMAL); return new ArrayList<Ability>(abilities); } /** * Get a list of real abiltiies of a particular AbilityCategory * no matter which AbilityCategory list they reside in. * * @param aCategory The AbilityCategory of the desired abilities. * @return List of abilities */ public List<Ability> getRealAbilitiesListAnyCat( final AbilityCategory aCategory) { List<Ability> abilities = new ArrayList<Ability>(); for (AbilityCategory cat : SettingsHandler.getGame() .getAllAbilityCategories()) { for (Ability ability : getRealAbilitiesList(cat)) { if (aCategory.getKeyName().equals(ability.getCategory())) { abilities.add(ability); } } } return abilities; } /** * Get an iterator over all the feats "Real" feats For Example, not virtual * or auto * * @return an iterator */ public List<Ability> getRealFeatList() { return getRealAbilitiesList(AbilityCategory.FEAT); } /** * @deprecated Use getRealAbilitiesList instead. * @param aCategory * @return */ @Deprecated public List<Ability> getRealAbilityList(final AbilityCategory aCategory) { return getRealAbilitiesList(aCategory); } /** * Returns the Feat definition searching by key (not name), as contained in * the <b>chosen</b> feat list. * * @param featName * String key of the feat to check for. * * @return the Feat (not the CharacterFeat) searched for, <code>null</code> * if not found. */ public Ability getRealFeatKeyed(final String featName) { return getRealAbilityKeyed(AbilityCategory.FEAT, featName); } public Ability getRealAbilityKeyed(final AbilityCategory aCategory, final String aKey) { final List<Ability> abilities = getRealAbilitiesList(aCategory); if (abilities != null) { for (final Ability ability : abilities) { if (ability.getKeyName().equals(aKey)) { return ability; } } } return null; } /** * Returns the Feat definition searching by name, as contained in the <b> * chosen</b> feat list. * * @param featName * String key of the feat to check for. * * @return the Feat (not the CharacterFeat) searched for, <code>null</code> * if not found. */ public Ability getRealFeatNamed(final String featName) { return AbilityUtilities.getAbilityFromList(realAbilities .get(AbilityCategory.FEAT), "FEAT", featName, Ability.Nature.ANY); } /** * Does the character have this feat (not virtual or auto). * * @param aFeat * The Ability object (of category FEAT) to check * * @return True if the character has the feat */ public boolean hasRealFeat(final Ability aFeat) { return hasRealAbility(AbilityCategory.FEAT, aFeat); } /** * Does the character have this ability (not virtual or auto). * * @param aCategory * The ability category to check. * @param anAbility * The Ability object (of category FEAT) to check * * @return True if the character has the feat */ public boolean hasRealAbility(final AbilityCategory aCategory, final Ability anAbility) { final List<Ability> abilities = theAbilities.get(aCategory, Ability.Nature.NORMAL); if (abilities == null) { return false; } return abilities.contains(anAbility); } /** * Checks if the character has the specified ability. * * <p> * If <tt>aCategory</tt> is <tt>null</tt> then all categories that have * the same innate ability category will be checked. * <p> * If <tt>anAbilityType</tt> is <tt>ANY</tt> then all Natures will be * checked for the ability. * * @param aCategory * An <tt>AbilityCategory</tt> or <tt>null</tt> * @param anAbilityType * A <tt>Nature</tt>. * @param anAbility * The <tt>Ability</tt> to check for. * * @return <tt>true</tt> if the character has the ability with the * criteria specified. */ public boolean hasAbility(final AbilityCategory aCategory, final Ability.Nature anAbilityType, final Ability anAbility) { final List<AbilityCategory> categories; if (aCategory == null) { // A null category means we have to check all categories for // abilities of the same innate category as the passed in one. categories = new ArrayList<AbilityCategory>(); final Collection<AbilityCategory> allCategories = SettingsHandler.getGame().getAllAbilityCategories(); for (final AbilityCategory cat : allCategories) { if (cat.getAbilityCategory().equals(anAbility.getCategory())) { categories.add(cat); } } } else { categories = new ArrayList<AbilityCategory>(); categories.add(aCategory); } final int start, end; if (anAbilityType == Ability.Nature.ANY) { start = 0; end = Ability.Nature.values().length - 2; } else { start = end = anAbilityType.ordinal(); } for (int i = start; i <= end; i++) { final Ability.Nature nature = Ability.Nature.values()[i]; boolean hasIt = false; for (final AbilityCategory cat : categories) { switch (nature) { case NORMAL: hasIt = hasRealAbility(cat, anAbility); break; case AUTOMATIC: hasIt = hasAutomaticAbility(cat, anAbility); break; case VIRTUAL: hasIt = hasVirtualAbility(cat, anAbility); break; } if (hasIt) { return true; } } } return false; } /** * Check if the characterFeat ArrayList contains the named Feat. * * @param featName * String name of the feat to check for. * @return <code>true</code> if the character has the feat, * <code>false</code> otherwise. */ public boolean hasRealFeatNamed(final String featName) { final List<Ability> abilities = theAbilities.get(AbilityCategory.FEAT, Ability.Nature.NORMAL); if (abilities == null) { return false; } return AbilityUtilities.getAbilityFromList(abilities, "FEAT", featName, Ability.Nature.ANY) != null; } /** * Remove a "real" (for example, not virtual or auto) feat from the * character. * * @param aFeat * the Ability (of category FEAT) to remove * @return True if successfully removed */ public boolean removeRealFeat(final Ability aFeat) { return removeRealAbility(AbilityCategory.FEAT, aFeat); } public boolean removeRealAbility(final AbilityCategory aCategory, final Ability anAbility) { final List<Ability> abilities = realAbilities.get(aCategory); if (abilities == null) { return false; } return abilities.remove(anAbility); } /** * Retrieve the list of directly added virtual abilities of a particular * category. * @param aCategory The category of the abilities. * @return The list of abilities. */ public List<Ability> getDirectVirtualAbilities( final AbilityCategory aCategory) { List<Ability> aList = virtualAbilities.get(aCategory); if (aList == null) { aList = new ArrayList<Ability>(); virtualAbilities.put(aCategory, aList); } return aList; } /** * Set the list of directly added virtual abilities of a particular * category. * @param aCategory The category of the abilities. * @param aList The list of abilities to set. */ public void setDirectVirtualAbilities(final AbilityCategory aCategory, List<Ability> aList) { virtualAbilities.put(aCategory, aList); } public void adjustFeats(final double arg) { if (allowFeatPoolAdjustment) { feats += arg; } setDirty(true); } public void adjustAbilities(final AbilityCategory aCategory, final BigDecimal arg) { if (arg.equals(BigDecimal.ZERO)) { return; } if (aCategory == AbilityCategory.FEAT) { adjustFeats(arg.doubleValue()); return; } if (theUserPoolBonuses == null) { theUserPoolBonuses = new HashMap<AbilityCategory, BigDecimal>(); } BigDecimal userMods = theUserPoolBonuses.get(aCategory); if (userMods != null) { userMods = userMods.add(arg); } else { userMods = arg; } theUserPoolBonuses.put(aCategory, userMods); setDirty(true); } // TODO - This method is ridiculously dangerous. public void setFeats(final double arg) { if (allowFeatPoolAdjustment) { feats = arg; } setDirty(true); } public void setAbilities(final AbilityCategory aCategory, final double arg) { if (aCategory == AbilityCategory.FEAT) { setFeats(arg); return; } // TODO: What about other types of ability pools? } public void setUserPoolBonus(final AbilityCategory aCategory, final BigDecimal anAmount) { if (theUserPoolBonuses == null) { theUserPoolBonuses = new HashMap<AbilityCategory, BigDecimal>(); } theUserPoolBonuses.put(aCategory, anAmount); } public double getUserPoolBonus(final AbilityCategory aCategory) { BigDecimal userBonus = null; if (theUserPoolBonuses != null) { userBonus = theUserPoolBonuses.get(aCategory); } if (userBonus == null) { return 0.0d; } return userBonus.doubleValue(); } public BigDecimal getTotalAbilityPool(final AbilityCategory aCategory) { if (aCategory == AbilityCategory.FEAT) { BigDecimal spent = getAbilityPoolSpent(aCategory); return spent.add(new BigDecimal(getFeats())); } Float basePool = this.getVariableValue(aCategory.getPoolFormula(), getClass() .toString()); if (!aCategory.allowFractionalPool()) { basePool = new Float(basePool.intValue()); } double bonus = getTotalBonusTo("ABILITYPOOL", aCategory.getKeyName()); // double bonus = getBonusValue("ABILITYPOOL", aCategory.getKeyName()); if (!aCategory.allowFractionalPool()) { bonus = Math.floor(bonus); } // User bonuses already handle the fractional pool flag. final double userBonus = getUserPoolBonus(aCategory); return BigDecimal.valueOf(basePool + bonus + userBonus); } public List<Ability> getSelectedAbilities(final AbilityCategory aCategory) { return getRealAbilitiesList(aCategory); } public double getFeats() { if (Globals.getGameModeHasPointPool()) { return getSkillPoints(); } return getRawFeats(true); } public BigDecimal getAvailableAbilityPool(final AbilityCategory aCategory) { if (aCategory == AbilityCategory.FEAT) { return BigDecimal.valueOf(getFeats()); } return getTotalAbilityPool(aCategory).subtract( getAbilityPoolSpent(aCategory)); } public double getRawFeats(final boolean bIncludeBonus) { double retVal = feats; if (bIncludeBonus) { retVal += getBonusFeatPool(); } return retVal; } /** * Query whether this PC should be able to select the ability passed in. * That is, does the PC meet the prerequisites and is the feat not one the * PC already has, or if the PC has the feat already, is it one that can be * taken multiple times. * * @param anAbility * the ability to test * @return true if the PC can take, false otherwise */ public boolean canSelectAbility(final Ability anAbility) { return this.canSelectAbility(anAbility, false); } /** * Query whether this PC should be able to select the ability passed in. * That is, does the PC meet the prerequisites and is the feat not one the * PC already has, or if the PC has the feat already, is it one that can be * taken multiple times. TODO: When the PlayerCharacter Object can have * abilities of category other than "FEAT" it will likely have methods to * test "hasRealAbility" and "hasVirtualAbilty", change this (or add * another) to deal with them * * @param anAbility * the ability to test * @param autoQualify * if true, the PC automatically meets the prerequisites * @return true if the PC can take, false otherwise */ public boolean canSelectAbility(final Ability anAbility, final boolean autoQualify) { final boolean qualify = this.qualifiesForFeat(anAbility); final boolean canTakeMult = anAbility.getSafe(ObjectKey.MULTIPLE_ALLOWED); final boolean hasOrdinary = this.hasRealFeat(anAbility); final boolean hasAuto = this.hasFeatAutomatic(anAbility.getKeyName()); final boolean notAlreadyHas = !(hasOrdinary || hasAuto); return (autoQualify || qualify) && (canTakeMult || notAlreadyHas); } /** * get unused feat count * * @return unused feat count */ public double getUsedFeatCount() { double iCount = 0; List<Ability> abilities = realAbilities.get(AbilityCategory.FEAT); if (abilities == null) { return 0; } for (Ability aFeat : abilities) { // // Don't increment the count for // hidden feats so the number // displayed matches this number // if (aFeat.getSafe(ObjectKey.VISIBILITY) == Visibility.HIDDEN || aFeat.getSafe(ObjectKey.VISIBILITY) == Visibility.OUTPUT_ONLY) { continue; } final int subfeatCount = aFeat.getAssociatedCount(); double cost = aFeat.getSafe(ObjectKey.SELECTION_COST).doubleValue(); int select = getVariableValue(aFeat.getSelectCount(), "").intValue(); double relativeCost = cost / select; if (aFeat.getChoiceString() != null && aFeat.getChoiceString().length() > 0) { iCount += Math.ceil(subfeatCount * relativeCost); } else { if (!AbilityCategory.FEAT.allowFractionalPool()) { iCount += (int) Math.ceil(relativeCost); } else { iCount += relativeCost; } } } return iCount; } public BigDecimal getAbilityPoolSpent(final AbilityCategory aCategory) { if (aCategory == AbilityCategory.FEAT) { return BigDecimal.valueOf(getUsedFeatCount()); } double spent = 0.0d; final List<Ability> abilities = getSelectedAbilities(aCategory); if (abilities != null) { for (final Ability ability : abilities) { final int subfeatCount = ability.getAssociatedCount(); double cost = ability.getSafe(ObjectKey.SELECTION_COST).doubleValue(); int select = getVariableValue(ability.getSelectCount(), "") .intValue(); double relativeCost = cost / select; if (ability.getChoiceString() != null && ability.getChoiceString().length() > 0) { spent += Math.ceil(subfeatCount * relativeCost); } else { if (!aCategory.allowFractionalPool()) { spent += (int) Math.ceil(relativeCost); } else { spent += relativeCost; } } } } if (!aCategory.allowFractionalPool()) { return BigDecimal.valueOf((int) Math.ceil(spent)); } return BigDecimal.valueOf(spent); } public double getUsedAbilityCount(final AbilityCategory aCategory) { if (aCategory == AbilityCategory.FEAT) { return getUsedFeatCount(); } return getAbilityPoolSpent(aCategory).doubleValue(); } private void setVirtualFeatsStable(final boolean stable) { virtualFeatsStable = stable; // setDirty(true); } public void setVirtualAbilitiesStable(final AbilityCategory aCategory, final boolean stable) { if (aCategory == AbilityCategory.FEAT) { setVirtualFeatsStable(stable); return; } if (!stable) { theAbilities.put(aCategory, Ability.Nature.VIRTUAL, null); } } public void addFeat(final Ability aFeat, final PCLevelInfo playerCharacterLevelInfo) { if (hasRealFeat(aFeat)) { Logging.errorPrint("Adding duplicate feat: " + aFeat.getDisplayName()); } if (!addRealAbility(AbilityCategory.FEAT, aFeat)) { Logging .errorPrint("Problem adding feat: " + aFeat.getDisplayName()); } if (playerCharacterLevelInfo != null) { // Add this feat to the level Info playerCharacterLevelInfo.addObject(aFeat); } addNaturalWeapons(aFeat.getNaturalWeapons()); setAggregateFeatsStable(false); calcActiveBonuses(); } public void addAbility(final AbilityCategory aCategory, final Ability anAbility, final PCLevelInfo aLevelInfo) { if (hasRealAbility(aCategory, anAbility)) { Logging.errorPrint("Adding duplicate ability: " + anAbility.getDisplayName()); } if (!addRealAbility(aCategory, anAbility)) { Logging.errorPrint("Problem adding ability: " + anAbility.getDisplayName()); } if (aLevelInfo != null) { // Add this feat to the level Info aLevelInfo.addObject(anAbility); } addNaturalWeapons(anAbility.getNaturalWeapons()); setAggregateAbilitiesStable(aCategory, false); calcActiveBonuses(); } public Ability getFeatAutomaticKeyed(final String aFeatKey) { return getAutomaticAbilityKeyed(AbilityCategory.FEAT, aFeatKey); } public Ability getAutomaticAbilityKeyed(final AbilityCategory aCategory, final String anAbilityKey) { final List<Ability> abilities = getAutomaticAbilityList(aCategory); for (final Ability ability : abilities) { if (ability.getKeyName().equals(anAbilityKey)) { return ability; } } return null; } public Ability getVirtualFeatKeyed(final String aKey) { return AbilityUtilities.getAbilityFromList(getVirtualFeatList(), "FEAT", aKey, Ability.Nature.ANY); } // TODO - Consolidate the various getXXXAbility functions to take a ability // type parameter. public Ability getVirtualAbilityKeyed(final AbilityCategory aCategory, final String aKey) { if (aCategory == AbilityCategory.FEAT) { return getVirtualFeatKeyed(aKey); } final List<Ability> abilities = getVirtualAbilityList(aCategory); for (final Ability ability : abilities) { if (ability.getKeyName().equals(aKey)) { return ability; } } return null; } public int addAbility(final PCLevelInfo LevelInfo, final AbilityCategory aCategory, final String aKey, final boolean addIt, final boolean singleChoice) { boolean singleChoice1 = !singleChoice; if (!isImporting()) { getSpellList(); } final Collection<String> choices = new ArrayList<String>(); final String undoctoredKey = aKey; final String baseKey = AbilityUtilities.getUndecoratedName(aKey, choices); String subKey = choices.size() > 0 ? choices.iterator().next() : Constants.EMPTY_STRING; // See if our choice is not auto or virtual Ability anAbility = getRealAbilityKeyed(aCategory, undoctoredKey); // if a feat keyed aFeatKey doesn't exist, and aFeatKey // contains a (blah) descriptor, try removing it. if (anAbility == null) { anAbility = getRealAbilityKeyed(aCategory, baseKey); if (!singleChoice1 && (anAbility != null) && (subKey.length() != 0)) { singleChoice1 = true; } } // (anAbility == null) means we don't have this feat, so we need to add // it if ((anAbility == null) && addIt) { // Adding feat for first time anAbility = Globals.getAbilityKeyed(aCategory, baseKey); if (anAbility == null) { anAbility = Globals.getAbilityKeyed(aCategory, undoctoredKey); if (anAbility != null) { subKey = Constants.EMPTY_STRING; } } if (anAbility == null) { Logging.errorPrint("Ability not found: " + undoctoredKey); return addIt ? 1 : 0; } anAbility = anAbility.clone(); // addFeat(anAbility, LevelInfo); addAbility(aCategory, anAbility, LevelInfo); selectTemplates(anAbility, isImporting()); } /* * Could not find the Ability: addIt true means that no global Ability * called featName exists, addIt false means that the PC does not have * this ability */ if (anAbility == null) { return addIt ? 1 : 0; } return AbilityUtilities.finaliseAbility(anAbility, subKey, this, addIt, singleChoice1, aCategory); } /** * Returns the Feat definition searching by key (not name), as found in the * <b>aggregate</b> feat list. * * @param featName * String key of the feat to check for. * @return the Feat (not the CharacterFeat) searched for, <code>null</code> * if not found. */ public Ability getFeatKeyed(final String featName) { return getAbilityKeyed(AbilityCategory.FEAT, featName); } public Ability getAbilityKeyed(final AbilityCategory aCategory, final String aKey) { final List<Ability> abilities = getAggregateAbilityList(aCategory); for (final Ability ability : abilities) { if (ability.getKeyName().equals(aKey)) { return ability; } } return null; } /** * Get an ability of any ctageory tat matches the key. * @param aKey The key to search for * @return An ability with the key, or null if none. */ public Ability getAbilityKeyed(final String aKey) { final List<Ability> abilities = getFullAbilityList(); for (final Ability ability : abilities) { if (ability.getKeyName().equals(aKey)) { return ability; } } return null; } /** * Identify if the character has an ability, of any category, that * matches the key. * @param aKey The key to search for * @return True if an ability is found, false otherwise. */ public boolean hasAbilityKeyed(final String aKey) { return getAbilityKeyed(aKey) != null; } public List<Ability> aggregateFeatList() { final List<Ability> aggregate = getStableAggregateFeatList(); // Did we get a valid list? If so, return it. if (aggregate != null) { return aggregate; } return rebuildFeatAggreagateList(); } public List<Ability> getAggregateAbilityList(final AbilityCategory aCategory) { if (aCategory == AbilityCategory.FEAT) { return aggregateFeatList(); } final List<Ability> abilities = new ArrayList<Ability>(getRealAbilitiesList(aCategory)); abilities.addAll(getVirtualAbilityList(aCategory)); abilities.addAll(getAutomaticAbilityList(aCategory)); return abilities; } private List<Ability> rebuildFeatAggreagateList() { List<Ability> aggregate = new ArrayList<Ability>(); final Map<String, Ability> aHashMap = new HashMap<String, Ability>(); for (Ability aFeat : getRealAbilitiesList(AbilityCategory.FEAT)) { if (aFeat != null) { aHashMap.put(aFeat.getKeyName(), aFeat); } } for (Ability vFeat : getVirtualFeatList()) { if (!aHashMap.containsKey(vFeat.getKeyName())) { aHashMap.put(vFeat.getKeyName(), vFeat); } else if (vFeat.getSafe(ObjectKey.MULTIPLE_ALLOWED)) { Ability aggregateFeat = aHashMap.get(vFeat.getKeyName()); aggregateFeat = aggregateFeat.clone(); for (int e1 = 0; e1 < vFeat.getAssociatedCount(); ++e1) { final String aString = vFeat.getAssociated(e1); if (aggregateFeat.getSafe(ObjectKey.STACKS) || !aggregateFeat.containsAssociated(aString)) { aggregateFeat.addAssociated(aString); } } aHashMap.put(vFeat.getKeyName(), aggregateFeat); } } aggregate.addAll(aHashMap.values()); setStableAggregateFeatList(aggregate); for (Ability autoFeat : featAutoList()) { if (!aHashMap.containsKey(autoFeat.getKeyName())) { aHashMap.put(autoFeat.getKeyName(), autoFeat); } else if (autoFeat.getSafe(ObjectKey.MULTIPLE_ALLOWED)) { Ability aggregateFeat = aHashMap.get(autoFeat.getKeyName()); aggregateFeat = aggregateFeat.clone(); for (int e1 = 0; e1 < autoFeat.getAssociatedCount(); ++e1) { final String aString = autoFeat.getAssociated(e1); if (aggregateFeat.getSafe(ObjectKey.STACKS) || !aggregateFeat.containsAssociated(aString)) { aggregateFeat.addAssociated(aString); } } aHashMap.put(autoFeat.getKeyName(), aggregateFeat); } } aggregate = new ArrayList<Ability>(); aggregate.addAll(aHashMap.values()); setStableAggregateFeatList(aggregate); return aggregate; } public List<Ability> aggregateVisibleFeatList() { return getAggregateVisibleAbilityList(AbilityCategory.FEAT); } public List<Ability> getAggregateVisibleAbilityList( final AbilityCategory aCategory) { final List<Ability> abilities = new ArrayList<Ability>(); abilities.addAll(getRealAbilitiesListAnyCat(aCategory)); abilities.addAll(getAutomaticAbilityList(aCategory)); abilities.addAll(getVirtualAbilityList(aCategory)); final List<Ability> ret = new ArrayList<Ability>(abilities.size()); for (final Ability ability : abilities) { if (ability.getSafe(ObjectKey.VISIBILITY) == Visibility.DEFAULT || ability.getSafe(ObjectKey.VISIBILITY) == Visibility.OUTPUT_ONLY) { ret.add(ability); } } return ret; } boolean isAutomaticAbilitiesStable(final AbilityCategory aCategory) { return theAbilities.get(aCategory, Ability.Nature.AUTOMATIC) != null; } public List<Ability> getVirtualFeatList() { return getVirtualAbilityList(AbilityCategory.FEAT); } public Set<Ability> getAbilitySetByNature(Ability.Nature n) { GameMode gm = SettingsHandler.getGame(); Set<AbilityCategory> Sc = new HashSet<AbilityCategory>(); Sc.addAll(gm.getAllAbilityCategories()); Set<Ability> Sa = new HashSet<Ability>(); switch (n) { case AUTOMATIC: for (AbilityCategory Ac : Sc) { Sa.addAll(this.getAutomaticAbilityList(Ac)); } break; case NORMAL: for (AbilityCategory Ac : Sc) { Sa.addAll(this.getRealAbilitiesList(Ac)); } break; case VIRTUAL: for (AbilityCategory Ac : Sc) { Sa.addAll(this.getVirtualAbilityList(Ac)); } break; default: Logging.errorPrint("Attempt to get abilities of Nature: " + n); } return Sa; } /** * Return a set of all abilities no matter what category or * nature that the PC has. * @return Set of all abilities. */ public Set<Ability> getFullAbilitySet() { GameMode gm = SettingsHandler.getGame(); Set<AbilityCategory> catSet = new HashSet<AbilityCategory>(); catSet.addAll(gm.getAllAbilityCategories()); Set<Ability> abilitySet = new HashSet<Ability>(); for (AbilityCategory cat : catSet) { abilitySet.addAll(this.getAggregateAbilityList(cat)); } return abilitySet; } /** * Return a list of all abilities no matter what category or * nature that the PC has. Note: This method allows duplicates, * such as when the same ability has been added by different * categories. * @return List of all abilities. */ public List<Ability> getFullAbilityList() { GameMode gm = SettingsHandler.getGame(); Set<AbilityCategory> catSet = new HashSet<AbilityCategory>(); catSet.addAll(gm.getAllAbilityCategories()); List<Ability> abilityList = new ArrayList<Ability>(); for (AbilityCategory cat : catSet) { abilityList.addAll(this.getAggregateAbilityList(cat)); } return abilityList; } public List<Ability> getVirtualAbilityList(final AbilityCategory aCategory) { List<Ability> abilities = theAbilities.get(aCategory, Ability.Nature.VIRTUAL); if (abilities == null) { rebuildAggregateAbilityList(); abilities = theAbilities.get(aCategory, Ability.Nature.VIRTUAL); } return Collections.unmodifiableList(abilities); } public List<Ability> featAutoList() { return getAutomaticAbilityList(AbilityCategory.FEAT); } /** * Returns the list of automatic abilities of the specified category the * character possesses. * * @param aCategory * The <tt>AbilityCategory</tt> to check. * * @return A <tt>List</tt> of <tt>Abiltity</tt> objects. * * @author boomer70 * @since 5.11.1 */ public List<Ability> getAutomaticAbilityList(final AbilityCategory aCategory) { List<Ability> abilities = theAbilities.get(aCategory, Ability.Nature.AUTOMATIC); if (abilities == null) { rebuildAggregateAbilityList(); abilities = theAbilities.get(aCategory, Ability.Nature.AUTOMATIC); } return abilities; } /** * Rebuild the full list of feats. * Note: only one version of this should be running per character otherwise * the results could be unpredictable. */ private synchronized void rebuildAggregateAbilityList() { rebuildingAbilities = true; getVariableProcessor().pauseCache(); try { rebuildAggregateAbilityListWorker(); } catch (Exception e) { Logging.errorPrint( "Encountered error while rebuilding abiltiies list - ignoring", e); } finally { getVariableProcessor().restartCache(); rebuildingAbilities = false; } } /** * Do the actual rebuilding of the feats. Split out to allow more * readable error handling. */ private synchronized void rebuildAggregateAbilityListWorker() { GameMode gm = SettingsHandler.getGame(); Set<AbilityCategory> catSet = new HashSet<AbilityCategory>(); catSet.addAll(gm.getAllAbilityCategories()); for (AbilityCategory cat : catSet) { for (Ability.Nature nature : Ability.Nature.values()) { if (nature != Ability.Nature.ANY) { List<Ability> abilities = new ArrayList<Ability>(); if (nature == Ability.Nature.NORMAL && realAbilities.get(cat) != null) { abilities.addAll(realAbilities.get(cat)); } else if (nature == Ability.Nature.VIRTUAL) { for (Ability ability : getDirectVirtualAbilities(cat)) { if (PrereqHandler.passesAll( ability.getPrerequisiteList(), this, ability)) { abilities.add(ability); } } } theAbilities.put(cat, nature, abilities); } } } int prevHashCode = -1; int i=0; while (prevHashCode != theAbilities.deepSize() && i < 10) { // Logging.log(Logging.ERROR, "Regen abilities pass #" + i + " prev:" // + prevHashCode + " curr:" + theAbilities.size() + " - " // + theAbilities /*, new Throwable()*/); prevHashCode = theAbilities.deepSize(); i++; List<PCTemplate> templateList = new ArrayList<PCTemplate>(); List<Equipment> naturalWeaponsList = new ArrayList<Equipment>(); for (CDOMObject cdo : getCDOMObjectList()) { for (CDOMReference<Ability> ref : cdo .getSafeListMods(Ability.FEATLIST)) { Collection<AssociatedPrereqObject> assoc = cdo .getListAssociations(Ability.FEATLIST, ref); for (Ability ab : ref.getContainedObjects()) { for (AssociatedPrereqObject apo : assoc) { List<String> choices = apo .getAssociation(AssociationKey.ASSOC_CHOICES); if (choices == null) { choices = Collections.emptyList(); } Nature nature = apo .getAssociation(AssociationKey.NATURE); List<Ability> abilities = theAbilities.get( AbilityCategory.FEAT, nature); Ability added = AbilityUtilities .addAbilityToListwithChoices(ab, choices, abilities); if (added != null) { added.setFeatType(nature); } } } } for (CDOMReference<Ability> ref : cdo .getSafeListMods(Ability.ABILITYLIST)) { Collection<AssociatedPrereqObject> assoc = cdo .getListAssociations(Ability.ABILITYLIST, ref); for (AssociatedPrereqObject apo : assoc) { if (!PrereqHandler.passesAll(apo.getPrerequisiteList(), this, cdo)) { continue; } for (Ability ab : ref.getContainedObjects()) { List<String> choices = apo .getAssociation(AssociationKey.ASSOC_CHOICES); if (choices == null) { choices = Collections.emptyList(); } Nature nature = apo .getAssociation(AssociationKey.NATURE); AbilityCategory cat = apo .getAssociation(AssociationKey.CATEGORY); List<Ability> abilities = theAbilities.get( cat, nature); Ability added = AbilityUtilities .addAbilityToListwithChoices(ab, choices, abilities); if (added != null) { added.setFeatType(nature); } } } } } addNonAbilityAutoFeats(theAbilities.get(AbilityCategory.FEAT, Ability.Nature.AUTOMATIC)); List<PObject> pobjectList = getConditionalTemplateObjects(); pobjectList.addAll(getPObjectList()); //TODO pobjectList needs to be getCDOMObjects() once Ability & AUTO are new syntax for (final PObject pobj : pobjectList) { for (AbilityCategory cat : pobj.getAbilityCategories()) { for (Ability.Nature nature : pobj.getAbilityNatures(cat)) { List<Ability> abilities = theAbilities.get(cat, nature); final List<String> abilityKeys = pobj.getAbilityKeys( this, cat, nature); for (final String key : abilityKeys) { final Ability added = AbilityUtilities .addCloneOfGlobalAbilityToListWithChoices( abilities, cat, key); if (added != null) { added.setFeatType(nature); for (CDOMReference<PCTemplate> ref : added .getSafeListFor(ListKey.TEMPLATE)) { templateList.addAll(ref .getContainedObjects()); } naturalWeaponsList.addAll(added .getNaturalWeapons()); } } // May have added templates, so scan for them addTemplatesIfMissing(templateList); addNaturalWeaponsIfMissing(naturalWeaponsList); } } // Feats have a second list which we need to populate stableAggregateFeatList = new ArrayList<Ability>(); stableAggregateFeatList.addAll(theAbilities.get( AbilityCategory.FEAT, Ability.Nature.NORMAL)); stableAggregateFeatList.addAll(theAbilities.get( AbilityCategory.FEAT, Ability.Nature.AUTOMATIC)); stableAggregateFeatList.addAll(theAbilities.get( AbilityCategory.FEAT, Ability.Nature.VIRTUAL)); } } cachedWeaponProfs = null; rebuildFeatAggreagateList(); } private void addTemplatesIfMissing(List<PCTemplate> templateList) { for (PCTemplate pct : templateList) { addTemplate(pct); } } private void addNaturalWeaponsIfMissing(List<Equipment> naturalWeaponsList) { for (Iterator<Equipment> iterator = naturalWeaponsList.iterator(); iterator .hasNext();) { Equipment wpn = iterator.next(); if (equipmentList.contains(wpn)) { iterator.remove(); } } addNaturalWeapons(naturalWeaponsList); } private List<Ability> getStableAggregateFeatList() { if (isAggregateFeatsStable()) { return stableAggregateFeatList; } return null; } /** * Add any automatic feats not stored as abilities to the * supplied list. * * @param abilities The Ability list to be populated. */ private void addNonAbilityAutoFeats(List<Ability> abilities) { // // add racial feats // if (getRace() != null) { addAutoProfsToList(getRace().getSafeListFor( ListKey.SELECTED_WEAPON_PROF_BONUS), abilities); } for (final PCClass aClass : getClassList()) { addAutoProfsToList(aClass .getSafeListFor(ListKey.SELECTED_WEAPON_PROF_BONUS), abilities); } if (!PlayerCharacterUtilities.canReassignTemplateFeats() && !getTemplateList().isEmpty()) { for (final PCTemplate aTemplate : getTemplateList()) { final List<String> templateFeats = feats(aTemplate, getTotalLevels(), totalHitDice(), false); if (!templateFeats.isEmpty()) { for (Iterator<String> e2 = templateFeats.iterator(); e2 .hasNext();) { final String aString = e2.next(); final StringTokenizer aTok = new StringTokenizer(aString, Constants.COMMA); while (aTok.hasMoreTokens()) { Ability added = AbilityUtilities .addCloneOfGlobalAbilityToListWithChoices( abilities, Constants.FEAT_CATEGORY, aTok.nextToken()); if (added != null) { added.setFeatType(Ability.Nature.AUTOMATIC); } } } } addAutoProfsToList(aTemplate .getSafeListFor(ListKey.SELECTED_WEAPON_PROF_BONUS), abilities); } } if (!getCharacterDomainList().isEmpty()) { for (final CharacterDomain aCD : getCharacterDomainList()) { final Domain aDomain = aCD.getDomain(); if (aDomain != null) { for (int e2 = 0; e2 < aDomain.getAssociatedCount(); ++e2) { final String aString = aDomain.getAssociated(e2); if (aString.startsWith("FEAT")) { final int idx = aString.indexOf('?'); if (idx > -1) { Ability added = AbilityUtilities .addCloneOfGlobalAbilityToListWithChoices( abilities, Constants.FEAT_CATEGORY, aString.substring(idx + 1)); if (added != null) { added.setFeatType(Ability.Nature.AUTOMATIC); } } else { Logging .errorPrint("no '?' in Domain assocatedList entry: " + aString); } } } for (CDOMReference<Ability> ref : aDomain.getSafeListMods(Ability.FEATLIST)) { Collection<AssociatedPrereqObject> assoc = aDomain .getListAssociations(Ability.FEATLIST, ref); for (Ability ab : ref.getContainedObjects()) { for (AssociatedPrereqObject apo : assoc) { List<String> choices = apo .getAssociation(AssociationKey.ASSOC_CHOICES); if (choices == null) { choices = Collections.emptyList(); } Ability added = AbilityUtilities .addAbilityToListwithChoices(ab, choices, abilities); if (added != null) { added.setFeatType(Ability.Nature.AUTOMATIC); } } } } addAutoProfsToList(aDomain .getSafeListFor(ListKey.SELECTED_WEAPON_PROF_BONUS), abilities); } } } } /** * Add the listed automatic weapon proficiencies to the list of abilities. * * @param autoProfList * The list of weapon profs to be added. * @param abilityList * The list to add the new entries to. */ private void addAutoProfsToList(List<String> autoProfList, List<Ability> abilityList) { for (Iterator<String> iter = autoProfList.iterator(); iter.hasNext();) { String prof = iter.next(); addWeaponProfToList(abilityList, prof, true); } } /** * Determine the character's facing. This is based on their race and any * applied templates. * * @return The facing. */ public Point2D.Double getFace() { final Race aRace = getRace(); // Default to 5' by 5' Point2D.Double face = new Point2D.Double(5, 0); if (aRace != null) { Point2D.Double rf = aRace.getFace(); if (rf != null) { face = rf; } } // Scan templates for any overrides for (PCTemplate template : getTemplateList()) { Point2D.Double tf = template.getFace(); if (tf != null) { face = tf; } } return face; } /** * Determine the number of hands the character has. This is based on their * race and any applied templates. * * @return The number of hands. */ public int getHands() { final Race aRace = getRace(); int hands = 0; if (aRace != null) { hands = aRace.getSafe(IntegerKey.CREATURE_HANDS); } // Scan templates for any overrides for (PCTemplate template : getTemplateList()) { Integer h = template.get(IntegerKey.CREATURE_HANDS); if (h != null) { hands = h; } } return hands; } /** * Determine the number of legs the character has. This is based on their * race and any applied templates. * * @return The number of legs. */ public int getLegs() { final Race aRace = getRace(); int legs = 0; if (aRace != null) { legs = aRace.getSafe(IntegerKey.LEGS); } // Scan templates for any overrides for (PCTemplate template : getTemplateList()) { Integer l = template.get(IntegerKey.LEGS); if (l != null) { legs = l; } } return legs; } /** * Determine the character's reach. This is based on their race, any applied * templates and any other bonuses to reach. * * @return The reach radius. */ public int getReach() { final Race aRace = getRace(); int reach = 0; if (aRace != null) { reach = aRace.getSafe(IntegerKey.REACH); } // Scan templates for any overrides for (PCTemplate template : getTemplateList()) { Integer r = template.get(IntegerKey.REACH); if (r != null) { reach = r; } } reach += (int) getTotalBonusTo("COMBAT", "REACH"); return reach; } /** * Fire any adds for any objects other than classes that * should be received at the character's current level. */ public void addAddsFromAllObjForLevel() { int totalCharacterLevel = getTotalCharacterLevel(); for (PObject pobj : getPObjectList()) { if (!(pobj instanceof PCClass)) { pobj.addAddsForLevel(totalCharacterLevel, this, null); } } for (PObject pobj : getConditionalTemplateObjects()) { pobj.addAddsForLevel(totalCharacterLevel, this, null); } } /** * Gets a list of feats matching the supplied name no matter what category * they were added in. * * @param featName the feat name * * @return the list of matching feats */ public List<Ability> getFeatNamedAnyCat(String featName) { List<Ability> feats = new ArrayList<Ability>(); for (AbilityCategory cat : SettingsHandler.getGame() .getAllAbilityCategories()) { Ability tempFeat = AbilityUtilities.getAbilityFromList( getAggregateAbilityList(cat), Constants.FEAT_CATEGORY, featName, Nature.ANY); if (tempFeat != null) { feats.add(tempFeat); } } return feats; } public boolean hasSpellInSpellbook(Spell spell, String spellbookname) { for (PObject po : getPObjectList()) { SpellSupport ss = po.getSpellSupport(); List<CharacterSpell> csl = ss.getCharacterSpell(spell, spellbookname, -1); if (csl != null && !csl.isEmpty()) { return true; } } return false; } public void resetEpicCache() { epicBAB = null; epicCheckMap.clear(); } // public double getBonusValue(final String aBonusType, final String // aBonusName ) // { // return TypedBonus.totalBonuses(getBonusesTo(aBonusType, aBonusName)); // } public int getCritRange(Equipment e, boolean primary) { if (!primary && !e.isDouble()) { return 0; } int raw = e.getRawCritRange(primary); int add = (int) e.bonusTo(this, "EQMWEAPON", "CRITRANGEADD", primary); int dbl = 1 + (int) e.bonusTo(this, "EQMWEAPON", "CRITRANGEDOUBLE", primary); return raw * dbl + add; } /** * Retrieve the list of the keynames of any feats * that the PC qualifies for at the supplied level and * hit dice. * * @param level * TODO DOCUMENT ME! * @param hitdice * TODO DOCUMENT ME! * @param aPC * TODO DOCUMENT ME! * @param addNew * TODO DOCUMENT ME! * * @return TODO DOCUMENT ME! */ public List<String> feats(PCTemplate pct, final int level, final int hitdice, final boolean addNew) { final List<String> feats = new ArrayList<String>(); for (CDOMReference<Ability> ref : pct.getSafeListMods(Ability.FEATLIST)) { /* * This is a hack for 5.x core compatibility... should use * ref.getContainedObjects(), but this ref string gives us back * items in () which were lost during resolution... */ feats.add(ref.getLSTformat()); } // arknight modified this back in 1.27 with the comment: Added support // for // Spycraft Game Mode we no longer support Spycraft (at this time), and // this // breaks other modes, so I've reverting back to the old method. I am // also fixing // a bug in the code I'm commenting out. levelStrings is used in the 2nd // loop // instead of hitDiceStrings. - Byngl Sept 25, 2003 // // Scrap all that. I'm using a HashMap to save those feats that have // been taken when // the required level/hitdie has been met. We need to do this so that // removing the // template will also remove the selected feat(s). PCTemplate instances // will also // need to be cloned() when adding them to PlayerCharacter. if (chosenFeatStrings != null) { feats.addAll(chosenFeatStrings.values()); } for (PCTemplate rlt : pct.getSafeListFor(ListKey.REPEATLEVEL_TEMPLATES)) { for (PCTemplate lt : rlt.getSafeListFor(ListKey.LEVEL_TEMPLATES)) { Integer lvl = lt.get(IntegerKey.LEVEL); if (addNew && lvl <= level) { doFeatOnTemplate(lt, lvl); } } } for (PCTemplate lt : pct.getSafeListFor(ListKey.LEVEL_TEMPLATES)) { Integer lvl = lt.get(IntegerKey.LEVEL); if (addNew && lvl <= level) { doFeatOnTemplate(lt, lvl); } } for (PCTemplate lt : pct.getSafeListFor(ListKey.HD_TEMPLATES)) { final String featKey = "H" + lt.get(IntegerKey.HD_MIN) + "-" + lt.get(IntegerKey.HD_MAX); String featName = null; if (chosenFeatStrings != null) { featName = chosenFeatStrings.get(featKey); } if ((featName == null) && addNew) { if (lt.get(IntegerKey.HD_MAX) <= hitdice && lt.get(IntegerKey.HD_MIN) >= hitdice) { getLevelFeat(lt, -1, featKey); } } } return feats; } private void doFeatOnTemplate(PCTemplate lt, int lvl) { // Check for an already selected value final String lvlKey = "L" + String.valueOf(lvl); String featKey = null; if (chosenFeatStrings != null) { featKey = chosenFeatStrings.get(lt); } // We haven't selected one yet. Ask for one if we are allowed. if (featKey == null && lt.hasListMods(Ability.FEATLIST)) { getLevelFeat(lt, lvl, lvlKey); } } /** * This is the function that implements a chooser for Feats granted by level * and/or HD by Templates. * * @param levelString * The string to be parsed for the choices to offer * @param lvl * The level this is being added at * @param featKey * either L<lvl> or H<lvl> * @param aPC * The PC that this Template is appled to */ private void getLevelFeat(PCTemplate pct, final int lvl, final String aKey) { String featKe = null; while (true) { List<String> featList = new ArrayList<String>(); List<String> featChoices = new ArrayList<String>(); for (CDOMReference<Ability> ref : pct.getSafeListMods(Ability.FEATLIST)) { /* * This is a hack for 5.x core compatibility... should use * ref.getContainedObjects(), but this ref string gives us back * items in () which were lost during resolution... */ featChoices.add(ref.getLSTformat()); } final LevelAbility la = LevelAbility.createAbility(pct, lvl, "FEAT(" + StringUtil.join(featChoices, Constants.COMMA) + ")"); la.process(featList, this, null); switch (featList.size()) { case 1: featKe = featList.get(0); break; default: if (!isImporting()) { Collections.sort(featList); final ChooserInterface c = ChooserFactory.getChooserInstance(); c.setTotalChoicesAvail(1); c.setTitle("Feat Choice"); c.setAvailableList(featList); c.setVisible(true); featList = c.getSelectedList(); if ((featList != null) && (featList.size() != 0)) { featKe = featList.get(0); break; } } // fall-through intentional case 0: return; } break; } addChosenFeat(pct, featKe); } public void addChosenFeat(PCTemplate pct, String feat) { if (chosenFeatStrings == null) { chosenFeatStrings = new IdentityHashMap<PCTemplate, String>(); } chosenFeatStrings.put(pct, feat); } public Map<PCTemplate, String> getChosenFeatStrings() { return chosenFeatStrings; } void selectTemplates(CDOMObject po, boolean isImporting) { // older version of this cleared the // templateAdded list, so this may have to do that as well? templatesAdded.removeListFor(po); if (!isImporting) { for (CDOMReference<PCTemplate> ref : po.getSafeListFor(ListKey.TEMPLATE)) { for (PCTemplate pct : ref.getContainedObjects()) { templatesAdded.addToListFor(po, pct); addTemplate(pct); } } List<PCTemplate> added = new ArrayList<PCTemplate>(); for (CDOMReference<PCTemplate> ref : po .getSafeListFor(ListKey.TEMPLATE_ADDCHOICE)) { added.addAll(ref.getContainedObjects()); } for (CDOMReference<PCTemplate> ref : po .getSafeListFor(ListKey.TEMPLATE_CHOOSE)) { List<PCTemplate> list = new ArrayList<PCTemplate>(added); list.addAll(ref.getContainedObjects()); PCTemplate selected = TemplateSelect.chooseTemplate(po, list, true, this); if (selected != null) { templatesAdded.addToListFor(po, selected); addTemplate(selected); } } for (CDOMReference<PCTemplate> ref : po .getSafeListFor(ListKey.REMOVE_TEMPLATES)) { for (PCTemplate pct : ref.getContainedObjects()) { removeTemplate(pct); } } } } public void removeTemplatesFrom(PObject po) { Collection<PCTemplate> list = getTemplatesAdded(po); if (list != null) { for (PCTemplate pct : list) { removeTemplate(getTemplateKeyed(pct.getKeyName())); } } } public Collection<PCTemplate> getTemplatesAdded(PObject po) { return templatesAdded.getListFor(po); } public void setTemplatesAdded(PObject po, PCTemplate pct) { templatesAdded.addToListFor(po, pct); } public boolean isClassSkill(Skill sk, PCClass pcc) { return SkillCost.CLASS.equals(cache.getSkillCost(this, sk, pcc)); } public boolean isClassSkill(Skill sk) { for (PCClass cl : classList) { if (isClassSkill(sk, cl)) { return true; } } return false; } public boolean isCrossClassSkill(Skill sk, PCClass pcc) { return SkillCost.CROSS_CLASS.equals(cache.getSkillCost(this, sk, pcc)); } public boolean isCrossClassSkill(Skill sk) { for (PCClass cl : classList) { if (isCrossClassSkill(sk, cl)) { return true; } } return false; } public SkillCost getSkillCostForClass(Skill sk, PCClass cl) { return cache.getSkillCost(this, sk, cl); } public void addAssociation(Object obj, Object o) { assocSupt.addAssociation(obj, o); } public boolean containsAssociated(Object obj, Object o) { return assocSupt.containsAssociated(obj, o); } public int getAssociationCount(Object obj) { return assocSupt.getAssociationCount(obj); } public List<Object> getAssociationList(Object obj) { return assocSupt.getAssociationList(obj); } public boolean hasAssociations(Object obj) { return assocSupt.hasAssociations(obj); } public List<Object> removeAllAssociations(Object obj) { return assocSupt.removeAllAssociations(obj); } public void removeAssociation(Object obj, Object o) { assocSupt.removeAssociation(obj, o); } public boolean hasUnlockedStat(PCStat stat) { for (CDOMObject cdo : getCDOMObjectList()) { if (cdo.containsInList(ListKey.UNLOCKED_STATS, stat)) { return true; } } return false; } public Number getLockedStat(PCStat stat) { for (CDOMObject cdo : getCDOMObjectList()) { List<StatLock> lockList = cdo.getListFor(ListKey.STAT_LOCKS); if (lockList != null) { for (StatLock lock : lockList) { if (lock.getLockedStat().equals(stat)) { return lock.getLockValue().resolve(this, cdo.getKeyName()); } } } } return null; } public PCClass getSelectedFavoredClass() { return selectedFavoredClass; } public void setSelectedFavoredClass(PCClass sfc) { selectedFavoredClass = sfc; } }

The table below shows all metrics for PlayerCharacter.java.

MetricValueDescription
BLOCKS2228.00Number of blocks
BLOCK_COMMENT178.00Number of block comment lines
COMMENTS4513.00Comment lines
COMMENT_DENSITY 0.65Comment density
COMPARISONS1475.00Number of comparison operators
CYCLOMATIC2074.00Cyclomatic complexity
DECL_COMMENTS464.00Comments in declarations
DOC_COMMENT2720.00Number of javadoc comment lines
ELOC6898.00Effective lines of code
EXEC_COMMENTS500.00Comments in executable code
EXITS1308.00Procedure exits
FUNCTIONS652.00Number of function declarations
HALSTEAD_DIFFICULTY144.76Halstead difficulty
HALSTEAD_EFFORT 0.00Halstead effort
INTERFACE_COMPLEXITY1583.00Interface complexity
JAVA0001 0.00JAVA0001 Package name does not contain only lower case letters
JAVA0002 1.00JAVA0002 Package name does not begin with a top level domain name or country code
JAVA0003 0.00JAVA0003 Minimize use of on-demand (.*) imports
JAVA0004 0.00JAVA0004 Unnecessary import from java.lang
JAVA0005 0.00JAVA0005 Imports not in specified order
JAVA0006 0.00JAVA0006 Empty finally block
JAVA0007 0.00JAVA0007 Should not declare public field
JAVA0008 0.00JAVA0008 Empty catch block
JAVA0009 0.00JAVA0009 Protected member in final class
JAVA0010 0.00JAVA0010 Non-instantiable class does not contain a non-private static member
JAVA0011 0.00JAVA0011 Abstract class does not contain an abstract method
JAVA0012 0.00JAVA0012 Non-constructor method with same name as declaring class
JAVA0013 0.00JAVA0013 Non-blank final field is not static
JAVA0014 0.00JAVA0014 Class with only static members has non-private constructor
JAVA0015 0.00JAVA0015 Package class contains public nested type
JAVA0016 0.00JAVA0016 Abstract class contains public constructor
JAVA0017 0.00JAVA0017 Class name does not have required form
JAVA0018 0.00JAVA0018 Method name does not have required form
JAVA0019 0.00JAVA0019 Interface name does not have required form
JAVA0020 0.00JAVA0020 Field name does not have required form
JAVA0021 0.00JAVA0021 Interface method name does not have required form
JAVA0022 0.00JAVA0022 Static final field name does not have required form
JAVA0023 0.00JAVA0023 Empty finalize method
JAVA0024 0.00JAVA0024 Empty class
JAVA0025 0.00JAVA0025 Method override is empty
JAVA0026 0.00JAVA0026 Finalize method with parameters
JAVA0029 1.00JAVA0029 Private method not used
JAVA0030 0.00JAVA0030 Private field not used
JAVA0031 2.00JAVA0031 Case statement not properly closed
JAVA0032 1.00JAVA0032 Switch statement missing default
JAVA0033 1.00JAVA0033 default: not last case in switch statement
JAVA0034 5.00JAVA0034 Missing braces in if statement
JAVA0035 0.00JAVA0035 Missing braces in for statement
JAVA0036 0.00JAVA0036 Missing braces in while statement
JAVA0038 0.00JAVA0038 Non-case label in switch statement
JAVA0039 1.00JAVA0039 Break statement with label
JAVA0040 0.00JAVA0040 Switch statement contains N cases (maximum: M)
JAVA0041 0.00JAVA0041 Nested synchronized block
JAVA0042 0.00JAVA0042 Empty synchronized statement
JAVA0043 1.00JAVA0043 Inner class does not use outer class
JAVA0044 0.00JAVA0044 Serializable class with no instance variables
JAVA0045 0.00JAVA0045 Serializable class with only transient fields
JAVA0046 0.00JAVA0046 Name of class not derived from Exception ends with 'Exception'
JAVA0047 0.00JAVA0047 Serializable class derives from invalid base class
JAVA0048 0.00JAVA0048 Name of class derived from Exception does not end with 'Exception'
JAVA004927.00JAVA0049 Nested block at depth N (maximum: M)
JAVA0050 0.00JAVA0050 Class derives from java.lang.Error
JAVA0051 0.00JAVA0051 Class derives from java.lang.RuntimeException
JAVA0052 0.00JAVA0052 Class derives from java.lang.Throwable
JAVA0053 0.00JAVA0053 Unused label
JAVA0054 0.00JAVA0054 Inheritance depth N exceeds maximum M
JAVA0055 0.00JAVA0055 Class should be interface
JAVA0056 0.00JAVA0056 Unnecessary abstract modifier for interface or annotation
JAVA0057 0.00JAVA0057 Unnecessary default constructor
JAVA0058 0.00JAVA0058 Constructor calls super()
JAVA0059 0.00JAVA0059 Method override only calls super()
JAVA0061 0.00JAVA0061 Inaccessible member in anonymous class
JAVA0062 0.00JAVA0062 Public class missing public member or protected constructor
JAVA0063 0.00JAVA0063 Identifier name should not contain '$'
JAVA006411.00JAVA0064 N variations of identifier name (maximum: M)
JAVA0065 1.00JAVA0065 Unnecessary final modifier for method in final class
JAVA0066 0.00JAVA0066 Unnecessary modifier for interface nested type
JAVA0067 0.00JAVA0067 Array descriptor on identifier name
JAVA0068 0.00JAVA0068 Modifiers not declared in recommended order
JAVA0071 0.00JAVA0071 Strings compared with ==
JAVA0073 1.00JAVA0073 Integer division in floating-point context
JAVA0074 0.00JAVA0074 Use of Object.notify()
JAVA0075 1.00JAVA0075 Method parameter hides field
JAVA007634.00JAVA0076 Use of magic number
JAVA0077 0.00JAVA0077 Private field not used in declaring class
JAVA0078 1.00JAVA0078 Floating point values compared with ==
JAVA0079 0.00JAVA0079 Use of instance to reference static member
JAVA0080 0.00JAVA0080 Import declaration not used
JAVA0081 0.00JAVA0081 Boolean literal in comparison
JAVA0082 0.00JAVA0082 Unnecessary widening cast
JAVA0083 0.00JAVA0083 Unnecessary instanceof test
JAVA0084 1.00JAVA0084 Should use compound assignment operator
JAVA0085 0.00JAVA0085 Use of sun.* class
JAVA0087 0.00JAVA0087 Use of Thread.sleep()
JAVA0089 0.00JAVA0089 Use of restricted package
JAVA0092 0.00JAVA0092 Use of restricted type
JAVA0093 0.00JAVA0093 Redundant assignment
JAVA0094 0.00JAVA0094 Field hides a superclass field
JAVA0095 0.00JAVA0095 Uninitialized private field
JAVA0096 0.00JAVA0096 Field in nested class hides outer field
JAVA0098 0.00JAVA0098 Minimize use of implicit field initializers
JAVA0100 1.00JAVA0100 Class contains N non-final fields (maximum: M)
JAVA0101 0.00JAVA0101 Unnecessary modifier for field in interface
JAVA0102 0.00JAVA0102 Last statement in finalize() not super.finalize()
JAVA0103 0.00JAVA0103 Explicit call to finalize()
JAVA0104 0.00JAVA0104 finalize() only calls super.finalize()
JAVA0105 0.00JAVA0105 Duplicate import declaration
JAVA0106 0.00JAVA0106 Unnecessary import from current package
JAVA0108 5.00JAVA0108 Incorrect javadoc: no @param tag for 'parameter'
JAVA0109 4.00JAVA0109 Incorrect javadoc: no parameter 'parameter'
JAVA0110 3.00JAVA0110 Incorrect javadoc: no @return tag
JAVA0111 0.00JAVA0111 Incorrect javadoc: @return tag for void method
JAVA0112 0.00JAVA0112 Incorrect javadoc: no exception 'exception' in throws
JAVA0113 0.00JAVA0113 Incorrect javadoc: no @author tag
JAVA0114 0.00JAVA0114 Incorrect javadoc: no @version tag
JAVA0115 0.00JAVA0115 Incorrect javadoc: no @throws or @exception tag for 'exception'
JAVA0116 0.00JAVA0116 Missing javadoc: field 'field'
JAVA0117196.00JAVA0117 Missing javadoc: method 'method'
JAVA0118 0.00JAVA0118 Missing javadoc: type 'type'
JAVA0119 1.00JAVA0119 Control variable changed within body of for loop
JAVA0123 3.00JAVA0123 Use all three components of for loop
JAVA0125 1.00JAVA0125 Continue statement with label
JAVA0126 0.00JAVA0126 Method declares unchecked exception in throws
JAVA0128 1.00JAVA0128 Public constructor in non-public class
JAVA0130 7.00JAVA0130 Non-static method does not use instance fields
JAVA0131 0.00JAVA0131 Compatible method does not override base
JAVA0132 0.00JAVA0132 Method overload with compatible signature
JAVA0133 0.00JAVA0133 Non-synchronized method overrides synchronized method
JAVA0135 0.00JAVA0135 Only one of Object.equals and Object.hashCode defined: missing 'method'
JAVA0136 1.00JAVA0136 N methods defined in class (maximum: M)
JAVA0137 0.00JAVA0137 Non-abstract class missing constructor
JAVA0138 2.00JAVA0138 N parameters defined for method (maximum: M)
JAVA0139 0.00JAVA0139 Definition of main other than public static void main(java.lang.String[])
JAVA0141 0.00JAVA0141 Unnecessary modifier for method in interface
JAVA0143 3.00JAVA0143 Synchronized method
JAVA0144 2.00JAVA0144 Line exceeds maximum M characters
JAVA014537999.00JAVA0145 Tab character used in source file
JAVA0150 0.00JAVA0150 java.lang.Error (or subclass) thrown
JAVA0153 0.00JAVA0153 Inefficient conversion of integer to string
JAVA0159 0.00JAVA0159 Inefficient conversion of string to integer
JAVA0160 0.00JAVA0160 Method does not throw specified exception
JAVA0161 0.00JAVA0161 Conditional wait() not in loop
JAVA0163 0.00JAVA0163 Empty statement
JAVA0165 0.00JAVA0165 Conflicting return statement in finally block
JAVA0166 1.00JAVA0166 Generic exception caught
JAVA0167 0.00JAVA0167 ThreadDeath not rethrown
JAVA0169 0.00JAVA0169 Unnecessary catch block: exception 'exception'
JAVA0170 0.00JAVA0170 Caught exception not derived from java.lang.Exception
JAVA0171 0.00JAVA0171 Unused local variable
JAVA0173 5.00JAVA0173 Unused method parameter
JAVA0174 1.00JAVA0174 Assigned local variable never used
JAVA0175 2.00JAVA0175 Successive assignment to variable
JAVA0176 1.00JAVA0176 Local variable name does not have required form
JAVA017740.00JAVA0177 Variable declaration missing initializer
JAVA017913.00JAVA0179 Local variable hides visible field
JAVA0233 0.00JAVA0233 Definition of serialVersionUID other than 'private static final long serialVersionUID'
JAVA0234 0.00JAVA0234 Class is Serializable but does not define serialVersionUID
JAVA0235 0.00JAVA0235 Class defines serialVersionUID but does not implement Serializable
JAVA0236 0.00JAVA0236 Attempt to clone an object which does not implement Cloneable
JAVA0237 0.00JAVA0237 Class implements Cloneable but does not have public clone method
JAVA0238 1.00JAVA0238 Clone method does not call super.clone()
JAVA0239 0.00JAVA0239 Class declares 'readObject' or 'writeObject' but does not implement Serializable
JAVA0240 0.00JAVA0240 Serializable class which declares readObject or writeObject but not both
JAVA0241 0.00JAVA0241 'readObject' or 'writeObject' should be declared private in Serializable class
JAVA0242 0.00JAVA0242 Transient field in non-Serializable class
JAVA0243 0.00JAVA0243 'readResolve' or 'writeReplace' should be declared private or protected
JAVA0244 0.00JAVA0244 Field or method name in subclass differs only by case from inherited field or method
JAVA0245 0.00JAVA0245 JUnit TestCase with non-trivial constructor
JAVA0246 0.00JAVA0246 JUnit assertXXX statement missing message parameter
JAVA0247 0.00JAVA0247 JUnit 'setUp()' and 'tearDown()' should call super method
JAVA0248 0.00JAVA0248 JUnit method 'setUp' or 'tearDown' with incorrect signature
JAVA0249 0.00JAVA0249 JUnit TestCase 'suite()' should be declared static
JAVA0250 0.00JAVA0250 JUnit TestCase declares testXXX method with incorrect signature
JAVA0251 0.00JAVA0251 Use '%n' for line breaks in printf/format for platform independence
JAVA0252 0.00JAVA0252 'enum' is a Java 1.5 reserved word
JAVA0253 1.00JAVA0253 Not all enum constants consumed in switch statement
JAVA0254 6.00JAVA0254 Use enhanced for loop construct instead of Iterator
JAVA0255 0.00JAVA0255 Result of method invocation not used
JAVA0256 3.00JAVA0256 Assignment of external collection/array to field
JAVA0257 0.00JAVA0257 Use of 'Constant Interface' anti-pattern
JAVA0258 0.00JAVA0258 Implement Iterable for foreach compatibility
JAVA025920.00JAVA0259 Return of collection/array field
JAVA0260 0.00JAVA0260 Use 'enum' instead of Enumerated Type pattern
JAVA0261 2.00JAVA0261 Use specialized Enum collection types
JAVA0262 0.00JAVA0262 Use of char in integer context
JAVA0263 0.00JAVA0263 Long literal ends with 'l' instead of 'L'
JAVA0264 0.00JAVA0264 Integer math in long context - check for overflow
JAVA0265 0.00JAVA0265 Use of Throwable.printStackTrace()
JAVA0266 0.00JAVA0266 Use of System.out
JAVA0267 0.00JAVA0267 Use of System.err
JAVA0269 0.00JAVA0269 Contents of StringBuffer never used
JAVA0270 1.00JAVA0270 Use Java 5.0 enhanced for loop construct to iterate over all elements in an array
JAVA0271 0.00JAVA0271 Minimize use of on-demand (.*) static imports
JAVA0272 0.00JAVA0272 Thread.run() called
JAVA0273 0.00JAVA0273 Non-final derivative of Thread calls start() in constructor
JAVA0274 0.00JAVA0274 Serializable class has a synchronized readObject()
JAVA0275 0.00JAVA0275 Serializable class has a synchronized writeObject() and no other synchronized methods
JAVA027630.00JAVA0276 Unnecessary use of String constructor
JAVA0277 0.00JAVA0277 Iterator.next() implementation does not throw NoSuchElementException
JAVA0278 0.00JAVA0278 Unnecessary use of Boolean constructor
JAVA0279 0.00JAVA0279 Serialization method readObject or readObjectNoData calls an overridable method
JAVA0280 0.00JAVA0280 IllegalMonitorStateException caught
JAVA0281 0.00JAVA0281 Iterator.next() not called in loop
JAVA0282 0.00JAVA0282 Call to Iterator.next() in loop which does not test Iterator.hasNext()
JAVA0283 0.00JAVA0283 Control variable not updated in loop body
JAVA0284 0.00JAVA0284 Explicit garbage collection
JAVA0285 5.00JAVA0285 Dereference of potentially null variable
JAVA0286 0.00JAVA0286 Dereference of null variable
JAVA0287 0.00JAVA0287 Unnecessary null check
JAVA0288 0.00JAVA0288 Inconsistent null check
LINES17923.00Number of lines in the source file
LINE_COMMENT1615.00Number of line comments
LOC11360.00Lines of code
LOGICAL_LINES3945.00Number of statements
LOOPS105.00Number of loops
NEST_DEPTH 8.00Maximum nesting depth
OPERANDS19539.00Number of operands
OPERATORS40286.00Number of operators
PARAMS631.00Number of formal parameter declarations
PROGRAM_LENGTH59825.00Halstead program length
PROGRAM_VOCAB4657.00Halstead program vocabulary
PROGRAM_VOLUME 0.00Halstead program volume
RETURNS952.00Number of return points from functions
SIZE421473.00Size of the file in bytes
UNIQUE_OPERANDS4589.00Number of unique operands
UNIQUE_OPERATORS68.00Number of unique operators
WHITESPACE2050.00Number of whitespace lines