/* * Copyright (c) Thomas Parker, 2012. * * 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; import pcgen.cdom.base.CDOMObject; import pcgen.cdom.content.HitDie; import pcgen.cdom.content.Modifier; import pcgen.cdom.enumeration.CharID; import pcgen.cdom.enumeration.ObjectKey; import pcgen.cdom.facet.analysis.LevelFacet; import pcgen.cdom.facet.base.AbstractAssociationFacet; import pcgen.cdom.facet.event.DataFacetChangeEvent; import pcgen.cdom.facet.event.DataFacetChangeListener; import pcgen.cdom.facet.model.ClassFacet; import pcgen.cdom.facet.model.RaceFacet; import pcgen.cdom.facet.model.TemplateFacet; import pcgen.cdom.inst.PCClassLevel; import pcgen.core.Globals; import pcgen.core.PCClass; import pcgen.core.PCTemplate; import pcgen.core.PlayerCharacter; import pcgen.core.SettingsHandler; /** * HitPointFacet stores information about hit points for a Player Character. * Specifically this Facet stores the number of hit points granted to a Player * Character for each PCClassLevel possessed by the Player Character. * * @author Thomas Parker (thpr [at] yahoo.com) */ public class HitPointFacet extends AbstractAssociationFacet<PCClassLevel, Integer> implements DataFacetChangeListener<CharID, CDOMObject> { private final PlayerCharacterTrackingFacet trackingFacet = FacetLibrary .getFacet(PlayerCharacterTrackingFacet.class); private ClassFacet classFacet; private RaceFacet raceFacet; private TemplateFacet templateFacet; private LevelFacet levelFacet; private BonusCheckingFacet bonusCheckingFacet; /** * Watches for new PCClassLevel objects to be granted to the Player * Character. When called, this then triggers the determination of the Hit * Points for that PCClassLevel. * * Triggered when one of the Facets to which FollowerOptionFacet listens * fires a DataFacetChangeEvent to indicate a FollowerOption 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) { CharID id = dfce.getCharID(); CDOMObject cdo = dfce.getCDOMObject(); PlayerCharacter pc = trackingFacet.getPC(id); if (!pc.isImporting()) { boolean first = true; for (PCClass pcClass : classFacet.getClassSet(id)) { // // Recalculate HPs in case HD have changed. // Modifier<HitDie> dieLock = cdo.get(ObjectKey.HITDIE); if (dieLock != null) { for (int level = 1; level <= classFacet.getLevel(id, pcClass); level++) { HitDie baseHD = pcClass.getSafe(ObjectKey.LEVEL_HITDIE); if (!baseHD.equals(getLevelHitDie(id, pcClass, level))) { // If the HD has changed from base reroll rollHP(id, pcClass, level, first); pc.setDirty(true); } } } first = false; } } } @Override public void dataRemoved(DataFacetChangeEvent<CharID, CDOMObject> dfce) { /* * TODO This probably needs some form of symmetry - when a PCClassLevel * is removed, the number of hit points for that PCClassLevel is * removed. * * Alternatively, we can define this in such a way that the otherwise * lost information is saved, so that addition and removal of the same * level doesn't trigger new seleciton of hit points - need to define * the best strategy here (and just clearly document the decision) */ } /** * Returns the HitDie for the given PCClass and level in the Player * Character identified by the given CharID. * * @param id * The CharID identifying the Player Character for which the * HitDie of the given PCClass and level will be returned * @param pcClass * The PCClass for which the HitDie will be returned * @param classLevel * The level for which the HitDie will be returned * @return The HitDie for the given PCClass and level in the Player * Character identified by the given CharID */ public HitDie getLevelHitDie(CharID id, PCClass pcClass, int classLevel) { // Class Base Hit Die HitDie currDie = pcClass.getSafe(ObjectKey.LEVEL_HITDIE); Modifier<HitDie> dieLock = raceFacet.get(id).get(ObjectKey.HITDIE); if (dieLock != null) { currDie = dieLock.applyModifier(currDie, pcClass); } // Templates for (PCTemplate template : templateFacet.getSet(id)) { if (template != null) { Modifier<HitDie> lock = template.get(ObjectKey.HITDIE); if (lock != null) { currDie = lock.applyModifier(currDie, pcClass); } } } // Levels PCClassLevel cl = classFacet.getClassLevel(id, pcClass, classLevel); if (cl != null) { if (cl.get(ObjectKey.DONTADD_HITDIE) != null) { currDie = HitDie.ZERO; //null; } else { Modifier<HitDie> lock = cl.get(ObjectKey.HITDIE); if (lock != null) { currDie = lock.applyModifier(currDie, pcClass); } } } return currDie; } /** * Rolls the hit points for a given PCClass and level. * * @param id * The CharID identifying the Player Character on which the hit * points are to be rolled * @param pcc * The PCClass for which the hit points are to be rolled * @param level * The class level for which the hit points are to be rolled * @param first * And identifier indicating if this is the Player Character's * first level. */ public void rollHP(CharID id, PCClass pcc, int level, boolean first) { int roll = 0; HitDie lvlDie = getLevelHitDie(id, pcc, level); if ((lvlDie == null) || (lvlDie.getDie() == 0)) { roll = 0; } else { final int min = 1 + (int) bonusCheckingFacet.getBonus(id, "HD", "MIN") + (int) bonusCheckingFacet.getBonus(id, "HD", "MIN;CLASS." + pcc.getKeyName()); final int max = getLevelHitDie(id, pcc, level).getDie() + (int) bonusCheckingFacet.getBonus(id, "HD", "MAX") + (int) bonusCheckingFacet.getBonus(id, "HD", "MAX;CLASS." + pcc.getKeyName()); if (SettingsHandler.getGame().getHPFormula().length() == 0) { if (first && level == 1 && SettingsHandler.isHPMaxAtFirstLevel() && (!SettingsHandler.isHPMaxAtFirstPCClassLevelOnly() || pcc .isType("PC"))) { roll = max; } else { PlayerCharacter pc = trackingFacet.getPC(id); if (!pc.isImporting()) { roll = Globals.rollHP(min, max, pcc.getDisplayName(), level, levelFacet.getTotalLevels(id)); } } } roll += ((int) bonusCheckingFacet.getBonus(id, "HP", "CURRENTMAXPERLEVEL")); } PCClassLevel classLevel = classFacet.getClassLevel(id, pcc, level - 1); set(id, classLevel, Integer.valueOf(roll)); } public void setClassFacet(ClassFacet classFacet) { this.classFacet = classFacet; } public void setRaceFacet(RaceFacet raceFacet) { this.raceFacet = raceFacet; } public void setTemplateFacet(TemplateFacet templateFacet) { this.templateFacet = templateFacet; } public void setLevelFacet(LevelFacet levelFacet) { this.levelFacet = levelFacet; } public void setBonusCheckingFacet(BonusCheckingFacet bonusCheckingFacet) { this.bonusCheckingFacet = bonusCheckingFacet; } /** * Initializes the connections for HitPointFacet to other facets. * * This method is automatically called by the Spring framework during * initialization of the HitPointFacet. */ public void init() { templateFacet.addDataFacetChangeListener(this); } }