/* * 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); } }