/* * TempBonusHelper.java * Copyright James Dempsey, 2012 * * 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 09/06/2012 12:41:49 PM * * $Id$ */ package pcgen.gui2.facade; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import java.util.regex.Pattern; import pcgen.cdom.base.CDOMObject; import pcgen.cdom.base.Constants; import pcgen.cdom.enumeration.ListKey; import pcgen.cdom.enumeration.StringKey; import pcgen.core.BonusManager; import pcgen.core.BonusManager.TempBonusInfo; import pcgen.core.Equipment; import pcgen.core.Globals; import pcgen.core.PlayerCharacter; import pcgen.core.bonus.Bonus; import pcgen.core.bonus.BonusObj; import pcgen.core.bonus.EquipBonus; import pcgen.core.display.BonusDisplay; import pcgen.core.display.CharacterDisplay; import pcgen.core.prereq.PrereqHandler; import pcgen.facade.core.ChooserFacade.ChooserTreeViewType; import pcgen.facade.core.InfoFacade; import pcgen.facade.core.InfoFactory; import pcgen.facade.core.UIDelegate; import pcgen.system.LanguageBundle; import pcgen.util.Logging; /** * The Class {@code TempBonusHelper} splits out processing for temporary * bonuses from CnaracterFacadeImpl. * * <br> * * @author James Dempsey <jdempsey@users.sourceforge.net> */ public class TempBonusHelper { /** An empty string. */ private static final String EMPTY_STRING = ""; //$NON-NLS-1$ /** * Get the target of the temporary bonus. This may present a chooser to the * user, if a choice of equipment is required. If the bonus is a character * bonus only, then no chooser will be presented and the return value will * be the character. If an equipment item is chosen, a new temporary item is * created from the chosen equipment item, i.e. a copy that has no weight or * cost. * * @param originObj The rules object providing the bonus. * @param theCharacter The target character. * @param delegate The user interface delegate which will provide the chooser. * @param infoFactory An object to provide formatted information about an object. * @return The temporary equipment item, the character or null if the request * was cancelled. */ static Object getTempBonusTarget(CDOMObject originObj, PlayerCharacter theCharacter, UIDelegate delegate, InfoFactory infoFactory) { List<InfoFacade> possibleTargets = getListOfApplicableEquipment(originObj, theCharacter); boolean canApplyToPC = hasCharacterTempBonus(originObj); if (possibleTargets.isEmpty()) { if (canApplyToPC) { return theCharacter; } delegate.showInfoMessage(Constants.APPLICATION_NAME, LanguageBundle.getString("in_itmNoSuitableEquip")); //$NON-NLS-1$ return null; } // Get the user's choice of item String label = LanguageBundle.getString("im_itmSelectItem"); //$NON-NLS-1$ if (canApplyToPC) { possibleTargets.add(new CharacterInfoFacade(theCharacter.getDisplay())); } final ArrayList<InfoFacade> selectedList = new ArrayList<>(); GeneralChooserFacadeBase chooserFacade = new GeneralChooserFacadeBase(label, possibleTargets, new ArrayList<>(), 1, infoFactory) { @Override public void commit() { for (InfoFacade item : getSelectedList()) { selectedList.add(item); } } }; chooserFacade.setDefaultView(ChooserTreeViewType.NAME); delegate.showGeneralChooser(chooserFacade); if (!selectedList.isEmpty()) { if (selectedList.get(0) instanceof CharacterInfoFacade) { return theCharacter; } // Create temporary item Equipment aEq = ((Equipment) selectedList.get(0)).clone(); aEq.makeVirtual(); String currAppName = aEq.getAppliedName(); if (currAppName != null && currAppName.length() > 2) { if (theCharacter.hasTempApplied(originObj)) { delegate.showInfoMessage(Constants.APPLICATION_NAME, LanguageBundle.getString("in_itmAppBonButAlreadyApplied")); //$NON-NLS-1$ return null; } // We need to remove the [] from the old name aEq.setAppliedName(currAppName.substring(2, currAppName .length() - 1) + ", " + originObj.getKeyName()); //$NON-NLS-1$ } else { aEq.setAppliedName(originObj.getKeyName()); } return aEq; } return null; } /** * Build a list of what equipment is possible to apply this bonus to. * @param originObj The rules object providing the bonus. * @param theCharacter The target character. * @return The list of possible equipment. */ public static List<InfoFacade> getListOfApplicableEquipment(CDOMObject originObj, PlayerCharacter theCharacter) { CharacterDisplay charDisplay = theCharacter.getDisplay(); List<InfoFacade> possibleEquipment = new ArrayList<>(); if (originObj == null) { return possibleEquipment; } boolean found = false; theCharacter.setCalcEquipmentList(theCharacter.getUseTempMods()); for (Equipment aEq : charDisplay.getEquipmentSet()) { found = false; for (EquipBonus eb : originObj.getSafeListFor(ListKey.BONUS_EQUIP)) { String conditions = eb.conditions; boolean passesConditions = passesConditions(aEq, conditions); if (passesConditions && !found) { possibleEquipment.add(aEq); found = true; } } } return possibleEquipment; } private static boolean passesConditions(Equipment aEq, String conditions) { for (String andToken : conditions.split(",")) { boolean passOr = false; for (String orToken : andToken.split("\\;")) { if (orToken.startsWith("[")) { if (!aEq.isType(orToken.substring(1, orToken.length() - 1))) { passOr = true; break; } } else { if (aEq.isType(orToken)) { passOr = true; break; } } } if (!passOr) { return false; } } return true; } /** * Apply the temporary bonus to the character's equipment. Generally * equipment will be a 'temporary' equipment item created to show the item * with the bonus. * * @param aEq * The temporary equipment item to apply the bonus to. * @param originObj * The rules object granting the bonus. * @param theCharacter * The character the bonus is being applied to. */ static TempBonusFacadeImpl applyBonusToCharacterEquipment( Equipment aEq, CDOMObject originObj, PlayerCharacter theCharacter) { TempBonusFacadeImpl appliedBonus = null; String repeatValue = EMPTY_STRING; for (EquipBonus eb : originObj.getListFor(ListKey.BONUS_EQUIP)) { BonusObj aBonus = eb.bonus; String oldValue = aBonus.toString(); String newValue = oldValue; if (!originObj.getSafe(StringKey.TEMPVALUE).isEmpty()) { BonusInfo bi = TempBonusHelper.getBonusChoice(oldValue, originObj, repeatValue, theCharacter); if (bi == null) { return null; } newValue = bi.getBonusValue(); repeatValue = bi.getRepeatValue(); } BonusObj newB = Bonus.newBonus(Globals.getContext(), newValue); if (newB != null) { // if Target was this PC, then add // bonus to TempBonusMap theCharacter.setApplied(newB, PrereqHandler.passesAll( newB.getPrerequisiteList(), aEq, theCharacter)); aEq.addTempBonus(newB); TempBonusInfo tempBonusInfo = theCharacter.addTempBonus(newB, originObj, aEq); if (appliedBonus == null) { String bonusName = BonusDisplay.getBonusDisplayName(tempBonusInfo); appliedBonus = new TempBonusFacadeImpl(originObj, aEq, bonusName); } } } // if the Target is an Equipment item // then add it to the tempBonusItemList if (aEq != null) { theCharacter.addTempBonusItemList(aEq); } return appliedBonus; } /** * Apply the temporary bonus to the character. The bonus is applied * directly to the character. * * @param originObj The rules object granting the bonus. * @param theCharacter The character the bonus is being applied to. */ static TempBonusFacadeImpl applyBonusToCharacter(CDOMObject originObj, PlayerCharacter theCharacter) { TempBonusFacadeImpl appliedBonus = null; String repeatValue = EMPTY_STRING; for (BonusObj aBonus : getTempCharBonusesFor(originObj)) { String oldValue = aBonus.toString(); String newValue = oldValue; if (!originObj.getSafe(StringKey.TEMPVALUE).isEmpty()) { BonusInfo bi = TempBonusHelper.getBonusChoice(oldValue, originObj, repeatValue, theCharacter); if (bi == null) { return null; } newValue = bi.getBonusValue(); repeatValue = bi.getRepeatValue(); } BonusObj newB = Bonus.newBonus(Globals.getContext(), newValue); if (newB != null) { // if Target was this PC, then add // bonus to TempBonusMap theCharacter .setApplied(newB, newB.qualifies(theCharacter, null)); TempBonusInfo tempBonusInfo = theCharacter .addTempBonus(newB, originObj, theCharacter); if (appliedBonus == null) { String bonusName = BonusDisplay.getBonusDisplayName(tempBonusInfo); appliedBonus = new TempBonusFacadeImpl(originObj, theCharacter, bonusName); } } } return appliedBonus; } private static List<BonusObj> getTempCharBonusesFor(CDOMObject originObj) { List<BonusObj> list = new ArrayList<>(5); list.addAll(originObj.getSafeListFor(ListKey.BONUS_ANYPC)); list.addAll(originObj.getSafeListFor(ListKey.BONUS_PC)); return list; } static void removeBonusFromCharacter(PlayerCharacter pc, Equipment aEq, CDOMObject aCreator) { for (Map.Entry<BonusObj, BonusManager.TempBonusInfo> me : pc .getTempBonusMap().entrySet()) { BonusObj aBonus = me.getKey(); TempBonusInfo tbi = me.getValue(); Object aC = tbi.source; if (aCreator != aC) { continue; } Object aT = tbi.target; if ((aT instanceof Equipment) && (aEq != null)) { if (aEq.equals(aT)) { pc.removeTempBonus(aBonus); pc.removeTempBonusItemList((Equipment) aT); ((Equipment) aT).removeTempBonus(aBonus); ((Equipment) aT).setAppliedName(EMPTY_STRING); } } else if ((aT instanceof PlayerCharacter) && (aEq == null)) { pc.removeTempBonus(aBonus); } } } /** * Allows user to choose the value of a bonus. * * @param oldValue The PCC text of the bonus. * @param source The object providing the bonus. * @param repeatValue The value of a previous bonus to be used for choices in this bonus. * @param pc The character the bonus is being applied to. * @return The new values for the bonus. */ private static BonusInfo getBonusChoice(String oldValue, final CDOMObject source, String repeatValue, PlayerCharacter pc) { String value = oldValue; // If repeatValue is set, this is a multi BONUS and they all // should get the same value as the first choice if (!repeatValue.isEmpty()) { // need to parse the aChoice string // and replace %CHOICE with choice if (value.indexOf("%CHOICE") >= 0) //$NON-NLS-1$ { value = value.replaceAll( Pattern.quote("%CHOICE"), //$NON-NLS-1$ repeatValue); } return new BonusInfo(value, repeatValue); } String aChoice = source.getSafe(StringKey.TEMPVALUE); StringTokenizer aTok = new StringTokenizer(aChoice, "|"); //$NON-NLS-1$ String minString = aTok.nextToken().substring(4); //Take off MIN= String maxString = aTok.nextToken().substring(4); //Take off MAX= String titleString = aTok.nextToken().substring(6); // Take off TITLE= int min = pc.getVariableValue(minString, EMPTY_STRING).intValue(); int max = pc.getVariableValue(maxString, EMPTY_STRING).intValue(); if (max < min) { Logging.errorPrint("Temp Bonus Value had max < min: " + max + "<" + min); return null; } if (max <= 0) { Logging.errorPrint("Temp Bonus Value had max <= 0: " + max); return null; } List<Integer> numberList = new ArrayList<>(); for (int i = min; i <= max; i++) { numberList.add(i); } // let them choose the number from a radio list List<Integer> selectedList = new ArrayList<>(); selectedList = Globals.getChoiceFromList(titleString, numberList, selectedList, 1, false, true, pc); if (!selectedList.isEmpty()) { final String aI = String.valueOf(selectedList.get(0)); // need to parse the bonus.getValue() // string and replace %CHOICE if (oldValue.indexOf("%CHOICE") >= 0) //$NON-NLS-1$ { value = oldValue.replaceAll(Pattern.quote("%CHOICE"), //$NON-NLS-1$ aI); } return new BonusInfo(value, aI); } // they hit the cancel button return null; } static class BonusInfo { private final String bonusValue; private final String repeatValue; public BonusInfo(String value, String repeat) { bonusValue = value; repeatValue = repeat; } public String getBonusValue() { return bonusValue; } public String getRepeatValue() { return repeatValue; } } /** * The Class {@code CharacterInfoFacade} presents a character as an InfoFacade. */ static class CharacterInfoFacade implements InfoFacade { private final CharacterDisplay charDisplay; public CharacterInfoFacade(CharacterDisplay charDisplay) { this.charDisplay = charDisplay; } @Override public String getSource() { return EMPTY_STRING; } @Override public String getSourceForNodeDisplay() { return EMPTY_STRING; } @Override public String getKeyName() { return "PC"; //$NON-NLS-1$ } @Override public boolean isNamePI() { return false; } @Override public String toString() { return LanguageBundle.getFormattedString("in_itmCharacterName", //$NON-NLS-1$ charDisplay.getName()); } @Override public String getType() { return ""; } } //////////////////////////////////////////////// // Public Accessors and Mutators // //////////////////////////////////////////////// public static boolean hasAnyPCTempBonus(CDOMObject obj) { return obj.containsListFor(ListKey.BONUS_ANYPC); } public static boolean hasPCTempBonus(CDOMObject obj) { return obj.containsListFor(ListKey.BONUS_PC); } public static boolean hasNonPCTempBonus(CDOMObject obj) { return hasEquipmentTempBonus(obj) || hasAnyPCTempBonus(obj); } public static boolean hasCharacterTempBonus(CDOMObject obj) { return hasAnyPCTempBonus(obj) || hasPCTempBonus(obj); } public static boolean hasEquipmentTempBonus(CDOMObject obj) { return obj.containsListFor(ListKey.BONUS_EQUIP); } public static Set<String> getEquipmentApplyString(CDOMObject obj) { Set<String> set = new HashSet<>(); //Should use hasEquipmentTempBonus first, so we do NOT do getSafeListFor for (EquipBonus bonus : obj.getListFor(ListKey.BONUS_EQUIP)) { set.add(bonus.conditions); } return set; } static boolean hasTempBonus(CDOMObject obj) { return hasEquipmentTempBonus(obj) || hasAnyPCTempBonus(obj) || hasPCTempBonus(obj); } }