package com.hero.objects.characteristics;

import java.util.ArrayList;
import java.util.Hashtable;

import org.jdom.Element;

import com.hero.HeroDesigner;
import com.hero.objects.Adder;
import com.hero.objects.CharAffectingObject;
import com.hero.objects.ElementalControl;
import com.hero.objects.GenericObject;
import com.hero.objects.Multipower;
import com.hero.objects.VariablePowerPool;
import com.hero.objects.modifiers.Modifier;
import com.hero.objects.powers.Automaton;
import com.hero.objects.powers.CompoundPower;
import com.hero.objects.powers.Power;
import com.hero.ui.dialog.CharacteristicDialog;
import com.hero.ui.dialog.GenericDialog;
import com.hero.util.Constants;
import com.hero.util.Rounder;
import com.hero.util.XMLUtility;

/**
 * Copyright (c) 2000 - 2005, CompNet Design, Inc. All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, is prohibited unless the following conditions are met: 1.
 * Express written consent of CompNet Design, Inc. is obtained by the developer.
 * 2. Redistributions must retain this copyright notice. THIS SOFTWARE IS
 * PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 * EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 * @author CompNet Design, Inc.
 * @version $Revision$
 */

public class Characteristic extends CharAffectingObject {
	protected boolean addModifiersToBase;

	protected long assignedModifiersCalcTime;

	protected ArrayList<Modifier> assignedMods;

	protected double baseLevel;

	protected double baseValue;

	protected long baseValueCalcTime;

	protected double doubleBase;

	protected double figuredMinValue;

	protected long figuredMinValueCalcTime;

	protected int maxVal;

	double ncmCharValue;

	double ncmCharValueCalcTime;

	protected Hashtable<String, Integer> ncmLevels;

	protected double origBaseLevel;

	protected double primaryValue;

	protected long primaryValueCalcTime;

	protected double secondaryValue;

	protected long secondaryValueCalcTime;

	protected boolean showRoll;

	public Characteristic(Element root, String xmlID) {
		super(root, xmlID);
		affectsPrimary = true;
	}

	/**
	 * Whether to add the Assigned Modifiers to the base Characteristic. This
	 * setting is ignored for non-Powers.
	 * 
	 * @return
	 */
	public boolean addModifiersToBase() {
		if (!isPower()) {
			return false;
		} else {
			return addModifiersToBase;
		}
	}

	void calcAssignedModifiers() {
		ArrayList<Modifier> ret = super.getAssignedModifiers();
		if (getParentList() != null
				&& (getParentList() instanceof Multipower
						|| getParentList() instanceof ElementalControl || getParentList() instanceof VariablePowerPool)) {
			if (GenericObject.findObjectByID(ret, "NOFIGURED") == null
					&& GenericObject.findObjectByID(availableModifiers,
							"NOFIGURED") != null
					&& HeroDesigner.getActiveHero().getRules()
							.automaticallyApplyNoFigured()) {
				Modifier mod = (Modifier) GenericObject.findObjectByID(
						availableModifiers, "NOFIGURED");
				mod.setParent(this);
				ret.add(mod);
			}
		}
		if (isPower()) {
			assignedMods = ret;
			return;
		}
		ret = (ArrayList<Modifier>) ret.clone();
		if (!isPower() && HeroDesigner.getActiveHero() != null) {
			for (int i = 0; i < HeroDesigner.getActiveHero().getPowers().size(); i++) {
				if (HeroDesigner.getActiveHero().getPowers().get(i) instanceof Characteristic) {
					Characteristic ch = (Characteristic) HeroDesigner
							.getActiveHero().getPowers().get(i);
					if (ch.getXMLID().equals(getXMLID())
							&& ch.addModifiersToBase() && ch.getID() != getID()) {
						for (Modifier mod : ch.getAssignedModifiers()) {
							if (GenericObject.findObjectByID(ret, mod
									.getXMLID()) == null) {
								ret.add(mod);
							}
						}
					}
				} else if (HeroDesigner.getActiveHero().getPowers().get(i) instanceof CompoundPower) {
					CompoundPower cp = (CompoundPower) HeroDesigner
							.getActiveHero().getPowers().get(i);
					for (int j = 0; j < cp.getPowers().size(); j++) {
						if (cp.getPowers().get(j) instanceof Characteristic) {
							Characteristic ch = (Characteristic) cp.getPowers()
									.get(j);
							if (ch.getXMLID().equals(getXMLID())
									&& ch.addModifiersToBase()
									&& ch.getID() != getID()) {
								for (Modifier mod : ch.getAssignedModifiers()) {
									if (GenericObject.findObjectByID(ret, mod
											.getXMLID()) == null) {
										ret.add(mod);
									}
								}
							}
						}
					}
				}
			}
			for (int i = 0; i < HeroDesigner.getActiveHero().getEquipment()
					.size(); i++) {
				if (HeroDesigner.getActiveHero().getEquipment().get(i) instanceof Characteristic) {
					Characteristic ch = (Characteristic) HeroDesigner
							.getActiveHero().getEquipment().get(i);
					if (ch.getXMLID().equals(getXMLID())
							&& ch.addModifiersToBase() && ch.getID() != getID()) {
						for (Modifier mod : ch.getAssignedModifiers()) {
							if (GenericObject.findObjectByID(ret, mod
									.getXMLID()) == null) {
								ret.add(mod);
							}
						}
					}
				} else if (HeroDesigner.getActiveHero().getEquipment().get(i) instanceof CompoundPower) {
					CompoundPower cp = (CompoundPower) HeroDesigner
							.getActiveHero().getEquipment().get(i);
					for (int j = 0; j < cp.getPowers().size(); j++) {
						if (cp.getPowers().get(j) instanceof Characteristic) {
							Characteristic ch = (Characteristic) cp.getPowers()
									.get(j);
							if (ch.getXMLID().equals(getXMLID())
									&& ch.addModifiersToBase()
									&& ch.getID() != getID()) {
								for (Modifier mod : ch.getAssignedModifiers()) {
									if (GenericObject.findObjectByID(ret, mod
											.getXMLID()) == null) {
										ret.add(mod);
									}
								}
							}
						}
					}
				}
			}
		}
		assignedMods = ret;
		assignedModifiersCalcTime = System.currentTimeMillis();
	}

