/* * EquipmentChoice.java * Copyright 2004 (C) James Dempsey <jdempsey@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 * * Created on Sep 12, 2004 * * $Id$ * */ package pcgen.core; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.StringTokenizer; import pcgen.cdom.enumeration.FormulaKey; import pcgen.cdom.enumeration.ObjectKey; import pcgen.cdom.enumeration.StringKey; import pcgen.cdom.enumeration.Type; import pcgen.cdom.facet.EquipmentTypeFacet; import pcgen.cdom.facet.FacetLibrary; import pcgen.core.analysis.ChooseActivation; import pcgen.rules.context.AbstractReferenceContext; import pcgen.util.Delta; import pcgen.util.Logging; import pcgen.util.SignedInteger; import pcgen.util.enumeration.Visibility; /** * {{@code EquipmentChoice}} holds the details of a choice or * choices required for an Equipment. It is a java bean with a * couple of helper functions to support the users of the bean. * This supports either the user manually choosing which option * they want, or the generator creating one object for each * combination of choices. * * * @author James Dempsey <jdempsey@users.sourceforge.net> */ public final class EquipmentChoice { private EquipmentTypeFacet equipmentTypeFacet = FacetLibrary .getFacet(EquipmentTypeFacet.class); private boolean allowDuplicates = false; private boolean noSign = false; private boolean bAdd = false; private boolean skipZero = false; private int minValue = 0; private int maxValue = 0; private int incValue = 1; private int maxSelect = 0; /* * CONSIDER This is never read, which is probably a bug - thpr Dec 6, 2008 */ private int pool = 0; private String title = null; private List<Object> availableList = new ArrayList<>(); /** * Default constructor for the equipment choice class. * @param bAdd * @param pool */ public EquipmentChoice(final boolean bAdd, final int pool) { super(); this.bAdd = bAdd; this.pool = pool; } /** * Create an iterator over the available choices. This will either be * an iterator of strings, should there be only one choice, or an array * of strings where nested choices are required. The iterator will run * through each possible combination in this case. * * @param neverEmpty True if a default record should be * added if there are no choices. * @return An iterator of choices */ EquipChoiceIterator getChoiceIterator(final boolean neverEmpty) { if (neverEmpty && availableList.isEmpty()) { final List<Object> temp = new ArrayList<>(); temp.add(""); return new EquipChoiceIterator(temp); } final List<Object> finalList; // Account for secondary values (sent as <primary>|<secondary>) if (getMinValue() < getMaxValue()) { finalList = new ArrayList<>(); for (int i = 0; i < availableList.size(); i++) { final String choice = String.valueOf(availableList.get(i)); if (choice.indexOf('|') < 0) { for (int j = getMinValue(); j <= getMaxValue(); j += getIncValue()) { if (!skipZero || j != 0) { finalList.add(choice + '|' + Delta.toString(j)); } } } else { finalList.add(choice); } } } else { finalList = availableList; } return new EquipChoiceIterator(finalList); } /** * @return Returns the pool. */ int getPool() { return pool; } /** * @param pool The pool to set. */ void setPool(final int pool) { this.pool = pool; } /** * @return Returns the bAdd. */ public boolean isBAdd() { return bAdd; } /** * @param add The bAdd to set. */ void setBAdd(final boolean add) { bAdd = add; } /** * @return Returns the availableList. */ public List<Object> getAvailableList() { return availableList; } /** * @return Returns the allowDuplicates. */ public boolean isAllowDuplicates() { return allowDuplicates; } /** * @param allowDuplicates The allowDuplicates to set. */ void setAllowDuplicates(final boolean allowDuplicates) { this.allowDuplicates = allowDuplicates; } /** * @return Returns the incValue. */ public int getIncValue() { return incValue; } /** * @param incValue The incValue to set. */ void setIncValue(final int incValue) { this.incValue = incValue; } /** * @return Returns the maxSelect. */ public int getMaxSelect() { return maxSelect; } /** * @param maxSelect The maxSelect to set. */ void setMaxSelect(final int maxSelect) { this.maxSelect = maxSelect; } /** * @return Returns the maxValue. */ public int getMaxValue() { return maxValue; } /** * @param maxValue The maxValue to set. */ void setMaxValue(final int maxValue) { this.maxValue = maxValue; } /** * @return Returns the minValue. */ public int getMinValue() { return minValue; } /** * @param minValue The minValue to set. */ void setMinValue(final int minValue) { this.minValue = minValue; } /** * @return Returns the noSign. */ boolean isNoSign() { return noSign; } /** * @param noSign The noSign to set. */ void setNoSign(final boolean noSign) { this.noSign = noSign; } /** * @return Returns the title. */ public String getTitle() { return title; } /** * @param title The title to set. */ void setTitle(final String title) { this.title = title; } /** * Add a list of all skills to the available list of the EquipmentChoice object */ public void addSkills() { for ( Skill skill : Globals.getContext().getReferenceContext().getConstructedCDOMObjects(Skill.class) ) { this.getAvailableList().add(skill.getKeyName()); } } /** * Set MinValue * @param minString a string with the minimum value */ public void setMinValueFromString(String minString) { try { this.setMinValue(Delta.parseInt(minString.substring(4))); } catch (NumberFormatException e) { Logging.errorPrint("Bad MIN= value: " + minString); } } /** * @param maxString a string with the maximum value */ public void setMaxValueFromString(String maxString) { try { this.setMaxValue(Delta.parseInt(maxString.substring(4))); } catch (NumberFormatException e) { Logging.errorPrint("Bad MAX= value: " + maxString); } } /** * * @param incString a string with the increment value */ public void setIncrementValueFromString(String incString) { try { this.setIncValue(Delta.parseInt(incString.substring(10))); if (this.getIncValue() < 1) { this.setIncValue(1); } } catch (NumberFormatException e) { // TODO Deal with Exception } } /** * Add abilities of Category aCategory and Type typeString to the * available list of the Equipment Chooser equipChoice * @param typeString the type of Ability to add to the chooser * @param aCategory the Category of Ability to add to the chooser */ public void addSelectableAbilities( final String typeString, final String aCategory) { AbstractReferenceContext ref = Globals.getContext().getReferenceContext(); AbilityCategory cat = ref.silentlyGetConstructedCDOMObject(AbilityCategory.class, aCategory); for (Ability anAbility : ref.getManufacturer( Ability.class, cat).getAllObjects()) { boolean matchesType = ( typeString.equalsIgnoreCase("ALL") || anAbility.isType(typeString) ); if ((anAbility.getSafe(ObjectKey.VISIBILITY) == Visibility.DEFAULT) && !this.getAvailableList().contains(anAbility.getKeyName())) { if (matchesType && !ChooseActivation.hasNewChooseToken(anAbility)) { this.getAvailableList().add(anAbility.getKeyName()); } } } } /** * Add Equipment of Type typeString to to the available list of * the Equipment Chooser equipChoice * @param typeString the type of Equipment to add to the chooser */ public void addSelectableEquipment( final String typeString) { for (Equipment aEquip : Globals.getContext().getReferenceContext().getConstructedCDOMObjects(Equipment.class)) { if ( aEquip.isType(typeString) && !this.getAvailableList().contains(aEquip.getName())) { this.getAvailableList().add(aEquip.getName()); } } } /** * Add a list of skills of Type typeString to the available list of * the EquipmentChoice object equipChoice * @param typeString the type of Skill to add to the chooser */ public void addSelectableSkills( final String typeString) { for ( Skill skill : Globals.getContext().getReferenceContext().getConstructedCDOMObjects(Skill.class) ) { if ( (typeString.equalsIgnoreCase("ALL") || skill.isType(typeString)) && !this.getAvailableList().contains(skill.getKeyName())) { this.getAvailableList().add(skill.getKeyName()); } } } /** * @param parent The piece of Equipment that this Equipment Modifier will be added to * @param choiceType the type of Skill to add to the chooser */ public void addParentsExistingEquipmentModifiersToChooser( final Equipment parent, String choiceType) { for ( EquipmentModifier sibling : parent.getEqModifierList(true) ) { /* * TODO sibling can't be this - different classes... so this is a * bug of some form. */ if ( !(sibling.equals(this)) && sibling.getSafe(StringKey.CHOICE_STRING).startsWith(choiceType)) { getAvailableList().addAll(parent.getAssociationList(sibling)); } } } /** * Populate an EquipmentChoice object with choices based on kindToAdd (Skill, * Feat, etc.) and filtered by filterBy. * @param parent the piece of Equipment that this Equipment Modifier will * be added to * @param numOfChoices the number of choices to make * @param numChosen the number of choices made up to this point * @param filterBy the type used to filter the kind of thing being * chosen * @param kindToAdd what kind of choice are we adding? skills, * equipment, etc. * @param category if adding abilities, this will contain the category * of ability to add */ public void addChoicesByType( final Equipment parent, final int numOfChoices, final int numChosen, String filterBy, String kindToAdd, String category) { if ((numOfChoices > 0) && (getMaxSelect() == 0)) { setPool(numOfChoices - numChosen); } final String type = filterBy.substring(5); if (type.startsWith("LASTCHOICE")) { addParentsExistingEquipmentModifiersToChooser( parent, kindToAdd); } else if ("SKILL".equalsIgnoreCase(kindToAdd)) { addSelectableSkills(type); } else if ("EQUIPMENT".equalsIgnoreCase(kindToAdd)) { addSelectableEquipment(type); } else if ("ABILITY".equalsIgnoreCase(kindToAdd)) { addSelectableAbilities(type, category); } else if ("FEAT".equalsIgnoreCase(kindToAdd)) { addSelectableAbilities(type, "FEAT"); } // Used by internal equipment modifier "Add Type" see LstSystemLoader.java else if ("EQTYPES".equalsIgnoreCase(type)) { Collection<Type> types = equipmentTypeFacet.getSet(Globals.getContext() .getDataSetID()); List<Object> list = getAvailableList(); for (Type t : types) { list.add(t.toString()); } } else { Logging.errorPrint( "Unknown option in CHOOSE '" + filterBy + "'"); } } /** * Add the current character stats as defined in the game mode to the chooser */ public void addStats() { for (PCStat stat : Globals.getContext().getReferenceContext().getConstructedCDOMObjects(PCStat.class)) { this.getAvailableList().add(stat.getKeyName()); } } /** * @param available * @param numSelected */ public void adjustPool(final int available, final int numSelected) { if ( (available > 0) && (this.getMaxSelect() > 0) && (this.getMaxSelect() != Integer.MAX_VALUE)) { this.setPool(this.getMaxSelect() - numSelected); } } /** * Populate this EquipmentChoice object using data held in choiceString * @param choiceString The string containing the info to be parsed and added * to the chooser * @param parent the piece of Equipment that this Equipment Modifier * will be added to * @param available used to adjust the pool * @param numSelected choices made so far * @param forEqBuilder is this being constructed by the equipment builder, * or for interaction with the user. */ public void constructFromChoiceString( String choiceString, final Equipment parent, final int available, final int numSelected, final boolean forEqBuilder, PlayerCharacter pc) { final StringTokenizer titleTok = new StringTokenizer(choiceString, "|", false); while (!forEqBuilder && titleTok.hasMoreTokens()) { String workingkind = titleTok.nextToken(); if (workingkind.startsWith("TITLE=")) { this.setTitle(workingkind.substring(6)); } } int select = parent.getSafe(FormulaKey.SELECT).resolve(parent, true, pc, "").intValue(); setMaxSelect(select); String originalkind = null; final StringTokenizer aTok = new StringTokenizer(choiceString, "|", false); boolean needStats = false; boolean needSkills = false; String category = null; while (!forEqBuilder && aTok.hasMoreTokens()) { String kind = aTok.nextToken(); if (category == null) { if (kind.equals("ABILITY")) { category = aTok.nextToken(); } else { category = "FEAT"; } } this.adjustPool(available, numSelected); if (kind.startsWith("TITLE=")) { //Do nothing, handled above } else if (kind.startsWith("COUNT=")) { // Do nothing, handled above } else { if (originalkind == null) { originalkind = kind; needStats = originalkind.equals("STATBONUS"); needSkills = originalkind.equals("SKILLBONUS"); } else if (kind.startsWith("TYPE=") || kind.startsWith("TYPE.")) { if (originalkind.equals("SKILLBONUS") || originalkind.equals("SKILL")) { //New Style needSkills = false; this.addChoicesByType(parent, available, numSelected, kind, "SKILL", ""); } else if (originalkind.equals("EQUIPMENT") || originalkind.equals("FEAT") || originalkind.equals("ABILITY")) { //New Style this.addChoicesByType(parent, available, numSelected, kind, originalkind, category); } else { //Old Style this.addChoicesByType(parent, available, numSelected, kind, getTitle(), category); } } else if ("STAT".equals(kind)) { this.addStats(); } else if ("SKILL".equals(kind) || originalkind.equals("SKILL") && "ANY".equals("SKILL")) { this.addSkills(); } else if ("SKILL".equals(kind)) { this.addSkills(); } else if ("SKIPZERO".equals(kind)) { skipZero = originalkind.equals("NUMBER"); } else if ("MULTIPLE".equals(kind)) { this.setAllowDuplicates(true); } else if ("NOSIGN".equals(kind)) { this.setNoSign(true); } else if (kind.startsWith("MIN=")) { this.setMinValueFromString(kind); } else if (kind.startsWith("MAX=")) { this.setMaxValueFromString(kind); } else if (kind.startsWith("INCREMENT=")) { this.setIncrementValueFromString(kind); } else { needStats = false; needSkills = false; if (!this.getAvailableList().contains(kind)) { this.getAvailableList().add(kind); } } } } if (needStats) { this.addStats(); } else if (needSkills) { this.addSkills(); } if (this.getTitle() == null) { this.setTitle(originalkind); } if (this.getMaxSelect() == Integer.MAX_VALUE) { this.setPool(this.getAvailableList().size() - numSelected); this.setBAdd(true); } if ( (this.getAvailableList().isEmpty()) && (this.getMinValue() < this.getMaxValue())) { for ( int j = this.getMinValue(); j <= this.getMaxValue(); j += this.getIncValue()) { if (!skipZero || j != 0) { if (this.isNoSign()) { this.getAvailableList().add(j); } else { this.getAvailableList().add(new SignedInteger(j)); } } } this.setMinValue(this.getMaxValue()); } } private static class EquipChoiceIterator implements Iterator<Object> { List<Object> choiceList; int currPos; EquipChoiceIterator(final List<Object> list) { choiceList = list; currPos=0; } /** * @see java.util.Iterator#hasNext() */ @Override public boolean hasNext() { return currPos<choiceList.size(); } /** * @see java.util.Iterator#next() */ @Override public Object next() { return choiceList.get(currPos++); } /** * @see java.util.Iterator#remove() */ @Override public void remove() { throw new UnsupportedOperationException(); } } }