/* * PCLevelCastingInfo.java * Copyright 2006 (C) Tom Parker <thpr@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: November 8, 2006 * * $Id: PCClass.java 1605 2006-11-08 02:14:21Z thpr $ */ package pcgen.core; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.Map.Entry; import pcgen.base.formula.Formula; /** * SpellProgressionInfo contains information about Spell Progression in support * of a PCClass. * * @author Tom Parker <thpr@users.sourceforge.net> */ public class SpellProgressionCache implements Cloneable { /* * FUTURETYPESAFETY Currently can't do better than String in knownMap, * castMap and specialtyKnownMap, because each one of these can be a * formula, or some special gunk for Psionicists (can we clean up the +d??) */ /** * This is a Progression of KNOWN spells. */ private Progression knownProgression = null; /** * This is a Progression KNOWN spells added by a specialty. */ private Progression specialtyKnownProgression = null; /** * This is a Progression of CAST spells. */ private Progression castProgression = null; /** * Sets the Known spells for the given class level for this * SpellProgression. The given character level must be greater than or equal * to one. * * Note that this is a SET (not an ADD) and will therefore OVERWRITE a KNOWN * spell progression for the given class level if one is already present * within this SpellProgression. * * @param iLevel * The class level for which the given known spell progression * applies. * @param aList * The known spell progression for the given class level. * @return The previously set KNOWN spell progression for the given class * level; null if no KNOWN spell progression was previously set. */ public List<Formula> setKnown(int iLevel, List<Formula> aList) { if (knownProgression == null) { knownProgression = new Progression(); } return knownProgression.setProgression(iLevel, aList); } /** * Returns true if this SpellProgression contains a KNOWN spell progression. * (this is not required, e.g. OGL Wizards do not have a KNOWN spell * progression, rather they are limited by what spells are in their * spellbook(s)) * * @return True if this SpellProgression contains a known spell progression; * false otherwise. */ public boolean hasKnownProgression() { return knownProgression != null && knownProgression.hasProgression(); } /** * Returns the known spell progression for this SpellProgression. * * **WARNING** This method exposes the internal contents of this * SpellProgression object. This method is therefore reference-semantic, and * the returned Map and the Lists contained within the Map should be * considered owned by this SpellProgression. The returned Map and Lists it * contains should not be altered. * * CONSIDER How to get rid of this - it makes this Object accidentally * mutable. - thpr 11/8/06 * * @return The known spell progression for this SpellProgression object. */ public Map<Integer, List<Formula>> getKnownProgression() { return knownProgression == null ? null : knownProgression .getProgression(); } /** * Returns the known spell progression for the given class level. If this * SpellProgression does not contain a KNOWN spell progression or if the * given class level is not high enough to have known spells, this method * returns null. * * This method is value-semantic. Ownership of the returned List is * transferred to the calling object (The returned list can be modified * without impacting the internal contents of this SpellProgression) * * @param aLevel * The class level for which the known spell progression should * be returned. * @return The known spell progression for the given class level, or null if * there is no known spell progression for the given class level. */ public List<Formula> getKnownForLevel(int aLevel) { return knownProgression == null ? null : knownProgression .getProgressionForLevel(aLevel); } /** * Returns the highest possible known spell level in this SpellProgression. * * Note that this is a theoretical highest level, and based on the abilities * of a PlayerCharacter, the highest known spell level may not be available * to that PlayerCharacter. * * There are at least two known situations where the theoretical known spell * level is higher than what a specific PlayerCharacter could actually know: * (1) When a Stat limits the level of spells that a given PlayerCharacter * can learn (2) When a Class grants 0 known spells, but some * PlayerCharacters could have a Stat that provides bonus spells [this * method WILL return a spell level where a Class grants 0 spells, since * this is a theoretical limit test.] * * @return The highest possible known spell level. */ public int getHighestKnownSpellLevel() { return knownProgression == null ? 0 : knownProgression .getHighestSpellLevel(); } /** * Sets the KNOWN SPECIALTY spells for the given class level for this * SpellProgression. The given character level must be greater than or equal * to one. * * Note that this is a SET (not an ADD) and will therefore OVERWRITE a KNOWN * SPECIALTY spell progression for the given class level if one is already * present within this SpellProgression. * * @param aLevel * The class level for which the given known specialty spell * progression applies. * @param aList * The known specialty spell progression for the given class * level. * @return The previously set KNOWN SPECIALTY spell progression for the * given class level; null if no KNOWN SPECIALTY spell progression * was previously set. */ public List<Formula> setSpecialtyKnown(int aLevel, List<Formula> aList) { if (specialtyKnownProgression == null) { specialtyKnownProgression = new Progression(); } return specialtyKnownProgression.setProgression(aLevel, aList); } /** * Returns true if this SpellProgression contains KNOWN SPECIALTY spell * progressions. (this is not required, e.g. most 3.0SRD classes do not have * a KNOWN SPECIALTY list) * * @return True if this SpellProgression contains a known specialty spell * progression; false otherwise. */ public boolean hasSpecialtyKnownProgression() { return specialtyKnownProgression != null && specialtyKnownProgression.hasProgression(); } /** * Returns the known specialty spell progression for this SpellProgression. * * **WARNING** This method exposes the internal contents of this * SpellProgression object. This method is therefore reference-semantic, and * the returned Map and the Lists contained within the Map should be * considered owned by this SpellProgression. The returned Map and Lists it * contains should not be altered. * * CONSIDER How to get rid of this - it makes this Object accidentally * mutable. - thpr 11/8/06 * * @return The known specialty spell progression for this SpellProgression * object. */ public Map<Integer, List<Formula>> getSpecialtyKnownMap() { return specialtyKnownProgression == null ? null : specialtyKnownProgression.getProgression(); } /** * Returns the known specialty spell progression for the given class level. * If this SpellProgression does not contain a KNOWN SPECIALTY spell * progression or if the given class level is not high enough to have * entered the known specialty spell progression, this method returns null. * * This method is value-semantic. Ownership of the returned List is * transferred to the calling object (The returned list can be modified * without impacting the internal contents of this SpellProgression) * * @param aLevel * The class level for which the known specialty spell * progression should be returned. * @return The known specialty spell progression for the given class level, * or null if there is no known specialty spell progression for the * given class level. */ public List<Formula> getSpecialtyKnownForLevel(int aLevel) { return specialtyKnownProgression == null ? null : specialtyKnownProgression.getProgressionForLevel(aLevel); } /** * Sets the CAST spells for the given class level for this SpellProgression. * The given character level must be greater than or equal to one. * * Note that this is a SET (not an ADD) and will therefore OVERWRITE a CAST * spell progression for the given class level if one is already present * within this SpellProgression. * * @param aLevel * The class level for which the given CAST spell progression * applies. * @param aList * The CAST spell progression for the given class level. * @return The previously set CAST spell progression for the given class * level; null if no CAST spell progression was previously set. */ public List<Formula> setCast(int aLevel, List<Formula> aList) { if (castProgression == null) { castProgression = new Progression(); } return castProgression.setProgression(aLevel, aList); } /** * Returns true if this SpellProgression contains CAST spell progressions. * (this is not required, but would be a bit strange to be empty) * * @return True if this SpellProgression contains a CAST spell progression; * false otherwise. */ public boolean hasCastProgression() { return castProgression != null && castProgression.hasProgression(); } /** * Returns the CAST spell progression for this SpellProgression. * * **WARNING** This method exposes the internal contents of this * SpellProgression object. This method is therefore reference-semantic, and * the returned Map and the Lists contained within the Map should be * considered owned by this SpellProgression. The returned Map and Lists it * contains should not be altered. * * CONSIDER How to get rid of this - it makes this Object accidentally * mutable. - thpr 11/8/06 * * @return The CAST spell progression for this SpellProgression object. */ public Map<Integer, List<Formula>> getCastProgression() { return castProgression == null ? null : castProgression .getProgression(); } /** * Returns the CAST spell progression for the given class level. If this * SpellProgression does not contain a CAST spell progression or if the * given class level is not high enough to have CAST spells, this method * returns null. * * This method is value-semantic. Ownership of the returned List is * transferred to the calling object (The returned list can be modified * without impacting the internal contents of this SpellProgression) * * @param aLevel * The class level for which the CAST spell progression should be * returned. * @return The CAST spell progression for the given class level, or null if * there is no CAST spell progression for the given class level. */ public List<Formula> getCastForLevel(int aLevel) { return castProgression == null ? null : castProgression .getProgressionForLevel(aLevel); } /** * Returns the highest possible CAST spell level in this SpellProgression. * * Note that this is a theoretical highest level, and based on the abilities * of a PlayerCharacter, the highest CAST spell level may not be available * to that PlayerCharacter. * * There are at least two known situations where the theoretical CAST spell * level is higher than what a specific PlayerCharacter could actually know: * (1) When a Stat limits the level of spells that a given PlayerCharacter * can learn (2) When a Class grants 0 CAST spells, but some * PlayerCharacters could have a Stat that provides bonus spells [this * method WILL return a spell level where a Class grants 0 spells, since * this is a theoretical limit test.] * * @return The highest possible CAST spell level. */ public int getHighestCastSpellLevel() { return castProgression == null ? 0 : castProgression .getHighestSpellLevel(); } /** * Returns the minimum class level required to acquire a spell of the given * spell level (and potential application of bonuses). * * There are two situations that can also be distinguished: * * If allowBonus is true, then this will return a class level where the CAST * or KNOWN spell count is equal to zero (since it assumes that bonuses * could provide that spell level). This DOES NOT return a class level where * a spell count is "-" (or spells are not available at all). * * If allowBonus is false, this this will return a class level where the * CAST or KNOWN spell count is greater than zero (since no bonuses would be * applied to make the spell count a non-zero value) * * *WARNING* This method is KNOWN to be broken if this SpellProgressionInfo * contains a spell formula. Analysis must be done in context to the formula * in that case. * * @param spellLevel * The spell level for which the minimum required class level * will be returned * @param allowBonus * true if the class level is allowed to have a "0" CAST or KNOWN * spell count. * @return The minimum class level required to acquire a spell of the given * spell level (and potential application of bonuses) */ public int getMinLevelForSpellLevel(int spellLevel, boolean allowBonus) { if (castProgression != null) { int lvl = castProgression.getMinLevelForSpellLevel(spellLevel, allowBonus); if (lvl != -1) { return lvl; } } /* * CONSIDER Should the castMap really dominate the knownMap like this??? - * I know that it probably returns the intended result, but it needs to * be explained better in the comments. - thpr 11/9/06 */ if (knownProgression != null) { int lvl = knownProgression.getMinLevelForSpellLevel(spellLevel, allowBonus); if (lvl != -1) { return lvl; } } return -1; } /** * Returns the theoretical maximum spell level for the given class level. * * Note that this is a theoretical highest level, and based on the abilities * of a PlayerCharacter, the highest spell level may not be available to * that PlayerCharacter. * * This theoretical test WILL return a spell level where the class grants 0 * known or cast spells, as bonuses acquired from Stats can provide use of * that spell level. * * @param classLevel * The class level for which the theoretical maximum spell level * should be returned. * @return The theoretical maximum spell level for the given class level; -1 * if this SpellProgressionInfo does not contain any KNOWN or CAST * spell progressions for the given class level. */ public int getMaxSpellLevelForClassLevel(int classLevel) { /* * Delegation to get*ForLevel is required because it is possible that * the given class level itself does not have a CAST or KNOWN * progression, but that a lower level does. Those methods account for * that situation. */ if (castProgression != null) { List<Formula> knownList = castProgression .getProgressionForLevel(classLevel); if (knownList != null) { return knownList.size() - 1; } } if (knownProgression != null) { List<Formula> knownList = knownProgression .getProgressionForLevel(classLevel); if (knownList != null) { return knownList.size() - 1; } } return -1; } /** * Clones this SpellProgressionInfo object. A semi-deep (or semi-shallow, * depending on one's point of view) clone is performed, under the * assumption that the cloned object should be allowed to have any of the * SpellProgressionInfo.set* method called without allowing either the * original or the cloned SpellProgressionInfo object to accidentally modify * the other. * * There is the assumption, however, that the Lists contained within the * SpellProgressionInfo object are never modified, and violation of that * semantic rule either within SpellProgressionInfo or by other objects * which call the reference-semantic methods of SpellProgressionInfo can * render this clone insufficient. * * @return A semi-shallow Clone of this SpellProgressionInfo object. * @throws CloneNotSupportedException */ @Override public SpellProgressionCache clone() throws CloneNotSupportedException { SpellProgressionCache spi = (SpellProgressionCache) super.clone(); /* * Each of knownMap, specialtyKnownMap, and castMap need one level deep * clones. Since the Lists that are stored are never individually * modified in SpellProgressionInfo (they are always overwritten by a * new set*) there is no need to do a full depth clone. However, the one * level deep clone is required in case there is a PCClass.COPY or * something that then mods a KNOWN or CAST. */ if (knownProgression != null) { spi.knownProgression = knownProgression.clone(); } if (specialtyKnownProgression != null) { spi.specialtyKnownProgression = specialtyKnownProgression.clone(); } if (castProgression != null) { spi.castProgression = castProgression.clone(); } return spi; } /* * CONSIDER Do I want an "isValid" method to check that spell type is * defined, et al?? - ensure consistency? - thpr 11/9/06 */ /** * Stores an individual Progression within this SpellProgressionInfo. Broken * out as a separate class in order to maintain consistent behavior and * avoid a ton of redundant code within SpellProgressionInfo. */ private static class Progression implements Cloneable { /** * This is a Map of spells. The Integer key is the Class level, the * value is a List of constants or Formula for each SpellLevel. * * The progressionMap must not contain any null values. */ private TreeMap<Integer, List<Formula>> progressionMap = null; /** * Sets the spells for the given class level for this Progression. The * given character level must be greater than or equal to one. * * Note that this is a SET (not an ADD) and will therefore OVERWRITE a * spell progression for the given class level if one is already present * within this Progression. * * @param iLevel * The class level for which the given spell progression * applies. * @param aList * The spell progression for the given class level. * @return The previously set spell progression for the given class * level; null if no spell progression was previously set. */ public List<Formula> setProgression(int iLevel, List<Formula> aList) { if (iLevel < 1) { throw new IllegalArgumentException( "Level must be >= 1 in spell progression"); } if (aList == null) { throw new IllegalArgumentException( "Cannot add null spell progression list to level " + iLevel); } if (aList.isEmpty()) { throw new IllegalArgumentException( "Cannot add empty spell progression list to level " + iLevel); } if (aList.contains(null)) { throw new IllegalArgumentException( "Cannot have null value in spell progrssion list in level " + iLevel); } if (progressionMap == null) { progressionMap = new TreeMap<>(); } return progressionMap.put(iLevel, new ArrayList<>(aList)); } public int getMinLevelForSpellLevel(int spellLevel, boolean allowBonus) { for (Entry<Integer, List<Formula>> me : progressionMap.entrySet()) { List<Formula> progressionList = me.getValue(); for (int lvl = spellLevel; lvl < progressionList.size(); lvl++) { /* * This for loop is to protect against a (admittedly * strange) class that grants N + 1 level spells, but not N * and N is the spellLevel parameter to this method. */ /* * FIXME This will break if there are spell formulae - thpr * 11/9/06 */ if (allowBonus || Integer.parseInt(progressionList.get(lvl).toString()) != 0) { return me.getKey(); } } } return -1; } /** * Returns true if this Progression contains a spell progression. * * @return True if this Progression contains a spell progression; false * otherwise. */ public boolean hasProgression() { return progressionMap != null; } /** * Returns the spell progression for this Progression. * * **WARNING** This method exposes the internal contents of this * Progression object. This method is therefore reference-semantic, and * the returned Map and the Lists contained within the Map should be * considered owned by this Progression. The returned Map and Lists it * contains should not be altered. * * CONSIDER How to get rid of this - it makes this Object accidentally * mutable. - thpr 11/8/06 * * @return The spell progression for this Progression object. */ public Map<Integer, List<Formula>> getProgression() { if (progressionMap == null) { return null; } return progressionMap; } /** * Returns the spell progression for the given class level. If this * Progression does not contain a spell progression or if the given * class level is not high enough to have spells, this method returns * null. * * This method is value-semantic. Ownership of the returned List is * transferred to the calling object (The returned list can be modified * without impacting the internal contents of this Progression) * * @param classLevel * The class level for which the spell progression should be * returned. * @return The spell progression for the given class level, or null if * there is no spell progression for the given class level. */ public List<Formula> getProgressionForLevel(int classLevel) { List<Formula> spellProgression = null; boolean found = false; if(progressionMap != null) { Integer key = classLevel; if (!progressionMap.containsKey(key)) { //No spellcasting at level key, check previous levels if (progressionMap.firstKey() < classLevel) { key = progressionMap.headMap(key).lastKey(); found = true; } } else { found = true; } if(found) { List<Formula> list = progressionMap.get(key); spellProgression = new ArrayList<>(list); } } return spellProgression; } /** * Returns the highest possible spell level in this Progression. * * Note that this is a theoretical highest level, and based on the * abilities of a PlayerCharacter, the highest spell level may not be * available to that PlayerCharacter. * * There are at least two known situations where the theoretical spell * level is higher than what a specific PlayerCharacter could actually * know: (1) When a Stat limits the level of spells that a given * PlayerCharacter can learn (2) When a Class grants 0 known spells, but * some PlayerCharacters could have a Stat that provides bonus spells * [this method WILL return a spell level where a Class grants 0 spells, * since this is a theoretical limit test.] * * @return The highest possible spell level in this Progression. */ public int getHighestSpellLevel() { if (progressionMap != null) { int highest = -1; for (List<Formula> list : progressionMap.values()) { highest = Math.max(highest, list.size() - 1); } return highest; } return -1; } /** * Clones this Progression object. A semi-deep (or semi-shallow, * depending on one's point of view) clone is performed, under the * assumption that the cloned object should be allowed to have the * Progression.set* method called without allowing either the original * or the cloned Progression object to accidentally modify the other. * * There is the assumption, however, that the Lists contained within the * Progression object are never modified, and violation of that semantic * rule either within Progression or by other objects which call the * reference-semantic methods of Progression can render this clone * insufficient. * * @return A semi-shallow Clone of this Progression object. * @throws CloneNotSupportedException */ @Override public Progression clone() throws CloneNotSupportedException { Progression p = (Progression) super.clone(); if (progressionMap != null) { p.progressionMap = new TreeMap<>( progressionMap); } return p; } } public boolean isEmpty() { return knownProgression == null && castProgression == null && specialtyKnownProgression == null; } }