	void calcBaseValue() {
		double bonus = 0;
		if (HeroDesigner.getActiveHero() != null) {
			for (int i = 0; i < HeroDesigner.getActiveHero()
					.getCharacteristics().size(); i++) {
				Characteristic ch = (Characteristic) HeroDesigner
						.getActiveHero().getCharacteristics().get(i);
				if (!ch.getXMLID().equals(getXMLID())
						&& ch.getIncreaseLevels(getType()) > 0
						&& ch.getIncrease(getType()) != 0) {
					double mult = ch.getCharacteristicValue()
							* ch.getIncrease(getType())
							/ ch.getIncreaseLevels(getType());
					doubleBase += mult;
					bonus += Rounder.roundHalfUp(mult);
				}
			}
			for (int i = 0; i < HeroDesigner.getActiveHero().getPowers().size(); i++) {
				if (HeroDesigner.getActiveHero().getPowers().get(i).getXMLID()
						.equals(getXMLID())) {
					continue; // only interested in figured
				} else if (HeroDesigner.getActiveHero().getPowers().get(i) instanceof CompoundPower) {
					CompoundPower cp = (CompoundPower) HeroDesigner
							.getActiveHero().getPowers().get(i);
					for (int j = 0; j < cp.getPowers().size(); j++) {
						if (cp.getPowers().get(j).getXMLID().equals(getXMLID())) {
							continue; // only interested in figured
						} else if (cp.getPowers().get(j) instanceof Characteristic) {
							CharAffectingObject power = (CharAffectingObject) cp
									.getPowers().get(j);
							if (power.getIncreaseLevels(getType()) > 0
									&& power.getIncrease(getType()) != 0) {
								if (power.getAffectPrimary()
										&& CharAffectingObject.checkFigured(
												power, getType())
										&& power.getAffectTotal()) {
									double mult = power.getIncreaseValue(
											getType(), true);
									doubleBase += mult;
									bonus += Rounder.roundHalfUp(mult);
								}
							}
						}
					}
				} else if (HeroDesigner.getActiveHero().getPowers().get(i) instanceof Characteristic) {
					CharAffectingObject power = (CharAffectingObject) HeroDesigner
							.getActiveHero().getPowers().get(i);
					if (power.getIncreaseLevels(getType()) > 0
							&& power.getIncrease(getType()) != 0) {
						if (power.getAffectPrimary()
								&& CharAffectingObject.checkFigured(power,
										getType()) && power.getAffectTotal()) {
							double mult = power.getIncreaseValue(getType(),
									true);
							doubleBase += mult;
							bonus += Rounder.roundHalfUp(mult);
						}
					}
				}
			}
			for (int i = 0; i < HeroDesigner.getActiveHero().getEquipment()
					.size(); i++) {
				if (HeroDesigner.getActiveHero().getEquipment().get(i)
						.getXMLID().equals(getXMLID())) {
					continue; // only interested in figured
				} else if (HeroDesigner.getActiveHero().getEquipment().get(i) instanceof CompoundPower) {
					CompoundPower cp = (CompoundPower) HeroDesigner
							.getActiveHero().getEquipment().get(i);
					for (int j = 0; j < cp.getPowers().size(); j++) {
						if (cp.getPowers().get(j).getXMLID().equals(getXMLID())) {
							continue; // only interested in figured
						} else if (cp.getPowers().get(j) instanceof Characteristic) {
							CharAffectingObject power = (CharAffectingObject) cp
									.getPowers().get(j);
							if (power.getIncreaseLevels(getType()) > 0
									&& power.getIncrease(getType()) != 0) {
								if (power.getAffectPrimary()
										&& CharAffectingObject.checkFigured(
												power, getType())
										&& power.getAffectTotal()) {
									double mult = power.getIncreaseValue(
											getType(), true);
									doubleBase += mult;
									bonus += Rounder.roundHalfUp(mult);
								}
							}
						}
					}
				} else if (HeroDesigner.getActiveHero().getEquipment().get(i) instanceof Characteristic) {
					CharAffectingObject power = (CharAffectingObject) HeroDesigner
							.getActiveHero().getEquipment().get(i);
					if (power.getIncreaseLevels(getType()) > 0
							&& power.getIncrease(getType()) != 0) {
						if (power.getAffectPrimary()
								&& CharAffectingObject.checkFigured(power,
										getType()) && power.getAffectTotal()) {
							double mult = power.getIncreaseValue(getType(),
									true);
							doubleBase += mult;
							bonus += Rounder.roundHalfUp(mult);
						}
					}
				}
			}
		}
		if (baseLevel + bonus < maxVal) {
			baseValue = baseLevel + bonus;
		} else {
			baseValue = maxVal;
		}
		baseValueCalcTime = System.currentTimeMillis();
	}

	/**
	 * Dumb method required to handle the fucked up calculation of pre on
	 * Vehicles.
	 * 
	 * @return
	 */
	public double getPREIncreaseValue() {
		if (getIncreaseLevels(Constants.PRE) > 0
				&& getIncrease(Constants.PRE) != 0) {
			return getCharacteristicValue() * getIncrease(Constants.PRE)
					/ getIncreaseLevels(Constants.PRE);
		} else {
			return 0;
		}
	}

	void calcFiguredMinValue() {
		double bonus = 0;
		if (HeroDesigner.getActiveHero() != null) {
			baseLevel = origBaseLevel;
			doubleBase = origBaseLevel;
			for (int i = 0; i < HeroDesigner.getActiveHero()
					.getCharacteristics().size(); i++) {
				Characteristic ch = (Characteristic) HeroDesigner
						.getActiveHero().getCharacteristics().get(i);
				if (!ch.getXMLID().equals(getXMLID())
						&& ch.getIncreaseLevels(getType()) > 0
						&& ch.getIncrease(getType()) != 0) {
					double mult = ch.getCharacteristicValue()
							* ch.getIncrease(getType())
							/ ch.getIncreaseLevels(getType());
					doubleBase += mult;
					bonus += Rounder.roundHalfUp(mult);
				}
			}
			for (int i = 0; i < HeroDesigner.getActiveHero().getPowers().size(); i++) {
				if (HeroDesigner.getActiveHero().getPowers().get(i).getXMLID()
						.equals(getXMLID())) {
					continue;
				} else if (HeroDesigner.getActiveHero().getPowers().get(i) instanceof Power) {
					CharAffectingObject power = (CharAffectingObject) HeroDesigner
							.getActiveHero().getPowers().get(i);
					if (power.getIncreaseLevels(getType()) > 0) {
						if (power.getAffectPrimary() && power.getAffectTotal()) {
							if (CharAffectingObject.checkFigured(power,
									getType())) {
								double mult = power.getIncreaseValue(getType(),
										true);
								doubleBase += mult;
								bonus += Rounder.roundHalfUp(mult);
							}
						}
					}
				}
			}
			for (int i = 0; i < HeroDesigner.getActiveHero().getEquipment()
					.size(); i++) {
				if (HeroDesigner.getActiveHero().getEquipment().get(i)
						.getXMLID().equals(getXMLID())) {
					continue;
				} else if (HeroDesigner.getActiveHero().getEquipment().get(i) instanceof Power) {
					CharAffectingObject power = (CharAffectingObject) HeroDesigner
							.getActiveHero().getEquipment().get(i);
					if (power.getIncreaseLevels(getType()) > 0) {
						if (power.getAffectPrimary() && power.getAffectTotal()) {
							if (CharAffectingObject.checkFigured(power,
									getType())) {
								double mult = power.getIncreaseValue(getType(),
										true);
								doubleBase += mult;
								bonus += Rounder.roundHalfUp(mult);
							}
						}
					}
				}
			}
		}
		if (baseLevel + bonus < maxVal) {
			figuredMinValue = baseLevel + bonus;
		} else {
			figuredMinValue = maxVal;
		}
		figuredMinValueCalcTime = System.currentTimeMillis();
	}

	void calcNCMCharValue() {
		double bonus = getLevels() + origBaseLevel;
		if (HeroDesigner.getActiveHero() != null) {
			for (int i = 0; i < HeroDesigner.getActiveHero()
					.getCharacteristics().size(); i++) {
				Characteristic ch = (Characteristic) HeroDesigner
						.getActiveHero().getCharacteristics().get(i);
				if (!ch.getXMLID().equals(getXMLID())
						&& ch.getIncreaseLevels(getType()) > 0
						&& ch.getIncrease(getType()) != 0) {
					double mult = ch.getNCMCharValue()
							* ch.getIncrease(getType())
							/ ch.getIncreaseLevels(getType());
					doubleBase += mult;
					bonus += Rounder.roundHalfUp(mult);
				}
			}
			if (bonus < maxVal) {
				ncmCharValue = bonus;
			} else {
				ncmCharValue = maxVal;
			}
			ncmCharValueCalcTime = System.currentTimeMillis();
		}
	}

