/*
* PCGVer2Parser.java
* Copyright 2002 (C) Thomas Behr <ravenlock@gmx.de>
*
* 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 March 22, 2002, 12:15 AM
*
* Current Ver: $Revision$
*
*/
package pcgen.io;
import java.awt.Rectangle;
import java.io.File;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import org.apache.commons.lang3.StringUtils;
import pcgen.base.util.HashMapToList;
import pcgen.cdom.base.AssociatedPrereqObject;
import pcgen.cdom.base.CDOMObject;
import pcgen.cdom.base.CDOMReference;
import pcgen.cdom.base.Constants;
import pcgen.cdom.base.PersistentTransitionChoice;
import pcgen.cdom.base.SelectableSet;
import pcgen.cdom.base.UserSelection;
import pcgen.cdom.content.CNAbility;
import pcgen.cdom.content.CNAbilityFactory;
import pcgen.cdom.enumeration.AssociationKey;
import pcgen.cdom.enumeration.AssociationListKey;
import pcgen.cdom.enumeration.BiographyField;
import pcgen.cdom.enumeration.Gender;
import pcgen.cdom.enumeration.Handed;
import pcgen.cdom.enumeration.IntegerKey;
import pcgen.cdom.enumeration.ListKey;
import pcgen.cdom.enumeration.Nature;
import pcgen.cdom.enumeration.NotePCAttribute;
import pcgen.cdom.enumeration.NumericPCAttribute;
import pcgen.cdom.enumeration.ObjectKey;
import pcgen.cdom.enumeration.PCAttribute;
import pcgen.cdom.enumeration.PCStringKey;
import pcgen.cdom.enumeration.Region;
import pcgen.cdom.enumeration.SkillFilter;
import pcgen.cdom.enumeration.SkillsOutputOrder;
import pcgen.cdom.enumeration.SourceFormat;
import pcgen.cdom.enumeration.StringKey;
import pcgen.cdom.enumeration.Type;
import pcgen.cdom.facet.FacetLibrary;
import pcgen.cdom.facet.input.DomainInputFacet;
import pcgen.cdom.facet.input.RaceInputFacet;
import pcgen.cdom.facet.input.TemplateInputFacet;
import pcgen.cdom.helper.CNAbilitySelection;
import pcgen.cdom.helper.ClassSource;
import pcgen.cdom.inst.EquipmentHead;
import pcgen.cdom.inst.PCClassLevel;
import pcgen.cdom.list.ClassSpellList;
import pcgen.cdom.list.CompanionList;
import pcgen.cdom.list.DomainSpellList;
import pcgen.core.Ability;
import pcgen.core.AbilityCategory;
import pcgen.core.BonusManager;
import pcgen.core.BonusManager.TempBonusInfo;
import pcgen.core.Campaign;
import pcgen.core.ChronicleEntry;
import pcgen.core.Deity;
import pcgen.core.Domain;
import pcgen.core.Equipment;
import pcgen.core.GameMode;
import pcgen.core.Globals;
import pcgen.core.Kit;
import pcgen.core.Language;
import pcgen.core.NoteItem;
import pcgen.core.PCAlignment;
import pcgen.core.PCClass;
import pcgen.core.PCStat;
import pcgen.core.PCTemplate;
import pcgen.core.PObject;
import pcgen.core.PlayerCharacter;
import pcgen.core.Race;
import pcgen.core.SettingsHandler;
import pcgen.core.Skill;
import pcgen.core.SpecialAbility;
import pcgen.core.SpellProhibitor;
import pcgen.core.SubClass;
import pcgen.core.SubstitutionClass;
import pcgen.core.SystemCollections;
import pcgen.core.WeaponProf;
import pcgen.core.analysis.BonusAddition;
import pcgen.core.analysis.ChooseActivation;
import pcgen.core.analysis.DomainApplication;
import pcgen.core.analysis.RaceAlignment;
import pcgen.core.analysis.SkillRankControl;
import pcgen.core.analysis.SpellLevel;
import pcgen.core.analysis.SubClassApplication;
import pcgen.core.analysis.SubstitutionLevelSupport;
import pcgen.core.bonus.Bonus;
import pcgen.core.bonus.BonusObj;
import pcgen.core.character.CharacterSpell;
import pcgen.core.character.EquipSet;
import pcgen.core.character.Follower;
import pcgen.core.character.SpellBook;
import pcgen.core.character.SpellInfo;
import pcgen.core.chooser.ChoiceManagerList;
import pcgen.core.chooser.ChooserUtilities;
import pcgen.core.display.BonusDisplay;
import pcgen.core.pclevelinfo.PCLevelInfo;
import pcgen.core.spell.Spell;
import pcgen.core.utils.CoreUtility;
import pcgen.core.utils.MessageType;
import pcgen.core.utils.ShowMessageDelegate;
import pcgen.facade.core.SourceSelectionFacade;
import pcgen.io.migration.AbilityMigration;
import pcgen.io.migration.AbilityMigration.CategorisedKey;
import pcgen.io.migration.EquipSetMigration;
import pcgen.io.migration.EquipmentMigration;
import pcgen.io.migration.RaceMigration;
import pcgen.io.migration.SourceMigration;
import pcgen.io.migration.SpellMigration;
import pcgen.persistence.PersistenceLayerException;
import pcgen.rules.context.AbstractReferenceContext;
import pcgen.rules.context.LoadContext;
import pcgen.system.FacadeFactory;
import pcgen.system.LanguageBundle;
import pcgen.system.PCGenSettings;
import pcgen.util.Logging;
import pcgen.util.enumeration.ProhibitedSpellType;
/**
* {@code PCGVer2Parser}
* Parses a line oriented format.
* Each line should adhere to the following grammar:<br>
*
* <i>line</i> := EMPTY | <i>comment</i> | <i>taglist</i>
* <i>comment</i> := '#' STRING
* <i>taglist</i> := tag ('|' tag)*
* <i>tag</i> := simpletag | nestedtag
* <i>nestedtag</i> := TAGNAME ':' '[' taglist ']'
* <i>simpletag</i> := TAGNAME ':' TAGVALUE
*
*
* @author Thomas Behr 22-03-02
*/
final class PCGVer2Parser implements PCGParser
{
private static final Class<Domain> DOMAIN_CLASS = Domain.class;
private static final String TAG_PCTEMPLATE = "PCTEMPLATE";
private RaceInputFacet raceInputFacet = FacetLibrary
.getFacet(RaceInputFacet.class);
private DomainInputFacet domainInputFacet = FacetLibrary
.getFacet(DomainInputFacet.class);
private TemplateInputFacet templateInputFacet = FacetLibrary
.getFacet(TemplateInputFacet.class);
/**
* DO NOT CHANGE line separator.
* Need to keep the Unix line separator to ensure cross-platform portability.
*
* author: Thomas Behr 2002-11-13
*/
private final List<String> warnings = new ArrayList<>();
private Cache cache;
private PlayerCharacter thePC;
private final Set<String> seenStats = new HashSet<>();
private final Set<Language> cachedLanguages = new HashSet<>();
//
// MAJOR.MINOR.REVISION
//
private int[] pcgenVersion = {0, 0, 0};
private String pcgenVersionSuffix;
private boolean calcFeatPoolAfterLoad = false;
private double baseFeatPool = 0.0;
private boolean featsPresent = false;
/**
* Constructor
* @param aPC
*/
PCGVer2Parser(PlayerCharacter aPC)
{
thePC = aPC;
}
/**
* Selector
*
* <br>author: Thomas Behr 22-03-02
*
* @return a list of warning messages
*/
@Override
public List<String> getWarnings()
{
return warnings;
}
/**
* parse a String in PCG format
*
* <br>author: Thomas Behr 20-07-02
*
* @param lines the String to parse
* @throws PCGParseException
*/
@Override
public void parsePCG(String[] lines) throws PCGParseException
{
buildPcgLineCache(lines);
parseCachedLines();
resolveLanguages();
}
/**
* Check the game mode and then build a list of campaigns the character
* requires to be loaded.
*
* @param lines The PCG lines to be parsed.
* @return The list of campaigns.
* @throws PCGParseException If the lines are invalid
*/
@Override
public SourceSelectionFacade parcePCGSourceOnly(String[] lines)
throws PCGParseException
{
buildPcgLineCache(lines);
/*
* VERSION:x.x.x
*/
if (cache.containsKey(IOConstants.TAG_VERSION))
{
parseVersionLine(cache.get(IOConstants.TAG_VERSION).get(0));
}
if (!cache.containsKey(IOConstants.TAG_GAMEMODE))
{
Logging
.errorPrint("Character does not have game mode information.");
return null;
}
String line = cache.get(IOConstants.TAG_GAMEMODE).get(0);
String requestedMode = line.substring(IOConstants.TAG_GAMEMODE.length() + 1);
GameMode mode = SystemCollections.getGameModeNamed(requestedMode);
if (mode == null)
{
for (GameMode gameMode : SystemCollections
.getUnmodifiableGameModeList())
{
if (gameMode.getAllowedModes().contains(requestedMode))
{
mode = gameMode;
break;
}
}
}
//if mode == null still then a game mode was not found
if (mode == null)
{
Logging.errorPrint("Character's game mode entry was not valid: "
+ line);
return null;
}
if (!cache.containsKey(IOConstants.TAG_CAMPAIGN))
{
Logging.errorPrint("Character does not have campaign information.");
return FacadeFactory.createSourceSelection(mode,
new ArrayList<>());
}
/*
* #System Information
* CAMPAIGN:CMP - Monkey Book I - Book For Monkeys
* CAMPAIGN:CMP - Monkey Book II - Book By Monkeys
* ...
*
* first thing to do is checking campaigns - no matter what!
*/
List<Campaign> campaigns = getCampaignList(cache.get(IOConstants.TAG_CAMPAIGN), mode.getName());
if (campaigns.isEmpty())
{
Logging.errorPrint("Character's campaign entry was empty.");
}
return FacadeFactory.createSourceSelection(mode, campaigns);
}
/**
* @param lines
*/
private void buildPcgLineCache(String[] lines)
{
initCache(lines.length);
for (int i = 0; i < lines.length; ++i)
{
if ((!lines[i].trim().isEmpty()) && !isComment(lines[i]))
{
cacheLine(lines[i].trim());
}
}
}
/*
* ###############################################################
* Miscellaneous methods
* ###############################################################
*/
/**
* Convenience Method
*
* <br>author: Thomas Behr 28-04-02
*
* @param line
* @return true if it is a comment
*/
private static boolean isComment(String line)
{
return line.trim().startsWith(IOConstants.TAG_COMMENT);
}
/*
* Given a Source string and Target string,
* return a List of BonusObj's
*/
private Map<BonusObj, TempBonusInfo> getBonusFromName(String sName,
String tName)
{
//sName = NAME=Haste
//tName = PC
String sourceStr = sName.substring(IOConstants.TAG_TEMPBONUS.length() + 1);
String targetStr = tName.substring(IOConstants.TAG_TEMPBONUSTARGET.length() + 1);
Object oSource = null;
Object oTarget = null;
if (sourceStr.startsWith(IOConstants.TAG_FEAT + '='))
{
sourceStr = sourceStr.substring(5);
oSource =
Globals.getContext().getReferenceContext().silentlyGetConstructedCDOMObject(
Ability.class, AbilityCategory.FEAT, sourceStr);
oSource = thePC.getAbilityKeyed(AbilityCategory.FEAT, sourceStr);
if (oSource == null)
{
for (final AbilityCategory cat : SettingsHandler.getGame()
.getAllAbilityCategories())
{
Ability abilSourceObj =
Globals.getContext().getReferenceContext()
.silentlyGetConstructedCDOMObject(
Ability.class, cat, sourceStr);
if (abilSourceObj != null)
{
oSource = abilSourceObj;
}
}
}
}
else if (sourceStr.startsWith(IOConstants.TAG_SPELL + '='))
{
sourceStr = sourceStr.substring(6);
//oSource = aPC.getSpellNamed(sourceStr);
oSource = Globals.getContext().getReferenceContext()
.silentlyGetConstructedCDOMObject(Spell.class, sourceStr);
}
else if (sourceStr.startsWith(IOConstants.TAG_EQUIPMENT + '='))
{
sourceStr = sourceStr.substring(10);
oSource = thePC.getEquipmentNamed(sourceStr);
}
else if (sourceStr.startsWith(IOConstants.TAG_CLASS + '='))
{
sourceStr = sourceStr.substring(6);
oSource = thePC.getClassKeyed(sourceStr);
}
else if (sourceStr.startsWith(IOConstants.TAG_TEMPLATE + '='))
{
sourceStr = sourceStr.substring(9);
oSource =
Globals.getContext().getReferenceContext().silentlyGetConstructedCDOMObject(
PCTemplate.class, sourceStr);
}
else if (sourceStr.startsWith(IOConstants.TAG_SKILL + '='))
{
sourceStr = sourceStr.substring(6);
Skill aSkill =
Globals.getContext().getReferenceContext().silentlyGetConstructedCDOMObject(
Skill.class, sourceStr);
if (thePC.hasSkill(aSkill))
{
oSource = aSkill;
}
else
{
// TODO Error message
}
}
else
{
// TODO Error message
// Hmm, not a supported type
}
if (oSource != null)
{
sourceStr = ((CDOMObject) oSource).getKeyName();
}
if (targetStr.equals(IOConstants.TAG_PC))
{
targetStr = thePC.getName();
}
else
{
oTarget = thePC.getEquipmentNamed(targetStr);
targetStr = ((CDOMObject) oTarget).getDisplayName();
}
return thePC.getTempBonusMap(sourceStr, targetStr);
}
private void addKeyedTemplate(PCTemplate template, String choice)
{
if (ChooseActivation.hasNewChooseToken(template) && choice == null)
{
final String message =
"Template ignored: "
+ template
+ " as a choice was expected but none was present in character.";
warnings.add(message);
return;
}
final int preXP = thePC.getXP();
templateInputFacet.importSelection(thePC.getCharID(), template, choice);
thePC.addTemplate(template);
//
// XP written to file contains leveladjustment XP. If template modifies
// XP, then
// it will have already been added into total. Need to make sure it is
// not doubled.
//
if (thePC.getXP() != preXP)
{
thePC.setXP(preXP);
}
}
private void cacheLine(String s)
{
cache.put(s.substring(0, s.indexOf(':')), s);
}
private void checkSkillPools()
{
int skillPoints = 0;
for (final PCClass pcClass : thePC.getClassSet())
{
skillPoints += pcClass.getSkillPool(thePC);
}
thePC.setDirty(true);
}
private void checkStats() throws PCGParseException
{
if (seenStats.size() != Globals.getContext().getReferenceContext()
.getConstructedObjectCount(PCStat.class))
{
final String message =
LanguageBundle.getFormattedString(
"Exceptions.PCGenParser.WrongNumAttributes", //$NON-NLS-1$
seenStats.size(), Globals.getContext().getReferenceContext()
.getConstructedObjectCount(PCStat.class));
throw new PCGParseException("parseStatLines", "N/A", message); //$NON-NLS-1$//$NON-NLS-2$
}
}
/*
* ###############################################################
* private helper methods
* ###############################################################
*/
private void initCache(int capacity)
{
cache = new Cache((capacity * 4) / 3);
}
private void parseAgeLine(String line)
{
try
{
thePC.setPCAttribute(NumericPCAttribute.AGE, Integer.parseInt(line.substring(IOConstants.TAG_AGE.length() + 1)));
}
catch (NumberFormatException nfe)
{
final String message =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.IllegalAgeLine", //$NON-NLS-1$
line);
warnings.add(message);
}
}
private void parseAgeSet(String line)
{
final StringTokenizer aTok = new StringTokenizer(line, IOConstants.TAG_END, false);
int i = 0;
aTok.nextToken(); // skip tag
while (aTok.hasMoreTokens() && (i < 10))
{
thePC.setHasMadeKitSelectionForAgeSet(i++,
aTok.nextToken().equals("1")); //$NON-NLS-1$
}
}
private void parseAlignmentLine(String line)
{
final String alignment = line.substring(IOConstants.TAG_ALIGNMENT.length() + 1);
PCAlignment align =
Globals.getContext().getReferenceContext().silentlyGetConstructedCDOMObject(
PCAlignment.class, alignment);
if (align != null)
{
if (!RaceAlignment.canBeAlignment(thePC.getRace(), align))
{
ShowMessageDelegate.showMessageDialog(
"Invalid alignment. Setting to <none selected>",
Constants.APPLICATION_NAME, MessageType.INFORMATION);
align = getNoAlignment();
}
thePC.setAlignment(align);
return;
}
final String message =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.IllegalAlignment", //$NON-NLS-1$
line);
warnings.add(message);
}
/**
* Auto sort gear (Y/N)
* @param line Line of saved data to be processed.
**/
private void parseAutoSortGearLine(String line)
{
thePC.setAutoSortGear(line.endsWith(IOConstants.VALUE_Y));
}
/**
* Ignore cost for gear (Y/N)
* @param line Line of saved data to be processed.
**/
private void parseIgnoreCostLine(String line)
{
thePC.setIgnoreCost(line.endsWith(IOConstants.VALUE_Y));
}
/**
* Allow debt for gear (Y/N)
* @param line Line of saved data to be processed.
**/
private void parseAllowDebtLine(String line)
{
thePC.setAllowDebt(line.endsWith(IOConstants.VALUE_Y));
}
/**
* Auto resize gear (Y/N)
* @param line Line of saved data to be processed.
**/
private void parseAutoResizeGearLine(String line)
{
thePC.setAutoResize(line.endsWith(IOConstants.VALUE_Y));
}
/**
* # Auto sort skills - transition only, line is no longer saved
* @param line
**/
private void parseAutoSortSkillsLine(String line)
{
if (line.endsWith(IOConstants.VALUE_Y))
{
thePC
.setSkillsOutputOrder(SkillsOutputOrder.NAME_ASC);
}
else
{
thePC
.setSkillsOutputOrder(SkillsOutputOrder.MANUAL);
}
}
/**
* # Auto known spells
* @param line
**/
private void parseAutoSpellsLine(String line)
{
thePC.setAutoSpells(line.endsWith(IOConstants.VALUE_Y));
}
/**
* Process the Use Higher Known Spell Slot line.
* @param line The buffer to append to.
*/
private void parseUseHigherKnownSpellSlotsLine(String line)
{
thePC.setUseHigherKnownSlots(line.endsWith(IOConstants.VALUE_Y));
}
/**
* Process the Use Higher Prepped Spell Slot line.
* @param line The buffer to append to.
*/
private void parseUseHigherPreppedSpellSlotsLine(String line)
{
thePC.setUseHigherPreppedSlots(line.endsWith(IOConstants.VALUE_Y));
}
private void parseBirthdayLine(String line)
{
thePC.setPCAttribute(PCAttribute.BIRTHDAY, EntityEncoder.decode(line.substring(IOConstants.TAG_BIRTHDAY
.length() + 1)));
}
private void parseBirthplaceLine(String line)
{
thePC.setPCAttribute(PCAttribute.BIRTHPLACE, EntityEncoder.decode(line.substring(IOConstants.TAG_BIRTHPLACE
.length() + 1)));
}
/**
* Does the actual work:<br>
* Retrieves cached lines and parses each line.
*
* Note: May have to change parse order!
*
* <br>author: Thomas Behr 31-07-02
*
* @throws PCGParseException
*/
private void parseCachedLines() throws PCGParseException
{
/*
* VERSION:x.x.x
*/
if (cache.containsKey(IOConstants.TAG_VERSION))
{
parseVersionLine(cache.get(IOConstants.TAG_VERSION).get(0));
}
if (cache.containsKey(IOConstants.TAG_GAMEMODE))
{
parseGameMode(cache.get(IOConstants.TAG_GAMEMODE).get(0));
}
/*
* #System Information
* CAMPAIGN:CMP - Monkey Book I - Book For Monkeys
* CAMPAIGN:CMP - Monkey Book II - Book By Monkeys
* ...
*
* first thing to do is checking campaigns - no matter what!
*/
if (cache.containsKey(IOConstants.TAG_CAMPAIGN))
{
checkDisplayListsHappy();
}
/*
* #Character Attributes
* STAT:STR=18
* STAT:DEX=18
* STAT:CON=18
* STAT:INT=18
* STAT:WIS=18
* STAT:CHA=18
* ALIGN:LG
* RACE:Human
*/
if (cache.containsKey(IOConstants.TAG_STAT))
{
for (final String stat : cache.get(IOConstants.TAG_STAT))
{
parseStatLine(stat);
}
checkStats();
}
if (cache.containsKey(IOConstants.TAG_ALIGNMENT))
{
parseAlignmentLine(cache.get(IOConstants.TAG_ALIGNMENT).get(0));
}
/*
* # Kits - Just adds a reference to the character that the template
* was picked.
*/
if (cache.containsKey(IOConstants.TAG_KIT))
{
for (final String line : cache.get(IOConstants.TAG_KIT))
{
parseKitLine(line);
}
}
if (cache.containsKey(IOConstants.TAG_RACE))
{
parseRaceLine(cache.get(IOConstants.TAG_RACE).get(0));
}
if (cache.containsKey(IOConstants.TAG_FAVOREDCLASS))
{
parseFavoredClassLine(cache.get(IOConstants.TAG_FAVOREDCLASS).get(0));
}
/*
* #System Information
* CAMPAIGNS:>:-delimited list<
* VERSION:x.x.x
* ROLLMETHOD:xxx
* PURCHASEPOINTS:Y or N|TYPE:>living City, Living greyhawk, etc<
* UNLIMITEDPOOLCHECKED:Y or N
* POOLPOINTS:>numeric value 0-?<
* POOLPOINTSAVAIL:>numeric value 0-?<
* GAMEMODE:DnD
* TABLABEL:0
* AUTOSPELLS:Y or N
* LOADCOMPANIONS:Y or N
* USETEMPMODS:Y or N
* AUTOSORTGEAR:Y or N
* SKILLSOUTPUTORDER:0
*/
if (cache.containsKey(IOConstants.TAG_POOLPOINTS))
{
parsePoolPointsLine(cache.get(IOConstants.TAG_POOLPOINTS).get(0));
}
if (cache.containsKey(IOConstants.TAG_POOLPOINTSAVAIL))
{
parsePoolPointsLine2(cache.get(IOConstants.TAG_POOLPOINTSAVAIL).get(0));
}
if (cache.containsKey(IOConstants.TAG_CHARACTERTYPE))
{
parseCharacterTypeLine(cache.get(IOConstants.TAG_CHARACTERTYPE).get(0));
}
if (cache.containsKey(IOConstants.TAG_PREVIEWSHEET))
{
parsePreviewSheetLine(cache.get(IOConstants.TAG_PREVIEWSHEET).get(0));
}
if (cache.containsKey(IOConstants.TAG_AUTOSPELLS))
{
parseAutoSpellsLine(cache.get(IOConstants.TAG_AUTOSPELLS).get(0));
}
if (cache.containsKey(IOConstants.TAG_USEHIGHERKNOWN))
{
parseUseHigherKnownSpellSlotsLine(cache.get(IOConstants.TAG_USEHIGHERKNOWN)
.get(0));
}
if (cache.containsKey(IOConstants.TAG_USEHIGHERPREPPED))
{
parseUseHigherPreppedSpellSlotsLine(cache.get(IOConstants.TAG_USEHIGHERPREPPED)
.get(0));
}
if (cache.containsKey(IOConstants.TAG_LOADCOMPANIONS))
{
parseLoadCompanionLine(cache.get(IOConstants.TAG_LOADCOMPANIONS).get(0));
}
if (cache.containsKey(IOConstants.TAG_USETEMPMODS))
{
parseUseTempModsLine(cache.get(IOConstants.TAG_USETEMPMODS).get(0));
}
if (cache.containsKey(IOConstants.TAG_HTMLOUTPUTSHEET))
{
parseHTMLOutputSheetLine(cache.get(IOConstants.TAG_HTMLOUTPUTSHEET).get(0));
}
if (cache.containsKey(IOConstants.TAG_PDFOUTPUTSHEET))
{
parsePDFOutputSheetLine(cache.get(IOConstants.TAG_PDFOUTPUTSHEET).get(0));
}
if (cache.containsKey(IOConstants.TAG_AUTOSORTGEAR))
{
parseAutoSortGearLine(cache.get(IOConstants.TAG_AUTOSORTGEAR).get(0));
}
if (cache.containsKey(IOConstants.TAG_IGNORECOST))
{
parseIgnoreCostLine(cache.get(IOConstants.TAG_IGNORECOST).get(0));
}
if (cache.containsKey(IOConstants.TAG_ALLOWDEBT))
{
parseAllowDebtLine(cache.get(IOConstants.TAG_ALLOWDEBT).get(0));
}
if (cache.containsKey(IOConstants.TAG_AUTORESIZEGEAR))
{
parseAutoResizeGearLine(cache.get(IOConstants.TAG_AUTORESIZEGEAR).get(0));
}
if (cache.containsKey(IOConstants.TAG_AUTOSORTSKILLS))
{
parseAutoSortSkillsLine(cache.get(IOConstants.TAG_AUTOSORTSKILLS).get(0));
}
if (cache.containsKey(IOConstants.TAG_SKILLSOUTPUTORDER))
{
parseSkillsOutputOrderLine(cache.get(IOConstants.TAG_SKILLSOUTPUTORDER).get(0));
}
if (cache.containsKey(IOConstants.TAG_SKILLFILTER))
{
parseSkillFilterLine(cache.get(IOConstants.TAG_SKILLFILTER).get(0));
}
/*
* #Character Class(es)
* CLASS:Fighter|LEVEL=3
* CLASSABILITIESLEVEL:Fighter=1(>This would only display up to the level the character has already,)
* CLASSABILITIESLEVEL:Fighter=2(>with any special abilities not covered by other areas,)
* CLASSABILITIESLEVEL:Fighter=3(>such as skills, feats, etc., but would list SA's, and the like<)
* CLASS:Wizard|LEVEL=1
* CLASSABILITIESLEVEL:Wizard=1(SA's, MEMORIZE:Y, etc)
*/
if (cache.containsKey(IOConstants.TAG_CLASS))
{
for (String line : cache.get(IOConstants.TAG_CLASS))
{
parseClassLine(line);
}
checkSkillPools();
}
final List<PCLevelInfo> pcLevelInfoList =
new ArrayList<>(thePC.getLevelInfo());
if (cache.containsKey(IOConstants.TAG_CLASSABILITIESLEVEL))
{
thePC.clearLevelInfo();
for (String line : cache.get(IOConstants.TAG_CLASSABILITIESLEVEL))
{
parseClassAbilitiesLevelLine(line, pcLevelInfoList);
}
}
/*
* #Character Experience
* EXPERIENCE:6000
* EXPERIENCETABLE:Medium
*/
if (cache.containsKey(IOConstants.TAG_EXPERIENCE))
{
parseExperienceLine(cache.get(IOConstants.TAG_EXPERIENCE).get(0));
}
if (cache.containsKey(IOConstants.TAG_EXPERIENCETABLE))
{
parseExperienceTableLine(cache.get(IOConstants.TAG_EXPERIENCETABLE).get(0));
}
/*
* #Character Templates
* TEMPLATESAPPLIED:If any, else this would just have the comment line, and skip to the next
*/
if (cache.containsKey(IOConstants.TAG_TEMPLATESAPPLIED))
{
for (String line : cache.get(IOConstants.TAG_TEMPLATESAPPLIED))
{
parseTemplateLine(line);
}
}
if (cache.containsKey(IOConstants.TAG_REGION))
{
for (String line : cache.get(IOConstants.TAG_REGION))
{
parseRegionLine(line);
}
}
/*
* ###############################################################
* Character Skills methods
* ###############################################################
*/
/*
* #Character Skills
* CLASSBOUGHT:Fighter
* SKILL:Alchemy|CLASS:N|COST:2|RANK:7 (Should be Obvious what each of these does, I hope ;p)
* SKILL:Survival|CLASS:Y|COST:1|SYNERGY:Wilderness Lore=5=2|RANK:10
* CLASSBOUGHT:Wizard
* SKILL:Spellcraft|CLASS:Y|COST:1|RANK:7
*
* CLASSBOUGHT not supported
*/
if (cache.containsKey(IOConstants.TAG_SKILL))
{
for (final String line : cache.get(IOConstants.TAG_SKILL))
{
parseSkillLine(line);
}
}
/*
* #Character Languages
* LANGUAGE:Chondathan|LANGUAGE:Common|LANGUAGE:Literacy
*/
if (cache.containsKey(IOConstants.TAG_LANGUAGE))
{
for (final String line : cache.get(IOConstants.TAG_LANGUAGE))
{
parseLanguageLine(line);
}
}
/*
* Anything that is already Pipe Delimited should be in
* parenthesis to avoid confusion on PCGen's part
*
* #Character Feats
* FEAT:Alertness|TYPE:General|(BONUS:SKILL|Listen,Spot|2)|DESC:+2 on Listen and Spot checks
* FEATPOOL:>number of remaining feats<
*/
if (cache.containsKey(IOConstants.TAG_FEAT))
{
for (final String line : cache.get(IOConstants.TAG_FEAT))
{
parseFeatLine(line);
}
}
if (cache.containsKey(IOConstants.TAG_VFEAT))
{
for (final String line : cache.get(IOConstants.TAG_VFEAT))
{
parseVFeatLine(line);
}
}
if (cache.containsKey(IOConstants.TAG_FEATPOOL))
{
for (final String line : cache.get(IOConstants.TAG_FEATPOOL))
{
parseFeatPoolLine(line);
}
}
if (cache.containsKey(IOConstants.TAG_ABILITY))
{
for (final String line : cache.get(IOConstants.TAG_ABILITY))
{
parseAbilityLine(line);
}
}
if (cache.containsKey(IOConstants.TAG_USERPOOL))
{
for (final String line : cache.get(IOConstants.TAG_USERPOOL))
{
parseUserPoolLine(line);
}
}
/*
* #Character Deity/Domain
* DEITY:Yondalla|DEITYDOMAINS:[DOMAIN:Good|DOMAIN:Law|DOMAIN:Protection]|ALIGNALLOW:013|DESC:Halflings, Protection, Fertility|SYMBOL:None|DEITYFAVWEAP:Sword (Short)|DEITYALIGN:ALIGN:LG
* DOMAIN:GOOD|DOMAINGRANTS:>list of abilities<
* DOMAINSPELLS:GOOD|SPELLLIST:[SPELL:bla|SPELL:blubber|...]
*/
if (cache.containsKey(IOConstants.TAG_DEITY))
{
for (final String line : cache.get(IOConstants.TAG_DEITY))
{
parseDeityLine(line);
}
}
if (cache.containsKey(IOConstants.TAG_DOMAIN))
{
for (final String line : cache.get(IOConstants.TAG_DOMAIN))
{
parseDomainLine(line);
}
}
//We ignore domain spells now
if (cache.containsKey(IOConstants.TAG_SPELLBOOK))
{
for (final String line : cache.get(IOConstants.TAG_SPELLBOOK))
{
parseSpellBookLines(line);
}
}
/*
* This one is what will make spellcasters U G L Y!!!
*
* #Character Spells Information
* CLASS:Wizard|CANCASTPERDAY:2,4(Totals the levels all up + includes attribute bonuses)
* SPELLNAME:Blah|SCHOOL:blah|SUBSCHOOL:blah|Etc
*/
if (cache.containsKey(IOConstants.TAG_SPELLLIST))
{
for (final String line : cache.get(IOConstants.TAG_SPELLLIST))
{
parseSpellListLines(line);
}
}
//For those that weren't explicitly specified, insert them
insertDefaultClassSpellLists();
if (cache.containsKey(IOConstants.TAG_SPELLNAME))
{
// Calculate what has been granted so far, particularly any ability granted spells
thePC.setImporting(false);
thePC.calcActiveBonuses();
thePC.setImporting(true);
for (final String line : cache.get(IOConstants.TAG_SPELLNAME))
{
parseSpellLine(line);
}
}
/*
* #Character Description/Bio/Historys
* CHARACTERBIO:any text that's in the BIO field
* CHARACTERDESC:any text that's in the BIO field
*/
if (cache.containsKey(IOConstants.TAG_CHARACTERBIO))
{
parseCharacterBioLine(cache.get(IOConstants.TAG_CHARACTERBIO).get(0));
}
if (cache.containsKey(IOConstants.TAG_CHARACTERDESC))
{
parseCharacterDescLine(cache.get(IOConstants.TAG_CHARACTERDESC).get(0));
}
if (cache.containsKey(IOConstants.TAG_CHARACTERCOMP))
{
for (final String line : cache.get(IOConstants.TAG_CHARACTERCOMP))
{
parseCharacterCompLine(line);
}
}
if (cache.containsKey(IOConstants.TAG_CHARACTERASSET))
{
for (final String line : cache.get(IOConstants.TAG_CHARACTERASSET))
{
parseCharacterAssetLine(line);
}
}
if (cache.containsKey(IOConstants.TAG_CHARACTERMAGIC))
{
for (final String line : cache.get(IOConstants.TAG_CHARACTERMAGIC))
{
parseCharacterMagicLine(line);
}
}
if (cache.containsKey(IOConstants.TAG_CHARACTERDMNOTES))
{
for (final String line : cache.get(IOConstants.TAG_CHARACTERDMNOTES))
{
parseCharacterDmNotesLine(line);
}
}
/*
* #Character Master/Followers
* MASTER:Mynex|TYPE:Follower|HITDICE:20|FILE:E$\DnD\dnd-chars\ravenlock.pcg
* FOLLOWER:Raven|TYPE:Animal Companion|HITDICE:5|FILE:E$\DnD\dnd-chars\raven.pcg
*/
if (cache.containsKey(IOConstants.TAG_MASTER))
{
for (final String line : cache.get(IOConstants.TAG_MASTER))
{
parseMasterLine(line);
}
}
if (cache.containsKey(IOConstants.TAG_FOLLOWER))
{
for (final String line : cache.get(IOConstants.TAG_FOLLOWER))
{
parseFollowerLine(line);
}
}
/*
* Contains information about PC's equipment
* Money goes here as well
*
* #Character Equipment
* EQUIPNAME:Longsword|OUTPUTORDER:1|COST:5|WT:5|NOTE:It's very sharp!|>other info<
* EQUIPNAME:Backpack|OUTPUTORDER:-1|COST:5|WT:5|NOTE:on my back
* EQUIPNAME:Rope (Silk)|OUTPUTORDER:3|COST:5|WT:5
*/
if (cache.containsKey(IOConstants.TAG_MONEY))
{
for (final String line : cache.get(IOConstants.TAG_MONEY))
{
parseMoneyLine(line);
}
}
if (cache.containsKey(IOConstants.TAG_EQUIPNAME))
{
// We process the bonuses loaded so far so that natural weapons from
// conditional abilities can be found.
thePC.setImporting(false);
thePC.setCalcFollowerBonus();
thePC.calcActiveBonuses();
thePC.setImporting(true);
for (final String line : cache.get(IOConstants.TAG_EQUIPNAME))
{
parseEquipmentLine(line);
}
}
if (cache.containsKey(IOConstants.TAG_EQUIPSET))
{
/*
* strangely enough this works even if we create a
* EquipSet for content whose container EquipSet
* has not been created yet
* author: Thomas Behr 10-09-02
*
* Comment from EquipSet author:
* It only works because I've already sorted on output
* in PCGVer2Creator
* author: Jayme Cox 01-16-03
*
*/
//Collections.sort(cache.get(TAG_EQUIPSET), new EquipSetLineComparator());
for (final String line : cache.get(IOConstants.TAG_EQUIPSET))
{
parseEquipmentSetLine(line);
}
EquipSetMigration.migrateEquipSets(thePC, pcgenVersion);
}
/**
* CALCEQUIPSET line contains the "working" equipment list
**/
if (cache.containsKey(IOConstants.TAG_CALCEQUIPSET))
{
for (final String line : cache.get(IOConstants.TAG_CALCEQUIPSET))
{
parseCalcEquipSet(line);
}
}
/*
* #Character Notes Tab
*/
if (cache.containsKey(IOConstants.TAG_NOTE))
{
for (final String line : cache.get(IOConstants.TAG_NOTE))
{
parseNoteLine(line);
}
}
/*
* #Character Bio
* CHARACTERNAME:Code Monkey
* TABNAME:Code Monkey the Best Ever No Really!
* PLAYERNAME:Jason Monkey
* HEIGHT:75
* WEIGHT:198
* AGE:17
* GENDER:enum name @see Gender
* HANDED:enum name @see Handed
* SKIN:text
* EYECOLOR:text
* HAIRCOLOR:text
* HAIRSTYLE:text
* LOCATION:text
* CITY:text
* PERSONALITYTRAIT1:text
* PERSONALITYTRAIT2:text
* SPEECHPATTERN:text
* PHOBIAS:text
* INTERESTS:text
* CATCHPHRASE:text
*/
if (cache.containsKey(IOConstants.TAG_CHARACTERNAME))
{
parseCharacterNameLine(cache.get(IOConstants.TAG_CHARACTERNAME).get(0));
}
if (cache.containsKey(IOConstants.TAG_TABNAME))
{
parseTabNameLine(cache.get(IOConstants.TAG_TABNAME).get(0));
}
if (cache.containsKey(IOConstants.TAG_PLAYERNAME))
{
parsePlayerNameLine(cache.get(IOConstants.TAG_PLAYERNAME).get(0));
}
if (cache.containsKey(IOConstants.TAG_HEIGHT))
{
parseHeightLine(cache.get(IOConstants.TAG_HEIGHT).get(0));
}
if (cache.containsKey(IOConstants.TAG_WEIGHT))
{
parseWeightLine(cache.get(IOConstants.TAG_WEIGHT).get(0));
}
if (cache.containsKey(IOConstants.TAG_AGE))
{
parseAgeLine(cache.get(IOConstants.TAG_AGE).get(0));
}
if (cache.containsKey(IOConstants.TAG_GENDER))
{
parseGenderLine(cache.get(IOConstants.TAG_GENDER).get(0));
}
if (cache.containsKey(IOConstants.TAG_HANDED))
{
parseHandedLine(cache.get(IOConstants.TAG_HANDED).get(0));
}
if (cache.containsKey(IOConstants.TAG_SKINCOLOR))
{
parseSkinColorLine(cache.get(IOConstants.TAG_SKINCOLOR).get(0));
}
if (cache.containsKey(IOConstants.TAG_EYECOLOR))
{
parseEyeColorLine(cache.get(IOConstants.TAG_EYECOLOR).get(0));
}
if (cache.containsKey(IOConstants.TAG_HAIRCOLOR))
{
parseHairColorLine(cache.get(IOConstants.TAG_HAIRCOLOR).get(0));
}
if (cache.containsKey(IOConstants.TAG_HAIRSTYLE))
{
parseHairStyleLine(cache.get(IOConstants.TAG_HAIRSTYLE).get(0));
}
if (cache.containsKey(IOConstants.TAG_LOCATION))
{
parseLocationLine(cache.get(IOConstants.TAG_LOCATION).get(0));
}
//this tag is obsolete, but left in for backward-compatibility, replaced by TAG_CITY
if (cache.containsKey(IOConstants.TAG_RESIDENCE))
{
parseResidenceLine(cache.get(IOConstants.TAG_RESIDENCE).get(0));
}
if (cache.containsKey(IOConstants.TAG_CITY))
{
parseCityLine(cache.get(IOConstants.TAG_CITY).get(0));
}
if (cache.containsKey(IOConstants.TAG_BIRTHDAY))
{
parseBirthdayLine(cache.get(IOConstants.TAG_BIRTHDAY).get(0));
}
if (cache.containsKey(IOConstants.TAG_BIRTHPLACE))
{
parseBirthplaceLine(cache.get(IOConstants.TAG_BIRTHPLACE).get(0));
}
if (cache.containsKey(IOConstants.TAG_PERSONALITYTRAIT1))
{
for (final String line : cache.get(IOConstants.TAG_PERSONALITYTRAIT1))
{
parsePersonalityTrait1Line(line);
}
}
if (cache.containsKey(IOConstants.TAG_PERSONALITYTRAIT2))
{
for (final String line : cache.get(IOConstants.TAG_PERSONALITYTRAIT2))
{
parsePersonalityTrait2Line(line);
}
}
if (cache.containsKey(IOConstants.TAG_SPEECHPATTERN))
{
parseSpeechPatternLine(cache.get(IOConstants.TAG_SPEECHPATTERN).get(0));
}
if (cache.containsKey(IOConstants.TAG_PHOBIAS))
{
parsePhobiasLine(cache.get(IOConstants.TAG_PHOBIAS).get(0));
}
if (cache.containsKey(IOConstants.TAG_INTERESTS))
{
parseInterestsLine(cache.get(IOConstants.TAG_INTERESTS).get(0));
}
if (cache.containsKey(IOConstants.TAG_CATCHPHRASE))
{
parseCatchPhraseLine(cache.get(IOConstants.TAG_CATCHPHRASE).get(0));
}
if (cache.containsKey(IOConstants.TAG_PORTRAIT))
{
parsePortraitLine(cache.get(IOConstants.TAG_PORTRAIT).get(0));
}
if (cache.containsKey(IOConstants.TAG_PORTRAIT_THUMBNAIL_RECT))
{
parsePortraitThumbnailRectLine(cache.get(
IOConstants.TAG_PORTRAIT_THUMBNAIL_RECT).get(0));
}
/*
* #Character Weapon proficiencies
*/
if (cache.containsKey(IOConstants.TAG_WEAPONPROF))
{
for (final String line : cache.get(IOConstants.TAG_WEAPONPROF))
{
parseWeaponProficienciesLine(line);
}
// This is not reliable during character load, and the warning is
// of little value, so I'm disabling the check for now.
//checkWeaponProficiencies();
}
/*
* # Temporary Bonuses
*/
if (cache.containsKey(IOConstants.TAG_TEMPBONUS))
{
for (final String line : cache.get(IOConstants.TAG_TEMPBONUS))
{
parseTempBonusLine(line);
}
}
/*
* # EquipSet Temporary bonuses
* Must be done after both EquipSet and TempBonuses are parsed
*/
if (cache.containsKey(IOConstants.TAG_EQSETBONUS))
{
for (final String line : cache.get(IOConstants.TAG_EQSETBONUS))
{
parseEquipSetTempBonusLine(line);
}
}
if (cache.containsKey(IOConstants.TAG_AGESET))
{
for (final String line : cache.get(IOConstants.TAG_AGESET))
{
parseAgeSet(line);
}
}
if (cache.containsKey(IOConstants.TAG_CHRONICLE_ENTRY))
{
for (final String line : cache.get(IOConstants.TAG_CHRONICLE_ENTRY))
{
parseChronicleEntryLine(line);
}
}
if (cache.containsKey(IOConstants.TAG_SUPPRESS_BIO_FIELDS))
{
for (final String line : cache.get(IOConstants.TAG_SUPPRESS_BIO_FIELDS))
{
parseSupressBioFieldsLine(line);
}
}
}
/*
* ###############################################################
* System Information methods
* ###############################################################
*/
private void checkDisplayListsHappy() throws PCGParseException
{
if (!Globals.displayListsHappy())
{
throw new PCGParseException("parseCampaignLines", "N/A", //$NON-NLS-1$ //$NON-NLS-2$
LanguageBundle
.getString("Exceptions.PCGenParser.NoCampaignInfo")); //$NON-NLS-1$
}
}
/**
* Retrieve a list of campaigns named on the supplied lines.
* @param lines The campaign lines from the PCG file.
* @param gameModeName The name of the charater's game mode.
* @return The list of campaigns.
* @throws PCGParseException
*/
private List<Campaign> getCampaignList(List<String> lines, String gameModeName)
throws PCGParseException
{
final List<Campaign> campaigns = new ArrayList<>();
PCGTokenizer tokens;
for (final String line : lines)
{
try
{
tokens = new PCGTokenizer(line);
}
catch (PCGParseException pcgpex)
{
/*
* Campaigns are critical for characters,
* need to stop the load process
*
* Thomas Behr 14-08-02
*/
throw new PCGParseException(
"parseCampaignLines", line, pcgpex.getMessage()); //$NON-NLS-1$
}
for (PCGElement element : tokens.getElements())
{
String sourceKey = SourceMigration.getNewSourceKey(element.getText(), pcgenVersion, gameModeName);
final Campaign aCampaign =
Globals.getCampaignKeyed(sourceKey);
if (aCampaign != null)
{
campaigns.add(aCampaign);
}
}
}
return campaigns;
}
private void parseCatchPhraseLine(final String line)
{
thePC.setPCAttribute(PCAttribute.CATCHPHRASE, EntityEncoder.decode(line
.substring(IOConstants.TAG_CATCHPHRASE.length() + 1)));
}
private void parseCharacterAssetLine(final String line)
{
thePC.setStringFor(PCStringKey.ASSETS, EntityEncoder.decode(line
.substring(IOConstants.TAG_CHARACTERASSET.length() + 1)));
}
private void parseCharacterCompLine(final String line)
{
thePC.setStringFor(PCStringKey.COMPANIONS, EntityEncoder.decode(line
.substring(IOConstants.TAG_CHARACTERCOMP.length() + 1)));
}
private void parseCharacterDescLine(final String line)
{
thePC.setPCAttribute(NotePCAttribute.DESCRIPTION, EntityEncoder.decode(line
.substring(IOConstants.TAG_CHARACTERDESC.length() + 1)));
}
private void parseCharacterMagicLine(final String line)
{
thePC.setStringFor(PCStringKey.MAGIC, EntityEncoder.decode(line
.substring(IOConstants.TAG_CHARACTERMAGIC.length() + 1)));
}
private void parseCharacterDmNotesLine(final String line)
{
thePC.setStringFor(PCStringKey.GMNOTES, EntityEncoder.decode(line
.substring(IOConstants.TAG_CHARACTERDMNOTES.length() + 1)));
}
/*
* ###############################################################
* Character Bio methods
* ###############################################################
*/
private void parseCharacterNameLine(final String line)
{
thePC.setPCAttribute(PCAttribute.NAME, EntityEncoder.decode(line.substring(IOConstants.TAG_CHARACTERNAME
.length() + 1)));
}
private void parseCityLine(final String line)
{
thePC.setPCAttribute(PCAttribute.RESIDENCE, EntityEncoder.decode(line.substring(IOConstants.TAG_CITY
.length() + 1)));
}
private void parseClassAbilitiesLevelLine(final String line,
final List<PCLevelInfo> pcLevelInfoList)
{
final PCGTokenizer tokens;
try
{
tokens = new PCGTokenizer(line);
}
catch (PCGParseException pcgpex)
{
final String message =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.IllegalClassAbility" //$NON-NLS-1$
, line, pcgpex.getMessage());
warnings.add(message);
return;
}
int level = -1;
PCClass aPCClass = null;
String tag;
PCGElement element;
PCLevelInfo pcl = null;
final Iterator<PCGElement> it = tokens.getElements().iterator();
// the first element defines the class key name and level
// eg: Cleric=4
if (it.hasNext())
{
element = it.next();
final int index = element.getText().indexOf('=');
if (index < 0)
{
final String message =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.InvalidClassLevel", //$NON-NLS-1$
element.getText());
warnings.add(message);
return;
}
final String classKeyName =
EntityEncoder.decode(element.getText().substring(0, index));
aPCClass = thePC.getClassKeyed(classKeyName);
if (aPCClass == null)
{
final String message =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.ClassNotFound", //$NON-NLS-1$
classKeyName);
warnings.add(message);
return;
}
try
{
level =
Integer
.parseInt(element.getText().substring(index + 1));
}
catch (NumberFormatException nfe)
{
final String message =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.InvalidClassLevel", //$NON-NLS-1$
element.getText());
warnings.add(message);
return;
}
if (level < 1)
{
final String message =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.InvalidClassLevel", //$NON-NLS-1$
element.getText());
warnings.add(message);
return;
}
for (PCLevelInfo info : pcLevelInfoList)
{
if (classKeyName.equalsIgnoreCase(info.getClassKeyName())
&& level == info.getClassLevel())
{
pcl = info;
break;
}
}
if (pcl == null)
{
pcl = thePC.addLevelInfo(classKeyName);
pcl.setClassLevel(level);
}
else
{
thePC.addLevelInfo(pcl);
}
pcl.setSkillPointsRemaining(0);
}
String specialAbilityName;
SpecialAbility specialAbility;
while (it.hasNext())
{
element = it.next();
tag = element.getName();
if (IOConstants.TAG_SUBSTITUTIONLEVEL.equals(tag))
{
final String substitutionClassKeyName =
EntityEncoder.decode(element.getText());
SubstitutionClass aSubstitutionClass =
aPCClass
.getSubstitutionClassKeyed(substitutionClassKeyName);
if (aSubstitutionClass == null)
{
final String message =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.ClassNotFound", //$NON-NLS-1$
substitutionClassKeyName);
warnings.add(message);
return;
}
SubstitutionLevelSupport.applyLevelArrayModsToLevel(
aSubstitutionClass, aPCClass, level, thePC);
thePC.setSubstitutionClassName(thePC.getActiveClassLevel(aPCClass, level),
substitutionClassKeyName);
}
else if (IOConstants.TAG_HITPOINTS.equals(tag))
{
try
{
PCClassLevel classLevel =
thePC.getActiveClassLevel(aPCClass, level - 1);
thePC.setHP(classLevel, Integer.valueOf(element.getText()));
}
catch (NumberFormatException nfe)
{
final String message =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.InvalidHP", //$NON-NLS-1$
tag, element.getText());
warnings.add(message);
}
}
else if (IOConstants.TAG_SAVES.equals(tag))
{
for (final PCGElement child : element.getChildren())
{
final String dString =
EntityEncoder.decode(child.getText());
if (dString.startsWith(IOConstants.TAG_BONUS + IOConstants.TAG_SEPARATOR))
{
String bonusString = dString.substring(6);
int pipeLoc = bonusString.indexOf('|');
if (pipeLoc != -1)
{
CDOMObject target = aPCClass;
String potentialInt =
bonusString.substring(0, pipeLoc);
try
{
int bonusLevel = Integer.parseInt(potentialInt);
if (bonusLevel > 0)
{
target =
thePC.getActiveClassLevel(aPCClass,
bonusLevel);
}
bonusString =
bonusString.substring(pipeLoc + 1);
}
catch (NumberFormatException e)
{
//OK (no level embedded in file)
if (level > 0)
{
target =
thePC.getActiveClassLevel(aPCClass,
level);
}
}
BonusAddition.applyBonus(bonusString, "", thePC,
target);
}
}
}
}
else if (IOConstants.TAG_SPECIALTIES.equals(tag))
{
for (final PCGElement child : element.getChildren())
{
thePC.setAssoc(aPCClass, AssociationKey.SPECIALTY,
EntityEncoder.decode(child.getText()));
}
}
else if (IOConstants.TAG_SPECIALABILITIES.equals(tag))
{
for (PCGElement child : element.getChildren())
{
specialAbilityName = EntityEncoder.decode(child.getText());
if (pcgenVersion[0] <= 5 && pcgenVersion[1] <= 5
&& pcgenVersion[2] < 6)
{
if (specialAbilityName.equals("Turn Undead")) //$NON-NLS-1$
{
parseFeatLine("FEAT:Turn Undead|TYPE:SPECIAL.TURNUNDEAD|DESC:"); //$NON-NLS-1$
continue;
}
else if (specialAbilityName.equals("Rebuke Undead")) //$NON-NLS-1$
{
parseFeatLine("FEAT:Rebuke Undead|TYPE:SPECIAL.TURNUNDEAD|DESC:"); //$NON-NLS-1$
continue;
}
}
specialAbility = new SpecialAbility(specialAbilityName);
CDOMObject target = aPCClass;
if (level > 0)
{
target = thePC.getActiveClassLevel(aPCClass, level);
}
if (!thePC.hasSpecialAbility(specialAbilityName))
{
thePC.addUserSpecialAbility(specialAbility, target);
}
}
}
else if (tag.equals(IOConstants.TAG_LEVELABILITY))
{
parseLevelAbilityInfo(element, aPCClass, level);
}
else if (tag.equals(IOConstants.TAG_ADDTOKEN))
{
parseAddTokenInfo(element,
thePC.getActiveClassLevel(aPCClass, level));
}
//
// abbrev=score
//
else if (tag.equals(IOConstants.TAG_PRESTAT) || tag.equals(IOConstants.TAG_POSTSTAT))
{
boolean isPre = false;
if (tag.equals(IOConstants.TAG_PRESTAT))
{
isPre = true;
}
final int idx = element.getText().indexOf('=');
if (idx > 0)
{
String statAbb = element.getText().substring(0, idx);
final PCStat pcstat =
Globals.getContext().getReferenceContext().silentlyGetConstructedCDOMObject(
PCStat.class, statAbb);
if (pcstat != null)
{
try
{
thePC.saveStatIncrease(
pcstat,
Integer.parseInt(element.getText().substring(
idx + 1)), isPre);
}
catch (NumberFormatException nfe)
{
final String msg =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.InvalidStatMod", //$NON-NLS-1$
tag, element.getText());
warnings.add(msg);
}
}
else
{
final String msg =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.UnknownStat", //$NON-NLS-1$
tag, element.getText());
warnings.add(msg);
}
}
else
{
final String msg =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.MissingEquals", //$NON-NLS-1$
tag, element.getText());
warnings.add(msg);
}
}
else if ((pcl != null) && IOConstants.TAG_SKILLPOINTSGAINED.equals(tag))
{
pcl.setFixedSkillPointsGained(Integer.parseInt(element
.getText()));
}
else if ((pcl != null) && IOConstants.TAG_SKILLPOINTSREMAINING.equals(tag))
{
pcl.setSkillPointsRemaining(Integer.parseInt(element.getText()));
}
else if (IOConstants.TAG_DATA.equals(tag))
{
// TODO
// for now it's ok to ignore it!
}
else
{
final String msg =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.UnknownTag", //$NON-NLS-1$
tag, element.getText());
warnings.add(msg);
}
}
// TODO:
// process data
//
// need to add some consistency checks here to avoid
// - duplicate entries for one and the same class/level pair
// - missing entries for a given class/level pair
}
private void parseAddTokenInfo(PCGElement element, CDOMObject cdo)
{
Iterator<PCGElement> it2 = element.getChildren().iterator();
if (!it2.hasNext())
{
warnings.add(cdo.getDisplayName() + "(" + cdo.getClass().getName()
+ ")\nInvalid save structure in ADD:");
return;
}
PCGElement addType = it2.next();
String name = addType.getName();
String dString = EntityEncoder.decode(addType.getText());
List<PersistentTransitionChoice<?>> addList =
cdo.getListFor(ListKey.ADD);
if (addList == null)
{
warnings.add(cdo.getDisplayName() + "(" + cdo.getClass().getName()
+ ")\nCould not find any ADD: " + name + "|" + dString);
return;
}
boolean found = false;
for (PersistentTransitionChoice<?> tc : addList)
{
found |= processTransitionChoice(cdo, it2, name, dString, tc);
}
if (!found)
{
warnings.add(cdo.getDisplayName() + "(" + cdo.getClass().getName()
+ ")\nCould not find matching ADD: " + name + "|" + dString);
}
}
private <T> boolean processTransitionChoice(CDOMObject cdo,
Iterator<PCGElement> it2, String name, String dString,
PersistentTransitionChoice<T> tc)
{
SelectableSet<? extends T> choices = tc.getChoices();
if (dString.equals(choices.getLSTformat()))
{
//Match
while (it2.hasNext())
{
String choice = EntityEncoder.decode(it2.next().getText());
Object obj = tc.decodeChoice(Globals.getContext(), choice);
if (obj == null)
{
warnings.add(cdo.getDisplayName() + "("
+ cdo.getClass().getName() + ")\nCould not decode "
+ choice + " for ADD: " + name + "|" + dString);
}
else
{
tc.restoreChoice(thePC, cdo, tc.castChoice(obj));
}
}
return true;
}
else
{
return false;
}
}
/*
* ###############################################################
* Character Class(es) methods
* ###############################################################
*/
private void parseClassLine(final String line) throws PCGParseException
{
final PCGTokenizer tokens;
try
{
tokens = new PCGTokenizer(line);
}
catch (PCGParseException pcgpex)
{
/*
* Classes are critical for characters,
* need to stop the load process
*
* Thomas Behr 14-08-02
*/
throw new PCGParseException(
"parseClassLine", line, pcgpex.getMessage()); //$NON-NLS-1$
}
PCClass aPCClass = null;
String tag;
PCGElement element;
final Iterator<PCGElement> it = tokens.getElements().iterator();
// the first element defines the class key name!!!
if (it.hasNext())
{
element = it.next();
String classKey = EntityEncoder.decode(element.getText());
// First check for an existing class, say from a racial casting ability
aPCClass = thePC.getClassKeyed(classKey);
if (aPCClass == null)
{
aPCClass =
Globals.getContext().getReferenceContext().silentlyGetConstructedCDOMObject(
PCClass.class, classKey);
if (aPCClass != null)
{
// Icky: Need to redesign the way classes work!
// Icky: Having to clone the class here is UGLY!
aPCClass = aPCClass.clone();
}
else
{
final String msg =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.CouldntAddClass", //$NON-NLS-1$
element.getText());
warnings.add(msg);
return;
}
}
}
int level = -1;
int skillPool = -1;
String subClassKey = Constants.NONE;
while (it.hasNext())
{
element = it.next();
tag = element.getName();
if (IOConstants.TAG_SUBCLASS.equals(tag))
{
subClassKey = EntityEncoder.decode(element.getText());
if ((!subClassKey.isEmpty())
&& !subClassKey.equals(Constants.NONE))
{
SubClass sc = aPCClass.getSubClassKeyed(subClassKey);
if (sc == null)
{
if (subClassKey.equals(aPCClass.getKeyName()))
{
subClassKey = Constants.NONE;
}
else
{
final String msg =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.InvalidSubclass", //$NON-NLS-1$
element.getText());
warnings.add(msg);
}
}
}
}
if (IOConstants.TAG_LEVEL.equals(tag))
{
try
{
level = Integer.parseInt(element.getText());
}
catch (NumberFormatException nfe)
{
final String msg =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.InvalidLevel", //$NON-NLS-1$
element.getText());
warnings.add(msg);
}
}
else if (IOConstants.TAG_SKILLPOOL.equals(tag))
{
try
{
skillPool = Integer.parseInt(element.getText());
}
catch (NumberFormatException nfe)
{
final String msg =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.InvalidSkillPool", //$NON-NLS-1$
element.getText());
warnings.add(msg);
}
}
else if (IOConstants.TAG_CANCASTPERDAY.equals(tag))
{
// TODO
}
else if (IOConstants.TAG_SPELLBASE.equals(tag))
{
final String spellBase =
EntityEncoder.decode(element.getText());
if (!Constants.NONE.equals(spellBase))
{
Globals.getContext().unconditionallyProcess(aPCClass,
"SPELLSTAT", spellBase);
}
}
else if (IOConstants.TAG_PROHIBITED.equals(tag))
{
String prohib = EntityEncoder.decode(element.getText());
StringTokenizer st =
new StringTokenizer(prohib, Constants.COMMA);
while (st.hasMoreTokens())
{
String choice = st.nextToken();
if (!"None".equalsIgnoreCase(choice))
{
SpellProhibitor prohibSchool = new SpellProhibitor();
prohibSchool.setType(ProhibitedSpellType.SCHOOL);
prohibSchool.addValue(choice);
SpellProhibitor prohibSubSchool = new SpellProhibitor();
prohibSubSchool.setType(ProhibitedSpellType.SUBSCHOOL);
prohibSubSchool.addValue(choice);
thePC.addProhibitedSchool(prohibSchool, aPCClass);
thePC.addProhibitedSchool(prohibSubSchool, aPCClass);
}
}
}
}
if (level > -1)
{
thePC.addClass(aPCClass);
if (StringUtils.isNotBlank(subClassKey)
&& !subClassKey.equals(Constants.NONE))
{
SubClassApplication
.setSubClassKey(thePC, aPCClass, subClassKey);
}
for (int i = 0; i < level; ++i)
{
PCLevelInfo levelInfo =
thePC.addLevelInfo(aPCClass.getKeyName());
aPCClass.addLevel(false, false, thePC, true);
}
}
//Must process ADD after CLASS is added to the PC
for (PCGElement e : new PCGTokenizer(line).getElements())
{
tag = e.getName();
if (tag.equals(IOConstants.TAG_ADDTOKEN))
{
parseAddTokenInfo(e, aPCClass);
}
}
if (skillPool > -1)
{
thePC.setSkillPool(aPCClass, skillPool);
}
}
/**
* ###############################################################
* Character Deity/Domain methods
* ###############################################################
* @param line
**/
private void parseDeityLine(final String line)
{
final PCGTokenizer tokens;
try
{
tokens = new PCGTokenizer(line);
}
catch (PCGParseException pcgpex)
{
final String msg =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.IllegalDeity", //$NON-NLS-1$
line, pcgpex.getMessage());
warnings.add(msg);
return;
}
final String deityKey =
EntityEncoder.decode(tokens.getElements().get(0).getText());
Deity aDeity =
Globals.getContext().getReferenceContext().silentlyGetConstructedCDOMObject(
Deity.class, deityKey);
if (aDeity != null)
{
thePC.setDeity(aDeity);
}
else if (!Constants.NONE.equals(deityKey))
{
// TODO
// create Deity object from information contained in pcg
// for now issue a warning
final String msg =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.DeityNotFound", //$NON-NLS-1$
deityKey);
warnings.add(msg);
}
}
private void parseDomainLine(final String line)
{
final PCGTokenizer tokens;
try
{
tokens = new PCGTokenizer(line);
}
catch (PCGParseException pcgpex)
{
final String msg =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.IllegalDomain", //$NON-NLS-1$
line, pcgpex.getMessage());
warnings.add(msg);
return;
}
final Iterator<PCGElement> it = tokens.getElements().iterator();
if (it.hasNext())
{
PCGElement element = it.next();
// the first element defines the domain name
final String domainKey = EntityEncoder.decode(element.getText());
final Domain aDomain =
Globals.getContext().getReferenceContext().silentlyGetConstructedCDOMObject(
Domain.class, domainKey);
if ((aDomain == null) && (!Constants.NONE.equals(domainKey)))
{
// TODO
// create Domain object from
// information contained in pcg
// But for now just issue a warning
final String msg =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.DomainNotFound", //$NON-NLS-1$
domainKey);
warnings.add(msg);
}
else if (!thePC.hasDomain(aDomain)
&& (!Constants.NONE.equals(domainKey)))
{
// PC doesn't have the domain, so create a new
// one and add it to the PC domain list
ClassSource source = null;
String fullassoc = null;
while (it.hasNext())
{
element = it.next();
String tag = element.getName();
if (IOConstants.TAG_SOURCE.equals(tag))
{
source =
getDomainSource(sourceElementToString(element));
}
else if (IOConstants.TAG_ASSOCIATEDDATA.equals(tag))
{
if (fullassoc != null)
{
warnings.add("Found multiple selections for Domain: "
+ aDomain.getKeyName());
}
fullassoc = EntityEncoder.decode(element.getText());
}
else if (tag.equals(IOConstants.TAG_DOMAINGRANTS))
{
//Can safely ignore
}
else if (!tag.equals(IOConstants.TAG_ADDTOKEN))
{
final String msg =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.UnknownDomainInfo", //$NON-NLS-1$
tag + ":" + element.getText());
warnings.add(msg);
}
}
if (source == null)
{
warnings.add("Domain not added due to no source: "
+ domainKey);
}
else
{
domainInputFacet.importSelection(thePC.getCharID(), aDomain, fullassoc, source);
DomainApplication.applyDomain(thePC, aDomain);
}
try
{
//Must process ADD after DOMAIN is added to the PC
for (PCGElement e : new PCGTokenizer(line).getElements())
{
String tag = e.getName();
if (tag.equals(IOConstants.TAG_ADDTOKEN))
{
parseAddTokenInfo(e, aDomain);
}
}
}
catch (PCGParseException pcgpex)
{
final String msg =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.IllegalDomain", //$NON-NLS-1$
line, pcgpex.getMessage());
warnings.add(msg);
return;
}
}
else
{
// PC already has this domain
Logging.errorPrintLocalised(
"Errors.PCGenParser.DuplicateDomain", //$NON-NLS-1$
domainKey);
}
}
}
/**
* ###############################################################
* EquipSet Temp Bonuses
* ###############################################################
* @param line
**/
private void parseEquipSetTempBonusLine(final String line)
{
PCGTokenizer tokens;
try
{
tokens = new PCGTokenizer(line);
}
catch (PCGParseException pcgpex)
{
final String msg =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.IllegalEquipSetTempBonus", //$NON-NLS-1$
line, pcgpex.getMessage());
warnings.add(msg);
return;
}
String tag;
String tagString = null;
for (PCGElement element : tokens.getElements())
{
tag = element.getName();
if (IOConstants.TAG_EQSETBONUS.equals(tag))
{
tagString = EntityEncoder.decode(element.getText());
}
}
if (tagString == null)
{
final String msg =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.InvalidEquipSetTempBonus", //$NON-NLS-1$
line);
warnings.add(msg);
return;
}
final EquipSet eSet = thePC.getEquipSetByIdPath(tagString);
if (eSet == null)
{
return;
}
//# EquipSet Temp Bonuses
//EQSETBONUS:0.2|TEMPBONUS:NAME=Haste|TBTARGET:PC|TEMPBONUS:SPELL=Shield of Faith|TBTARGET:PC
final Map<BonusObj, BonusManager.TempBonusInfo> aList =
new IdentityHashMap<>();
for (final PCGElement element : tokens.getElements())
{
tag = element.getName();
if (IOConstants.TAG_TEMPBONUSBONUS.equals(tag))
{
final String aString = EntityEncoder.decode(element.getText());
// Parse aString looking for
// TEMPBONUS and TBTARGET pairs
StringTokenizer aTok =
new StringTokenizer(aString, IOConstants.TAG_SEPARATOR);
if (aTok.countTokens() < 2)
{
continue;
}
String sName = aTok.nextToken();
String tName = aTok.nextToken();
aList.putAll(getBonusFromName(sName, tName));
}
}
eSet.setTempBonusList(aList);
}
private void parseCharacterTypeLine(final String line)
throws PCGParseException
{
final StringTokenizer stok =
new StringTokenizer(
line.substring(IOConstants.TAG_CHARACTERTYPE.length() + 1), IOConstants.TAG_END,
false);
String characterType = stok.nextToken();
if (!SettingsHandler.getGame().getCharacterTypeList()
.contains(characterType))
{
String wantedType = characterType;
characterType = SettingsHandler.getGame().getDefaultCharacterType();
final String message =
"Character type " + wantedType + " not found. Using " + characterType; //$NON-NLS-1$
warnings.add(message);
}
thePC.setCharacterType(characterType);
}
private void parsePreviewSheetLine(final String line)
throws PCGParseException
{
final StringTokenizer stok =
new StringTokenizer(
line.substring(IOConstants.TAG_PREVIEWSHEET.length() + 1), IOConstants.TAG_END,
false);
thePC.setPreviewSheet(stok.nextToken());
}
/*
* ###############################################################
* Character Experience methods
* ###############################################################
*/
private void parseExperienceLine(final String line)
throws PCGParseException
{
final StringTokenizer stok =
new StringTokenizer(
line.substring(IOConstants.TAG_EXPERIENCE.length() + 1), IOConstants.TAG_END, false);
try
{
thePC.setXP(Integer.parseInt(stok.nextToken()));
}
catch (NumberFormatException nfe)
{
throw new PCGParseException(
"parseExperienceLine", line, nfe.getMessage()); //$NON-NLS-1$
}
}
private void parseExperienceTableLine(final String line)
throws PCGParseException
{
final StringTokenizer stok =
new StringTokenizer(
line.substring(IOConstants.TAG_EXPERIENCETABLE.length() + 1), IOConstants.TAG_END,
false);
String xpTableName = stok.nextToken();
if (!SettingsHandler.getGame().getXPTableNames().contains(xpTableName))
{
String wantedName = xpTableName;
xpTableName = SettingsHandler.getGame().getDefaultXPTableName();
final String message =
"XP table " + wantedName + " not found. Using " + xpTableName; //$NON-NLS-1$
warnings.add(message);
}
thePC.setXPTable(xpTableName);
}
private void parseEyeColorLine(final String line)
{
thePC.setEyeColor(EntityEncoder.decode(line.substring(IOConstants.TAG_EYECOLOR
.length() + 1)));
}
/*
* ###############################################################
* Character Ability methods
* ###############################################################
*/
private void parseAbilityLine(final String line)
{
final PCGTokenizer tokens;
try
{
tokens = new PCGTokenizer(line);
}
catch (PCGParseException pcgpex)
{
final String msg =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.IllegalAbility", //$NON-NLS-1$
line, pcgpex.getMessage());
warnings.add(msg);
return;
}
AbilityCategory category = null;
Nature nature = Nature.NORMAL;
String abilityCat = null;
Ability ability = null;
String abilityKey = "";
String missingCat = null;
final Iterator<PCGElement> it = tokens.getElements().iterator();
// the first element defines the AbilityCategory key name
if (it.hasNext())
{
final PCGElement element = it.next();
final String categoryKey = EntityEncoder.decode(element.getText());
category =
SettingsHandler.getGame().getAbilityCategory(categoryKey);
if (category == null)
{
missingCat = categoryKey;
}
}
// The next element will be the nature
if (it.hasNext())
{
final PCGElement element = it.next();
final String natureKey = EntityEncoder.decode(element.getText());
nature = Nature.valueOf(natureKey);
}
// The next element will be the ability's innate category
AbilityCategory innateCategory = null;
if (it.hasNext())
{
final PCGElement element = it.next();
abilityCat = EntityEncoder.decode(element.getText());
}
// The next element will be the ability key
if (it.hasNext())
{
final PCGElement element = it.next();
abilityKey = EntityEncoder.decode(element.getText());
// Check for an ability that has been updated.
CategorisedKey categorisedKey =
AbilityMigration.getNewAbilityKey(abilityCat, abilityKey,
pcgenVersion, SettingsHandler.getGame().getName());
abilityCat = categorisedKey.getCategory();
abilityKey = categorisedKey.getKey();
innateCategory =
SettingsHandler.getGame().getAbilityCategory(abilityCat);
if (innateCategory == null)
{
missingCat = abilityCat;
}
if (innateCategory == null || category == null)
{
final String msg =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.AbilityCategoryNotFound", //$NON-NLS-1$
abilityKey, missingCat);
warnings.add(msg);
return;
}
ability =
Globals.getContext().getReferenceContext().silentlyGetConstructedCDOMObject(
Ability.class, innateCategory, abilityKey);
if (ability == null)
{
warnings.add("Unable to Find Ability: " + abilityKey);
return;
}
}
List<String> associations = new ArrayList<>();
List<BonusObj> bonuses = new ArrayList<>();
while (it.hasNext())
{
final PCGElement element = it.next();
final String tag = element.getName();
if (tag.equals(IOConstants.TAG_APPLIEDTO))
{
associations.add(EntityEncoder.decode(element.getText()));
}
else if (IOConstants.TAG_SAVE.equals(tag))
{
final String saveKey = EntityEncoder.decode(element.getText());
// TODO - This never gets written to the file
if (saveKey.startsWith(IOConstants.TAG_BONUS) && (saveKey.length() > 6))
{
final BonusObj aBonus =
Bonus.newBonus(Globals.getContext(),
saveKey.substring(6));
if (aBonus != null)
{
bonuses.add(aBonus);
}
}
else
{
if (Logging.isDebugMode())
{
Logging.debugPrint("Ignoring SAVE:" + saveKey);
}
}
}
}
if (ability != null && category != null && nature != null)
{
CNAbility cna = null;
boolean needError = true;
if (nature == Nature.NORMAL)
{
// If we weren't loading an old character who had feats stored as seperate
// lines, save the feat now.
if (!featsPresent || category != AbilityCategory.FEAT)
{
try
{
cna = CNAbilityFactory.getCNAbility(category, nature, ability);
}
catch (IllegalArgumentException e)
{
Logging.log(Logging.INFO, "Unabe to parse ability line: " + e.getMessage());
}
}
else
{
needError = false;
}
}
else if (nature == Nature.VIRTUAL)
{
cna = CNAbilityFactory.getCNAbility(category, nature, ability);
}
if (cna == null)
{
if (needError)
{
warnings.add("Unable to build Ability: " + ability);
}
}
else
{
if (ability.getSafe(ObjectKey.MULTIPLE_ALLOWED))
{
for (String appliedToKey : associations)
{
String[] assoc =
appliedToKey.split(Constants.COMMA, -1);
for (String string : assoc)
{
CNAbilitySelection cnas =
new CNAbilitySelection(cna, string);
try
{
if (nature == Nature.VIRTUAL)
{
thePC.addSavedAbility(cnas,
UserSelection.getInstance(),
UserSelection.getInstance());
}
else
{
thePC.addAbility(cnas,
UserSelection.getInstance(),
UserSelection.getInstance());
}
}
catch (IllegalArgumentException e)
{
Logging.errorPrint("PCGVer2Parser.parseAbilityLine failed", e);
warnings.add(cna + " with selection: " + string
+ " is no longer valid.");
}
}
}
}
else
{
if (associations != null && !associations.isEmpty())
{
warnings.add(cna + " found with selections: "
+ associations + " but is MULT:NO in the data");
}
CNAbilitySelection cnas = new CNAbilitySelection(cna);
if (nature == Nature.VIRTUAL)
{
thePC.addSavedAbility(cnas,
UserSelection.getInstance(),
UserSelection.getInstance());
}
else
{
thePC.addAbility(cnas, UserSelection.getInstance(),
UserSelection.getInstance());
}
}
for (BonusObj b : bonuses)
{
thePC.addSaveableBonus(b, cna.getAbility());
}
}
}
}
/*
* ###############################################################
* Character Feats methods
* ###############################################################
*/
private void parseFeatLine(final String line)
{
final PCGTokenizer tokens;
try
{
tokens = new PCGTokenizer(line);
}
catch (PCGParseException pcgpex)
{
final String msg =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.IllegalFeat", //$NON-NLS-1$
line, pcgpex.getMessage());
warnings.add(msg);
return;
}
final Iterator<PCGElement> it = tokens.getElements().iterator();
// the first element defines the Ability key name
if (it.hasNext())
{
final PCGElement element = it.next();
final String abilityKey = EntityEncoder.decode(element.getText());
/* First, check to see if the PC already has this ability. If so,
* then we just need to mod it. Otherwise we need to create a new
* one and add it using non-aggregate (when using aggregate, we
* get clones of the PCs actual feats, which don't get saved or
* preserved) */
Ability anAbility =
Globals.getContext().getReferenceContext().silentlyGetConstructedCDOMObject(
Ability.class, AbilityCategory.FEAT, abilityKey);
if (anAbility == null)
{
final String msg =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.CouldntAddAbility", //$NON-NLS-1$
abilityKey);
warnings.add(msg);
return;
}
CNAbility pcAbility =
CNAbilityFactory.getCNAbility(AbilityCategory.FEAT,
Nature.NORMAL, anAbility);
if (!anAbility.getSafe(ObjectKey.MULTIPLE_ALLOWED))
{
thePC.addAbility(new CNAbilitySelection(pcAbility),
UserSelection.getInstance(), UserSelection.getInstance());
}
parseFeatsHandleAppliedToAndSaveTags(it, pcAbility);
featsPresent = true;
}
}
private void parseFeatPoolLine(final String line)
{
try
{
double featPool =
Double
.parseDouble(line.substring(IOConstants.TAG_FEATPOOL.length() + 1));
// In earlier versions the featpool included the bonus, so we need to counter it
if (compareVersionTo(new int[]{5, 11, 1}) < 0)
{
calcFeatPoolAfterLoad = true;
baseFeatPool = featPool;
}
else
{
thePC.setUserPoolBonus(AbilityCategory.FEAT, new BigDecimal(featPool));
}
}
catch (NumberFormatException nfe)
{
final String msg =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.IllegalFeatPool", //$NON-NLS-1$
line);
warnings.add(msg);
}
}
private void parseUserPoolLine(final String line)
{
final PCGTokenizer tokens;
try
{
tokens = new PCGTokenizer(line);
}
catch (PCGParseException pcgpex)
{
final String msg =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.IllegalAbilityPool", //$NON-NLS-1$
line, pcgpex.getMessage());
warnings.add(msg);
return;
}
final Iterator<PCGElement> it = tokens.getElements().iterator();
final String cat = EntityEncoder.decode(it.next().getText());
final AbilityCategory category =
SettingsHandler.getGame().getAbilityCategory(cat);
try
{
thePC.setUserPoolBonus(category,
new BigDecimal(it.next().getText()));
}
catch (NumberFormatException nfe)
{
final String msg =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.IllegalAbilityPool", //$NON-NLS-1$
line);
warnings.add(msg);
}
}
private void parseFeatsHandleAppliedToAndSaveTags(
final Iterator<PCGElement> it, final CNAbility cna)
{
Ability aFeat = cna.getAbility();
while (it.hasNext())
{
final PCGElement element = it.next();
final String tag = element.getName();
if (IOConstants.TAG_APPLIEDTO.equals(tag))
{
final String appliedToKey =
EntityEncoder.decode(element.getText());
if (aFeat.getSafe(ObjectKey.MULTIPLE_ALLOWED))
{
String[] assoc = appliedToKey.split(Constants.COMMA, -1);
for (String string : assoc)
{
CNAbilitySelection cnas =
new CNAbilitySelection(cna, string);
if (cna.getNature() == Nature.VIRTUAL)
{
thePC.addSavedAbility(cnas,
UserSelection.getInstance(),
UserSelection.getInstance());
}
else
{
thePC.addAbility(cnas, UserSelection.getInstance(),
UserSelection.getInstance());
}
}
}
}
else if (IOConstants.TAG_SAVE.equals(tag))
{
final String saveKey = EntityEncoder.decode(element.getText());
if (saveKey.startsWith(IOConstants.TAG_BONUS) && (saveKey.length() > 6))
{
final BonusObj aBonus =
Bonus.newBonus(Globals.getContext(),
saveKey.substring(6));
if (aBonus != null)
{
thePC.addSaveableBonus(aBonus, aFeat);
}
}
else
{
if (Logging.isDebugMode())
{
Logging.debugPrint("Ignoring SAVE:" + saveKey);
}
}
}
else if (tag.equals(IOConstants.TAG_LEVELABILITY))
{
parseLevelAbilityInfo(element, aFeat);
}
else if (tag.equals(IOConstants.TAG_ADDTOKEN))
{
parseAddTokenInfo(element, aFeat);
}
}
}
/*
* ###############################################################
* Character Follower methods
* ###############################################################
*/
private void parseFollowerLine(final String line)
{
final PCGTokenizer tokens;
try
{
tokens = new PCGTokenizer(line);
}
catch (PCGParseException pcgpex)
{
final String msg =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.IllegalFollower", //$NON-NLS-1$
line, pcgpex.getMessage());
warnings.add(msg);
return;
}
final Follower aFollower =
new Follower(Constants.EMPTY_STRING, Constants.EMPTY_STRING,
null);
for (final PCGElement element : tokens.getElements())
{
final String tag = element.getName();
if (IOConstants.TAG_FOLLOWER.equals(tag))
{
aFollower.setName(EntityEncoder.decode(element.getText()));
}
else if (IOConstants.TAG_TYPE.equals(tag))
{
String cType = EntityEncoder.decode(element.getText());
CompanionList cList =
Globals.getContext().getReferenceContext()
.silentlyGetConstructedCDOMObject(
CompanionList.class, cType);
if (cList == null)
{
Logging.errorPrint("Cannot find CompanionList: " + cType);
}
else
{
aFollower.setType(cList);
}
}
else if (IOConstants.TAG_RACE.equals(tag))
{
String raceText = EntityEncoder.decode(element.getText());
Race r =
Globals.getContext().getReferenceContext()
.silentlyGetConstructedCDOMObject(Race.class,
raceText);
if (r == null)
{
Logging.errorPrint("Cannot find Race: " + raceText);
}
else
{
aFollower.setRace(r);
}
}
else if (IOConstants.TAG_HITDICE.equals(tag))
{
try
{
aFollower.setUsedHD(Integer.parseInt(element.getText()));
}
catch (NumberFormatException nfe)
{
// nothing we can do about it
}
}
else if (IOConstants.TAG_FILE.equals(tag))
{
String inputFileName = EntityEncoder.decode(element.getText());
String masterFileName = makeFilenameAbsolute(inputFileName);
if (masterFileName == null)
{
final String msg =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.CantFindFollower", //$NON-NLS-1$
inputFileName);
warnings.add(msg);
}
else
{
aFollower.setFileName(masterFileName);
}
}
}
if (!Constants.EMPTY_STRING.equals(aFollower.getFileName())
&& !Constants.EMPTY_STRING.equals(aFollower.getName())
&& aFollower.getType() != null
&& !Constants.EMPTY_STRING.equals(aFollower.getType().toString()))
{
thePC.addFollower(aFollower);
}
}
private void parseGameMode(final String line) throws PCGParseException
{
final String requestedMode = line.substring(IOConstants.TAG_GAMEMODE.length() + 1);
final GameMode currentGameMode = SettingsHandler.getGame();
final String currentMode = currentGameMode.getName();
if (!requestedMode.equals(currentMode))
{
final String msg =
LanguageBundle.getFormattedString(
"Exceptions.PCGenParser.WrongGameMode", //$NON-NLS-1$
requestedMode, currentMode);
throw new PCGParseException("ParseGameMode", line, msg); //$NON-NLS-1$
}
}
private void parseGenderLine(final String line)
{
String genderString =
EntityEncoder.decode(line.substring(IOConstants.TAG_GENDER.length() + 1));
Gender gender;
if ("M".equals(genderString))
{
gender = Gender.Male;
}
else if ("F".equals(genderString))
{
gender = Gender.Female;
}
else
{
try
{
gender = Gender.getGenderByName(genderString);
}
catch (IllegalArgumentException e)
{
gender = Gender.getDefaultValue();
final String msg =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.IllegalGender", //$NON-NLS-1$
line);
warnings.add(msg);
}
}
thePC.setGender(gender);
}
/**
* # HTML Output Sheet location
*
* @param line
*/
private void parseHTMLOutputSheetLine(final String line)
{
String aFileName =
EntityEncoder.decode(line.substring(IOConstants.TAG_HTMLOUTPUTSHEET
.length() + 1));
if (aFileName.length() <= 0)
{
aFileName =
SettingsHandler.getSelectedCharacterHTMLOutputSheet(thePC);
}
thePC.setSelectedCharacterHTMLOutputSheet(aFileName);
}
private void parseHairColorLine(final String line)
{
thePC.setPCAttribute(PCAttribute.HAIRCOLOR, EntityEncoder.decode(line.substring(IOConstants.TAG_HAIRCOLOR
.length() + 1)));
}
private void parseHairStyleLine(final String line)
{
thePC.setPCAttribute(PCAttribute.HAIRSTYLE, EntityEncoder.decode(line.substring(IOConstants.TAG_HAIRSTYLE
.length() + 1)));
}
private void parseHandedLine(final String line)
{
String handed =
EntityEncoder.decode(line.substring(IOConstants.TAG_HANDED.length() + 1));
Handed h;
try
{
h = Handed.getHandedByName(handed);
}
catch (IllegalArgumentException e)
{
h = Handed.getDefaultValue();
final String msg =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.IllegalHandedness", //$NON-NLS-1$
line, h);
warnings.add(msg);
}
thePC.setHanded(h);
}
private void parseHeightLine(final String line)
{
try
{
thePC
.setHeight(Integer.parseInt(line.substring(IOConstants.TAG_HEIGHT.length() + 1)));
}
catch (NumberFormatException nfe)
{
final String msg =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.IllegalHeight", //$NON-NLS-1$
line);
warnings.add(msg);
}
}
private void parseInterestsLine(final String line)
{
thePC.setPCAttribute(PCAttribute.INTERESTS, EntityEncoder.decode(line.substring(IOConstants.TAG_INTERESTS
.length() + 1)));
}
private void parseKitLine(final String line)
{
final StringTokenizer stok =
new StringTokenizer(line.substring(IOConstants.TAG_KIT.length() + 1),
IOConstants.TAG_SEPARATOR, false);
if (stok.countTokens() != 2)
{
// TODO This if switch currently does nothing?
}
/** final String region = */
stok.nextToken(); //TODO: Is this intended to be thrown away? The value is never used.
/** final String kit = stok.nextToken(); */
final Kit aKit =
Globals.getContext().getReferenceContext().silentlyGetConstructedCDOMObject(
Kit.class, line.substring(IOConstants.TAG_KIT.length() + 1));
if (aKit == null)
{
final String msg =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.KitNotFound", //$NON-NLS-1$
line);
warnings.add(msg);
return;
}
thePC.addKit(aKit);
}
/*
* ###############################################################
* Character Languages methods
* ###############################################################
*/
private void parseLanguageLine(final String line)
{
final PCGTokenizer tokens;
try
{
tokens = new PCGTokenizer(line);
}
catch (PCGParseException pcgpex)
{
final String msg =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.IllegalLanguage", //$NON-NLS-1$
line, pcgpex.getMessage());
warnings.add(msg);
return;
}
for (PCGElement element : tokens.getElements())
{
final Language aLang =
Globals.getContext().getReferenceContext()
.silentlyGetConstructedCDOMObject(Language.class,
EntityEncoder.decode(element.getText()));
if (aLang == null)
{
final String message =
"No longer speaks language: " + element.getText(); //$NON-NLS-1$
warnings.add(message);
continue;
}
cachedLanguages.add(aLang);
}
}
/**
* # Load companions with master?
* @param line
**/
private void parseLoadCompanionLine(final String line)
{
thePC.setLoadCompanion(line.endsWith(IOConstants.VALUE_Y));
}
private void parseLocationLine(final String line)
{
thePC.setPCAttribute(PCAttribute.LOCATION, EntityEncoder.decode(line.substring(IOConstants.TAG_LOCATION
.length() + 1)));
}
private void parseMasterLine(final String line)
{
final PCGTokenizer tokens;
try
{
tokens = new PCGTokenizer(line);
}
catch (PCGParseException pcgpex)
{
final String msg =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.IllegalMaster", //$NON-NLS-1$
line, pcgpex.getMessage());
warnings.add(msg);
return;
}
final Follower aMaster =
new Follower(Constants.EMPTY_STRING, Constants.EMPTY_STRING,
null);
for (PCGElement element : tokens.getElements())
{
final String tag = element.getName();
if (IOConstants.TAG_MASTER.equals(tag))
{
aMaster.setName(EntityEncoder.decode(element.getText()));
}
else if (IOConstants.TAG_TYPE.equals(tag))
{
String cType = EntityEncoder.decode(element.getText());
CompanionList cList =
Globals.getContext().getReferenceContext()
.silentlyGetConstructedCDOMObject(
CompanionList.class, cType);
if (cList == null)
{
Logging.errorPrint("Cannot find CompanionList: " + cType);
}
else
{
aMaster.setType(cList);
}
}
else if (IOConstants.TAG_HITDICE.equals(tag))
{
try
{
aMaster.setUsedHD(Integer.parseInt(element.getText()));
}
catch (NumberFormatException nfe)
{
// nothing we can do about it
}
}
else if (IOConstants.TAG_FILE.equals(tag))
{
String inputFileName = EntityEncoder.decode(element.getText());
String masterFileName = makeFilenameAbsolute(inputFileName);
if (masterFileName == null)
{
final String msg =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.CantFindMaster", //$NON-NLS-1$
inputFileName);
warnings.add(msg);
}
else
{
aMaster.setFileName(masterFileName);
}
}
else if (IOConstants.TAG_ADJUSTMENT.equals(tag))
{
aMaster.setAdjustment(Integer.parseInt(element.getText()));
}
}
if (!Constants.EMPTY_STRING.equals(aMaster.getFileName())
&& !Constants.EMPTY_STRING.equals(aMaster.getName())
&& !Constants.EMPTY_STRING.equals(aMaster.getType().toString()))
{
thePC.setMaster(aMaster);
}
}
/**
* Convert the passed in file name to an absolute file name. The file name
* may be relative to the PCG file being loaded, to the PCG directory or
* it may be absolute.
* @param inFileName The file name to be converted.
* @return The absolute file name, or null if the file cannot be found.
*/
private String makeFilenameAbsolute(String inFileName)
{
// Is it relative to this character file?
File pcFile = new File(thePC.getFileName());
File inFile = new File(pcFile.getParentFile(), inFileName);
if (inFile.exists())
{
return inFile.getAbsolutePath();
}
// Is it relative to the PCG directory?
File pcgDir = new File(PCGenSettings.getPcgDir());
inFile = new File(pcgDir, inFileName);
if (inFile.exists())
{
return inFile.getAbsolutePath();
}
// Is it absolute?
inFile = new File(inFileName);
if (inFile.exists())
{
return inFile.getAbsolutePath();
}
// We can't find it!
return null;
}
/*
* ###############################################################
* Character Notes Tab methods
* ###############################################################
*/
private void parseNoteLine(final String line)
{
final PCGTokenizer tokens;
try
{
tokens = new PCGTokenizer(line);
}
catch (PCGParseException pcgpex)
{
final String msg =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.IllegalNotes", //$NON-NLS-1$
line, pcgpex.getMessage());
warnings.add(msg);
return;
}
final NoteItem ni =
new NoteItem(-1, -1, Constants.EMPTY_STRING,
Constants.EMPTY_STRING);
for (PCGElement element : tokens.getElements())
{
final String tag = element.getName();
if (IOConstants.TAG_NOTE.equals(tag))
{
ni.setName(EntityEncoder.decode(element.getText()));
}
else if (IOConstants.TAG_ID.equals(tag))
{
try
{
ni.setIdValue(Integer.parseInt(element.getText()));
}
catch (NumberFormatException nfe)
{
ni.setIdValue(-1);
final String msg =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.InvalidNotes", //$NON-NLS-1$
line);
warnings.add(msg);
break;
}
}
else if (IOConstants.TAG_PARENTID.equals(tag))
{
try
{
ni.setParentId(Integer.parseInt(element.getText()));
}
catch (NumberFormatException nfe)
{
ni.setIdValue(-1);
final String msg =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.InvalidNotes", //$NON-NLS-1$
line);
warnings.add(msg);
break;
}
}
else if (IOConstants.TAG_VALUE.equals(tag))
{
ni.setValue(EntityEncoder.decode(element.getText()));
}
}
if (ni.getId() > -1)
{
thePC.addNotesItem(ni);
}
}
/*
* ###############################################################
* Character Chronicle Entry methods
* ###############################################################
*/
private void parseChronicleEntryLine(final String line)
{
final PCGTokenizer tokens;
try
{
tokens = new PCGTokenizer(line);
}
catch (PCGParseException pcgpex)
{
final String msg =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.IllegalChronicleEntry", //$NON-NLS-1$
line, pcgpex.getMessage());
warnings.add(msg);
return;
}
final ChronicleEntry ce = new ChronicleEntry();
for (PCGElement element : tokens.getElements())
{
final String tag = element.getName();
if (IOConstants.TAG_CHRONICLE_ENTRY.equals(tag))
{
ce.setOutputEntry("Y".equals(element.getText()));
}
else if (IOConstants.TAG_EXPERIENCE.equals(tag))
{
try
{
ce.setXpField(Integer.parseInt(element.getText()));
}
catch (NumberFormatException nfe)
{
ce.setXpField(0);
final String msg =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.InvalidChronicleEntry", //$NON-NLS-1$
line);
warnings.add(msg);
break;
}
}
else if (IOConstants.TAG_CAMPAIGN.equals(tag))
{
ce.setCampaign(EntityEncoder.decode(element.getText()));
}
else if (IOConstants.TAG_ADVENTURE.equals(tag))
{
ce.setAdventure(EntityEncoder.decode(element.getText()));
}
else if (IOConstants.TAG_PARTY.equals(tag))
{
ce.setParty(EntityEncoder.decode(element.getText()));
}
else if (IOConstants.TAG_DATE.equals(tag))
{
ce.setDate(EntityEncoder.decode(element.getText()));
}
else if (IOConstants.TAG_GM.equals(tag))
{
ce.setGmField(EntityEncoder.decode(element.getText()));
}
else if (IOConstants.TAG_CHRONICLE.equals(tag))
{
ce.setChronicle(EntityEncoder.decode(element.getText()));
}
}
thePC.addChronicleEntry(ce);
}
/**
* Biography fields that are to be hidden from output.
* @param line The SUPPRESS_BIO_FIELDS line
*/
private void parseSupressBioFieldsLine(final String line)
{
String fieldNames =
EntityEncoder.decode(line.substring(IOConstants.TAG_SUPPRESS_BIO_FIELDS
.length() + 1));
if (!fieldNames.isEmpty())
{
String[] names = fieldNames.split("\\|");
for (String field : names)
{
thePC.setSuppressBioField(BiographyField.valueOf(field), true);
}
}
}
/**
* # PDF Output Sheet location
* @param line
**/
private void parsePDFOutputSheetLine(final String line)
{
String aFileName =
EntityEncoder
.decode(line.substring(IOConstants.TAG_PDFOUTPUTSHEET.length() + 1));
if (aFileName.length() <= 0)
{
aFileName =
SettingsHandler.getSelectedCharacterPDFOutputSheet(thePC);
}
thePC.setSelectedCharacterPDFOutputSheet(aFileName);
}
private void parsePersonalityTrait1Line(final String line)
{
thePC.setPCAttribute(PCAttribute.PERSONALITY1, EntityEncoder.decode(line
.substring(IOConstants.TAG_PERSONALITYTRAIT1.length() + 1)));
}
private void parsePersonalityTrait2Line(final String line)
{
thePC.setPCAttribute(PCAttribute.PERSONALITY2, EntityEncoder.decode(line
.substring(IOConstants.TAG_PERSONALITYTRAIT2.length() + 1)));
}
private void parsePhobiasLine(final String line)
{
thePC.setPCAttribute(PCAttribute.PHOBIAS, EntityEncoder.decode(line.substring(IOConstants.TAG_PHOBIAS
.length() + 1)));
}
private void parsePlayerNameLine(final String line)
{
thePC.setPCAttribute(PCAttribute.PLAYERSNAME, EntityEncoder.decode(line.substring(IOConstants.TAG_PLAYERNAME
.length() + 1)));
}
private void parsePoolPointsLine(final String line)
{
try
{
final int poolPoints =
Integer
.parseInt(line.substring(IOConstants.TAG_POOLPOINTS.length() + 1));
thePC.setPoolAmount(poolPoints);
thePC.setCostPool(poolPoints);
}
catch (NumberFormatException nfe)
{
final String msg =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.InvalidPoolPoints", //$NON-NLS-1$
line);
warnings.add(msg);
}
}
private void parsePoolPointsLine2(final String line)
{
try
{
thePC.setPointBuyPoints(Integer.parseInt(line
.substring(IOConstants.TAG_POOLPOINTSAVAIL.length() + 1)));
}
catch (NumberFormatException nfe)
{
final String msg =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.InvalidPoolPoints", //$NON-NLS-1$
line);
warnings.add(msg);
}
}
private void parsePortraitLine(final String line)
{
thePC.setPortraitPath(EntityEncoder.decode(line.substring(IOConstants.TAG_PORTRAIT
.length() + 1)));
}
private void parsePortraitThumbnailRectLine(final String line)
{
String[] dim =
line.substring(IOConstants.TAG_PORTRAIT_THUMBNAIL_RECT.length() + 1).split(
",");
Rectangle rect =
new Rectangle(Integer.parseInt(dim[0]),
Integer.parseInt(dim[1]), Integer.parseInt(dim[2]),
Integer.parseInt(dim[3]));
thePC.setPortraitThumbnailRect(rect);
}
private void parseRaceLine(final String line) throws PCGParseException
{
List<PCGElement> elements = new PCGTokenizer(line).getElements();
PCGElement raceElement = elements.get(0);
String raceName = EntityEncoder.decode(raceElement.getText());
// Check for a race key that has been updated.
raceName =
RaceMigration.getNewRaceKey(raceName, pcgenVersion,
SettingsHandler.getGame().getName());
final Race aRace =
Globals.getContext().getReferenceContext().silentlyGetConstructedCDOMObject(
Race.class, raceName);
if (aRace == null)
{
final String msg =
LanguageBundle.getFormattedString(
"Exceptions.PCGenParser.RaceNotFound", //$NON-NLS-1$
raceName);
throw new PCGParseException("parseRaceLine", line, msg); //$NON-NLS-1$
}
String selection = null;
//Yes, start at 1, 0 was the race
for (int i = 1; i < elements.size(); i++)
{
PCGElement thisElement = elements.get(i);
final String aString = thisElement.getName();
if (aString.startsWith(IOConstants.TAG_APPLIEDTO))
{
if (selection != null)
{
warnings.add("Found multiple selections for Race: "
+ aRace.getKeyName());
}
selection = thisElement.getText();
}
else if (!aString.startsWith(IOConstants.TAG_ADDTOKEN))
{
final String msg =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.UnknownRaceInfo", //$NON-NLS-1$
aString + ":" + thisElement.getText());
warnings.add(msg);
}
}
raceInputFacet.importSelection(thePC.getCharID(), aRace, selection);
thePC.setDirty(true);
//Must process ADD after RACE is added to the PC
for (PCGElement e : new PCGTokenizer(line).getElements())
{
String tag = e.getName();
if (tag.equals(IOConstants.TAG_ADDTOKEN))
{
parseAddTokenInfo(e, aRace);
}
}
// TODO
// adjust for more information according to
// PCGVer1Creator.appendRaceLine
}
private void parseFavoredClassLine(final String line)
{
String favClass =
EntityEncoder
.decode(line.substring(IOConstants.TAG_FAVOREDCLASS.length() + 1));
PCClass cl =
Globals.getContext().getReferenceContext().silentlyGetConstructedCDOMObject(
PCClass.class, favClass);
if (cl != null)
{
thePC.addFavoredClass(cl, thePC);
}
}
/*
* ###############################################################
* Character Region methods
* ###############################################################
*/
private void parseRegionLine(final String line)
{
final String r =
EntityEncoder.decode(line.substring(IOConstants.TAG_REGION.length() + 1));
thePC.setRegion(Region.getConstant(r));
}
//this method is obsolete, but left in for backward-compatibility, replaced by parseCityLine()
private void parseResidenceLine(final String line)
{
thePC.setPCAttribute(PCAttribute.RESIDENCE, EntityEncoder.decode(line.substring(IOConstants.TAG_RESIDENCE
.length() + 1)));
thePC.setDirty(true); // trigger a save prompt so that the PCG will be updated
}
/*
* ###############################################################
* Character Skills methods
* ###############################################################
*/
private void parseSkillLine(final String line)
{
final PCGTokenizer tokens;
try
{
tokens = new PCGTokenizer(line);
}
catch (PCGParseException pcgpex)
{
final String message =
"Illegal Skill line ignored: " + line
+ Constants.LINE_SEPARATOR + "Error: "
+ pcgpex.getMessage();
warnings.add(message);
return;
}
Skill aSkill = null;
final Iterator<PCGElement> it = tokens.getElements().iterator();
// the first element defines the skill key name!!!
String skillKey = "";
if (it.hasNext())
{
final PCGElement element = it.next();
skillKey = EntityEncoder.decode(element.getText());
aSkill =
Globals.getContext().getReferenceContext().silentlyGetConstructedCDOMObject(
Skill.class, skillKey);
}
while (it.hasNext())
{
final PCGElement element = it.next();
final String tag = element.getName();
if (IOConstants.TAG_SYNERGY.equals(tag))
{
// TODO
// for now it's ok to ignore it!
}
else if (IOConstants.TAG_OUTPUTORDER.equals(tag))
{
int outputindex = 0;
try
{
outputindex = Integer.parseInt(element.getText());
}
catch (NumberFormatException nfe)
{
// This is not critical.
// Maybe warn the user?
}
if (aSkill != null)
{
thePC.setSkillOrder(aSkill, outputindex);
}
}
else if (IOConstants.TAG_CLASSBOUGHT.equals(tag))
{
PCGElement childClass = null;
PCGElement childRanks = null;
for (PCGElement child : element.getChildren())
{
if (IOConstants.TAG_CLASS.equals(child.getName()))
{
childClass = child;
}
else if (IOConstants.TAG_RANKS.equals(child.getName()))
{
childRanks = child;
}
}
if (childClass == null)
{
final String message =
"Invalid class/ranks specification: " + line;
warnings.add(message);
continue;
}
if (childRanks == null)
{
final String message =
"Invalid class/ranks specification: " + line;
warnings.add(message);
continue;
}
//None for a class is a special key word. It is used when a familiar inherits a skill from its master
PCClass aPCClass = null;
if (!childClass.getText().equals("None")) //$NON-NLS-1$
{
final String childClassKey =
EntityEncoder.decode(childClass.getText());
aPCClass = thePC.getClassKeyed(childClassKey);
if (aPCClass == null)
{
final String message =
"Could not find class: " + childClassKey;
warnings.add(message);
continue;
}
}
if (aSkill == null)
{
// We only need to report this if the skill had ranks.
final String message = "Could not add skill: " + skillKey;
warnings.add(message);
return;
}
try
{
double ranks = Double.parseDouble(childRanks.getText());
SkillRankControl.modRanks(ranks, aPCClass, true, thePC,
aSkill);
}
catch (NumberFormatException nfe)
{
final String message =
"Invalid ranks specification: "
+ childRanks.getText();
warnings.add(message);
continue;
}
}
else if (aSkill != null && IOConstants.TAG_ASSOCIATEDDATA.equals(tag))
{
String key = EntityEncoder.decode(element.getText());
ChoiceManagerList<Object> controller =
ChooserUtilities.getConfiguredController(aSkill, thePC,
null, new ArrayList<>());
if (controller != null)
{
String[] assoc = key.split(Constants.COMMA, -1);
for (String string : assoc)
{
controller.restoreChoice(thePC, aSkill, string);
}
}
else
{
warnings
.add("Failed to find choose controller for skill "
+ aSkill);
}
}
else if (aSkill != null && tag.equals(IOConstants.TAG_LEVELABILITY))
{
parseLevelAbilityInfo(element, aSkill);
}
else if (aSkill != null && tag.equals(IOConstants.TAG_ADDTOKEN))
{
parseAddTokenInfo(element, aSkill);
}
}
}
/**
* # Skills Output order
* @param line
**/
private void parseSkillsOutputOrderLine(final String line)
{
try
{
int orderNum = Integer.parseInt(line
.substring(IOConstants.TAG_SKILLSOUTPUTORDER.length() + 1));
SkillsOutputOrder order = SkillsOutputOrder.values()[orderNum];
// setting is correct in some 6.1.8-dev characters
if (compareVersionTo(new int[]{6, 1, 9}) < 0)
{
order = SkillsOutputOrder.NAME_ASC;
}
thePC.setSkillsOutputOrder(order);
}
catch (NumberFormatException nfe)
{
final String message =
"Illegal Skills Output Order line ignored: " + line;
warnings.add(message);
}
}
/**
* # Skills Filter
* @param line
**/
private void parseSkillFilterLine(final String line)
{
try
{
int value = Integer.parseInt(line
.substring(IOConstants.TAG_SKILLFILTER.length() + 1));
thePC.setSkillFilter(SkillFilter.getByValue(value));
}
catch (NumberFormatException nfe)
{
final String message =
"Illegal Skill Filter line ignored: " + line;
warnings.add(message);
}
}
private void parseSkinColorLine(final String line)
{
thePC.setPCAttribute(PCAttribute.SKINCOLOR, EntityEncoder.decode(line.substring(IOConstants.TAG_SKINCOLOR
.length() + 1)));
}
private void parseSpeechPatternLine(final String line)
{
thePC.setPCAttribute(PCAttribute.SPEECHTENDENCY, EntityEncoder.decode(line
.substring(IOConstants.TAG_SPEECHPATTERN.length() + 1)));
}
/*
* ###############################################################
* Spell Book Information methods
* ###############################################################
*/
/*
* #Spell Book Information
* SPELLBOOK:bookname|TYPE:spellbooktype
*/
private void parseSpellBookLines(final String line)
{
final PCGTokenizer tokens;
try
{
tokens = new PCGTokenizer(line);
}
catch (PCGParseException pcgpex)
{
final String message =
"Illegal Spell book ignored: " + line
+ Constants.LINE_SEPARATOR + "Error: "
+ pcgpex.getMessage();
warnings.add(message);
return;
}
SpellBook aSpellBook = null;
for (PCGElement element : tokens.getElements())
{
final String tag = element.getName();
if (IOConstants.TAG_SPELLBOOK.equals(tag))
{
final String bookName = EntityEncoder.decode(element.getText());
aSpellBook =
new SpellBook(bookName, SpellBook.TYPE_PREPARED_LIST);
}
else if (IOConstants.TAG_TYPE.equals(tag))
{
try
{
aSpellBook.setType(Integer.parseInt(element.getText()));
}
catch (NumberFormatException nfe)
{
// nothing we can do about it
final String message =
"Spell book " + aSpellBook.getName()
+ " had an illegal type: " + element.getText()
+ " in line " + line;
warnings.add(message);
}
}
else if (IOConstants.TAG_AUTOADDKNOWN.equals(tag))
{
if (IOConstants.VALUE_Y.equals(element.getText()))
{
thePC.setSpellBookNameToAutoAddKnown(aSpellBook.getName());
}
}
}
if (aSpellBook == null)
{
warnings
.add("Internal Error: Did not build Spell Book from SPELLBOOK line");
}
else
{
thePC.addSpellBook(aSpellBook);
}
}
/*
* ###############################################################
* Character Spells Information methods
* ###############################################################
*/
private void parseSpellLine(final String line)
{
final PCGTokenizer tokens;
try
{
tokens = new PCGTokenizer(line);
}
catch (PCGParseException pcgpex)
{
final String message =
"Illegal Spell line ignored: " + line
+ Constants.LINE_SEPARATOR + "Error: "
+ pcgpex.getMessage();
warnings.add(message);
return;
}
Spell aSpell = null;
PCClass aPCClass = null;
PObject source = null;
String spellBook = null;
int times = 1;
int spellLevel = 0;
int numPages = 0;
final List<Ability> metaFeats = new ArrayList<>();
int ppCost = -1;
for (final PCGElement element : tokens.getElements())
{
final String tag = element.getName();
if (IOConstants.TAG_SPELLNAME.equals(tag))
{
String spellName =
EntityEncoder.decode(element.getText());
spellName = SpellMigration.getNewSpellKey(spellName, pcgenVersion,
SettingsHandler.getGame().getName());
// either NULL (no spell) a Spell instance,
aSpell = Globals.getContext().getReferenceContext()
.silentlyGetConstructedCDOMObject(Spell.class, spellName);
if (aSpell == null)
{
final String message =
"Could not find spell named: " + spellName;
warnings.add(message);
return;
}
}
else if (IOConstants.TAG_TIMES.equals(tag))
{
try
{
times = Integer.parseInt(element.getText());
}
catch (NumberFormatException nfe)
{
// nothing we can do about it
}
}
else if (IOConstants.TAG_CLASS.equals(tag))
{
final String classKey = EntityEncoder.decode(element.getText());
aPCClass = thePC.getClassKeyed(classKey);
if (aPCClass == null)
{
final String message =
"Invalid class specification: " + classKey;
warnings.add(message);
return;
}
}
else if (IOConstants.TAG_SPELL_BOOK.equals(tag))
{
spellBook = EntityEncoder.decode(element.getText());
}
else if (IOConstants.TAG_SPELLLEVEL.equals(tag))
{
try
{
spellLevel = Integer.parseInt(element.getText());
}
catch (NumberFormatException nfe)
{
// nothing we can do about it
}
}
else if (IOConstants.TAG_SPELLPPCOST.equals(tag))
{
try
{
ppCost = Integer.parseInt(element.getText());
}
catch (NumberFormatException nfe)
{
// nothing we can do about it
}
}
else if (IOConstants.TAG_SPELLNUMPAGES.equals(tag))
{
try
{
numPages = Integer.parseInt(element.getText());
}
catch (NumberFormatException nfe)
{
// nothing we can do about it
}
}
else if (IOConstants.TAG_SOURCE.equals(tag))
{
String typeName = Constants.EMPTY_STRING;
String objectKey = Constants.EMPTY_STRING;
for (final PCGElement child : element.getChildren())
{
final String childTag = child.getName();
if (IOConstants.TAG_TYPE.equals(childTag))
{
typeName = child.getText().toUpperCase();
}
else if (IOConstants.TAG_NAME.equals(childTag))
{
objectKey = child.getText();
}
}
if (IOConstants.TAG_DOMAIN.equals(typeName))
{
Domain domain =
Globals.getContext().getReferenceContext()
.silentlyGetConstructedCDOMObject(DOMAIN_CLASS,
objectKey);
ClassSource cs = thePC.getDomainSource(domain);
if (cs == null)
{
final String message =
"Could not find domain: " + objectKey;
warnings.add(message);
return;
}
source = domain;
}
else
{
// it's either the class, sub-class or a cast-as class
// first see if it's the class
ClassSpellList csl =
Globals.getContext().getReferenceContext()
.silentlyGetConstructedCDOMObject(
ClassSpellList.class, objectKey);
if (((aPCClass != null) && objectKey.equals(aPCClass
.getKeyName()))
|| (aPCClass != null && thePC.getSpellLists(aPCClass)
.contains(csl)))
{
source = aPCClass;
}
else
{
source = thePC.getClassKeyed(objectKey); // see if PC has the class
}
}
}
else if (IOConstants.TAG_FEATLIST.equals(tag))
{
for (PCGElement child : element.getChildren())
{
final String featKey =
EntityEncoder.decode(child.getText());
final Ability anAbility =
Globals.getContext().getReferenceContext()
.silentlyGetConstructedCDOMObject(
Ability.class, AbilityCategory.FEAT,
featKey);
if (anAbility != null)
{
metaFeats.add(anAbility);
}
}
}
}
if ((aPCClass == null) || (spellBook == null))
{
final String message = "Illegal Spell line ignored: " + line;
warnings.add(message);
return;
}
/*
* this can only happen if the source type was NOT DOMAIN!
*/
if (source == null)
{
source = aPCClass;
}
// if (obj instanceof List)
// {
// // find the instance of Spell in this class
// // best suited to this spell
// for (final Spell spell : (ArrayList<Spell>) obj)
// {
// // valid spell has a non-negative spell level
// if ((spell != null)
// && (SpellLevel.getFirstLevelForKey(spell,
// thePC.getSpellLists(source), thePC) >= 0))
// {
// aSpell = spell;
// break;
// }
// }
// if (aSpell == null)
// {
// Logging.errorPrint("Could not resolve spell " + obj.toString());
// }
// }
// if (aSpell == null)
// {
// final String message =
// "Could not find spell named: " + String.valueOf(obj);
// warnings.add(message);
//
// return;
// }
// just to make sure the spellbook is present
thePC.addSpellBook(spellBook);
final SpellBook book = thePC.getSpellBookByName(spellBook);
thePC.calculateKnownSpellsForClassLevel(aPCClass);
final Integer[] spellLevels =
SpellLevel.levelForKey(aSpell, thePC.getSpellLists(source),
thePC);
boolean found = false;
for (int sindex = 0; sindex < spellLevels.length; ++sindex)
{
final int level = spellLevels[sindex];
final int metmagicLevels = totalAddedLevelsFromMetamagic(metaFeats);
if (spellLevel > 0 && spellLevel != (level+metmagicLevels))
{
// Skip spell in class lists that does not match level the character knows it.
continue;
}
if (level < 0)
{
Collection<CDOMReference<Spell>> mods =
source.getListMods(Spell.SPELLS);
if (mods == null)
{
continue;
}
for (CDOMReference<Spell> ref : mods)
{
Collection<Spell> refSpells = ref.getContainedObjects();
Collection<AssociatedPrereqObject> assocs =
source.getListAssociations(Spell.SPELLS, ref);
for (Spell sp : refSpells)
{
if (aSpell.getKeyName().equals(sp.getKeyName()))
{
for (AssociatedPrereqObject apo : assocs)
{
String sb =
apo.getAssociation(AssociationKey.SPELLBOOK);
if (spellBook.equals(sb))
{
found = true;
break;
}
}
}
}
}
continue;
}
found = true;
// do not load auto knownspells into default spellbook
if (spellBook.equals(Globals.getDefaultSpellBook())
&& thePC.getSpellSupport(aPCClass).isAutoKnownSpell(aSpell,
level, false, thePC) && thePC.getAutoSpells())
{
continue;
}
CharacterSpell aCharacterSpell =
thePC.getCharacterSpellForSpell(aPCClass, aSpell, source);
// PC does not have that spell on that classes list
// so we'll need to add it to the list
if (aCharacterSpell == null)
{
aCharacterSpell = new CharacterSpell(source, aSpell);
aCharacterSpell.addInfo(level, times, spellBook);
thePC.addCharacterSpell(aPCClass, aCharacterSpell);
}
SpellInfo aSpellInfo = null;
if (source.getKeyName().equals(aPCClass.getKeyName())
|| !spellBook.equals(Globals.getDefaultSpellBook()))
{
aSpellInfo =
aCharacterSpell.getSpellInfoFor(spellBook, spellLevel);
// This doesn't make sense. What does the
// metaFeats list have to do with this?
if ((aSpellInfo == null) || !metaFeats.isEmpty())
{
aSpellInfo =
aCharacterSpell.addInfo(spellLevel, times,
spellBook);
}
}
if (aSpellInfo != null)
{
if (!metaFeats.isEmpty())
{
aSpellInfo.addFeatsToList(metaFeats);
}
aSpellInfo.setActualPPCost(ppCost);
aSpellInfo.setNumPages(numPages);
book.setNumPagesUsed(book.getNumPagesUsed() + numPages);
book.setNumSpells(book.getNumSpells() + 1);
}
}
// end sindex for loop
if (!found)
{
final String message =
"Could not find spell " + aSpell.getDisplayName() + " in "
+ shortClassName(source) + " "
+ source.getDisplayName();
warnings.add(message);
}
}
private int totalAddedLevelsFromMetamagic(List<Ability> metaFeats)
{
int addedLevels = 0;
for (Ability ability : metaFeats)
{
Integer featAddSpellLevel = ability.get(IntegerKey.ADD_SPELL_LEVEL);
if (featAddSpellLevel != null)
{
addedLevels += featAddSpellLevel;
}
}
return addedLevels;
}
/*
* ###############################################################
* Spell List Information methods
* ###############################################################
*/
/*
* #Spell List Information
* SPELLLIST:sourceclassname|spelllistentry|spelllistentry
*/
private void parseSpellListLines(final String line)
{
final String subLine = line.substring(IOConstants.TAG_SPELLLIST.length() + 1);
final StringTokenizer stok =
new StringTokenizer(subLine, IOConstants.TAG_SEPARATOR, false);
final String classKey = stok.nextToken();
final PCClass aClass = thePC.getClassKeyed(classKey);
AbstractReferenceContext refContext = Globals.getContext().getReferenceContext();
while ((aClass != null) && stok.hasMoreTokens())
{
final String tok = stok.nextToken();
if (tok.startsWith("CLASS."))
{
ClassSpellList csl =
refContext.silentlyGetConstructedCDOMObject(
ClassSpellList.class, tok.substring(6));
thePC.addClassSpellList(csl, aClass);
}
else if (tok.startsWith("DOMAIN."))
{
DomainSpellList dsl =
refContext.silentlyGetConstructedCDOMObject(
DomainSpellList.class, tok.substring(7));
thePC.addClassSpellList(dsl, aClass);
}
else
{
/*
* This is 5.14-ish, but have to try anyway:
*/
ClassSpellList csl =
refContext.silentlyGetConstructedCDOMObject(
ClassSpellList.class, tok);
if (csl == null)
{
DomainSpellList dsl =
refContext.silentlyGetConstructedCDOMObject(
DomainSpellList.class, tok);
if (dsl != null)
{
thePC.addClassSpellList(dsl, aClass);
}
}
else
{
thePC.addClassSpellList(csl, aClass);
}
}
}
}
/*
* ###############################################################
* Character Attributes methods
* ###############################################################
*/
private void parseStatLine(final String line) throws PCGParseException
{
final PCGTokenizer tokens;
try
{
tokens = new PCGTokenizer(line);
}
catch (PCGParseException pcgpex)
{
/*
* Ability scores are critical for characters,
* need to stop the load process
*
* Thomas Behr 09-09-02
*/
throw new PCGParseException(
"parseStatLine", line, pcgpex.getMessage()); //$NON-NLS-1$
}
final Iterator<PCGElement> it = tokens.getElements().iterator();
if (it.hasNext())
{
PCGElement element = it.next();
final String statName = element.getText();
PCStat stat =
Globals.getContext().getReferenceContext().silentlyGetConstructedCDOMObject(PCStat.class,
statName);
if ((stat != null) && seenStats.add(statName.toUpperCase())
&& (it.hasNext()))
{
element = it.next();
try
{
thePC.setStat(stat, Integer.parseInt(element.getText()));
}
catch (NumberFormatException nfe)
{
throw new PCGParseException(
"parseStatLine", line, nfe.getMessage()); //$NON-NLS-1$
}
}
else
{
final String message =
"Invalid attribute specification. "
+ "Cannot load character.";
throw new PCGParseException("parseStatLine", line, message); //$NON-NLS-1$
}
}
else
{
final String message =
"Invalid attribute specification. "
+ "Cannot load character.";
throw new PCGParseException("parseStatLine", line, message); //$NON-NLS-1$
}
}
private void parseTabNameLine(final String line)
{
thePC.setPCAttribute(PCAttribute.TABNAME, EntityEncoder.decode(line.substring(IOConstants.TAG_TABNAME
.length() + 1)));
}
/*
* ###############################################################
* Character Templates methods
* ###############################################################
*/
private void parseTemplateLine(final String line) throws PCGParseException
{
if (line.charAt(IOConstants.TAG_TEMPLATESAPPLIED.length() + 1) == '[')
{
final PCGTokenizer tokens;
try
{
tokens = new PCGTokenizer(line);
}
catch (PCGParseException pcgpex)
{
final String message =
"Illegal Template line ignored: " + line
+ Constants.LINE_SEPARATOR + "Error: "
+ pcgpex.getMessage();
warnings.add(message);
return;
}
PCTemplate aPCTemplate = null;
Iterator<PCGElement> it = tokens.getElements().iterator();
if (it.hasNext())
{
final PCGElement element = it.next();
String assoc = null;
//Must deal with APPLIEDTO first (before item is added to the PC)
for (final PCGElement child : element.getChildren())
{
String childTag = child.getName();
if (IOConstants.TAG_NAME.equals(childTag))
{
aPCTemplate =
Globals.getContext().getReferenceContext()
.silentlyGetConstructedCDOMObject(
PCTemplate.class,
EntityEncoder.decode(child.getText()));
if (aPCTemplate == null)
{
break;
}
}
else if (IOConstants.TAG_APPLIEDTO.equals(childTag))
{
assoc = child.getText();
}
}
for (final PCGElement child : element.getChildren())
{
final String childTag = child.getName();
if (IOConstants.TAG_NAME.equals(childTag))
{
if (aPCTemplate == null)
{
break;
}
addKeyedTemplate(aPCTemplate, assoc);
}
else if (IOConstants.TAG_CHOSENFEAT.equals(childTag))
{
String mapKey = null;
String mapValue = null;
for (PCGElement subChild : child.getChildren())
{
final String subChildTag = subChild.getName();
if (IOConstants.TAG_MAPKEY.equals(subChildTag))
{
mapKey = subChild.getText();
}
else if (IOConstants.TAG_MAPVALUE.equals(subChildTag))
{
mapValue = subChild.getText();
}
}
if ((mapKey != null) && (mapValue != null))
{
String feat = EntityEncoder.decode(mapValue);
PCTemplate subt =
Compatibility.getTemplateFor(aPCTemplate,
EntityEncoder.decode(mapKey), feat);
if (subt != null)
{
CNAbilitySelection as =
CNAbilitySelection
.getAbilitySelectionFromPersistentFormat(feat);
thePC.addTemplateFeat(subt, as);
}
}
}
else if (IOConstants.TAG_CHOSENTEMPLATE.equals(childTag))
{
for (PCGElement subChild : child.getChildren())
{
final String subChildTag = subChild.getName();
if (IOConstants.TAG_NAME.equals(subChildTag))
{
final String ownedTemplateKey =
EntityEncoder
.decode(subChild.getText());
final PCTemplate ownedTemplate =
Globals.getContext().getReferenceContext()
.silentlyGetConstructedCDOMObject(
PCTemplate.class,
ownedTemplateKey);
if (ownedTemplate != null)
{
thePC.setTemplatesAdded(aPCTemplate,
ownedTemplate);
}
}
}
}
//Add handled below, AppliedTo handled in the first loop
else if (!IOConstants.TAG_ADDTOKEN.equals(childTag)
&& !IOConstants.TAG_APPLIEDTO.equals(childTag))
{
final String msg =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.UnknownTemplateInfo", //$NON-NLS-1$
childTag + ":" + child.getText());
warnings.add(msg);
}
}
}
//Must process ADD after Template is added to the PC
for (PCGElement e : new PCGTokenizer(line).getElements())
{
String tag = e.getName();
if (tag.equals(IOConstants.TAG_ADDTOKEN))
{
parseAddTokenInfo(e, aPCTemplate);
}
}
}
else
{
String key =
EntityEncoder.decode(line.substring(IOConstants.TAG_TEMPLATESAPPLIED
.length() + 1));
PCTemplate aPCTemplate =
Globals.getContext().getReferenceContext().silentlyGetConstructedCDOMObject(
PCTemplate.class, key);
addKeyedTemplate(aPCTemplate, null);
}
}
/**
* # Use temporary mods/bonuses?
*
* @param line
*/
private void parseUseTempModsLine(final String line)
{
thePC.setUseTempMods(line.endsWith(IOConstants.VALUE_Y));
}
private void parseVFeatLine(final String line)
{
final PCGTokenizer tokens;
try
{
tokens = new PCGTokenizer(line);
}
catch (PCGParseException pcgpex)
{
final String message =
"Illegal VFeat line ignored: " + line
+ Constants.LINE_SEPARATOR + "Error: "
+ pcgpex.getMessage();
warnings.add(message);
return;
}
Ability anAbility = null;
final Iterator<PCGElement> it = tokens.getElements().iterator();
// the first element defines the Feat key name
if (it.hasNext())
{
final PCGElement element = it.next();
final String abilityKey = EntityEncoder.decode(element.getText());
anAbility =
Globals.getContext().getReferenceContext().silentlyGetConstructedCDOMObject(
Ability.class, AbilityCategory.FEAT, abilityKey);
if (anAbility == null)
{
final String message = "Could not add vfeat: " + abilityKey;
warnings.add(message);
return;
}
CNAbility cna = CNAbilityFactory.getCNAbility(AbilityCategory.FEAT, Nature.VIRTUAL, anAbility);
parseFeatsHandleAppliedToAndSaveTags(it, cna);
thePC.setDirty(true);
}
}
/**
* Parses the version information from a PCG file.
* @param line The line containing version information
* @throws PCGParseException if the line is not a valid version line
*/
void parseVersionLine(final String line) throws PCGParseException
{
int[] version = {0, 0, 0};
// Check to make sure that this is a version line
if (!line.startsWith(IOConstants.TAG_VERSION + IOConstants.TAG_END))
{
throw new PCGParseException(
"parseVersionLine", line, "Not a Version Line."); //$NON-NLS-1$
}
// extract the tokens from the version line
String[] tokens =
line.substring(IOConstants.TAG_VERSION.length() + 1).split(" |\\.|\\-", 4); //$NON-NLS-1$
for (int idx = 0; idx < 3 && idx < tokens.length; idx++)
{
try
{
version[idx] = Integer.parseInt(tokens[idx]);
}
catch (NumberFormatException e)
{
if (idx == 2 && (tokens[idx].startsWith("RC")))
{
pcgenVersionSuffix = tokens[2];
}
else
{
// Something in the first 3 digits was not an integer
throw new PCGParseException(
"parseVersionLine", line, "Invalid PCGen version."); //$NON-NLS-1$
}
}
}
if (tokens.length == 4)
{
pcgenVersionSuffix = tokens[3];
}
pcgenVersion = version;
}
/*
* ###############################################################
* Character Weapon proficiencies methods
* ###############################################################
*/
private void parseWeaponProficienciesLine(final String line)
{
final PCGTokenizer tokens;
try
{
tokens = new PCGTokenizer(line);
}
catch (PCGParseException pcgpex)
{
final String message =
"Illegal Weapon proficiencies line ignored: " + line
+ Constants.LINE_SEPARATOR + "Error: "
+ pcgpex.getMessage();
warnings.add(message);
return;
}
CDOMObject source = null;
boolean hadSource = false;
for (PCGElement element : tokens.getElements())
{
if (IOConstants.TAG_SOURCE.equals(element.getName()))
{
hadSource = true;
String type = Constants.EMPTY_STRING;
String key = Constants.EMPTY_STRING;
for (final PCGElement child : element.getChildren())
{
final String tag = child.getName();
if (IOConstants.TAG_TYPE.equals(tag))
{
type = child.getText().toUpperCase();
}
else if (IOConstants.TAG_NAME.equals(tag))
{
key = child.getText();
}
}
if (Constants.EMPTY_STRING.equals(type)
|| Constants.EMPTY_STRING.equals(key))
{
final String message =
"Illegal Weapon proficiencies line ignored: "
+ line;
warnings.add(message);
return;
}
if (IOConstants.TAG_RACE.equals(type))
{
source = thePC.getRace();
}
else if (TAG_PCTEMPLATE.equals(type))
{
PCTemplate template =
Globals.getContext().getReferenceContext()
.silentlyGetConstructedCDOMObject(
PCTemplate.class, key);
if (thePC.hasTemplate(template))
{
source = template;
}
else
{
warnings.add("PC does not have Template: " + key);
}
}
else if (IOConstants.TAG_PCCLASS.equals(type))
{
source = thePC.getClassKeyed(key);
}
if (source == null)
{
final String message =
"Invalid source specification: " + line;
warnings.add(message);
}
break;
}
}
final PCGElement element = tokens.getElements().get(0);
boolean processed = false;
if (source != null)
{
List<PersistentTransitionChoice<?>> adds =
source.getListFor(ListKey.ADD);
if (adds != null)
{
for (PersistentTransitionChoice<?> ptc: adds)
{
if (ptc.getChoiceClass().equals(WeaponProf.class))
{
for (PCGElement child : element.getChildren())
{
WeaponProf wp = getWeaponProf(child.getText());
Set c = Collections.singleton(wp);
ptc.act(c, source, thePC);
}
processed = true;
break;
}
}
}
}
if (hadSource && !processed)
{
final String message = "Unable to apply WeaponProfs: " + line;
warnings.add(message);
}
}
private void parseWeightLine(final String line)
{
try
{
thePC.setPCAttribute(NumericPCAttribute.WEIGHT, Integer.parseInt(line.substring(IOConstants.TAG_WEIGHT.length() + 1)));
}
catch (NumberFormatException nfe)
{
final String message = "Illegal Weight line ignored: " + line;
warnings.add(message);
}
}
private static String shortClassName(final Object o)
{
final Class<?> objClass = o.getClass();
final String pckName = objClass.getPackage().getName();
return objClass.getName().substring(pckName.length() + 1);
}
private WeaponProf getWeaponProf(final String aString)
{
WeaponProf wp =
Globals.getContext().getReferenceContext().silentlyGetConstructedCDOMObject(
WeaponProf.class, EntityEncoder.decode(aString));
if (wp == null)
{
final String message =
"Unable to find Weapon Proficiency in Rules Data:"
+ aString;
if (Logging.isDebugMode())
{
Logging.debugPrint(message);
}
}
return wp;
}
/**
* ###############################################################
* Character EquipSet Stuff
* ###############################################################
* @param line
**/
private void parseCalcEquipSet(final String line)
{
final PCGTokenizer tokens;
try
{
tokens = new PCGTokenizer(line);
}
catch (PCGParseException pcgpex)
{
/*
* EquipSet is not critical for characters,
* no need to stop the load process
*/
final String message =
"Illegal Calc EquipSet line ignored: " + line
+ Constants.LINE_SEPARATOR + "Error: "
+ pcgpex.getMessage();
warnings.add(message);
return;
}
final String calcEQId =
EntityEncoder.decode(tokens.getElements().get(0).getText());
if (calcEQId != null)
{
thePC.setCalcEquipSetId(calcEQId);
}
}
/*
* ###############################################################
* Character Description/Bio/History methods
* ###############################################################
*/
private void parseCharacterBioLine(final String line)
{
thePC.setPCAttribute(NotePCAttribute.BIO, EntityEncoder.decode(line.substring(IOConstants.TAG_CHARACTERBIO
.length() + 1)));
}
private void parseEquipmentLine(final String line)
{
final PCGTokenizer tokens;
try
{
tokens = new PCGTokenizer(line);
}
catch (PCGParseException pcgpex)
{
final String message =
"Illegal Equipment line ignored: " + line
+ Constants.LINE_SEPARATOR + "Error: "
+ pcgpex.getMessage();
warnings.add(message);
return;
}
String itemKey;
Equipment aEquip;
PCGElement element;
String tag;
// the first element defines the item key name
element = tokens.getElements().get(0);
itemKey = EntityEncoder.decode(element.getText());
// Check for an equipment key that has been updated.
itemKey =
EquipmentMigration.getNewEquipmentKey(itemKey, pcgenVersion,
SettingsHandler.getGame().getName());
// might be dynamically created container
aEquip = thePC.getEquipmentNamed(itemKey);
if (aEquip == null)
{
// Must load custom equipment from the .pcg file
// before we check the Global list (which may get
// loaded from customeEquipment.lst) as equipment
// in the PC's .pcg may contain additional info
// such as Charges on a wand, etc
//
// Make sure that we are not picking up custom items!
aEquip =
Globals.getContext().getReferenceContext().silentlyGetConstructedCDOMObject(
Equipment.class, itemKey);
if (aEquip != null)
{
if (aEquip.isType(Constants.TYPE_CUSTOM))
{
aEquip = null;
}
else
{
// standard item
aEquip = aEquip.clone();
}
}
if (line.indexOf(IOConstants.TAG_CUSTOMIZATION) >= 0)
{
// might be customized item
for (Iterator<PCGElement> it = tokens.getElements().iterator(); it
.hasNext();)
{
element = it.next();
if (IOConstants.TAG_CUSTOMIZATION.equals(element.getName()))
{
String baseItemKey = Constants.EMPTY_STRING;
String customProperties = Constants.EMPTY_STRING;
for (PCGElement child : element.getChildren())
{
final String childTag = child.getName();
if (IOConstants.TAG_BASEITEM.equals(childTag))
{
baseItemKey =
EntityEncoder.decode(child.getText());
// Check for an equipment key that has been updated.
baseItemKey =
EquipmentMigration
.getNewEquipmentKey(baseItemKey,
pcgenVersion, SettingsHandler
.getGame().getName());
}
else if (IOConstants.TAG_DATA.equals(childTag))
{
customProperties =
EntityEncoder.decode(child.getText());
}
}
if (aEquip != null
&& baseItemKey.equals(aEquip.getBaseItemName()))
{
// We clear out any eqmods that the base item has as the
// EQMODs on the saved item override them.
EquipmentHead head =
aEquip.getEquipmentHeadReference(1);
if (head != null)
{
head.removeListFor(ListKey.EQMOD);
head.removeListFor(ListKey.EQMOD_INFO);
}
aEquip.setBase();
aEquip.load(customProperties, "$", "=", thePC); //$NON-NLS-1$ //$NON-NLS-2$
aEquip.setToCustomSize(thePC);
}
else
{
// Make sure that we are not picking up custom items!
Equipment aEquip2 =
Globals.getContext().getReferenceContext()
.silentlyGetConstructedCDOMObject(
Equipment.class, baseItemKey);
if (aEquip2 != null)
{
// Make sure we are not getting a custom item
if (aEquip2.isType(Constants.TYPE_CUSTOM))
{
aEquip2 = null;
}
else
{
// standard item
aEquip = aEquip2.clone();
// We clear out any eqmods that the base item has as the
// EQMODs on the saved item override them.
EquipmentHead head =
aEquip.getEquipmentHeadReference(1);
if (head != null)
{
head.removeListFor(ListKey.EQMOD);
head.removeListFor(ListKey.EQMOD_INFO);
}
aEquip.setBase();
aEquip.load(customProperties,
"$", "=", thePC); //$NON-NLS-1$//$NON-NLS-2$
aEquip.setToCustomSize(thePC);
aEquip.remove(StringKey.OUTPUT_NAME);
if (!aEquip.isType(Constants.TYPE_CUSTOM))
{
aEquip.addType(Type.CUSTOM);
}
Globals.getContext().getReferenceContext()
.importObject(aEquip.clone());
}
}
}
break;
}
}
}
if (aEquip == null)
{
final String msg =
LanguageBundle.getFormattedString(
"Warnings.PCGenParser.EquipmentNotFound", //$NON-NLS-1$
itemKey);
warnings.add(msg);
return;
}
thePC.addEquipment(aEquip);
}
for (final Iterator<PCGElement> it = tokens.getElements().iterator(); it
.hasNext();)
{
element = it.next();
tag = element.getName();
if (IOConstants.TAG_QUANTITY.equals(tag))
{
float oldQty = aEquip.getQty();
aEquip.setQty(element.getText());
thePC.updateEquipmentQty(aEquip, oldQty, aEquip.getQty());
}
else if (IOConstants.TAG_OUTPUTORDER.equals(tag))
{
int index = 0;
try
{
index = Integer.parseInt(element.getText());
}
catch (NumberFormatException nfe)
{
// nothing we can or have to do about this
}
aEquip.setOutputIndex(index);
if (aEquip.isAutomatic())
{
thePC.cacheOutputIndex(aEquip);
}
}
else if (IOConstants.TAG_COST.equals(tag))
{
// TODO This else if switch currently does nothing?
}
else if (IOConstants.TAG_WT.equals(tag))
{
// TODO This else if switch currently does nothing?
}
else if (IOConstants.TAG_NOTE.equals(tag))
{
aEquip.setNote(element.getText());
}
}
}
private void parseEquipmentSetLine(final String line)
{
final PCGTokenizer tokens;
try
{
tokens = new PCGTokenizer(line);
}
catch (PCGParseException pcgpex)
{
final String message =
"Illegal EquipSet line ignored: " + line
+ Constants.LINE_SEPARATOR + "Error: "
+ pcgpex.getMessage();
warnings.add(message);
return;
}
String setName = null;
String setID = null;
String itemKey = null;
String setNote = null;
Float itemQuantity = null;
boolean useTempMods = false;
for (PCGElement element : tokens.getElements())
{
final String tag = element.getName();
if (IOConstants.TAG_EQUIPSET.equals(tag))
{
setName = EntityEncoder.decode(element.getText());
}
else if (IOConstants.TAG_ID.equals(tag))
{
setID = element.getText();
}
else if (IOConstants.TAG_VALUE.equals(tag))
{
itemKey = EntityEncoder.decode(element.getText());
}
else if (IOConstants.TAG_QUANTITY.equals(tag))
{
try
{
itemQuantity = new Float(element.getText());
}
catch (NumberFormatException nfe)
{
itemQuantity = new Float(0.0f);
}
}
else if (IOConstants.TAG_NOTE.equals(tag))
{
setNote = EntityEncoder.decode(element.getText());
}
else if (IOConstants.TAG_USETEMPMODS.equals(tag))
{
useTempMods = element.getText().endsWith(IOConstants.VALUE_Y);
}
}
if ((setName == null) || Constants.EMPTY_STRING.equals(setName)
|| (setID == null) || Constants.EMPTY_STRING.equals(setID))
{
final String message = "Illegal EquipSet line ignored: " + line;
warnings.add(message);
return;
}
final EquipSet aEquipSet;
Equipment aEquip;
Equipment eqI;
aEquipSet = new EquipSet(setID, setName);
if (setNote != null)
{
aEquipSet.setNote(setNote);
}
if (itemKey != null)
{
aEquipSet.setValue(itemKey);
eqI = thePC.getEquipmentNamed(itemKey);
if (eqI == null)
{
eqI =
Globals.getContext().getReferenceContext()
.silentlyGetConstructedCDOMObject(Equipment.class,
itemKey);
}
if (eqI == null)
{
final String message = "Could not find equipment: " + itemKey;
warnings.add(message);
return;
}
aEquip = eqI.clone();
if (itemQuantity != null)
{
aEquipSet.setQty(itemQuantity);
aEquip.setQty(itemQuantity);
aEquip.setNumberCarried(itemQuantity);
}
// if the idPath is longer than 3
// it's inside a container
if ((new StringTokenizer(setID, ".")).countTokens() > 3) //$NON-NLS-1$
{
// get parent EquipSet
final EquipSet aEquipSet2 =
thePC.getEquipSetByIdPath(aEquipSet.getParentIdPath());
// get the container
Equipment aEquip2 = null;
if (aEquipSet2 != null)
{
aEquip2 = aEquipSet2.getItem();
}
// add the child to container
if (aEquip2 != null)
{
aEquip2.insertChild(thePC, aEquip);
aEquip.setParent(aEquip2);
}
}
aEquipSet.setItem(aEquip);
}
aEquipSet.setUseTempMods(useTempMods);
thePC.addEquipSet(aEquipSet);
}
/*
* ###############################################################
* Character Equipment methods
* ###############################################################
*/
private void parseMoneyLine(final String line)
{
thePC.setGold(line.substring(IOConstants.TAG_MONEY.length() + 1));
}
/**
* ###############################################################
* Temporary Bonuses
* ###############################################################
* @param line
**/
private void parseTempBonusLine(final String line)
{
PCGTokenizer tokens;
try
{
tokens = new PCGTokenizer(line);
}
catch (PCGParseException pcgpex)
{
final String message =
"Illegal TempBonus line ignored: " + line
+ Constants.LINE_SEPARATOR + "Error: "
+ pcgpex.getMessage();
warnings.add(message);
return;
}
String cTag = null;
String tName = null;
boolean active = true;
for (PCGElement element : tokens.getElements())
{
final String tag = element.getName();
if (IOConstants.TAG_TEMPBONUS.equals(tag))
{
cTag = EntityEncoder.decode(element.getText());
}
else if (IOConstants.TAG_TEMPBONUSTARGET.equals(tag))
{
tName = EntityEncoder.decode(element.getText());
}
else if (IOConstants.TAG_TEMPBONUSACTIVE.equals(tag))
{
active = element.getText().endsWith(IOConstants.VALUE_Y);
}
}
if ((cTag == null) || (tName == null))
{
warnings.add("Illegal TempBonus line ignored: " + line);
return;
}
final StringTokenizer aTok = new StringTokenizer(cTag, "=", false); //$NON-NLS-1$
if (aTok.countTokens() < 2)
{
return;
}
final String cType = aTok.nextToken();
final String cKey = aTok.nextToken();
Equipment aEq = null;
if (!tName.equals(IOConstants.TAG_PC))
{
// bonus is applied to an equipment item
// so create a new one and add to PC
final Equipment eq = thePC.getEquipmentNamed(tName);
if (eq == null)
{
return;
}
aEq = eq.clone();
//aEq.setWeight("0");
aEq.resetTempBonusList();
}
for (PCGElement element : tokens.getElements())
{
final String tag = element.getName();
final String bonus;
if (IOConstants.TAG_TEMPBONUSBONUS.equals(tag))
{
bonus = EntityEncoder.decode(element.getText());
}
else
{
continue;
}
if ((bonus == null) || (bonus.length() <= 0))
{
continue;
}
BonusObj newB = null;
Object creator = null;
LoadContext context = Globals.getContext();
// Check the Creator type so we know what
// type of object to set as the creator
if (cType.equals(IOConstants.TAG_FEAT))
{
for (AbilityCategory aCat : SettingsHandler.getGame().getAllAbilityCategories())
{
Ability a = Globals.getContext().getReferenceContext()
.silentlyGetConstructedCDOMObject(Ability.class,
aCat, cKey);
if (a != null)
{
newB = Bonus.newBonus(context, bonus);
creator = a;
break;
}
}
}
else if (cType.equals(IOConstants.TAG_EQUIPMENT))
{
Equipment aEquip = thePC.getEquipmentNamed(cKey);
if (aEquip == null)
{
aEquip =
context.getReferenceContext().silentlyGetConstructedCDOMObject(
Equipment.class, cKey);
}
if (aEquip != null)
{
newB = Bonus.newBonus(context, bonus);
creator = aEquip;
}
}
else if (cType.equals(IOConstants.TAG_CLASS))
{
final PCClass aClass = thePC.getClassKeyed(cKey);
if (aClass == null)
{
continue;
}
int idx = bonus.indexOf('|');
newB = Bonus.newBonus(context, bonus.substring(idx + 1));
creator = aClass;
}
else if (cType.equals(IOConstants.TAG_TEMPLATE))
{
PCTemplate aTemplate =
context.getReferenceContext().silentlyGetConstructedCDOMObject(
PCTemplate.class, cKey);
if (aTemplate != null)
{
newB = Bonus.newBonus(context, bonus);
creator = aTemplate;
}
}
else if (cType.equals(IOConstants.TAG_SKILL))
{
Skill aSkill =
context.getReferenceContext().silentlyGetConstructedCDOMObject(
Skill.class, cKey);
if (aSkill != null)
{
newB = Bonus.newBonus(context, bonus);
creator = aSkill;
}
}
else if (cType.equals(IOConstants.TAG_SPELL))
{
final Spell aSpell =
context.getReferenceContext().silentlyGetConstructedCDOMObject(
Spell.class, cKey);
if (aSpell != null)
{
newB = Bonus.newBonus(context, bonus);
creator = aSpell;
}
}
else if (cType.equals(IOConstants.TAG_NAME))
{
newB = Bonus.newBonus(context, bonus);
//newB.setCreatorObject(thePC);
}
if (newB == null)
{
return;
}
TempBonusInfo tempBonusInfo;
// Check to see if the target was the PC or an Item
if (tName.equals(IOConstants.TAG_PC))
{
thePC.setApplied(newB, true);
tempBonusInfo = thePC.addTempBonus(newB, creator, thePC);
}
else
{
thePC.setApplied(newB, true);
aEq.addTempBonus(newB);
tempBonusInfo = thePC.addTempBonus(newB, creator, aEq);
}
if (!active)
{
String bonusName = BonusDisplay.getBonusDisplayName(tempBonusInfo);
thePC.setTempBonusFilter(bonusName);
}
}
if (aEq != null)
{
aEq.setAppliedName(cKey);
thePC.addTempBonusItemList(aEq);
}
}
/*
* currently source is either empty or
* PCCLASS|classname|classlevel (means it's a chosen special ability)
* PCCLASS=classname|classlevel (means it's a defined special ability)
* DEITY=deityname|totallevels
*/
private static String sourceElementToString(final PCGElement source)
{
String type = Constants.EMPTY_STRING;
String name = Constants.EMPTY_STRING;
String level = Constants.EMPTY_STRING;
String defined = Constants.EMPTY_STRING;
for (PCGElement child : source.getChildren())
{
final String tag = child.getName();
if (IOConstants.TAG_TYPE.equals(tag))
{
type = child.getText();
}
else if (IOConstants.TAG_NAME.equals(tag))
{
name = child.getText();
}
else if (IOConstants.TAG_LEVEL.equals(tag))
{
level = child.getText();
}
else if (IOConstants.TAG_DEFINED.equals(tag))
{
defined = child.getText().toUpperCase();
}
}
//TODO:gorm - guestimate good starting buffer size
final StringBuilder buffer = new StringBuilder(1000);
buffer.append(type);
buffer.append((IOConstants.VALUE_Y.equals(defined)) ? '=' : '|');
buffer.append(name);
if (!Constants.EMPTY_STRING.equals(level))
{
buffer.append('|');
buffer.append(level);
}
return buffer.toString();
}
/*
* ###############################################################
* Inner classes
* ###############################################################
*/
private static final class PCGElement
{
private final String name;
private List<PCGElement> children;
private String text;
private PCGElement(final String aName)
{
this.name = aName;
}
/**
* Returns a string representation of the element. This string is
* written in XML format.
* @return An XML formatted string.
* @see java.lang.Object#toString()
*/
@Override
public String toString()
{
//TODO:gorm - optimize StringBuilder size
final StringBuilder buffer = new StringBuilder(1000);
buffer.append('<').append(getName()).append('>').append(IOConstants.LINE_SEP);
buffer
.append("<text>").append(getText()).append("</text>").append(IOConstants.LINE_SEP); //$NON-NLS-1$//$NON-NLS-2$
for (PCGElement child : getChildren())
{
buffer.append(child.toString()).append(IOConstants.LINE_SEP);
}
buffer.append("</").append(getName()).append('>'); //$NON-NLS-1$
return buffer.toString();
}
/**
* Returns all the children of this element.
* <p><b>Note</b>: This has a side effect of initializing the children
* list for the element.
* @return A {@code List} of children
*/
public List<PCGElement> getChildren()
{
if (children == null)
{
this.children = new ArrayList<>(0);
}
return children;
}
private String getName()
{
return name;
}
private String getText()
{
return (text != null) ? text : Constants.EMPTY_STRING;
}
private void addContent(final PCGElement child)
{
if (children == null)
{
this.children = new ArrayList<>(0);
}
children.add(child);
}
private void addContent(final String argText)
{
text = argText;
}
}
private static final class PCGTokenizer
{
private final List<PCGElement> elements;
private final String innerDelimiter;
private final String nestedStartDelimiter;
private final String nestedStopDelimiter;
private final String outerDelimiter;
private final char nestedStartDelimiterChar;
private final char nestedStopDelimiterChar;
/**
* Constructor
* @param line
* @throws PCGParseException
*/
private PCGTokenizer(final String line) throws PCGParseException
{
this(line, ":|[]"); //$NON-NLS-1$
}
/**
* Constructor
* <br>
* @param line a String to tokenize
* @param delimiters a FOUR-character String specifying the four needed delimiters:
* <ol>
* <li>the inner delimiter for a PCGElement</li>
* <li>the outer delimiter for a PCGElement</li>
* <li>the start delimiter for nested PCGElements</li>
* <li>the stop delimiter for nested PCGElement</li>
* </ol>
* @throws PCGParseException
*/
private PCGTokenizer(final String line, final String delimiters)
throws PCGParseException
{
final char[] dels = delimiters.toCharArray();
this.innerDelimiter = String.valueOf(dels[0]);
this.outerDelimiter = String.valueOf(dels[1]);
this.nestedStartDelimiter = String.valueOf(dels[2]);
this.nestedStopDelimiter = String.valueOf(dels[3]);
this.nestedStartDelimiterChar = nestedStartDelimiter.charAt(0);
this.nestedStopDelimiterChar = nestedStopDelimiter.charAt(0);
this.elements = new ArrayList<>(0);
tokenizeLine(line);
}
private List<PCGElement> getElements()
{
return elements;
}
private void checkSyntax(final String line) throws PCGParseException
{
final char[] chars = line.toCharArray();
int delimCount = 0;
for (int i = 0; i < chars.length; ++i)
{
if (chars[i] == nestedStartDelimiterChar)
{
++delimCount;
}
else if (chars[i] == nestedStopDelimiterChar)
{
--delimCount;
}
}
if (delimCount < 0)
{
final String message = "Missing " + nestedStartDelimiter;
throw new PCGParseException("PCGTokenizer::checkSyntax", line,
message);
}
else if (delimCount > 0)
{
final String message = "Missing " + nestedStopDelimiter;
throw new PCGParseException("PCGTokenizer::checkSyntax", line,
message);
}
}
private void tokenizeLine(final String line) throws PCGParseException
{
checkSyntax(line);
final PCGElement root = new PCGElement("root"); //$NON-NLS-1$
tokenizeLine(root, line);
elements.addAll(root.getChildren());
}
private void tokenizeLine(final PCGElement parent, final String line)
throws PCGParseException
{
final String dels =
outerDelimiter + nestedStartDelimiter + nestedStopDelimiter;
final StringTokenizer tokens =
new StringTokenizer(line, dels, true);
int nestedDepth = 0;
String tag = null;
final StringBuilder buffer = new StringBuilder(1000);
while (tokens.hasMoreTokens())
{
String token = tokens.nextToken().trim();
if (token.equals(outerDelimiter))
{
if (nestedDepth == 0)
{
if (buffer.length() > 0)
{
token = buffer.toString();
int index = token.indexOf(innerDelimiter);
if (index >= 0)
{
buffer.delete(0, buffer.length());
final PCGElement element =
new PCGElement(
token.substring(0, index));
element.addContent(token.substring(index + 1));
parent.addContent(element);
}
else
{
final String message =
"Malformed PCG element: " + token;
throw new PCGParseException(
"PCGTokenizer::tokenizeLine", line, message);
}
}
}
else
{
buffer.append(token);
}
}
else if (token.equals(nestedStartDelimiter))
{
if (nestedDepth == 0)
{
token = buffer.toString();
int index = token.indexOf(innerDelimiter);
if ((index >= 0) && (index == (token.length() - 1)))
{
buffer.delete(0, buffer.length());
tag = token.substring(0, index);
}
else
{
final String message =
"Malformed PCG element: " + token;
throw new PCGParseException(
"PCGTokenizer::tokenizeLine", line, message);
}
}
else
{
buffer.append(token);
}
++nestedDepth;
}
else if (token.equals(nestedStopDelimiter))
{
--nestedDepth;
if (nestedDepth == 0)
{
final PCGElement element = new PCGElement(tag);
tokenizeLine(element, buffer.toString());
parent.addContent(element);
buffer.delete(0, buffer.length());
}
else
{
buffer.append(token);
}
}
else
{
buffer.append(token);
}
}
if (buffer.length() > 0)
{
final String token = buffer.toString();
final int index = token.indexOf(innerDelimiter);
if (index >= 0)
{
buffer.delete(0, buffer.length());
final PCGElement element =
new PCGElement(token.substring(0, index));
element.addContent(token.substring(index + 1));
parent.addContent(element);
}
else
{
final String message = "Malformed PCG element: " + token;
throw new PCGParseException("PCGTokenizer::tokenizeLine",
line, message);
}
}
}
}
/**
* Returns the version of the application that wrote the file
* @return An {@code int} array containing the 3 digit version
*/
int[] getPcgenVersion()
{
return pcgenVersion;
}
/**
* Compare the PCG version with a supplied version number
* @param inVer The version to compare with the PCG version. Must have at least 3 elements.
* @return the value 0 if the PCG version is equal to the supplied version; a
* value less than 0 if the PCG version is less than the supplied version;
* and a value greater than 0 if the PCG version is greater than the supplied version.
*/
int compareVersionTo(int inVer[])
{
return CoreUtility.compareVersions(pcgenVersion, inVer);
}
/**
* Returns any extra version info after the regular version number.
* @return String extra version information
*/
String getPcgenVersionSuffix()
{
return pcgenVersionSuffix;
}
private void parseLevelAbilityInfo(final PCGElement element,
final CDOMObject pObj)
{
parseLevelAbilityInfo(element, pObj, -9);
}
private void parseLevelAbilityInfo(final PCGElement element,
final CDOMObject pObj, final int level)
{
final Iterator<PCGElement> it2 = element.getChildren().iterator();
if (it2.hasNext())
{
final String dString = EntityEncoder.decode(it2.next().getText());
PersistentTransitionChoice<?> ptc = null;
try
{
ptc =
Compatibility.processOldAdd(Globals.getContext(),
dString);
}
catch (PersistenceLayerException ple)
{
warnings.add(pObj.getDisplayName() + "("
+ pObj.getClass().getName()
+ ")\nCould not process LevelAbility: " + dString + "\n"
+ ple.getLocalizedMessage());
return;
}
if (ptc == null)
{
warnings.add(pObj.getDisplayName() + "("
+ pObj.getClass().getName()
+ ")\nCould not process LevelAbility: " + dString);
return;
}
CDOMObject target = pObj;
if (pObj instanceof PCClass)
{
target = thePC.getActiveClassLevel(((PCClass) pObj), level);
}
for (PersistentTransitionChoice<?> tptc : target
.getSafeListFor(ListKey.ADD))
{
if (tptc.equals(ptc))
{
while (it2.hasNext())
{
final String choice =
EntityEncoder.decode(it2.next().getText());
thePC.addAssoc(tptc, AssociationListKey.ADD, choice);
}
}
}
}
}
/**
* @return the baseFeatPool
*/
@Override
public double getBaseFeatPool()
{
return baseFeatPool;
}
/**
* @return the calcFeatPoolAfterLoad
*/
@Override
public boolean isCalcFeatPoolAfterLoad()
{
return calcFeatPoolAfterLoad;
}
private static final Class<Language> LANGUAGE_CLASS = Language.class;
private void resolveLanguages()
{
CNAbility langbonus = thePC.getBonusLanguageAbility();
int currentBonusLang = thePC.getDetailedAssociationCount(langbonus);
boolean foundLang = currentBonusLang > 0;
Set<Language> foundLanguages = new HashSet<>();
//Captures Auto (AUTO:LANG) and Persistent choices (ADD ex ability and CHOOSE)
foundLanguages.addAll(thePC.getLanguageSet());
cachedLanguages.removeAll(foundLanguages);
HashMapToList<Language, Object> langSources = new HashMapToList<>();
Map<Object, Integer> actorLimit = new IdentityHashMap<>();
Map<PersistentTransitionChoice, CDOMObject> ptcSources = new IdentityHashMap<>();
List<? extends CDOMObject> abilities = thePC.getCDOMObjectList();
for (CDOMObject a : abilities)
{
List<PersistentTransitionChoice<?>> addList =
a.getListFor(ListKey.ADD);
if (addList != null)
{
for (PersistentTransitionChoice<?> ptc : addList)
{
SelectableSet<?> ss = ptc.getChoices();
if (ss.getName().equals("LANGUAGE")
&& LANGUAGE_CLASS.equals(ss.getChoiceClass()))
{
Collection<Language> selected =
(Collection<Language>) ss.getSet(thePC);
for (Language l : selected)
{
if (cachedLanguages.contains(l))
{
String source =
SourceFormat.getFormattedString(a,
Globals.getSourceDisplay(), true);
int choiceCount =
ptc.getCount()
.resolve(thePC, source)
.intValue();
if (choiceCount > 0)
{
langSources.addToListFor(l, ptc);
ptcSources.put(ptc, a);
actorLimit.put(ptc, choiceCount);
}
}
}
}
}
}
}
if (!foundLang)
{
Set<Language> bonusAllowed = thePC.getLanguageBonusSelectionList();
int count = thePC.getBonusLanguageCount();
int choiceCount = count - currentBonusLang;
if (choiceCount > 0)
{
for (Language l : bonusAllowed)
{
if (cachedLanguages.contains(l))
{
langSources.addToListFor(l, langbonus);
actorLimit.put(langbonus, choiceCount);
}
}
}
}
//Try to match them up as best as possible (this matches things with only one possible location...)
boolean acted = !cachedLanguages.isEmpty();
while (acted)
{
acted = false;
for (Language l : langSources.getKeySet())
{
List<Object> actors = langSources.getListFor(l);
if ((actors != null) && (actors.size() == 1))
{
Object actor = actors.get(0);
acted = true;
processRemoval(langbonus, langSources, actorLimit, ptcSources,
l, actor);
}
}
if (!acted && !langSources.isEmpty() && !actorLimit.isEmpty())
{
//pick one
Language l = langSources.getKeySet().iterator().next();
Object source = langSources.getListFor(l).get(0);
processRemoval(langbonus, langSources, actorLimit, ptcSources, l, source);
acted = true;
}
}
for (Language l : cachedLanguages)
{
warnings.add("Unable to find source: "
+ "Character no longer speaks language: " + l.getDisplayName());
}
}
private void processRemoval(CNAbility langbonus,
HashMapToList<Language, Object> sources,
Map<Object, Integer> actorLimit,
Map<PersistentTransitionChoice, CDOMObject> ptcSources, Language l,
Object actor)
{
Integer limit = actorLimit.get(actor);
//apply
processActor(langbonus, ptcSources, l, actor);
cachedLanguages.remove(l);
sources.removeListFor(l);
//Remove this sources from all languages (may create more items with only one source)
if (limit == 1)
{
for (Language lang : sources.getKeySet())
{
sources.removeFromListFor(lang, actor);
}
//Used up
actorLimit.remove(actor);
}
else
{
//Use a slot
actorLimit.put(actor, limit - 1);
}
}
private void processActor(CNAbility langbonus,
Map<PersistentTransitionChoice, CDOMObject> ptcSources, Language l,
Object actor)
{
if (actor instanceof CNAbility)
{
thePC.addSavedAbility(
new CNAbilitySelection(langbonus, l.getKeyName()),
UserSelection.getInstance(), UserSelection.getInstance());
}
else if (actor instanceof PersistentTransitionChoice)
{
PersistentTransitionChoice<Language> ptc =
(PersistentTransitionChoice<Language>) actor;
ptc.restoreChoice(thePC, ptcSources.get(ptc), l);
}
else
{
warnings.add("Internal Error: Language actor of "
+ actor.getClass() + " is not understood");
}
}
/**
* Set the source of the domain. See getDomainSource() for details.
* This method should NOT be called outside of file i/o routines!
* @param aSource the source to be set
**/
public ClassSource getDomainSource(String aSource)
{
final StringTokenizer aTok = new StringTokenizer(aSource, "|", false);
if (aTok.countTokens() < 2)
{
Logging.errorPrint("Invalid Domain Source:" + aSource);
return null;
}
aTok.nextToken(); //Throw away "PCClass"
String classString = aTok.nextToken();
PCClass cl = thePC.getClassKeyed(classString);
if (cl == null)
{
Logging.errorPrint("Invalid Class in Domain Source:" + aSource);
return null;
}
ClassSource cs;
if (aTok.hasMoreTokens())
{
int level = Integer.parseInt(aTok.nextToken());
cs = new ClassSource(cl, level);
}
else
{
cs = new ClassSource(cl);
}
return cs;
}
private void insertDefaultClassSpellLists()
{
for (PCClass pcc : thePC.getClassList())
{
thePC.addDefaultSpellList(pcc);
}
}
public PCAlignment getNoAlignment()
{
return Globals.getContext().getReferenceContext().silentlyGetConstructedCDOMObject(
PCAlignment.class, Constants.NONE);
}
}