pcgen.cdom.facet.analysis.ChallengeRatingFacet.java Source code

Java tutorial

Introduction

Here is the source code for pcgen.cdom.facet.analysis.ChallengeRatingFacet.java

Source

/*
 * 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.analysis;

import java.util.List;
import java.util.Map;

import org.apache.commons.lang.math.Fraction;

import pcgen.base.formula.Formula;
import pcgen.cdom.base.FormulaFactory;
import pcgen.cdom.content.ChallengeRating;
import pcgen.cdom.enumeration.CharID;
import pcgen.cdom.enumeration.FormulaKey;
import pcgen.cdom.enumeration.ListKey;
import pcgen.cdom.enumeration.MapKey;
import pcgen.cdom.enumeration.ObjectKey;
import pcgen.cdom.enumeration.Type;
import pcgen.cdom.facet.BonusCheckingFacet;
import pcgen.cdom.facet.FormulaResolvingFacet;
import pcgen.cdom.facet.model.ClassFacet;
import pcgen.cdom.facet.model.RaceFacet;
import pcgen.cdom.facet.model.TemplateFacet;
import pcgen.core.ClassType;
import pcgen.core.PCClass;
import pcgen.core.PCTemplate;
import pcgen.core.SettingsHandler;

/**
 * ChallengeRatingFacet is a Facet that calculates the Challenge Rating of a
 * Player Character
 * 
 * @author Thomas Parker (thpr [at] yahoo.com)
 */
public class ChallengeRatingFacet {
    private TemplateFacet templateFacet;
    private RaceFacet raceFacet;
    private ClassFacet classFacet;
    private FormulaResolvingFacet formulaResolvingFacet;
    private BonusCheckingFacet bonusCheckingFacet;
    private LevelFacet levelFacet;

    /**
     * Returns the Challenge Rating of the Player Character represented by the
     * given CharID
     * 
     * @param id
     *            The CharID representing the Player Character for which the
     *            Challenge Rating should be returned
     * @return The Challenge Rating of the Player Character represented by the
     *         given CharID
     */
    public Float getCR(CharID id) {
        Float CR = new Float(0);

        if (levelFacet.getMonsterLevelCount(id) == 0) {
            if (levelFacet.getNonMonsterLevelCount(id) == 0) {
                return Float.NaN;
            }
            // calculate and add class CR for 0-HD races
            CR += calcClassesCR(id);
        } else {
            // calculate and add race CR and classes CR for 
            // races with racial hit dice
            Float classRaceCR = calcClassesForRaceCR(id);
            if (classRaceCR.isNaN()) {
                return Float.NaN;
            }
            CR += calcRaceCR(id);
            CR += classRaceCR;
        }

        // calculate and add CR bonus from templates
        CR += getTemplateCR(id);

        // calculate and add in the MISC bonus to CR
        CR += (float) bonusCheckingFacet.getBonus(id, "MISC", "CR");

        // change calculated results of less than CR 1 to fraction format
        if (CR < 1) {
            Float crMod = SettingsHandler.getGame().getCRSteps().get((int) CR.floatValue());
            if (crMod != null) {
                CR = crMod;
            } else {
                CR = Math.max(CR, 0);
            }
        }

        return CR;
    }

    /**
     * Returns the ChallengeRating provided solely by the Race of the Player
     * Character identified by the given CharID.
     * 
     * @param id
     *            The CharID representing the Player Character
     * @return the Challenge Rating provided by the Race of the Player Character
     *         identified by the given CharID
     */
    public Float calcRaceCR(CharID id) {
        // Calculate and add the CR from race
        ChallengeRating cr = raceFacet.get(id).getSafe(ObjectKey.CHALLENGE_RATING);
        final Float raceCR = formulaResolvingFacet.resolve(id, cr.getRating(), "").floatValue();
        return raceCR;
    }

    /**
     * Returns the ChallengeRating provided solely by PCTemplate objects granted
     * to the Player Character identified by the given CharID.
     * 
     * @param id
     *            The CharID representing the Player Character
     * @return the Challenge Rating provided by the PCTemplate objects granted
     *         to the Player Character identified by the given CharID
     */
    private Float getTemplateCR(CharID id) {
        Float CR = new Float(0);

        // Calculate and add the CR from the templates
        for (PCTemplate template : templateFacet.getSet(id)) {
            CR += template.getCR(levelFacet.getTotalLevels(id), levelFacet.getMonsterLevelCount(id));
        }
        return CR;
    }