	void calcPrimaryValue() {
		double bonus = getCharacteristicValue();
		if (isPower) { // need to add in the actual value purchased on the
			// characteristic....
			for (GenericObject o : HeroDesigner.getActiveHero()
					.getCharacteristics()) {
				if (o.getXMLID().equals(getXMLID())) {
					doubleBase += o.getLevels();
				}
			}
		}
		for (int i = 0; i < HeroDesigner.getActiveHero().getPowers().size(); i++) {
			if (HeroDesigner.getActiveHero().getPowers().get(i).getXMLID()
					.equals(getXMLID())) {
				Characteristic power = (Characteristic) HeroDesigner
						.getActiveHero().getPowers().get(i);
				if (power.getAffectPrimary() && power.getAffectTotal()) {
					bonus += power.getLevels();
					doubleBase += power.getLevels();
				}
			} else if (HeroDesigner.getActiveHero().getPowers().get(i) instanceof CompoundPower) {
				CompoundPower cp = (CompoundPower) HeroDesigner.getActiveHero()
						.getPowers().get(i);
				for (int j = 0; j < cp.getPowers().size(); j++) {
					if (cp.getPowers().get(j).getXMLID().equals(getXMLID())) {
						Characteristic power = (Characteristic) cp.getPowers()
								.get(j);
						if (power.getAffectPrimary() && power.getAffectTotal()) {
							bonus += power.getLevels();
							doubleBase += power.getLevels();
						}
					} else if (cp.getPowers().get(j) instanceof Power) {
						CharAffectingObject power = (CharAffectingObject) cp
								.getPowers().get(j);
						if (power.getIncreaseLevels(getType()) > 0
								&& power.getIncrease(getType()) != 0) {
							if (power.getAffectPrimary()
									&& CharAffectingObject.checkFigured(power,
											getType())
									&& power.getAffectTotal()) {
								double mult = power.getIncreaseValue(getType(),
										true);
								doubleBase += mult;
								bonus += Rounder.roundHalfUp(mult);
							}
						}
					}
				}
			} else if (HeroDesigner.getActiveHero().getPowers().get(i) instanceof Power) {
				CharAffectingObject power = (CharAffectingObject) HeroDesigner
						.getActiveHero().getPowers().get(i);
				if (power.getIncreaseLevels(getType()) > 0
						&& power.getIncrease(getType()) != 0) {
					if (power.getAffectPrimary()
							&& CharAffectingObject.checkFigured(power,
									getType()) && power.getAffectTotal()) {
						double mult = power.getIncreaseValue(getType(), true);
						doubleBase += mult;
						bonus += Rounder.roundHalfUp(mult);
					}
				}
			}
		}
		for (int i = 0; i < HeroDesigner.getActiveHero().getEquipment().size(); i++) {
			if (HeroDesigner.getActiveHero().getEquipment().get(i).getXMLID()
					.equals(getXMLID())) {
				Characteristic power = (Characteristic) HeroDesigner
						.getActiveHero().getEquipment().get(i);
				if (power.getAffectPrimary() && power.getAffectTotal()) {
					bonus += power.getLevels();
					doubleBase += power.getLevels();
				}
			} else if (HeroDesigner.getActiveHero().getEquipment().get(i) instanceof CompoundPower) {
				CompoundPower cp = (CompoundPower) HeroDesigner.getActiveHero()
						.getEquipment().get(i);
				for (int j = 0; j < cp.getPowers().size(); j++) {
					if (cp.getPowers().get(j).getXMLID().equals(getXMLID())) {
						Characteristic power = (Characteristic) cp.getPowers()
								.get(j);
						if (power.getAffectPrimary() && power.getAffectTotal()) {
							bonus += power.getLevels();
							doubleBase += power.getLevels();
						}
					} else if (cp.getPowers().get(j) instanceof Power) {
						CharAffectingObject power = (CharAffectingObject) cp
								.getPowers().get(j);
						if (power.getIncreaseLevels(getType()) > 0
								&& power.getIncrease(getType()) != 0) {
							if (power.getAffectPrimary()
									&& CharAffectingObject.checkFigured(power,
											getType())
									&& power.getAffectTotal()) {
								double mult = power.getIncreaseValue(getType(),
										true);
								doubleBase += mult;
								bonus += Rounder.roundHalfUp(mult);
							}
						}
					}
				}
			} else if (HeroDesigner.getActiveHero().getEquipment().get(i) instanceof Power) {
				CharAffectingObject power = (CharAffectingObject) HeroDesigner
						.getActiveHero().getEquipment().get(i);
				if (power.getIncreaseLevels(getType()) > 0
						&& power.getIncrease(getType()) != 0) {
					if (power.getAffectPrimary()
							&& CharAffectingObject.checkFigured(power,
									getType()) && power.getAffectTotal()) {
						double mult = power.getIncreaseValue(getType(), true);
						doubleBase += mult;
						bonus += Rounder.roundHalfUp(mult);
					}
				}
			}
		}
		primaryValue = bonus;
		primaryValueCalcTime = System.currentTimeMillis();
	}

	void calcSecondaryValue() {
		double bonus = 0;
		for (int i = 0; i < HeroDesigner.getActiveHero().getPowers().size(); i++) {
			if (HeroDesigner.getActiveHero().getPowers().get(i).getXMLID()
					.equals(getXMLID())) {
				Characteristic power = (Characteristic) HeroDesigner
						.getActiveHero().getPowers().get(i);
				if (!power.getAffectPrimary() && power.getAffectTotal()) {
					bonus += power.getLevels();
				}
			} else if (HeroDesigner.getActiveHero().getPowers().get(i) instanceof CompoundPower) {
				CompoundPower cp = (CompoundPower) HeroDesigner.getActiveHero()
						.getPowers().get(i);
				for (int j = 0; j < cp.getPowers().size(); j++) {
					if (cp.getPowers().get(j).getXMLID().equals(getXMLID())) {
						Characteristic power = (Characteristic) cp.getPowers()
								.get(j);
						if (!power.getAffectPrimary() && power.getAffectTotal()) {
							bonus += power.getLevels();
						}
					} else if (cp.getPowers().get(j) instanceof CharAffectingObject) {
						CharAffectingObject power = (CharAffectingObject) cp
								.getPowers().get(j);
						if (power.getIncreaseLevels(getType()) > 0
								&& CharAffectingObject.checkFigured(power,
										getType())) {
							double add = power.getIncreaseValue(getType(),
									false);
							bonus += Rounder.roundHalfUp(add);
						}
					}
				}
			} else if (HeroDesigner.getActiveHero().getPowers().get(i) instanceof CharAffectingObject) {
				CharAffectingObject power = (CharAffectingObject) HeroDesigner
						.getActiveHero().getPowers().get(i);
				if (power.getIncreaseLevels(getType()) > 0
						&& CharAffectingObject.checkFigured(power, getType())) {
					double add = power.getIncreaseValue(getType(), false);
					bonus += Rounder.roundHalfUp(add);
				}
			}
		}
		for (int i = 0; i < HeroDesigner.getActiveHero().getEquipment().size(); i++) {
			if (HeroDesigner.getActiveHero().getEquipment().get(i).getXMLID()
					.equals(getXMLID())) {
				Characteristic power = (Characteristic) HeroDesigner
						.getActiveHero().getEquipment().get(i);
				if (!power.getAffectPrimary() && power.getAffectTotal()) {
					bonus += power.getLevels();
				}
			} else if (HeroDesigner.getActiveHero().getEquipment().get(i) instanceof CompoundPower) {
				CompoundPower cp = (CompoundPower) HeroDesigner.getActiveHero()
						.getEquipment().get(i);
				for (int j = 0; j < cp.getPowers().size(); j++) {
					if (cp.getPowers().get(j).getXMLID().equals(getXMLID())) {
						Characteristic power = (Characteristic) cp.getPowers()
								.get(j);
						if (!power.getAffectPrimary() && power.getAffectTotal()) {
							bonus += power.getLevels();
						}
					} else if (cp.getPowers().get(j) instanceof CharAffectingObject) {
						CharAffectingObject power = (CharAffectingObject) cp
								.getPowers().get(j);
						if (power.getIncreaseLevels(getType()) > 0
								&& CharAffectingObject.checkFigured(power,
										getType())) {
							double add = power.getIncreaseValue(getType(),
									false);
							bonus += Rounder.roundHalfUp(add);
						}
					}
				}
			} else if (HeroDesigner.getActiveHero().getEquipment().get(i) instanceof CharAffectingObject) {
				CharAffectingObject power = (CharAffectingObject) HeroDesigner
						.getActiveHero().getEquipment().get(i);
				if (power.getIncreaseLevels(getType()) > 0
						&& CharAffectingObject.checkFigured(power, getType())) {
					double add = power.getIncreaseValue(getType(), false);
					bonus += Rounder.roundHalfUp(add);
				}
			}
		}
		if (getPrimaryValue() + bonus < maxVal) {
			secondaryValue = getPrimaryValue() + bonus;
		} else {
			secondaryValue = maxVal;
		}
		secondaryValueCalcTime = System.currentTimeMillis();
	}

