/*
* Kit.java
* Copyright 2001 (C) Greg Bingleman <byngl@hotmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Created on September 23, 2002, 1:49 PM
*/
package pcgen.core;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import pcgen.base.formula.Formula;
import pcgen.base.lang.StringUtil;
import pcgen.cdom.enumeration.KitApply;
import pcgen.cdom.enumeration.ListKey;
import pcgen.cdom.enumeration.MapKey;
import pcgen.cdom.enumeration.ObjectKey;
import pcgen.cdom.enumeration.SourceFormat;
import pcgen.cdom.enumeration.Type;
import pcgen.core.analysis.OutputNameFormatting;
import pcgen.facade.core.KitFacade;
import pcgen.core.kit.BaseKit;
import pcgen.core.kit.KitStat;
import pcgen.core.kit.KitTable;
import pcgen.core.prereq.PrereqHandler;
import pcgen.core.prereq.PrerequisiteUtilities;
import pcgen.util.Logging;
import pcgen.util.enumeration.View;
import pcgen.util.enumeration.Visibility;
/**
* <code>Kit</code>.
*
* @author Greg Bingleman <byngl@hotmail.com>
*/
public final class Kit extends PObject implements Comparable<Object>, KitFacade
{
private int selectValue = -1;
private boolean doLevelAbilitiesFlag = true;
/**
* Returns the list of base stats that are set by the kit.
*
* @return List the List of stats to be set by the kit. The stats are
* wrapped in a KitStat object.
*/
public List<KitStat> getStats()
{
return getSafeListFor(ListKey.STAT_LIST);
}
/**
* Set the Select value
* @param aValue The currently active select value to use for the kit.
*/
public void setSelectValue(final int aValue)
{
selectValue = aValue;
}
/**
* Returns true if we are doing level abilities for the Kit
* @return true if we are doing level abilities for the Kit
*/
public boolean doLevelAbilities()
{
return doLevelAbilitiesFlag;
}
/**
* Set whether we will do the level abilities for the Kit
* @param yesNo true means we are handling level abilities in the kit.
*/
public void setDoLevelAbilities(final boolean yesNo)
{
doLevelAbilitiesFlag = yesNo;
}
/**
* Add a stat, wrapped in a KitStat, that will be set by this kit.
*
* @param kitStat The stat-value pair to set.
*/
public void addStat(final KitStat kitStat)
{
if (kitStat != null)
{
addToListFor(ListKey.STAT_LIST, kitStat);
}
}
/**
* Used to compare Kits.
*
* @param other Object
*
* @return int
*
* @see java.lang.Comparable#compareTo(Object)
*/
@Override
public int compareTo(final Object other)
{
final Kit oKit = (Kit) other;
return getKeyName().compareToIgnoreCase(oKit.getKeyName());
}
/**
* The method that actually adds the various items in this Kit to the PC.
* Does not take account of Kit Number.
*
* @param pc The Player Character object that we will be applying
* the kit to.
* @param thingsToAdd The list of things that will be added by this kit
* wrapped in KitWrapper objects
*/
public void processKit(final PlayerCharacter pc,
final List<BaseKit> thingsToAdd)
{
processKit(pc, thingsToAdd, -1);
}
/**
* The method that actually adds the various items in this Kit to the PC.
* Takes account of Kit Number
*
* @param pc The Player Character object that we will be applying
* the kit to.
* @param thingsToAdd The list of things that will be added by this kit
* wrapped in KitWrapper objects
* @param kitNo An integer that will be used to set the kit number
* in items of equipment added by this kit
*/
public void processKit(final PlayerCharacter pc,
final List<BaseKit> thingsToAdd, final int kitNo)
{
BigDecimal totalCostToBeCharged = getTotalCostToBeCharged(pc);
if (totalCostToBeCharged != null)
{
pc.setGold(pc.getGold().subtract(totalCostToBeCharged));
}
for (KitStat kStat : getStats())
{
kStat.apply(pc);
}
for (BaseKit bk : thingsToAdd)
{
bk.apply(pc);
}
pc.setCalcEquipmentList();
if (getSafe(ObjectKey.APPLY_MODE) == KitApply.PERMANENT)
{
pc.addKit(this);
}
}
/**
* Gets the buy rate of the kit.
*
* @param aPC The character used to evaluate expressions in relation to
* @return String
*/
public int getBuyRate(PlayerCharacter aPC)
{
QualifiedObject<Formula> buy = get(ObjectKey.EQUIP_BUY);
Formula f = (buy == null ? null : buy.getObject(aPC, this));
int buyRate;
if (f == null)
{
buyRate = SettingsHandler.getGearTab_BuyRate();
}
else
{
buyRate = f.resolve(aPC, "").intValue();
if (buyRate == 100)
{
buyRate = SettingsHandler.getGearTab_BuyRate();
}
}
return buyRate;
}
/**
* Gets the specified total cost of the kit. Note this is a base total cost
* which would then be modified by the chosen gear purchase rate on the gear
* tab.
*
* @param aPC The character used to evaluate expressions in relation to
* @return total cost, or null if no total cost was specified.
*/
public BigDecimal getTotalCost(PlayerCharacter aPC)
{
QualifiedObject<Formula> buy = get(ObjectKey.KIT_TOTAL_COST);
Formula f = (buy == null ? null : buy.getObject(aPC, this));
BigDecimal totalCost = null;
if (f != null)
{
totalCost = new BigDecimal(f.resolve(aPC, "").doubleValue());
}
return totalCost;
}
/**
* Gets the specified total cost of the kit modified by the user's chosen
* gear purchase rate on the gear tab.
*
* @param aPC The character used to evaluate expressions in relation to
* @return Cost to be charged, or null if no total cost was specified.
*/
public BigDecimal getTotalCostToBeCharged(PlayerCharacter aPC)
{
BigDecimal theCost = null;
BigDecimal fixedTotalCost = getTotalCost(aPC);
if (fixedTotalCost != null)
{
fixedTotalCost = fixedTotalCost.setScale(3);
BigDecimal buyRate =
new BigDecimal(SettingsHandler.getGearTab_BuyRate());
theCost =
fixedTotalCost.multiply(buyRate).divide(
new BigDecimal(100).setScale(3), RoundingMode.FLOOR);
}
return theCost;
}
/**
* Returns true if the kit is visible
*
* @param aPC if the kit visibility depends on the PC, this is the PC
* that will be used to check the prerequisites.
*
* @return Whether the kit is visible
*/
public boolean isVisible(PlayerCharacter aPC, View v)
{
Visibility kitVisible = getSafe(ObjectKey.VISIBILITY);
if (kitVisible == Visibility.QUALIFY)
{
return qualifies(aPC, this);
}
else if (kitVisible.isVisibleTo(v))
{
return true;
}
return false;
}
/**
* Test applying the top level kit and record the choices made and any
* warnings encountered. Note these changes are made on a copy of the
* character.
*
* @param aPC PlayerCharacter
* @param thingsToAdd List of kit actions to be taken.
* @param warnings List of issues to be reported to the user.
*/
public void testApplyKit(PlayerCharacter aPC, List<BaseKit> thingsToAdd,
List<String> warnings)
{
testApplyKit(aPC, thingsToAdd, warnings, false);
}
/**
* Test applying the kit and record the choices made and any warnings
* encountered. Note these changes are made on a copy of the character.
*
* @param aPC PlayerCharacter
* @param thingsToAdd List of kit actions to be taken.
* @param warnings List of issues to be reported to the user.
* @param subkit Is this kit being added by a parent kit?
*/
public void testApplyKit(PlayerCharacter aPC, List<BaseKit> thingsToAdd,
List<String> warnings, boolean subkit)
{
// Ensure a reset of random values from a prior run
selectValue = -1;
// We will create a copy of the PC since we may need to add classes and
// levels to the PC that the user may choose not to apply.
// NOTE: These methods need to be called in the correct order.
PlayerCharacter tempPC = subkit ? aPC : aPC.clone();
for (KitStat kStat : getStats())
{
kStat.testApply(this, tempPC, warnings);
}
for (BaseKit bk : getSafeListFor(ListKey.KIT_TASKS))
{
if (!PrereqHandler
.passesAll(bk.getPrerequisiteList(), tempPC, this))
{
continue;
}
if (selectValue != -1 && bk.isOptional()
&& !bk.isOption(tempPC, selectValue))
{
continue;
}
if (bk.testApply(this, tempPC, warnings))
{
thingsToAdd.add(bk);
}
}
BigDecimal totalCostToBeCharged = getTotalCostToBeCharged(tempPC);
if (totalCostToBeCharged != null)
{
BigDecimal pcGold = tempPC.getGold();
if (pcGold.compareTo(BigDecimal.ZERO) >= 0
&& pcGold.compareTo(totalCostToBeCharged) < 0)
{
warnings.add("Could not purchase kit. Not enough funds.");
}
else
{
tempPC.setGold(pcGold.subtract(totalCostToBeCharged));
}
}
}
private static class ObjectTypeComparator implements Comparator<BaseKit>
{
@Override
public int compare(BaseKit bk1, BaseKit bk2)
{
String name1 = bk1.getObjectName();
String name2 = bk2.getObjectName();
return name1.compareTo(name2);
}
}
/**
* Get the Kit info for this PC
* @param aPC the PC this kit is being applied to.
* @return the Kit info for this PC
*/
public String getInfo(PlayerCharacter aPC)
{
StringBuilder info = new StringBuilder(255);
info.append("<html>");
info.append("<b><font size=+1>");
info.append(OutputNameFormatting.piString(this, false));
info.append("</font></b><br>\n");
String aString = getPreReqHTMLStrings(aPC);
if (!aString.isEmpty())
{
info.append(" <b>Requirements</b>: ").append(aString);
}
List<BaseKit> sortedObjects = new ArrayList<>();
sortedObjects.addAll(getSafeListFor(ListKey.KIT_TASKS));
sortedObjects.sort(new ObjectTypeComparator());
String lastObjectName = "";
for (BaseKit bk : sortedObjects)
{
String objName = bk.getObjectName();
if (!objName.equals(lastObjectName))
{
if (!"".equals(lastObjectName))
{
info.append("; ");
}
info.append(" <b>" + objName + "</b>: ");
lastObjectName = objName;
}
else
{
info.append(", ");
}
info.append(bk.toString());
}
info.append(" <b>Source</b>: ").append(SourceFormat.getFormattedString(this,
Globals.getSourceDisplay(), true));
info.append("</html>");
//TODO ListKey.KIT_TASKS
return info.toString();
}
private String getPreReqHTMLStrings(PlayerCharacter aPC)
{
return PrerequisiteUtilities.preReqHTMLStringsForList(aPC, this,
getPrerequisiteList(), false);
}
public static void applyKit(final Kit aKit, final PlayerCharacter aPC)
{
if (aKit == null)
{
return;
}
if (aKit.getSafe(ObjectKey.APPLY_MODE) == KitApply.PERMANENT
&& aPC.containsKit(aKit))
{
return;
}
final List<BaseKit> thingsToAdd = new ArrayList<>();
final List<String> warnings = new ArrayList<>();
aKit.testApplyKit(aPC, thingsToAdd, warnings);
if (Logging.isLoggable(Logging.WARNING))
{
if (!warnings.isEmpty())
{
Logging.log(Logging.WARNING,
"The following warnings were encountered when applying the kit "
+ aKit.getKeyName());
for (String string : warnings)
{
Logging.log(Logging.WARNING, " " + string);
}
}
}
aKit.processKit(aPC, thingsToAdd, 0);
}
public KitTable getTable(String name)
{
return get(MapKey.KIT_TABLE, name);
}
public KitTable addTable(KitTable table)
{
return addToMapFor(MapKey.KIT_TABLE, table.getTableName(), table);
}
@Override
public String getDisplayType()
{
List<Type> trueTypeList = getTrueTypeList(true);
return StringUtil.join(trueTypeList, ".");
}
@Override
public boolean isPermanent()
{
return getSafe(ObjectKey.APPLY_MODE) == KitApply.PERMANENT;
}
}