/*
* Copyright (c) 1998-2017 by Richard A. Wilkes. All rights reserved.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, version 2.0. If a copy of the MPL was not distributed with
* this file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This Source Code Form is "Incompatible With Secondary Licenses", as
* defined by the Mozilla Public License, version 2.0.
*/
package com.trollworks.gcs.character;
import com.trollworks.gcs.advantage.Advantage;
import com.trollworks.gcs.advantage.AdvantageContainerType;
import com.trollworks.gcs.advantage.AdvantageList;
import com.trollworks.gcs.app.GCSImages;
import com.trollworks.gcs.common.DataFile;
import com.trollworks.gcs.common.LoadState;
import com.trollworks.gcs.equipment.Equipment;
import com.trollworks.gcs.equipment.EquipmentList;
import com.trollworks.gcs.feature.AttributeBonusLimitation;
import com.trollworks.gcs.feature.Bonus;
import com.trollworks.gcs.feature.BonusAttributeType;
import com.trollworks.gcs.feature.CostReduction;
import com.trollworks.gcs.feature.Feature;
import com.trollworks.gcs.feature.SkillBonus;
import com.trollworks.gcs.feature.SpellBonus;
import com.trollworks.gcs.feature.WeaponBonus;
import com.trollworks.gcs.modifier.Modifier;
import com.trollworks.gcs.notes.Note;
import com.trollworks.gcs.notes.NoteList;
import com.trollworks.gcs.preferences.OutputPreferences;
import com.trollworks.gcs.preferences.SheetPreferences;
import com.trollworks.gcs.skill.Skill;
import com.trollworks.gcs.skill.SkillList;
import com.trollworks.gcs.skill.Technique;
import com.trollworks.gcs.spell.Spell;
import com.trollworks.gcs.spell.SpellList;
import com.trollworks.gcs.widgets.outline.ListRow;
import com.trollworks.toolkit.annotation.Localize;
import com.trollworks.toolkit.collections.FilteredIterator;
import com.trollworks.toolkit.io.Log;
import com.trollworks.toolkit.io.xml.XMLNodeType;
import com.trollworks.toolkit.io.xml.XMLReader;
import com.trollworks.toolkit.io.xml.XMLWriter;
import com.trollworks.toolkit.ui.image.StdImageSet;
import com.trollworks.toolkit.ui.print.PrintManager;
import com.trollworks.toolkit.ui.widget.outline.OutlineModel;
import com.trollworks.toolkit.ui.widget.outline.Row;
import com.trollworks.toolkit.ui.widget.outline.RowIterator;
import com.trollworks.toolkit.utility.Dice;
import com.trollworks.toolkit.utility.FileType;
import com.trollworks.toolkit.utility.Localization;
import com.trollworks.toolkit.utility.text.Numbers;
import com.trollworks.toolkit.utility.undo.StdUndoManager;
import com.trollworks.toolkit.utility.units.LengthUnits;
import com.trollworks.toolkit.utility.units.WeightUnits;
import com.trollworks.toolkit.utility.units.WeightValue;
import java.io.File;
import java.io.IOException;
import java.text.DateFormat;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
/** A GURPS character. */
public class GURPSCharacter extends DataFile {
@Localize("Modified at {0} on {1}")
@Localize(locale = "de", value = "Verändert um {0} am {1}.")
@Localize(locale = "ru", value = "Изменен в {0} {1}")
@Localize(locale = "es", value = "Modificado el {0} a las {1}")
private static String LAST_MODIFIED;
@Localize("Created On Change")
@Localize(locale = "de", value = "Erstellt am ändern")
@Localize(locale = "ru", value = "Изменить дату создания")
@Localize(locale = "es", value = "Cambiar Fecha de creacción")
private static String CREATED_ON_UNDO;
@Localize("Strength Change")
@Localize(locale = "de", value = "Stärke ändern")
@Localize(locale = "ru", value = "Изменить силу")
@Localize(locale = "es", value = "Cambiar Fuerza")
private static String STRENGTH_UNDO;
@Localize("Dexterity Change")
@Localize(locale = "de", value = "Geschick ändern")
@Localize(locale = "ru", value = "Изменить ловкость")
@Localize(locale = "es", value = "Cambiar Destreza")
private static String DEXTERITY_UNDO;
@Localize("Intelligence Change")
@Localize(locale = "de", value = "Intelligenz ändern")
@Localize(locale = "ru", value = "Смена интеллекта")
@Localize(locale = "es", value = "Cambiar Inteligencia")
private static String INTELLIGENCE_UNDO;
@Localize("Health Change")
@Localize(locale = "de", value = "Konstitution ändern")
@Localize(locale = "ru", value = "Смена уровня здоровья")
@Localize(locale = "es", value = "Cambiar Salud")
private static String HEALTH_UNDO;
@Localize("Basic Speed Change")
@Localize(locale = "de", value = "Grundgeschwindigkeit ändern")
@Localize(locale = "ru", value = "Смена Базовой Скорости")
@Localize(locale = "es", value = "Cambiar Velocidad Básica")
private static String BASIC_SPEED_UNDO;
@Localize("Basic Move Change")
@Localize(locale = "de", value = "Grundbewegung ändern")
@Localize(locale = "ru", value = "Смена Базового Движения")
@Localize(locale = "es", value = "Cambiar Movimiento Básico")
private static String BASIC_MOVE_UNDO;
@Localize("Perception Change")
@Localize(locale = "de", value = "Wahrnehmung ändern")
@Localize(locale = "ru", value = "Смена восприятия")
@Localize(locale = "es", value = "Cambiar Percepción")
private static String PERCEPTION_UNDO;
@Localize("Will Change")
@Localize(locale = "de", value = "Wille ändern")
@Localize(locale = "ru", value = "Изменить волю")
@Localize(locale = "es", value = "Cambiar Voluntad")
private static String WILL_UNDO;
@Localize("Earned Points Change")
@Localize(locale = "de", value = "Verdiente Punkte ändern")
@Localize(locale = "ru", value = "Изменить заработаные очки")
@Localize(locale = "es", value = "Cambiar Puntos Obtenidos")
private static String EARNED_POINTS_UNDO;
@Localize("Hit Points Change")
@Localize(locale = "de", value = "Normale Trefferpunkte ändern")
@Localize(locale = "ru", value = "Изменить единицы здоровья")
@Localize(locale = "es", value = "Cambiar Puntos de Vida")
private static String HIT_POINTS_UNDO;
@Localize("Current Hit Points Change")
@Localize(locale = "de", value = "Aktuelle Trefferpunkte ändern")
@Localize(locale = "ru", value = "Смена текущих очков (единиц) жизни")
@Localize(locale = "es", value = "Cambiar Puntos de Vida Actuales")
private static String CURRENT_HIT_POINTS_UNDO;
@Localize("Fatigue Points Change")
@Localize(locale = "de", value = "Normale Erschöpfungspunkte ändern")
@Localize(locale = "ru", value = "Изменить очки усталости")
@Localize(locale = "es", value = "Cambiar Puntos de Fatiga")
private static String FATIGUE_POINTS_UNDO;
@Localize("Current Fatigue Points Change")
@Localize(locale = "de", value = "Aktuelle Erschöpfungspunkte ändern")
@Localize(locale = "ru", value = "Изменить текущие единицы усталости")
@Localize(locale = "es", value = "Cambiar Puntos de Fatiga Actuales")
private static String CURRENT_FATIGUE_POINTS_UNDO;
@Localize("Include Punch In Weapons")
@Localize(locale = "de", value = "Schlag als Waffe aufführen")
@Localize(locale = "ru", value = "Отображать удар в оружии")
@Localize(locale = "es", value = "Incluir Puñetazo como Arma")
private static String INCLUDE_PUNCH_UNDO;
@Localize("Include Kick In Weapons")
@Localize(locale = "de", value = "Tritt als Waffe aufführen")
@Localize(locale = "ru", value = "Отображать пинок в оружии")
@Localize(locale = "es", value = "Incluir Patada como Arma")
private static String INCLUDE_KICK_UNDO;
@Localize("Include Kick w/Boots In Weapons")
@Localize(locale = "de", value = "Tritt mit Schuh als Waffe aufführen")
@Localize(locale = "ru", value = "Отображать пинок (в ботинке) в оружии")
@Localize(locale = "es", value = "Incluir Patada con botas como Arma")
private static String INCLUDE_BOOTS_UNDO;
@Localize("Unable to set a value for %s")
@Localize(locale = "de", value = "Kann keinen Wert für %s setzen")
@Localize(locale = "ru", value = "Невозможно установить значение для %s")
@Localize(locale = "es", value = "No puede establecerse un valor para %s")
private static String UNABLE_TO_SET_VALUE;
static {
Localization.initialize();
}
/** The extension for character sheets. */
public static final String EXTENSION = "gcs"; //$NON-NLS-1$
private static final int CURRENT_VERSION = 3;
private static final String EMPTY = ""; //$NON-NLS-1$
private static final String TAG_ROOT = "character"; //$NON-NLS-1$
private static final String TAG_CREATED_DATE = "created_date"; //$NON-NLS-1$
private static final String TAG_MODIFIED_DATE = "modified_date"; //$NON-NLS-1$
private static final String TAG_CURRENT_HP = "current_hp"; //$NON-NLS-1$
private static final String TAG_CURRENT_FP = "current_fp"; //$NON-NLS-1$
private static final String TAG_UNSPENT_POINTS = "unspent_points"; //$NON-NLS-1$
private static final String TAG_TOTAL_POINTS = "total_points"; //$NON-NLS-1$
private static final String TAG_INCLUDE_PUNCH = "include_punch"; //$NON-NLS-1$
private static final String TAG_INCLUDE_KICK = "include_kick"; //$NON-NLS-1$
private static final String TAG_INCLUDE_BOOTS = "include_kick_with_boots"; //$NON-NLS-1$
private static final String ATTRIBUTE_CARRIED = "carried"; //$NON-NLS-1$
/** The prefix for all character IDs. */
public static final String CHARACTER_PREFIX = "gcs."; //$NON-NLS-1$
/** The field ID for last modified date changes. */
public static final String ID_LAST_MODIFIED = CHARACTER_PREFIX + "LastModifiedDate"; //$NON-NLS-1$
/** The field ID for created on date changes. */
public static final String ID_CREATED_ON = CHARACTER_PREFIX + "CreatedOn"; //$NON-NLS-1$
/** The field ID for include punch changes. */
public static final String ID_INCLUDE_PUNCH = CHARACTER_PREFIX + "IncludePunch"; //$NON-NLS-1$
/** The field ID for include kick changes. */
public static final String ID_INCLUDE_KICK = CHARACTER_PREFIX + "IncludeKickFeet"; //$NON-NLS-1$
/** The field ID for include kick with boots changes. */
public static final String ID_INCLUDE_BOOTS = CHARACTER_PREFIX + "IncludeKickBoots"; //$NON-NLS-1$
/**
* The prefix used to indicate a point value is requested from {@link #getValueForID(String)}.
*/
public static final String POINTS_PREFIX = CHARACTER_PREFIX + "points."; //$NON-NLS-1$
/** The prefix used in front of all IDs for basic attributes. */
public static final String ATTRIBUTES_PREFIX = CHARACTER_PREFIX + "ba."; //$NON-NLS-1$
/** The field ID for strength (ST) changes. */
public static final String ID_STRENGTH = ATTRIBUTES_PREFIX + BonusAttributeType.ST.name();
/** The field ID for lifting strength bonuses -- used by features. */
public static final String ID_LIFTING_STRENGTH = ID_STRENGTH + AttributeBonusLimitation.LIFTING_ONLY.name();
/** The field ID for striking strength bonuses -- used by features. */
public static final String ID_STRIKING_STRENGTH = ID_STRENGTH + AttributeBonusLimitation.STRIKING_ONLY.name();
/** The field ID for dexterity (DX) changes. */
public static final String ID_DEXTERITY = ATTRIBUTES_PREFIX + BonusAttributeType.DX.name();
/** The field ID for intelligence (IQ) changes. */
public static final String ID_INTELLIGENCE = ATTRIBUTES_PREFIX + BonusAttributeType.IQ.name();
/** The field ID for health (HT) changes. */
public static final String ID_HEALTH = ATTRIBUTES_PREFIX + BonusAttributeType.HT.name();
/** The field ID for perception changes. */
public static final String ID_PERCEPTION = ATTRIBUTES_PREFIX + BonusAttributeType.PERCEPTION.name();
/** The field ID for vision changes. */
public static final String ID_VISION = ATTRIBUTES_PREFIX + BonusAttributeType.VISION.name();
/** The field ID for hearing changes. */
public static final String ID_HEARING = ATTRIBUTES_PREFIX + BonusAttributeType.HEARING.name();
/** The field ID for taste changes. */
public static final String ID_TASTE_AND_SMELL = ATTRIBUTES_PREFIX + BonusAttributeType.TASTE_SMELL.name();
/** The field ID for smell changes. */
public static final String ID_TOUCH = ATTRIBUTES_PREFIX + BonusAttributeType.TOUCH.name();
/** The field ID for will changes. */
public static final String ID_WILL = ATTRIBUTES_PREFIX + BonusAttributeType.WILL.name();
/** The field ID for fright check changes. */
public static final String ID_FRIGHT_CHECK = ATTRIBUTES_PREFIX + BonusAttributeType.FRIGHT_CHECK.name();
/** The field ID for basic speed changes. */
public static final String ID_BASIC_SPEED = ATTRIBUTES_PREFIX + BonusAttributeType.SPEED.name();
/** The field ID for basic move changes. */
public static final String ID_BASIC_MOVE = ATTRIBUTES_PREFIX + BonusAttributeType.MOVE.name();
/** The prefix used in front of all IDs for dodge changes. */
public static final String DODGE_PREFIX = ATTRIBUTES_PREFIX + BonusAttributeType.DODGE.name() + "#."; //$NON-NLS-1$
/** The field ID for dodge bonus changes. */
public static final String ID_DODGE_BONUS = ATTRIBUTES_PREFIX + BonusAttributeType.DODGE.name();
/** The field ID for parry bonus changes. */
public static final String ID_PARRY_BONUS = ATTRIBUTES_PREFIX + BonusAttributeType.PARRY.name();
/** The field ID for block bonus changes. */
public static final String ID_BLOCK_BONUS = ATTRIBUTES_PREFIX + BonusAttributeType.BLOCK.name();
/** The prefix used in front of all IDs for move changes. */
public static final String MOVE_PREFIX = ATTRIBUTES_PREFIX + BonusAttributeType.MOVE.name() + "#."; //$NON-NLS-1$
/** The field ID for carried weight changes. */
public static final String ID_CARRIED_WEIGHT = CHARACTER_PREFIX + "CarriedWeight"; //$NON-NLS-1$
/** The field ID for carried wealth changes. */
public static final String ID_CARRIED_WEALTH = CHARACTER_PREFIX + "CarriedWealth"; //$NON-NLS-1$
/** The prefix used in front of all IDs for encumbrance changes. */
public static final String MAXIMUM_CARRY_PREFIX = ATTRIBUTES_PREFIX + "MaximumCarry"; //$NON-NLS-1$
private static final String LIFT_PREFIX = ATTRIBUTES_PREFIX + "lift."; //$NON-NLS-1$
/** The field ID for basic lift changes. */
public static final String ID_BASIC_LIFT = LIFT_PREFIX + "BasicLift"; //$NON-NLS-1$
/** The field ID for one-handed lift changes. */
public static final String ID_ONE_HANDED_LIFT = LIFT_PREFIX + "OneHandedLift"; //$NON-NLS-1$
/** The field ID for two-handed lift changes. */
public static final String ID_TWO_HANDED_LIFT = LIFT_PREFIX + "TwoHandedLift"; //$NON-NLS-1$
/** The field ID for shove and knock over changes. */
public static final String ID_SHOVE_AND_KNOCK_OVER = LIFT_PREFIX + "ShoveAndKnockOver"; //$NON-NLS-1$
/** The field ID for running shove and knock over changes. */
public static final String ID_RUNNING_SHOVE_AND_KNOCK_OVER = LIFT_PREFIX + "RunningShoveAndKnockOver"; //$NON-NLS-1$
/** The field ID for carry on back changes. */
public static final String ID_CARRY_ON_BACK = LIFT_PREFIX + "CarryOnBack"; //$NON-NLS-1$
/** The field ID for carry on back changes. */
public static final String ID_SHIFT_SLIGHTLY = LIFT_PREFIX + "ShiftSlightly"; //$NON-NLS-1$
/** The prefix used in front of all IDs for point summaries. */
public static final String POINT_SUMMARY_PREFIX = CHARACTER_PREFIX + "ps."; //$NON-NLS-1$
/** The field ID for point total changes. */
public static final String ID_TOTAL_POINTS = POINT_SUMMARY_PREFIX + "TotalPoints"; //$NON-NLS-1$
/** The field ID for attribute point summary changes. */
public static final String ID_ATTRIBUTE_POINTS = POINT_SUMMARY_PREFIX + "AttributePoints"; //$NON-NLS-1$
/** The field ID for advantage point summary changes. */
public static final String ID_ADVANTAGE_POINTS = POINT_SUMMARY_PREFIX + "AdvantagePoints"; //$NON-NLS-1$
/** The field ID for disadvantage point summary changes. */
public static final String ID_DISADVANTAGE_POINTS = POINT_SUMMARY_PREFIX + "DisadvantagePoints"; //$NON-NLS-1$
/** The field ID for quirk point summary changes. */
public static final String ID_QUIRK_POINTS = POINT_SUMMARY_PREFIX + "QuirkPoints"; //$NON-NLS-1$
/** The field ID for skill point summary changes. */
public static final String ID_SKILL_POINTS = POINT_SUMMARY_PREFIX + "SkillPoints"; //$NON-NLS-1$
/** The field ID for spell point summary changes. */
public static final String ID_SPELL_POINTS = POINT_SUMMARY_PREFIX + "SpellPoints"; //$NON-NLS-1$
/** The field ID for racial point summary changes. */
public static final String ID_RACE_POINTS = POINT_SUMMARY_PREFIX + "RacePoints"; //$NON-NLS-1$
/** The field ID for earned point changes. */
public static final String ID_EARNED_POINTS = POINT_SUMMARY_PREFIX + "EarnedPoints"; //$NON-NLS-1$
/** The prefix used in front of all IDs for basic damage. */
public static final String BASIC_DAMAGE_PREFIX = CHARACTER_PREFIX + "bd."; //$NON-NLS-1$
/** The field ID for basic thrust damage changes. */
public static final String ID_BASIC_THRUST = BASIC_DAMAGE_PREFIX + "Thrust"; //$NON-NLS-1$
/** The field ID for basic swing damage changes. */
public static final String ID_BASIC_SWING = BASIC_DAMAGE_PREFIX + "Swing"; //$NON-NLS-1$
private static final String HIT_POINTS_PREFIX = ATTRIBUTES_PREFIX + "derived_hp."; //$NON-NLS-1$
/** The field ID for hit point changes. */
public static final String ID_HIT_POINTS = ATTRIBUTES_PREFIX + BonusAttributeType.HP.name();
/** The field ID for current hit point changes. */
public static final String ID_CURRENT_HIT_POINTS = HIT_POINTS_PREFIX + "Current"; //$NON-NLS-1$
/** The field ID for reeling hit point changes. */
public static final String ID_REELING_HIT_POINTS = HIT_POINTS_PREFIX + "Reeling"; //$NON-NLS-1$
/** The field ID for unconscious check hit point changes. */
public static final String ID_UNCONSCIOUS_CHECKS_HIT_POINTS = HIT_POINTS_PREFIX + "UnconsciousChecks"; //$NON-NLS-1$
/** The field ID for death check #1 hit point changes. */
public static final String ID_DEATH_CHECK_1_HIT_POINTS = HIT_POINTS_PREFIX + "DeathCheck1"; //$NON-NLS-1$
/** The field ID for death check #2 hit point changes. */
public static final String ID_DEATH_CHECK_2_HIT_POINTS = HIT_POINTS_PREFIX + "DeathCheck2"; //$NON-NLS-1$
/** The field ID for death check #3 hit point changes. */
public static final String ID_DEATH_CHECK_3_HIT_POINTS = HIT_POINTS_PREFIX + "DeathCheck3"; //$NON-NLS-1$
/** The field ID for death check #4 hit point changes. */
public static final String ID_DEATH_CHECK_4_HIT_POINTS = HIT_POINTS_PREFIX + "DeathCheck4"; //$NON-NLS-1$
/** The field ID for dead hit point changes. */
public static final String ID_DEAD_HIT_POINTS = HIT_POINTS_PREFIX + "Dead"; //$NON-NLS-1$
private static final String FATIGUE_POINTS_PREFIX = ATTRIBUTES_PREFIX + "derived_fp."; //$NON-NLS-1$
/** The field ID for fatigue point changes. */
public static final String ID_FATIGUE_POINTS = ATTRIBUTES_PREFIX + BonusAttributeType.FP.name();
/** The field ID for current fatigue point changes. */
public static final String ID_CURRENT_FATIGUE_POINTS = FATIGUE_POINTS_PREFIX + "Current"; //$NON-NLS-1$
/** The field ID for tired fatigue point changes. */
public static final String ID_TIRED_FATIGUE_POINTS = FATIGUE_POINTS_PREFIX + "Tired"; //$NON-NLS-1$
/** The field ID for unconscious check fatigue point changes. */
public static final String ID_UNCONSCIOUS_CHECKS_FATIGUE_POINTS = FATIGUE_POINTS_PREFIX + "UnconsciousChecks"; //$NON-NLS-1$
/** The field ID for unconscious fatigue point changes. */
public static final String ID_UNCONSCIOUS_FATIGUE_POINTS = FATIGUE_POINTS_PREFIX + "Unconscious"; //$NON-NLS-1$
private long mLastModified;
private long mCreatedOn;
private HashMap<String, ArrayList<Feature>> mFeatureMap;
private int mStrength;
private int mStrengthBonus;
private int mLiftingStrengthBonus;
private int mStrikingStrengthBonus;
private int mStrengthCostReduction;
private int mDexterity;
private int mDexterityBonus;
private int mDexterityCostReduction;
private int mIntelligence;
private int mIntelligenceBonus;
private int mIntelligenceCostReduction;
private int mHealth;
private int mHealthBonus;
private int mHealthCostReduction;
private int mWill;
private int mWillBonus;
private int mFrightCheckBonus;
private int mPerception;
private int mPerceptionBonus;
private int mVisionBonus;
private int mHearingBonus;
private int mTasteAndSmellBonus;
private int mTouchBonus;
private String mCurrentHitPoints;
private int mHitPoints;
private int mHitPointBonus;
private int mFatiguePoints;
private String mCurrentFatiguePoints;
private int mFatiguePointBonus;
private double mSpeed;
private double mSpeedBonus;
private int mMove;
private int mMoveBonus;
private int mDodgeBonus;
private int mParryBonus;
private int mBlockBonus;
private int mTotalPoints;
private Profile mDescription;
private Armor mArmor;
private OutlineModel mAdvantages;
private OutlineModel mSkills;
private OutlineModel mSpells;
private OutlineModel mEquipment;
private OutlineModel mNotes;
private boolean mDidModify;
private boolean mNeedAttributePointCalculation;
private boolean mNeedAdvantagesPointCalculation;
private boolean mNeedSkillPointCalculation;
private boolean mNeedSpellPointCalculation;
private boolean mNeedEquipmentCalculation;
private WeightValue mCachedWeightCarried;
private double mCachedWealthCarried;
private int mCachedAttributePoints;
private int mCachedAdvantagePoints;
private int mCachedDisadvantagePoints;
private int mCachedQuirkPoints;
private int mCachedSkillPoints;
private int mCachedSpellPoints;
private int mCachedRacePoints;
private boolean mSkillsUpdated;
private boolean mSpellsUpdated;
private PrintManager mPageSettings;
private boolean mIncludePunch;
private boolean mIncludeKick;
private boolean mIncludeKickBoots;
/** Creates a new character with only default values set. */
public GURPSCharacter() {
super();
characterInitialize(true);
calculateAll();
}
/**
* Creates a new character from the specified file.
*
* @param file The file to load the data from.
* @throws IOException if the data cannot be read or the file doesn't contain a valid character
* sheet.
*/
public GURPSCharacter(File file) throws IOException {
super();
load(file);
}
private void characterInitialize(boolean full) {
mFeatureMap = new HashMap<>();
mAdvantages = new OutlineModel();
mSkills = new OutlineModel();
mSpells = new OutlineModel();
mEquipment = new OutlineModel();
mNotes = new OutlineModel();
mTotalPoints = SheetPreferences.getInitialPoints();
mStrength = 10;
mDexterity = 10;
mIntelligence = 10;
mHealth = 10;
mCurrentHitPoints = EMPTY;
mCurrentFatiguePoints = EMPTY;
mDescription = new Profile(this, full);
mArmor = new Armor(this);
mIncludePunch = true;
mIncludeKick = true;
mIncludeKickBoots = true;
mCachedWeightCarried = new WeightValue(0, SheetPreferences.getWeightUnits());
mPageSettings = OutputPreferences.getDefaultPageSettings();
mLastModified = System.currentTimeMillis();
mCreatedOn = mLastModified;
// This will force the long value to match the string value.
setCreatedOn(getCreatedOn());
}
/** @return The page settings. May return <code>null</code> if no printer has been defined. */
public PrintManager getPageSettings() {
return mPageSettings;
}
@Override
public FileType getFileType() {
return FileType.getByExtension(EXTENSION);
}
@Override
public StdImageSet getFileIcons() {
return GCSImages.getCharacterSheetDocumentIcons();
}
@Override
protected final void loadSelf(XMLReader reader, LoadState state) throws IOException {
String marker = reader.getMarker();
int unspentPoints = 0;
characterInitialize(false);
do {
if (reader.next() == XMLNodeType.START_TAG) {
String name = reader.getName();
if (state.mDataFileVersion == 0) {
if (mDescription.loadTag(reader, name)) {
continue;
}
}
if (Profile.TAG_ROOT.equals(name)) {
mDescription.load(reader);
} else if (TAG_CREATED_DATE.equals(name)) {
mCreatedOn = Numbers.extractDate(reader.readText());
} else if (TAG_MODIFIED_DATE.equals(name)) {
mLastModified = Numbers.extractDateTime(reader.readText());
} else if (BonusAttributeType.HP.getXMLTag().equals(name)) {
mHitPoints = reader.readInteger(0);
} else if (TAG_CURRENT_HP.equals(name)) {
mCurrentHitPoints = reader.readText();
} else if (BonusAttributeType.FP.getXMLTag().equals(name)) {
mFatiguePoints = reader.readInteger(0);
} else if (TAG_CURRENT_FP.equals(name)) {
mCurrentFatiguePoints = reader.readText();
} else if (TAG_UNSPENT_POINTS.equals(name)) {
unspentPoints = reader.readInteger(0);
} else if (TAG_TOTAL_POINTS.equals(name)) {
mTotalPoints = reader.readInteger(0);
} else if (BonusAttributeType.ST.getXMLTag().equals(name)) {
mStrength = reader.readInteger(0);
} else if (BonusAttributeType.DX.getXMLTag().equals(name)) {
mDexterity = reader.readInteger(0);
} else if (BonusAttributeType.IQ.getXMLTag().equals(name)) {
mIntelligence = reader.readInteger(0);
} else if (BonusAttributeType.HT.getXMLTag().equals(name)) {
mHealth = reader.readInteger(0);
} else if (BonusAttributeType.WILL.getXMLTag().equals(name)) {
mWill = reader.readInteger(0);
} else if (BonusAttributeType.PERCEPTION.getXMLTag().equals(name)) {
mPerception = reader.readInteger(0);
} else if (BonusAttributeType.SPEED.getXMLTag().equals(name)) {
mSpeed = reader.readDouble(0.0);
} else if (BonusAttributeType.MOVE.getXMLTag().equals(name)) {
mMove = reader.readInteger(0);
} else if (TAG_INCLUDE_PUNCH.equals(name)) {
mIncludePunch = reader.readBoolean();
} else if (TAG_INCLUDE_KICK.equals(name)) {
mIncludeKick = reader.readBoolean();
} else if (TAG_INCLUDE_BOOTS.equals(name)) {
mIncludeKickBoots = reader.readBoolean();
} else if (AdvantageList.TAG_ROOT.equals(name)) {
loadAdvantageList(reader, state);
} else if (SkillList.TAG_ROOT.equals(name)) {
loadSkillList(reader, state);
} else if (SpellList.TAG_ROOT.equals(name)) {
loadSpellList(reader, state);
} else if (EquipmentList.TAG_ROOT.equals(name)) {
loadEquipmentList(reader, state);
} else if (NoteList.TAG_ROOT.equals(name)) {
loadNoteList(reader, state);
} else if (PrintManager.TAG_ROOT.equals(name)) {
if (mPageSettings != null) {
mPageSettings.load(reader);
}
} else {
reader.skipTag(name);
}
}
} while (reader.withinMarker(marker));
calculateAll();
if (unspentPoints != 0) {
setEarnedPoints(unspentPoints);
}
}
private void loadAdvantageList(XMLReader reader, LoadState state) throws IOException {
String marker = reader.getMarker();
do {
if (reader.next() == XMLNodeType.START_TAG) {
String name = reader.getName();
if (Advantage.TAG_ADVANTAGE.equals(name) || Advantage.TAG_ADVANTAGE_CONTAINER.equals(name)) {
mAdvantages.addRow(new Advantage(this, reader, state), true);
} else {
reader.skipTag(name);
}
}
} while (reader.withinMarker(marker));
}
private void loadSkillList(XMLReader reader, LoadState state) throws IOException {
String marker = reader.getMarker();
do {
if (reader.next() == XMLNodeType.START_TAG) {
String name = reader.getName();
if (Skill.TAG_SKILL.equals(name) || Skill.TAG_SKILL_CONTAINER.equals(name)) {
mSkills.addRow(new Skill(this, reader, state), true);
} else if (Technique.TAG_TECHNIQUE.equals(name)) {
mSkills.addRow(new Technique(this, reader, state), true);
} else {
reader.skipTag(name);
}
}
} while (reader.withinMarker(marker));
}
private void loadSpellList(XMLReader reader, LoadState state) throws IOException {
String marker = reader.getMarker();
do {
if (reader.next() == XMLNodeType.START_TAG) {
String name = reader.getName();
if (Spell.TAG_SPELL.equals(name) || Spell.TAG_SPELL_CONTAINER.equals(name)) {
mSpells.addRow(new Spell(this, reader, state), true);
} else {
reader.skipTag(name);
}
}
} while (reader.withinMarker(marker));
}
private void loadEquipmentList(XMLReader reader, LoadState state) throws IOException {
String marker = reader.getMarker();
state.mDefaultCarried = state.mDataFileVersion != 0 ? true : reader.isAttributeSet(ATTRIBUTE_CARRIED);
do {
if (reader.next() == XMLNodeType.START_TAG) {
String name = reader.getName();
if (Equipment.TAG_EQUIPMENT.equals(name) || Equipment.TAG_EQUIPMENT_CONTAINER.equals(name)) {
mEquipment.addRow(new Equipment(this, reader, state), true);
} else {
reader.skipTag(name);
}
}
} while (reader.withinMarker(marker));
}
private void loadNoteList(XMLReader reader, LoadState state) throws IOException {
String marker = reader.getMarker();
do {
if (reader.next() == XMLNodeType.START_TAG) {
String name = reader.getName();
if (Note.TAG_NOTE.equals(name) || Note.TAG_NOTE_CONTAINER.equals(name)) {
mNotes.addRow(new Note(this, reader, state), true);
} else {
reader.skipTag(name);
}
}
} while (reader.withinMarker(marker));
}
private void calculateAll() {
calculateAttributePoints();
calculateAdvantagePoints();
calculateSkillPoints();
calculateSpellPoints();
calculateWeightAndWealthCarried(false);
}
@Override
public int getXMLTagVersion() {
return CURRENT_VERSION;
}
@Override
public String getXMLTagName() {
return TAG_ROOT;
}
@Override
protected void saveSelf(XMLWriter out) {
out.simpleTag(TAG_CREATED_DATE, DateFormat.getDateInstance(DateFormat.MEDIUM).format(new Date(mCreatedOn)));
out.simpleTag(TAG_MODIFIED_DATE, DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT).format(new Date(mLastModified)));
mDescription.save(out);
out.simpleTag(BonusAttributeType.HP.getXMLTag(), mHitPoints);
out.simpleTagNotEmpty(TAG_CURRENT_HP, mCurrentHitPoints);
out.simpleTag(BonusAttributeType.FP.getXMLTag(), mFatiguePoints);
out.simpleTagNotEmpty(TAG_CURRENT_FP, mCurrentFatiguePoints);
out.simpleTag(TAG_TOTAL_POINTS, mTotalPoints);
out.simpleTag(BonusAttributeType.ST.getXMLTag(), mStrength);
out.simpleTag(BonusAttributeType.DX.getXMLTag(), mDexterity);
out.simpleTag(BonusAttributeType.IQ.getXMLTag(), mIntelligence);
out.simpleTag(BonusAttributeType.HT.getXMLTag(), mHealth);
out.simpleTag(BonusAttributeType.WILL.getXMLTag(), mWill);
out.simpleTag(BonusAttributeType.PERCEPTION.getXMLTag(), mPerception);
out.simpleTag(BonusAttributeType.SPEED.getXMLTag(), mSpeed);
out.simpleTag(BonusAttributeType.MOVE.getXMLTag(), mMove);
out.simpleTag(TAG_INCLUDE_PUNCH, mIncludePunch);
out.simpleTag(TAG_INCLUDE_KICK, mIncludeKick);
out.simpleTag(TAG_INCLUDE_BOOTS, mIncludeKickBoots);
saveList(AdvantageList.TAG_ROOT, mAdvantages, out);
saveList(SkillList.TAG_ROOT, mSkills, out);
saveList(SpellList.TAG_ROOT, mSpells, out);
saveList(EquipmentList.TAG_ROOT, mEquipment, out);
saveList(NoteList.TAG_ROOT, mNotes, out);
if (mPageSettings != null) {
mPageSettings.save(out, LengthUnits.IN);
}
}
private static void saveList(String tag, OutlineModel model, XMLWriter out) {
if (model.getRowCount() > 0) {
out.startSimpleTagEOL(tag);
for (ListRow row : new FilteredIterator<>(model.getTopLevelRows(), ListRow.class)) {
row.save(out, false);
}
out.endTagEOL(tag, true);
}
}
/**
* @param id The field ID to retrieve the data for.
* @return The value of the specified field ID, or <code>null</code> if the field ID is invalid.
*/
public Object getValueForID(String id) {
if (id == null) {
return null;
}
if (id.startsWith(POINTS_PREFIX)) {
id = id.substring(POINTS_PREFIX.length());
if (ID_STRENGTH.equals(id)) {
return new Integer(getStrengthPoints());
} else if (ID_DEXTERITY.equals(id)) {
return new Integer(getDexterityPoints());
} else if (ID_INTELLIGENCE.equals(id)) {
return new Integer(getIntelligencePoints());
} else if (ID_HEALTH.equals(id)) {
return new Integer(getHealthPoints());
} else if (ID_WILL.equals(id)) {
return new Integer(getWillPoints());
} else if (ID_PERCEPTION.equals(id)) {
return new Integer(getPerceptionPoints());
} else if (ID_BASIC_SPEED.equals(id)) {
return new Integer(getBasicSpeedPoints());
} else if (ID_BASIC_MOVE.equals(id)) {
return new Integer(getBasicMovePoints());
} else if (ID_FATIGUE_POINTS.equals(id)) {
return new Integer(getFatiguePointPoints());
} else if (ID_HIT_POINTS.equals(id)) {
return new Integer(getHitPointPoints());
}
return null;
} else if (ID_LAST_MODIFIED.equals(id)) {
return getLastModified();
} else if (ID_CREATED_ON.equals(id)) {
return new Long(getCreatedOn());
} else if (ID_STRENGTH.equals(id)) {
return new Integer(getStrength());
} else if (ID_DEXTERITY.equals(id)) {
return new Integer(getDexterity());
} else if (ID_INTELLIGENCE.equals(id)) {
return new Integer(getIntelligence());
} else if (ID_HEALTH.equals(id)) {
return new Integer(getHealth());
} else if (ID_BASIC_SPEED.equals(id)) {
return new Double(getBasicSpeed());
} else if (ID_BASIC_MOVE.equals(id)) {
return new Integer(getBasicMove());
} else if (ID_BASIC_LIFT.equals(id)) {
return getBasicLift();
} else if (ID_PERCEPTION.equals(id)) {
return new Integer(getPerception());
} else if (ID_VISION.equals(id)) {
return new Integer(getVision());
} else if (ID_HEARING.equals(id)) {
return new Integer(getHearing());
} else if (ID_TASTE_AND_SMELL.equals(id)) {
return new Integer(getTasteAndSmell());
} else if (ID_TOUCH.equals(id)) {
return new Integer(getTouch());
} else if (ID_WILL.equals(id)) {
return new Integer(getWill());
} else if (ID_FRIGHT_CHECK.equals(id)) {
return new Integer(getFrightCheck());
} else if (ID_ATTRIBUTE_POINTS.equals(id)) {
return new Integer(getAttributePoints());
} else if (ID_ADVANTAGE_POINTS.equals(id)) {
return new Integer(getAdvantagePoints());
} else if (ID_DISADVANTAGE_POINTS.equals(id)) {
return new Integer(getDisadvantagePoints());
} else if (ID_QUIRK_POINTS.equals(id)) {
return new Integer(getQuirkPoints());
} else if (ID_SKILL_POINTS.equals(id)) {
return new Integer(getSkillPoints());
} else if (ID_SPELL_POINTS.equals(id)) {
return new Integer(getSpellPoints());
} else if (ID_RACE_POINTS.equals(id)) {
return new Integer(getRacePoints());
} else if (ID_EARNED_POINTS.equals(id)) {
return new Integer(getEarnedPoints());
} else if (ID_ONE_HANDED_LIFT.equals(id)) {
return getOneHandedLift();
} else if (ID_TWO_HANDED_LIFT.equals(id)) {
return getTwoHandedLift();
} else if (ID_SHOVE_AND_KNOCK_OVER.equals(id)) {
return getShoveAndKnockOver();
} else if (ID_RUNNING_SHOVE_AND_KNOCK_OVER.equals(id)) {
return getRunningShoveAndKnockOver();
} else if (ID_CARRY_ON_BACK.equals(id)) {
return getCarryOnBack();
} else if (ID_SHIFT_SLIGHTLY.equals(id)) {
return getShiftSlightly();
} else if (ID_TOTAL_POINTS.equals(id)) {
return new Integer(getTotalPoints());
} else if (ID_BASIC_THRUST.equals(id)) {
return getThrust();
} else if (ID_BASIC_SWING.equals(id)) {
return getSwing();
} else if (ID_HIT_POINTS.equals(id)) {
return new Integer(getHitPoints());
} else if (ID_CURRENT_HIT_POINTS.equals(id)) {
return getCurrentHitPoints();
} else if (ID_REELING_HIT_POINTS.equals(id)) {
return new Integer(getReelingHitPoints());
} else if (ID_UNCONSCIOUS_CHECKS_HIT_POINTS.equals(id)) {
return new Integer(getUnconsciousChecksHitPoints());
} else if (ID_DEATH_CHECK_1_HIT_POINTS.equals(id)) {
return new Integer(getDeathCheck1HitPoints());
} else if (ID_DEATH_CHECK_2_HIT_POINTS.equals(id)) {
return new Integer(getDeathCheck2HitPoints());
} else if (ID_DEATH_CHECK_3_HIT_POINTS.equals(id)) {
return new Integer(getDeathCheck3HitPoints());
} else if (ID_DEATH_CHECK_4_HIT_POINTS.equals(id)) {
return new Integer(getDeathCheck4HitPoints());
} else if (ID_DEAD_HIT_POINTS.equals(id)) {
return new Integer(getDeadHitPoints());
} else if (ID_FATIGUE_POINTS.equals(id)) {
return new Integer(getFatiguePoints());
} else if (ID_CURRENT_FATIGUE_POINTS.equals(id)) {
return getCurrentFatiguePoints();
} else if (ID_TIRED_FATIGUE_POINTS.equals(id)) {
return new Integer(getTiredFatiguePoints());
} else if (ID_UNCONSCIOUS_CHECKS_FATIGUE_POINTS.equals(id)) {
return new Integer(getUnconsciousChecksFatiguePoints());
} else if (ID_UNCONSCIOUS_FATIGUE_POINTS.equals(id)) {
return new Integer(getUnconsciousFatiguePoints());
} else if (ID_PARRY_BONUS.equals(id)) {
return new Integer(getParryBonus());
} else if (ID_BLOCK_BONUS.equals(id)) {
return new Integer(getBlockBonus());
} else if (ID_DODGE_BONUS.equals(id)) {
return new Integer(getDodgeBonus());
} else if (id.startsWith(Profile.PROFILE_PREFIX)) {
return mDescription.getValueForID(id);
} else if (id.startsWith(Armor.DR_PREFIX)) {
return mArmor.getValueForID(id);
} else {
for (Encumbrance encumbrance : Encumbrance.values()) {
int index = encumbrance.ordinal();
if ((DODGE_PREFIX + index).equals(id)) {
return new Integer(getDodge(encumbrance));
}
if ((MOVE_PREFIX + index).equals(id)) {
return new Integer(getMove(encumbrance));
}
if ((MAXIMUM_CARRY_PREFIX + index).equals(id)) {
return getMaximumCarry(encumbrance);
}
}
return null;
}
}
/**
* @param id The field ID to set the value for.
* @param value The value to set.
*/
public void setValueForID(String id, Object value) {
if (id != null) {
if (ID_CREATED_ON.equals(id)) {
if (value instanceof Long) {
setCreatedOn(((Long) value).longValue());
} else {
setCreatedOn((String) value);
}
} else if (ID_INCLUDE_PUNCH.equals(id)) {
setIncludePunch(((Boolean) value).booleanValue());
} else if (ID_INCLUDE_KICK.equals(id)) {
setIncludeKick(((Boolean) value).booleanValue());
} else if (ID_INCLUDE_BOOTS.equals(id)) {
setIncludeKickBoots(((Boolean) value).booleanValue());
} else if (ID_STRENGTH.equals(id)) {
setStrength(((Integer) value).intValue());
} else if (ID_DEXTERITY.equals(id)) {
setDexterity(((Integer) value).intValue());
} else if (ID_INTELLIGENCE.equals(id)) {
setIntelligence(((Integer) value).intValue());
} else if (ID_HEALTH.equals(id)) {
setHealth(((Integer) value).intValue());
} else if (ID_BASIC_SPEED.equals(id)) {
setBasicSpeed(((Double) value).doubleValue());
} else if (ID_BASIC_MOVE.equals(id)) {
setBasicMove(((Integer) value).intValue());
} else if (ID_PERCEPTION.equals(id)) {
setPerception(((Integer) value).intValue());
} else if (ID_WILL.equals(id)) {
setWill(((Integer) value).intValue());
} else if (ID_EARNED_POINTS.equals(id)) {
setEarnedPoints(((Integer) value).intValue());
} else if (ID_HIT_POINTS.equals(id)) {
setHitPoints(((Integer) value).intValue());
} else if (ID_CURRENT_HIT_POINTS.equals(id)) {
setCurrentHitPoints((String) value);
} else if (ID_FATIGUE_POINTS.equals(id)) {
setFatiguePoints(((Integer) value).intValue());
} else if (ID_CURRENT_FATIGUE_POINTS.equals(id)) {
setCurrentFatiguePoints((String) value);
} else if (id.startsWith(Profile.PROFILE_PREFIX)) {
mDescription.setValueForID(id, value);
} else if (id.startsWith(Armor.DR_PREFIX)) {
mArmor.setValueForID(id, value);
} else {
Log.error(String.format(UNABLE_TO_SET_VALUE, id));
}
}
}
@Override
protected void startNotifyAtBatchLevelZero() {
mDidModify = false;
mNeedAttributePointCalculation = false;
mNeedAdvantagesPointCalculation = false;
mNeedSkillPointCalculation = false;
mNeedSpellPointCalculation = false;
mNeedEquipmentCalculation = false;
}
@Override
public void notify(String type, Object data) {
super.notify(type, data);
if (Advantage.ID_POINTS.equals(type) || Advantage.ID_ROUND_COST_DOWN.equals(type) || Advantage.ID_LEVELS.equals(type) || Advantage.ID_CONTAINER_TYPE.equals(type) || Advantage.ID_LIST_CHANGED.equals(type) || Advantage.ID_CR.equals(type) || Modifier.ID_LIST_CHANGED.equals(type) || Modifier.ID_ENABLED.equals(type)) {
mNeedAdvantagesPointCalculation = true;
}
if (Skill.ID_POINTS.equals(type) || Skill.ID_LIST_CHANGED.equals(type)) {
mNeedSkillPointCalculation = true;
}
if (Spell.ID_POINTS.equals(type) || Spell.ID_LIST_CHANGED.equals(type)) {
mNeedSpellPointCalculation = true;
}
if (Equipment.ID_QUANTITY.equals(type) || Equipment.ID_WEIGHT.equals(type) || Equipment.ID_EXTENDED_WEIGHT.equals(type) || Equipment.ID_LIST_CHANGED.equals(type)) {
mNeedEquipmentCalculation = true;
}
if (Profile.ID_SIZE_MODIFIER.equals(type) || SheetPreferences.OPTIONAL_STRENGTH_RULES_PREF_KEY.equals(type)) {
mNeedAttributePointCalculation = true;
}
}
@Override
protected void notifyOccured() {
mDidModify = true;
}
@Override
protected void endNotifyAtBatchLevelOne() {
if (mNeedAttributePointCalculation) {
calculateAttributePoints();
notify(ID_ATTRIBUTE_POINTS, new Integer(getAttributePoints()));
}
if (mNeedAdvantagesPointCalculation) {
calculateAdvantagePoints();
notify(ID_ADVANTAGE_POINTS, new Integer(getAdvantagePoints()));
notify(ID_DISADVANTAGE_POINTS, new Integer(getDisadvantagePoints()));
notify(ID_QUIRK_POINTS, new Integer(getQuirkPoints()));
notify(ID_RACE_POINTS, new Integer(getRacePoints()));
}
if (mNeedSkillPointCalculation) {
calculateSkillPoints();
notify(ID_SKILL_POINTS, new Integer(getSkillPoints()));
}
if (mNeedSpellPointCalculation) {
calculateSpellPoints();
notify(ID_SPELL_POINTS, new Integer(getSpellPoints()));
}
if (mNeedAttributePointCalculation || mNeedAdvantagesPointCalculation || mNeedSkillPointCalculation || mNeedSpellPointCalculation) {
notify(ID_EARNED_POINTS, new Integer(getEarnedPoints()));
}
if (mNeedEquipmentCalculation) {
calculateWeightAndWealthCarried(true);
}
if (mDidModify) {
long now = System.currentTimeMillis();
if (mLastModified != now) {
mLastModified = now;
notify(ID_LAST_MODIFIED, new Long(mLastModified));
}
}
}
/** @return The last modified date and time. */
public String getLastModified() {
Date date = new Date(mLastModified);
return MessageFormat.format(LAST_MODIFIED, DateFormat.getTimeInstance(DateFormat.SHORT).format(date), DateFormat.getDateInstance(DateFormat.MEDIUM).format(date));
}
/** @return The created on date. */
public long getCreatedOn() {
return mCreatedOn;
}
/**
* Sets the created on date.
*
* @param date The new created on date.
*/
public void setCreatedOn(long date) {
if (mCreatedOn != date) {
Long value = new Long(date);
postUndoEdit(CREATED_ON_UNDO, ID_CREATED_ON, new Long(mCreatedOn), value);
mCreatedOn = date;
notifySingle(ID_CREATED_ON, value);
}
}
/**
* Sets the created on date.
*
* @param date The new created on date.
*/
public void setCreatedOn(String date) {
setCreatedOn(Numbers.extractDate(date));
}
private void updateSkills() {
for (Skill skill : getSkillsIterator()) {
skill.updateLevel(true);
}
mSkillsUpdated = true;
}
private void updateSpells() {
for (Spell spell : getSpellsIterator()) {
spell.updateLevel(true);
}
mSpellsUpdated = true;
}
/** @return The strength (ST). */
public int getStrength() {
return mStrength + mStrengthBonus;
}
/**
* Sets the strength (ST).
*
* @param strength The new strength.
*/
public void setStrength(int strength) {
int oldStrength = getStrength();
if (oldStrength != strength) {
postUndoEdit(STRENGTH_UNDO, ID_STRENGTH, new Integer(oldStrength), new Integer(strength));
updateStrengthInfo(strength - mStrengthBonus, mStrengthBonus, mLiftingStrengthBonus, mStrikingStrengthBonus);
}
}
/** @return The current strength bonus from features. */
public int getStrengthBonus() {
return mStrengthBonus;
}
/** @param bonus The new strength bonus. */
public void setStrengthBonus(int bonus) {
if (mStrengthBonus != bonus) {
updateStrengthInfo(mStrength, bonus, mLiftingStrengthBonus, mStrikingStrengthBonus);
}
}
/** @param reduction The cost reduction for strength. */
public void setStrengthCostReduction(int reduction) {
if (mStrengthCostReduction != reduction) {
mStrengthCostReduction = reduction;
mNeedAttributePointCalculation = true;
}
}
/** @return The current lifting strength bonus from features. */
public int getLiftingStrengthBonus() {
return mLiftingStrengthBonus;
}
/** @param bonus The new lifting strength bonus. */
public void setLiftingStrengthBonus(int bonus) {
if (mLiftingStrengthBonus != bonus) {
updateStrengthInfo(mStrength, mStrengthBonus, bonus, mStrikingStrengthBonus);
}
}
/** @return The current striking strength bonus from features. */
public int getStrikingStrengthBonus() {
return mStrikingStrengthBonus;
}
/** @param bonus The new striking strength bonus. */
public void setStrikingStrengthBonus(int bonus) {
if (mStrikingStrengthBonus != bonus) {
updateStrengthInfo(mStrength, mStrengthBonus, mLiftingStrengthBonus, bonus);
}
}
private void updateStrengthInfo(int strength, int bonus, int liftingBonus, int strikingBonus) {
Dice thrust = getThrust();
Dice swing = getSwing();
WeightValue lift = getBasicLift();
boolean notifyST = mStrength != strength || mStrengthBonus != bonus;
Dice dice;
mStrength = strength;
mStrengthBonus = bonus;
mLiftingStrengthBonus = liftingBonus;
mStrikingStrengthBonus = strikingBonus;
startNotify();
if (notifyST) {
notify(ID_STRENGTH, new Integer(getStrength()));
notifyOfBaseHitPointChange();
}
WeightValue newLift = getBasicLift();
if (!newLift.equals(lift)) {
notify(ID_BASIC_LIFT, newLift);
notify(ID_ONE_HANDED_LIFT, getOneHandedLift());
notify(ID_TWO_HANDED_LIFT, getTwoHandedLift());
notify(ID_SHOVE_AND_KNOCK_OVER, getShoveAndKnockOver());
notify(ID_RUNNING_SHOVE_AND_KNOCK_OVER, getRunningShoveAndKnockOver());
notify(ID_CARRY_ON_BACK, getCarryOnBack());
notify(ID_SHIFT_SLIGHTLY, getShiftSlightly());
for (Encumbrance encumbrance : Encumbrance.values()) {
notify(MAXIMUM_CARRY_PREFIX + encumbrance.ordinal(), getMaximumCarry(encumbrance));
}
}
dice = getThrust();
if (!dice.equals(thrust)) {
notify(ID_BASIC_THRUST, dice);
}
dice = getSwing();
if (!dice.equals(swing)) {
notify(ID_BASIC_SWING, dice);
}
updateSkills();
mNeedAttributePointCalculation = true;
endNotify();
}
/** @return The number of points spent on strength. */
public int getStrengthPoints() {
int reduction = mStrengthCostReduction;
if (!SheetPreferences.areOptionalStrengthRulesUsed()) {
reduction += mDescription.getSizeModifier() * 10;
}
return getPointsForAttribute(mStrength - 10, 10, reduction);
}
private static int getPointsForAttribute(int delta, int ptsPerLevel, int reduction) {
int amt = delta * ptsPerLevel;
if (reduction > 0 && delta > 0) {
int rounder = delta < 0 ? -99 : 99;
if (reduction > 80) {
reduction = 80;
}
amt = (rounder + amt * (100 - reduction)) / 100;
}
return amt;
}
/** @return The basic thrusting damage. */
public Dice getThrust() {
return getThrust(getStrength() + mStrikingStrengthBonus);
}
/**
* @param strength The strength to return basic thrusting damage for.
* @return The basic thrusting damage.
*/
public static Dice getThrust(int strength) {
if (SheetPreferences.areOptionalStrengthRulesUsed()) {
if (strength < 12) {
return new Dice(1, strength - 12);
}
return new Dice((strength - 7) / 4, (strength + 1) % 4 - 1);
}
int value = strength;
if (value < 19) {
return new Dice(1, -(6 - (value - 1) / 2));
}
value -= 11;
if (strength > 50) {
value--;
if (strength > 79) {
value -= 1 + (strength - 80) / 5;
}
}
return new Dice(value / 8 + 1, value % 8 / 2 - 1);
}
/** @return The basic swinging damage. */
public Dice getSwing() {
return getSwing(getStrength() + mStrikingStrengthBonus);
}
/**
* @param strength The strength to return basic swinging damage for.
* @return The basic thrusting damage.
*/
public static Dice getSwing(int strength) {
if (SheetPreferences.areOptionalStrengthRulesUsed()) {
if (strength < 10) {
return new Dice(1, strength - 10);
}
return new Dice((strength - 5) / 4, (strength - 1) % 4 - 1);
}
int value = strength;
if (value < 10) {
return new Dice(1, -(5 - (value - 1) / 2));
}
if (value < 28) {
value -= 9;
return new Dice(value / 4 + 1, value % 4 - 1);
}
if (strength > 40) {
value -= (strength - 40) / 5;
}
if (strength > 59) {
value++;
}
value += 9;
return new Dice(value / 8 + 1, value % 8 / 2 - 1);
}
/** @return Basic lift. */
public WeightValue getBasicLift() {
return getBasicLift(SheetPreferences.getWeightUnits());
}
private WeightValue getBasicLift(WeightUnits desiredUnits) {
WeightUnits units;
double divisor;
double multiplier;
double roundAt;
if (SheetPreferences.areGurpsMetricRulesUsed() && SheetPreferences.getWeightUnits().isMetric()) {
units = WeightUnits.KG;
divisor = 10;
multiplier = 1;
roundAt = 5;
} else {
units = WeightUnits.LB;
divisor = 5;
multiplier = 2;
roundAt = 10;
}
int strength = getStrength() + mLiftingStrengthBonus;
double value;
if (strength < 1) {
value = 0;
} else {
if (SheetPreferences.areOptionalStrengthRulesUsed()) {
int diff = 0;
if (strength > 19) {
diff = strength / 10 - 1;
strength -= diff * 10;
}
value = Math.pow(10.0, strength / 10.0) * multiplier;
if (strength <= 6) {
value = Math.round(value * 10.0) / 10.0;
} else {
value = Math.round(value);
}
value *= Math.pow(10, diff);
} else {
value = strength * strength / divisor;
}
if (value >= roundAt) {
value = Math.round(value);
}
value = Math.floor(value * 10.0) / 10.0;
}
return new WeightValue(desiredUnits.convert(units, value), desiredUnits);
}
private WeightValue getMultipleOfBasicLift(double multiple) {
WeightValue lift = getBasicLift();
lift.setValue(lift.getValue() * multiple);
return lift;
}
/** @return The one-handed lift value. */
public WeightValue getOneHandedLift() {
return getMultipleOfBasicLift(2);
}
/** @return The two-handed lift value. */
public WeightValue getTwoHandedLift() {
return getMultipleOfBasicLift(8);
}
/** @return The shove and knock over value. */
public WeightValue getShoveAndKnockOver() {
return getMultipleOfBasicLift(12);
}
/** @return The running shove and knock over value. */
public WeightValue getRunningShoveAndKnockOver() {
return getMultipleOfBasicLift(24);
}
/** @return The carry on back value. */
public WeightValue getCarryOnBack() {
return getMultipleOfBasicLift(15);
}
/** @return The shift slightly value. */
public WeightValue getShiftSlightly() {
return getMultipleOfBasicLift(50);
}
/**
* @param encumbrance The encumbrance level.
* @return The maximum amount the character can carry for the specified encumbrance level.
*/
public WeightValue getMaximumCarry(Encumbrance encumbrance) {
WeightUnits calcUnits = SheetPreferences.areGurpsMetricRulesUsed() && SheetPreferences.getWeightUnits().isMetric() ? WeightUnits.KG : WeightUnits.LB;
WeightValue lift = getBasicLift(calcUnits);
lift.setValue(lift.getValue() * encumbrance.getWeightMultiplier());
WeightUnits desiredUnits = SheetPreferences.getWeightUnits();
return new WeightValue(desiredUnits.convert(calcUnits, lift.getValue()), desiredUnits);
}
/**
* @return The character's basic speed.
*/
public double getBasicSpeed() {
return mSpeed + mSpeedBonus + getRawBasicSpeed();
}
private double getRawBasicSpeed() {
return (getDexterity() + getHealth()) / 4.0;
}
/**
* Sets the basic speed.
*
* @param speed The new basic speed.
*/
public void setBasicSpeed(double speed) {
double oldBasicSpeed = getBasicSpeed();
if (oldBasicSpeed != speed) {
postUndoEdit(BASIC_SPEED_UNDO, ID_BASIC_SPEED, new Double(oldBasicSpeed), new Double(speed));
updateBasicSpeedInfo(speed - (mSpeedBonus + getRawBasicSpeed()), mSpeedBonus);
}
}
/** @return The basic speed bonus. */
public double getBasicSpeedBonus() {
return mSpeedBonus;
}
/** @param bonus The basic speed bonus. */
public void setBasicSpeedBonus(double bonus) {
if (mSpeedBonus != bonus) {
updateBasicSpeedInfo(mSpeed, bonus);
}
}
private void updateBasicSpeedInfo(double speed, double bonus) {
int move = getBasicMove();
int[] data = preserveMoveAndDodge();
int tmp;
mSpeed = speed;
mSpeedBonus = bonus;
startNotify();
notify(ID_BASIC_SPEED, new Double(getBasicSpeed()));
tmp = getBasicMove();
if (move != tmp) {
notify(ID_BASIC_MOVE, new Integer(tmp));
}
notifyIfMoveOrDodgeAltered(data);
mNeedAttributePointCalculation = true;
endNotify();
}
/** @return The number of points spent on basic speed. */
public int getBasicSpeedPoints() {
return (int) (mSpeed * 20.0);
}
/**
* @return The character's basic move.
*/
public int getBasicMove() {
int move = mMove + mMoveBonus + getRawBasicMove();
return move < 0 ? 0 : move;
}
private int getRawBasicMove() {
return (int) Math.floor(getBasicSpeed());
}
/**
* Sets the basic move.
*
* @param move The new basic move.
*/
public void setBasicMove(int move) {
int oldBasicMove = getBasicMove();
if (oldBasicMove != move) {
postUndoEdit(BASIC_MOVE_UNDO, ID_BASIC_MOVE, new Integer(oldBasicMove), new Integer(move));
updateBasicMoveInfo(move - (mMoveBonus + getRawBasicMove()), mMoveBonus);
}
}
/** @return The basic move bonus. */
public int getBasicMoveBonus() {
return mMoveBonus;
}
/** @param bonus The basic move bonus. */
public void setBasicMoveBonus(int bonus) {
if (mMoveBonus != bonus) {
updateBasicMoveInfo(mMove, bonus);
}
}
private void updateBasicMoveInfo(int move, int bonus) {
int[] data = preserveMoveAndDodge();
startNotify();
mMove = move;
mMoveBonus = bonus;
notify(ID_BASIC_MOVE, new Integer(getBasicMove()));
notifyIfMoveOrDodgeAltered(data);
mNeedAttributePointCalculation = true;
endNotify();
}
/** @return The number of points spent on basic move. */
public int getBasicMovePoints() {
return mMove * 5;
}
/**
* @param encumbrance The encumbrance level.
* @return The character's ground move for the specified encumbrance level.
*/
public int getMove(Encumbrance encumbrance) {
int basicMove = getBasicMove();
int move = basicMove * (10 + 2 * encumbrance.getEncumbrancePenalty()) / 10;
if (move < 1) {
return basicMove > 0 ? 1 : 0;
}
return move;
}
/**
* @param encumbrance The encumbrance level.
* @return The character's dodge for the specified encumbrance level.
*/
public int getDodge(Encumbrance encumbrance) {
int dodge = (int) Math.floor(getBasicSpeed()) + 3 + encumbrance.getEncumbrancePenalty() + mDodgeBonus;
return dodge < 1 ? 1 : dodge;
}
/** @return The dodge bonus. */
public int getDodgeBonus() {
return mDodgeBonus;
}
/** @param bonus The dodge bonus. */
public void setDodgeBonus(int bonus) {
if (mDodgeBonus != bonus) {
int[] data = preserveMoveAndDodge();
mDodgeBonus = bonus;
startNotify();
notifySingle(ID_DODGE_BONUS, new Integer(mDodgeBonus));
notifyIfMoveOrDodgeAltered(data);
endNotify();
}
}
/** @return The parry bonus. */
public int getParryBonus() {
return mParryBonus;
}
/** @param bonus The parry bonus. */
public void setParryBonus(int bonus) {
if (mParryBonus != bonus) {
mParryBonus = bonus;
notifySingle(ID_PARRY_BONUS, new Integer(mParryBonus));
}
}
/** @return The block bonus. */
public int getBlockBonus() {
return mBlockBonus;
}
/** @param bonus The block bonus. */
public void setBlockBonus(int bonus) {
if (mBlockBonus != bonus) {
mBlockBonus = bonus;
notifySingle(ID_BLOCK_BONUS, new Integer(mBlockBonus));
}
}
/** @return The current encumbrance level. */
public Encumbrance getEncumbranceLevel() {
double carried = getWeightCarried().getNormalizedValue();
for (Encumbrance encumbrance : Encumbrance.values()) {
if (carried <= getMaximumCarry(encumbrance).getNormalizedValue()) {
return encumbrance;
}
}
return Encumbrance.EXTRA_HEAVY;
}
/**
* @return <code>true</code> if the carried weight is greater than the maximum allowed for an
* extra-heavy load.
*/
public boolean isCarryingGreaterThanMaxLoad() {
return getWeightCarried().getNormalizedValue() > getMaximumCarry(Encumbrance.EXTRA_HEAVY).getNormalizedValue();
}
/** @return The current weight being carried. */
public WeightValue getWeightCarried() {
return mCachedWeightCarried;
}
/** @return The current wealth being carried. */
public double getWealthCarried() {
return mCachedWealthCarried;
}
/**
* Convert a metric {@link WeightValue} by GURPS Metric rules into an imperial one. If an
* imperial {@link WeightValue} is passed as an argument, it will be returned unchanged.
*
* @param value The {@link WeightValue} to be converted by GURPS Metric rules.
* @return The converted imperial {@link WeightValue}.
*/
public static WeightValue convertFromGurpsMetric(WeightValue value) {
switch (value.getUnits()) {
case G:
return new WeightValue(value.getValue() / 30, WeightUnits.OZ);
case KG:
return new WeightValue(value.getValue() * 2, WeightUnits.LB);
case T:
return new WeightValue(value.getValue(), WeightUnits.LT);
default:
return value;
}
}
/**
* Convert an imperial {@link WeightValue} by GURPS Metric rules into a metric one. If a metric
* {@link WeightValue} is passed as an argument, it will be returned unchanged.
*
* @param value The {@link WeightValue} to be converted by GURPS Metric rules.
* @return The converted metric {@link WeightValue}.
*/
public static WeightValue convertToGurpsMetric(WeightValue value) {
switch (value.getUnits()) {
case LB:
return new WeightValue(value.getValue() / 2, WeightUnits.KG);
case LT:
return new WeightValue(value.getValue(), WeightUnits.T);
case OZ:
return new WeightValue(value.getValue() * 30, WeightUnits.G);
case TN:
return new WeightValue(value.getValue(), WeightUnits.T);
default:
return value;
}
}
/**
* Calculate the total weight and wealth carried.
*
* @param notify Whether to send out notifications if the resulting values are different from
* the previous values.
*/
public void calculateWeightAndWealthCarried(boolean notify) {
WeightValue savedWeight = new WeightValue(mCachedWeightCarried);
double savedWealth = mCachedWealthCarried;
mCachedWeightCarried = new WeightValue(0, SheetPreferences.getWeightUnits());
mCachedWealthCarried = 0.0;
for (Row one : mEquipment.getTopLevelRows()) {
Equipment equipment = (Equipment) one;
if (equipment.isCarried()) {
WeightValue weight = new WeightValue(equipment.getExtendedWeight());
if (SheetPreferences.areGurpsMetricRulesUsed()) {
if (SheetPreferences.getWeightUnits().isMetric()) {
weight = GURPSCharacter.convertToGurpsMetric(weight);
} else {
weight = GURPSCharacter.convertFromGurpsMetric(weight);
}
}
mCachedWeightCarried.add(weight);
}
mCachedWealthCarried += equipment.getExtendedValue();
}
if (notify) {
if (!savedWeight.equals(mCachedWeightCarried)) {
notify(ID_CARRIED_WEIGHT, mCachedWeightCarried);
}
if (savedWealth != mCachedWealthCarried) {
notify(ID_CARRIED_WEALTH, new Double(mCachedWealthCarried));
}
}
}
private int[] preserveMoveAndDodge() {
Encumbrance[] values = Encumbrance.values();
int[] data = new int[values.length * 2];
for (Encumbrance encumbrance : values) {
int index = encumbrance.ordinal();
data[index] = getMove(encumbrance);
data[values.length + index] = getDodge(encumbrance);
}
return data;
}
private void notifyIfMoveOrDodgeAltered(int[] data) {
Encumbrance[] values = Encumbrance.values();
for (Encumbrance encumbrance : values) {
int index = encumbrance.ordinal();
int tmp = getDodge(encumbrance);
if (tmp != data[values.length + index]) {
notify(DODGE_PREFIX + index, new Integer(tmp));
}
tmp = getMove(encumbrance);
if (tmp != data[index]) {
notify(MOVE_PREFIX + index, new Integer(tmp));
}
}
}
/** @return The dexterity (DX). */
public int getDexterity() {
return mDexterity + mDexterityBonus;
}
/**
* Sets the dexterity (DX).
*
* @param dexterity The new dexterity.
*/
public void setDexterity(int dexterity) {
int oldDexterity = getDexterity();
if (oldDexterity != dexterity) {
postUndoEdit(DEXTERITY_UNDO, ID_DEXTERITY, new Integer(oldDexterity), new Integer(dexterity));
updateDexterityInfo(dexterity - mDexterityBonus, mDexterityBonus);
}
}
/** @return The dexterity bonus. */
public int getDexterityBonus() {
return mDexterityBonus;
}
/** @param bonus The new dexterity bonus. */
public void setDexterityBonus(int bonus) {
if (mDexterityBonus != bonus) {
updateDexterityInfo(mDexterity, bonus);
}
}
/** @param reduction The cost reduction for dexterity. */
public void setDexterityCostReduction(int reduction) {
if (mDexterityCostReduction != reduction) {
mDexterityCostReduction = reduction;
mNeedAttributePointCalculation = true;
}
}
private void updateDexterityInfo(int dexterity, int bonus) {
double speed = getBasicSpeed();
int move = getBasicMove();
int[] data = preserveMoveAndDodge();
double newSpeed;
int newMove;
mDexterity = dexterity;
mDexterityBonus = bonus;
startNotify();
notify(ID_DEXTERITY, new Integer(getDexterity()));
newSpeed = getBasicSpeed();
if (newSpeed != speed) {
notify(ID_BASIC_SPEED, new Double(newSpeed));
}
newMove = getBasicMove();
if (newMove != move) {
notify(ID_BASIC_MOVE, new Integer(newMove));
}
notifyIfMoveOrDodgeAltered(data);
updateSkills();
mNeedAttributePointCalculation = true;
endNotify();
}
/** @return The number of points spent on dexterity. */
public int getDexterityPoints() {
return getPointsForAttribute(mDexterity - 10, 20, mDexterityCostReduction);
}
/** @return The intelligence (IQ). */
public int getIntelligence() {
return mIntelligence + mIntelligenceBonus;
}
/**
* Sets the intelligence (IQ).
*
* @param intelligence The new intelligence.
*/
public void setIntelligence(int intelligence) {
int oldIntelligence = getIntelligence();
if (oldIntelligence != intelligence) {
postUndoEdit(INTELLIGENCE_UNDO, ID_INTELLIGENCE, new Integer(oldIntelligence), new Integer(intelligence));
updateIntelligenceInfo(intelligence - mIntelligenceBonus, mIntelligenceBonus);
}
}
/** @return The intelligence bonus. */
public int getIntelligenceBonus() {
return mIntelligenceBonus;
}
/** @param bonus The new intelligence bonus. */
public void setIntelligenceBonus(int bonus) {
if (mIntelligenceBonus != bonus) {
updateIntelligenceInfo(mIntelligence, bonus);
}
}
/** @param reduction The cost reduction for intelligence. */
public void setIntelligenceCostReduction(int reduction) {
if (mIntelligenceCostReduction != reduction) {
mIntelligenceCostReduction = reduction;
mNeedAttributePointCalculation = true;
}
}
private void updateIntelligenceInfo(int intelligence, int bonus) {
int perception = getPerception();
int will = getWill();
int newPerception;
int newWill;
mIntelligence = intelligence;
mIntelligenceBonus = bonus;
startNotify();
notify(ID_INTELLIGENCE, new Integer(getIntelligence()));
newPerception = getPerception();
if (newPerception != perception) {
notify(ID_PERCEPTION, new Integer(newPerception));
notify(ID_VISION, new Integer(getVision()));
notify(ID_HEARING, new Integer(getHearing()));
notify(ID_TASTE_AND_SMELL, new Integer(getTasteAndSmell()));
notify(ID_TOUCH, new Integer(getTouch()));
}
newWill = getWill();
if (newWill != will) {
notify(ID_WILL, new Integer(newWill));
notify(ID_FRIGHT_CHECK, new Integer(getFrightCheck()));
}
updateSkills();
updateSpells();
mNeedAttributePointCalculation = true;
endNotify();
}
/** @return The number of points spent on intelligence. */
public int getIntelligencePoints() {
return getPointsForAttribute(mIntelligence - 10, 20, mIntelligenceCostReduction);
}
/** @return The health (HT). */
public int getHealth() {
return mHealth + mHealthBonus;
}
/**
* Sets the health (HT).
*
* @param health The new health.
*/
public void setHealth(int health) {
int oldHealth = getHealth();
if (oldHealth != health) {
postUndoEdit(HEALTH_UNDO, ID_HEALTH, new Integer(oldHealth), new Integer(health));
updateHealthInfo(health - mHealthBonus, mHealthBonus);
}
}
/** @return The health bonus. */
public int getHealthBonus() {
return mHealthBonus;
}
/** @param bonus The new health bonus. */
public void setHealthBonus(int bonus) {
if (mHealthBonus != bonus) {
updateHealthInfo(mHealth, bonus);
}
}
/** @param reduction The cost reduction for health. */
public void setHealthCostReduction(int reduction) {
if (mHealthCostReduction != reduction) {
mHealthCostReduction = reduction;
mNeedAttributePointCalculation = true;
}
}
private void updateHealthInfo(int health, int bonus) {
double speed = getBasicSpeed();
int move = getBasicMove();
int[] data = preserveMoveAndDodge();
double newSpeed;
int tmp;
mHealth = health;
mHealthBonus = bonus;
startNotify();
notify(ID_HEALTH, new Integer(getHealth()));
newSpeed = getBasicSpeed();
if (newSpeed != speed) {
notify(ID_BASIC_SPEED, new Double(newSpeed));
}
tmp = getBasicMove();
if (tmp != move) {
notify(ID_BASIC_MOVE, new Integer(tmp));
}
notifyIfMoveOrDodgeAltered(data);
notifyOfBaseFatiguePointChange();
updateSkills();
mNeedAttributePointCalculation = true;
endNotify();
}
/** @return The number of points spent on health. */
public int getHealthPoints() {
return getPointsForAttribute(mHealth - 10, 10, mHealthCostReduction);
}
/** @return The total number of points this character has. */
public int getTotalPoints() {
return mTotalPoints;
}
/** @return The total number of points spent. */
public int getSpentPoints() {
return getAttributePoints() + getAdvantagePoints() + getDisadvantagePoints() + getQuirkPoints() + getSkillPoints() + getSpellPoints() + getRacePoints();
}
/** @return The number of earned points. */
public int getEarnedPoints() {
return mTotalPoints - getSpentPoints();
}
/**
* Sets the earned character points.
*
* @param earned The new earned character points.
*/
public void setEarnedPoints(int earned) {
int current = getEarnedPoints();
if (current != earned) {
Integer value = new Integer(earned);
postUndoEdit(EARNED_POINTS_UNDO, ID_EARNED_POINTS, new Integer(current), value);
mTotalPoints = earned + getSpentPoints();
startNotify();
notify(ID_EARNED_POINTS, value);
notify(ID_TOTAL_POINTS, new Integer(getTotalPoints()));
endNotify();
}
}
/** @return The number of points spent on basic attributes. */
public int getAttributePoints() {
return mCachedAttributePoints;
}
private void calculateAttributePoints() {
mCachedAttributePoints = getStrengthPoints() + getDexterityPoints() + getIntelligencePoints() + getHealthPoints() + getWillPoints() + getPerceptionPoints() + getBasicSpeedPoints() + getBasicMovePoints() + getHitPointPoints() + getFatiguePointPoints();
}
/** @return The number of points spent on a racial package. */
public int getRacePoints() {
return mCachedRacePoints;
}
/** @return The number of points spent on advantages. */
public int getAdvantagePoints() {
return mCachedAdvantagePoints;
}
/** @return The number of points spent on disadvantages. */
public int getDisadvantagePoints() {
return mCachedDisadvantagePoints;
}
/** @return The number of points spent on quirks. */
public int getQuirkPoints() {
return mCachedQuirkPoints;
}
private void calculateAdvantagePoints() {
mCachedAdvantagePoints = 0;
mCachedDisadvantagePoints = 0;
mCachedRacePoints = 0;
mCachedQuirkPoints = 0;
for (Advantage advantage : new FilteredIterator<>(mAdvantages.getTopLevelRows(), Advantage.class)) {
calculateSingleAdvantagePoints(advantage);
}
}
private void calculateSingleAdvantagePoints(Advantage advantage) {
if (advantage.canHaveChildren()) {
AdvantageContainerType type = advantage.getContainerType();
if (type == AdvantageContainerType.GROUP) {
for (Advantage child : new FilteredIterator<>(advantage.getChildren(), Advantage.class)) {
calculateSingleAdvantagePoints(child);
}
return;
} else if (type == AdvantageContainerType.RACE) {
mCachedRacePoints += advantage.getAdjustedPoints();
return;
}
}
int pts = advantage.getAdjustedPoints();
if (pts > 0) {
mCachedAdvantagePoints += pts;
} else if (pts < -1) {
mCachedDisadvantagePoints += pts;
} else if (pts == -1) {
mCachedQuirkPoints--;
}
}
/** @return The number of points spent on skills. */
public int getSkillPoints() {
return mCachedSkillPoints;
}
private void calculateSkillPoints() {
mCachedSkillPoints = 0;
for (Skill skill : getSkillsIterator()) {
if (!skill.canHaveChildren()) {
mCachedSkillPoints += skill.getPoints();
}
}
}
/** @return The number of points spent on spells. */
public int getSpellPoints() {
return mCachedSpellPoints;
}
private void calculateSpellPoints() {
mCachedSpellPoints = 0;
for (Spell spell : getSpellsIterator()) {
if (!spell.canHaveChildren()) {
mCachedSpellPoints += spell.getPoints();
}
}
}
/** @return Whether to include the punch natural weapon or not. */
public boolean includePunch() {
return mIncludePunch;
}
/** @param include Whether to include the punch natural weapon or not. */
public void setIncludePunch(boolean include) {
if (mIncludePunch != include) {
postUndoEdit(INCLUDE_PUNCH_UNDO, ID_INCLUDE_PUNCH, new Boolean(mIncludePunch), new Boolean(include));
mIncludePunch = include;
notifySingle(ID_INCLUDE_PUNCH, new Boolean(mIncludePunch));
}
}
/** @return Whether to include the kick natural weapon or not. */
public boolean includeKick() {
return mIncludeKick;
}
/** @param include Whether to include the kick natural weapon or not. */
public void setIncludeKick(boolean include) {
if (mIncludeKick != include) {
postUndoEdit(INCLUDE_KICK_UNDO, ID_INCLUDE_KICK, new Boolean(mIncludeKick), new Boolean(include));
mIncludeKick = include;
notifySingle(ID_INCLUDE_KICK, new Boolean(mIncludeKick));
}
}
/** @return Whether to include the kick w/boots natural weapon or not. */
public boolean includeKickBoots() {
return mIncludeKickBoots;
}
/** @param include Whether to include the kick w/boots natural weapon or not. */
public void setIncludeKickBoots(boolean include) {
if (mIncludeKickBoots != include) {
postUndoEdit(INCLUDE_BOOTS_UNDO, ID_INCLUDE_BOOTS, new Boolean(mIncludeKickBoots), new Boolean(include));
mIncludeKickBoots = include;
notifySingle(ID_INCLUDE_BOOTS, new Boolean(mIncludeKickBoots));
}
}
/** @return The hit points (HP). */
public int getHitPoints() {
return getStrength() + mHitPoints + mHitPointBonus;
}
/**
* Sets the hit points (HP).
*
* @param hp The new hit points.
*/
public void setHitPoints(int hp) {
int oldHP = getHitPoints();
if (oldHP != hp) {
postUndoEdit(HIT_POINTS_UNDO, ID_HIT_POINTS, new Integer(oldHP), new Integer(hp));
startNotify();
mHitPoints = hp - (getStrength() + mHitPointBonus);
mNeedAttributePointCalculation = true;
notifyOfBaseHitPointChange();
endNotify();
}
}
/** @return The number of points spent on hit points. */
public int getHitPointPoints() {
int pts = 2 * mHitPoints;
int sizeModifier = mDescription.getSizeModifier();
if (sizeModifier > 0) {
int rem;
if (sizeModifier > 8) {
sizeModifier = 8;
}
pts *= 10 - sizeModifier;
rem = pts % 10;
pts /= 10;
if (rem > 4) {
pts++;
} else if (rem < -5) {
pts--;
}
}
return pts;
}
/** @return The hit point bonus. */
public int getHitPointBonus() {
return mHitPointBonus;
}
/** @param bonus The hit point bonus. */
public void setHitPointBonus(int bonus) {
if (mHitPointBonus != bonus) {
mHitPointBonus = bonus;
notifyOfBaseHitPointChange();
}
}
private void notifyOfBaseHitPointChange() {
startNotify();
notify(ID_HIT_POINTS, new Integer(getHitPoints()));
notify(ID_DEATH_CHECK_1_HIT_POINTS, new Integer(getDeathCheck1HitPoints()));
notify(ID_DEATH_CHECK_2_HIT_POINTS, new Integer(getDeathCheck2HitPoints()));
notify(ID_DEATH_CHECK_3_HIT_POINTS, new Integer(getDeathCheck3HitPoints()));
notify(ID_DEATH_CHECK_4_HIT_POINTS, new Integer(getDeathCheck4HitPoints()));
notify(ID_DEAD_HIT_POINTS, new Integer(getDeadHitPoints()));
notify(ID_REELING_HIT_POINTS, new Integer(getReelingHitPoints()));
endNotify();
}
/** @return The current hit points. */
public String getCurrentHitPoints() {
return mCurrentHitPoints;
}
/**
* Sets the current hit points.
*
* @param hp The hit point amount.
*/
public void setCurrentHitPoints(String hp) {
if (!mCurrentHitPoints.equals(hp)) {
postUndoEdit(CURRENT_HIT_POINTS_UNDO, ID_CURRENT_HIT_POINTS, mCurrentHitPoints, hp);
mCurrentHitPoints = hp;
notifySingle(ID_CURRENT_HIT_POINTS, mCurrentHitPoints);
}
}
/** @return The number of hit points where "reeling" effects start. */
public int getReelingHitPoints() {
int reeling = (getHitPoints() - 1) / 3;
return reeling < 0 ? 0 : reeling;
}
/** @return The number of hit points where unconsciousness checks must start being made. */
@SuppressWarnings("static-method")
public int getUnconsciousChecksHitPoints() {
return 0;
}
/** @return The number of hit points where the first death check must be made. */
public int getDeathCheck1HitPoints() {
return -1 * getHitPoints();
}
/** @return The number of hit points where the second death check must be made. */
public int getDeathCheck2HitPoints() {
return -2 * getHitPoints();
}
/** @return The number of hit points where the third death check must be made. */
public int getDeathCheck3HitPoints() {
return -3 * getHitPoints();
}
/** @return The number of hit points where the fourth death check must be made. */
public int getDeathCheck4HitPoints() {
return -4 * getHitPoints();
}
/** @return The number of hit points where the character is just dead. */
public int getDeadHitPoints() {
return -5 * getHitPoints();
}
/** @return The will. */
public int getWill() {
return mWill + mWillBonus + (SheetPreferences.areOptionalIQRulesUsed() ? 10 : getIntelligence());
}
/** @param will The new will. */
public void setWill(int will) {
int oldWill = getWill();
if (oldWill != will) {
postUndoEdit(WILL_UNDO, ID_WILL, new Integer(oldWill), new Integer(will));
updateWillInfo(will - (mWillBonus + (SheetPreferences.areOptionalIQRulesUsed() ? 10 : getIntelligence())), mWillBonus);
}
}
/** @return The will bonus. */
public int getWillBonus() {
return mWillBonus;
}
/** @param bonus The new will bonus. */
public void setWillBonus(int bonus) {
if (mWillBonus != bonus) {
updateWillInfo(mWill, bonus);
}
}
private void updateWillInfo(int will, int bonus) {
mWill = will;
mWillBonus = bonus;
startNotify();
notify(ID_WILL, new Integer(getWill()));
notify(ID_FRIGHT_CHECK, new Integer(getFrightCheck()));
updateSkills();
mNeedAttributePointCalculation = true;
endNotify();
}
/** Called to ensure notifications are sent out when the optional IQ rule use is changed. */
public void updateWillAndPerceptionDueToOptionalIQRuleUseChange() {
updateWillInfo(mWill, mWillBonus);
updatePerceptionInfo(mPerception, mPerceptionBonus);
}
/** @return The number of points spent on will. */
public int getWillPoints() {
return mWill * 5;
}
/** @return The fright check. */
public int getFrightCheck() {
return getWill() + mFrightCheckBonus;
}
/** @return The fright check bonus. */
public int getFrightCheckBonus() {
return mFrightCheckBonus;
}
/** @param bonus The new fright check bonus. */
public void setFrightCheckBonus(int bonus) {
if (mFrightCheckBonus != bonus) {
mFrightCheckBonus = bonus;
startNotify();
notify(ID_FRIGHT_CHECK, new Integer(getFrightCheck()));
endNotify();
}
}
/** @return The vision. */
public int getVision() {
return getPerception() + mVisionBonus;
}
/** @return The vision bonus. */
public int getVisionBonus() {
return mVisionBonus;
}
/** @param bonus The new vision bonus. */
public void setVisionBonus(int bonus) {
if (mVisionBonus != bonus) {
mVisionBonus = bonus;
startNotify();
notify(ID_VISION, new Integer(getVision()));
endNotify();
}
}
/** @return The hearing. */
public int getHearing() {
return getPerception() + mHearingBonus;
}
/** @return The hearing bonus. */
public int getHearingBonus() {
return mHearingBonus;
}
/** @param bonus The new hearing bonus. */
public void setHearingBonus(int bonus) {
if (mHearingBonus != bonus) {
mHearingBonus = bonus;
startNotify();
notify(ID_HEARING, new Integer(getHearing()));
endNotify();
}
}
/** @return The touch perception. */
public int getTouch() {
return getPerception() + mTouchBonus;
}
/** @return The touch bonus. */
public int getTouchBonus() {
return mTouchBonus;
}
/** @param bonus The new touch bonus. */
public void setTouchBonus(int bonus) {
if (mTouchBonus != bonus) {
mTouchBonus = bonus;
startNotify();
notify(ID_TOUCH, new Integer(getTouch()));
endNotify();
}
}
/** @return The taste and smell perception. */
public int getTasteAndSmell() {
return getPerception() + mTasteAndSmellBonus;
}
/** @return The taste and smell bonus. */
public int getTasteAndSmellBonus() {
return mTasteAndSmellBonus;
}
/** @param bonus The new taste and smell bonus. */
public void setTasteAndSmellBonus(int bonus) {
if (mTasteAndSmellBonus != bonus) {
mTasteAndSmellBonus = bonus;
startNotify();
notify(ID_TASTE_AND_SMELL, new Integer(getTasteAndSmell()));
endNotify();
}
}
/** @return The perception (Per). */
public int getPerception() {
return mPerception + mPerceptionBonus + (SheetPreferences.areOptionalIQRulesUsed() ? 10 : getIntelligence());
}
/**
* Sets the perception.
*
* @param perception The new perception.
*/
public void setPerception(int perception) {
int oldPerception = getPerception();
if (oldPerception != perception) {
postUndoEdit(PERCEPTION_UNDO, ID_PERCEPTION, new Integer(oldPerception), new Integer(perception));
updatePerceptionInfo(perception - (mPerceptionBonus + (SheetPreferences.areOptionalIQRulesUsed() ? 10 : getIntelligence())), mPerceptionBonus);
}
}
/** @return The perception bonus. */
public int getPerceptionBonus() {
return mPerceptionBonus;
}
/** @param bonus The new perception bonus. */
public void setPerceptionBonus(int bonus) {
if (mPerceptionBonus != bonus) {
updatePerceptionInfo(mPerception, bonus);
}
}
private void updatePerceptionInfo(int perception, int bonus) {
mPerception = perception;
mPerceptionBonus = bonus;
startNotify();
notify(ID_PERCEPTION, new Integer(getPerception()));
notify(ID_VISION, new Integer(getVision()));
notify(ID_HEARING, new Integer(getHearing()));
notify(ID_TASTE_AND_SMELL, new Integer(getTasteAndSmell()));
notify(ID_TOUCH, new Integer(getTouch()));
updateSkills();
mNeedAttributePointCalculation = true;
endNotify();
}
/** @return The number of points spent on perception. */
public int getPerceptionPoints() {
return mPerception * 5;
}
/** @return The fatigue points (FP). */
public int getFatiguePoints() {
return getHealth() + mFatiguePoints + mFatiguePointBonus;
}
/**
* Sets the fatigue points (HP).
*
* @param fp The new fatigue points.
*/
public void setFatiguePoints(int fp) {
int oldFP = getFatiguePoints();
if (oldFP != fp) {
postUndoEdit(FATIGUE_POINTS_UNDO, ID_FATIGUE_POINTS, new Integer(oldFP), new Integer(fp));
startNotify();
mFatiguePoints = fp - (getHealth() + mFatiguePointBonus);
mNeedAttributePointCalculation = true;
notifyOfBaseFatiguePointChange();
endNotify();
}
}
/** @return The number of points spent on fatigue points. */
public int getFatiguePointPoints() {
return 3 * mFatiguePoints;
}
/** @return The fatigue point bonus. */
public int getFatiguePointBonus() {
return mFatiguePointBonus;
}
/** @param bonus The fatigue point bonus. */
public void setFatiguePointBonus(int bonus) {
if (mFatiguePointBonus != bonus) {
mFatiguePointBonus = bonus;
notifyOfBaseFatiguePointChange();
}
}
private void notifyOfBaseFatiguePointChange() {
startNotify();
notify(ID_FATIGUE_POINTS, new Integer(getFatiguePoints()));
notify(ID_UNCONSCIOUS_CHECKS_FATIGUE_POINTS, new Integer(getUnconsciousChecksFatiguePoints()));
notify(ID_UNCONSCIOUS_FATIGUE_POINTS, new Integer(getUnconsciousFatiguePoints()));
notify(ID_TIRED_FATIGUE_POINTS, new Integer(getTiredFatiguePoints()));
endNotify();
}
/** @return The current fatigue points. */
public String getCurrentFatiguePoints() {
return mCurrentFatiguePoints;
}
/**
* Sets the current fatigue points.
*
* @param fp The fatigue point amount.
*/
public void setCurrentFatiguePoints(String fp) {
if (!mCurrentFatiguePoints.equals(fp)) {
postUndoEdit(CURRENT_FATIGUE_POINTS_UNDO, ID_CURRENT_FATIGUE_POINTS, mCurrentFatiguePoints, fp);
mCurrentFatiguePoints = fp;
notifySingle(ID_CURRENT_FATIGUE_POINTS, mCurrentFatiguePoints);
}
}
/** @return The number of fatigue points where "tired" effects start. */
public int getTiredFatiguePoints() {
int tired = (getFatiguePoints() - 1) / 3;
return tired < 0 ? 0 : tired;
}
/** @return The number of fatigue points where unconsciousness checks must start being made. */
@SuppressWarnings("static-method")
public int getUnconsciousChecksFatiguePoints() {
return 0;
}
/** @return The number of hit points where the character falls over, unconscious. */
public int getUnconsciousFatiguePoints() {
return -1 * getFatiguePoints();
}
/** @return The {@link Profile} data. */
public Profile getDescription() {
return mDescription;
}
/** @return The {@link Armor} stats. */
public Armor getArmor() {
return mArmor;
}
/** @return The outline model for the character's advantages. */
public OutlineModel getAdvantagesModel() {
return mAdvantages;
}
/**
* @param includeDisabled <code>true</code> if disabled entries should be included.
* @return A recursive iterator over the character's advantages.
*/
public RowIterator<Advantage> getAdvantagesIterator(boolean includeDisabled) {
if (includeDisabled) {
return new RowIterator<>(mAdvantages);
}
return new RowIterator<>(mAdvantages, (row) -> {
return row.isEnabled();
});
}
/**
* Searches the character's current advantages list for the specified name.
*
* @param name The name to look for.
* @return The advantage, if present, or <code>null</code>.
*/
public Advantage getAdvantageNamed(String name) {
for (Advantage advantage : getAdvantagesIterator(false)) {
if (advantage.getName().equals(name)) {
return advantage;
}
}
return null;
}
/**
* Searches the character's current advantages list for the specified name.
*
* @param name The name to look for.
* @return Whether it is present or not.
*/
public boolean hasAdvantageNamed(String name) {
return getAdvantageNamed(name) != null;
}
/** @return The outline model for the character's skills. */
public OutlineModel getSkillsRoot() {
return mSkills;
}
/** @return A recursive iterable for the character's skills. */
public RowIterator<Skill> getSkillsIterator() {
return new RowIterator<>(mSkills);
}
/**
* Searches the character's current skill list for the specified name.
*
* @param name The name to look for.
* @param specialization The specialization to look for. Pass in <code>null</code> or an empty
* string to ignore.
* @param requirePoints Only look at {@link Skill}s that have points.
* @param excludes The set of {@link Skill}s to exclude from consideration.
* @return The skill if it is present, or <code>null</code> if its not.
*/
public ArrayList<Skill> getSkillNamed(String name, String specialization, boolean requirePoints, HashSet<String> excludes) {
ArrayList<Skill> skills = new ArrayList<>();
boolean checkSpecialization = specialization != null && specialization.length() > 0;
for (Skill skill : getSkillsIterator()) {
if (excludes == null || !excludes.contains(skill.toString())) {
if (!requirePoints || skill.getPoints() > 0) {
if (skill.getName().equalsIgnoreCase(name)) {
if (!checkSpecialization || skill.getSpecialization().equalsIgnoreCase(specialization)) {
skills.add(skill);
}
}
}
}
}
return skills;
}
/**
* Searches the character's current {@link Skill} list for the {@link Skill} with the best level
* that matches the name.
*
* @param name The {@link Skill} name to look for.
* @param specialization An optional specialization to look for. Pass <code>null</code> if it is
* not needed.
* @param requirePoints Only look at {@link Skill}s that have points.
* @param excludes The set of {@link Skill}s to exclude from consideration.
* @return The {@link Skill} that matches with the highest level.
*/
public Skill getBestSkillNamed(String name, String specialization, boolean requirePoints, HashSet<String> excludes) {
Skill best = null;
int level = Integer.MIN_VALUE;
for (Skill skill : getSkillNamed(name, specialization, requirePoints, excludes)) {
int skillLevel = skill.getLevel(excludes);
if (best == null || skillLevel > level) {
best = skill;
level = skillLevel;
}
}
return best;
}
/** @return The outline model for the character's spells. */
public OutlineModel getSpellsRoot() {
return mSpells;
}
/** @return A recursive iterator over the character's spells. */
public RowIterator<Spell> getSpellsIterator() {
return new RowIterator<>(mSpells);
}
/** @return The outline model for the character's equipment. */
public OutlineModel getEquipmentRoot() {
return mEquipment;
}
/** @return A recursive iterator over the character's equipment. */
public RowIterator<Equipment> getEquipmentIterator() {
return new RowIterator<>(mEquipment);
}
/** @return The outline model for the character's notes. */
public OutlineModel getNotesRoot() {
return mNotes;
}
/** @return A recursive iterator over the character's notes. */
public RowIterator<Note> getNoteIterator() {
return new RowIterator<>(mNotes);
}
/** @param map The new feature map. */
public void setFeatureMap(HashMap<String, ArrayList<Feature>> map) {
mFeatureMap = map;
mSkillsUpdated = false;
mSpellsUpdated = false;
startNotify();
setStrengthBonus(getIntegerBonusFor(ID_STRENGTH));
setStrengthCostReduction(getCostReductionFor(ID_STRENGTH));
setLiftingStrengthBonus(getIntegerBonusFor(ID_LIFTING_STRENGTH));
setStrikingStrengthBonus(getIntegerBonusFor(ID_STRIKING_STRENGTH));
setDexterityBonus(getIntegerBonusFor(ID_DEXTERITY));
setDexterityCostReduction(getCostReductionFor(ID_DEXTERITY));
setIntelligenceBonus(getIntegerBonusFor(ID_INTELLIGENCE));
setIntelligenceCostReduction(getCostReductionFor(ID_INTELLIGENCE));
setHealthBonus(getIntegerBonusFor(ID_HEALTH));
setHealthCostReduction(getCostReductionFor(ID_HEALTH));
setWillBonus(getIntegerBonusFor(ID_WILL));
setFrightCheckBonus(getIntegerBonusFor(ID_FRIGHT_CHECK));
setPerceptionBonus(getIntegerBonusFor(ID_PERCEPTION));
setVisionBonus(getIntegerBonusFor(ID_VISION));
setHearingBonus(getIntegerBonusFor(ID_HEARING));
setTasteAndSmellBonus(getIntegerBonusFor(ID_TASTE_AND_SMELL));
setTouchBonus(getIntegerBonusFor(ID_TOUCH));
setHitPointBonus(getIntegerBonusFor(ID_HIT_POINTS));
setFatiguePointBonus(getIntegerBonusFor(ID_FATIGUE_POINTS));
mDescription.update();
setDodgeBonus(getIntegerBonusFor(ID_DODGE_BONUS));
setParryBonus(getIntegerBonusFor(ID_PARRY_BONUS));
setBlockBonus(getIntegerBonusFor(ID_BLOCK_BONUS));
setBasicSpeedBonus(getDoubleBonusFor(ID_BASIC_SPEED));
setBasicMoveBonus(getIntegerBonusFor(ID_BASIC_MOVE));
mArmor.update();
if (!mSkillsUpdated) {
updateSkills();
}
if (!mSpellsUpdated) {
updateSpells();
}
endNotify();
}
/**
* @param id The cost reduction ID to search for.
* @return The cost reduction, as a percentage.
*/
public int getCostReductionFor(String id) {
int total = 0;
ArrayList<Feature> list = mFeatureMap.get(id.toLowerCase());
if (list != null) {
for (Feature feature : list) {
if (feature instanceof CostReduction) {
total += ((CostReduction) feature).getPercentage();
}
}
}
if (total > 80) {
total = 80;
}
return total;
}
/**
* @param id The feature ID to search for.
* @return The bonus.
*/
public int getIntegerBonusFor(String id) {
int total = 0;
ArrayList<Feature> list = mFeatureMap.get(id.toLowerCase());
if (list != null) {
for (Feature feature : list) {
if (feature instanceof Bonus && !(feature instanceof WeaponBonus)) {
total += ((Bonus) feature).getAmount().getIntegerAdjustedAmount();
}
}
}
return total;
}
/**
* @param id The feature ID to search for.
* @param nameQualifier The name qualifier.
* @param specializationQualifier The specialization qualifier.
* @return The bonuses.
*/
public ArrayList<WeaponBonus> getWeaponComparedBonusesFor(String id, String nameQualifier, String specializationQualifier) {
ArrayList<WeaponBonus> bonuses = new ArrayList<>();
int rsl = Integer.MIN_VALUE;
for (Skill skill : getSkillNamed(nameQualifier, specializationQualifier, true, null)) {
int srsl = skill.getRelativeLevel();
if (srsl > rsl) {
rsl = srsl;
}
}
if (rsl != Integer.MIN_VALUE) {
ArrayList<Feature> list = mFeatureMap.get(id.toLowerCase());
if (list != null) {
for (Feature feature : list) {
if (feature instanceof WeaponBonus) {
WeaponBonus bonus = (WeaponBonus) feature;
if (bonus.getNameCriteria().matches(nameQualifier) && bonus.getSpecializationCriteria().matches(specializationQualifier) && bonus.getLevelCriteria().matches(rsl)) {
bonuses.add(bonus);
}
}
}
}
}
return bonuses;
}
/**
* @param id The feature ID to search for.
* @param nameQualifier The name qualifier.
* @param specializationQualifier The specialization qualifier.
* @return The bonus.
*/
public int getSkillComparedIntegerBonusFor(String id, String nameQualifier, String specializationQualifier) {
int total = 0;
ArrayList<Feature> list = mFeatureMap.get(id.toLowerCase());
if (list != null) {
for (Feature feature : list) {
if (feature instanceof SkillBonus) {
SkillBonus bonus = (SkillBonus) feature;
if (bonus.getNameCriteria().matches(nameQualifier) && bonus.getSpecializationCriteria().matches(specializationQualifier)) {
total += bonus.getAmount().getIntegerAdjustedAmount();
}
}
}
}
return total;
}
/**
* @param id The feature ID to search for.
* @param qualifier The qualifier.
* @return The bonus.
*/
public int getSpellComparedIntegerBonusFor(String id, String qualifier) {
int total = 0;
ArrayList<Feature> list = mFeatureMap.get(id.toLowerCase());
if (list != null) {
for (Feature feature : list) {
if (feature instanceof SpellBonus) {
SpellBonus bonus = (SpellBonus) feature;
if (bonus.getNameCriteria().matches(qualifier)) {
total += bonus.getAmount().getIntegerAdjustedAmount();
}
}
}
}
return total;
}
/**
* @param id The feature ID to search for.
* @return The bonus.
*/
public double getDoubleBonusFor(String id) {
double total = 0;
ArrayList<Feature> list = mFeatureMap.get(id.toLowerCase());
if (list != null) {
for (Feature feature : list) {
if (feature instanceof Bonus && !(feature instanceof WeaponBonus)) {
total += ((Bonus) feature).getAmount().getAdjustedAmount();
}
}
}
return total;
}
/**
* Post an undo edit if we're not currently in an undo.
*
* @param name The name of the undo.
* @param id The ID of the field being changed.
* @param before The original value.
* @param after The new value.
*/
void postUndoEdit(String name, String id, Object before, Object after) {
StdUndoManager mgr = getUndoManager();
if (!mgr.isInTransaction()) {
boolean add;
if (before instanceof ListRow) {
add = !((ListRow) before).isEquivalentTo(after);
} else {
add = !before.equals(after);
}
if (add) {
addEdit(new CharacterFieldUndo(this, name, id, before, after));
}
}
}
}