	@Override
	public int compareTo(Object obj) {
		if (obj instanceof GenericObject) {
			GenericObject go = (GenericObject) obj;
			return getPosition() - go.getPosition();
		} else {
			return toString().compareTo(obj.toString());
		}
	}

	@Override
	public double getActiveCost() {
		double total = getTotalCost();
		// no Modifiers on the Characteristics tab...
		if (!isPower()) {
			return total;
		}
		double advantageTotal = 0d;
		boolean advantagesApplied = false;
		double check = 0d;
		double baseAddition = 0;
		for (int i = 0; i < getAssignedModifiers().size(); i++) {
			Modifier mod = getAssignedModifiers().get(i);
			if (mod.getTotalValue() > 0) {
				advantageTotal += mod.getTotalValue();
				advantagesApplied = true;
			}
		}
		if (getParentList() != null) {
			ArrayList<Modifier> parentMods = getParentList()
					.getAssignedModifiers();
			LOOP: for (int i = 0; i < parentMods.size(); i++) {
				Modifier mod = parentMods.get(i);
				if (mod.getTypes().contains("VPP")) {
					continue LOOP;
				}
				if (mod.getXMLID().equals("CHARGES")
						&& getParentList() instanceof Multipower) {
					continue LOOP;
				}
				if (mod.getTotalValue() > 0
						&& (GenericObject.findObjectByID(
								getAssignedModifiers(), mod.getXMLID()) == null
								|| mod.getXMLID().equals("GENERIC_OBJECT") || mod
								.getXMLID().equals("CUSTOM_MODIFIER"))) {
					if (getParentList() instanceof Multipower
							|| getParentList() instanceof ElementalControl) {
						continue;
					} else {
						advantageTotal += mod.getTotalValue();
					}
					advantagesApplied = true;
				}
			}
		}
		if (addModifiersToBase()) {
			Characteristic ch = HeroDesigner.getActiveHero().getCharacteristic(
					getType());
			if (ch != null) {
				double chLevels = ch.getCharacteristicValue();
				double chBaseCost = chLevels * ch.getLevelCost()
						/ ch.getLevelValue();
				if (getTypes().contains("DEFENSE")
						&& HeroDesigner.getActiveHero() != null) {
					if (GenericObject.findObjectByID(HeroDesigner
							.getActiveHero().getPowers(), "AUTOMATON") != null) {
						Automaton auto = (Automaton) GenericObject
								.findObjectByID(HeroDesigner.getActiveHero()
										.getPowers(), "AUTOMATON");
						if (auto.getSelectedOption().getXMLID().toUpperCase()
								.startsWith("NOSTUN")) {
							chBaseCost = chBaseCost
									* auto.getDefenseCostMultiplier();
						}
					}
				}
				double chActiveAddition = chBaseCost * (1 + advantageTotal)
						- chBaseCost;
				baseAddition = Rounder.roundHalfDown(chActiveAddition);
			}
		}
		double ret = total * (1 + advantageTotal);
		check = total * (1 + advantageTotal);
		if (advantagesApplied) {
			ret = Rounder.roundHalfDown(ret);
		}
		if (ret == 0 && check > 0) {
			ret = 1;
		}
		ret += baseAddition;
		return ret;
	}

	/**
	 * Returns the assigned Modifiers, including any assigned via "Add Modifiers
	 * to Base Characteristic"
	 */
	@Override
	public ArrayList<Modifier> getAssignedModifiers() {
		if (assignedModifiersCalcTime <= 0
				|| assignedModifiersCalcTime < HeroDesigner.lastEdit) {
			calcAssignedModifiers();
		}
		return assignedMods;
	}

	/**
	 * Returns the base/starting value for the Characteristic (performing and
	 * figured Characteristic calculations as necessary).
	 * 
	 * @return
	 */
	public double getBaseValue() {
		if (baseValueCalcTime <= 0 || baseValueCalcTime < HeroDesigner.lastEdit) {
			calcBaseValue();
		}
		return baseValue;
	}

	/**
	 * Overridden method. Negative Characteristics will not detract from Figured
	 * Characteristics.
	 */
	@Override
	public double getBodyIncrease() {
		if (!CharAffectingObject.checkFigured(this, Constants.BODY)) {
			return 0;
		}
		if (bodyIncrease == 0) {
			return 0;
		}
		if (getCharacteristicValue() < 0) {
			return 0;
		}
		return bodyIncrease;
	}

	/**
	 * Overridden method. Negative Characteristics will not detract from Figured
	 * Characteristics.
	 */
	@Override
	public double getCustom1Increase() {
		if (!CharAffectingObject.checkFigured(this, Constants.CUSTOM1)) {
			return 0;
		}
		if (custom1Increase == 0) {
			return 0;
		}
		if (getCharacteristicValue() < 0) {
			return 0;
		}
		return custom1Increase;
	}

	/**
	 * Overridden method. Negative Characteristics will not detract from Figured
	 * Characteristics.
	 */
	@Override
	public double getCustom2Increase() {
		if (!CharAffectingObject.checkFigured(this, Constants.CUSTOM2)) {
			return 0;
		}
		if (custom2Increase == 0) {
			return 0;
		}
		if (getCharacteristicValue() < 0) {
			return 0;
		}
		return custom2Increase;
	}

	/**
	 * Returns any Mental Defense levels gained by this Characteristic.
	 * 
	 * @return
	 */
	public int getMdLevels() {
		if (getMDIncreaseLevels() != 0) {
			return (int) Rounder.roundHalfUp(getSecondaryValue()
					/ getMDIncreaseLevels() * getMDIncrease());
		}
		return 0;
	}

	/**
	 * Overridden method. Negative Characteristics will not detract from Figured
	 * Characteristics.
	 */
	@Override
	public double getCustom3Increase() {
		if (!CharAffectingObject.checkFigured(this, Constants.CUSTOM3)) {
			return 0;
		}
		if (custom3Increase == 0) {
			return 0;
		}
		if (getCharacteristicValue() < 0) {
			return 0;
		}
		return custom3Increase;
	}

	/**
	 * Overridden method. Negative Characteristics will not detract from Figured
	 * Characteristics.
	 */
	@Override
	public double getCustom4Increase() {
		if (!CharAffectingObject.checkFigured(this, Constants.CUSTOM4)) {
			return 0;
		}
		if (custom4Increase == 0) {
			return 0;
		}
		if (getCharacteristicValue() < 0) {
			return 0;
		}
		return custom4Increase;
	}

	/**
	 * Overridden method. Negative Characteristics will not detract from Figured
	 * Characteristics.
	 */
	@Override
	public double getCustom5Increase() {
		if (!CharAffectingObject.checkFigured(this, Constants.CUSTOM5)) {
			return 0;
		}
		if (custom5Increase == 0) {
			return 0;
		}
		if (getCharacteristicValue() < 0) {
			return 0;
		}
		return custom5Increase;
	}

	/**
	 * Overridden method. Negative Characteristics will not detract from Figured
	 * Characteristics.
	 */
	@Override
	public double getCustom6Increase() {
		if (!CharAffectingObject.checkFigured(this, Constants.CUSTOM6)) {
			return 0;
		}
		if (custom6Increase == 0) {
			return 0;
		}
		if (getCharacteristicValue() < 0) {
			return 0;
		}
		return custom6Increase;
	}

	/**
	 * Overridden method. Negative Characteristics will not detract from Figured
	 * Characteristics.
	 */
	@Override
	public double getCustom7Increase() {
		if (!CharAffectingObject.checkFigured(this, Constants.CUSTOM7)) {
			return 0;
		}
		if (custom7Increase == 0) {
			return 0;
		}
		if (getCharacteristicValue() < 0) {
			return 0;
		}
		return custom7Increase;
	}

