public static int CalculateReassemblyChance(NWPlayer player, int penalty) { const int BaseChance = 70; int harvesting = SkillService.GetPCSkillRank(player, SkillType.Harvesting); var itemBonuses = PlayerStatService.GetPlayerItemEffectiveStats(player); int perkLevel = PerkService.GetCreaturePerkLevel(player, PerkType.MolecularReassemblyProficiency); // Calculate the base chance after factoring in skills, perks, and items. int categoryChance = (int)(BaseChance + (harvesting / 2.5f) + perkLevel * 10 + itemBonuses.Harvesting / 3f); // Reduce the chance by the penalty. This penalty is generally determined by how many properties were already // applied during this batch. categoryChance -= penalty; // Keep bounds between 0 and 100 if (categoryChance < 0) { return(0); } else if (categoryChance > 100) { return(100); } else { return(categoryChance); } }
public static int CalculateChanceForComponentBonus(NWPlayer player, int tier, ResourceQuality quality, bool scavenging = false) { int rank = (scavenging ? SkillService.GetPCSkillRank(player, SkillType.Scavenging) : SkillService.GetPCSkillRank(player, SkillType.Harvesting)); int difficulty = (tier - 1) * 10 + GetDifficultyAdjustment(quality); int delta = difficulty - rank; if (delta >= 7) { return(0); } if (delta <= -7) { return(45); } int chance = 0; switch (delta) { case 6: chance = 1; break; case 5: chance = 2; break; case 4: chance = 3; break; case 3: chance = 6; break; case 2: chance = 9; break; case 1: chance = 12; break; case 0: chance = 15; break; case -1: chance = 18; break; case -2: chance = 20; break; case -3: chance = 21; break; case -4: chance = 23; break; case -5: chance = 25; break; case -6: chance = 27; break; } var effectiveStats = PlayerStatService.GetPlayerItemEffectiveStats(player); int itemBonus = (scavenging ? effectiveStats.Scavenging : effectiveStats.Harvesting) / 2; if (itemBonus > 30) { itemBonus = 30; } chance += itemBonus; return(chance); }
private static int EffectiveArmorClass(NWPlayer player, NWItem ignoreItem, EffectiveItemStats stats) { int baseAC = stats.AC + CustomEffectService.CalculateEffectAC(player); // Calculate AC bonus granted by skill ranks. // Only chest armor is checked for this bonus. if (ignoreItem != player.Chest) { CustomItemType armorType = player.Chest.CustomItemType; int skillRank = 0; switch (armorType) { case CustomItemType.LightArmor: skillRank = SkillService.GetPCSkillRank(player, SkillType.LightArmor); break; case CustomItemType.HeavyArmor: skillRank = SkillService.GetPCSkillRank(player, SkillType.HeavyArmor); break; case CustomItemType.ForceArmor: skillRank = SkillService.GetPCSkillRank(player, SkillType.ForceArmor); break; } // +1 AC per 10 skill ranks, while wearing the appropriate armor. int skillACBonus = skillRank / 10; baseAC += skillACBonus; } int totalAC = _.GetAC(player) - baseAC; // Shield Oath and Precision Targeting affect a percentage of the TOTAL armor class on a creature. var stance = CustomEffectService.GetCurrentStanceType(player); if (stance == CustomEffectType.ShieldOath) { int bonus = (int)(totalAC * 0.2f); baseAC += bonus; } else if (stance == CustomEffectType.PrecisionTargeting) { int penalty = (int)(totalAC * 0.3f); baseAC -= penalty; } if (baseAC < 0) { baseAC = 0; } return(baseAC); }
private static void OnModuleApplyDamage() { var data = NWNXDamage.GetDamageEventData(); if (data.Base <= 0) { return; } NWObject damager = data.Damager; if (!damager.IsPlayer) { return; } NWCreature target = NWGameObject.OBJECT_SELF; // Check that this was a normal attack, and not (say) a damage over time effect. if (target.GetLocalInt(AbilityService.LAST_ATTACK + damager.GlobalID) != AbilityService.ATTACK_PHYSICAL) { return; } NWItem weapon = (_.GetLastWeaponUsed(damager.Object)); int damageBonus = weapon.DamageBonus; NWPlayer player = (damager.Object); int itemLevel = weapon.RecommendedLevel; SkillType skill = ItemService.GetSkillTypeForItem(weapon); if (skill == SkillType.Unknown) { return; } int rank = SkillService.GetPCSkillRank(player, skill); int delta = itemLevel - rank; if (delta >= 1) { damageBonus--; } damageBonus = damageBonus - delta / 5; if (damageBonus <= 0) { damageBonus = 0; } data.Base += damageBonus; NWNXDamage.SetDamageEventData(data); }
public static bool OnModuleExamine(NWPlayer examiner, NWObject target) { string backupDescription = target.GetLocalString("BACKUP_DESCRIPTION"); if (!string.IsNullOrWhiteSpace(backupDescription)) { target.UnidentifiedDescription = backupDescription; } if (!examiner.IsDM || !target.IsPlayer || target.IsDM) { return(false); } backupDescription = target.IdentifiedDescription; target.SetLocalString("BACKUP_DESCRIPTION", backupDescription); Player playerEntity = DataService.Player.GetByID(target.GlobalID); NWArea area = NWModule.Get().Areas.Single(x => x.Resref == playerEntity.RespawnAreaResref); string respawnAreaName = area.Name; StringBuilder description = new StringBuilder( ColorTokenService.Green("ID: ") + target.GlobalID + "\n" + ColorTokenService.Green("Character Name: ") + target.Name + "\n" + ColorTokenService.Green("Respawn Area: ") + respawnAreaName + "\n" + ColorTokenService.Green("Skill Points: ") + playerEntity.TotalSPAcquired + " (Unallocated: " + playerEntity.UnallocatedSP + ")" + "\n" + ColorTokenService.Green("FP: ") + playerEntity.CurrentFP + " / " + playerEntity.MaxFP + "\n" + ColorTokenService.Green("Skill Levels: ") + "\n\n"); List <PCSkill> pcSkills = SkillService.GetAllPCSkills(target.Object); foreach (PCSkill pcSkill in pcSkills) { Skill skill = SkillService.GetSkill(pcSkill.SkillID); description.Append(skill.Name).Append(" rank ").Append(pcSkill.Rank).AppendLine(); } description.Append("\n\n").Append(ColorTokenService.Green("Perks: ")).Append("\n\n"); var pcPerks = DataService.PCPerk.GetAllByPlayerID(target.GlobalID); foreach (PCPerk pcPerk in pcPerks) { var perk = DataService.Perk.GetByID(pcPerk.PerkID); description.Append(perk.Name).Append(" Lvl. ").Append(pcPerk.PerkLevel).AppendLine(); } description.Append("\n\n").Append(ColorTokenService.Green("Description: \n\n")).Append(backupDescription).AppendLine(); target.UnidentifiedDescription = description.ToString(); _.DelayCommand(0.1f, () => { _.SetDescription(target, backupDescription, false); }); return(true); }
public static string TranslateSnippetForListener(NWObject speaker, NWObject listener, SkillType language, string snippet) { Dictionary <SkillType, Type> map = new Dictionary <SkillType, Type> { { SkillType.Bothese, typeof(TranslatorBothese) }, { SkillType.Catharese, typeof(TranslatorCatharese) }, { SkillType.Cheunh, typeof(TranslatorCheunh) }, { SkillType.Dosh, typeof(TranslatorDosh) }, { SkillType.Droidspeak, typeof(TranslatorDroidspeak) }, { SkillType.Huttese, typeof(TranslatorHuttese) }, { SkillType.Mandoa, typeof(TranslatorMandoa) }, { SkillType.Shyriiwook, typeof(TranslatorShyriiwook) }, { SkillType.Twileki, typeof(TranslatorTwileki) }, { SkillType.Zabraki, typeof(TranslatorZabraki) }, { SkillType.Mirialan, typeof(TranslatorMirialan) }, { SkillType.MonCalamarian, typeof(TranslatorMonCalamarian) }, { SkillType.Ugnaught, typeof(TranslatorUgnaught) } }; Type type = typeof(TranslatorGeneric); map.TryGetValue(language, out type); ITranslator translator = (ITranslator)Activator.CreateInstance(type); if (speaker.IsPC && !speaker.IsDM) { // Get the rank and max rank for the speaker, and garble their English text based on it. NWPlayer speakerAsPlayer = speaker.Object; int speakerSkillRank = SkillService.GetPCSkillRank(speakerAsPlayer, language); int speakerSkillMaxRank = SkillService.GetSkill(language).MaxRank; if (speakerSkillRank != speakerSkillMaxRank) { int garbledChance = 100 - (int)(((float)speakerSkillRank / (float)speakerSkillMaxRank) * 100); string[] split = snippet.Split(' '); for (int i = 0; i < split.Length; ++i) { if (RandomService.Random(100) <= garbledChance) { split[i] = new string(split[i].ToCharArray().OrderBy(s => (RandomService.Random(2) % 2) == 0).ToArray()); } } snippet = split.Aggregate((a, b) => a + " " + b); } } if (!listener.IsPC || listener.IsDM) { // Short circuit for a DM or NPC - they will always understand the text. return(snippet); } // Let's grab the max rank for the listener skill, and then we roll for a successful translate based on that. NWPlayer listenerAsPlayer = listener.Object; int rank = SkillService.GetPCSkillRank(listenerAsPlayer, language); int maxRank = SkillService.GetSkill(language).MaxRank; // Check for the Comprehend Speech concentration ability. Player dbPlayer = DataService.Player.GetByID(listenerAsPlayer.GlobalID); bool grantSenseXP = false; if (dbPlayer.ActiveConcentrationPerkID == (int)PerkType.ComprehendSpeech) { int bonus = 5 * dbPlayer.ActiveConcentrationTier; rank += bonus; grantSenseXP = true; } // Ensure we don't go over the maximum. if (rank > maxRank) { rank = maxRank; } if (rank == maxRank || speaker == listener) { // Guaranteed success - return original. return(snippet); } string textAsForeignLanguage = translator.Translate(snippet); if (rank != 0) { int englishChance = (int)(((float)rank / (float)maxRank) * 100); string[] originalSplit = snippet.Split(' '); string[] foreignSplit = textAsForeignLanguage.Split(' '); StringBuilder endResult = new StringBuilder(); // WARNING: We're making the assumption that originalSplit.Length == foreignSplit.Length. // If this assumption changes, the below logic needs to change too. for (int i = 0; i < originalSplit.Length; ++i) { if (RandomService.Random(100) <= englishChance) { endResult.Append(originalSplit[i]); } else { endResult.Append(foreignSplit[i]); } endResult.Append(" "); } textAsForeignLanguage = endResult.ToString(); } long now = DateTime.Now.Ticks; int lastSkillUpLow = listenerAsPlayer.GetLocalInt("LAST_LANGUAGE_SKILL_INCREASE_LOW"); int lastSkillUpHigh = listenerAsPlayer.GetLocalInt("LAST_LANGUAGE_SKILL_INCREASE_HIGH"); long lastSkillUp = lastSkillUpHigh; lastSkillUp = (lastSkillUp << 32) | (uint)lastSkillUpLow; long differenceInSeconds = (now - lastSkillUp) / 10000000; if (differenceInSeconds / 60 >= 2) { int amount = Math.Max(10, Math.Min(150, snippet.Length) / 3); // Reward exp towards the language - we scale this with character count, maxing at 50 exp for 150 characters. SkillService.GiveSkillXP(listenerAsPlayer, language, amount); // Grant Sense XP if player is concentrating Comprehend Speech. if (grantSenseXP) { SkillService.GiveSkillXP(listenerAsPlayer, SkillType.ForceSense, amount * 10); } listenerAsPlayer.SetLocalInt("LAST_LANGUAGE_SKILL_INCREASE_LOW", (int)(now & 0xFFFFFFFF)); listenerAsPlayer.SetLocalInt("LAST_LANGUAGE_SKILL_INCREASE_HIGH", (int)((now >> 32) & 0xFFFFFFFF)); } return(textAsForeignLanguage); }
public static void CalculateEffectiveStats(NWPlayer player, NWItem item) { if (item == null || !item.IsValid || !player.IsPlayer || player.IsDMPossessed || player.IsDM || !player.IsInitializedAsPlayer) { return; } // Calculating effective stats can be expensive, so we cache it on the item. SkillType skill; if (item.BaseItemType == BaseItem.Amulet || item.BaseItemType == BaseItem.Ring) { var forceArmor = SkillService.GetPCSkill(player, (int)SkillType.ForceArmor); var lightArmor = SkillService.GetPCSkill(player, (int)SkillType.LightArmor); var heavyArmor = SkillService.GetPCSkill(player, (int)SkillType.HeavyArmor); var highest = forceArmor.Rank; skill = SkillType.ForceArmor; if (lightArmor.Rank > highest) { highest = lightArmor.Rank; skill = SkillType.LightArmor; } if (heavyArmor.Rank > highest) { skill = SkillType.HeavyArmor; } } else { skill = ItemService.GetSkillTypeForItem(item); } var rank = DataService.PCSkill.GetByPlayerIDAndSkillID(player.GlobalID, (int)skill).Rank; using (new Profiler("PlayerStatService::ApplyStatChanges::GetPlayerItemEffectiveStats::ItemLoop::CalculateEffectiveStats")) { // Only scale cooldown recovery if it's a bonus. Penalties remain regardless of skill level difference. item.SetLocalInt("STAT_EFFECTIVE_LEVEL_COOLDOWN_RECOVERY", item.CooldownRecovery > 0 ? CalculateAdjustedValue(item.CooldownRecovery, item.RecommendedLevel, rank, 1) : item.CooldownRecovery); item.SetLocalFloat("STAT_EFFECTIVE_LEVEL_ENMITY_RATE", CalculateAdjustedValue(0.01f * item.EnmityRate, item.RecommendedLevel, rank, 0.00f)); item.SetLocalInt("STAT_EFFECTIVE_LEVEL_LUCK_BONUS", CalculateAdjustedValue(item.LuckBonus, item.RecommendedLevel, rank, 0)); item.SetLocalInt("STAT_EFFECTIVE_LEVEL_MEDITATE_BONUS", CalculateAdjustedValue(item.MeditateBonus, item.RecommendedLevel, rank, 0)); item.SetLocalInt("STAT_EFFECTIVE_LEVEL_REST_BONUS", CalculateAdjustedValue(item.RestBonus, item.RecommendedLevel, rank, 0)); item.SetLocalInt("STAT_EFFECTIVE_LEVEL_MEDICINE_BONUS", CalculateAdjustedValue(item.MedicineBonus, item.RecommendedLevel, rank, 0)); item.SetLocalInt("STAT_EFFECTIVE_LEVEL_HP_REGEN_BONUS", CalculateAdjustedValue(item.HPRegenBonus, item.RecommendedLevel, rank, 0)); item.SetLocalInt("STAT_EFFECTIVE_LEVEL_FP_REGEN_BONUS", CalculateAdjustedValue(item.FPRegenBonus, item.RecommendedLevel, rank, 0)); item.SetLocalInt("STAT_EFFECTIVE_LEVEL_WEAPONSMITH_BONUS", CalculateAdjustedValue(item.CraftBonusWeaponsmith, item.RecommendedLevel, rank, 0)); item.SetLocalInt("STAT_EFFECTIVE_LEVEL_COOKING_BONUS", CalculateAdjustedValue(item.CraftBonusCooking, item.RecommendedLevel, rank, 0)); item.SetLocalInt("STAT_EFFECTIVE_LEVEL_ENGINEERING_BONUS", CalculateAdjustedValue(item.CraftBonusEngineering, item.RecommendedLevel, rank, 0)); item.SetLocalInt("STAT_EFFECTIVE_LEVEL_FABRICATION_BONUS", CalculateAdjustedValue(item.CraftBonusFabrication, item.RecommendedLevel, rank, 0)); item.SetLocalInt("STAT_EFFECTIVE_LEVEL_ARMORSMITH_BONUS", CalculateAdjustedValue(item.CraftBonusArmorsmith, item.RecommendedLevel, rank, 0)); item.SetLocalInt("STAT_EFFECTIVE_LEVEL_HARVESTING_BONUS", CalculateAdjustedValue(item.HarvestingBonus, item.RecommendedLevel, rank, 0)); item.SetLocalInt("STAT_EFFECTIVE_LEVEL_PILOTING_BONUS", CalculateAdjustedValue(item.PilotingBonus, item.RecommendedLevel, rank, 0)); item.SetLocalInt("STAT_EFFECTIVE_LEVEL_SCAVENGING_BONUS", CalculateAdjustedValue(item.ScavengingBonus, item.RecommendedLevel, rank, 0)); item.SetLocalInt("STAT_EFFECTIVE_LEVEL_SNEAK_ATTACK_BONUS", CalculateAdjustedValue(item.SneakAttackBonus, item.RecommendedLevel, rank, 0)); item.SetLocalInt("STAT_EFFECTIVE_LEVEL_STRENGTH_BONUS", CalculateAdjustedValue(item.StrengthBonus, item.RecommendedLevel, rank, 0)); item.SetLocalInt("STAT_EFFECTIVE_LEVEL_DEXTERITY_BONUS", CalculateAdjustedValue(item.DexterityBonus, item.RecommendedLevel, rank, 0)); item.SetLocalInt("STAT_EFFECTIVE_LEVEL_CONSTITUTION_BONUS", CalculateAdjustedValue(item.ConstitutionBonus, item.RecommendedLevel, rank, 0)); item.SetLocalInt("STAT_EFFECTIVE_LEVEL_WISDOM_BONUS", CalculateAdjustedValue(item.WisdomBonus, item.RecommendedLevel, rank, 0)); item.SetLocalInt("STAT_EFFECTIVE_LEVEL_INTELLIGENCE_BONUS", CalculateAdjustedValue(item.IntelligenceBonus, item.RecommendedLevel, rank, 0)); item.SetLocalInt("STAT_EFFECTIVE_LEVEL_CHARISMA_BONUS", CalculateAdjustedValue(item.CharismaBonus, item.RecommendedLevel, rank, 0)); item.SetLocalInt("STAT_EFFECTIVE_LEVEL_HP_BONUS", CalculateAdjustedValue(item.HPBonus, item.RecommendedLevel, rank, 0)); item.SetLocalInt("STAT_EFFECTIVE_LEVEL_FP_BONUS", CalculateAdjustedValue(item.FPBonus, item.RecommendedLevel, rank, 0)); } }
private static void OnModuleApplyDamage() { var data = NWNXDamage.GetDamageEventData(); if (data.Base <= 0) { return; } NWObject damager = data.Damager; if (!damager.IsPlayer) { return; } NWCreature target = _.OBJECT_SELF; // Check that this was a normal attack, and not (say) a damage over time effect. if (target.GetLocalInt(AbilityService.LAST_ATTACK + damager.GlobalID) != AbilityService.ATTACK_PHYSICAL) { return; } NWItem weapon = (_.GetLastWeaponUsed(damager.Object)); if (!weapon.IsValid) { // Double weapons don't show up correctly when their offhand makes an attack. // So check for that case here. if (_.GetObjectType(damager) == ObjectType.Creature) { NWCreature attacker = data.Damager.Object; if (attacker.RightHand.BaseItemType == BaseItem.Saberstaff || attacker.RightHand.BaseItemType == BaseItem.TwoBladedSword || attacker.RightHand.BaseItemType == BaseItem.DoubleAxe || attacker.RightHand.BaseItemType == BaseItem.DireMace) { weapon = attacker.RightHand; } } } int damageBonus = weapon.DamageBonus; NWPlayer player = (damager.Object); int itemLevel = weapon.RecommendedLevel; SkillType skill = ItemService.GetSkillTypeForItem(weapon); if (skill == SkillType.Unknown) { return; } int rank = SkillService.GetPCSkillRank(player, skill); int delta = itemLevel - rank; if (delta >= 1) { damageBonus--; } damageBonus = damageBonus - delta / 5; if (damageBonus <= 0) { damageBonus = 0; } data.Base += damageBonus; NWNXDamage.SetDamageEventData(data); }
/// <summary> /// Calculates ability resistance for an ability. /// The attacker and defender's skills, ability modifiers, and balance affinity will be /// used to make this determination. /// </summary> /// <param name="attacker">The creature using the ability.</param> /// <param name="defender">The creature being targeted by the ability.</param> /// <param name="skill">The skill used for this ability.</param> /// <param name="balanceType">The force balance type to use for this ability.</param> /// <param name="sendRollMessage">If true, the roll message will be sent. Otherwise it won't be.</param> /// <returns>Data regarding the ability resistance roll</returns> public static AbilityResistanceResult CalculateAbilityResistance(NWCreature attacker, NWCreature defender, SkillType skill, ForceBalanceType balanceType, bool sendRollMessage = true) { int abilityScoreType; switch (skill) { case SkillType.ForceAlter: abilityScoreType = ABILITY_INTELLIGENCE; break; case SkillType.ForceControl: abilityScoreType = ABILITY_WISDOM; break; case SkillType.ForceSense: abilityScoreType = ABILITY_CHARISMA; break; default: throw new ArgumentException("Invalid skill type called for " + nameof(CalculateAbilityResistance) + ", value '" + skill + "' not supported."); } AbilityResistanceResult result = new AbilityResistanceResult(); int attackerSkill = SkillService.GetPCSkillRank(attacker.Object, skill); int attackerAbility = _.GetAbilityModifier(abilityScoreType, attacker); int defenderSkill = SkillService.GetPCSkillRank(defender.Object, skill); int defenderAbility = _.GetAbilityModifier(abilityScoreType, defender); // If the defender is equipped with a lightsaber, we check their lightsaber skill if (defender.RightHand.CustomItemType == CustomItemType.Lightsaber || defender.LeftHand.CustomItemType == CustomItemType.Lightsaber) { int lightsaberSkill = SkillService.GetPCSkillRank(defender.Object, SkillType.Lightsaber); if (lightsaberSkill > defenderSkill) { defenderSkill = lightsaberSkill; } } // If the defender's martial arts skill is greater than the current skill they're using, we'll use that instead. int defenderMASkill = SkillService.GetPCSkillRank(defender.Object, SkillType.MartialArts); if (defenderMASkill > defenderSkill) { defenderSkill = defenderMASkill; } int attackerAffinity = 0; int defenderAffinity = 0; // Only check affinity if ability has a force balance type. if (balanceType == ForceBalanceType.Dark || balanceType == ForceBalanceType.Light) { attackerAffinity = GetBalanceAffinity(attacker.Object, balanceType); defenderAffinity = GetBalanceAffinity(defender.Object, balanceType); } float attackerCR = attacker.IsPlayer ? 0f : attacker.ChallengeRating * 5f; float defenderCR = defender.IsPlayer ? 0f : defender.ChallengeRating * 5f; float attackerTotal = attackerSkill + attackerAbility + attackerAffinity + attackerCR; float defenderTotal = defenderSkill + defenderAbility + defenderAffinity + defenderCR; float divisor = attackerTotal + defenderTotal + 1; // +1 to prevent division by zero. //Console.WriteLine("attackerCR = " + attackerCR); //Console.WriteLine("defenderCR = " + defenderCR); //Console.WriteLine("attackerSkill = " + attackerSkill); //Console.WriteLine("attackerAbility = " + attackerAbility); //Console.WriteLine("attackerAffinity = " + attackerAffinity); //Console.WriteLine("defenderSkill = " + defenderSkill); //Console.WriteLine("defenderAbility = " + defenderAbility); //Console.WriteLine("defenderAffinity = " + defenderAffinity); //Console.WriteLine("attackerTotal = " + attackerTotal); //Console.WriteLine("defenderTotal = " + defenderTotal); //Console.WriteLine("divisor = " + divisor); result.DC = (int)(attackerTotal / divisor * 100); result.Roll = RandomService.D100(1); if (sendRollMessage) { string resisted = result.IsResisted ? ColorTokenService.Red(" [RESISTED " + Math.Abs(result.Delta) + "%]") : string.Empty; string message = ColorTokenService.SavingThrow("Roll: " + result.Roll + " VS " + result.DC + " DC") + resisted; attacker.SendMessage(message); defender.SendMessage(message); } return(result); }
private static void HandleDamageImmunity() { DamageEventData data = NWNXDamage.GetDamageEventData(); if (data.Total <= 0) { return; } NWCreature target = NWGameObject.OBJECT_SELF; NWItem shield = target.LeftHand; var concentrationEffect = AbilityService.GetActiveConcentrationEffect(target); double reduction = 0.0f; // Shield damage reduction and absorb energy are calculated here. They don't stack, so the one // with the highest reduction will take precedence. // Calculate shield damage reduction. if (ItemService.ShieldBaseItemTypes.Contains(shield.BaseItemType)) { // Apply damage scaling based on shield presence int perkLevel = PerkService.GetCreaturePerkLevel(target.Object, PerkType.ShieldProficiency); float perkBonus = 0.02f * perkLevel; // DI = 10% + 1% / 3 AC bonuses on the shield + 2% per perk bonus. reduction = (0.1 + 0.01 * shield.AC / 3) + perkBonus; } // Calculate Absorb Energy concentration effect reduction. if (concentrationEffect.Type == PerkType.AbsorbEnergy) { double perkReduction = concentrationEffect.Tier * 0.1; if (perkReduction > reduction) { reduction = perkReduction; // Calculate and award force XP based on total damage reduced. int xp = (int)(data.Total * reduction * 3); if (xp < 5) { xp = 5; } SkillService.GiveSkillXP(target.Object, SkillType.ForceControl, xp); // Play a visual effect signifying the ability was activated. _.ApplyEffectToObject(DURATION_TYPE_TEMPORARY, EffectVisualEffect(VFX_DUR_BLUR), target, 0.5f); } } // No reduction found. Bail out early. if (reduction <= 0.0f) { return; } target.SendMessage("Damage reduced by " + (int)(reduction * 100) + "%"); reduction = 1.0f - reduction; data.Bludgeoning = (int)(data.Bludgeoning * reduction); data.Pierce = (int)(data.Pierce * reduction); data.Slash = (int)(data.Slash * reduction); data.Magical = (int)(data.Magical * reduction); data.Acid = (int)(data.Acid * reduction); data.Cold = (int)(data.Cold * reduction); data.Divine = (int)(data.Divine * reduction); data.Electrical = (int)(data.Electrical * reduction); data.Fire = (int)(data.Fire * reduction); data.Negative = (int)(data.Negative * reduction); data.Positive = (int)(data.Positive * reduction); data.Sonic = (int)(data.Sonic * reduction); data.Base = (int)(data.Base * reduction); NWNXDamage.SetDamageEventData(data); }