/* * BonusCalc.java * Missing License Header, Copyright 2016 (C) Andrew Maitland <amaitland@users.sourceforge.net> * * 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 * */ package pcgen.core.analysis; import java.util.Collection; import java.util.StringTokenizer; import pcgen.core.Ability; import pcgen.core.AbilityUtilities; import pcgen.core.Equipment; import pcgen.core.Globals; import pcgen.core.PCStat; import pcgen.core.PObject; import pcgen.core.PlayerCharacter; import pcgen.core.RuleConstants; import pcgen.core.bonus.BonusObj; import pcgen.core.prereq.PrereqHandler; import pcgen.util.Logging; public class BonusCalc { /** * Gets the bonuses to a given stat. * * @param po * @param stat the Stat to get the bonus for * @param aPC the Player Character that the bonus will apply to * @return the bonus to the given stat. */ public static int getStatMod(PObject po, PCStat stat, final PlayerCharacter aPC) { return (int) BonusCalc.charBonusTo(po, "STAT", stat.getKeyName(), aPC); } public static double bonusTo( PObject po, String aType, String aName, final Object obj, final Collection<BonusObj> aBonusList, final PlayerCharacter aPC) { if ((aBonusList == null) || (aBonusList.isEmpty())) { return 0; } double retVal = 0; aType = aType.toUpperCase(); aName = aName.toUpperCase(); final String aTypePlusName = new StringBuilder(aType).append('.').append(aName).append('.').toString(); if (!BonusCalc.dontRecurse && (po instanceof Ability) && (AbilityUtilities.isFeat(obj)) && !Globals.checkRule(RuleConstants.FEATPRE)) { // SUCK! This is horrid, but bonusTo is actually recursive with respect to // passesPreReqToGain and there is no other way to do this without decomposing the // dependencies. I am loathe to break working code. // This addresses bug #709677 -- Feats give bonuses even if you no longer qualify BonusCalc.dontRecurse = true; boolean returnZero = false; if (!po.qualifies(aPC, po)) { returnZero = true; } BonusCalc.dontRecurse = false; if (returnZero) { return 0; } } int iTimes = 1; if (aPC != null && "VAR".equals(aType)) { iTimes = Math.max(1, aPC.getConsolidatedAssociationList(po).size()); } for (BonusObj bonus : aBonusList) { String bString = bonus.toString().toUpperCase(); if (aPC != null && !aPC.getConsolidatedAssociationList(po).isEmpty()) { int span = 4; int idx = bString.indexOf("%VAR"); if (idx == -1) { idx = bString.indexOf("%LIST|"); span = 5; } if (idx >= 0) { final String firstPart = bString.substring(0, idx); final String secondPart = bString.substring(idx + span); for (String assoc : aPC.getConsolidatedAssociationList(po)) { final String xString = new StringBuilder(50) .append(firstPart) .append(assoc) .append(secondPart) .toString().toUpperCase(); retVal += BonusCalc.calcBonus( po, xString, aType, aName, aTypePlusName, obj, iTimes, bonus, aPC); } } } else { retVal += BonusCalc.calcBonus(po, bString, aType, aName, aTypePlusName, obj, iTimes, bonus, aPC); } } return retVal; } /** a boolean for whether something should recurse, default is false. */ private static boolean dontRecurse = false; /** * Apply the bonus to a PC, pass through object's default bonuslist * * @param po * @param aType * @param aName * @param aPC * @return the bonus */ public static double charBonusTo( PObject po, final String aType, final String aName, final PlayerCharacter aPC) { return bonusTo(po, aType, aName, aPC, po.getBonusList(aPC), aPC); } public static double equipBonusTo( Equipment po, final String aType, final String aName, final PlayerCharacter aPC) { return bonusTo(po, aType, aName, po, po.getBonusList(po), aPC); } /** * calcBonus adds together all the bonuses for aType of aName. * * @param po * @param bString Either the entire BONUS:COMBAT|AC|2 string or part of a %LIST or %VAR bonus section * @param aType Such as "COMBAT" * @param aName Such as "AC" * @param aTypePlusName "COMBAT.AC." * @param obj The object to get the bonus from * @param iTimes multiply bonus * iTimes * @param aBonusObj * @param aPC * @return the value of the bonus */ private static double calcBonus( PObject po, final String bString, final String aType, final String aName, String aTypePlusName, final Object obj, final int iTimes, final BonusObj aBonusObj, final PlayerCharacter aPC) { final StringTokenizer aTok = new StringTokenizer(bString, "|"); if (aTok.countTokens() < 3) { Logging.errorPrint("Badly formed BONUS:" + bString); return 0; } String aString = aTok.nextToken(); if (!aString.equalsIgnoreCase(aType) || aString.endsWith("%LIST") || aName.equals("ALL")) { return 0; } final String aList = aTok.nextToken(); if (!aList.equals("LIST") && !aList.equals("ALL") && (aList.toUpperCase().indexOf(aName.toUpperCase()) < 0)) { return 0; } if (aList.equals("ALL") && ((aName.indexOf("STAT=") >= 0) || (aName.indexOf("TYPE=") >= 0) || (aName.indexOf("LIST") >= 0) || (aName.indexOf("VAR") >= 0))) { return 0; } if (aTok.hasMoreTokens()) { aString = aTok.nextToken(); } double iBonus = 0; if (obj instanceof PlayerCharacter) { iBonus = ((PlayerCharacter) obj).getVariableValue(aString, "").doubleValue(); } else if (obj instanceof Equipment) { iBonus = ((Equipment) obj).getVariableValue(aString, "", aPC).doubleValue(); } else { try { iBonus = Float.parseFloat(aString); } catch (NumberFormatException e) { //Should this be ignored? Logging.errorPrint("calcBonus NumberFormatException in BONUS: " + aString, e); } } final String possibleBonusTypeString = aBonusObj.getTypeString(); // must meet criteria before adding any bonuses if (obj instanceof PlayerCharacter) { if (!aBonusObj.qualifies((PlayerCharacter) obj, po)) { return 0; } } else { if (!PrereqHandler.passesAll(aBonusObj.getPrerequisiteList(), (Equipment) obj, aPC)) { return 0; } } double bonus = 0; String bonusTypeString = null; final StringTokenizer bTok = new StringTokenizer(aList, ","); if (aList.equalsIgnoreCase("LIST")) { bTok.nextToken(); } else if (aList.equalsIgnoreCase("ALL")) { // aTypePlusName looks like: "SKILL.ALL." // so we need to reset it to "SKILL.Hide." aTypePlusName = new StringBuilder(aType).append('.').append(aName).append('.').toString(); bonus = iBonus; bonusTypeString = possibleBonusTypeString; } while (bTok.hasMoreTokens()) { if (bTok.nextToken().equalsIgnoreCase(aName)) { bonus += iBonus; bonusTypeString = possibleBonusTypeString; } } if (obj instanceof Equipment) { ((Equipment) obj).setBonusStackFor(bonus * iTimes, aTypePlusName + bonusTypeString); } // The "ALL" subtag is used to build the stacking bonusMap // not to get a bonus value, so just return if (aList.equals("ALL")) { return 0; } return bonus * iTimes; } }