	/**
	 * Overridden method. Negative Characteristics will not detract from Figured
	 * Characteristics.
	 */
	@Override
	public double getCustom8Increase() {
		if (!CharAffectingObject.checkFigured(this, Constants.CUSTOM8)) {
			return 0;
		}
		if (custom8Increase == 0) {
			return 0;
		}
		if (getCharacteristicValue() < 0) {
			return 0;
		}
		return custom8Increase;
	}

	/**
	 * Overridden method. Negative Characteristics will not detract from Figured
	 * Characteristics.
	 */
	@Override
	public double getCustom9Increase() {
		if (!CharAffectingObject.checkFigured(this, Constants.CUSTOM9)) {
			return 0;
		}
		if (custom9Increase == 0) {
			return 0;
		}
		if (getCharacteristicValue() < 0) {
			return 0;
		}
		return custom9Increase;
	}

	/**
	 * Overridden method. Negative Characteristics will not detract from Figured
	 * Characteristics.
	 */
	@Override
	public double getCustom10Increase() {
		if (!CharAffectingObject.checkFigured(this, Constants.CUSTOM10)) {
			return 0;
		}
		if (custom10Increase == 0) {
			return 0;
		}
		if (getCharacteristicValue() < 0) {
			return 0;
		}
		return custom10Increase;
	}

	/**
	 * Used to get a String representation of the base value for the
	 * characteristic. This is mainly useful for displaying only a fixed number
	 * of decimal places for characteristics that can have fractional values
	 * (like SPD).
	 * 
	 * @return
	 */
	public String getCharacteristicBase() {
		return "" + Rounder.roundHalfUp(getBaseValue());
	}

	/**
	 * Returns the base value plus any levels that have been purchased (if not a
	 * Power). If the object is a Characteristic Power, then the base value is
	 * returned.
	 * 
	 * @return
	 */
	public double getCharacteristicValue() {
		double ret = 0;
		if (!isPower()) {
			ret = getBaseValue() + getLevels();
		} else {
			ret = getBaseValue();
			if (HeroDesigner.getActiveHero() != null) {
				Characteristic c = HeroDesigner.getActiveHero()
						.getCharacteristic(getType());
				if (c != null) {
					ret += c.getLevels();
				}
			}
		}
		if (ret < minimumLevel) {
			return minimumLevel;
		} else if (ret > maxVal) {
			return maxVal;
		} else {
			return ret;
		}
	}

	@Override
	public String getColumn2Output() {
		if (getLevels() == 0 && addModifiersToBase()
				&& getModifierString().trim().length() > 0) {
			String mods = getModifierString();
			if (mods.trim().startsWith(",")) {
				mods = mods.trim().substring(1, mods.length()).trim();
			}
			mods += " applied to " + getAlias();
			if (getName() != null && getName().trim().length() > 0) {
				mods = "<i>" + getName() + ":</i>  " + mods;
			}
			return mods;
		}
		String ret = getLevels() >= 0 ? "+" : "";
		if (getName() != null && getName().trim().length() > 0) {
			ret = "<i>" + getName() + ":</i>  " + ret;
		}
		ret += getLevels() + " " + getAlias();
		if (getInput() != null && getInput().trim().length() > 0) {
			ret += ":  " + getInput();
		}
		if (getSelectedOption() != null) {
			ret += " (";
			ret += getSelectedOption().getAlias();
			String adderString = getAdderString();
			if (adderString.trim().length() > 0) {
				ret += "; " + adderString;
			}
			ret += ")";
		} else {
			String adderString = getAdderString();
			if (adderString.trim().length() > 0) {
				ret += " (" + adderString + ")";
			}
		}
		ret += getModifierString();
		if (getEndUsage() > 0
				&& GenericObject.findObjectByID(HeroDesigner.getActiveHero()
						.getPowers(), "ENDURANCERESERVE") != null
				&& GenericObject.findObjectByID(getAssignedModifiers(),
						"ENDRESERVEOREND") == null
				&& !HeroDesigner.getInstance().getPrefs().useWG()) {
			if (getUseENDReserve()) {
				ret += " (uses END Reserve)";
			} else {
				ret += " (uses Personal END)";
			}
		}
		if (addModifiersToBase()) {
			ret += " (Modifiers affect Base Characteristic)";
		}
		return ret;
	}

	@Override
	public String getColumn3Output() {
		if (getEndUsage() > 0) {
			return "" + getEndUsage();
		} else {
			return "";
		}
	}

	/**
	 * Overridden method. Negative Characteristics will not detract from Figured
	 * Characteristics.
	 */
	@Override
	public double getComIncrease() {
		if (!CharAffectingObject.checkFigured(this, Constants.COM)) {
			return 0;
		}
		if (comIncrease == 0) {
			return 0;
		}
		if (getCharacteristicValue() < 0) {
			return 0;
		}
		return comIncrease;
	}

	/**
	 * Overridden method. Negative Characteristics will not detract from Figured
	 * Characteristics.
	 */
	@Override
	public double getConIncrease() {
		if (!CharAffectingObject.checkFigured(this, Constants.CON)) {
			return 0;
		}
		if (conIncrease == 0) {
			return 0;
		}
		if (getCharacteristicValue() < 0) {
			return 0;
		}
		return conIncrease;
	}

	/**
	 * The damage display is something that is really only used on the Powers
	 * tab. It shows information where necessary when levels are increased
	 * (frex: the phases for a given SPD).
	 * 
	 * @return
	 */
	public String getDamageDisplay() {
		return "";
	}

	/**
	 * This is needed because of the totally fucked up calculation for the DCV
	 * minus on the Vehicle size table.
	 * 
	 * @param primary
	 * @return
	 */
	public int getDcvEffect(boolean primary) {
		if (getDcvIncreaseLevels() != 0) {
			int mul = getDcvIncrease() < 0 ? -1 : 1;
			double dcv = 0;
			double val = primary ? getPrimaryValue() : getSecondaryValue();
			dcv = Math.abs(getDcvIncrease()) * val / getDcvIncreaseLevels();
			dcv = dcv * mul;
			dcv = Rounder.roundHalfUp(dcv);
			return (int) dcv;
		} else {
			return 0;
		}
	}

	/**
	 * Overridden method. Negative Characteristics will not detract from Figured
	 * Characteristics.
	 */
	@Override
	public double getDexIncrease() {
		if (!CharAffectingObject.checkFigured(this, Constants.DEX)) {
			return 0;
		}
		if (dexIncrease == 0) {
			return 0;
		}
		if (getCharacteristicValue() < 0) {
			return 0;
		}
		return dexIncrease;
	}

	@Override
	public GenericDialog getDialog(boolean isNew, boolean isPower) {
		CharacteristicDialog dialog = new CharacteristicDialog(this, isNew,
				isPower);
		return dialog;
	}

	/**
	 * The display notes are shown in the last column on the Characteristics
	 * tab.
	 */
	@Override
	public String getDisplayNotes() {
		return "";
	}

	/**
	 * Overridden method. Negative Characteristics will not detract from Figured
	 * Characteristics.
	 */
	@Override
	public double getEdIncrease() {
		if (!CharAffectingObject.checkFigured(this, Constants.ED)) {
			return 0;
		}
		if (edIncrease == 0) {
			return 0;
		}
		if (getCharacteristicValue() < 0) {
			return 0;
		}
		return edIncrease;
	}

	/**
	 * Overridden method. Negative Characteristics will not detract from Figured
	 * Characteristics.
	 */
	@Override
	public double getEgoIncrease() {
		if (!CharAffectingObject.checkFigured(this, Constants.EGO)) {
			return 0;
		}
		if (egoIncrease == 0) {
			return 0;
		}
		if (getCharacteristicValue() < 0) {
			return 0;
		}
		return egoIncrease;
	}

