/* * Copyright 2007 (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 */ package pcgen.cdom.processor; import pcgen.cdom.content.HitDie; import pcgen.cdom.content.Processor; /** * A HitDieStep represents a modified HitDie that changes along the path of Hit * Dice defined in the Game Mode. A bound may be set in order to limit movement * beyond a given point. * * Because the number of steps that this HitDieStep will apply is provided * during construction, the object constructing this HitDieStep is expected to * understand if it is setting an upper or lower bound. */ public class HitDieStep implements Processor<HitDie> { /** * The number of steps by which this HitDieStep object will modify the * incoming HitDie. */ private final int numSteps; /** * The bound beyond which this HitDieStep will not modify the incoming * HitDie. This bound overrides the number of steps to be taken, if the * bound is reached. */ private final HitDie dieLimit; /** * Constructs a new HitDieStep object with the given number of steps and * bound. * * Because the number of steps that this HitDieStep will apply is provided * during construction, the object constructing this HitDieStep is expected * to understand if it is setting an upper or lower bound. * * *NOTE* if the HitDie provided as a bound is not in the global sequence of * HitDie objects (defined in the Game Mode), then this method will fail to * stop at the bound. Matching on this bound is exact. * * @param steps * The number of steps this HitDieStep will modify the incoming * HitDie provided to applyProcessor * @param stopAt * The bound, indicating the HitDie at which the HitDitStep will * not proceed. This bound overrides the number of steps to be * taken, if the bound is reached. This bound may be null to * indicate there is no bound. * @throws IllegalArgumentException * if the number of steps is zero (since that is effectively a * pass-through, no Processor is required) */ public HitDieStep(int steps, HitDie stopAt) { if (steps == 0) { throw new IllegalArgumentException(); } numSteps = steps; dieLimit = stopAt; } /** * Applies this Processor to the given input object, in the context of the * given context object. * * *NOTE* if the HitDie provided is not in the global sequence of HitDie * objects (defined in the Game Mode), then this method will fail to step * that HitDie, and the original, unmodified HitDie will be returned. * * Since HitDieStep is universal, the given context is ignored. * * @param origHD * The input HitDie this HitDieStep will act upon. * @param context * The context, ignored by HitDieStep. * @return The modified HitDie, as limited by the bound of this HitDieStep. * @throws NullPointerException * if the given HitDie is null */ @Override public HitDie applyProcessor(HitDie origHD, Object context) { int steps = numSteps; HitDie currentDie = origHD; while (steps != 0) { // Order is important, dieLimit may be null if (currentDie.equals(dieLimit)) { return currentDie; } if (steps > 0) { currentDie = currentDie.getNext(); steps--; } else { assert steps < 0; currentDie = currentDie.getPrevious(); steps++; } } return currentDie; /* * Theoretically, the die sizes here should be stored as ... what? A * AbstractSequencedConstant, effectively? This gives the ability to * look up the next one... that makes HitDie not really storing an * Int... so Hit Die really should be a helper, or an enumeration? * * So it looks like an enumeration is OUT because the MODs will actually * alter to unexpected values... like 8 * 3 = 24... therefore, this * really needs to be thought through to determine what is best... The * behavior for these cases is undefined. * * There is a (short) thread on pcgen-devel "Hit Die Locking" from Nov * 2006, where this issue remains unresolved. */ } /** * Returns a representation of this HitDieStep, suitable for storing in an * LST file. * * @return A representation of this HitDieStep, suitable for storing in an * LST file. */ @Override public String getLSTformat() { StringBuilder sb = new StringBuilder(); sb.append('%'); if (dieLimit == null) { sb.append('H'); } if (numSteps > 0) { sb.append("up"); } else { sb.append("down"); } sb.append(Math.abs(numSteps)); return sb.toString(); } /** * The class of object this Processor acts upon (HitDie). * * @return The class of object this Processor acts upon (HitDie.class) */ @Override public Class<HitDie> getModifiedClass() { return HitDie.class; } /** * Returns the consistent-with-equals hashCode for this HitDieStep * * @see java.lang.Object#hashCode() */ @Override public int hashCode() { return dieLimit == null ? numSteps : numSteps + dieLimit.hashCode() * 29; } /** * Returns true if this HitDieStep is equal to the given Object. Equality is * defined as being another HitDieStep object with step count and HitDie * limit. * * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { if (obj instanceof HitDieStep) { HitDieStep other = (HitDieStep) obj; return other.numSteps == numSteps && (dieLimit == null && other.dieLimit == null || dieLimit != null && dieLimit.equals(other.dieLimit)); } return false; } }