/*
* KitSkill.java
* Copyright 2001 (C) Greg Bingleman <byngl@hotmail.com>
*
* 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 September 23, 2002, 10:28 PM
*
* $Id$
*/
package pcgen.core.kit;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import pcgen.base.lang.StringUtil;
import pcgen.cdom.base.CDOMReference;
import pcgen.cdom.reference.CDOMSingleRef;
import pcgen.cdom.reference.ReferenceUtilities;
import pcgen.core.Globals;
import pcgen.core.Kit;
import pcgen.core.Language;
import pcgen.core.PCClass;
import pcgen.core.PlayerCharacter;
import pcgen.core.RuleConstants;
import pcgen.core.Skill;
import pcgen.core.analysis.ChooseActivation;
import pcgen.core.analysis.SkillRankControl;
import pcgen.core.chooser.ChoiceManagerList;
import pcgen.core.chooser.ChooserUtilities;
import pcgen.core.pclevelinfo.PCLevelInfo;
import pcgen.core.utils.CoreUtility;
import pcgen.util.Logging;
/**
* {@code KitSkill}.
*
* @author Greg Bingleman <byngl@hotmail.com>
*/
public final class KitSkill extends BaseKit
{
private Boolean free = null;
private BigDecimal rank = null;
private List<CDOMReference<Skill>> skillList =
new ArrayList<>();
private CDOMSingleRef<PCClass> className = null;
private Integer choiceCount;
private List<CDOMSingleRef<Language>> selection =
new ArrayList<>();
private transient List<KitSkillAdd> skillsToAdd;
/**
* Used to make purchasing ranks of this skill not come out of the skill
* pool.
* @param argFree {@code true} to make the skill ranks free.
*/
public void setFree(Boolean argFree)
{
free = argFree;
}
/**
* Returns if the skill will be purchased for free.
* @return {@code true} if the skill will be free
*/
public boolean isFree()
{
return free != null && free;
}
public void setRank(BigDecimal setRank)
{
rank = setRank;
}
/**
* Get the rank of the skill
* @return rank
*/
public BigDecimal getRank()
{
return rank;
}
@Override
public String toString()
{
final StringBuilder info = new StringBuilder(100);
if (skillList.size() > 1)
{
// This is a choice of skills.
info.append(getSafeCount() + " of (");
info.append(ReferenceUtilities.joinLstFormat(skillList, ", "));
info.append(")");
}
else
{
info.append(skillList.get(0).getLSTformat(false));
}
info.append(" (").append(rank);
if (info.toString().endsWith(".0"))
{
info.setLength(info.length() - 2);
}
if (isFree())
{
info.append("/free");
}
if (selection != null && !selection.isEmpty())
{
info.append("/");
info.append(StringUtil.join(selection, ", "));
}
info.append(')');
return info.toString();
}
@Override
public boolean testApply(Kit aKit, PlayerCharacter aPC,
List<String> warnings)
{
skillsToAdd = new ArrayList<>();
List<Skill> skillChoices = getSkillChoices(aPC);
if (skillChoices == null || skillChoices.isEmpty())
{
// They didn't make a choice so don't add any ranks.
return false;
}
for (Skill skill : skillChoices)
{
BigDecimal ranksLeftToAdd = getRank();
if (ranksLeftToAdd == null)
{
ranksLeftToAdd = BigDecimal.ONE;
}
double ranksLeft = ranksLeftToAdd.doubleValue();
List<PCClass> classList = new ArrayList<>();
if (className != null)
{
String classKey = className.get().getKeyName();
// Make sure if they specified a class to add from we try that
// class first.
PCClass pcClass = aPC.getClassKeyed(classKey);
if (pcClass != null)
{
classList.add(pcClass);
}
else
{
warnings.add("SKILL: Could not find specified class "
+ classKey + " in PC to add ranks from.");
}
}
for (PCClass pcClass : aPC.getClassSet())
{
if (!classList.contains(pcClass))
{
classList.add(pcClass);
}
}
// Try and find a class we can add them from.
boolean oldImporting = aPC.isImporting();
aPC.setImporting(true);
for (PCClass pcClass : classList)
{
final KitSkillAdd sta =
addRanks(aPC, pcClass, skill, ranksLeft, isFree(),
warnings);
if (sta != null)
{
skillsToAdd.add(sta);
ranksLeft -= sta.getRanks();
if (ranksLeft <= 0.0)
{
break;
}
}
}
aPC.setImporting(oldImporting);
if (ranksLeft > 0.0)
{
warnings.add("SKILL: Could not add " + ranksLeft
+ " ranks to " + skill.getKeyName()
+ ". Not enough points.");
}
}
return true;
}
@Override
public void apply(PlayerCharacter aPC)
{
/** @todo Fix this to return what panes need to be refreshed */
for (KitSkillAdd ksa : skillsToAdd)
{
updatePCSkills(aPC, ksa.getSkill(), (int) ksa.getRanks(), ksa
.getCost(), ksa.getLanguages(), ksa.getPCClass());
}
}
/**
* Needs documentation.
*
* @param pc update skills for this PC
* @param aSkill Skill to update
* @param aRank Number of ranks to add
* @param aCost Cost of added ranks
* @param langList Languages to be selected for a language skill
* @param pcClass skills apply to this class
*
* @return {@code true} for success
* TODO What about throwing on failure?
*/
private boolean updatePCSkills(final PlayerCharacter pc,
final Skill aSkill, final int aRank, final double aCost,
List<Language> langList, final PCClass pcClass)
{
boolean oldImporting = pc.isImporting();
pc.setImporting(true);
final String aString =
SkillRankControl.modRanks(aRank, pcClass, true, pc, aSkill);
pc.setImporting(oldImporting);
if (!aString.isEmpty())
{
Logging.errorPrint("SKILL: " + aString);
return false;
}
// Add any supplied languages
ChoiceManagerList<Language> controller = ChooserUtilities
.getConfiguredController(aSkill, pc, null,
new ArrayList<>());
for (Language lang : langList)
{
if (!controller.conditionallyApply(pc, lang))
{
Logging.errorPrint("Failed to apply Language into Skill: "
+ lang.getLSTformat());
}
}
//
// Fix up the skill pools to reflect what we just spent.
//
double ptsToSpend = aCost;
if (ptsToSpend >= 0.0)
{
for (PCLevelInfo info : pc.getLevelInfo())
{
if (info.getClassKeyName().equals(pcClass.getKeyName()))
{
// We are spending this class' points.
int remaining = info.getSkillPointsRemaining();
if (remaining == 0)
{
continue;
}
int left =
remaining - (int) Math.min(remaining, ptsToSpend);
info.setSkillPointsRemaining(left);
ptsToSpend -= (remaining - left);
if (ptsToSpend <= 0)
{
break;
}
}
}
}
return true;
}
@Override
public String getObjectName()
{
return "Skills";
}
private List<Skill> getSkillChoices(PlayerCharacter aPC)
{
final List<Skill> skillsOfType = new ArrayList<>();
for (CDOMReference<Skill> ref : skillList)
{
skillsOfType.addAll(ref.getContainedObjects());
}
if (skillsOfType.isEmpty())
{
return null;
}
else if (skillsOfType.size() == 1)
{
return skillsOfType;
}
List<Skill> skillChoices = new ArrayList<>();
skillChoices = Globals.getChoiceFromList("Select skill", skillsOfType, skillChoices,
getSafeCount(), aPC);
return skillChoices;
}
private KitSkillAdd addRanks(PlayerCharacter pc, PCClass pcClass,
Skill aSkill, double ranksLeftToAdd, boolean isFree,
List<String> warnings)
{
if (!isFree && pcClass.getSkillPool(pc) == 0)
{
return null;
}
double curRank = 0.0;
if (pc.hasSkill(aSkill))
{
curRank = pc.getRank(aSkill).doubleValue();
}
double ranksToAdd = ranksLeftToAdd;
if (!Globals.checkRule(RuleConstants.SKILLMAX) && (ranksToAdd > 0.0))
{
ranksToAdd =
Math.min(pc.getMaxRank(aSkill, pcClass)
.doubleValue(), curRank + ranksLeftToAdd);
ranksToAdd -= curRank;
if (!CoreUtility.doublesEqual(ranksToAdd, ranksLeftToAdd))
{
warnings.add("SKILL: Could not add " + (ranksLeftToAdd - ranksToAdd)
+ " to " + aSkill.getDisplayName()
+ ". Exceeds MAXRANK of "
+ pc.getMaxRank(aSkill, pcClass) + ".");
}
}
int ptsToSpend = 0;
int[] points = new int[pc.getLevelInfoSize()];
if (!isFree)
{
double ranksAdded = 0.0;
int skillCost = pc.getSkillCostForClass(aSkill, pcClass).getCost();
ptsToSpend = (int) (ranksToAdd * skillCost);
for (int i = 0; i < pc.getLevelInfoSize(); i++)
{
PCLevelInfo info = pc.getLevelInfo(i);
if (info.getClassKeyName().equals(pcClass.getKeyName()))
{
// We are spending this class' points.
points[i] = info.getSkillPointsRemaining();
}
else
{
points[i] = -1;
}
}
for (int i = 0; i < points.length; i++)
{
int remaining = points[i];
if (remaining <= 0)
{
continue;
}
int left = remaining - Math.min(remaining, ptsToSpend);
points[i] = left;
int spent = (remaining - left);
ptsToSpend -= spent;
ranksAdded += ((double) spent / (double) skillCost);
if (ranksAdded == ranksToAdd || ptsToSpend <= 0)
{
break;
}
}
ranksToAdd = ranksAdded;
ptsToSpend = (int) (ranksToAdd * skillCost);
}
String ret =
SkillRankControl
.modRanks(ranksToAdd, pcClass, false, pc, aSkill);
if (!ret.isEmpty())
{
if (isFree
&& ret.indexOf("You do not have enough skill points.") != -1)
{
SkillRankControl.modRanks(ranksToAdd, pcClass, true, pc, aSkill);
}
else
{
warnings.add(ret);
return null;
}
}
if (!isFree)
{
for (int i = 0; i < pc.getLevelInfoSize(); i++)
{
PCLevelInfo info = pc.getLevelInfo(i);
if (points[i] >= 0)
{
info.setSkillPointsRemaining(points[i]);
}
}
}
List<Language> langList = new ArrayList<>();
if (ChooseActivation.hasNewChooseToken(aSkill) && !selection.isEmpty())
{
ChoiceManagerList<Language> controller = ChooserUtilities
.getConfiguredController(aSkill, pc, null,
new ArrayList<>());
int limit = (int) ranksToAdd;
for (CDOMSingleRef<Language> ref : selection)
{
Language lang = ref.get();
if (controller.conditionallyApply(pc, lang))
{
langList.add(lang);
limit--;
}
if (limit <= 0)
{
break;
}
}
}
return new KitSkillAdd(aSkill, ranksToAdd, ptsToSpend, langList,
pcClass);
}
public Boolean getFree()
{
return free;
}
public void addSkill(CDOMReference<Skill> ref)
{
skillList.add(ref);
}
public Collection<CDOMReference<Skill>> getSkills()
{
return skillList;
}
public void setPcclass(CDOMSingleRef<PCClass> ref)
{
className = ref;
}
public CDOMReference<PCClass> getPcclass()
{
return className;
}
public void setCount(Integer quan)
{
choiceCount = quan;
}
public Integer getCount()
{
return choiceCount;
}
public int getSafeCount()
{
return choiceCount == null ? 1 : choiceCount;
}
public void addSelection(CDOMSingleRef<Language> ref)
{
selection.add(ref);
}
public List<CDOMSingleRef<Language>> getSelections()
{
return selection;
}
}