/*
* 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.modifier;
import com.trollworks.gcs.common.DataFile;
import com.trollworks.gcs.common.LoadState;
import com.trollworks.gcs.widgets.outline.ListRow;
import com.trollworks.gcs.widgets.outline.RowEditor;
import com.trollworks.toolkit.annotation.Localize;
import com.trollworks.toolkit.io.xml.XMLReader;
import com.trollworks.toolkit.io.xml.XMLWriter;
import com.trollworks.toolkit.ui.image.StdImage;
import com.trollworks.toolkit.ui.widget.outline.Column;
import com.trollworks.toolkit.utility.Localization;
import com.trollworks.toolkit.utility.notification.Notifier;
import com.trollworks.toolkit.utility.text.Enums;
import com.trollworks.toolkit.utility.text.Numbers;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
/** Model for trait modifiers */
public class Modifier extends ListRow implements Comparable<Modifier> {
@Localize("Modifier")
@Localize(locale = "de", value = "Modifikator")
@Localize(locale = "ru", value = "Модификатор")
@Localize(locale = "es", value = "Modificador")
private static String DEFAULT_NAME;
@Localize("Enhancement/Limitation")
@Localize(locale = "de", value = "Verbesserung / Einschränkung")
@Localize(locale = "ru", value = "Улучшение/ограничение")
@Localize(locale = "es", value = "Mejora/Limitación")
private static String MODIFIER_TYPE;
@Localize("** From container - not modifiable here **")
@Localize(locale = "de", value = "** Aus dem Container \u2013 hier nicht veränderbar **")
@Localize(locale = "ru", value = "** Из контейнера – не меняйте здесь **")
@Localize(locale = "es", value = "** Desde el contenedor - No modificable desde aquí **")
private static String READ_ONLY;
static {
Localization.initialize();
}
private static final int CURRENT_VERSION = 1;
/** The root tag. */
public static final String TAG_MODIFIER = "modifier"; //$NON-NLS-1$
/** The tag for the name. */
protected static final String TAG_NAME = "name"; //$NON-NLS-1$
/** The tag for the base cost. */
public static final String TAG_COST = "cost"; //$NON-NLS-1$
/** The attribute for the cost type. */
public static final String ATTRIBUTE_COST_TYPE = "type"; //$NON-NLS-1$
/** The tag for the cost per level. */
public static final String TAG_LEVELS = "levels"; //$NON-NLS-1$
/** The tag for how the cost is affected. */
public static final String TAG_AFFECTS = "affects"; //$NON-NLS-1$
/** The tag for the page reference. */
protected static final String TAG_REFERENCE = "reference"; //$NON-NLS-1$
/** The attribute for whether it is enabled. */
protected static final String ATTRIBUTE_ENABLED = "enabled"; //$NON-NLS-1$
/** The prefix for notifications. */
public static final String MODIFIER_PREFIX = TAG_MODIFIER + Notifier.SEPARATOR;
/** The ID for name change notification. */
public static final String ID_NAME = MODIFIER_PREFIX + TAG_NAME;
/** The ID for enabled change notification. */
public static final String ID_ENABLED = MODIFIER_PREFIX + ATTRIBUTE_ENABLED;
/** The ID for cost change notification. */
public static final String ID_COST_MODIFIER = MODIFIER_PREFIX + TAG_COST;
/** The ID for cost affect change notification. */
public static final String ID_AFFECTS = MODIFIER_PREFIX + TAG_AFFECTS;
/** The ID for page reference change notification. */
public static final String ID_REFERENCE = MODIFIER_PREFIX + TAG_REFERENCE;
/** The ID for list changed change notification. */
public static final String ID_LIST_CHANGED = MODIFIER_PREFIX + "ListChanged"; //$NON-NLS-1$
/** The name of the {@link Modifier}. */
protected String mName;
/** The page reference for the {@link Modifier}. */
protected String mReference;
/** The cost type of the {@link Modifier}. */
protected CostType mCostType;
private int mCost;
private double mCostMultiplier;
private int mLevels;
private Affects mAffects;
private boolean mEnabled;
private boolean mReadOnly;
/**
* Creates a new {@link Modifier}.
*
* @param file The {@link DataFile} to use.
* @param other Another {@link Modifier} to clone.
*/
public Modifier(DataFile file, Modifier other) {
super(file, other);
mName = other.mName;
mReference = other.mReference;
mCostType = other.mCostType;
mCost = other.mCost;
mCostMultiplier = other.mCostMultiplier;
mLevels = other.mLevels;
mAffects = other.mAffects;
mEnabled = other.mEnabled;
}
/**
* Creates a new {@link Modifier}.
*
* @param file The {@link DataFile} to use.
* @param reader The {@link XMLReader} to use.
* @param state The {@link LoadState} to use.
*/
public Modifier(DataFile file, XMLReader reader, LoadState state) throws IOException {
super(file, false);
load(reader, state);
}
/**
* Creates a new {@link Modifier}.
*
* @param file The {@link DataFile} to use.
*/
public Modifier(DataFile file) {
super(file, false);
mName = DEFAULT_NAME;
mReference = ""; //$NON-NLS-1$
mCostType = CostType.PERCENTAGE;
mCost = 0;
mCostMultiplier = 1.0;
mLevels = 0;
mAffects = Affects.TOTAL;
mEnabled = true;
}
@Override
public boolean isEquivalentTo(Object obj) {
if (obj == this) {
return true;
}
if (obj instanceof Modifier && super.isEquivalentTo(obj)) {
Modifier row = (Modifier) obj;
return mEnabled == row.mEnabled && mLevels == row.mLevels && mCost == row.mCost && mCostMultiplier == row.mCostMultiplier && mCostType == row.mCostType && mAffects == row.mAffects && mName.equals(row.mName) && mReference.equals(row.mReference);
}
return false;
}
/** @return The enabled. */
public boolean isEnabled() {
return mEnabled;
}
/**
* @param enabled The value to set for enabled.
* @return <code>true</code> if enabled has changed.
*/
public boolean setEnabled(boolean enabled) {
if (mEnabled != enabled) {
mEnabled = enabled;
notifySingle(ID_ENABLED);
return true;
}
return false;
}
/** @return The page reference. */
public String getReference() {
return mReference;
}
/**
* @param reference The new page reference.
* @return <code>true</code> if page reference has changed.
*/
public boolean setReference(String reference) {
if (!mReference.equals(reference)) {
mReference = reference;
notifySingle(ID_REFERENCE);
return true;
}
return false;
}
/** @return Whether this {@link Modifier} has been marked as "read-only". */
public boolean isReadOnly() {
return mReadOnly;
}
/** @param readOnly Whether this {@link Modifier} has been marked as "read-only". */
public void setReadOnly(boolean readOnly) {
mReadOnly = readOnly;
}
@Override
public String getModifierNotes() {
return mReadOnly ? READ_ONLY : super.getModifierNotes();
}
/** @return An exact clone of this modifier. */
public Modifier cloneModifier() {
return new Modifier(mDataFile, this);
}
/** @return The total cost modifier. */
public int getCostModifier() {
return mLevels > 0 ? mCost * mLevels : mCost;
}
/** @return The costType. */
public CostType getCostType() {
return mCostType;
}
/**
* @param costType The value to set for costType.
* @return Whether it was changed.
*/
public boolean setCostType(CostType costType) {
if (costType != mCostType) {
mCostType = costType;
notifySingle(ID_COST_MODIFIER);
return true;
}
return false;
}
/** @return The cost. */
public int getCost() {
return mCost;
}
/**
* @param cost The value to set for cost modifier.
* @return Whether it was changed.
*/
public boolean setCost(int cost) {
if (mCost != cost) {
mCost = cost;
notifySingle(ID_COST_MODIFIER);
return true;
}
return false;
}
/** @return The total cost multiplier. */
public double getCostMultiplier() {
return mCostMultiplier;
}
/**
* @param multiplier The value to set for the cost multiplier.
* @return Whether it was changed.
*/
public boolean setCostMultiplier(double multiplier) {
if (mCostMultiplier != multiplier) {
mCostMultiplier = multiplier;
notifySingle(ID_COST_MODIFIER);
return true;
}
return false;
}
/** @return The levels. */
public int getLevels() {
return mLevels;
}
/**
* @param levels The value to set for cost modifier.
* @return Whether it was changed.
*/
public boolean setLevels(int levels) {
if (levels < 0) {
levels = 0;
}
if (mLevels != levels) {
mLevels = levels;
notifySingle(ID_COST_MODIFIER);
return true;
}
return false;
}
/** @return <code>true</code> if this {@link Modifier} has levels. */
public boolean hasLevels() {
return mCostType == CostType.PERCENTAGE && mLevels > 0;
}
@Override
public boolean contains(String text, boolean lowerCaseOnly) {
if (getName().toLowerCase().indexOf(text) != -1) {
return true;
}
return super.contains(text, lowerCaseOnly);
}
@Override
public RowEditor<Modifier> createEditor() {
return new ModifierEditor(this);
}
@Override
public StdImage getIcon(boolean large) {
return null;
}
@Override
public String getListChangedID() {
return ID_LIST_CHANGED;
}
@Override
public String getLocalizedName() {
return DEFAULT_NAME;
}
@Override
public String getRowType() {
return MODIFIER_TYPE;
}
@Override
public String getXMLTagName() {
return TAG_MODIFIER;
}
@Override
public int getXMLTagVersion() {
return CURRENT_VERSION;
}
@Override
protected void loadAttributes(XMLReader reader, LoadState state) {
super.loadAttributes(reader, state);
mEnabled = !reader.hasAttribute(ATTRIBUTE_ENABLED) || reader.isAttributeSet(ATTRIBUTE_ENABLED);
}
@Override
protected void loadSubElement(XMLReader reader, LoadState state) throws IOException {
String name = reader.getName();
if (TAG_NAME.equals(name)) {
mName = reader.readText().replace("\n", " "); //$NON-NLS-1$ //$NON-NLS-2$
} else if (TAG_REFERENCE.equals(name)) {
mReference = reader.readText().replace("\n", " "); //$NON-NLS-1$ //$NON-NLS-2$
} else if (TAG_COST.equals(name)) {
mCostType = Enums.extract(reader.getAttribute(ATTRIBUTE_COST_TYPE), CostType.values(), CostType.PERCENTAGE);
if (mCostType == CostType.MULTIPLIER) {
mCostMultiplier = reader.readDouble(1.0);
} else {
mCost = reader.readInteger(0);
}
} else if (TAG_LEVELS.equals(name)) {
mLevels = reader.readInteger(0);
} else if (TAG_AFFECTS.equals(name)) {
mAffects = Enums.extract(reader.readText(), Affects.values(), Affects.TOTAL);
} else {
super.loadSubElement(reader, state);
}
}
@Override
protected void prepareForLoad(LoadState state) {
super.prepareForLoad(state);
mName = DEFAULT_NAME;
mCostType = CostType.PERCENTAGE;
mCost = 0;
mCostMultiplier = 1.0;
mLevels = 0;
mAffects = Affects.TOTAL;
mReference = ""; //$NON-NLS-1$
mEnabled = true;
}
@Override
protected void saveAttributes(XMLWriter out, boolean forUndo) {
super.saveAttributes(out, forUndo);
if (!mEnabled) {
out.writeAttribute(ATTRIBUTE_ENABLED, mEnabled);
}
}
@Override
protected void saveSelf(XMLWriter out, boolean forUndo) {
out.simpleTag(TAG_NAME, mName);
if (mCostType == CostType.MULTIPLIER) {
out.simpleTagWithAttribute(TAG_COST, mCostMultiplier, ATTRIBUTE_COST_TYPE, Enums.toId(mCostType));
} else {
out.simpleTagWithAttribute(TAG_COST, mCost, ATTRIBUTE_COST_TYPE, Enums.toId(mCostType));
}
out.simpleTagNotZero(TAG_LEVELS, mLevels);
if (mCostType != CostType.MULTIPLIER) {
out.simpleTag(TAG_AFFECTS, Enums.toId(mAffects));
}
out.simpleTagNotEmpty(TAG_REFERENCE, mReference);
}
@Override
public Object getData(Column column) {
return ModifierColumnID.values()[column.getID()].getData(this);
}
@Override
public String getDataAsText(Column column) {
return ModifierColumnID.values()[column.getID()].getDataAsText(this);
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append(getName());
if (hasLevels()) {
builder.append(' ');
builder.append(getLevels());
}
return builder.toString();
}
/** @return A full description of this {@link Modifier}. */
public String getFullDescription() {
StringBuilder builder = new StringBuilder();
String modNote = getNotes();
builder.append(toString());
if (modNote.length() > 0) {
builder.append(" ("); //$NON-NLS-1$
builder.append(modNote);
builder.append(')');
}
builder.append(", "); //$NON-NLS-1$
builder.append(getCostDescription());
return builder.toString();
}
/** @return The formatted cost. */
public String getCostDescription() {
StringBuilder builder = new StringBuilder();
CostType costType = getCostType();
switch (costType) {
case PERCENTAGE:
case POINTS:
default:
builder.append(Numbers.formatWithForcedSign(getCostModifier()));
if (costType == CostType.PERCENTAGE) {
builder.append('%');
}
String desc = mAffects.getShortTitle();
if (desc.length() > 0) {
builder.append(' ');
builder.append(desc);
}
break;
case MULTIPLIER:
builder.append('x');
builder.append(Numbers.format(getCostMultiplier()));
break;
}
return builder.toString();
}
/** @return The {@link Affects} setting. */
public Affects getAffects() {
return mAffects;
}
/**
* @param affects The new {@link Affects} setting.
* @return <code>true</code> if the setting changed.
*/
public boolean setAffects(Affects affects) {
if (affects != mAffects) {
mAffects = affects;
notifySingle(ID_AFFECTS);
return true;
}
return false;
}
/** @return The name. */
public String getName() {
return mName;
}
/**
* @param name The value to set for name.
* @return <code>true</code> if name has changed
*/
public boolean setName(String name) {
if (!mName.equals(name)) {
mName = name;
notifySingle(ID_NAME);
return true;
}
return false;
}
@Override
public void fillWithNameableKeys(HashSet<String> set) {
if (isEnabled()) {
super.fillWithNameableKeys(set);
extractNameables(set, mName);
}
}
@Override
public void applyNameableKeys(HashMap<String, String> map) {
if (isEnabled()) {
super.applyNameableKeys(map);
mName = nameNameables(map, mName);
}
}
@Override
public int compareTo(Modifier other) {
if (this == other) {
return 0;
}
int result = mName.compareTo(other.mName);
if (result == 0) {
result = getNotes().compareTo(other.getNotes());
}
return result;
}
}