	/**
	 * Overridden method. Negative Characteristics will not detract from Figured
	 * Characteristics.
	 */
	@Override
	public double getEndIncrease() {
		if (!CharAffectingObject.checkFigured(this, Constants.END)) {
			return 0;
		}
		if (endIncrease == 0) {
			return 0;
		}
		if (getCharacteristicValue() < 0) {
			return 0;
		}
		return endIncrease;
	}

	/**
	 * returns the base value plus any figured effects from other
	 * Characteristics, Powers, and Equipment. Levels are not included.
	 * 
	 * @param figuredChar
	 * @return
	 */
	public double getFiguredBaseValue(int figuredChar) {
		double bonus = 0;
		if (HeroDesigner.getActiveHero() != null) {
			baseLevel = origBaseLevel;
			doubleBase = origBaseLevel;
			for (int i = 0; i < HeroDesigner.getActiveHero()
					.getCharacteristics().size(); i++) {
				Characteristic ch = (Characteristic) HeroDesigner
						.getActiveHero().getCharacteristics().get(i);
				if (!ch.getXMLID().equals(getXMLID())
						&& ch.getIncreaseLevels(getType()) > 0
						&& ch.getIncrease(getType()) != 0
						&& CharAffectingObject.checkFigured(ch, getType())) {
					double mult = ch.getCharacteristicValue()
							* ch.getIncrease(getType())
							/ ch.getIncreaseLevels(getType());
					doubleBase += mult;
					bonus += Rounder.roundHalfUp(mult);
				}
			}
			for (int i = 0; i < HeroDesigner.getActiveHero().getPowers().size(); i++) {
				if (HeroDesigner.getActiveHero().getPowers().get(i).getXMLID()
						.equals(getXMLID())) {
					Characteristic power = (Characteristic) HeroDesigner
							.getActiveHero().getPowers().get(i);
					if (power.getAffectPrimary()
							&& power.getAffectTotal()
							&& CharAffectingObject.checkFigured(power,
									figuredChar)) {
						bonus += power.getLevels();
					}
				} else if (HeroDesigner.getActiveHero().getPowers().get(i) instanceof Power) {
					CharAffectingObject power = (CharAffectingObject) HeroDesigner
							.getActiveHero().getPowers().get(i);
					if (power.getIncreaseLevels(getType()) > 0) {
						if (power.getAffectPrimary() && power.getAffectTotal()) {
							if (CharAffectingObject.checkFigured(power,
									getType())) {
								double mult = power.getIncreaseValue(getType(),
										true);
								doubleBase += mult;
								bonus += Rounder.roundHalfUp(mult);
							}
						}
					}
				}
			}
			for (int i = 0; i < HeroDesigner.getActiveHero().getEquipment()
					.size(); i++) {
				if (HeroDesigner.getActiveHero().getEquipment().get(i)
						.getXMLID().equals(getXMLID())) {
					Characteristic power = (Characteristic) HeroDesigner
							.getActiveHero().getEquipment().get(i);
					if (power.getAffectPrimary()
							&& power.getAffectTotal()
							&& CharAffectingObject.checkFigured(power,
									figuredChar)) {
						bonus += power.getLevels();
					}
				} else if (HeroDesigner.getActiveHero().getEquipment().get(i) instanceof Power) {
					CharAffectingObject power = (CharAffectingObject) HeroDesigner
							.getActiveHero().getEquipment().get(i);
					if (power.getIncreaseLevels(getType()) > 0) {
						if (power.getAffectPrimary() && power.getAffectTotal()) {
							if (CharAffectingObject.checkFigured(power,
									getType())) {
								double mult = power.getIncreaseValue(getType(),
										true);
								doubleBase += mult;
								bonus += Rounder.roundHalfUp(mult);
							}
						}
					}
				}
			}
		}
		if (baseLevel + bonus < maxVal) {
			return baseLevel + bonus;
		} else {
			return maxVal;
		}
	}

	public double getFiguredMinValue() {
		if (figuredMinValueCalcTime <= 0
				|| figuredMinValueCalcTime < HeroDesigner.lastEdit) {
			calcFiguredMinValue();
		}
		return figuredMinValue;
	}

	/**
	 * Overridden method. Negative Characteristics will not detract from Figured
	 * Characteristics.
	 */
	@Override
	public double getIntIncrease() {
		if (!CharAffectingObject.checkFigured(this, Constants.INT)) {
			return 0;
		}
		if (intIncrease == 0) {
			return 0;
		}
		if (getCharacteristicValue() < 0) {
			return 0;
		}
		return intIncrease;
	}

	/**
	 * Overridden method. Negative Characteristics will not detract from Figured
	 * Characteristics.
	 */
	@Override
	public double getKbIncrease() {
		if (getCharacteristicValue() < 0) {
			return 0;
		}
		return kbIncrease;
	}

	/**
	 * Overridden method. Negative Characteristics will not detract from Figured
	 * Characteristics.
	 */
	@Override
	public double getMassMultiplier() {
		if (getCharacteristicValue() < 0) {
			return 0;
		}
		return massMultiplier;
	}

	public int getMaxVal() {
		return maxVal;
	}

	public double getMinLevels() {
		if (isPower()) {
			return 0;
		}
		double base = getBaseValue();
		return 0 - base;
	}

	@Override
	public int getMinimumLevel() {
		if (isPower()) {
			return 0;
		} else {
			return super.getMinimumLevel();
		}
	}

	@Override
	protected void getModifierSaveXML(Element root) {
		for (int i = 0; i < getModifiers().size(); i++) {
			Modifier mod = getModifiers().get(i);
			root.addContent(mod.getSaveXML());
		}
	}

	/**
	 * Returns the value of the Characteristic that will be used for NCM point
	 * cost calculations.
	 * 
	 * @return
	 */
	public double getNCMCharValue() {
		if (ncmCharValueCalcTime <= 0
				|| ncmCharValueCalcTime < HeroDesigner.lastEdit) {
			calcNCMCharValue();
		}
		return ncmCharValue;
	}

	/**
	 * Returns the max level that the Characteristic can reach without exceeding
	 * NCM limits (includes Age Disadvantage, if selected).
	 * 
	 * @return
	 */
	public int getNCMLevel() {
		if (HeroDesigner.getActiveTemplate().is6E()) {
			return HeroDesigner.getActiveHero().getRules()
					.getCharacteristicMaxima(this.getXMLID());
		} else {
			Adder ncm = HeroDesigner.getActiveHero().getFullNCMObject();
			if (ncm == null) {
				return 9999;
			}
			String opID = ncm.getXMLID();
			if (ncm.getSelectedOption() != null) {
				opID = ncm.getSelectedOption().getXMLID();
			}
			if (ncmLevels.get(opID) == null) {
				return 9999;
			}
			Integer val = ncmLevels.get(opID);
			return val.intValue();
		}
	}

	/**
	 * Overridden method. Negative Characteristics will not detract from Figured
	 * Characteristics.
	 */
	@Override
	public double getPdIncrease() {
		if (!CharAffectingObject.checkFigured(this, Constants.PD)) {
			return 0;
		}
		if (pdIncrease == 0) {
			return 0;
		}
		if (getCharacteristicValue() < 0) {
			return 0;
		}
		return pdIncrease;
	}

	/**
	 * Overridden method. Negative Characteristics will not detract from Figured
	 * Characteristics.
	 */
	@Override
	public double getPreIncrease() {
		if (!CharAffectingObject.checkFigured(this, Constants.PRE)) {
			return 0;
		}
		if (preIncrease == 0) {
			return 0;
		}
		if (getCharacteristicValue() < 0) {
			return 0;
		}
		return preIncrease;
	}

	public double getPrimaryValue() {
		if (isPower() || primaryValueCalcTime <= 0
				|| primaryValueCalcTime < HeroDesigner.lastEdit) {
			calcPrimaryValue();
		}
		return primaryValue;
	}

