/* * Copyright 2012-14 (C) Tom Parker <thpr@users.sourceforge.net> * Derived from Skill.java * Copyright 2001 (C) Bryan McRoberts <merton_monk@yahoo.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 */ package pcgen.core.display; import java.text.NumberFormat; import java.util.ArrayList; import java.util.List; import java.util.regex.Pattern; import pcgen.base.lang.StringUtil; import pcgen.cdom.enumeration.ObjectKey; import pcgen.cdom.enumeration.Type; import pcgen.cdom.reference.CDOMSingleRef; import pcgen.core.PCClass; import pcgen.core.PCStat; import pcgen.core.PlayerCharacter; import pcgen.core.SettingsHandler; import pcgen.core.Skill; import pcgen.core.analysis.SkillRankControl; import pcgen.core.bonus.BonusObj; import pcgen.core.bonus.BonusPair; import pcgen.core.utils.CoreUtility; import pcgen.util.Delta; public final class SkillCostDisplay { /** * Builds up a string describing what makes up the misc modifier for a skill * for a character. This can either be in long form '+2[skill TUMBLE gteq * 5|TYPE=SYNERGY.STACK]' or in short form '+2[TUMBLE]'. Any modifiers that * cannot be determined will be displayed as a single entry of 'OTHER'. * * @param aPC * The character associated with this skill. * @param shortForm * True if the abbreviated form should be used. * @return The explanation of the misc modifier's make-up. */ public static String getModifierExplanation(Skill sk, PlayerCharacter aPC, boolean shortForm) { double bonusObjTotal = 0.0; List<String> explanation = new ArrayList<>(); String keyName = sk.getKeyName(); String bonusKey = ("SKILL." + keyName).toUpperCase(); for (BonusObj bonus : aPC.getActiveBonusList()) { // calculate bonus and add to activeBonusMap if (aPC.isApplied(bonus) && "SKILL".equals(bonus.getBonusName())) { boolean include = bonusForThisSkill(bonus, keyName); if (!include) { for (BonusPair bp : aPC.getStringListFromBonus(bonus)) { String bpKey = bp.fullyQualifiedBonusType.toUpperCase(); if (bpKey.equals(bonusKey)) { include = true; break; } } } if (include) { double iBonus = 0; for (BonusPair bp : aPC.getStringListFromBonus(bonus)) { String bpKey = bp.fullyQualifiedBonusType.toUpperCase(); if (bpKey.startsWith(bonusKey)) { iBonus += bp.resolve(aPC).doubleValue(); } } if (!CoreUtility.doublesEqual(iBonus, 0.0)) { explanation.add(Delta.toString((int) iBonus) + aPC.getBonusContext(bonus, shortForm)); bonusObjTotal += iBonus; } } } } StringBuilder bonusDetails = new StringBuilder(); bonusDetails.append(StringUtil.joinToStringBuilder(explanation, " ")); // TODO: Need to add other bonuses which are not encoded as bonus // objects // - familiars, racial, feats - and add them to bonusObjTotal double bonus; CDOMSingleRef<PCStat> statref = sk.get(ObjectKey.KEY_STAT); if (statref != null) { PCStat stat = statref.get(); bonus = aPC.getStatModFor(stat); bonus += aPC.getTotalBonusTo("SKILL", "STAT." + stat.getKeyName()); SkillCostDisplay.appendBonusDesc(bonusDetails, bonus, "STAT"); } // The catch-all for non-bonusObj modifiers. bonus = aPC.getTotalBonusTo("SKILL", keyName) - bonusObjTotal; SkillCostDisplay.appendBonusDesc(bonusDetails, bonus, "OTHER"); // loop through all current skill types checking for boni for (Type singleType : sk.getTrueTypeList(false)) { bonus = aPC.getTotalBonusTo("SKILL", "TYPE." + singleType); SkillCostDisplay.appendBonusDesc(bonusDetails, bonus, "TYPE." + singleType); } // now check for any lists of skills, etc bonus = aPC.getTotalBonusTo("SKILL", "LIST"); SkillCostDisplay.appendBonusDesc(bonusDetails, bonus, "LIST"); // now check for ALL bonus = aPC.getTotalBonusTo("SKILL", "ALL"); SkillCostDisplay.appendBonusDesc(bonusDetails, bonus, "ALL"); // these next two if-blocks try to get BONUS:[C]CSKILL|TYPE=xxx|y to // function if (aPC.isClassSkill(sk)) { bonus = aPC.getTotalBonusTo("CSKILL", keyName); SkillCostDisplay.appendBonusDesc(bonusDetails, bonus, "CSKILL"); // loop through all current skill types checking for boni for (Type singleType : sk.getTrueTypeList(false)) { bonus = aPC.getTotalBonusTo("CSKILL", "TYPE." + singleType); SkillCostDisplay .appendBonusDesc(bonusDetails, bonus, "CSKILL"); } bonus = aPC.getTotalBonusTo("CSKILL", "LIST"); SkillCostDisplay.appendBonusDesc(bonusDetails, bonus, "CSKILL"); } if (!aPC.isClassSkill(sk) && !sk.getSafe(ObjectKey.EXCLUSIVE)) { bonus = aPC.getTotalBonusTo("CCSKILL", keyName); SkillCostDisplay.appendBonusDesc(bonusDetails, bonus, "CCSKILL"); // loop through all current skill types checking for boni for (Type singleType : sk.getTrueTypeList(false)) { bonus = aPC.getTotalBonusTo("CCSKILL", "TYPE." + singleType); SkillCostDisplay.appendBonusDesc(bonusDetails, bonus, "CCSKILL"); } bonus = aPC.getTotalBonusTo("CCSKILL", "LIST"); SkillCostDisplay.appendBonusDesc(bonusDetails, bonus, "CCSKILL"); } // Encumbrance int aCheckMod = sk.getSafe(ObjectKey.ARMOR_CHECK).calculateBonus(aPC); SkillCostDisplay.appendBonusDesc(bonusDetails, aCheckMod, "ARMOR"); String aString = SettingsHandler.getGame().getRankModFormula(); if (!aString.isEmpty()) { aString = aString.replaceAll(Pattern.quote("$$RANK$$"), SkillRankControl.getTotalRank(aPC, sk).toString()); bonus = aPC.getVariableValue(aString, "").intValue(); SkillCostDisplay.appendBonusDesc(bonusDetails, bonus, "RANKS"); } return bonusDetails.toString(); } private static boolean bonusForThisSkill(BonusObj bonus, String keyName) { for (Object target : bonus.getBonusInfoList()) { if (String.valueOf(target).equalsIgnoreCase(keyName)) { return true; } } return false; } public static String getSituationModifierExplanation(Skill sk, String situation, PlayerCharacter aPC, boolean shortForm) { List<String> explanation = new ArrayList<>(); String keyName = sk.getKeyName(); String bonusKey = ("SITUATION." + keyName + "=" + situation).toUpperCase(); for (BonusObj bonus : aPC.getActiveBonusList()) { // calculate bonus and add to activeBonusMap if (aPC.isApplied(bonus) && "SITUATION".equals(bonus.getBonusName())) { boolean include = bonusForThisSkill(bonus, keyName); if (!include) { for (BonusPair bp : aPC.getStringListFromBonus(bonus)) { String bpKey = bp.fullyQualifiedBonusType.toUpperCase(); if (bpKey.equals(bonusKey)) { include = true; break; } } } if (include) { double iBonus = 0; for (BonusPair bp : aPC.getStringListFromBonus(bonus)) { String bpKey = bp.fullyQualifiedBonusType.toUpperCase(); if (bpKey.startsWith(bonusKey)) { iBonus += bp.resolve(aPC).doubleValue(); } } if (!CoreUtility.doublesEqual(iBonus, 0.0)) { explanation.add(Delta.toString((int) iBonus) + aPC.getBonusContext(bonus, shortForm)); } } } } return StringUtil.join(explanation, " "); } /** * Append a description of the bonus to the supplied buffer if the bonus * value is not 0. * * @param bonusDetails * The StringBuilder being built up. NB: May be modified. * @param bonus * The value of the bonus. * @param description * The description of the bonus. */ public static void appendBonusDesc(StringBuilder bonusDetails, double bonus, String description) { if (CoreUtility.doublesEqual(bonus, 0.0)) { return; } if (bonusDetails.length() > 0) { bonusDetails.append(' '); } String value = Delta.toString((float) bonus); if (value.endsWith(".0")) { value = value.substring(0, value.length() - 2); } bonusDetails.append(value); bonusDetails.append('[').append(description).append(']'); } public static String getRanksExplanation(PlayerCharacter pc, Skill sk) { StringBuilder sb = new StringBuilder(100); boolean needComma = false; for (PCClass pcc : pc.getSkillRankClasses(sk)) { if (needComma) { sb.append(", "); } sb.append(pcc == null ? "None" : pcc.getKeyName()); sb.append(':'); Double rank = pc.getSkillRankForClass(sk, pcc); sb.append(rank == null ? 0 : rank); needComma = true; } double bonus = SkillRankControl.getSkillRankBonusTo(pc, sk); if (bonus != 0.0d) { if (sb.length() > 0) { sb.append("; "); } sb.append("Skillrank bonus "); sb.append(NumberFormat.getNumberInstance().format(bonus)); } return sb.toString(); } }