/** * Needs documentation. * * @param pc update skills for this PC * @param aSkill Skill to update * @param aRank Number of ranks to add * @param aCost Cost of added ranks * @param langList Languages to be selected for a language skill * @param pcClass skills apply to this class * @return <code>true</code> for success TODO What about throwing on failure? */ private boolean updatePCSkills( final PlayerCharacter pc, final Skill aSkill, final int aRank, final double aCost, List<Language> langList, final PCClass pcClass) { pc.addSkill(aSkill); boolean oldImporting = pc.isImporting(); pc.setImporting(true); final String aString = SkillRankControl.modRanks(aRank, pcClass, true, pc, aSkill); pc.setImporting(oldImporting); if (aString.length() > 0) { Logging.errorPrint("SKILL: " + aString); return false; } // Add any supplied languages ChoiceManagerList<Language> controller = ChooserUtilities.getConfiguredController(aSkill, pc, null, new ArrayList<String>()); for (Language lang : langList) { if (!controller.conditionallyApply(pc, lang)) { Logging.errorPrint("Failed to apply Language into Skill: " + lang.getLSTformat()); } } // // Fix up the skill pools to reflect what we just spent. // double ptsToSpend = aCost; if (ptsToSpend >= 0.0) { for (PCLevelInfo info : pc.getLevelInfo()) { if (info.getClassKeyName().equals(pcClass.getKeyName())) { // We are spending this class' points. int remaining = info.getSkillPointsRemaining(); if (remaining == 0) { continue; } int left = remaining - (int) Math.min(remaining, ptsToSpend); info.setSkillPointsRemaining(left); ptsToSpend -= (remaining - left); if (ptsToSpend <= 0) { break; } } } } return true; }
/* * REFACTOR This is BAD that this is referring to PCLevelInfo - that gets * VERY confusing as far as object interaction. Can we get rid of * PCLevelInfo altogether? */ public final int getSkillPool(final PlayerCharacter aPC) { int returnValue = 0; // ////////////////////////////////// // Using this method will return skills for level 0 even when there is // no information // Byngl - December 28, 2004 // for (int i = 0; i <= level; i++) // { // final PCLevelInfo pcl = aPC.getLevelInfoFor(getKeyName(), i); // // if ((pcl != null) && pcl.getClassKeyName().equals(getKeyName())) // { // returnValue += pcl.getSkillPointsRemaining(); // } // } for (PCLevelInfo pcl : aPC.getLevelInfo()) { if (pcl.getClassKeyName().equals(getKeyName())) { returnValue += pcl.getSkillPointsRemaining(); } } // ////////////////////////////////// return returnValue; }
void subLevel(final PlayerCharacter aPC) { if (aPC != null) { int total = aPC.getTotalLevels(); int oldLevel = aPC.getLevel(this); int spMod = 0; final PCLevelInfo pcl = aPC.getLevelInfoFor(getKeyName(), oldLevel); if (pcl != null) { spMod = pcl.getSkillPointsGained(aPC); } else { Logging.errorPrint( "ERROR: could not find class/level info for " + getDisplayName() + "/" + oldLevel); } final int newLevel = oldLevel - 1; if (oldLevel > 0) { PCClassLevel classLevel = aPC.getActiveClassLevel(this, oldLevel - 1); aPC.removeHP(classLevel); } // aPC.adjustFeats(-aPC.getBonusFeatsForNewLevel(this)); setLevel(newLevel, aPC); aPC.removeKnownSpellsForClassLevel(this); doMinusLevelMods(aPC, newLevel + 1); DomainApplication.removeDomainsForLevel(this, newLevel + 1, aPC); if (newLevel == 0) { SubClassApplication.setSubClassKey(aPC, this, Constants.NONE); // // Remove all skills associated with this class // for (Skill skill : aPC.getSkillSet()) { SkillRankControl.setZeroRanks(this, aPC, skill); } Integer currentPool = aPC.getSkillPool(this); spMod = currentPool == null ? 0 : currentPool; } if (!isMonster() && (total > aPC.getTotalLevels())) { total = aPC.getTotalLevels(); // Roll back any stat changes that were made as part of the // level final List<PCLevelInfoStat> moddedStats = new ArrayList<>(); if (pcl.getModifiedStats(true) != null) { moddedStats.addAll(pcl.getModifiedStats(true)); } if (pcl.getModifiedStats(false) != null) { moddedStats.addAll(pcl.getModifiedStats(false)); } if (!moddedStats.isEmpty()) { for (PCLevelInfoStat statToRollback : moddedStats) { for (PCStat aStat : aPC.getStatSet()) { if (aStat.equals(statToRollback.getStat())) { aPC.setStat(aStat, aPC.getStat(aStat) - statToRollback.getStatMod()); break; } } } } } aPC.setLevelWithoutConsequence(this, newLevel); if (isMonster() || (total != 0)) { Integer currentPool = aPC.getSkillPool(this); int newSkillPool = (currentPool == null ? 0 : currentPool) - spMod; aPC.setSkillPool(this, newSkillPool); aPC.setDirty(true); } if (aPC.getLevel(this) == 0) { aPC.removeClass(this); } aPC.validateCharacterDomains(); if (!aPC.isImporting()) { final int maxxp = aPC.minXPForNextECL(); if (aPC.getXP() >= maxxp) { aPC.setXP(Math.max(maxxp - 1, 0)); } } } else { Logging.errorPrint("No current pc in subLevel()? How did this happen?"); return; } }
/* * REFACTOR Clearly this is part of the PCClass factory method that produces * PCClassLevels combined with some other work that will need to be done to * extract some of the complicated gunk out of here that goes out and puts * information into PCLevelInfo and PlayerCharacter. */ public boolean addLevel( final boolean argLevelMax, final boolean bSilent, final PlayerCharacter aPC, final boolean ignorePrereqs) { // Check to see if we can add a level of this class to the // current character final int newLevel = aPC.getLevel(this) + 1; boolean levelMax = argLevelMax; aPC.setAllowInteraction(false); aPC.setLevelWithoutConsequence(this, newLevel); if (!ignorePrereqs) { // When loading a character, classes are added before feats, so // this test would always fail on loading if feats are required boolean doReturn = false; if (!qualifies(aPC, this)) { doReturn = true; if (!bSilent) { ShowMessageDelegate.showMessageDialog( "This character does not qualify for level " + newLevel, Constants.APPLICATION_NAME, MessageType.ERROR); } } aPC.setLevelWithoutConsequence(this, newLevel - 1); if (doReturn) { return false; } } aPC.setAllowInteraction(true); if (isMonster()) { levelMax = false; } if (hasMaxLevel() && (newLevel > getSafe(IntegerKey.LEVEL_LIMIT)) && levelMax) { if (!bSilent) { ShowMessageDelegate.showMessageDialog( "This class cannot be raised above level " + Integer.toString(getSafe(IntegerKey.LEVEL_LIMIT)), Constants.APPLICATION_NAME, MessageType.ERROR); } return false; } // Add the level to the current character int total = aPC.getTotalLevels(); // No longer need this since the race now sets a bonus itself and Templates // are not able to reassign their feats. There was nothing else returned in // this number // if (total == 0) { // aPC.setFeats(aPC.getInitialFeats()); // } setLevel(newLevel, aPC); // the level has now been added to the character, // so now assign the attributes of this class level to the // character... PCClassLevel classLevel = aPC.getActiveClassLevel(this, newLevel); // Make sure that if this Class adds a new domain that // we record where that domain came from final int dnum = aPC.getMaxCharacterDomains(this, aPC) - aPC.getDomainCount(); if (dnum > 0 && !aPC.hasDefaultDomainSource()) { aPC.setDefaultDomainSource(new ClassSource(this, newLevel)); } // Don't roll the hit points if the gui is not being used. // This is so GMGen can add classes to a person without pcgen flipping // out if (Globals.getUseGUI()) { final int levels = SettingsHandler.isHPMaxAtFirstClassLevel() ? aPC.totalNonMonsterLevels() : aPC.getTotalLevels(); final boolean isFirst = levels == 1; aPC.rollHP(this, aPC.getLevel(this), isFirst); } if (!aPC.isImporting()) { DomainApplication.addDomainsUpToLevel(this, newLevel, aPC); } int levelUpStats = 0; // Add any bonus feats or stats that will be gained from this level // i.e. a bonus feat every 3 levels if (aPC.getTotalLevels() > total) { boolean processBonusStats = true; total = aPC.getTotalLevels(); if (isMonster()) { // If we have less levels that the races monster levels // then we can not give a stat bonus (i.e. an Ogre has // 4 levels of Giant, so it does not get a stat increase at // 4th level because that is already taken into account in // its racial stat modifiers, but it will get one at 8th LevelCommandFactory lcf = aPC.getRace().get(ObjectKey.MONSTER_CLASS); int monLevels = 0; if (lcf != null) { monLevels = lcf.getLevelCount().resolve(aPC, "").intValue(); } if (total <= monLevels) { processBonusStats = false; } } if (!aPC.isImporting()) { // We do not want to do these // calculations a second time when are // importing a character. The feat // number and the stat point pool are // already saved in the import file. // if (processBonusFeats) { // final double bonusFeats = aPC.getBonusFeatsForNewLevel(this); // if (bonusFeats > 0) { // aPC.adjustFeats(bonusFeats); // } // } if (processBonusStats) { final int bonusStats = Globals.getBonusStatsForLevel(total, aPC); if (bonusStats > 0) { aPC.setPoolAmount(aPC.getPoolAmount() + bonusStats); if (!bSilent && SettingsHandler.getShowStatDialogAtLevelUp()) { levelUpStats = StatApplication.askForStatIncrease(aPC, bonusStats, true); } } } } } int spMod = getSkillPointsForLevel(aPC, classLevel, total); PCLevelInfo pcl; if (aPC.getLevelInfoSize() > 0) { pcl = aPC.getLevelInfo(aPC.getLevelInfoSize() - 1); if (pcl != null) { pcl.setClassLevel(aPC.getLevel(this)); pcl.setSkillPointsGained(aPC, spMod); pcl.setSkillPointsRemaining(pcl.getSkillPointsGained(aPC)); } } Integer currentPool = aPC.getSkillPool(this); int newSkillPool = spMod + (currentPool == null ? 0 : currentPool); aPC.setSkillPool(this, newSkillPool); if (!aPC.isImporting()) { // // Ask for stat increase after skill points have been calculated // if (levelUpStats > 0) { StatApplication.askForStatIncrease(aPC, levelUpStats, false); } if (newLevel == 1) { AddObjectActions.doBaseChecks(this, aPC); CDOMObjectUtilities.addAdds(this, aPC); CDOMObjectUtilities.checkRemovals(this, aPC); } for (TransitionChoice<Kit> kit : classLevel.getSafeListFor(ListKey.KIT_CHOICE)) { kit.act(kit.driveChoice(aPC), classLevel, aPC); } TransitionChoice<Region> region = classLevel.get(ObjectKey.REGION_CHOICE); if (region != null) { region.act(region.driveChoice(aPC), classLevel, aPC); } } // this is a monster class, so don't worry about experience if (isMonster()) { return true; } if (!aPC.isImporting()) { CDOMObjectUtilities.checkRemovals(this, aPC); final int minxp = aPC.minXPForECL(); if (aPC.getXP() < minxp) { aPC.setXP(minxp); } else if (aPC.getXP() >= aPC.minXPForNextECL()) { if (!bSilent) { ShowMessageDelegate.showMessageDialog( SettingsHandler.getGame().getLevelUpMessage(), Constants.APPLICATION_NAME, MessageType.INFORMATION); } } } // // Allow exchange of classes only when assign 1st level // if (containsKey(ObjectKey.EXCHANGE_LEVEL) && (aPC.getLevel(this) == 1) && !aPC.isImporting()) { ExchangeLevelApplication.exchangeLevels(aPC, this); } return true; }
private KitSkillAdd addRanks( PlayerCharacter pc, PCClass pcClass, Skill aSkill, double ranksLeftToAdd, boolean isFree, List<String> warnings) { if (!isFree && pcClass.getSkillPool(pc) == 0) { return null; } double curRank = 0.0; if (pc.hasSkill(aSkill)) { curRank = pc.getRank(aSkill).doubleValue(); } double ranksToAdd = ranksLeftToAdd; if (!Globals.checkRule(RuleConstants.SKILLMAX) && (ranksToAdd > 0.0)) { ranksToAdd = Math.min(pc.getMaxRank(aSkill, pcClass).doubleValue(), curRank + ranksLeftToAdd); ranksToAdd -= curRank; if (!CoreUtility.doublesEqual(ranksToAdd, ranksLeftToAdd)) { warnings.add( "SKILL: Could not add " + (ranksLeftToAdd - ranksToAdd) + " to " + aSkill.getDisplayName() + ". Exceeds MAXRANK of " + pc.getMaxRank(aSkill, pcClass) + "."); } } int ptsToSpend = 0; int[] points = new int[pc.getLevelInfoSize()]; if (!isFree) { double ranksAdded = 0.0; int skillCost = pc.getSkillCostForClass(aSkill, pcClass).getCost(); ptsToSpend = (int) (ranksToAdd * skillCost); for (int i = 0; i < pc.getLevelInfoSize(); i++) { PCLevelInfo info = pc.getLevelInfo(i); if (info.getClassKeyName().equals(pcClass.getKeyName())) { // We are spending this class' points. points[i] = info.getSkillPointsRemaining(); } else { points[i] = -1; } } for (int i = 0; i < points.length; i++) { int remaining = points[i]; if (remaining <= 0) { continue; } int left = remaining - Math.min(remaining, ptsToSpend); points[i] = left; int spent = (remaining - left); ptsToSpend -= spent; ranksAdded += ((double) spent / (double) skillCost); if (ranksAdded == ranksToAdd || ptsToSpend <= 0) { break; } } ranksToAdd = ranksAdded; ptsToSpend = (int) (ranksToAdd * skillCost); } pc.addSkill(aSkill); String ret = SkillRankControl.modRanks(ranksToAdd, pcClass, false, pc, aSkill); if (ret.length() > 0) { if (isFree && ret.indexOf("You do not have enough skill points.") != -1) { SkillRankControl.modRanks(ranksToAdd, pcClass, true, pc, aSkill); } else { warnings.add(ret); return null; } } if (!isFree) { for (int i = 0; i < pc.getLevelInfoSize(); i++) { PCLevelInfo info = pc.getLevelInfo(i); if (points[i] >= 0) { info.setSkillPointsRemaining(points[i]); } } } List<Language> langList = new ArrayList<Language>(); if (ChooseActivation.hasChooseToken(aSkill) && !selection.isEmpty()) { ChoiceManagerList<Language> controller = ChooserUtilities.getConfiguredController(aSkill, pc, null, new ArrayList<String>()); int limit = (int) ranksToAdd; for (CDOMSingleRef<Language> ref : selection) { Language lang = ref.resolvesTo(); if (controller.conditionallyApply(pc, lang)) { langList.add(lang); limit--; } if (limit <= 0) { break; } } } return new KitSkillAdd(aSkill, ranksToAdd, ptsToSpend, langList, pcClass); }