	/**
	 * Overridden method. Negative Characteristics will not detract from Figured
	 * Characteristics.
	 */
	@Override
	public double getReachIncrease() {
		if (getCharacteristicValue() < 0) {
			return 0;
		}
		return reachIncrease;
	}

	@Override
	public double getRealCostPreList() {
		double active = getActiveCost();
		// no Modifiers on Characteristics tab....
		if (!isPower()) {
			return active;
		}
		double limitationTotal = 0d;
		boolean limitationsApplied = false;
		double baseAddition = 0;
		for (int i = 0; i < getAssignedModifiers().size(); i++) {
			Modifier mod = getAssignedModifiers().get(i);
			if (mod.getTotalValue() < 0) {
				limitationTotal += mod.getTotalValue();
				limitationsApplied = true;
			}
		}
		if (getParentList() != null) {
			ArrayList<Modifier> parentMods = getParentList()
					.getAssignedModifiers();
			LOOP: for (int i = 0; i < parentMods.size(); i++) {
				Modifier mod = parentMods.get(i);
				if (mod.getTypes().contains("VPP")) {
					continue LOOP;
				}
				if (mod.getXMLID().equals("CHARGES")
						&& getParentList() instanceof Multipower) {
					continue LOOP;
				}
				if (mod.getTotalValue() < 0
						&& GenericObject.findObjectByID(getAssignedModifiers(),
								mod.getXMLID()) == null
						|| mod.getXMLID().equals("GENERIC_OBJECT")
						|| mod.getXMLID().equals("CUSTOM_MODIFIER")
						|| mod.getXMLID().equals("MODIFIER")) {
					limitationTotal += mod.getTotalValue();
					limitationsApplied = true;
				}
			}
		}
		if (addModifiersToBase()) {
			Characteristic ch = HeroDesigner.getActiveHero().getCharacteristic(
					getType());
			double chLevels = ch.getCharacteristicValue();
			double chBaseCost = chLevels * ch.getLevelCost()
					/ ch.getLevelValue();
			if (getTypes().contains("DEFENSE")
					&& HeroDesigner.getActiveHero() != null) {
				if (GenericObject.findObjectByID(HeroDesigner.getActiveHero()
						.getPowers(), "AUTOMATON") != null) {
					Automaton auto = (Automaton) GenericObject.findObjectByID(
							HeroDesigner.getActiveHero().getPowers(),
							"AUTOMATON");
					if (auto.getSelectedOption().getXMLID().toUpperCase()
							.startsWith("NOSTUN")) {
						chBaseCost = chBaseCost
								* auto.getDefenseCostMultiplier();
					}
				}
			}
			double chActiveAddition = chBaseCost - chBaseCost
					/ (1 + Math.abs(limitationTotal));
			baseAddition = Rounder.roundHalfDown(chActiveAddition);
		}
		double ret = active / (1d + Math.abs(limitationTotal));
		if (limitationsApplied) {
			ret = Rounder.roundHalfDown(ret);
		}
		if (ret == 0
				&& (active > 0 || getLevels() > 0
						&& getAssignedAdders().size() == 0
						&& getBaseCost() >= 0)) {
			ret = 1;
		}
		ret -= baseAddition;
		if (HeroDesigner.getActiveHero() != null
				&& HeroDesigner.getActiveHero().getRules().multiplierAllowed()
				&& getMultiplier() != 1) {
			ret = ret * getMultiplier();
			ret = Rounder.roundHalfDown(ret);
		} else if (HeroDesigner.getActiveHero() != null
				&& HeroDesigner.getActiveHero().getRules().multiplierAllowed()
				&& getParentList() != null
				&& getParentList().getMultiplier() != 1) {
			ret = ret * getParentList().getMultiplier();
			ret = Rounder.roundHalfDown(ret);
		}
		if (getQuantity() > 1) {
			double q = (double) getQuantity();
			int doublings = 0;
			while (q > 1d) {
				doublings += 1;
				q = q / 2d;
			}
			ret += doublings * 5;
		}
		return ret;
	}

	/**
	 * Overridden method. Negative Characteristics will not detract from Figured
	 * Characteristics.
	 */
	@Override
	public double getRecIncrease() {
		if (!CharAffectingObject.checkFigured(this, Constants.REC)) {
			return 0;
		}
		if (recIncrease == 0) {
			return 0;
		}
		if (getCharacteristicValue() < 0) {
			return 0;
		}
		return recIncrease;
	}
	
	public boolean showRoll() {
		return showRoll;
	}

	/**
	 * Returns the Characteristic Roll (split, if necessary).
	 * 
	 * @return
	 */
	public String getRoll() {
		int charBase = 9;
		double charDenominator = 5;
		if (HeroDesigner.getActiveHero() != null) {
			charBase = HeroDesigner.getActiveHero().getRules().getCharRollBase();
			charDenominator = HeroDesigner.getActiveHero().getRules().getCharRollDenominator();
		}
		if (showRoll) {
			int base1 = (int) (charBase + Rounder
					.roundHalfUp((getPrimaryValue() / charDenominator)));
			int base2 = (int) (charBase + Rounder
					.roundHalfUp((getSecondaryValue() / charDenominator)));
			if (base1 != base2) {
				return base1 + "- / " + base2 + "-";
			} else {
				return base1 + "-";
			}
		} else {
			return "";
		}
	}

	@Override
	public Element getSaveXML() {
		Element root = getGeneralSaveXML();
		root.setName(getXMLID());
		root.setAttribute("AFFECTS_PRIMARY", getAffectPrimary() ? "Yes" : "No");
		root.setAttribute("AFFECTS_TOTAL", getAffectTotal() ? "Yes" : "No");
		if (isPower() && getUseENDReserve()) {
			root.setAttribute("USE_END_RESERVE", "Yes");
		}
		if (isPower()) {
			root.setAttribute("ADD_MODIFIERS_TO_BASE",
					addModifiersToBase ? "Yes" : "No");
		}
		return root;
	}

	public double getSecondaryValue() {
		if (secondaryValueCalcTime <= 0
				|| secondaryValueCalcTime < HeroDesigner.lastEdit) {
			if (isPower()
					|| HeroDesigner.getActiveHero()
							.getCharacteristic(getType()).hashCode() == hashCode()) {
				calcSecondaryValue();
			}
		}
		return secondaryValue;
	}

	/**
	 * Overridden method. Negative Characteristics will not detract from Figured
	 * Characteristics.
	 */
	@Override
	public double getSpdIncrease() {
		if (!CharAffectingObject.checkFigured(this, Constants.SPD)) {
			return 0;
		}
		if (spdIncrease == 0) {
			return 0;
		}
		if (getCharacteristicValue() < 0) {
			return 0;
		}
		return spdIncrease;
	}

	/**
	 * Overridden method. Negative Characteristics will not detract from Figured
	 * Characteristics.
	 */
	@Override
	public double getStrIncrease() {
		if (!CharAffectingObject.checkFigured(this, Constants.STR)) {
			return 0;
		}
		if (strIncrease == 0) {
			return 0;
		}
		if (getCharacteristicValue() < 0) {
			return 0;
		}
		return strIncrease;
	}

	/**
	 * Overridden method. Negative Characteristics will not detract from Figured
	 * Characteristics.
	 */
	@Override
	public double getStunIncrease() {
		if (!CharAffectingObject.checkFigured(this, Constants.STUN)) {
			return 0;
		}
		if (stunIncrease == 0) {
			return 0;
		}
		if (getCharacteristicValue() < 0) {
			return 0;
		}
		return stunIncrease;
	}

