/*
* Copyright (c) 2014 Tom Parker <thpr@users.sourceforge.net>
* derived from export token SkillToken.java
* Copyright 2004 (C) James Dempsey <jdempsey@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
*
* Created on Aug 5, 2004
*
* $Id: SkillToken.java 23287 2014-02-18 01:27:57Z thpr $
*
*/
package plugin.exporttokens;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import pcgen.cdom.enumeration.ListKey;
import pcgen.cdom.enumeration.ObjectKey;
import pcgen.cdom.enumeration.SkillCost;
import pcgen.cdom.enumeration.SkillFilter;
import pcgen.cdom.helper.SkillSituation;
import pcgen.core.Globals;
import pcgen.core.PCClass;
import pcgen.core.PlayerCharacter;
import pcgen.core.SettingsHandler;
import pcgen.core.Skill;
import pcgen.core.analysis.QualifiedName;
import pcgen.core.analysis.SkillInfoUtilities;
import pcgen.core.analysis.SkillModifier;
import pcgen.core.analysis.SkillRankControl;
import pcgen.core.display.SkillCostDisplay;
import pcgen.core.display.SkillDisplay;
import pcgen.io.ExportHandler;
import pcgen.io.exporttoken.SkillToken;
import pcgen.io.exporttoken.SkillToken.SkillDetails;
import pcgen.io.exporttoken.Token;
import pcgen.util.Logging;
import pcgen.util.enumeration.View;
public class SkillSitToken extends Token
{
public static final String TOKENNAME = "SKILLSIT";
// Cache the skill list as it is expensive to build
private List<Skill> cachedSkillList = null;
private PlayerCharacter lastPC = null;
private int lastPCSerial;
/**
* @see pcgen.io.exporttoken.Token#getTokenName()
*/
@Override
public String getTokenName()
{
return TOKENNAME;
}
/**
* @see pcgen.io.exporttoken.Token#getToken(java.lang.String, pcgen.core.PlayerCharacter, pcgen.io.ExportHandler)
*/
@Override
public String getToken(String tokenSource, PlayerCharacter pc,
ExportHandler eh)
{
SkillDetails details = SkillToken.buildSkillDetails(tokenSource);
Object aSkill = getSkill(pc, details, eh);
return getSkillProperty(aSkill, details.getProperty(0), pc);
}
/**
* Select the target skill based on the supplied criteria. Uses the
* id in the details object to either retrieve a skill by name or by
* position in the skill list.
*
* @param pc The character being processed.
* @param details The parsed details of the token.
* @param eh The ExportHandler
* @return The matching skill, or null if none match.
*/
private Object getSkill(PlayerCharacter pc, SkillDetails details,
ExportHandler eh)
{
Object skill = null;
try
{
int i = Integer.parseInt(details.getSkillId());
final List<Skill> pcSkills = new ArrayList<>(getSkillList(pc));
SkillFilter filter = details.getSkillFilter();
if (filter == null || filter == SkillFilter.Selected)
{
filter = pc.getSkillFilter();
}
Iterator<Skill> iter = pcSkills.iterator();
while (iter.hasNext())
{
Skill sk = iter.next();
if (!pc.includeSkill(sk, filter)
|| !sk.qualifies(pc, null))
{
iter.remove();
}
}
if ((i >= (pcSkills.size() - 1)) && eh != null
&& eh.getExistsOnly())
{
eh.setNoMoreItems(true);
}
for (iter = pcSkills.iterator(); i >= 0;)
{
Skill sk = iter.next();
if (i == 0)
{
return sk;
}
i--; //wasn't the base skill
List<String> situations =
new ArrayList<>(
sk.getUniqueListFor(ListKey.SITUATION));
if (situations != null)
{
int numSits = situations.size();
if (i < numSits)
{
Collections.sort(situations);
}
for (String situation : situations)
{
double bonus = pc.getTotalBonusTo("SITUATION", sk.getKeyName()
+ "=" + situation);
if (bonus > 0.01 || bonus < -0.01)
{
if (i == 0)
{
return new SkillSituation(sk, situation, bonus);
}
i--; //Wasn't this situation
}
}
}
}
}
catch (NumberFormatException exc)
{
String skillName = details.getSkillId();
int equalLoc = skillName.indexOf("=");
if (equalLoc == -1)
{
//Allowing SKILL.Spot.<subtoken>
skill = Globals.getContext().getReferenceContext().silentlyGetConstructedCDOMObject(
Skill.class, skillName);
}
else
{
//Allowing SKILL.Spot=Situation.<subtoken>
String situation = skillName.substring(equalLoc + 1);
Skill sk = Globals.getContext().getReferenceContext().silentlyGetConstructedCDOMObject(
Skill.class, skillName.substring(0, equalLoc));
double bonus = pc.getTotalBonusTo("SITUATION", sk.getKeyName()
+ "=" + situation);
return new SkillSituation(sk, situation, bonus);
}
}
return skill;
}
private synchronized List<Skill> getSkillList(PlayerCharacter pc)
{
if (pc == lastPC && pc.getSerial() == lastPCSerial)
{
return cachedSkillList;
}
final List<Skill> pcSkills =
SkillDisplay.getSkillListInOutputOrder(pc, pc.getDisplay()
.getPartialSkillList(View.VISIBLE_EXPORT));
cachedSkillList = pcSkills;
lastPC = pc;
lastPCSerial = pc.getSerial();
return pcSkills;
}
/**
* Calculate the value of the specified skill property for the
* supplied skill and character.
*
* @param aSkill The skill to be processed.
* @param property The property being processed.
* @param pc The character to be reported.
* @return The skill tag output value.
*/
protected String getSkillProperty(Object aSkill, String property,
PlayerCharacter pc)
{
if (aSkill == null)
{
return "";
}
int action = SkillToken.getPropertyId(property);
return getSkillPropValue(aSkill, action, property, pc);
}
/**
* Evaluate the property for the supplied skill and character. For
* properties such as ACP and the extended UNTRAINED property, the
* property text is required to be further parsed to pull out user
* defined text to be output in each case.
*
* @param skillSit The skill to be reported upon.
* @param property The property to be reported.
* @param propertyText The original text of the property.
* @param pc The character to be reported upon.
* @return The value of the property.
*/
private String getSkillPropValue(Object skillSit, int property,
String propertyText, PlayerCharacter pc)
{
StringBuilder retValue = new StringBuilder();
if (((property == SkillToken.SKILL_ABMOD) || (property == SkillToken.SKILL_MISC))
&& false)//&& aSkill.get(ObjectKey.KEY_STAT) == null)
{
retValue.append("n/a");
}
else
{
Skill skill;
boolean isSituation;
String situation;
SkillSituation sit;
if (skillSit instanceof Skill)
{
sit = null;
skill = (Skill) skillSit;
isSituation = false;
situation = "";
}
else if (skillSit instanceof SkillSituation)
{
sit = (SkillSituation) skillSit;
skill = sit.getSkill();
isSituation = true;
situation = sit.getSituation();
}
else
{
Logging.errorPrint("Internal Error: unexpected type: "
+ skillSit.getClass());
return "";
}
switch (property)
{
case SkillToken.SKILL_NAME:
String name = QualifiedName.qualifiedName(pc, skill);
if (isSituation)
{
name += " (" + situation + ")";
}
retValue.append(name);
break;
case SkillToken.SKILL_TOTAL:
int rank = SkillRankControl.getTotalRank(pc, skill).intValue()
+ SkillModifier.modifier(skill, pc).intValue();
if (isSituation)
{
rank += sit.getSituationBonus();
}
if (SettingsHandler.getGame().hasSkillRankDisplayText())
{
retValue.append(SettingsHandler.getGame()
.getSkillRankDisplayText(rank));
}
else
{
retValue.append(Integer.toString(rank));
}
break;
case SkillToken.SKILL_RANK:
Float sRank = SkillRankControl.getTotalRank(pc, skill);
if (SettingsHandler.getGame().hasSkillRankDisplayText())
{
retValue.append(SettingsHandler.getGame()
.getSkillRankDisplayText(sRank.intValue()));
}
else
{
retValue.append(SkillRankControl.getTotalRank(pc, skill).toString());
}
break;
case SkillToken.SKILL_MOD:
int mod = SkillModifier.modifier(skill, pc).intValue();
if (isSituation)
{
mod += sit.getSituationBonus();
}
retValue.append(Integer.toString(mod));
break;
case SkillToken.SKILL_ABILITY:
retValue.append(SkillInfoUtilities.getKeyStatFromStats(pc, skill));
break;
case SkillToken.SKILL_ABMOD:
retValue.append(Integer.toString(SkillModifier.getStatMod(skill, pc)));
break;
case SkillToken.SKILL_MISC:
int misc = SkillModifier.modifier(skill, pc).intValue();
if (isSituation)
{
misc += sit.getSituationBonus();
}
misc -= SkillModifier.getStatMod(skill, pc);
retValue.append(Integer.toString(misc));
break;
case SkillToken.SKILL_UNTRAINED:
retValue.append(skill.getSafe(ObjectKey.USE_UNTRAINED) ? "Y" : "NO");
break;
case SkillToken.SKILL_EXCLUSIVE:
retValue.append(skill.getSafe(ObjectKey.EXCLUSIVE) ? "Y" : "N");
break;
case SkillToken.SKILL_UNTRAINED_EXTENDED:
retValue.append(SkillToken.getUntrainedOutput(skill, propertyText));
break;
case SkillToken.SKILL_ACP:
retValue.append(SkillToken.getAcpOutput(skill, propertyText));
break;
case SkillToken.SKILL_COST:
SkillCost cost = null;
for (PCClass pcc : pc.getDisplay().getClassSet())
{
if (cost == null)
{
cost = pc.getSkillCostForClass(skill, pcc);
}
else
{
SkillCost newCost = pc.getSkillCostForClass(skill, pcc);
if (SkillCost.CLASS.equals(newCost)
|| SkillCost.EXCLUSIVE.equals(cost))
{
cost = newCost;
}
}
if (SkillCost.CLASS.equals(cost))
{
break;
}
}
retValue.append(cost.toString());
break;
case SkillToken.SKILL_EXCLUSIVE_TOTAL:
int etRank =
SkillRankControl.getTotalRank(pc, skill).intValue();
boolean b = (skill.getSafe(ObjectKey.EXCLUSIVE) || !skill.getSafe(ObjectKey.USE_UNTRAINED)) && (etRank == 0);
if (b)
{
retValue.append("0");
}
else
{
int mRank =
etRank
+ SkillModifier.modifier(skill, pc)
.intValue();
if (isSituation)
{
mRank += sit.getSituationBonus();
}
retValue.append(Integer.toString(mRank));
}
break;
case SkillToken.SKILL_TRAINED_TOTAL:
int tRank =
SkillRankControl.getTotalRank(pc, skill).intValue();
boolean isNotTrained = !skill.getSafe(ObjectKey.USE_UNTRAINED) && (tRank == 0);
if (isNotTrained)
{
retValue.append("0");
}
else
{
int mRank =
tRank
+ SkillModifier.modifier(skill, pc)
.intValue();
if (isSituation)
{
mRank += sit.getSituationBonus();
}
retValue.append(Integer.toString(mRank));
}
break;
case SkillToken.SKILL_EXPLANATION:
boolean shortFrom =
!("_LONG".equals(propertyText.substring(7)));
String bonusDetails =
SkillCostDisplay.getModifierExplanation(skill, pc, shortFrom);
if (isSituation)
{
String sitDetails =
SkillCostDisplay
.getSituationModifierExplanation(skill,
situation, pc, shortFrom);
retValue.append(bonusDetails + " situational: " + sitDetails);
}
else
{
retValue.append(bonusDetails);
}
break;
case SkillToken.SKILL_TYPE:
String type = skill.getType();
retValue.append(type);
break;
case SkillToken.SKILL_SIZE:
int i = (int)(pc.getSizeAdjustmentBonusTo("SKILL", skill.getKeyName()));
if (isSituation)
{
i += pc.getSizeAdjustmentBonusTo("SITUATION",
skill.getKeyName() + "=" + situation);
}
retValue.append(Integer.toString(i));
break;
case SkillToken.SKILL_CLASSES:
List<String> classes = new ArrayList<>();
for (PCClass aClass : pc.getClassList())
{
if (pc.getSkillCostForClass(skill, aClass) == SkillCost.CLASS)
{
classes.add(aClass.getDisplayName());
}
}
retValue.append(StringUtils.join(classes, "."));
break;
default:
Logging
.errorPrint("In ExportHandler._writeSkillProperty the propIdvalue "
+ property + " is not handled.");
break;
}
}
return retValue.toString();
}
}