/* * PlayerCharacterTest.java * * Copyright 2003 (C) Chris Ward <frugal@purplewombat.co.uk> * * 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 09-Jan-2004 * * Current Ver: $Revision$ * * * */ package pcgen.core; import java.awt.HeadlessException; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Set; import junit.textui.TestRunner; import pcgen.AbstractCharacterTestCase; import pcgen.cdom.base.CDOMReference; import pcgen.cdom.base.Constants; import pcgen.cdom.base.FormulaFactory; import pcgen.cdom.content.LevelCommandFactory; import pcgen.cdom.enumeration.IntegerKey; import pcgen.cdom.enumeration.ListKey; import pcgen.cdom.enumeration.ObjectKey; import pcgen.cdom.enumeration.StringKey; import pcgen.cdom.enumeration.Type; import pcgen.cdom.enumeration.VariableKey; import pcgen.cdom.inst.PCClassLevel; import pcgen.cdom.list.CompanionList; import pcgen.cdom.reference.CDOMDirectSingleRef; import pcgen.cdom.reference.CDOMSimpleSingleRef; import pcgen.cdom.reference.CDOMSingleRef; import pcgen.core.analysis.DomainApplication; import pcgen.core.analysis.SkillRankControl; import pcgen.core.bonus.Bonus; import pcgen.core.bonus.BonusObj; import pcgen.core.character.CharacterSpell; import pcgen.core.character.SpellBook; import pcgen.core.display.CharacterDisplay; import pcgen.core.pclevelinfo.PCLevelInfo; import pcgen.core.spell.Spell; import pcgen.core.system.LoadInfo; import pcgen.gui2.UIPropertyContext; import pcgen.io.exporttoken.StatToken; import pcgen.rules.context.LoadContext; import pcgen.util.Logging; import pcgen.util.TestHelper; import pcgen.util.chooser.ChooserFactory; import pcgen.util.chooser.RandomChooser; import pcgen.util.enumeration.View; import pcgen.util.enumeration.Visibility; import plugin.lsttokens.testsupport.BuildUtilities; /** * The Class <code>PlayerCharacterTest</code> is responsible for testing * that PlayerCharacter is working correctly. * * * @author Chris Ward <frugal@purplewombat.co.uk> */ @SuppressWarnings("nls") public class PlayerCharacterTest extends AbstractCharacterTestCase { Race giantRace = null; PCClass giantClass = null; PCClass pcClass = null; PCClass classWarmind = null; PCClass class2LpfM = null; PCClass class3LpfM = null; PCClass class3LpfBlank = null; Race human = null; Ability toughness = null; AbilityCategory specialFeatCat; AbilityCategory specialAbilityCat; private PCClass classMemDivine; private Domain luckDomain; private Spell luckDomainLvl1Spell; private Spell luckDomainLvl2Spell; /** * Run the tests. * @param args */ public static void main(final String[] args) { TestRunner.run(PlayerCharacterTest.class); } // /** // * @return Test // */ // public static Test suite() // { // return new TestSuite(PlayerCharacterTest.class); // } /** * @see junit.framework.TestCase#setUp() */ @Override protected void setUp() throws Exception { super.setUp(); LoadContext context = Globals.getContext(); // Giant Class giantClass = new PCClass(); giantClass.setName("Giant"); BuildUtilities.setFact(giantClass, "ClassType", "Monster"); final BonusObj babClassBonus = Bonus.newBonus(context, "COMBAT|BASEAB|CL*3/4"); giantClass.getOriginalClassLevel(1).addToListFor(ListKey.BONUS, babClassBonus); context.getReferenceContext().importObject(giantClass); // Human human = new Race(); final BonusObj humanRaceFeatBonus = Bonus.newBonus(context, "FEAT|POOL|2"); human.addToListFor(ListKey.BONUS, humanRaceFeatBonus); human.ownBonuses(human); // Giant Race giantRace = new Race(); giantRace.setName("Ogre"); giantRace.put(ObjectKey.MONSTER_CLASS, new LevelCommandFactory( CDOMDirectSingleRef.getRef(giantClass), FormulaFactory .getFormulaFor(4))); giantRace.addToListFor(ListKey.HITDICE_ADVANCEMENT, 100); final BonusObj giantRaceFeatBonus = Bonus.newBonus(context, "FEAT|POOL|1"); giantRace.addToListFor(ListKey.BONUS, giantRaceFeatBonus); giantRace.ownBonuses(giantRace); context.getReferenceContext().importObject(giantRace); // Create the monster class type SettingsHandler.getGame().addClassType( "Monster CRFORMULA:0 ISMONSTER:YES XPPENALTY:NO"); pcClass = new PCClass(); pcClass.setName("MyClass"); BuildUtilities.setFact(pcClass, "SpellType", "Arcane"); context.getReferenceContext().importObject(pcClass); classMemDivine = new PCClass(); classMemDivine.setName("MemDivine"); BuildUtilities.setFact(classMemDivine, "SpellType", "Divine"); classMemDivine.put(ObjectKey.MEMORIZE_SPELLS, true); context.unconditionallyProcess(classMemDivine, "SPELLSTAT", "WIS"); context.unconditionallyProcess(classMemDivine.getOriginalClassLevel(1), "CAST", "3,2,2"); context.unconditionallyProcess(classMemDivine, "BONUS", "DOMAIN|NUMBER|1"); context.getReferenceContext().importObject(classMemDivine); classWarmind = new PCClass(); classWarmind.setName("Warmind"); context.getReferenceContext().importObject(classWarmind); class2LpfM = new PCClass(); class2LpfM.setName("2LpfM"); BuildUtilities.setFact(class2LpfM, "ClassType", "Monster"); class2LpfM.put(IntegerKey.LEVELS_PER_FEAT, 2); class2LpfM.put(StringKey.LEVEL_TYPE, "MONSTER"); context.getReferenceContext().importObject(class2LpfM); class3LpfM = new PCClass(); class3LpfM.setName("3LpfM"); BuildUtilities.setFact(class3LpfM, "ClassType", "Monster"); class3LpfM.put(IntegerKey.LEVELS_PER_FEAT, 3); class3LpfM.put(StringKey.LEVEL_TYPE, "MONSTER"); context.getReferenceContext().importObject(class3LpfM); class3LpfBlank = new PCClass(); class3LpfBlank.setName("3LpfBlank"); BuildUtilities.setFact(class3LpfBlank, "ClassType", "Foo"); class3LpfBlank.put(IntegerKey.LEVELS_PER_FEAT, 3); context.getReferenceContext().importObject(class3LpfBlank); toughness = new Ability(); toughness.setName("Toughness"); toughness.put(ObjectKey.MULTIPLE_ALLOWED, Boolean.TRUE); toughness.put(ObjectKey.STACKS, Boolean.TRUE); context.unconditionallyProcess(toughness, "CHOOSE", "NOCHOICE"); toughness.setCDOMCategory(AbilityCategory.FEAT); final BonusObj aBonus = Bonus.newBonus(context, "HP|CURRENTMAX|3"); if (aBonus != null) { toughness.addToListFor(ListKey.BONUS, aBonus); } context.getReferenceContext().importObject(toughness); Ability exoticWpnProf = TestHelper.makeAbility("Exotic Weapon Proficiency", AbilityCategory.FEAT, "General.Fighter"); exoticWpnProf.put(ObjectKey.MULTIPLE_ALLOWED, Boolean.TRUE); context.unconditionallyProcess(exoticWpnProf, "CHOOSE", "WEAPONPROFICIENCY|!PC[TYPE.Exotic]"); context.unconditionallyProcess(exoticWpnProf, "AUTO", "WEAPONPROF|%LIST"); WeaponProf wpnProfTestA = new WeaponProf(); wpnProfTestA.setName("Weapon A"); wpnProfTestA.put(StringKey.KEY_NAME, "Weapon A"); wpnProfTestA.addToListFor(ListKey.TYPE, Type.getConstant("Exotic")); context.getReferenceContext().importObject(wpnProfTestA); WeaponProf wpnProfTestB = new WeaponProf(); wpnProfTestB.setName("Weapon B"); wpnProfTestB.put(StringKey.KEY_NAME, "Weapon B"); wpnProfTestB.addToListFor(ListKey.TYPE, Type.getConstant("Exotic")); context.getReferenceContext().importObject(wpnProfTestB); WeaponProf wpnProfTestC = new WeaponProf(); wpnProfTestC.setName("Weapon C"); wpnProfTestC.put(StringKey.KEY_NAME, "Weapon C"); wpnProfTestC.addToListFor(ListKey.TYPE, Type.getConstant("Exotic")); context.getReferenceContext().importObject(wpnProfTestC); UIPropertyContext.setSingleChoiceAction(Constants.CHOOSER_SINGLE_CHOICE_METHOD_SELECT_EXIT); ChooserFactory.pushChooserClassname(RandomChooser.class.getName()); context.unconditionallyProcess(pcClass.getOriginalClassLevel(1), "ADD", "FEAT|KEY_Exotic Weapon Proficiency (Weapon B)"); context.unconditionallyProcess(pcClass.getOriginalClassLevel(2), "ADD", "FEAT|KEY_Exotic Weapon Proficiency (Weapon A)"); context.unconditionallyProcess(pcClass.getOriginalClassLevel(3), "ADD", "FEAT|KEY_Exotic Weapon Proficiency (Weapon C)"); specialFeatCat = Globals.getContext().getReferenceContext() .constructNowIfNecessary(AbilityCategory.class, "Special Feat"); specialFeatCat.setAbilityCategory(CDOMDirectSingleRef.getRef(AbilityCategory.FEAT)); specialAbilityCat = Globals.getContext().getReferenceContext() .constructNowIfNecessary(AbilityCategory.class, "Special Ability"); luckDomain = TestHelper.makeDomain("Luck"); context.getReferenceContext().buildDerivedObjects(); luckDomainLvl1Spell = TestHelper.makeSpell("true strike"); luckDomainLvl2Spell = TestHelper.makeSpell("aid"); TestHelper.makeSpell("protection from energy"); context .unconditionallyProcess( luckDomain, "SPELLLEVEL", "DOMAIN|Luck=1|KEY_True Strike|Luck=2|KEY_Aid|Luck=3|KEY_Protection from Energy"); } private void readyToRun() { LoadContext context = Globals.getContext(); context.resolveDeferredTokens(); assertTrue(context.getReferenceContext().resolveReferences(null)); } @Override protected void tearDown() throws Exception { ChooserFactory.popChooserClassname(); Logging.setDebugMode(false); human.removeListFor(ListKey.BONUS); giantRace.removeListFor(ListKey.BONUS); super.tearDown(); } /** * @throws Exception */ public void testGetBonusFeatsForNewLevel1() throws Exception { readyToRun(); final PlayerCharacter character = new PlayerCharacter(); character.setRace(human); character.incrementClassLevel(1, pcClass, true); assertEquals(2, (int) character.getRemainingFeatPoints(true)); } /** * @throws Exception */ public void testGetBonusFeatsForNewLevel3() throws Exception { readyToRun(); final PlayerCharacter character = new PlayerCharacter(); character.setRace(human); character.incrementClassLevel(3, pcClass, true); assertEquals(3, (int) character.getRemainingFeatPoints(true)); } /** * Test bonus monster feats where there default monster mode is off. * Note: As PCClass grants feats which do not exist, the feat pool gets * incremented instead. * @throws Exception */ public void testGetMonsterBonusFeatsForNewLevel1() throws Exception { readyToRun(); final PlayerCharacter character = new PlayerCharacter(); character.setRace(giantRace); character.incrementClassLevel(1, pcClass, true); is((int) character.getRemainingFeatPoints(true), eq(2), "One level of PCClass, PC has one feat for levels of monster class and one for a missing feat."); character.incrementClassLevel(1, pcClass, true); is((int) character.getRemainingFeatPoints(true), eq(3), "Three levels of PCClass (6 total), feats increment"); } /** * Test level per feat bonus to feats. */ public void testGetNumFeatsFromLevels() { readyToRun(); final PlayerCharacter pc = new PlayerCharacter(); pc.setRace(human); assertEquals("Should start at 0", 0, pc.getNumFeatsFromLevels(), 0.001); pc.incrementClassLevel(1, class3LpfM, true); assertEquals("1/3 truncs to 0", 0, pc.getNumFeatsFromLevels(), 0.001); pc.incrementClassLevel(1, class3LpfM, true); assertEquals("2/3 truncs to 0", 0, pc.getNumFeatsFromLevels(), 0.001); pc.incrementClassLevel(1, class3LpfM, true); assertEquals("3/3 truncs to 1", 1, pc.getNumFeatsFromLevels(), 0.001); pc.incrementClassLevel(1, class3LpfM, true); assertEquals("4/3 truncs to 1", 1, pc.getNumFeatsFromLevels(), 0.001); pc.incrementClassLevel(1, class2LpfM, true); assertEquals("4/3 + 1/2 truncs to 1", 1, pc.getNumFeatsFromLevels(), 0.001); pc.incrementClassLevel(1, class3LpfBlank, true); assertEquals("4/3 + 1/2 truncs to 1 + 1/3 truncs to 0", 1, pc .getNumFeatsFromLevels(), 0.001); pc.incrementClassLevel(1, class2LpfM, true); assertEquals("5/3 + 2/2 truncs to 2 + 1/3 truncs to 0", 2, pc .getNumFeatsFromLevels(), 0.001); } /** * Test stacking rules for a mixture of normal progression and * levelsperfeat progression. Stacking should only occur within like * leveltypes or within standard progression * @throws Exception */ public void testGetMonsterBonusFeatsForNewLevel2() throws Exception { readyToRun(); final PlayerCharacter pc = new PlayerCharacter(); pc.setRace(giantRace); is((int) pc.getRemainingFeatPoints(true), eq(2), "Four levels from race (4/3), PC has one racial feat."); pc.incrementClassLevel(1, class3LpfM, true); is((int) pc.getRemainingFeatPoints(true), eq(2), "One level of 3LpfM (1/3), four levels from race(4/3), PC has one racial feat."); pc.incrementClassLevel(1, class3LpfM, true); is((int) pc.getRemainingFeatPoints(true), eq(2), "Two level of 3LpfM (2/3), four levels from race(4/3), PC has one racial feat."); pc.incrementClassLevel(1, class3LpfM, true); is((int) pc.getRemainingFeatPoints(true), eq(3), "Three level of 3LpfM (3/3), four levels from race(4/3), PC has one racial feat."); } /** * Tests getVariableValue * @throws Exception */ public void testGetVariableValue1() throws Exception { readyToRun(); LoadContext context = Globals.getContext(); //Logging.setDebugMode(true); Logging.debugPrint("\n\n\ntestGetVariableValue1()"); giantRace.put(VariableKey.getConstant("GiantVar1"), FormulaFactory.ZERO); final BonusObj raceBonus = Bonus.newBonus(context, "VAR|GiantVar1|7+HD"); giantClass.getOriginalClassLevel(1).addToListFor(ListKey.BONUS, raceBonus); giantClass.getOriginalClassLevel(1).put(VariableKey.getConstant("GiantClass1"), FormulaFactory.ZERO); final BonusObj babClassBonus = Bonus.newBonus(context, "VAR|GiantClass1|CL=Giant"); giantClass.getOriginalClassLevel(1).addToListFor(ListKey.BONUS, babClassBonus); final PlayerCharacter character = new PlayerCharacter(); // NOTE: This will add 4 levels of giantClass to the character character.setRace(giantRace); character.incrementClassLevel(4, giantClass, true); assertEquals(new Float(15.0), character.getVariableValue("GiantVar1", "CLASS:Giant")); assertEquals(new Float(8.0), character.getVariableValue("GiantClass1", "CLASS:Giant")); } /** * Tests getVariableValue for stat modifier * @throws Exception */ public void testGetVariableValueStatMod() throws Exception { readyToRun(); //Logging.setDebugMode(true); Logging.debugPrint("\n\n\ntestGetVariableValueStatMod()"); final PlayerCharacter character = new PlayerCharacter(); character.setRace(human); character.setStat(str, 16); character.incrementClassLevel(2, pcClass, true); final Float result = character.getVariableValue("floor(SCORE/2)-5", "STAT:STR"); assertEquals("Stat modifier not correct", 3.0, result.doubleValue(), 0.1); } /** * @throws Exception */ public void testGetVariableValueStatModNew() throws Exception { readyToRun(); //Logging.setDebugMode(true); Logging.debugPrint("\n\n\ntestGetVariableValueStatModNew()"); final PlayerCharacter character = new PlayerCharacter(); character.setRace(human); character.setStat(str, 16); character.incrementClassLevel(2, pcClass, true); final Float result = character.getVariableValue("floor(SCORE/2)-5", "STAT:STR"); assertEquals("Stat modifier not correct", 3.0, result.doubleValue(), 0.1); } /** * Test out the caching of variable values. */ public void testGetVariableCaching() { readyToRun(); final PlayerCharacter character = new PlayerCharacter(); character.setRace(human); character.setStat(str, 16); character.incrementClassLevel(2, pcClass, true); int iVal = character.getVariableValue("roll(\"3d6\")+5", "").intValue(); boolean match = true; for (int i = 0; i < 10; i++) { match = (iVal == character.getVariableValue("roll(\"3d6\")+5", "") .intValue()); if (!match) { break; } } assertFalse("Roll function should not be cached.", match); } /** * Test the processing of modFeat. Checks that when in select single and * close mode, only one instance of a feat with a sub-choice is added. */ public void testModFeat() { readyToRun(); final PlayerCharacter character = new PlayerCharacter(); character.setRace(human); character.incrementClassLevel(1, pcClass, true); UIPropertyContext.setSingleChoiceAction(Constants.CHOOSER_SINGLE_CHOICE_METHOD_SELECT_EXIT); ChooserFactory.pushChooserClassname(RandomChooser.class.getName()); is((int) character.getRemainingFeatPoints(true), eq(2), "Start with 2 feats"); try { AbstractCharacterTestCase.applyAbility(character, AbilityCategory.FEAT, toughness, ""); is((int) character.getRemainingFeatPoints(true), eq(1), "Only 1 feat used"); } catch (HeadlessException e) { Logging.debugPrint("Ignoring Headless exception."); } finally { ChooserFactory.popChooserClassname(); } } /** * Test that multiple exotic weapon proficiencies work correctly. */ public void testExoticWpnProf() { readyToRun(); PlayerCharacter character = new PlayerCharacter(); character.setRace(human); assertFalse("Not yet proficient in Weapon A", TestHelper.hasWeaponProfKeyed(character, "Weapon A")); assertFalse("Not yet proficient in Weapon B", TestHelper.hasWeaponProfKeyed(character, "Weapon B")); assertFalse("Not yet proficient in Weapon C", TestHelper.hasWeaponProfKeyed(character, "Weapon C")); character.incrementClassLevel(1, pcClass, true); assertFalse("First Proficient in Weapon A", TestHelper.hasWeaponProfKeyed(character, "Weapon A")); assertTrue("Not yet proficient in Weapon B", TestHelper.hasWeaponProfKeyed(character, "Weapon B")); assertFalse("Not yet proficient in Weapon C", TestHelper.hasWeaponProfKeyed(character, "Weapon C")); character.incrementClassLevel(1, pcClass, true); assertTrue("Second Proficient in Weapon A", TestHelper.hasWeaponProfKeyed(character, "Weapon A")); assertTrue("Proficient in Weapon B", TestHelper.hasWeaponProfKeyed(character, "Weapon B")); assertFalse("Not yet proficient in Weapon C", TestHelper.hasWeaponProfKeyed(character, "Weapon C")); character.incrementClassLevel(1, pcClass, true); assertTrue("Third Proficient in Weapon A", TestHelper.hasWeaponProfKeyed(character, "Weapon A")); assertTrue("Proficient in Weapon B", TestHelper.hasWeaponProfKeyed(character, "Weapon B")); assertTrue("Proficient in Weapon C", TestHelper.hasWeaponProfKeyed(character, "Weapon C")); } /** * Tests CL variable * @throws Exception */ public void testGetClassVar() throws Exception { readyToRun(); //Logging.setDebugMode(true); Logging.debugPrint("\n\n\ntestGetClassVar()"); final PlayerCharacter character = new PlayerCharacter(); character.setRace(human); character.incrementClassLevel(2, classWarmind, true); final Float result = character.getVariableValue("var(\"CL=Warmind\")", ""); assertEquals("CL count not correct", 2.0, result.doubleValue(), 0.1); } /** * Test the processing of the MAX function with respect to character stats. */ public void testMaxValue() { readyToRun(); PlayerCharacter pc = getCharacter(); LoadContext context = Globals.getContext(); setPCStat(pc, str, 8); setPCStat(pc, dex, 14); pc.setUseTempMods(true); assertEquals("STR", -1.0, pc.getVariableValue("STR", ""), 0.1); assertEquals("DEX", 2.0, pc.getVariableValue("DEX", ""), 0.1); assertEquals("max(STR,DEX)", 2.0, pc.getVariableValue("max(STR,DEX)", ""), 0.1); StatToken statTok = new StatToken(); assertEquals("Total stat.", "14", statTok.getToken("STAT.1", pc, null)); assertEquals("Temp stat.", "14", statTok.getToken("STAT.1.NOEQUIP", pc, null)); assertEquals("Equip stat.", "14", statTok.getToken("STAT.1.NOTEMP", pc, null)); assertEquals("No equip/temp stat.", "14", statTok.getToken( "STAT.1.NOEQUIP.NOTEMP", pc, null)); assertEquals("Base stat.", "14", statTok.getToken( "STAT.1.NOEQUIP.NOTEMP", pc, null)); final BonusObj raceBonus = Bonus.newBonus(context, "STAT|DEX|-2"); giantClass.addToListFor(ListKey.BONUS, raceBonus); pc.setRace(giantRace); pc.incrementClassLevel(4, giantClass, true); assertEquals("Total stat.", "12", statTok.getToken("STAT.1", pc, null)); assertEquals("Temp stat.", "12", statTok.getToken("STAT.1.NOEQUIP", pc, null)); assertEquals("Base stat.", "12", statTok.getToken( "STAT.1.NOEQUIP.NOTEMP", pc, null)); assertEquals("DEX", 1.0, pc.getVariableValue("DEX", ""), 0.1); assertEquals("max(STR,DEX)", 1.0, pc.getVariableValue("max(STR,DEX)", ""), 0.1); Spell spell2 = new Spell(); spell2.setName("Concrete Boots"); final BonusObj aBonus = Bonus.newBonus(context, "STAT|DEX|-2"); if (aBonus != null) { spell2.addToListFor(ListKey.BONUS, aBonus); } BonusObj penalty = spell2.getRawBonusList(pc).get(0); pc.addTempBonus(penalty, spell2, pc); pc.calcActiveBonuses(); assertEquals("Total stat.", "10", statTok.getToken("STAT.1", pc, null)); assertEquals("Temp stat.", "10", statTok.getToken("STAT.1.NOEQUIP", pc, null)); assertEquals("Base stat.", "12", statTok.getToken( "STAT.1.NOEQUIP.NOTEMP", pc, null)); assertEquals("DEX", 0.0, pc.getVariableValue("DEX", ""), 0.1); assertEquals("max(STR,DEX)-STR", 1.0, pc.getVariableValue( "max(STR,DEX)-STR", ""), 0.1); } /** * Test the skills visibility functionality. We want to ensure that * each call retrieves the right set of skills. */ public void testSkillsVisibility() { readyToRun(); PlayerCharacter pc = getCharacter(); Skill guiSkill = new Skill(); Skill outputSkill = new Skill(); Skill defaultSkill = new Skill(); LoadContext context = Globals.getContext(); context.unconditionallyProcess(guiSkill, "CLASSES", "MyClass"); guiSkill.setName("GUI"); guiSkill.addToListFor(ListKey.TYPE, Type.getConstant("INT")); guiSkill.put(ObjectKey.VISIBILITY, Visibility.DISPLAY_ONLY); SkillRankControl.modRanks(1.0, pcClass, true, pc, guiSkill); context.unconditionallyProcess(outputSkill, "CLASSES", "MyClass"); outputSkill.setName("Output"); outputSkill.addToListFor(ListKey.TYPE, Type.getConstant("INT")); outputSkill.put(ObjectKey.VISIBILITY, Visibility.OUTPUT_ONLY); SkillRankControl.modRanks(1.0, pcClass, true, pc, outputSkill); context.unconditionallyProcess(defaultSkill, "CLASSES", "MyClass"); defaultSkill.setName("Default"); defaultSkill.addToListFor(ListKey.TYPE, Type.getConstant("INT")); defaultSkill.put(ObjectKey.VISIBILITY, Visibility.DEFAULT); SkillRankControl.modRanks(1.0, pcClass, true, pc, defaultSkill); // Test retrieved list Collection<Skill> skillList = pc.getSkillSet(); assertEquals("Full skill list should have all 3 skills", 3, skillList .size()); skillList = pc.getDisplay().getPartialSkillList(View.VISIBLE_DISPLAY); assertEquals("GUI skill list should have 2 skills", 2, skillList.size()); skillList = pc.getDisplay().getPartialSkillList(View.VISIBLE_EXPORT); assertEquals("Output skill list should have 2 skills", 2, skillList .size()); skillList = pc.getDisplay().getPartialSkillList(View.ALL); assertEquals("Full skill list should have 3 skills", 3, skillList .size()); } /** * Tests adding a spell. */ public void testAddSpells() { readyToRun(); final PlayerCharacter character = new PlayerCharacter(); character.setRace(human); character.incrementClassLevel(1, pcClass, true); final List<Ability> none = Collections.emptyList(); String response = character .addSpell(null, none, pcClass.getKeyName(), null, 1, 1); assertEquals("Add spell should be rejected due to no spell", "Invalid parameter to add spell", response); Spell spell = new Spell(); spell.setName("test spell 1"); CharacterSpell charSpell = new CharacterSpell(pcClass, spell); response = character.addSpell(charSpell, none, pcClass.getKeyName(), null, 1, 1); assertEquals("Add spell should be rejected due to no book", "Invalid spell list/book name.", response); response = character.addSpell(charSpell, none, pcClass.getKeyName(), "", 1, 1); assertEquals("Add spell should be rejected due to no book", "Invalid spell list/book name.", response); // Add a non existant spell to a non existent spellbook String spellBookName = "Test book"; response = character.addSpell(charSpell, none, pcClass.getKeyName(), spellBookName, 1, 1); assertEquals("Add spell should be rejected due to book not existing", "Could not find spell list/book Test book", response); character.addSpellBook(spellBookName); response = character.addSpell(charSpell, none, pcClass.getKeyName(), spellBookName, 1, 1); assertEquals( "Add spell should be rejected due to no levels.", "You can only prepare 0 spells for level 1 \nand there are no higher-level slots available.", response); response = character.addSpell(charSpell, none, "noclass", spellBookName, 1, 1); assertEquals("Add spell should be rejected due to no matching class", "No class keyed noclass", response); SpellBook book = character.getSpellBookByName(spellBookName); book.setType(SpellBook.TYPE_PREPARED_LIST); character.addSpellBook(spellBookName); response = character.addSpell(charSpell, none, pcClass.getKeyName(), spellBookName, 1, 1); assertEquals( "Add spell should be rejected due to no levels.", "You can only prepare 0 spells for level 1 \nand there are no higher-level slots available.", response); book.setType(SpellBook.TYPE_SPELL_BOOK); book.setPageFormula(FormulaFactory.getFormulaFor("SPELLLEVEL")); book.setNumPages(3); character.addSpellBook(spellBookName); response = character.addSpell(charSpell, none, pcClass.getKeyName(), spellBookName, 1, 1); assertEquals("Add spell should not be rejected.", "", response); // Add a second time to cover multiples response = character.addSpell(charSpell, none, pcClass.getKeyName(), spellBookName, 1, 1); assertEquals("Add spell should not be rejected.", "", response); response = character.addSpell(charSpell, none, giantClass.getKeyName(), spellBookName, 1, 1); assertEquals("Add spell should not be rejected.", "", response); response = character.addSpell(charSpell, none, giantClass.getKeyName(), spellBookName, 1, 1); assertEquals( "Add spell should be rejected due to the book being full.", "There are not enough pages left to add this spell to the spell book.", response); PCClass c = character.getClassKeyed(pcClass.getKeyName()); List<CharacterSpell> aList = character.getCharacterSpells(c, null, spellBookName, 1); CharacterSpell addedSpell = aList.get(0); response = character.delSpell(addedSpell.getSpellInfoFor(spellBookName, 1, none), pcClass, spellBookName); assertEquals("Delete spell should not be rejected.", "", response); aList = character.getCharacterSpells(giantClass, null, spellBookName, 1); addedSpell = aList.get(0); response = character.delSpell( addedSpell.getSpellInfoFor(spellBookName, 1), giantClass, spellBookName); assertEquals("Delete spell should not be rejected.", "", response); } /** * Tests available spell slot calculations for a divine caster who * memorizes spells. */ public void testAvailableSpellsMemorizedDivine() { readyToRun(); final PlayerCharacter character = new PlayerCharacter(); character.setRace(human); character.setStat(wis, 15); character.incrementClassLevel(1, classMemDivine, true); PCClass pcMdClass = character.getClassKeyed(classMemDivine.getKeyName()); Spell spellNonSpec0 = new Spell(); spellNonSpec0.setName("Basic Spell Lvl0"); CharacterSpell charSpellNonSpec0 = new CharacterSpell(pcMdClass, spellNonSpec0); Spell spellNonSpec1 = new Spell(); spellNonSpec1.setName("Basic Spell Lvl1"); CharacterSpell charSpellNonSpec1 = new CharacterSpell(pcMdClass, spellNonSpec1); Spell spellNonSpec2 = new Spell(); spellNonSpec2.setName("Basic Spell Lvl2"); CharacterSpell charSpellNonSpec2 = new CharacterSpell(pcMdClass, spellNonSpec2); final List<Ability> none = Collections.emptyList(); boolean available = character.availableSpells(1, pcMdClass, Globals.getDefaultSpellBook(), true, false); assertEquals("availableSpells should not be called when there ar eno limits on known spells", false, available); // Test specialty/non with no spells, some spells, all spells, spells from lower level String spellBookName = "Town Spells"; SpellBook townSpells = new SpellBook(spellBookName, SpellBook.TYPE_PREPARED_LIST); assertTrue("Adding spellbook " + townSpells, character.addSpellBook(townSpells)); assertTrue("Adding domain " + luckDomain, character.addDomain(luckDomain)); DomainApplication.applyDomain(character, luckDomain); // Test for spell availability with no spells in list for (int i = 0; i < 3; i++) { assertEquals("Empty list - Non specialty available for level " + i, true, character.availableSpells(i, pcMdClass, townSpells.getName(), false, false)); assertEquals("Empty list - Specialty available for level " + i, i>0, character.availableSpells(i, pcMdClass, townSpells.getName(), false, true)); } // Test for spell availability with some spells in list assertEquals("", character.addSpell(charSpellNonSpec0, none, pcMdClass.getKeyName(), spellBookName, 0, 0)); assertEquals("", character.addSpell(charSpellNonSpec1, none, pcMdClass.getKeyName(), spellBookName, 1, 1)); assertEquals("", character.addSpell(charSpellNonSpec2, none, pcMdClass.getKeyName(), spellBookName, 2, 2)); for (int i = 0; i < 3; i++) { assertEquals("Partial list - Non specialty available for level " + i, true, character.availableSpells(i, pcMdClass, townSpells.getName(), false, false)); assertEquals("Partial list - Specialty available for level " + i, i>0, character.availableSpells(i, pcMdClass, townSpells.getName(), false, true)); } // Test for spell availability with only 1st level with a spare slot assertEquals("", character.addSpell(charSpellNonSpec0, none, pcMdClass.getKeyName(), spellBookName, 0, 0)); assertEquals("", character.addSpell(charSpellNonSpec0, none, pcMdClass.getKeyName(), spellBookName, 0, 0)); assertEquals("", character.addSpell(charSpellNonSpec2, none, pcMdClass.getKeyName(), spellBookName, 2, 2)); for (int i = 0; i < 3; i++) { assertEquals("Full lvl0, lvl2 list - Non specialty available for level " + i, i==1, character.availableSpells(i, pcMdClass, townSpells.getName(), false, false)); //TODO: The current implementation only finds the domain specialty slot if a domain spell is already prepared. // So the domain spell can't be the last added. Once fixed, i==1 should be i>=1 assertEquals("Full lvl0, lvl2 list - Specialty available for level " + i, i>=1, character.availableSpells(i, pcMdClass, townSpells.getName(), false, true)); } // Test for spell availability with 1st having one domain spell full and one non domain free CharacterSpell charSpellSpec1 = new CharacterSpell(luckDomain, luckDomainLvl1Spell); assertEquals("", character.addSpell(charSpellSpec1, none, pcMdClass.getKeyName(), spellBookName, 1, 1)); for (int i = 0; i < 3; i++) { assertEquals("Specialty: No, Level: " + i + ". 1st lvl non domain only free", i==1, character.availableSpells(i, pcMdClass, townSpells.getName(), false, false)); assertEquals("Specialty: Yes, Level: " + i + ". 1st lvl non domain only free", i==2, character.availableSpells(i, pcMdClass, townSpells.getName(), false, true)); } // Test for spell availability with 2nd having both domain and normal full CharacterSpell charSpellSpec2 = new CharacterSpell(luckDomain, luckDomainLvl2Spell); assertEquals("", character.addSpell(charSpellSpec2, none, pcMdClass.getKeyName(), spellBookName, 2, 2)); for (int i = 0; i < 3; i++) { assertEquals("Specialty: No, Level: " + i + ". 1st lvl non domain only free", i==1, character.availableSpells(i, pcMdClass, townSpells.getName(), false, false)); assertEquals("Specialty: Yes, Level: " + i + ". 1st lvl non domain only free", false, character.availableSpells(i, pcMdClass, townSpells.getName(), false, true)); } } public void testIsNonAbility() { readyToRun(); PlayerCharacter pc = getCharacter(); //Base assertEquals("Initially character should not have a locked ability", false, pc.isNonAbility(str)); // With template lock PCTemplate nonAbilityLocker = new PCTemplate(); nonAbilityLocker.setName("locker"); CDOMDirectSingleRef<PCStat> strRef = CDOMDirectSingleRef.getRef(str); nonAbilityLocker.addToListFor(ListKey.NONSTAT_STATS, strRef); pc.addTemplate(nonAbilityLocker); assertEquals("STR now locked to non ability", true, pc.isNonAbility(str)); pc.removeTemplate(nonAbilityLocker); assertEquals("STR no longer locked to non ability", false, pc.isNonAbility(str)); // With race lock Race nonAbilityLockerRace = new Race(); nonAbilityLockerRace.setName("locker"); nonAbilityLockerRace.addToListFor(ListKey.NONSTAT_STATS, strRef); pc.setRace(nonAbilityLockerRace); assertEquals("STR now locked to non ability", true, pc.isNonAbility(str)); // With template unlock nonAbilityLocker.addToListFor(ListKey.NONSTAT_TO_STAT_STATS, strRef); pc.addTemplate(nonAbilityLocker); assertEquals("STR now unlocked from a non ability by template", false, pc.isNonAbility(str)); pc.removeTemplate(nonAbilityLocker); assertEquals("STR no longer locked to non ability", true, pc.isNonAbility(str)); // With race unlock nonAbilityLockerRace.addToListFor(ListKey.NONSTAT_TO_STAT_STATS, strRef); //This weirdness is because we are altering the race after application (no-no at runtime) pc.setRace(null); pc.setRace(nonAbilityLockerRace); assertEquals("STR now unlocked from a non ability by race", false, pc.isNonAbility(str)); } /** * Test the stacking of the same ability added via different abiltiy * categories. */ public void testStackDifferentAbiltyCat() { readyToRun(); PlayerCharacter pc = getCharacter(); double base = pc.getTotalBonusTo("HP", "CURRENTMAX"); assertEquals("Check repeatability of bonus", base, pc.getTotalBonusTo( "HP", "CURRENTMAX")); try { AbstractCharacterTestCase.applyAbility(pc, AbilityCategory.FEAT, toughness, ""); //pc.calcActiveBonuses(); assertEquals("Check application of single bonus", base+3, pc.getTotalBonusTo( "HP", "CURRENTMAX")); AbstractCharacterTestCase.applyAbility(pc, AbilityCategory.FEAT, toughness, ""); pc.calcActiveBonuses(); assertEquals("Check application of second bonus", base+6, pc.getTotalBonusTo( "HP", "CURRENTMAX")); AbstractCharacterTestCase.applyAbility(pc, specialFeatCat, toughness, "Toughness"); pc.calcActiveBonuses(); assertEquals( "Check application of third bonus in different catgeory", base + 9, pc.getTotalBonusTo("HP", "CURRENTMAX")); } catch (HeadlessException e) { Logging.debugPrint("Ignoring Headless exception."); } } /** * Verify that bested abilities are processed correctly. */ public void testNestedAbilities() { PlayerCharacter pc = getCharacter(); Ability resToAcid = TestHelper.makeAbility("Resistance To Acid", specialAbilityCat, "Foo"); Ability resToAcidOutputVirt = TestHelper.makeAbility("Resistance To Acid Output Virt", specialAbilityCat, "Foo"); Ability resToAcidOutputAuto = TestHelper.makeAbility("Resistance To Acid Output Auto", specialAbilityCat, "Foo"); LoadContext context = Globals.getContext(); context.unconditionallyProcess(human, "ABILITY", specialAbilityCat .getKeyName() + "|AUTOMATIC|" + resToAcid.getKeyName()); context.unconditionallyProcess(resToAcid, "ABILITY", specialAbilityCat .getKeyName() + "|VIRTUAL|" + resToAcidOutputVirt.getKeyName()); context.unconditionallyProcess(resToAcid, "ABILITY", specialAbilityCat .getKeyName() + "|AUTOMATIC|" + resToAcidOutputAuto.getKeyName()); readyToRun(); pc.setRace(human); assertEquals("PC should now have a race of human", human, pc.getRace()); assertFalse("Character should have the first feat", pc.getMatchingCNAbilities(resToAcid).isEmpty()); assertFalse("Character should have the second feat", pc.getMatchingCNAbilities(resToAcidOutputVirt).isEmpty()); assertFalse("Character should have the third feat", pc.getMatchingCNAbilities(resToAcidOutputAuto).isEmpty()); } public void testGetPartialStatFor() { readyToRun(); PlayerCharacter pc = getCharacter(); LoadContext context = Globals.getContext(); setPCStat(pc, str, 14); Ability strBonusAbility = TestHelper.makeAbility("Strength power up", AbilityCategory.FEAT, "General.Fighter"); final BonusObj strBonus = Bonus.newBonus(context, "STAT|STR|2"); strBonusAbility.addToListFor(ListKey.BONUS, strBonus); assertEquals("Before bonus, no temp no equip", 14, pc.getPartialStatFor(str, false, false)); assertEquals("Before bonus, temp no equip", 14, pc.getPartialStatFor(str, true, false)); AbstractCharacterTestCase.applyAbility(pc, AbilityCategory.FEAT, strBonusAbility, null); pc.calcActiveBonuses(); assertEquals("After bonus, no temp no equip", 16, pc.getPartialStatFor(str, false, false)); assertEquals("After bonus, temp no equip", 16, pc.getPartialStatFor(str, true, false)); // final BonusObj strBonusViaList = Bonus.newBonus("STAT|%LIST|3"); // strBonusAbility.addBonusList(strBonusViaList); // strBonusAbility.addAssociated("STR"); // strBonusAbility.put(ObjectKey.MULTIPLE_ALLOWED, Boolean.TRUE); // pc.calcActiveBonuses(); // // assertEquals("After list bonus, no temp no equip", 3, pc.getPartialStatBonusFor("STR", false, false)); // assertEquals("After list bonus, temp no equip", 3, pc.getPartialStatBonusFor("STR", true, false)); } /** * Validate the getAvailableFollowers function. */ public void testGetAvailableFollowers() { readyToRun(); Ability ab = TestHelper.makeAbility("Tester1", AbilityCategory.FEAT, "Empty Container"); Ability mab = TestHelper.makeAbility("Tester2", AbilityCategory.FEAT, "Mount Container"); Ability fab = TestHelper.makeAbility("Tester3", AbilityCategory.FEAT, "Familiar Container"); PlayerCharacter pc = getCharacter(); CharacterDisplay display = pc.getDisplay(); addAbility(AbilityCategory.FEAT, ab); CDOMSingleRef<CompanionList> ref = new CDOMSimpleSingleRef<>( CompanionList.class, "Mount"); CDOMReference<Race> race = new CDOMDirectSingleRef<>(giantRace); FollowerOption option = new FollowerOption(race, ref); mab.addToListFor(ListKey.COMPANIONLIST, option); ref = new CDOMSimpleSingleRef<>( CompanionList.class, "Familiar"); race = new CDOMDirectSingleRef<>(human); option = new FollowerOption(race, ref); fab.addToListFor(ListKey.COMPANIONLIST, option); Set<FollowerOption> fo = display.getAvailableFollowers("Familiar", null).keySet(); assertTrue("Initially familiar list should be empty", fo.isEmpty()); fo = display.getAvailableFollowers("MOUNT", null).keySet(); assertTrue("Initially mount list should be empty", fo.isEmpty()); addAbility(AbilityCategory.FEAT, mab); fo = display.getAvailableFollowers("Familiar", null).keySet(); assertTrue("Familiar list should still be empty", fo.isEmpty()); fo = display.getAvailableFollowers("MOUNT", null).keySet(); assertFalse("Mount list should not be empty anymore", fo.isEmpty()); assertEquals("Mount should be the giant race", giantRace.getKeyName(), fo.iterator().next().getRace().getKeyName()); assertEquals("Mount list should only have one entry", 1, fo.size()); addAbility(AbilityCategory.FEAT, fab); fo = display.getAvailableFollowers("Familiar", null).keySet(); assertFalse("Familiar list should not be empty anymore", fo.isEmpty()); assertEquals("Familiar should be the human race", human.getKeyName(), fo.iterator().next().getRace().getKeyName()); assertEquals("Familiar list should only have one entry", 1, fo.size()); fo = display.getAvailableFollowers("MOUNT", null).keySet(); assertFalse("Mount list should not be empty anymore", fo.isEmpty()); assertEquals("Mount should be the giant race", giantRace.getKeyName(), fo.iterator().next().getRace().getKeyName()); assertEquals("Mount list should only have one entry", 1, fo.size()); } public void testGetAggregateAbilityList() { Ability resToAcid = TestHelper.makeAbility("Swelter", AbilityCategory.FEAT.getKeyName(), "Foo"); LoadContext context = Globals.getContext(); context.unconditionallyProcess(resToAcid, "MULT", "YES"); context.unconditionallyProcess(resToAcid, "STACK", "YES"); context.unconditionallyProcess(resToAcid, "CHOOSE", "NOCHOICE"); PCTemplate template = TestHelper.makeTemplate("TemplateVirt"); PCTemplate templateNorm = TestHelper.makeTemplate("TemplateNorm"); context.getReferenceContext().importObject(resToAcid); context.unconditionallyProcess(human, "ABILITY", "FEAT|AUTOMATIC|KEY_Swelter"); context.unconditionallyProcess(template, "ABILITY", "FEAT|VIRTUAL|KEY_Swelter"); context.unconditionallyProcess(templateNorm, "ABILITY", "FEAT|NORMAL|KEY_Swelter"); readyToRun(); PlayerCharacter pc = getCharacter(); List<Ability> abList = pc.getAggregateAbilityListNoDuplicates(AbilityCategory.FEAT); assertEquals(0, abList.size()); pc.setRace(human); abList = pc.getAggregateAbilityListNoDuplicates(AbilityCategory.FEAT); assertEquals(1, abList.size()); pc.addTemplate(template); abList = pc.getAggregateAbilityListNoDuplicates(AbilityCategory.FEAT); assertEquals(1, abList.size()); pc.addTemplate(templateNorm); abList = pc.getAggregateAbilityListNoDuplicates(AbilityCategory.FEAT); assertEquals(1, abList.size()); } /** * Test the processing and order of operations of the adjustMoveRates method. */ public void testAdjustMoveRates() { Ability quickFlySlowSwim = TestHelper.makeAbility("quickFlySlowSwim", AbilityCategory.FEAT .getKeyName(), "Foo"); PCTemplate template = TestHelper.makeTemplate("slowFlyQuickSwim"); PCTemplate template2 = TestHelper.makeTemplate("dig"); LoadContext context = Globals.getContext(); final BonusObj digBonus = Bonus.newBonus(context, "MOVEADD|TYPE.Dig|60"); assertNotNull("Failed to create bonus", digBonus); template2.addToListFor(ListKey.BONUS, digBonus); //template.addm context.getReferenceContext().importObject(quickFlySlowSwim); context.getReferenceContext().importObject(template2); context.unconditionallyProcess(human, "MOVE", "Walk,30"); context.unconditionallyProcess(quickFlySlowSwim, "MOVE", "Swim,10,Fly,30"); context.unconditionallyProcess(template, "MOVE", "Swim,30,Fly,10"); readyToRun(); GameMode game = SettingsHandler.getGame(); LoadInfo li = game.getModeContext().getReferenceContext().constructNowIfNecessary( LoadInfo.class, game.getName()); li.addLoadScoreValue(0, new BigDecimal("100.0")); li.addLoadScoreValue(10, new BigDecimal("100.0")); li.addLoadMultiplier("LIGHT", new Float(100), "100", 0); PlayerCharacter pc = getCharacter(); setPCStat(pc, str, 10); pc.setRace(human); pc.calcActiveBonuses(); pc.adjustMoveRates(); CharacterDisplay display = pc.getDisplay(); assertEquals(0.0, display.movementOfType("Swim"), 0.1); assertEquals(0.0, display.movementOfType("Fly"), 0.1); addAbility(AbilityCategory.FEAT, quickFlySlowSwim); pc.calcActiveBonuses(); pc.adjustMoveRates(); assertEquals(10.0, display.movementOfType("Swim"), 0.1); assertEquals(30.0, display.movementOfType("Fly"), 0.1); pc.addTemplate(template); pc.adjustMoveRates(); assertEquals(30.0, display.movementOfType("Swim"), 0.1); assertEquals(30.0, display.movementOfType("Fly"), 0.1); pc.addTemplate(template2); pc.adjustMoveRates(); assertEquals(30.0, display.movementOfType("Swim"), 0.1); assertEquals(30.0, display.movementOfType("Fly"), 0.1); assertEquals(60.0, display.movementOfType("Dig"), 0.1); } public void testMakeIntoExClass() { // Prepare class and ex-class LoadContext context = Globals.getContext(); PCClass paladin = new PCClass(); paladin.setName("Paladin"); context.getReferenceContext().importObject(paladin); PCClass exPaladin = new PCClass(); exPaladin.setName("exPaladin"); context.getReferenceContext().importObject(exPaladin); paladin.put(ObjectKey.EX_CLASS, context.getReferenceContext().getCDOMReference(PCClass.class, exPaladin.getKeyName())); readyToRun(); PlayerCharacter pc = getCharacter(); // Add a level of the class pc.incrementClassLevel(2, paladin, true, false); PCClass pcPalClass = pc.getClassKeyed(paladin.getKeyName()); pc.setHP(pc.getActiveClassLevel(pcPalClass, 0), 10); pc.setHP(pc.getActiveClassLevel(pcPalClass, 1), 6); // Make it into an ex-class pc.makeIntoExClass(pcPalClass); assertNull("Paladin class should not be held", pc.getClassKeyed(paladin.getKeyName())); PCClass pcExPalClass = pc.getClassKeyed(exPaladin.getKeyName()); assertNotNull("Ex-Paladin class should be held", pcExPalClass); PCClassLevel pcLvl1 = pc.getActiveClassLevel(pcExPalClass, 0); assertNotNull("Level 1 should be Ex-Paladin", pcLvl1); assertEquals("Should still be level 2 character", 2, pc.getTotalLevels()); assertEquals("Hp at first level incorrect", 10, (int)pc.getHP(pcLvl1)); } public void testGetVariableCachingRollTopNode() { readyToRun(); final PlayerCharacter character = new PlayerCharacter(); character.setRace(human); character.setStat(str, 16); character.incrementClassLevel(2, pcClass, true); int iVal = character.getVariableValue("roll(\"1d100\")", "").intValue(); boolean match = true; for (int i = 0; i < 10; i++) { int rolledValue = character.getVariableValue("roll(\"1d100\")", "") .intValue(); match = (iVal == rolledValue); if (!match) { break; } } assertFalse("Roll function should not be cached.", match); } /** * Validate the checkSkillModChange correctly handles non bonused * skill pools. */ public void testCheckSkillModChangeNoBonus() { readyToRun(); PlayerCharacter character = getCharacter(); character.setRace(human); character.setStat(intel, 10); character.incrementClassLevel(2, pcClass, true); List<PCLevelInfo> levelInfoList = new ArrayList<>(character.getLevelInfo()); assertEquals("Level number lvl 1", 1, levelInfoList.get(0) .getClassLevel()); assertEquals("Level number lvl 2", 2, levelInfoList.get(1) .getClassLevel()); assertEquals("Skills remaining lvl 1", 1, levelInfoList.get(0) .getSkillPointsRemaining()); assertEquals("Skills gained lvl 2", 1, levelInfoList.get(1) .getSkillPointsGained(character)); assertEquals("Skills remaining lvl 2", 1, levelInfoList.get(1) .getSkillPointsRemaining()); character.checkSkillModChange(); assertEquals("Skills gained lvl 1", 1, levelInfoList.get(0) .getSkillPointsGained(character)); assertEquals("Skills remaining lvl 1", 1, levelInfoList.get(0) .getSkillPointsRemaining()); assertEquals("Skills gained lvl 2", 1, levelInfoList.get(1) .getSkillPointsGained(character)); assertEquals("Skills remaining lvl 2", 1, levelInfoList.get(1) .getSkillPointsRemaining()); } /** * Validate the checkSkillModChange correctly handles SKILLPOOL bonuses */ public void testCheckSkillModChangeWithBonus() { readyToRun(); PlayerCharacter character = getCharacter(); character.setRace(human); character.setStat(intel, 10); PCTemplate template = TestHelper.makeTemplate("grantsskills"); LoadContext context = Globals.getContext(); final BonusObj skillBonusLvl1 = Bonus.newBonus(context, "SKILLPOOL|CLASS=MyClass;LEVEL=1|2"); assertNotNull("Failed to create bonus", skillBonusLvl1); template.addToListFor(ListKey.BONUS, skillBonusLvl1); character.addTemplate(template); character.incrementClassLevel(2, pcClass, true); List<PCLevelInfo> levelInfoList = new ArrayList<>(character.getLevelInfo()); assertEquals("Level number lvl 1", 1, levelInfoList.get(0) .getClassLevel()); assertEquals("Level number lvl 2", 2, levelInfoList.get(1) .getClassLevel()); assertEquals("Skills gained lvl 1", 3, levelInfoList.get(0) .getSkillPointsGained(character)); assertEquals("Skills remaining lvl 1", 3, levelInfoList.get(0) .getSkillPointsRemaining()); assertEquals("Skills gained lvl 2", 1, levelInfoList.get(1) .getSkillPointsGained(character)); assertEquals("Skills remaining lvl 2", 1, levelInfoList.get(1) .getSkillPointsRemaining()); character.checkSkillModChange(); character.checkSkillModChange(); assertEquals("Skills gained lvl 1", 3, levelInfoList.get(0) .getSkillPointsGained(character)); assertEquals("Skills remaining lvl 1", 3, levelInfoList.get(0) .getSkillPointsRemaining()); assertEquals("Skills gained lvl 2", 1, levelInfoList.get(1) .getSkillPointsGained(character)); assertEquals("Skills remaining lvl 2", 1, levelInfoList.get(1) .getSkillPointsRemaining()); } }