	@Override
	public double getTotalCost() {
		enhancerApplied = null;
		double total = getBaseCost();
		total += getLevels() / getLevelValue() * getLevelCost();
		if ((HeroDesigner.getActiveTemplate().is6E() && !isPower())
				|| (HeroDesigner.getActiveHero().isNCMSelected()
						&& HeroDesigner.getActiveTemplate() != null
						&& HeroDesigner.getActiveTemplate()
								.getNcmCostMultiplier() > 0 && !isPower() && (getLevels() > 0 || getTrueBase() > getNCMLevel()))) {
			if (getNCMCharValue() > getNCMLevel()) {
				double expensiveLevels = getNCMCharValue() - getNCMLevel();
				if (expensiveLevels > getLevels()
						&& getTrueBase() < getNCMLevel()) {
					expensiveLevels = getLevels();
				}
				double adjustment = expensiveLevels
						/ getLevelValue()
						* getLevelCost()
						* (HeroDesigner.getActiveTemplate()
								.getNcmCostMultiplier() - 1);
				total += adjustment;
			}
		}
		for (Adder ad : getAssignedAdders()) {
			if (ad.getRealCost() > 0) {
				total += ad.getRealCost();
			}
		}
		if (total < getMinimumCost() && isMinSet()) {
			total = getMinimumCost();
		} else if (total > getMaxCost() && isMaxSet()) {
			total = getMaxCost();
		}
		for (Adder ad : getAssignedAdders()) {
			if (ad.getRealCost() < 0) {
				total += ad.getRealCost();
			}
		}
		if (getTypes().contains("DEFENSE")
				&& HeroDesigner.getActiveHero() != null) {
			if (GenericObject.findObjectByID(HeroDesigner.getActiveHero()
					.getPowers(), "AUTOMATON") != null) {
				Automaton auto = (Automaton) GenericObject.findObjectByID(
						HeroDesigner.getActiveHero().getPowers(), "AUTOMATON");
				if (auto.getSelectedOption().getXMLID().toUpperCase()
						.startsWith("NOSTUN")) {
					total = total * auto.getDefenseCostMultiplier();
				}
			}
		}
		return total;
	}

	/**
	 * If the base has been affected (Figured Characteristic), this will return
	 * the original value.
	 * 
	 * @return
	 */
	public double getTrueBase() {
		return origBaseLevel;
	}

	@Override
	public int getType() {
		return Constants.GENERAL;
	}

	/**
	 * returns the full value of the Characteristic. Value returned depends on
	 * whether the calculation is for figured characteristics or not.
	 * 
	 * @param figured
	 * @param figuredType
	 * @return
	 */
	public double getValue(boolean figured, int figuredType) {
		double val = figured ? getFiguredBaseValue(figuredType)
				: getBaseValue();
		if (val + getLevels() < minimumLevel) {
			setLevels((int) Rounder.roundHalfDown(minimumLevel - val));
			return minimumLevel;
		}
		if (val + getLevels() < maxVal) {
			return val + getLevels();
		} else {
			setLevels((int) Rounder.roundHalfDown(maxVal - val));
			return maxVal;
		}
	}

	/**
	 * Gives the String representation of this Characteristic's value.
	 * 
	 * @return
	 */
	public String getValueDisplay() {
		if (getPrimaryValue() != getSecondaryValue()) {
			return Rounder.roundHalfUp(getPrimaryValue()) + "/"
					+ Rounder.roundHalfUp(getSecondaryValue());
		} else {
			return "" + Rounder.roundHalfUp(getPrimaryValue());
		}
	}

	@Override
	protected void init(Element element) {
		baseCost = 0;
		baseLevel = 0;
		maxVal = 999;
		affectsPrimary = true;
		addModifiersToBase = false;
		ncmLevels = new Hashtable<String, Integer>();
		duration = "PERSISTENT";
		super.init(element);
		target = "SELFONLY";
		if (!types.contains("STANDARD")) {
			types.add("STANDARD");
		}
		String check = XMLUtility.getValue(element, "TARGET");
		if (check != null && check.trim().length() > 0) {
			target = check;
		}
		check = XMLUtility.getValue(element, "BASE");
		if (check != null && check.trim().length() > 0) {
			try {
				baseLevel = Double.parseDouble(check);
			} catch (Exception exp) {
				baseLevel = 0;
			}
		}
		origBaseLevel = baseLevel;
		check = XMLUtility.getValue(element, "MAXVAL");
		if (check != null && check.trim().length() > 0) {
			try {
				maxVal = Integer.parseInt(check);
			} catch (Exception exp) {
				maxVal = 999;
			}
		}
		check = XMLUtility.getValue(element, "SHOWROLL");
		if (check != null && check.trim().length() > 0) {
			showRoll = check.trim().toUpperCase().startsWith("Y");
		} else {
			showRoll = true;
		}
		if (HeroDesigner.getActiveTemplate() != null) {
			Adder ncmObject = HeroDesigner.getActiveTemplate().getNCMObject();
			if (ncmObject != null) {
				check = XMLUtility.getValue(element, ncmObject.getXMLID());
				if (check != null && check.trim().toUpperCase().length() > 0) {
					try {
						int val = Integer.parseInt(check);
						ncmLevels.put(ncmObject.getXMLID(), new Integer(val));
					} catch (Exception exp) {
					}
				}
			}
			if (ncmObject != null && ncmObject.getOptions() != null) {
				for (int i = 0; i < ncmObject.getOptions().size(); i++) {
					Adder op = ncmObject.getOptions().get(i);
					check = XMLUtility.getValue(element, op.getXMLID());
					if (check != null
							&& check.trim().toUpperCase().length() > 0) {
						try {
							int val = Integer.parseInt(check);
							ncmLevels.put(op.getXMLID(), new Integer(val));
						} catch (Exception exp) {
						}
					}
				}
			}
		}
	}

	/**
	 * utility to check whether this is a Figured Characteristic
	 * 
	 * @return
	 */
	public boolean isFigured() {
		for (int i = 0; i < HeroDesigner.getActiveHero().getCharacteristics()
				.size(); i++) {
			Characteristic ch = (Characteristic) HeroDesigner.getActiveHero()
					.getCharacteristics().get(i);
			if (ch.getIncrease(getType()) > 0
					&& ch.getIncreaseLevels(getType()) > 0) {
				return true;
			}
		}
		return false;
	}

	@Override
	public boolean isPower() {
		boolean ret = super.isPower();
		if (ret) {
			return true;
		} else {
			if (HeroDesigner.getActiveHero() == null) {
				return ret;
			}
			ret = true;
			for (GenericObject o : HeroDesigner.getActiveHero()
					.getCharacteristics()) {
				if (o.getID() == getID()) {
					ret = false;
					break;
				}
			}
			return ret;
		}
	}

	@Override
	public void restoreFromSave(Element root) {
		super.restoreFromSave(root);
		String check = XMLUtility.getValue(root, "AFFECTS_PRIMARY");
		if (check != null && check.trim().length() > 0) {
			affectsPrimary = check.trim().toUpperCase().startsWith("Y");
		}
		check = XMLUtility.getValue(root, "AFFECTS_TOTAL");
		if (check != null && check.trim().length() > 0) {
			affectsTotal = check.trim().toUpperCase().startsWith("Y");
		}
		check = XMLUtility.getValue(root, "ADD_MODIFIERS_TO_BASE");
		if (check != null && check.trim().length() > 0) {
			addModifiersToBase = check.trim().toUpperCase().startsWith("Y");
		}
		assignedModifiersCalcTime = 0;
		baseValueCalcTime = 0;
		figuredMinValueCalcTime = 0;
		ncmCharValueCalcTime = 0;
		primaryValueCalcTime = 0;
		secondaryValueCalcTime = 0;
	}

	public void setAddModifiersToBase(boolean val) {
		addModifiersToBase = val;
	}

	@Override
	public void setLevels(int val) {
		levels = val;
	}

	public void setValue(double val) {
		double check = getCharacteristicValue();
		double base = getBaseValue();
		if (val == check) {
			return; // no action needed...
		} else {
			if (val < minimumLevel) {
				setLevels((int) Rounder.roundHalfDown(minimumLevel - val));
			}
			if (val < maxVal) {
				setLevels((int) Rounder.roundHalfDown(val - base));
			} else {
				setLevels((int) Rounder.roundHalfDown(maxVal - val));
			}
		}
	}
}