    /**
     * Returns the ChallengeRating provided solely by PCClass objects granted to
     * the Player Character identified by the given CharID.
     * 
     * @param id
     *            The CharID representing the Player Character
     * @return the Challenge Rating provided by the PCClass objects granted to
     *         the Player Character identified by the given CharID
     */
    private Float calcClassesCR(CharID id) {
        Float CR = new Float(0);
        Float CRMod = new Float(0);
        int CRModPriority = 0;

        for (PCClass pcClass : classFacet.getClassSet(id)) {
            CR += calcClassCR(id, pcClass);
            int crmp = getClassCRModPriority(id, pcClass);
            if (crmp != 0 && (crmp < CRModPriority || CRModPriority == 0)) {
                Float raceMod = getClassRaceCRMod(id, pcClass);
                if (raceMod != null) {
                    CRMod = raceMod;
                } else {
                    CRMod = getClassCRMod(id, pcClass);
                }
                CRModPriority = crmp;
            }
        }
        CR += CRMod;

        return CR;
    }

    private Float calcClassesForRaceCR(CharID id) {
        Float CR = new Float(0);
        int levelsKey = 0;
        int levelsNonKey = 0;
        int levelsConverted = 0;
        int threshold = 0;

        List<String> raceRoleList = raceFacet.get(id).getListFor(ListKey.MONSTER_ROLES);
        if (raceRoleList == null || raceRoleList.isEmpty()) {
            raceRoleList = SettingsHandler.getGame().getMonsterRoleDefaultList();
        }

        // Calculate and add the CR from the PC Classes
        for (PCClass pcClass : classFacet.getClassSet(id)) {
            Float levels = calcClassCR(id, pcClass);
            if (levels.isNaN()) {
                return Float.NaN;
            }

            List<String> classRoleList = pcClass.getListFor(ListKey.MONSTER_ROLES);
            if (classRoleList != null) {
                classRoleList.retainAll(raceRoleList);
                if (classRoleList.size() > 0) {
                    levelsKey += (int) levels.floatValue();
                } else {
                    levelsNonKey += (int) levels.floatValue();
                }
            } else {
                if (raceRoleList != null) {
                    levelsNonKey += (int) levels.floatValue();
                } else {
                    levelsKey += (int) levels.floatValue();
                }
            }

        }
        String sThreshold = SettingsHandler.getGame().getCRThreshold();
        if (sThreshold != null) {
            threshold = formulaResolvingFacet.resolve(id, FormulaFactory.getFormulaFor(sThreshold), "").intValue();
        }

        while (levelsNonKey > 1) {
            CR++;
            // TODO: maybe the divisor 2 should be be made configurable, 
            // or the whole calculation put into a formula
            levelsNonKey -= 2;
            levelsConverted += 2;
            if (levelsConverted >= threshold) {
                break;
            }
        }
        if (levelsConverted > 0) {
            CR += levelsNonKey;
        }
        CR += levelsKey;

        return CR;
    }

    private Float getClassRaceCRMod(CharID id, PCClass cl) {
        String classType = cl.getClassType();

        if (classType != null) {
            if (SettingsHandler.getGame().getClassTypeByName(classType) != null) {
                Float crMod = raceFacet.get(id).get(MapKey.CRMOD, classType);
                if (crMod != null) {
                    return crMod;
                }
            }
        } else {
            // For migration purposes, if CLASSTYPE is not set, 
            // use old method to determine the class type from TYPE. 
            for (Type type : cl.getTrueTypeList(false)) {
                classType = type.toString();
                if (SettingsHandler.getGame().getClassTypeByName(classType) != null) {
                    Float crMod = raceFacet.get(id).get(MapKey.CRMOD, classType);
                    if (crMod != null) {
                        return crMod;
                    }
                }
            }
        }
        return null;
    }

    /**
     * Returns the ChallengeRating provided solely by the given Class of the
     * Player Character identified by the given CharID.
     * 
     * @param id
     *            The CharID representing the Player Character
     * @param cl
     *            The PCClass for which the class Challenge Rating should be
     *            calculated
     * @return the Challenge Rating provided solely by the given Class of the
     *         Player Character identified by the given CharID
     */
    private Float calcClassCR(CharID id, PCClass cl) {
        Formula cr = cl.get(FormulaKey.CR);
        if (cr == null) {
            /*
             * TODO I don't like the fact that this method is accessing the
             * ClassTypes and using one of those to set one of its variables. In
             * theory, we should have a ClassType that triggers CR that is not a
             * TYPE, but a unique token. See this thread:
             * http://tech.groups.yahoo.com/group/pcgen_experimental/message/10778
             */
            ClassType aClassType = SettingsHandler.getGame().getClassTypeByName(cl.getClassType());
            if (aClassType != null) {
                String crf = aClassType.getCRFormula();
                if ("NONE".equalsIgnoreCase(crf)) {
                    return Float.NaN;
                } else if (!"0".equals(crf)) {
                    cr = FormulaFactory.getFormulaFor(crf);
                }
            } else {
                // For migration purposes, if CLASSTYPE is not set, 
                // use old method to determine the class type from TYPE. 
                for (Type type : cl.getTrueTypeList(false)) {
                    aClassType = SettingsHandler.getGame().getClassTypeByName(type.toString());
                    if (aClassType != null) {
                        String crf = aClassType.getCRFormula();
                        if ("NONE".equalsIgnoreCase(crf)) {
                            return Float.NaN;
                        } else if (!"0".equals(crf)) {
                            cr = FormulaFactory.getFormulaFor(crf);
                        }
                    }
                }
            }
        }

        return cr == null ? 0 : formulaResolvingFacet.resolve(id, cr, cl.getQualifiedKey()).floatValue();
    }

