/* * Copyright (c) Thomas Parker, 2009. * * This program 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 program 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ package pcgen.cdom.facet.model; import java.util.List; import pcgen.base.formula.Formula; import pcgen.cdom.base.CDOMObject; import pcgen.cdom.base.ItemFacet; import pcgen.cdom.enumeration.CharID; import pcgen.cdom.enumeration.FormulaKey; import pcgen.cdom.enumeration.IntegerKey; import pcgen.cdom.enumeration.ListKey; import pcgen.cdom.facet.BonusChangeFacet.BonusChangeEvent; import pcgen.cdom.facet.BonusChangeFacet.BonusChangeListener; import pcgen.cdom.facet.BonusCheckingFacet; import pcgen.cdom.facet.CDOMObjectConsolidationFacet; import pcgen.cdom.facet.FormulaResolvingFacet; import pcgen.cdom.facet.analysis.LevelFacet; import pcgen.cdom.facet.analysis.LevelFacet.LevelChangeEvent; import pcgen.cdom.facet.analysis.LevelFacet.LevelChangeListener; import pcgen.cdom.facet.base.AbstractDataFacet; import pcgen.cdom.facet.event.DataFacetChangeEvent; import pcgen.cdom.facet.event.DataFacetChangeListener; import pcgen.core.Globals; import pcgen.core.PCTemplate; import pcgen.core.Race; import pcgen.core.SizeAdjustment; import pcgen.core.analysis.SizeUtilities; import pcgen.output.publish.OutputDB; /** * SizeFacet tracks the SizeAdjustment for a Player Character. * * @author Thomas Parker (thpr [at] yahoo.com) */ public class SizeFacet extends AbstractDataFacet<CharID, SizeAdjustment> implements DataFacetChangeListener<CharID, CDOMObject>, LevelChangeListener, BonusChangeListener, ItemFacet<CharID, SizeAdjustment> { private static final Class<SizeAdjustment> SIZEADJUSTMENT_CLASS = SizeAdjustment.class; private TemplateFacet templateFacet; private RaceFacet raceFacet; private FormulaResolvingFacet formulaResolvingFacet; private BonusCheckingFacet bonusCheckingFacet; private LevelFacet levelFacet; private CDOMObjectConsolidationFacet consolidationFacet; /** * Returns the integer indicating the racial size for the Player Character * identified by the given CharID. * * @param id * The CharID identifying the Player Character for which the * racial size will be returned * @return the integer indicating the racial size for the Player Character * identified by the given CharID */ public int racialSizeInt(CharID id) { SizeFacetInfo info = getInfo(id); if (info == null) { return SizeUtilities.getDefaultSizeAdjustment().get( IntegerKey.SIZEORDER); } return info.racialSizeInt; } private int calcRacialSizeInt(CharID id) { SizeFacetInfo info = getConstructingInfo(id); int iSize = SizeUtilities.getDefaultSizeAdjustment().get(IntegerKey.SIZEORDER); Race race = raceFacet.get(id); if (race != null) { // get the base size for the race Formula size = race.getSafe(FormulaKey.SIZE); iSize = formulaResolvingFacet.resolve(id, size, "") .intValue(); // now check and see if a template has set the // size of the character in question // with something like SIZE:L for (PCTemplate template : templateFacet.getSet(id)) { Formula sizeFormula = template.get(FormulaKey.SIZE); if (sizeFormula != null) { iSize = formulaResolvingFacet.resolve(id, sizeFormula, template.getKeyName()).intValue(); } } } info.racialSizeInt = iSize; return iSize; } /** * Returns the integer indicating the size of the Player Character * identified by the given CharID. * * @param id * The CharID identifying the Player Character for which the * integer indicating the size of the Player Character. * @return the integer indicating the size of the Player Character * identified by the given CharID */ public int sizeInt(CharID id) { SizeFacetInfo info = getInfo(id); if (info == null) { return SizeUtilities.getDefaultSizeAdjustment().get( IntegerKey.SIZEORDER); } return info.sizeInt; } /** * Forces a complete update of the size information for the Player Character * identified by the given CharID. * * @param id * The CharID indicating the Player Character on which to update * the size information */ public void update(CharID id) { SizeFacetInfo info = getConstructingInfo(id); int iSize = calcRacialSizeInt(id); Race race = raceFacet.get(id); if (race != null) { // Now check and see if a class has modified // the size of the character with something like: // BONUS:SIZEMOD|NUMBER|+1 iSize += (int) bonusCheckingFacet.getBonus(id, "SIZEMOD", "NUMBER"); // Now see if there is a HD advancement in size // (Such as for Dragons) iSize += sizesToAdvance(id, race); // // Must still be be a valid size // int maxIndex = Globals.getContext().getReferenceContext() .getConstructedObjectCount(SIZEADJUSTMENT_CLASS) - 1; iSize = Math.min(maxIndex, Math.max(0, iSize)); } info.sizeInt = iSize; SizeAdjustment oldSize = info.sizeAdj; SizeAdjustment newSize = Globals.getContext().getReferenceContext() .getSortedList(SizeAdjustment.class, IntegerKey.SIZEORDER) .get(sizeInt(id)); info.sizeAdj = newSize; if (oldSize != newSize) { if (oldSize != null) { fireDataFacetChangeEvent(id, oldSize, DataFacetChangeEvent.DATA_REMOVED); } fireDataFacetChangeEvent(id, newSize, DataFacetChangeEvent.DATA_ADDED); } } private int sizesToAdvance(CharID id, Race race) { return sizesToAdvance(race, levelFacet.getMonsterLevelCount(id)); } int sizesToAdvance(Race race, int monsterLevelCount) { List<Integer> hda = race.getListFor(ListKey.HITDICE_ADVANCEMENT); int steps = 0; if (hda != null) { int limit = race.maxHitDiceAdvancement(); for (Integer hitDie : hda) { if (monsterLevelCount <= hitDie) { break; } if (hitDie < limit) { steps++; } } } return steps; } /** * Returns the SizeAdjustment active for the Player Character identified by * the given CharID. * * @param id * The CharID identifying the Player Character for which the * SizeAdjustment will be returned * @return The SizeAdjustment active for the Player Character identified by * the given CharID */ @Override public SizeAdjustment get(CharID id) { SizeFacetInfo info = getInfo(id); return info == null ? SizeUtilities.getDefaultSizeAdjustment() : info.sizeAdj; } /** * Returns the abbreviation of the SizeAdjustment active for the Player * Character identified by the given CharID. * * @param id * The CharID identifying the Player Character for which the * abbreviation of the SizeAdjustment will be returned * @return The abbreviation of the SizeAdjustment active for the Player * Character identified by the given CharID */ public String getSizeAbb(CharID id) { return get(id).getKeyName(); } /** * Returns the type-safe SizeFacetInfo for this SizeFacet and the given * CharID. Will return a new, empty SizeFacetInfo if no Size information has * been set for the given CharID. Will not return null. * * Note that this method SHOULD NOT be public. The SizeFacetInfo object is * owned by SizeFacet, and since it can be modified, a reference to that * object should not be exposed to any object other than SizeFacet. * * @param id * The CharID for which the SizeFacetInfo should be returned * @return The SizeFacetInfo for the Player Character represented by the * given CharID. */ private SizeFacetInfo getConstructingInfo(CharID id) { SizeFacetInfo rci = getInfo(id); if (rci == null) { rci = new SizeFacetInfo(); setCache(id, rci); } return rci; } /** * Returns the type-safe SizeFacetInfo for this SizeFacet and the given * CharID. Will return a null if no Size information has been set for the * given CharID. * * Note that this method SHOULD NOT be public. The SizeFacetInfo object is * owned by SizeFacet, and since it can be modified, a reference to that * object should not be exposed to any object other than SizeFacet. * * @param id * The CharID for which the SizeFacetInfo should be returned * @return The SizeFacetInfo for the Player Character represented by the * given CharID. */ private SizeFacetInfo getInfo(CharID id) { return (SizeFacetInfo) getCache(id); } /** * SizeFacetInfo is the data structure used by SizeFacet to store a Player * Character's size information. */ private static class SizeFacetInfo { private int sizeInt; private int racialSizeInt; private SizeAdjustment sizeAdj; @Override public int hashCode() { return sizeInt ^ racialSizeInt * 29; } @Override public boolean equals(Object o) { if (o == this) { return true; } if (o instanceof SizeFacetInfo) { SizeFacetInfo sfi = (SizeFacetInfo) o; return (sizeInt == sfi.sizeInt) && (racialSizeInt == sfi.racialSizeInt) && sizeAdj.equals(sizeAdj); } return false; } } /** * Drives a recalculation of the size information for a Player Character * when a CDOMObject is added to the Player Character. * * Triggered when one of the Facets to which SizeFacet listens fires a * DataFacetChangeEvent to indicate a CDOMObject was added to a Player * Character. * * @param dfce * The DataFacetChangeEvent containing the information about the * change * * @see pcgen.cdom.facet.event.DataFacetChangeListener#dataAdded(pcgen.cdom.facet.event.DataFacetChangeEvent) */ @Override public void dataAdded(DataFacetChangeEvent<CharID, CDOMObject> dfce) { update(dfce.getCharID()); } /** * Drives a recalculation of the size information for a Player Character * when a CDOMObject is removed from the Player Character. * * Triggered when one of the Facets to which SizeFacet listens fires a * DataFacetChangeEvent to indicate a CDOMObject was removed from a Player * Character. * * @param dfce * The DataFacetChangeEvent containing the information about the * change * * @see pcgen.cdom.facet.event.DataFacetChangeListener#dataRemoved(pcgen.cdom.facet.event.DataFacetChangeEvent) */ @Override public void dataRemoved(DataFacetChangeEvent<CharID, CDOMObject> dfce) { update(dfce.getCharID()); } /** * Drives a recalculation of the size information for a Player Character * when the level of the Player Character is changed. * * @param lce * The LevelChangeEvent containing the information about the * change * * @see pcgen.cdom.facet.event.DataFacetChangeListener#dataRemoved(pcgen.cdom.facet.event.DataFacetChangeEvent) */ @Override public void levelChanged(LevelChangeEvent lce) { update(lce.getCharID()); } /** * Drives a recalculation of the size information for a Player Character * when a BONUS on the Player Character is changed. * * @param bce * The BonusChangeEvent containing the information about the * change * * @see pcgen.cdom.facet.event.DataFacetChangeListener#dataRemoved(pcgen.cdom.facet.event.DataFacetChangeEvent) */ @Override public void bonusChange(BonusChangeEvent bce) { update(bce.getCharID()); } public void setTemplateFacet(TemplateFacet templateFacet) { this.templateFacet = templateFacet; } public void setRaceFacet(RaceFacet raceFacet) { this.raceFacet = raceFacet; } public void setFormulaResolvingFacet(FormulaResolvingFacet formulaResolvingFacet) { this.formulaResolvingFacet = formulaResolvingFacet; } public void setBonusCheckingFacet(BonusCheckingFacet bonusCheckingFacet) { this.bonusCheckingFacet = bonusCheckingFacet; } public void setLevelFacet(LevelFacet levelFacet) { this.levelFacet = levelFacet; } public void setConsolidationFacet(CDOMObjectConsolidationFacet consolidationFacet) { this.consolidationFacet = consolidationFacet; } /** * Initializes the connections for SizeFacet to other facets. * * This method is automatically called by the Spring framework during * initialization of the SizeFacet. */ public void init() { consolidationFacet.addDataFacetChangeListener(this); OutputDB.register("sizeadjustment", this); } /** * Copies the contents of the SizeFacet from one Player Character to another * Player Character, based on the given CharIDs representing those Player * Characters. * * This is a method in SizeFacet in order to avoid exposing the mutable * SizeFacetInfo object to other classes. This should not be inlined, as * SizeFacetInfo is internal information to SizeFacet and should not be * exposed to other classes. * * Note also the copy is a one-time event and no Size references are * maintained between the Player Characters represented by the given CharIDs * (meaning once this copy takes place, any change to the Size will only * impact the Player Character where the Size was changed). * * @param source * The CharID representing the Player Character from which the * Size information should be copied * @param copy * The CharID representing the Player Character to which the Size * information should be copied */ @Override public void copyContents(CharID source, CharID copy) { SizeFacetInfo si = getInfo(source); if (si != null) { SizeFacetInfo copysfi = getConstructingInfo(copy); copysfi.racialSizeInt = si.racialSizeInt; copysfi.sizeAdj = si.sizeAdj; copysfi.sizeInt = si.sizeInt; } } }