/*
* 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 pcgen.base.formula.Formula;
import pcgen.cdom.base.FormulaFactory;
import pcgen.cdom.content.ChallengeRating;
import pcgen.cdom.content.LevelCommandFactory;
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 Integer getCR(CharID id)
{
int cr = 0;
if (levelFacet.getMonsterLevelCount(id) == 0)
{
if (levelFacet.getNonMonsterLevelCount(id) == 0)
{
return null;
}
// 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
Integer classRaceCR = calcClassesForRaceCR(id);
if (classRaceCR == null)
{
return null;
}
Integer raceCR = calcRaceCR(id);
if (raceCR != null)
{
cr += raceCR;
}
cr += classRaceCR;
}
// calculate and add CR bonus from templates
cr += getTemplateCR(id);
// calculate and add in the MISC bonus to CR
cr += (int) bonusCheckingFacet.getBonus(id, "MISC", "CR");
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 Integer calcRaceCR(CharID id)
{
// Calculate and add the CR from race
ChallengeRating cr = raceFacet.get(id).getSafe(
ObjectKey.CHALLENGE_RATING);
return cr.toInteger();
}
/**
* Returns the racial hit dice provided solely by the race of the
* character identified by the given CharID.
*
* @param id The CharID representing the character
*
* @return the racial hit dice provided by the race of the character
* identified by the given CharID
*/
public int getBaseHD(CharID id)
{
final LevelCommandFactory lcf = raceFacet.get(id).getSafe(
ObjectKey.MONSTER_CLASS);
/*
* BUG This converts a formula to a String? What if it's a formula, NFE?
*/
return Integer.parseInt(lcf.getLevelCount().toString());
}
/**
* 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 int getTemplateCR(CharID id)
{
int cr = 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 int calcClassesCR(CharID id)
{
int cr = 0;
int crMod = 0;
int crModPriority = 0;
for (PCClass pcClass : classFacet.getSet(id))
{
cr += calcClassCR(id, pcClass);
int crmp = getClassCRModPriority(pcClass);
if (crmp != 0 && (crmp < crModPriority || crModPriority == 0))
{
Integer raceMod = getClassRaceCRMod(id, pcClass);
if (raceMod != null)
{
crMod = raceMod;
}
else
{
crMod = getClassCRMod(id, pcClass);
}
crModPriority = crmp;
}
}
cr += crMod;
return cr;
}
private Integer calcClassesForRaceCR(CharID id)
{
int cr = 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.getSet(id))
{
Integer levels = calcClassCR(id, pcClass);
if (levels == null)
{
return null;
}
List<String> classRoleList = pcClass.getListFor(ListKey.MONSTER_ROLES);
if (classRoleList != null)
{
classRoleList.retainAll(raceRoleList);
if (!classRoleList.isEmpty())
{
levelsKey += levels;
}
else
{
levelsNonKey += levels;
}
}
else
{
if (raceRoleList != null)
{
levelsNonKey += levels;
}
else
{
levelsKey += levels;
}
}
}
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 Integer getClassRaceCRMod(CharID id, PCClass cl)
{
String classType = cl.getClassType();
if (classType != null)
{
if (SettingsHandler.getGame().getClassTypeByName(classType) != null)
{
Integer 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)
{
Integer 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 Integer calcClassCR(CharID id, PCClass cl)
{
Formula cr = cl.get(FormulaKey.CR);
if (cr == null)
{
ClassType aClassType = SettingsHandler.getGame()
.getClassTypeByName(cl.getClassType());
if (aClassType != null)
{
String crf = aClassType.getCRFormula();
if ("NONE".equalsIgnoreCase(crf))
{
return null;
}
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 null;
}
else if (!"0".equals(crf))
{
cr = FormulaFactory.getFormulaFor(crf);
}
}
}
}
}
return cr == null ? 0 : formulaResolvingFacet.resolve(id, cr,
cl.getQualifiedKey()).intValue();
}
private int getClassCRMod(CharID id, PCClass cl)
{
int crMod = 0;
ClassType aClassType = SettingsHandler.getGame()
.getClassTypeByName(cl.getClassType());
if (aClassType != null)
{
String crmf = aClassType.getCRMod();
Formula crm = FormulaFactory.getFormulaFor(crmf);
crMod = Math.min(crMod, formulaResolvingFacet.resolve(id, crm,
cl.getQualifiedKey()).intValue());
}
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();
Formula crm = FormulaFactory.getFormulaFor(crmf);
crMod = Math.min(crMod, formulaResolvingFacet.resolve(id, crm,
cl.getQualifiedKey()).intValue());
}
}
}
return crMod;
}
private int getClassCRModPriority(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<Integer, Integer> xpAwardsMap = SettingsHandler.getGame().getXPAwards();
if (!xpAwardsMap.isEmpty())
{
Integer cr = getCR(id);
if (cr == null)
{
return 0;
}
Integer xp = xpAwardsMap.get(cr);
return xp == null ? 0 : xp;
}
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;
}
}