    private Float getClassCRMod(CharID id, PCClass cl) {
        Formula crm = cl.get(FormulaKey.CRMOD);
        Float crMod = new Float(0);

        ClassType aClassType = SettingsHandler.getGame().getClassTypeByName(cl.getClassType());
        if (aClassType != null) {
            String crmf = aClassType.getCRMod();
            crm = FormulaFactory.getFormulaFor(crmf);

            crMod = Math.min(crMod, formulaResolvingFacet.resolve(id, crm, cl.getQualifiedKey()).floatValue());
        } else {
            // For migration purposes, if CLASSTYPE is not set, 
            // use old method to determine the class type from TYPE. 
            for (Type type : cl.getTrueTypeList(false)) {
                aClassType = SettingsHandler.getGame().getClassTypeByName(type.toString());
                if (aClassType != null) {
                    String crmf = aClassType.getCRMod();
                    crm = FormulaFactory.getFormulaFor(crmf);

                    crMod = Math.min(crMod,
                            formulaResolvingFacet.resolve(id, crm, cl.getQualifiedKey()).floatValue());
                }
            }
        }

        return crMod;
    }

    private int getClassCRModPriority(CharID id, PCClass cl) {
        int crModPriority = 0;

        ClassType aClassType = SettingsHandler.getGame().getClassTypeByName(cl.getClassType());
        if (aClassType != null) {
            int crmp = aClassType.getCRModPriority();
            if (crmp != 0) {
                crModPriority = crmp;
            }
        } else {
            // For migration purposes, if CLASSTYPE is not set, 
            // use old method to determine the class type from TYPE. 
            for (Type type : cl.getTrueTypeList(false)) {
                aClassType = SettingsHandler.getGame().getClassTypeByName(type.toString());
                if (aClassType != null) {
                    int crmp = aClassType.getCRModPriority();
                    if (crmp != 0) {
                        crModPriority = aClassType.getCRModPriority();
                    }
                }
            }
        }

        return crModPriority;
    }

    public int getXPAward(CharID id) {
        Map<String, Integer> xpAwardsMap = SettingsHandler.getGame().getXPAwards();

        if (xpAwardsMap.size() > 0) {
            Float cr = getCR(id);
            if (cr.isNaN() || cr == 0) {
                return 0;
            }
            String crString = "";
            String crAsString = Float.toString(cr);
            String decimalPlaceValue = crAsString.substring(crAsString.length() - 2);

            // If the CR is a fractional CR then we convert to a 1/x format
            if (cr > 0 && cr < 1) {
                Fraction fraction = Fraction.getFraction(cr);// new Fraction(CR);
                int denominator = fraction.getDenominator();
                int numerator = fraction.getNumerator();
                crString = numerator + "/" + denominator;
            } else if (cr >= 1 || cr == 0) {
                int newCr = -99;
                if (decimalPlaceValue.equals(".0")) {
                    newCr = (int) cr.floatValue();
                }

                if (newCr > -99) {
                    crString = crString + newCr;
                } else {
                    crString = crString + cr;
                }
            }
            return xpAwardsMap.get(crString);
        }
        return 0;
    }

    public void setTemplateFacet(TemplateFacet templateFacet) {
        this.templateFacet = templateFacet;
    }

    public void setRaceFacet(RaceFacet raceFacet) {
        this.raceFacet = raceFacet;
    }

    public void setClassFacet(ClassFacet classFacet) {
        this.classFacet = classFacet;
    }

    public void setFormulaResolvingFacet(FormulaResolvingFacet formulaResolvingFacet) {
        this.formulaResolvingFacet = formulaResolvingFacet;
    }

    public void setBonusCheckingFacet(BonusCheckingFacet bonusCheckingFacet) {
        this.bonusCheckingFacet = bonusCheckingFacet;
    }

    public void setLevelFacet(LevelFacet levelFacet) {
        this.levelFacet = levelFacet;
    }

}