/// <summary> /// Modifies a player's Base Attack Bonus (BAB) by a certain amount. /// This method will not persist the changes so be sure you call DB.Set after calling this. /// </summary> /// <param name="entity">The entity to modify.</param> /// <param name="player">The player to modify.</param> /// <param name="adjustBy">The amount to adjust by</param> public static void AdjustBAB(Player entity, uint player, int adjustBy) { entity.BAB += adjustBy; if (entity.BAB < 1) { entity.BAB = 1; } Creature.SetBaseAttackBonus(player, entity.BAB); }
/// <summary> /// Retrieves the maximum STM on a player. /// CON modifier will be checked. Each modifier grants +2 to max STM. /// </summary> /// <param name="player">The player object</param> /// <param name="dbPlayer">The player entity. If this is not set, a call to the DB will be made.</param> /// <returns>The max amount of STM</returns> public static int GetMaxStamina(uint player, Player dbPlayer = null) { if (dbPlayer == null) { var playerId = GetObjectUUID(player); dbPlayer = DB.Get <Player>(playerId); } var baseStamina = dbPlayer.MaxStamina; var conModifier = GetAbilityModifier(AbilityType.Constitution, player); return(baseStamina + (conModifier * 2)); }
/// <summary> /// Reduces an entity's MP by a specified amount. /// If player would fall below 0 MP, they will be reduced to 0 instead. /// This method will not persist the changes so be sure you call DB.Set after calling this. /// </summary> /// <param name="entity">The entity to modify</param> /// <param name="reduceBy">The amount of MP to reduce by.</param> public static void ReduceMP(Player entity, int reduceBy) { if (reduceBy <= 0) { return; } entity.MP -= reduceBy; if (entity.MP < 0) { entity.MP = 0; } }
/// <summary> /// Retrieves the maximum MP on a player. /// INT and WIS modifiers will be checked. The higher one is used for calculations. /// Each modifier grants +2 to max MP. /// </summary> /// <param name="player">The player object</param> /// <param name="dbPlayer">The player entity. If this is not set, a call to the DB will be made.</param> /// <returns>The max amount of MP</returns> public static int GetMaxMP(uint player, Player dbPlayer = null) { if (dbPlayer == null) { var playerId = GetObjectUUID(player); dbPlayer = DB.Get <Player>(playerId); } var baseMP = dbPlayer.MaxMP; var intModifier = GetAbilityModifier(AbilityType.Intelligence, player); var wisModifier = GetAbilityModifier(AbilityType.Wisdom, player); var modifier = intModifier > wisModifier ? intModifier : wisModifier; return(baseMP + (modifier * 2)); }
/// <summary> /// Reduces an entity's Stamina by a specified amount. /// If player would fall below 0 stamina, they will be reduced to 0 instead. /// This method will not persist the changes so be sure you call DB.Set after calling this. /// </summary> /// <param name="entity">The entity to modify</param> /// <param name="reduceBy">The amount of Stamina to reduce by.</param> public static void ReduceStamina(Player entity, int reduceBy) { if (reduceBy <= 0) { return; } entity.Stamina -= reduceBy; if (entity.Stamina < 0) { entity.Stamina = 0; } }
/// <summary> /// Increases or decreases a player's HP by a specified amount. /// There is a cap of 255 HP per NWN level. Players are auto-leveled to 5 by default, so this /// gives 255 * 5 = 1275 HP maximum. If the player's HP would go over this amount, it will be set to 1275. /// This method will not persist the changes so be sure you call DB.Set after calling this. /// </summary> /// <param name="entity">The entity to modify</param> /// <param name="player">The player to adjust</param> /// <param name="adjustBy">The amount to adjust by.</param> public static void AdjustMaxHP(Player entity, uint player, int adjustBy) { const int MaxHPPerLevel = 255; entity.MaxHP += adjustBy; var nwnLevelCount = GetLevelByPosition(1, player) + GetLevelByPosition(2, player) + GetLevelByPosition(3, player); var hpToApply = entity.MaxHP; // All levels must have at least 1 HP, so apply those right now. for (var nwnLevel = 1; nwnLevel <= nwnLevelCount; nwnLevel++) { hpToApply--; Creature.SetMaxHitPointsByLevel(player, nwnLevel, 1); } // It's possible for the MaxHP value to be a negative if builders misuse item properties, etc. // Players cannot go under 'nwnLevel' HP, so we apply that first. If our HP to apply is zero, we don't want to // do any more logic with HP application. if (hpToApply > 0) { // Apply the remaining HP. for (var nwnLevel = 1; nwnLevel <= nwnLevelCount; nwnLevel++) { if (hpToApply > MaxHPPerLevel) // Levels can only contain a max of 255 HP { Creature.SetMaxHitPointsByLevel(player, nwnLevel, 255); hpToApply -= 254; } else // Remaining value gets set to the level. (<255 hp) { Creature.SetMaxHitPointsByLevel(player, nwnLevel, hpToApply + 1); break; } } } // If player's current HP is higher than max, deal the difference in damage to bring them back down to their new maximum. var currentHP = GetCurrentHitPoints(player); var maxHP = GetMaxHitPoints(player); if (currentHP > maxHP) { var damage = EffectDamage(currentHP - maxHP); ApplyEffectToObject(DurationType.Instant, damage, player); } }
/// <summary> /// Restores an entity's MP by a specified amount. /// This method will not persist the changes so be sure you call DB.Set after calling this. /// </summary> /// <param name="player">The player to modify.</param> /// <param name="entity">The entity to modify.</param> /// <param name="amount">The amount of MP to restore.</param> public static void RestoreMP(uint player, Player entity, int amount) { if (amount <= 0) { return; } var maxMP = GetMaxMP(player); entity.MP += amount; if (entity.MP > maxMP) { entity.MP = maxMP; } }
/// <summary> /// Restores an entity's Stamina by a specified amount. /// This method will not persist the changes so be sure you call DB.Set after calling this. /// </summary> /// <param name="player">The player to modify.</param> /// <param name="entity">The entity to modify.</param> /// <param name="amount">The amount of Stamina to restore.</param> public static void RestoreStamina(uint player, Player entity, int amount) { if (amount <= 0) { return; } var maxStamina = GetMaxStamina(player, entity); entity.Stamina += amount; if (entity.Stamina > maxStamina) { entity.Stamina = maxStamina; } }
/// <summary> /// Modifies a player's maximum STM by a certain amount. /// This method will not persist the changes so be sure you call DB.Set after calling this. /// </summary> /// <param name="entity">The entity to modify</param> /// <param name="adjustBy">The amount to adjust by</param> public static void AdjustMaxSTM(Player entity, int adjustBy) { // Note: It's possible for Max STM to drop to a negative number. This is expected to ensure calculations stay in sync. // If there are any visual indicators (GUI elements for example) be sure to account for this scenario. entity.MaxStamina += adjustBy; if (entity.Stamina > entity.MaxStamina) { entity.Stamina = entity.MaxStamina; } // Current STM, however, should never drop below zero. if (entity.Stamina < 0) { entity.Stamina = 0; } }
/// <summary> /// Modifies the player's unallocated SP, version, max HP, and other assorted stats. /// </summary> /// <param name="player">The player object</param> /// <param name="dbPlayer">The player entity.</param> private static void AdjustStats(uint player, Player dbPlayer) { dbPlayer.UnallocatedSP = 10; dbPlayer.Version = 1; dbPlayer.Name = GetName(player); Stat.AdjustMaxHP(dbPlayer, player, 10); Stat.AdjustMaxSTM(dbPlayer, 10); Stat.AdjustBAB(dbPlayer, player, 1); dbPlayer.MP = Stat.GetMaxMP(player, dbPlayer); dbPlayer.Stamina = Stat.GetMaxStamina(player, dbPlayer); dbPlayer.BaseStats[AbilityType.Strength] = Creature.GetRawAbilityScore(player, AbilityType.Strength); dbPlayer.BaseStats[AbilityType.Dexterity] = Creature.GetRawAbilityScore(player, AbilityType.Dexterity); dbPlayer.BaseStats[AbilityType.Constitution] = Creature.GetRawAbilityScore(player, AbilityType.Constitution); dbPlayer.BaseStats[AbilityType.Wisdom] = Creature.GetRawAbilityScore(player, AbilityType.Wisdom); dbPlayer.BaseStats[AbilityType.Intelligence] = Creature.GetRawAbilityScore(player, AbilityType.Intelligence); dbPlayer.BaseStats[AbilityType.Charisma] = Creature.GetRawAbilityScore(player, AbilityType.Charisma); }
/// <summary> /// Modifies a player's attribute by a certain amount. /// This method will not persist the changes so be sure you call DB.Set after calling this. /// </summary> /// <param name="entity">The entity to modify.</param> /// <param name="player">The player to modify.</param> /// <param name="ability">The ability to modify.</param> /// <param name="adjustBy">The amount to adjust by.</param> public static void AdjustAttribute(Player entity, uint player, AbilityType ability, float adjustBy) { if (!GetIsPC(player) || GetIsDM(player)) { return; } if (ability == AbilityType.Invalid) { return; } if (adjustBy == 0f) { return; } entity.AdjustedStats[ability] += adjustBy; var totalStat = (int)(entity.BaseStats[ability] + entity.AdjustedStats[ability]); Creature.SetRawAbilityScore(player, ability, totalStat); }
/// <summary> /// Retrieves a player's effective perk level. /// </summary> /// <param name="player">The player object</param> /// <param name="dbPlayer">The database entity</param> /// <param name="perkType">The type of perk</param> /// <returns>The effective level for a given player and perk</returns> private static int GetPlayerPerkLevel(uint player, Player dbPlayer, PerkType perkType) { var playerPerkLevel = dbPlayer.Perks.ContainsKey(perkType) ? dbPlayer.Perks[perkType] : 0; // Early exit if player doesn't have the perk at all. if (playerPerkLevel <= 0) { return(0); } // Retrieve perk levels at or below player's perk level and then order them from highest level to lowest. var perk = GetPerkDetails(perkType); var perkLevels = perk.PerkLevels .Where(x => x.Key <= playerPerkLevel) .OrderByDescending(o => o.Key); // Iterate over each perk level and check requirements. // The first perk level the player passes requirements on is the player's effective level. foreach (var(level, detail) in perkLevels) { // No requirements set for this perk level. Return the level. if (detail.Requirements.Count <= 0) { return(level); } foreach (var req in detail.Requirements) { if (string.IsNullOrWhiteSpace(req.CheckRequirements(player))) { return(level); } } } // Otherwise none of the perk level requirements passed. Player's effective level is zero. return(0); }
/// <summary> /// This will manually recalculate all stats. /// This should be used sparingly because it can be a heavy call. /// This method will not persist the changes so be sure your call DB.Set after calling this. /// </summary> public static void RecalculateAllStats(uint player, Player dbPlayer) { // Reset all adjusted stat values. foreach (var adjustedStat in dbPlayer.AdjustedStats) { dbPlayer.AdjustedStats[adjustedStat.Key] = 0.0f; } // Apply adjusted stat increases based on the player's skill ranks. // We use a pre-filtered list of skills for this to cut down on the number of iterations. var skills = Skill.GetAllSkillsWhichIncreaseStats(); foreach (var(type, value) in skills) { var primaryIncrease = dbPlayer.Skills[type].Rank * Skill.PrimaryStatIncrease; var secondaryIncrease = dbPlayer.Skills[type].Rank * Skill.SecondaryStatIncrease; if (value.PrimaryStat == AbilityType.Strength) { dbPlayer.AdjustedStats[AbilityType.Strength] += primaryIncrease; } if (value.PrimaryStat == AbilityType.Dexterity) { dbPlayer.AdjustedStats[AbilityType.Dexterity] += primaryIncrease; } if (value.PrimaryStat == AbilityType.Constitution) { dbPlayer.AdjustedStats[AbilityType.Constitution] += primaryIncrease; } if (value.PrimaryStat == AbilityType.Wisdom) { dbPlayer.AdjustedStats[AbilityType.Wisdom] += primaryIncrease; } if (value.PrimaryStat == AbilityType.Intelligence) { dbPlayer.AdjustedStats[AbilityType.Intelligence] += primaryIncrease; } if (value.PrimaryStat == AbilityType.Charisma) { dbPlayer.AdjustedStats[AbilityType.Charisma] += primaryIncrease; } if (value.SecondaryStat == AbilityType.Strength) { dbPlayer.AdjustedStats[AbilityType.Strength] += secondaryIncrease; } if (value.SecondaryStat == AbilityType.Dexterity) { dbPlayer.AdjustedStats[AbilityType.Dexterity] += secondaryIncrease; } if (value.SecondaryStat == AbilityType.Constitution) { dbPlayer.AdjustedStats[AbilityType.Constitution] += secondaryIncrease; } if (value.SecondaryStat == AbilityType.Wisdom) { dbPlayer.AdjustedStats[AbilityType.Wisdom] += secondaryIncrease; } if (value.SecondaryStat == AbilityType.Intelligence) { dbPlayer.AdjustedStats[AbilityType.Intelligence] += secondaryIncrease; } if (value.SecondaryStat == AbilityType.Charisma) { dbPlayer.AdjustedStats[AbilityType.Charisma] += secondaryIncrease; } } // We now have all of the correct values. Apply them to the player object. foreach (var(ability, amount) in dbPlayer.AdjustedStats) { var totalStat = (int)(dbPlayer.BaseStats[ability] + amount); Creature.SetRawAbilityScore(player, ability, totalStat); } }
/// <summary> /// Handles applying skill XP decay when a player has reached the skill cap. /// If decay cannot be applied, false will be returned. /// If decay was unnecessary or succeeded, true will be returned. /// </summary> /// <param name="dbPlayer">The player entity to apply skill decay to</param> /// <param name="player">The player object.</param> /// <param name="skill">The skill which is receiving XP. This skill will be excluded from decay.</param> /// <param name="xp">The amount of XP being applied.</param> /// <returns>true if successful or unnecessary, false otherwise</returns> private static bool ApplyDecay(Player dbPlayer, uint player, SkillType skill, int xp) { if (dbPlayer.TotalSPAcquired < SkillCap) { return(true); } var playerId = GetObjectUUID(player); var skillsPossibleToDecay = dbPlayer.Skills .Where(x => { var detail = GetSkillDetails(x.Key); return(!x.Value.IsLocked && detail.ContributesToSkillCap && x.Key != skill && (x.Value.XP > 0 || x.Value.Rank > 0)); }).ToList(); // If no skills can be decayed, return false. if (!skillsPossibleToDecay.Any()) { return(false); } // Get the total XP acquired, then add up any remaining XP for a partial level int totalAvailableXPToDecay = skillsPossibleToDecay.Sum(x => { var totalXP = GetTotalXP(x.Value.Rank); xp += x.Value.XP; return(totalXP); }); // There's not enough XP to decay for this gain. Exit early. if (totalAvailableXPToDecay < xp) { return(false); } while (xp > 0) { var index = Random.Next(skillsPossibleToDecay.Count); var decaySkill = skillsPossibleToDecay[index]; int totalDecayXP = GetTotalXP(decaySkill.Value.Rank) + decaySkill.Value.XP; if (totalDecayXP >= xp) { totalDecayXP -= xp; xp = 0; } else if (totalDecayXP < xp) { totalDecayXP = 0; xp -= totalDecayXP; } // If skill drops to 0 total XP, remove it from the possible list of skills if (totalDecayXP <= 0) { skillsPossibleToDecay.Remove(decaySkill); decaySkill.Value.XP = 0; decaySkill.Value.Rank = 0; } // Otherwise calculate what rank and XP value the skill should now be. else { // Get the XP amounts required per level, in ascending order, so we can see how many levels we're now meant to have. var reqs = _skillTotalXP.Where(x => x.Key <= decaySkill.Value.Rank).OrderBy(o => o.Key); // The first entry in the database is for rank 0, and if passed, will raise us to 1. So start our count at 0. int newDecaySkillRank = 0; foreach (var req in reqs) { if (totalDecayXP >= req.Value) { totalDecayXP -= req.Value; newDecaySkillRank++; } else if (totalDecayXP < req.Value) { break; } } decaySkill.Value.Rank = newDecaySkillRank; decaySkill.Value.XP = totalDecayXP; } dbPlayer.Skills[decaySkill.Key].Rank = decaySkill.Value.Rank; dbPlayer.Skills[decaySkill.Key].XP = decaySkill.Value.XP; } // Perform a full recalc on the player's stats Stat.RecalculateAllStats(player, dbPlayer); // Save all changes made. DB.Set(playerId, dbPlayer); return(true); }