/*
* Copyright (c) 2008 Tom Parker <thpr@users.sourceforge.net>
*
* 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.helper;
import org.apache.commons.lang3.StringUtils;
import pcgen.cdom.base.CDOMObject;
import pcgen.cdom.base.PersistentChoiceActor;
import pcgen.cdom.enumeration.IntegerKey;
import pcgen.cdom.enumeration.ObjectKey;
import pcgen.cdom.enumeration.SkillCost;
import pcgen.cdom.inst.PCClassLevel;
import pcgen.core.PCClass;
import pcgen.core.PlayerCharacter;
import pcgen.core.Skill;
import pcgen.core.SubClass;
import pcgen.core.analysis.SkillRankControl;
import pcgen.core.pclevelinfo.PCLevelInfo;
import pcgen.rules.context.LoadContext;
import pcgen.util.Logging;
/**
* A ClassSkillChoiceActor is a PersistentChoiceActor that can apply skill
* selections that are related to a specific PCClass (this adds the selected
* skill(s) as Class Skills) and which may apply a number of ranks to the
* selected skill.
*/
public class ClassSkillChoiceActor implements PersistentChoiceActor<Skill>
{
/**
* The PCClass to which skill selections will be applied as a class skill
*/
private final PCClass source;
/**
* The number of ranks that this ClassSkillChoiceActor should apply to the
* selected skills. May be null if this ClassSkillChoiceActor should not
* apply any ranks to the selected skills.
*/
private final Integer applyRank;
/**
* Constructs a new ClassSkillChoiceActor which will apply skill selections
* to the given class, and automatically apply the given ranks to selected
* skills.
*
* @param pcc
* The PCClass to which skill selections will be applied as a
* class skill
* @param autoRank
* The number of ranks that this ClassSkillChoiceActor should
* apply to selected skills.
*/
public ClassSkillChoiceActor(PCClass pcc, Integer autoRank)
{
applyRank = autoRank;
source = pcc;
}
/**
* Applies the given Skill choice to the given PlayerCharacter. The given
* Skill is added as a class skill for the PCClass provided during
* construction of this ClassSkillChoiceActor. If the number of ranks
* provided during construction of this ClassSkillChoiceActor was not null
* or zero, then ranks are also applied to the given skill.
*
* @param owner
* The owning object for this choice.
* @param choice
* The Skill which should be added as a Class Skill for the
* PCClass provided during construction of this
* ClassSkillChoiceActor
* @param pc
* The PlayerCharacter to which the changes driven by this
* ClassSkillChoiceActor should be applied.
*/
@Override
public void applyChoice(CDOMObject owner, Skill choice, PlayerCharacter pc)
{
PCClass pcc = getSourceClass(pc);
if (pcc == null)
{
Logging.errorPrint("Unable to find the pc's class " + source
+ " to apply skill choices to.");
return;
}
pc.addLocalCost(pcc, choice, SkillCost.CLASS, owner);
if (applyRank != null)
{
if (owner instanceof PCClassLevel)
{
// Ensure that the skill points for this level are already calculated.
PCClassLevel classLevel = (PCClassLevel) owner;
PCClass pcClass = (PCClass) classLevel.getSafe(ObjectKey.PARENT);
int levelIndex = 1;
for (PCLevelInfo lvlInfo : pc.getLevelInfo())
{
if (lvlInfo.getClassKeyName() == pcClass.getKeyName()
&& lvlInfo.getClassLevel() == classLevel
.getSafe(IntegerKey.LEVEL))
{
pc.checkSkillModChangeForLevel(pcClass, lvlInfo,
classLevel, levelIndex++);
break;
}
}
}
String result =
SkillRankControl
.modRanks(applyRank, pcc, false, pc, choice);
if (StringUtils.isNotEmpty(result))
{
Logging.errorPrint(
"Unable to apply {0} ranks of {1}. Error: {2}", applyRank,
choice, result);
}
}
}
/**
* Returns true if the given Skill should be allowed as a selection when
* determining which Skills can be added as a Class Skill to the PCClass in
* this ClassSkillChoiceActor for the given PlayerCharacter. Generally, this
* is used to filter out Skills that are already class skills for the
* PCClass in this ClassSkillChoiceActor.
*
* @param choice
* The Skill to be tested to see if selection of the Skill should
* be allowed for this ClassSkillChoiceActor.
* @param pc
* The PlayerCharacter to which the changes driven by this
* ClassSkillChoiceActor will be applied.
* @param allowStack
* True if stacking is allowed (ignored by ClassSkillChoiceActor)
*
* @return true if the given Skill should be allowed as a selection.
*/
@Override
public boolean allow(Skill choice, PlayerCharacter pc, boolean allowStack)
{
return !pc.isClassSkill(source, choice);
}
/**
* Decodes the given String into a Skill. The String format to be passed
* into this method is defined solely by the return result of the
* encodeChoice method. There is no guarantee that the encoding is human
* readable, simply that the encoding is uniquely identifying such that this
* method is capable of decoding the String into the Skill.
* @param persistentFormat
* The String which should be decoded to provide a Skill.
*
* @return A Skill that was encoded in the given String.
*/
@Override
public Skill decodeChoice(LoadContext context, String persistentFormat)
{
return context.getReferenceContext().silentlyGetConstructedCDOMObject(
Skill.class, persistentFormat);
}
/**
* Encodes the given Skill into a String sufficient to uniquely identify the
* Skill. This may not sufficiently encode to be stored into a file or
* format which restricts certain characters (such as URLs), it simply
* encodes into an identifying String. There is no guarantee that this
* encoding is human readable, simply that the encoding is uniquely
* identifying such that the decodeChoice method of ClassSkillChoiceActor is
* capable of decoding the String into the Skill.
*
* @param choice
* The Skill which should be encoded into a String sufficient to
* identify the Skill.
*
* @return A String sufficient to uniquely identify the Skill.
*/
@Override
public String encodeChoice(Skill choice)
{
return choice.getKeyName();
}
/**
* Restores a choice to a PlayerCharacter. This method re-applies a Skill
* when a PlayerCharacter is restored from a persistent state (the
* applyChoice method of ClassSkillChoiceActor having been used to first
* apply the choice to a PlayerCharacter).
*
* @param pc
* The PlayerCharacter to which the Skill should be restored.
* @param owner
* The owning object of the Skill being restored.
* @param choice
* The Skill being restored to the given PlayerCharacter.
*/
@Override
public void restoreChoice(PlayerCharacter pc, CDOMObject owner, Skill choice)
{
PCClass pcc = getSourceClass(pc);
if (pcc == null)
{
Logging.errorPrint("Unable to find the pc's class " + source
+ " to restore skill choices to.");
return;
}
pc.addLocalCost(pcc, choice, SkillCost.CLASS, owner);
}
/**
* Identify the character's instance of the class being linked to the skill.
* @param pc The character
* @return The character's class.
*/
private PCClass getSourceClass(PlayerCharacter pc)
{
PCClass pcc;
if (source instanceof SubClass)
{
pcc = pc.getClassKeyed(((SubClass) source).getCDOMCategory().getKeyName());
}
else
{
pcc = pc.getClassKeyed(source.getKeyName());
}
return pcc;
}
/**
* Returns the number of ranks that this ClassSkillChoiceActor should apply
* to the selected skills. May be null if this ClassSkillChoiceActor should
* not apply any ranks to the selected skills.
*
* @return The number of ranks that this ClassSkillChoiceActor should apply
* to the selected skills.
*/
public Integer getApplyRank()
{
return applyRank;
}
/**
* Removes a Skill choice from a PlayerCharacter.
*
* @param pc
* The PlayerCharacter from which the Skill should be removed.
* @param owner
* The owning object of the Skill being removed.
* @param choice
* The Skill being removed from the given PlayerCharacter.
*/
@Override
public void removeChoice(PlayerCharacter pc, CDOMObject owner, Skill choice)
{
PCClass pcc = pc.getClassKeyed(source.getKeyName());
if (applyRank != null)
{
SkillRankControl.modRanks(-applyRank, pcc, false, pc, choice);
}
pc.removeLocalCost(pcc, choice, SkillCost.CLASS, owner);
}
}