/* * EquipmentList.java * Copyright 2003 (C) Jonas Karlsson <jujutsunerd@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 November 30, 2003, 15:24 * * Current Ver: $Revision$ * */ package pcgen.core; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.StringTokenizer; import pcgen.cdom.enumeration.FormulaKey; import pcgen.cdom.enumeration.IntegerKey; import pcgen.cdom.enumeration.ObjectKey; import pcgen.cdom.enumeration.Type; import pcgen.core.analysis.EquipmentChoiceDriver; import pcgen.core.analysis.SizeUtilities; import pcgen.core.prereq.PrereqHandler; import pcgen.core.utils.CoreUtility; import pcgen.rules.context.AbstractReferenceContext; import pcgen.util.Delta; import pcgen.util.Logging; /** * Equipment-related lists and methods extracted from Globals.java. Will * probably try to disentangle modifierlist into it's own class later. * * @author Jonas Karlsson <jujutsunerd@users.sourceforge.net> */ public final class EquipmentList { /** this is determined by preferences */ private static boolean autoGeneration = false; /** * Private to ensure utility object can't be instantiated. */ private EquipmentList() { // Empty Constructor } private static boolean isAutoGeneration() { return autoGeneration; } /** * Set whether magic equipment auto generation should be on. * * @param auto * true if it should be on */ public static void setAutoGeneration(final boolean auto) { autoGeneration = auto; } /** * Return the equipment that has the passed-in name. * * @param baseName * the name to return an equipment for * @param aPC * TODO * @return the Equipment matching the name */ public static Equipment getEquipmentFromName(final String baseName, final PlayerCharacter aPC) { final List<String> modList = new ArrayList<>(); final List<String> namList = new ArrayList<>(); final List<String> sizList = new ArrayList<>(); Equipment eq; String aName = baseName; int i = aName.indexOf('('); // Remove all modifiers from item name and // split into "size" and "non-size" lists if (i >= 0) { final StringTokenizer aTok = new StringTokenizer(aName.substring(i + 1), "/)", false); while (aTok.hasMoreTokens()) { final String cString = aTok.nextToken(); SizeAdjustment sa = Globals.getContext().getReferenceContext() .silentlyGetConstructedCDOMObject(SizeAdjustment.class, cString); if (sa != null) { sizList.add(cString); } else { if ("Mighty Composite".equalsIgnoreCase(cString)) { modList.add("Mighty"); modList.add("Composite"); } else { modList.add(cString); } } } aName = aName.substring(0, i).trim(); } // Separate the "non-size" descriptors into 2 Lists. // One containing those descriptors whose names match a // modifier name, and the other containing those descriptors // which are not possibly modifiers // (because they're not in the modifier list). // if (i >= 0) { for (i = modList.size() - 1; i >= 0; --i) { final String namePart = modList.get(i); if (getModifierNamed(namePart) == null) { namList.add(0, namePart); // add to the start as otherwise the list // will be reversed modList.remove(i); } } } // Look for magic (or mighty) bonuses // int[] bonuses = null; int bonusCount = 0; i = aName.indexOf('+'); if (i >= 0) { final StringTokenizer aTok = new StringTokenizer(aName.substring(i), "/", false); bonusCount = aTok.countTokens(); bonuses = new int[bonusCount]; int idx = 0; while (aTok.hasMoreTokens()) { final String cString = aTok.nextToken(); bonuses[idx++] = Delta.decode(cString).intValue(); } aName = aName.substring(0, i).trim(); // // Mighty bows suffered a (much-needed) renaming // (Long|Short)bow +n (Mighty/Composite) --> (Long|Short)bow (+n // Mighty/Composite) // (Long|Short)bow +x/+n (Mighty/Composite) --> (Long|Short)bow +x (+n // Mighty/Composite) // // Look through the modifier list for MIGHTY, // if found add the bonus to the start of the modifier's name // if (bonusCount > 0) { for (int idx1 = 0; idx1 < namList.size(); ++idx1) { String aString = namList.get(idx1); if ("Mighty".equalsIgnoreCase(aString)) { aString = Delta.toString(bonuses[bonusCount - 1]) + " " + aString; namList.set(idx1, aString); bonusCount -= 1; } } } } // // aName : name of item minus all descriptors held in () as well as any // bonuses // namList : list of all descriptors which cannot be modifiers // modList : list of all descriptors which *might* be modifiers // sizList : list of all size descriptors // String omitString = ""; String bonusString = ""; while (true) { final String eqName = aName + bonusString; eq = findEquipment(eqName, null, namList, sizList, omitString); if (eq != null) { if (sizList.size() > 1) // was used in name, ignore as modifier { sizList.remove(0); } break; } eq = findEquipment(eqName, namList, null, sizList, omitString); if (eq != null) { if (sizList.size() > 1) // was used in name, ignore as modifier { sizList.remove(0); } break; } eq = findEquipment(eqName, namList, null, null, omitString); if (eq != null) { break; } // If only 1 size then include it in name if (sizList.size() == 1) { eq = findEquipment(eqName, sizList, namList, null, omitString); if (eq == null) { eq = findEquipment(eqName, namList, sizList, null, omitString); } if (eq != null) { sizList.clear(); break; } } // If we haven't found it yet, // try stripping Thrown from name if (baseName.indexOf("Thrown") >= 0) { if (omitString.isEmpty()) { omitString = "Thrown"; continue; } } // Still haven't found it? // Try adding bonus to end of name if ((bonusCount > 0) && (bonuses != null)) { if (bonusString.isEmpty()) { omitString = ""; bonusString = " " + Delta.toString(bonuses[0]); continue; } } break; } if (eq != null) { boolean bModified = false; boolean bError = false; eq = eq.clone(); // // Now attempt to add all the modifiers. // for (Iterator<String> e = modList.iterator(); e.hasNext();) { final String namePart = e.next(); final EquipmentModifier eqMod = getQualifiedModifierNamed(namePart, eq); if (eqMod != null) { eq.addEqModifier(eqMod, true, aPC); if (eqMod.getSafe(ObjectKey.ASSIGN_TO_ALL) && eq.isDouble()) { eq.addEqModifier(eqMod, false, aPC); bModified = true; } } else { Logging.errorPrint("Could not find a qualified modifier named: " + namePart + " for " + eq.getName() + ":" + eq.typeList()); bError = true; } } // Found what appeared to be the base item, // but one of the modifiers is not qualified // to be attached to the item // if (bError) { return null; } if (!sizList.isEmpty()) { /* * CONSIDER This size can be further optimized by changing sizList */ eq.resizeItem(aPC, Globals.getContext().getReferenceContext() .silentlyGetConstructedCDOMObject(SizeAdjustment.class, sizList .get(0))); bModified = true; if (sizList.size() > 1) { Logging.errorPrint("Too many sizes in item name, used only 1st of: " + sizList); } } if (bModified) { eq.nameItemFromModifiers(aPC); Equipment equip = Globals.getContext().getReferenceContext() .silentlyGetConstructedCDOMObject(Equipment.class, eq .getKeyName()); if (equip == null) { Globals.getContext().getReferenceContext().importObject(eq); } else { eq = equip; } } } return eq; } /** * Get an Equipment object from the list matching the passed-in type(s). * * @param desiredTypes * a '.' separated list of types to match * @param excludedTypes * a '.' separated list of types to NOT match * @return the matching Equipment */ public static List<Equipment> getEquipmentOfType(final String desiredTypes, final String excludedTypes) { final List<String> desiredTypeList = CoreUtility.split(desiredTypes, '.'); final List<String> excludedTypeList = CoreUtility.split(excludedTypes, '.'); final List<Equipment> typeList = new ArrayList<>(100); if (!desiredTypeList.isEmpty()) { for (Equipment eq : Globals.getContext().getReferenceContext() .getConstructedCDOMObjects(Equipment.class)) { boolean addIt = true; // // Must have all of the types in the desired list // for ( String type : desiredTypeList ) { if (!eq.isType(type)) { addIt = false; break; } } if (addIt && (!excludedTypeList.isEmpty())) { // // Can't have any of the types on the excluded list // for ( String type : excludedTypeList ) { if (eq.isType(type)) { addIt = false; break; } } } if (addIt) { typeList.add(eq); } } } return typeList; } /** * Automatically add equipment types as requested by user. * TODO */ public static void autoGenerateEquipment() { setAutoGeneration(true); autogenerateRacialEquipment(); autogenerateMasterWorkEquipment(); autogenerateMagicEquipment(); autogenerateExoticMaterialsEquipment(); setAutoGeneration(false); } private static void autogenerateExoticMaterialsEquipment() { if (SettingsHandler.isAutogenExoticMaterial()) { for (Equipment eq : Globals.getContext().getReferenceContext().getConstructedCDOMObjects(Equipment.class)) { // // Only apply to non-magical Armor, Shield and Weapon // if (eq.isMagic() || eq.isUnarmed() || eq.isMasterwork() || (!eq.isAmmunition() && !eq.isArmor() && !eq.isShield() && !eq.isWeapon())) { continue; } final EquipmentModifier eqDarkwood = getQualifiedModifierNamed("Darkwood", eq); final EquipmentModifier eqAdamantine = getQualifiedModifierNamed("Adamantine", eq); final EquipmentModifier eqMithral = getQualifiedModifierNamed("Mithral", eq); createItem(eq, eqDarkwood, null, null, null); createItem(eq, eqAdamantine, null, null, null); createItem(eq, eqMithral, null, null, null); } } } private static void autogenerateMagicEquipment() { if (SettingsHandler.isAutogenMagic()) { for (int iPlus = 1; iPlus <= 5; iPlus++) { final String aBonus = Delta.toString(iPlus); for (Equipment eq : Globals.getContext().getReferenceContext().getConstructedCDOMObjects(Equipment.class)) { // Only apply to non-magical // Armor, Shield and Weapon if (eq.isMagic() || eq.isMasterwork() || (!eq.isAmmunition() && !eq.isArmor() && !eq.isShield() && !eq.isWeapon())) { continue; } // Items must be masterwork before // you can assign magic to them EquipmentModifier eqMod = getQualifiedModifierNamed("Masterwork", eq); if (eqMod == null) { Logging .debugPrint("Could not generate a Masterwork " + eq.toString() + " as the equipment modifier could not be found."); continue; } // Get list of choices final EquipmentChoice equipChoice = EquipmentChoiceDriver.buildEquipmentChoice(0, eq, eqMod, false, false, 0, null); // Iterate over list, creating an item for each choice. final Iterator<Object> equipIter = equipChoice.getChoiceIterator(true); for (; equipIter.hasNext();) { final String mwChoice = String.valueOf(equipIter.next()); eq = eq.clone(); eq.addEqModifier(eqMod, true, null, mwChoice, equipChoice); if (eq.isWeapon() && eq.isDouble()) { eq.addEqModifier(eqMod, false, null, mwChoice, equipChoice); } eqMod = getQualifiedModifierNamed(aBonus, eq); if (eqMod == null) { Logging .debugPrint("Could not generate a " + aBonus + " " + eq.toString() + " as the equipment modifier could not be found."); continue; } createItem(eq, eqMod, null, null, null); } } } } } private static void autogenerateMasterWorkEquipment() { if (SettingsHandler.isAutogenMasterwork()) { for (Equipment eq : Globals.getContext().getReferenceContext().getConstructedCDOMObjects(Equipment.class)) { // // Only apply to non-magical Armor, Shield and Weapon // if (eq.isMagic() || eq.isUnarmed() || eq.isMasterwork() || (!eq.isAmmunition() && !eq.isArmor() && !eq.isShield() && !eq.isWeapon())) { continue; } final EquipmentModifier eqMasterwork = getQualifiedModifierNamed("Masterwork", eq); if (eqMasterwork == null) { continue; } // Get list of choices (extract code from EquipmentModifier.getChoice) final EquipmentChoice equipChoice = EquipmentChoiceDriver.buildEquipmentChoice(0, eq, eqMasterwork, false, false, 0, null); // Iterate over list, creating an item for each choice. final Iterator<Object> equipIter = equipChoice.getChoiceIterator(true); for (; equipIter.hasNext();) { final String choice = String.valueOf(equipIter.next()); createItem(eq, eqMasterwork, null, choice, equipChoice); } } } } private static void autogenerateRacialEquipment() { if (SettingsHandler.isAutogenRacial()) { Set<Integer> gensizesid = new HashSet<>(); // // Go through all loaded races and flag whether or not to make equipment // sized for them. Karianna, changed the array length by 1 as Collosal // creatures weren't being catered for (and therefore an OutOfBounds exception // was being thrown) - Bug 937586 // AbstractReferenceContext ref = Globals.getContext().getReferenceContext(); for (final Race race : ref.getConstructedCDOMObjects(Race.class)) { /* * SIZE: in Race LST files enforces that the formula is fixed, * so no isStatic() check needed here */ final int iSize = race.getSafe(FormulaKey.SIZE).resolveStatic() .intValue(); gensizesid.add(iSize); } SizeAdjustment defaultSize = SizeUtilities.getDefaultSizeAdjustment(); Set<SizeAdjustment> gensizes = new HashSet<>(); for (Integer i : gensizesid) { gensizes.add(ref.getSortedList(SizeAdjustment.class, IntegerKey.SIZEORDER).get(i)); } // skip over default size gensizes.remove(defaultSize); PlayerCharacter dummyPc = new PlayerCharacter(); for (Equipment eq : ref.getConstructedCDOMObjects(Equipment.class)) { // // Only apply to Armor, Shield and resizable items // if (!Globals.canResizeHaveEffect(eq, null)) { continue; } for (SizeAdjustment sa : gensizes) { createItem(eq, sa, dummyPc); } } } } private static EquipmentModifier getModifierNamed(final String aName) { for (EquipmentModifier eqMod : Globals.getContext().getReferenceContext().getConstructedCDOMObjects(EquipmentModifier.class)) { if (eqMod.getDisplayName().equals(aName)) { return eqMod; } } return null; } private static EquipmentModifier getQualifiedModifierNamed(final String aName, final Equipment eq) { for (EquipmentModifier eqMod : Globals.getContext().getReferenceContext().getConstructedCDOMObjects(EquipmentModifier.class)) { if (eqMod.getDisplayName().startsWith(aName)) { for (String t : eq.typeList() ) { if (eqMod.isType(t)) { // Type matches, passes prereqs? if (PrereqHandler.passesAll(eqMod.getPrerequisiteList(), eq, null)) { return eqMod; } } } } } return null; } /** * Appends name parts to the newName. * * @param nameList * @param omitString * @param newName */ private static void appendNameParts(final List<String> nameList, final String omitString, final StringBuilder newName) { for ( String namePart : nameList ) { if ((!omitString.isEmpty()) && namePart.equals(omitString)) { continue; } if (newName.length() > 2) { newName.append('/'); } newName.append(namePart); } } private static void createItem(final Equipment eq, final SizeAdjustment sa, final PlayerCharacter aPC) { createItem(eq, null, sa, aPC, "", null); } private static void createItem(final Equipment eq, final EquipmentModifier eqMod, final PlayerCharacter aPC, final String choice, final EquipmentChoice equipChoice) { createItem(eq, eqMod, null, aPC, choice, equipChoice); } private static void createItem(Equipment eq, final EquipmentModifier eqMod, final SizeAdjustment sa, final PlayerCharacter aPC, final String choice, final EquipmentChoice equipChoice) { if (eq == null) { return; } try { // Armor without an armor bonus is an exception // if (!eq.getSafe(ObjectKey.MOD_CONTROL).getModifiersAllowed() || (eq.isArmor() && (eq.getACMod(aPC).intValue() == 0) && ((eqMod != null) && !eqMod.getDisplayName() .equalsIgnoreCase("MASTERWORK")))) { return; } eq = eq.clone(); if (eq == null) { Logging.errorPrint("could not clone item"); return; } if (eqMod != null) { eq.addEqModifier(eqMod, true, aPC, choice, equipChoice); if (eq.isWeapon() && eq.isDouble()) { eq.addEqModifier(eqMod, false, aPC, choice, equipChoice); } } if (sa != null) { eq.resizeItem(aPC, sa); } // // Change the names, to protect the innocent // final String sKeyName = eq.nameItemFromModifiers(aPC); final Equipment eqExists = Globals.getContext().getReferenceContext().silentlyGetConstructedCDOMObject( Equipment.class, sKeyName); if (eqExists != null) { return; } final Type newType; if (isAutoGeneration()) { newType = Type.AUTO_GEN; } else { newType = Type.CUSTOM; } if (!eq.isType(newType.toString())) { eq.addType(newType); } Globals.getContext().getReferenceContext().importObject(eq); } catch (NumberFormatException exception) { Logging.errorPrint("createItem: exception: " + eq.getName()); } } private static Equipment findEquipment(final String aName, final List<String> preNameList, final List<String> postNameList, final List<String> sizList, final String omitString) { final StringBuilder newName = new StringBuilder(80); newName.append(" ("); if (preNameList != null) { final List<String> nameList = preNameList; appendNameParts(nameList, omitString, newName); } if (sizList != null) { // Append 1st size if multiple sizes // if (sizList.size() > 1) { newName.append(sizList.get(0)); } } if (postNameList != null) { appendNameParts(postNameList, omitString, newName); } if (newName.length() == 2) { newName.setLength(0); } else { newName.append(')'); } final Equipment eq = Globals.getContext().getReferenceContext().silentlyGetConstructedCDOMObject( Equipment.class, aName + newName); return eq; } }