/* * Copyright (C) 2012-2020 CypherCore * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ using Framework.Constants; using Game.DataStorage; using Game.Networking.Packets; using Game.Spells; using System; using System.Collections.Generic; using System.Linq; namespace Game.Entities { public partial class Unit { public void HandleStatFlatModifier(UnitMods unitMod, UnitModifierFlatType modifierType, float amount, bool apply) { if (unitMod >= UnitMods.End || modifierType >= UnitModifierFlatType.End) { Log.outError(LogFilter.Unit, "ERROR in HandleStatFlatModifier(): non-existing UnitMods or wrong UnitModifierFlatType!"); return; } if (amount == 0) return; switch (modifierType) { case UnitModifierFlatType.Base: case UnitModifierFlatType.BasePCTExcludeCreate: case UnitModifierFlatType.Total: m_auraFlatModifiersGroup[(int)unitMod][(int)modifierType] += apply ? amount : -amount; break; default: break; } UpdateUnitMod(unitMod); } public void ApplyStatPctModifier(UnitMods unitMod, UnitModifierPctType modifierType, float pct) { if (unitMod >= UnitMods.End || modifierType >= UnitModifierPctType.End) { Log.outError(LogFilter.Unit, "ERROR in ApplyStatPctModifier(): non-existing UnitMods or wrong UnitModifierPctType!"); return; } if (pct == 0) return; switch (modifierType) { case UnitModifierPctType.Base: case UnitModifierPctType.Total: MathFunctions.AddPct(ref m_auraPctModifiersGroup[(int)unitMod][(int)modifierType], pct); break; default: break; } UpdateUnitMod(unitMod); } public void SetStatFlatModifier(UnitMods unitMod, UnitModifierFlatType modifierType, float val) { if (m_auraFlatModifiersGroup[(int)unitMod][(int)modifierType] == val) return; m_auraFlatModifiersGroup[(int)unitMod][(int)modifierType] = val; UpdateUnitMod(unitMod); } public void SetStatPctModifier(UnitMods unitMod, UnitModifierPctType modifierType, float val) { if (m_auraPctModifiersGroup[(int)unitMod][(int)modifierType] == val) return; m_auraPctModifiersGroup[(int)unitMod][(int)modifierType] = val; UpdateUnitMod(unitMod); } public float GetFlatModifierValue(UnitMods unitMod, UnitModifierFlatType modifierType) { if (unitMod >= UnitMods.End || modifierType >= UnitModifierFlatType.End) { Log.outError(LogFilter.Unit, "attempt to access non-existing modifier value from UnitMods!"); return 0.0f; } return m_auraFlatModifiersGroup[(int)unitMod][(int)modifierType]; } public float GetPctModifierValue(UnitMods unitMod, UnitModifierPctType modifierType) { if (unitMod >= UnitMods.End || modifierType >= UnitModifierPctType.End) { Log.outError(LogFilter.Unit, "attempt to access non-existing modifier value from UnitMods!"); return 0.0f; } return m_auraPctModifiersGroup[(int)unitMod][(int)modifierType]; } void UpdateUnitMod(UnitMods unitMod) { if (!CanModifyStats()) return; switch (unitMod) { case UnitMods.StatStrength: case UnitMods.StatAgility: case UnitMods.StatStamina: case UnitMods.StatIntellect: UpdateStats(GetStatByAuraGroup(unitMod)); break; case UnitMods.Armor: UpdateArmor(); break; case UnitMods.Health: UpdateMaxHealth(); break; case UnitMods.Mana: case UnitMods.Rage: case UnitMods.Focus: case UnitMods.Energy: case UnitMods.ComboPoints: case UnitMods.Runes: case UnitMods.RunicPower: case UnitMods.SoulShards: case UnitMods.LunarPower: case UnitMods.HolyPower: case UnitMods.Alternate: case UnitMods.Maelstrom: case UnitMods.Chi: case UnitMods.Insanity: case UnitMods.BurningEmbers: case UnitMods.DemonicFury: case UnitMods.ArcaneCharges: case UnitMods.Fury: case UnitMods.Pain: UpdateMaxPower((PowerType)(unitMod - UnitMods.PowerStart)); break; case UnitMods.ResistanceHoly: case UnitMods.ResistanceFire: case UnitMods.ResistanceNature: case UnitMods.ResistanceFrost: case UnitMods.ResistanceShadow: case UnitMods.ResistanceArcane: UpdateResistances(GetSpellSchoolByAuraGroup(unitMod)); break; case UnitMods.AttackPower: UpdateAttackPowerAndDamage(); break; case UnitMods.AttackPowerRanged: UpdateAttackPowerAndDamage(true); break; case UnitMods.DamageMainHand: UpdateDamagePhysical(WeaponAttackType.BaseAttack); break; case UnitMods.DamageOffHand: UpdateDamagePhysical(WeaponAttackType.OffAttack); break; case UnitMods.DamageRanged: UpdateDamagePhysical(WeaponAttackType.RangedAttack); break; default: break; } } int GetMinPower(PowerType power) { return power == PowerType.LunarPower ? -100 : 0; } // returns negative amount on power reduction public int ModifyPower(PowerType power, int dVal, bool withPowerUpdate = true) { int gain = 0; if (dVal == 0) return 0; if (dVal > 0) dVal *= (int)GetTotalAuraMultiplierByMiscValue(AuraType.ModPowerGainPct, (int)power); int curPower = GetPower(power); int val = (dVal + curPower); if (val <= GetMinPower(power)) { SetPower(power, GetMinPower(power), withPowerUpdate); return -curPower; } int maxPower = GetMaxPower(power); if (val < maxPower) { SetPower(power, val, withPowerUpdate); gain = val - curPower; } else if (curPower != maxPower) { SetPower(power, maxPower, withPowerUpdate); gain = maxPower - curPower; } return gain; } Stats GetStatByAuraGroup(UnitMods unitMod) { Stats stat = Stats.Strength; switch (unitMod) { case UnitMods.StatStrength: stat = Stats.Strength; break; case UnitMods.StatAgility: stat = Stats.Agility; break; case UnitMods.StatStamina: stat = Stats.Stamina; break; case UnitMods.StatIntellect: stat = Stats.Intellect; break; default: break; } return stat; } public void UpdateStatBuffMod(Stats stat) { float modPos = 0.0f; float modNeg = 0.0f; float factor = 0.0f; UnitMods unitMod = UnitMods.StatStart + (int)stat; // includes value from items and enchantments float modValue = GetFlatModifierValue(unitMod, UnitModifierFlatType.Base); if (modValue > 0.0f) modPos += modValue; else modNeg += modValue; if (IsGuardian()) { modValue = ((Guardian)this).GetBonusStatFromOwner(stat); if (modValue > 0.0f) modPos += modValue; else modNeg += modValue; } // SPELL_AURA_MOD_STAT_BONUS_PCT only affects BASE_VALUE modPos = MathFunctions.CalculatePct(modPos, Math.Max(GetFlatModifierValue(unitMod, UnitModifierFlatType.BasePCTExcludeCreate), -100.0f)); modNeg = MathFunctions.CalculatePct(modNeg, Math.Max(GetFlatModifierValue(unitMod, UnitModifierFlatType.BasePCTExcludeCreate), -100.0f)); modPos += GetTotalAuraModifier(AuraType.ModStat, aurEff => { if ((aurEff.GetMiscValue() < 0 || aurEff.GetMiscValue() == (int)stat) && aurEff.GetAmount() > 0) return true; return false; }); modNeg += GetTotalAuraModifier(AuraType.ModStat, aurEff => { if ((aurEff.GetMiscValue() < 0 || aurEff.GetMiscValue() == (int)stat) && aurEff.GetAmount() < 0) return true; return false; }); factor = GetTotalAuraMultiplier(AuraType.ModPercentStat, aurEff => { if (aurEff.GetMiscValue() == -1 || aurEff.GetMiscValue() == (int)stat) return true; return false; }); factor *= GetTotalAuraMultiplier(AuraType.ModTotalStatPercentage, aurEff => { if (aurEff.GetMiscValue() == -1 || aurEff.GetMiscValue() == (int)stat) return true; return false; }); modPos *= factor; modNeg *= factor; m_floatStatPosBuff[(int)stat] = modPos; m_floatStatNegBuff[(int)stat] = modNeg; UpdateStatBuffModForClient(stat); } void UpdateStatBuffModForClient(Stats stat) { SetUpdateFieldValue(ref m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.StatPosBuff, (int)stat), (int)m_floatStatPosBuff[(int)stat]); SetUpdateFieldValue(ref m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.StatNegBuff, (int)stat), (int)m_floatStatNegBuff[(int)stat]); } public virtual bool UpdateStats(Stats stat) { return false; } public virtual bool UpdateAllStats() { return false; } public virtual void UpdateResistances(SpellSchools school) { if (school > SpellSchools.Normal) { UnitMods unitMod = UnitMods.ResistanceStart + (int)school; float value = MathFunctions.CalculatePct(GetFlatModifierValue(unitMod, UnitModifierFlatType.Base), Math.Max(GetFlatModifierValue(unitMod, UnitModifierFlatType.BasePCTExcludeCreate), -100.0f)); value *= GetPctModifierValue(unitMod, UnitModifierPctType.Base); float baseValue = value; value += GetFlatModifierValue(unitMod, UnitModifierFlatType.Total); value *= GetPctModifierValue(unitMod, UnitModifierPctType.Total); SetResistance(school, (int)value); SetBonusResistanceMod(school, (int)(value - baseValue)); } else UpdateArmor(); } public virtual void UpdateArmor() { } public virtual void UpdateMaxHealth() { } public virtual void UpdateMaxPower(PowerType power) { } public virtual void UpdateAttackPowerAndDamage(bool ranged = false) { } public virtual void UpdateDamagePhysical(WeaponAttackType attType) { CalculateMinMaxDamage(attType, false, true, out float minDamage, out float maxDamage); switch (attType) { case WeaponAttackType.BaseAttack: default: SetUpdateFieldStatValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.MinDamage), minDamage); SetUpdateFieldStatValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.MaxDamage), maxDamage); break; case WeaponAttackType.OffAttack: SetUpdateFieldStatValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.MinOffHandDamage), minDamage); SetUpdateFieldStatValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.MaxOffHandDamage), maxDamage); break; case WeaponAttackType.RangedAttack: SetUpdateFieldStatValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.MinRangedDamage), minDamage); SetUpdateFieldStatValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.MaxRangedDamage), maxDamage); break; } } public virtual void CalculateMinMaxDamage(WeaponAttackType attType, bool normalized, bool addTotalPct, out float minDamage, out float maxDamage) { minDamage = 0f; maxDamage = 0f; } public void UpdateAllResistances() { for (var i = SpellSchools.Normal; i < SpellSchools.Max; ++i) UpdateResistances(i); } //Stats public float GetStat(Stats stat) { return m_unitData.Stats[(int)stat]; } public void SetStat(Stats stat, int val) { SetUpdateFieldValue(ref m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.Stats, (int)stat), val); } public uint GetCreateMana() { return m_unitData.BaseMana; } public void SetCreateMana(uint val) { SetUpdateFieldValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.BaseMana), val); } public uint GetArmor() { return (uint)GetResistance(SpellSchools.Normal); } public void SetArmor(int val, int bonusVal) { SetResistance(SpellSchools.Normal, val); SetBonusResistanceMod(SpellSchools.Normal, bonusVal); } public float GetCreateStat(Stats stat) { return CreateStats[(int)stat]; } public void SetCreateStat(Stats stat, float val) { CreateStats[(int)stat] = val; } public float GetPosStat(Stats stat) { return m_unitData.StatPosBuff[(int)stat]; } public float GetNegStat(Stats stat) { return m_unitData.StatNegBuff[(int)stat]; } public int GetResistance(SpellSchools school) { return m_unitData.Resistances[(int)school]; } public int GetBonusResistanceMod(SpellSchools school) { return m_unitData.BonusResistanceMods[(int)school]; } public int GetResistance(SpellSchoolMask mask) { int? resist = null; for (int i = (int)SpellSchools.Normal; i < (int)SpellSchools.Max; ++i) { int schoolResistance = GetResistance((SpellSchools)i); if (Convert.ToBoolean((int)mask & (1 << i)) && (!resist.HasValue || resist.Value > schoolResistance)) resist = schoolResistance; } // resist value will never be negative here return resist.HasValue ? resist.Value : 0; } public void SetResistance(SpellSchools school, int val) { SetUpdateFieldValue(ref m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.Resistances, (int)school), val); } public void SetBonusResistanceMod(SpellSchools school, int val) { SetUpdateFieldValue(ref m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.BonusResistanceMods, (int)school), val); } public void SetModCastingSpeed(float castingSpeed) { SetUpdateFieldValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.ModCastingSpeed), castingSpeed); } public void SetModSpellHaste(float spellHaste) { SetUpdateFieldValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.ModSpellHaste), spellHaste); } public void SetModHaste(float haste) { SetUpdateFieldValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.ModHaste), haste); } public void SetModRangedHaste(float rangedHaste) { SetUpdateFieldValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.ModRangedHaste), rangedHaste); } public void SetModHasteRegen(float hasteRegen) { SetUpdateFieldValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.ModHasteRegen), hasteRegen); } public void SetModTimeRate(float timeRate) { SetUpdateFieldValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.ModTimeRate), timeRate); } public void InitStatBuffMods() { for (var stat = Stats.Strength; stat < Stats.Max; ++stat) { m_floatStatPosBuff[(int)stat] = 0.0f; m_floatStatNegBuff[(int)stat] = 0.0f; UpdateStatBuffModForClient(stat); } } public bool CanModifyStats() { return canModifyStats; } public void SetCanModifyStats(bool modifyStats) { canModifyStats = modifyStats; } public float GetTotalStatValue(Stats stat) { UnitMods unitMod = UnitMods.StatStart + (int)stat; float value = MathFunctions.CalculatePct(GetFlatModifierValue(unitMod, UnitModifierFlatType.Base), Math.Max(GetFlatModifierValue(unitMod, UnitModifierFlatType.BasePCTExcludeCreate), -100.0f)); value += GetCreateStat(stat); value *= GetPctModifierValue(unitMod, UnitModifierPctType.Base); value += GetFlatModifierValue(unitMod, UnitModifierFlatType.Total); value *= GetPctModifierValue(unitMod, UnitModifierPctType.Total); return value; } //Health public uint GetCreateHealth() { return m_unitData.BaseHealth; } public void SetCreateHealth(uint val) { SetUpdateFieldValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.BaseHealth), val); } public ulong GetHealth() { return m_unitData.Health; } public void SetHealth(ulong val) { if (GetDeathState() == DeathState.JustDied || GetDeathState() == DeathState.Corpse) val = 0; else if (IsTypeId(TypeId.Player) && GetDeathState() == DeathState.Dead) val = 1; else { ulong maxHealth = GetMaxHealth(); if (maxHealth < val) val = maxHealth; } SetUpdateFieldValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.Health), val); // group update Player player = ToPlayer(); if (player) { if (player.GetGroup()) player.SetGroupUpdateFlag(GroupUpdateFlags.CurHp); } else if (IsPet()) { Pet pet = ToCreature().ToPet(); if (pet.IsControlled()) pet.SetGroupUpdateFlag(GroupUpdatePetFlags.CurHp); } } public ulong GetMaxHealth() { return m_unitData.MaxHealth; } public void SetMaxHealth(ulong val) { if (val == 0) val = 1; SetUpdateFieldValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.MaxHealth), val); ulong health = GetHealth(); // group update if (IsTypeId(TypeId.Player)) { if (ToPlayer().GetGroup()) ToPlayer().SetGroupUpdateFlag(GroupUpdateFlags.MaxHp); } else if (IsPet()) { Pet pet = ToCreature().ToPet(); if (pet.IsControlled()) pet.SetGroupUpdateFlag(GroupUpdatePetFlags.MaxHp); } if (val < health) SetHealth(val); } public float GetHealthPct() { return GetMaxHealth() != 0 ? 100.0f * GetHealth() / GetMaxHealth() : 0.0f; } public void SetFullHealth() { SetHealth(GetMaxHealth()); } public bool IsFullHealth() { return GetHealth() == GetMaxHealth(); } public bool HealthBelowPct(int pct) { return GetHealth() < CountPctFromMaxHealth(pct); } public bool HealthBelowPctDamaged(int pct, uint damage) { return GetHealth() - damage < CountPctFromMaxHealth(pct); } public bool HealthAbovePct(int pct) { return GetHealth() > CountPctFromMaxHealth(pct); } public bool HealthAbovePctHealed(int pct, uint heal) { return GetHealth() + heal > CountPctFromMaxHealth(pct); } public ulong CountPctFromMaxHealth(int pct) { return MathFunctions.CalculatePct(GetMaxHealth(), pct); } public ulong CountPctFromCurHealth(int pct) { return MathFunctions.CalculatePct(GetHealth(), pct); } public virtual float GetHealthMultiplierForTarget(WorldObject target) { return 1.0f; } public virtual float GetDamageMultiplierForTarget(WorldObject target) { return 1.0f; } public virtual float GetArmorMultiplierForTarget(WorldObject target) { return 1.0f; } //Powers public PowerType GetPowerType() { return (PowerType)(byte)m_unitData.DisplayPower; } public void SetPowerType(PowerType powerType, bool sendUpdate = true) { if (GetPowerType() == powerType) return; SetUpdateFieldValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.DisplayPower), (byte)powerType); if (!sendUpdate) return; Player thisPlayer = ToPlayer(); if (thisPlayer != null) { if (thisPlayer.GetGroup()) thisPlayer.SetGroupUpdateFlag(GroupUpdateFlags.PowerType); } /*else if (IsPet()) TODO 6.x { Pet pet = ToCreature().ToPet(); if (pet.isControlled()) pet.SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_POWER_TYPE); }*/ // Update max power UpdateMaxPower(powerType); // Update current power switch (powerType) { case PowerType.Mana: // Keep the same (druid form switching...) case PowerType.Energy: break; case PowerType.Rage: // Reset to zero SetPower(PowerType.Rage, 0); break; case PowerType.Focus: // Make it full SetFullPower(powerType); break; default: break; } } public void SetOverrideDisplayPowerId(uint powerDisplayId) { SetUpdateFieldValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.OverrideDisplayPowerID), powerDisplayId); } public void SetMaxPower(PowerType powerType, int val) { uint powerIndex = GetPowerIndex(powerType); if (powerIndex == (int)PowerType.Max || powerIndex >= (int)PowerType.MaxPerClass) return; int cur_power = GetPower(powerType); SetUpdateFieldValue(ref m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.MaxPower, (int)powerIndex), (uint)val); // group update if (IsTypeId(TypeId.Player)) { if (ToPlayer().GetGroup()) ToPlayer().SetGroupUpdateFlag(GroupUpdateFlags.MaxPower); } /*else if (IsPet()) TODO 6.x { Pet pet = ToCreature().ToPet(); if (pet.isControlled()) pet.SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_MAX_POWER); }*/ if (val < cur_power) SetPower(powerType, val); } public void SetPower(PowerType powerType, int val, bool withPowerUpdate = true) { uint powerIndex = GetPowerIndex(powerType); if (powerIndex == (int)PowerType.Max || powerIndex >= (int)PowerType.MaxPerClass) return; int maxPower = GetMaxPower(powerType); if (maxPower < val) val = maxPower; int oldPower = m_unitData.Power[(int)powerIndex]; SetUpdateFieldValue(ref m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.Power, (int)powerIndex), val); if (IsInWorld && withPowerUpdate) { PowerUpdate packet = new(); packet.Guid = GetGUID(); packet.Powers.Add(new PowerUpdatePower(val, (byte)powerType)); SendMessageToSet(packet, IsTypeId(TypeId.Player)); } TriggerOnPowerChangeAuras(powerType, oldPower, val); // group update if (IsTypeId(TypeId.Player)) { Player player = ToPlayer(); if (player.GetGroup()) player.SetGroupUpdateFlag(GroupUpdateFlags.CurPower); } /*else if (IsPet()) TODO 6.x { Pet pet = ToCreature().ToPet(); if (pet.isControlled()) pet.SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_CUR_POWER); }*/ } public void SetFullPower(PowerType powerType) { SetPower(powerType, GetMaxPower(powerType)); } public int GetPower(PowerType powerType) { uint powerIndex = GetPowerIndex(powerType); if (powerIndex == (int)PowerType.Max || powerIndex >= (int)PowerType.MaxPerClass) return 0; return m_unitData.Power[(int)powerIndex]; } public int GetMaxPower(PowerType powerType) { uint powerIndex = GetPowerIndex(powerType); if (powerIndex == (int)PowerType.Max || powerIndex >= (int)PowerType.MaxPerClass) return 0; return (int)(uint)m_unitData.MaxPower[(int)powerIndex]; } public int GetCreatePowerValue(PowerType powerType) { if (powerType == PowerType.Mana) return (int)GetCreateMana(); PowerTypeRecord powerTypeEntry = Global.DB2Mgr.GetPowerTypeEntry(powerType); if (powerTypeEntry != null) return powerTypeEntry.MaxBasePower; return 0; } public virtual uint GetPowerIndex(PowerType powerType) { return 0; } public float GetPowerPct(PowerType powerType) { return GetMaxPower(powerType) != 0 ? 100.0f * GetPower(powerType) / GetMaxPower(powerType) : 0.0f; } void TriggerOnPowerChangeAuras(PowerType power, int oldVal, int newVal) { var effects = GetAuraEffectsByType(AuraType.TriggerSpellOnPowerPct); var effectsAmount = GetAuraEffectsByType(AuraType.TriggerSpellOnPowerAmount); effects.AddRange(effectsAmount); foreach (AuraEffect effect in effects) { if (effect.GetMiscValue() == (int)power) { uint effectAmount = (uint)effect.GetAmount(); uint triggerSpell = effect.GetSpellEffectInfo().TriggerSpell; float oldValueCheck = oldVal; float newValueCheck = newVal; if (effect.GetAuraType() == AuraType.TriggerSpellOnPowerPct) { int maxPower = GetMaxPower(power); oldValueCheck = MathFunctions.GetPctOf(oldVal, maxPower); newValueCheck = MathFunctions.GetPctOf(newVal, maxPower); } switch ((AuraTriggerOnPowerChangeDirection)effect.GetMiscValueB()) { case AuraTriggerOnPowerChangeDirection.Gain: if (oldValueCheck >= effect.GetAmount() || newValueCheck < effectAmount) continue; break; case AuraTriggerOnPowerChangeDirection.Loss: if (oldValueCheck <= effect.GetAmount() || newValueCheck > effectAmount) continue; break; default: break; } CastSpell(this, triggerSpell, new CastSpellExtraArgs(effect)); } } } public bool CanApplyResilience() { return !IsVehicle() && GetOwnerGUID().IsPlayer(); } public static void ApplyResilience(Unit victim, ref int damage) { // player mounted on multi-passenger mount is also classified as vehicle if (victim.IsVehicle() && !victim.IsPlayer()) return; Unit target = null; if (victim.IsPlayer()) target = victim; else // victim->GetTypeId() == TYPEID_UNIT { Unit owner = victim.GetOwner(); if (owner != null) if (owner.IsPlayer()) target = owner; } if (!target) return; damage -= (int)target.GetDamageReduction((uint)damage); } public int CalculateAOEAvoidance(int damage, uint schoolMask, ObjectGuid casterGuid) { damage = (int)((float)damage * GetTotalAuraMultiplierByMiscMask(AuraType.ModAoeDamageAvoidance, schoolMask)); if (casterGuid.IsAnyTypeCreature()) damage = (int)((float)damage * GetTotalAuraMultiplierByMiscMask(AuraType.ModCreatureAoeDamageAvoidance, schoolMask)); return damage; } // player or player's pet resilience (-1%) uint GetDamageReduction(uint damage) { return GetCombatRatingDamageReduction(CombatRating.ResiliencePlayerDamage, 1.0f, 100.0f, damage); } float GetCombatRatingReduction(CombatRating cr) { Player player = ToPlayer(); if (player) return player.GetRatingBonusValue(cr); // Player's pet get resilience from owner else if (IsPet() && GetOwner()) { Player owner = GetOwner().ToPlayer(); if (owner) return owner.GetRatingBonusValue(cr); } return 0.0f; } uint GetCombatRatingDamageReduction(CombatRating cr, float rate, float cap, uint damage) { float percent = Math.Min(GetCombatRatingReduction(cr) * rate, cap); return MathFunctions.CalculatePct(damage, percent); } public void SetAttackPower(int attackPower) { SetUpdateFieldValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.AttackPower), attackPower); } public void SetAttackPowerModPos(int attackPowerMod) { SetUpdateFieldValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.AttackPowerModPos), attackPowerMod); } public void SetAttackPowerModNeg(int attackPowerMod) { SetUpdateFieldValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.AttackPowerModNeg), attackPowerMod); } public void SetAttackPowerMultiplier(float attackPowerMult) { SetUpdateFieldValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.AttackPowerMultiplier), attackPowerMult); } public void SetRangedAttackPower(int attackPower) { SetUpdateFieldValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.RangedAttackPower), attackPower); } public void SetRangedAttackPowerModPos(int attackPowerMod) { SetUpdateFieldValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.RangedAttackPowerModPos), attackPowerMod); } public void SetRangedAttackPowerModNeg(int attackPowerMod) { SetUpdateFieldValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.RangedAttackPowerModNeg), attackPowerMod); } public void SetRangedAttackPowerMultiplier(float attackPowerMult) { SetUpdateFieldValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.RangedAttackPowerMultiplier), attackPowerMult); } public void SetMainHandWeaponAttackPower(int attackPower) { SetUpdateFieldValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.MainHandWeaponAttackPower), attackPower); } public void SetOffHandWeaponAttackPower(int attackPower) { SetUpdateFieldValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.OffHandWeaponAttackPower), attackPower); } public void SetRangedWeaponAttackPower(int attackPower) { SetUpdateFieldValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.RangedWeaponAttackPower), attackPower); } //Chances public override float MeleeSpellMissChance(Unit victim, WeaponAttackType attType, SpellInfo spellInfo) { //calculate miss chance float missChance = victim.GetUnitMissChance(); // melee attacks while dual wielding have +19% chance to miss if (spellInfo == null && HaveOffhandWeapon() && !IsInFeralForm()) missChance += 19.0f; // Spellmod from SpellModOp.HitChance float resistMissChance = 100.0f; if (spellInfo != null) { Player modOwner = GetSpellModOwner(); if (modOwner != null) modOwner.ApplySpellMod(spellInfo, SpellModOp.HitChance, ref resistMissChance); } missChance += resistMissChance - 100.0f; if (attType == WeaponAttackType.RangedAttack) missChance -= ModRangedHitChance; else missChance -= ModMeleeHitChance; // miss chance from auras after calculating skill based miss missChance -= GetTotalAuraModifier(AuraType.ModHitChance); if (attType == WeaponAttackType.RangedAttack) missChance -= victim.GetTotalAuraModifier(AuraType.ModAttackerRangedHitChance); else missChance -= victim.GetTotalAuraModifier(AuraType.ModAttackerMeleeHitChance); return Math.Max(missChance, 0f); } float GetUnitCriticalChanceDone(WeaponAttackType attackType) { float chance = 0.0f; Player thisPlayer = ToPlayer(); if (thisPlayer != null) { switch (attackType) { case WeaponAttackType.BaseAttack: chance = thisPlayer.m_activePlayerData.CritPercentage; break; case WeaponAttackType.OffAttack: chance = thisPlayer.m_activePlayerData.OffhandCritPercentage; break; case WeaponAttackType.RangedAttack: chance = thisPlayer.m_activePlayerData.RangedCritPercentage; break; } } else { if (!ToCreature().GetCreatureTemplate().FlagsExtra.HasAnyFlag(CreatureFlagsExtra.NoCrit)) { chance = 5.0f; chance += GetTotalAuraModifier(AuraType.ModWeaponCritPercent); chance += GetTotalAuraModifier(AuraType.ModCritPct); } } return chance; } float GetUnitCriticalChanceTaken(Unit attacker, WeaponAttackType attackType, float critDone) { float chance = critDone; // flat aura mods if (attackType == WeaponAttackType.RangedAttack) chance += GetTotalAuraModifier(AuraType.ModAttackerRangedCritChance); else chance += GetTotalAuraModifier(AuraType.ModAttackerMeleeCritChance); chance += GetTotalAuraModifier(AuraType.ModCritChanceVersusTargetHealth, aurEff => !HealthBelowPct(aurEff.GetMiscValueB())); chance += GetTotalAuraModifier(AuraType.ModCritChanceForCaster, aurEff => aurEff.GetCasterGUID() == attacker.GetGUID()); TempSummon tempSummon = attacker.ToTempSummon(); if (tempSummon != null) chance += GetTotalAuraModifier(AuraType.ModCritChanceForCasterPet, aurEff => aurEff.GetCasterGUID() == tempSummon.GetSummonerGUID()); chance += GetTotalAuraModifier(AuraType.ModAttackerSpellAndWeaponCritChance); return Math.Max(chance, 0.0f); } float GetUnitCriticalChanceAgainst(WeaponAttackType attackType, Unit victim) { float chance = GetUnitCriticalChanceDone(attackType); return victim.GetUnitCriticalChanceTaken(this, attackType, chance); } float GetUnitDodgeChance(WeaponAttackType attType, Unit victim) { int levelDiff = (int)(victim.GetLevelForTarget(this) - GetLevelForTarget(victim)); float chance = 0.0f; float levelBonus = 0.0f; Player playerVictim = victim.ToPlayer(); if (playerVictim) chance = playerVictim.m_activePlayerData.DodgePercentage; else { if (!victim.IsTotem()) { chance = 3.0f; chance += victim.GetTotalAuraModifier(AuraType.ModDodgePercent); if (levelDiff > 0) levelBonus = 1.5f * levelDiff; } } chance += levelBonus; // Reduce enemy dodge chance by SPELL_AURA_MOD_COMBAT_RESULT_CHANCE chance += GetTotalAuraModifierByMiscValue(AuraType.ModCombatResultChance, (int)VictimState.Dodge); // reduce dodge by SPELL_AURA_MOD_ENEMY_DODGE chance += GetTotalAuraModifier(AuraType.ModEnemyDodge); // Reduce dodge chance by attacker expertise rating if (IsTypeId(TypeId.Player)) chance -= ToPlayer().GetExpertiseDodgeOrParryReduction(attType); else chance -= GetTotalAuraModifier(AuraType.ModExpertise) / 4.0f; return Math.Max(chance, 0.0f); } float GetUnitParryChance(WeaponAttackType attType, Unit victim) { int levelDiff = (int)(victim.GetLevelForTarget(this) - GetLevelForTarget(victim)); float chance = 0.0f; float levelBonus = 0.0f; Player playerVictim = victim.ToPlayer(); if (playerVictim) { if (playerVictim.CanParry()) { Item tmpitem = playerVictim.GetWeaponForAttack(WeaponAttackType.BaseAttack, true); if (!tmpitem) tmpitem = playerVictim.GetWeaponForAttack(WeaponAttackType.OffAttack, true); if (tmpitem) chance = playerVictim.m_activePlayerData.ParryPercentage; } } else { if (!victim.IsTotem() && !victim.ToCreature().GetCreatureTemplate().FlagsExtra.HasAnyFlag(CreatureFlagsExtra.NoParry)) { chance = 6.0f; chance += victim.GetTotalAuraModifier(AuraType.ModParryPercent); if (levelDiff > 0) levelBonus = 1.5f * levelDiff; } } chance += levelBonus; // Reduce parry chance by attacker expertise rating if (IsTypeId(TypeId.Player)) chance -= ToPlayer().GetExpertiseDodgeOrParryReduction(attType); else chance -= GetTotalAuraModifier(AuraType.ModExpertise) / 4.0f; return Math.Max(chance, 0.0f); } float GetUnitMissChance() { float miss_chance = 5.0f; return miss_chance; } float GetUnitBlockChance(WeaponAttackType attType, Unit victim) { int levelDiff = (int)(victim.GetLevelForTarget(this) - GetLevelForTarget(victim)); float chance = 0.0f; float levelBonus = 0.0f; Player playerVictim = victim.ToPlayer(); if (playerVictim) { if (playerVictim.CanBlock()) { Item tmpitem = playerVictim.GetUseableItemByPos(InventorySlots.Bag0, EquipmentSlot.OffHand); if (tmpitem && !tmpitem.IsBroken() && tmpitem.GetTemplate().GetInventoryType() == InventoryType.Shield) chance = playerVictim.m_activePlayerData.BlockPercentage; } } else { if (!victim.IsTotem() && !(victim.ToCreature().GetCreatureTemplate().FlagsExtra.HasAnyFlag(CreatureFlagsExtra.NoBlock))) { chance = 3.0f; chance += victim.GetTotalAuraModifier(AuraType.ModBlockPercent); if (levelDiff > 0) levelBonus = 1.5f * levelDiff; } } chance += levelBonus; return Math.Max(chance, 0.0f); } public int GetMechanicResistChance(SpellInfo spellInfo) { if (spellInfo == null) return 0; int resistMech = 0; foreach (var spellEffectInfo in spellInfo.GetEffects()) { if (!spellEffectInfo.IsEffect()) break; int effect_mech = (int)spellInfo.GetEffectMechanic(spellEffectInfo.EffectIndex); if (effect_mech != 0) { int temp = GetTotalAuraModifierByMiscValue(AuraType.ModMechanicResistance, effect_mech); if (resistMech < temp) resistMech = temp; } } return Math.Max(resistMech, 0); } public void ApplyModManaCostMultiplier(float manaCostMultiplier, bool apply) { ApplyModUpdateFieldValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.ManaCostMultiplier), manaCostMultiplier, apply); } public void ApplyModManaCostModifier(SpellSchools school, int mod, bool apply) { ApplyModUpdateFieldValue(ref m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.ManaCostModifier, (int)school), mod, apply); } } public partial class Player { public override bool UpdateAllStats() { for (var i = Stats.Strength; i < Stats.Max; ++i) { float value = GetTotalStatValue(i); SetStat(i, (int)value); } UpdateArmor(); // calls UpdateAttackPowerAndDamage() in UpdateArmor for SPELL_AURA_MOD_ATTACK_POWER_OF_ARMOR UpdateAttackPowerAndDamage(true); UpdateMaxHealth(); for (var i = PowerType.Mana; i < PowerType.Max; ++i) UpdateMaxPower(i); UpdateAllRatings(); UpdateAllCritPercentages(); UpdateSpellCritChance(); UpdateBlockPercentage(); UpdateParryPercentage(); UpdateDodgePercentage(); UpdateSpellDamageAndHealingBonus(); UpdateManaRegen(); UpdateExpertise(WeaponAttackType.BaseAttack); UpdateExpertise(WeaponAttackType.OffAttack); RecalculateRating(CombatRating.ArmorPenetration); UpdateAllResistances(); return true; } public override bool UpdateStats(Stats stat) { // value = ((base_value * base_pct) + total_value) * total_pct float value = GetTotalStatValue(stat); SetStat(stat, (int)value); if (stat == Stats.Stamina || stat == Stats.Intellect || stat == Stats.Strength) { Pet pet = GetPet(); if (pet != null) pet.UpdateStats(stat); } switch (stat) { case Stats.Agility: UpdateAllCritPercentages(); UpdateDodgePercentage(); break; case Stats.Stamina: UpdateMaxHealth(); break; case Stats.Intellect: UpdateSpellCritChance(); break; default: break; } if (stat == Stats.Strength) UpdateAttackPowerAndDamage(false); else if (stat == Stats.Agility) { UpdateAttackPowerAndDamage(false); UpdateAttackPowerAndDamage(true); } UpdateArmor(); UpdateSpellDamageAndHealingBonus(); UpdateManaRegen(); return true; } public override void UpdateResistances(SpellSchools school) { if (school > SpellSchools.Normal) { base.UpdateResistances(school); Pet pet = GetPet(); if (pet != null) pet.UpdateResistances(school); } else UpdateArmor(); } public void ApplyModTargetResistance(int mod, bool apply) { ApplyModUpdateFieldValue(m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.ModTargetResistance), mod, apply); } public void ApplyModTargetPhysicalResistance(int mod, bool apply) { ApplyModUpdateFieldValue(m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.ModTargetPhysicalResistance), mod, apply); } public void RecalculateRating(CombatRating cr) { ApplyRatingMod(cr, 0, true); } public void ApplyModDamageDonePos(SpellSchools school, int mod, bool apply) { ApplyModUpdateFieldValue(ref m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.ModDamageDonePos, (int)school), mod, apply); } public void ApplyModDamageDoneNeg(SpellSchools school, int mod, bool apply) { ApplyModUpdateFieldValue(ref m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.ModDamageDoneNeg, (int)school), mod, apply); } public void ApplyModDamageDonePercent(SpellSchools school, float pct, bool apply) { ApplyPercentModUpdateFieldValue(ref m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.ModDamageDonePercent, (int)school), pct, apply); } public void SetModDamageDonePercent(SpellSchools school, float pct) { SetUpdateFieldValue(ref m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.ModDamageDonePercent, (int)school), pct); } public void ApplyRatingMod(CombatRating combatRating, int value, bool apply) { baseRatingValue[(int)combatRating] += (apply ? value : -value); UpdateRating(combatRating); } public override void CalculateMinMaxDamage(WeaponAttackType attType, bool normalized, bool addTotalPct, out float min_damage, out float max_damage) { UnitMods unitMod; switch (attType) { case WeaponAttackType.BaseAttack: default: unitMod = UnitMods.DamageMainHand; break; case WeaponAttackType.OffAttack: unitMod = UnitMods.DamageOffHand; break; case WeaponAttackType.RangedAttack: unitMod = UnitMods.DamageRanged; break; } float attackPowerMod = Math.Max(GetAPMultiplier(attType, normalized), 0.25f); float baseValue = GetFlatModifierValue(unitMod, UnitModifierFlatType.Base) + GetTotalAttackPowerValue(attType, false) / 3.5f * attackPowerMod; float basePct = GetPctModifierValue(unitMod, UnitModifierPctType.Base); float totalValue = GetFlatModifierValue(unitMod, UnitModifierFlatType.Total); float totalPct = addTotalPct ? GetPctModifierValue(unitMod, UnitModifierPctType.Total) : 1.0f; float weaponMinDamage = GetWeaponDamageRange(attType, WeaponDamageRange.MinDamage); float weaponMaxDamage = GetWeaponDamageRange(attType, WeaponDamageRange.MaxDamage); float versaDmgMod = 1.0f; MathFunctions.AddPct(ref versaDmgMod, GetRatingBonusValue(CombatRating.VersatilityDamageDone) + (float)GetTotalAuraModifier(AuraType.ModVersatility)); SpellShapeshiftFormRecord shapeshift = CliDB.SpellShapeshiftFormStorage.LookupByKey(GetShapeshiftForm()); if (shapeshift != null && shapeshift.CombatRoundTime != 0) { weaponMinDamage = weaponMinDamage * shapeshift.CombatRoundTime / 1000.0f / attackPowerMod; weaponMaxDamage = weaponMaxDamage * shapeshift.CombatRoundTime / 1000.0f / attackPowerMod; } else if (!CanUseAttackType(attType)) //check if player not in form but still can't use (disarm case) { //cannot use ranged/off attack, set values to 0 if (attType != WeaponAttackType.BaseAttack) { min_damage = 0; max_damage = 0; return; } weaponMinDamage = SharedConst.BaseMinDamage; weaponMaxDamage = SharedConst.BaseMaxDamage; } min_damage = ((baseValue + weaponMinDamage) * basePct + totalValue) * totalPct * versaDmgMod; max_damage = ((baseValue + weaponMaxDamage) * basePct + totalValue) * totalPct * versaDmgMod; } public void UpdateAllCritPercentages() { float value = 5.0f; SetBaseModPctValue(BaseModGroup.CritPercentage, value); SetBaseModPctValue(BaseModGroup.OffhandCritPercentage, value); SetBaseModPctValue(BaseModGroup.RangedCritPercentage, value); UpdateCritPercentage(WeaponAttackType.BaseAttack); UpdateCritPercentage(WeaponAttackType.OffAttack); UpdateCritPercentage(WeaponAttackType.RangedAttack); } public void UpdateManaRegen() { uint manaIndex = GetPowerIndex(PowerType.Mana); if (manaIndex == (int)PowerType.Max) return; // Get base of Mana Pool in sBaseMPGameTable uint basemana; Global.ObjectMgr.GetPlayerClassLevelInfo(GetClass(), GetLevel(), out basemana); float base_regen = basemana / 100.0f; base_regen += GetTotalAuraModifierByMiscValue(AuraType.ModPowerRegen, (int)PowerType.Mana); // Apply PCT bonus from SPELL_AURA_MOD_POWER_REGEN_PERCENT base_regen *= GetTotalAuraMultiplierByMiscValue(AuraType.ModPowerRegenPercent, (int)PowerType.Mana); // Apply PCT bonus from SPELL_AURA_MOD_MANA_REGEN_PCT base_regen *= GetTotalAuraMultiplierByMiscValue(AuraType.ModManaRegenPct, (int)PowerType.Mana); SetUpdateFieldValue(ref m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.PowerRegenFlatModifier, (int)manaIndex), base_regen); SetUpdateFieldValue(ref m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.PowerRegenInterruptedFlatModifier, (int)manaIndex), base_regen); } public void UpdateSpellDamageAndHealingBonus() { // Magic damage modifiers implemented in Unit.SpellDamageBonusDone // This information for client side use only // Get healing bonus for all schools SetUpdateFieldStatValue(m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.ModHealingDonePos), (int)SpellBaseHealingBonusDone(SpellSchoolMask.All)); // Get damage bonus for all schools var modDamageAuras = GetAuraEffectsByType(AuraType.ModDamageDone); for (int i = (int)SpellSchools.Holy; i < (int)SpellSchools.Max; ++i) { SetUpdateFieldValue(ref m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.ModDamageDoneNeg, i), modDamageAuras.Aggregate(0, (negativeMod, aurEff) => { if (aurEff.GetAmount() < 0 && Convert.ToBoolean(aurEff.GetMiscValue() & (1 << i))) negativeMod += aurEff.GetAmount(); return negativeMod; })); SetUpdateFieldStatValue(ref m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.ModDamageDonePos, i), (SpellBaseDamageBonusDone((SpellSchoolMask)(1 << i)) - m_activePlayerData.ModDamageDoneNeg[i])); } if (HasAuraType(AuraType.OverrideAttackPowerBySpPct)) { UpdateAttackPowerAndDamage(); UpdateAttackPowerAndDamage(true); } } public uint GetBaseSpellPowerBonus() { return m_baseSpellPower; } public override void UpdateAttackPowerAndDamage(bool ranged = false) { float val2; float level = GetLevel(); var entry = CliDB.ChrClassesStorage.LookupByKey(GetClass()); UnitMods unitMod = ranged ? UnitMods.AttackPowerRanged : UnitMods.AttackPower; if (!HasAuraType(AuraType.OverrideAttackPowerBySpPct)) { if (!ranged) { float strengthValue = Math.Max((GetStat(Stats.Strength)) * entry.AttackPowerPerStrength, 0.0f); float agilityValue = Math.Max((GetStat(Stats.Agility)) * entry.AttackPowerPerAgility, 0.0f); var form = CliDB.SpellShapeshiftFormStorage.LookupByKey((uint)GetShapeshiftForm()); // Directly taken from client, SHAPESHIFT_FLAG_AP_FROM_STRENGTH ? if (form != null && Convert.ToBoolean((uint)form.Flags & 0x20)) agilityValue += Math.Max(GetStat(Stats.Agility) * entry.AttackPowerPerStrength, 0.0f); val2 = strengthValue + agilityValue; } else val2 = (level + Math.Max(GetStat(Stats.Agility), 0.0f)) * entry.RangedAttackPowerPerAgility; } else { int minSpellPower = m_activePlayerData.ModHealingDonePos; for (var i = SpellSchools.Holy; i < SpellSchools.Max; ++i) minSpellPower = Math.Min(minSpellPower, m_activePlayerData.ModDamageDonePos[(int)i]); val2 = MathFunctions.CalculatePct(minSpellPower, m_activePlayerData.OverrideAPBySpellPowerPercent); } SetStatFlatModifier(unitMod, UnitModifierFlatType.Base, val2); float base_attPower = GetFlatModifierValue(unitMod, UnitModifierFlatType.Base) * GetPctModifierValue(unitMod, UnitModifierPctType.Base); float attPowerMod = GetFlatModifierValue(unitMod, UnitModifierFlatType.Total); float attPowerMultiplier = GetPctModifierValue(unitMod, UnitModifierPctType.Total) - 1.0f; if (ranged) { SetRangedAttackPower((int)base_attPower); SetRangedAttackPowerModPos((int)attPowerMod); SetRangedAttackPowerMultiplier(attPowerMultiplier); } else { SetAttackPower((int)base_attPower); SetAttackPowerModPos((int)attPowerMod); SetAttackPowerMultiplier(attPowerMultiplier); } Pet pet = GetPet(); //update pet's AP Guardian guardian = GetGuardianPet(); //automatically update weapon damage after attack power modification if (ranged) { UpdateDamagePhysical(WeaponAttackType.RangedAttack); if (pet != null && pet.IsHunterPet()) // At ranged attack change for hunter pet pet.UpdateAttackPowerAndDamage(); } else { UpdateDamagePhysical(WeaponAttackType.BaseAttack); Item offhand = GetWeaponForAttack(WeaponAttackType.OffAttack, true); if (offhand) if (CanDualWield() || offhand.GetTemplate().HasFlag(ItemFlags3.AlwaysAllowDualWield)) UpdateDamagePhysical(WeaponAttackType.OffAttack); if (HasAuraType(AuraType.OverrideSpellPowerByApPct)) UpdateSpellDamageAndHealingBonus(); if (pet != null && pet.IsPetGhoul()) // At melee attack power change for DK pet pet.UpdateAttackPowerAndDamage(); if (guardian != null && guardian.IsSpiritWolf()) // At melee attack power change for Shaman feral spirit guardian.UpdateAttackPowerAndDamage(); } } public override void UpdateArmor() { UnitMods unitMod = UnitMods.Armor; float value = GetFlatModifierValue(unitMod, UnitModifierFlatType.Base); // base armor value *= GetPctModifierValue(unitMod, UnitModifierPctType.Base); // armor percent // SPELL_AURA_MOD_ARMOR_PCT_FROM_STAT counts as base armor GetTotalAuraModifier(AuraType.ModArmorPctFromStat, aurEff => { int miscValue = aurEff.GetMiscValue(); Stats stat = (miscValue != -2) ? (Stats)miscValue : GetPrimaryStat(); value += MathFunctions.CalculatePct((float)GetStat(stat), aurEff.GetAmount()); return true; }); float baseValue = value; value += GetFlatModifierValue(unitMod, UnitModifierFlatType.Total); // bonus armor from auras and items value *= GetPctModifierValue(unitMod, UnitModifierPctType.Total); SetArmor((int)value, (int)(value - baseValue)); Pet pet = GetPet(); if (pet) pet.UpdateArmor(); UpdateAttackPowerAndDamage(); // armor dependent auras update for SPELL_AURA_MOD_ATTACK_POWER_OF_ARMOR } void _ApplyAllStatBonuses() { SetCanModifyStats(false); _ApplyAllAuraStatMods(); _ApplyAllItemMods(); ApplyAllAzeriteItemMods(true); SetCanModifyStats(true); UpdateAllStats(); } void _RemoveAllStatBonuses() { SetCanModifyStats(false); ApplyAllAzeriteItemMods(false); _RemoveAllItemMods(); _RemoveAllAuraStatMods(); SetCanModifyStats(true); UpdateAllStats(); } void UpdateAllRatings() { for (CombatRating cr = 0; cr < CombatRating.Max; ++cr) UpdateRating(cr); } public void UpdateRating(CombatRating cr) { int amount = baseRatingValue[(int)cr]; foreach (AuraEffect aurEff in GetAuraEffectsByType(AuraType.ModCombatRatingFromCombatRating)) { if ((aurEff.GetMiscValueB() & (1 << (int)cr)) != 0) { short? highestRating = null; for (byte dependentRating = 0; dependentRating < (int)CombatRating.Max; ++dependentRating) if ((aurEff.GetMiscValue() & (1 << dependentRating)) != 0) highestRating = (short)Math.Max(highestRating.HasValue ? highestRating.Value : baseRatingValue[dependentRating], baseRatingValue[dependentRating]); if (highestRating != 0) amount += MathFunctions.CalculatePct(highestRating.Value, aurEff.GetAmount()); } } foreach (var aurEff in GetAuraEffectsByType(AuraType.ModRatingPct)) if (Convert.ToBoolean(aurEff.GetMiscValue() & (1 << (int)cr))) amount += MathFunctions.CalculatePct(amount, aurEff.GetAmount()); if (amount < 0) amount = 0; uint oldRating = m_activePlayerData.CombatRatings[(int)cr]; SetUpdateFieldValue(ref m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.CombatRatings, (int)cr), (uint)amount); bool affectStats = CanModifyStats(); switch (cr) { case CombatRating.Amplify: case CombatRating.DefenseSkill: break; case CombatRating.Dodge: UpdateDodgePercentage(); break; case CombatRating.Parry: UpdateParryPercentage(); break; case CombatRating.Block: UpdateBlockPercentage(); break; case CombatRating.HitMelee: UpdateMeleeHitChances(); break; case CombatRating.HitRanged: UpdateRangedHitChances(); break; case CombatRating.HitSpell: UpdateSpellHitChances(); break; case CombatRating.CritMelee: if (affectStats) { UpdateCritPercentage(WeaponAttackType.BaseAttack); UpdateCritPercentage(WeaponAttackType.OffAttack); } break; case CombatRating.CritRanged: if (affectStats) UpdateCritPercentage(WeaponAttackType.RangedAttack); break; case CombatRating.CritSpell: if (affectStats) UpdateSpellCritChance(); break; case CombatRating.Corruption: case CombatRating.CorruptionResistance: UpdateCorruption(); break; case CombatRating.HasteMelee: case CombatRating.HasteRanged: case CombatRating.HasteSpell: { // explicit affected values float multiplier = GetRatingMultiplier(cr); float oldVal = ApplyRatingDiminishing(cr, oldRating * multiplier); float newVal = ApplyRatingDiminishing(cr, amount * multiplier); switch (cr) { case CombatRating.HasteMelee: ApplyAttackTimePercentMod(WeaponAttackType.BaseAttack, oldVal, false); ApplyAttackTimePercentMod(WeaponAttackType.OffAttack, oldVal, false); ApplyAttackTimePercentMod(WeaponAttackType.BaseAttack, newVal, true); ApplyAttackTimePercentMod(WeaponAttackType.OffAttack, newVal, true); if (GetClass() == Class.Deathknight) UpdateAllRunesRegen(); break; case CombatRating.HasteRanged: ApplyAttackTimePercentMod(WeaponAttackType.RangedAttack, oldVal, false); ApplyAttackTimePercentMod(WeaponAttackType.RangedAttack, newVal, true); break; case CombatRating.HasteSpell: ApplyCastTimePercentMod(oldVal, false); ApplyCastTimePercentMod(newVal, true); break; default: break; } break; } case CombatRating.Expertise: if (affectStats) { UpdateExpertise(WeaponAttackType.BaseAttack); UpdateExpertise(WeaponAttackType.OffAttack); } break; case CombatRating.ArmorPenetration: if (affectStats) UpdateArmorPenetration(amount); break; case CombatRating.Mastery: UpdateMastery(); break; case CombatRating.VersatilityDamageDone: UpdateVersatilityDamageDone(); break; case CombatRating.VersatilityHealingDone: UpdateHealingDonePercentMod(); break; } } public void UpdateMastery() { if (!CanUseMastery()) { SetUpdateFieldValue(m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.Mastery), 0.0f); return; } float value = GetTotalAuraModifier(AuraType.Mastery); value += GetRatingBonusValue(CombatRating.Mastery); SetUpdateFieldValue(m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.Mastery), value); ChrSpecializationRecord chrSpec = CliDB.ChrSpecializationStorage.LookupByKey(GetPrimarySpecialization()); if (chrSpec == null) return; foreach (uint masterySpellId in chrSpec.MasterySpellID) { Aura aura = GetAura(masterySpellId); if (aura != null) { foreach (var spellEffectInfo in aura.GetSpellInfo().GetEffects()) { float mult = spellEffectInfo.BonusCoefficient; if (MathFunctions.fuzzyEq(mult, 0.0f)) continue; aura.GetEffect(spellEffectInfo.EffectIndex).ChangeAmount((int)(value * mult)); } } } } public void UpdateVersatilityDamageDone() { // No proof that CR_VERSATILITY_DAMAGE_DONE is allways = ActivePlayerData::Versatility SetUpdateFieldValue(m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.Versatility), (int)m_activePlayerData.CombatRatings[(int)CombatRating.VersatilityDamageDone]); if (GetClass() == Class.Hunter) UpdateDamagePhysical(WeaponAttackType.RangedAttack); else UpdateDamagePhysical(WeaponAttackType.BaseAttack); } public void UpdateHealingDonePercentMod() { float value = 1.0f; MathFunctions.AddPct(ref value, GetRatingBonusValue(CombatRating.VersatilityHealingDone) + GetTotalAuraModifier(AuraType.ModVersatility)); foreach (AuraEffect auraEffect in GetAuraEffectsByType(AuraType.ModHealingDonePercent)) MathFunctions.AddPct(ref value, auraEffect.GetAmount()); for (int i = 0; i < (int)SpellSchools.Max; ++i) SetUpdateFieldStatValue(ref m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.ModHealingDonePercent, i), value); } void UpdateCorruption() { float effectiveCorruption = GetRatingBonusValue(CombatRating.Corruption) - GetRatingBonusValue(CombatRating.CorruptionResistance); foreach (var corruptionEffect in CliDB.CorruptionEffectsStorage.Values) { if (((CorruptionEffectsFlag)corruptionEffect.Flags).HasAnyFlag(CorruptionEffectsFlag.Disabled)) continue; if (effectiveCorruption < corruptionEffect.MinCorruption) { RemoveAura(corruptionEffect.Aura); continue; } PlayerConditionRecord playerCondition = CliDB.PlayerConditionStorage.LookupByKey(corruptionEffect.PlayerConditionID); if (playerCondition != null) { if (!ConditionManager.IsPlayerMeetingCondition(this, playerCondition)) { RemoveAura(corruptionEffect.Aura); continue; } } CastSpell(this, corruptionEffect.Aura, true); } } void UpdateArmorPenetration(int amount) { // Store Rating Value SetUpdateFieldValue(ref m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.CombatRatings, (int)CombatRating.ArmorPenetration), (uint)amount); } float CalculateDiminishingReturns(float[] capArray, Class playerClass, float nonDiminishValue, float diminishValue) { float[] m_diminishing_k = { 0.9560f, // Warrior 0.9560f, // Paladin 0.9880f, // Hunter 0.9880f, // Rogue 0.9830f, // Priest 0.9560f, // DK 0.9880f, // Shaman 0.9830f, // Mage 0.9830f, // Warlock 0.9830f, // Monk 0.9720f, // Druid 0.9830f // Demon Hunter }; // 1 1 k cx // --- = --- + --- <=> x' = -------- // x' c x x + ck // where: // k is m_diminishing_k for that class // c is capArray for that class // x is chance before DR (diminishValue) // x' is chance after DR (our result) uint classIdx = (byte)playerClass - 1u; float k = m_diminishing_k[classIdx]; float c = capArray[classIdx]; float result = c * diminishValue / (diminishValue + c * k); result += nonDiminishValue; return result; } float[] parry_cap = { 65.631440f, // Warrior 65.631440f, // Paladin 145.560408f, // Hunter 145.560408f, // Rogue 0.0f, // Priest 65.631440f, // DK 145.560408f, // Shaman 0.0f, // Mage 0.0f, // Warlock 90.6425f, // Monk 0.0f, // Druid 65.631440f // Demon Hunter }; public void UpdateParryPercentage() { // No parry float value = 0.0f; int pclass = (int)GetClass() - 1; if (CanParry() && parry_cap[pclass] > 0.0f) { float nondiminishing = 5.0f; // Parry from rating float diminishing = GetRatingBonusValue(CombatRating.Parry); // Parry from SPELL_AURA_MOD_PARRY_PERCENT aura nondiminishing += GetTotalAuraModifier(AuraType.ModParryPercent); // apply diminishing formula to diminishing parry chance value = CalculateDiminishingReturns(parry_cap, GetClass(), nondiminishing, diminishing); if (WorldConfig.GetBoolValue(WorldCfg.StatsLimitsEnable)) value = value > WorldConfig.GetFloatValue(WorldCfg.StatsLimitsParry) ? WorldConfig.GetFloatValue(WorldCfg.StatsLimitsParry) : value; } SetUpdateFieldStatValue(m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.ParryPercentage), value); } float[] dodge_cap = { 65.631440f, // Warrior 65.631440f, // Paladin 145.560408f, // Hunter 145.560408f, // Rogue 150.375940f, // Priest 65.631440f, // DK 145.560408f, // Shaman 150.375940f, // Mage 150.375940f, // Warlock 145.560408f, // Monk 116.890707f, // Druid 145.560408f // Demon Hunter }; public void UpdateDodgePercentage() { float diminishing = 0.0f, nondiminishing = 0.0f; GetDodgeFromAgility(diminishing, nondiminishing); // Dodge from SPELL_AURA_MOD_DODGE_PERCENT aura nondiminishing += GetTotalAuraModifier(AuraType.ModDodgePercent); // Dodge from rating diminishing += GetRatingBonusValue(CombatRating.Dodge); // apply diminishing formula to diminishing dodge chance float value = CalculateDiminishingReturns(dodge_cap, GetClass(), nondiminishing, diminishing); if (WorldConfig.GetBoolValue(WorldCfg.StatsLimitsEnable)) value = value > WorldConfig.GetFloatValue(WorldCfg.StatsLimitsDodge) ? WorldConfig.GetFloatValue(WorldCfg.StatsLimitsDodge) : value; SetUpdateFieldStatValue(m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.DodgePercentage), value); } public void UpdateBlockPercentage() { // No block float value = 0.0f; if (CanBlock()) { // Base value value = 5.0f; // Increase from SPELL_AURA_MOD_BLOCK_PERCENT aura value += GetTotalAuraModifier(AuraType.ModBlockPercent); // Increase from rating value += GetRatingBonusValue(CombatRating.Block); if (WorldConfig.GetBoolValue(WorldCfg.StatsLimitsEnable)) value = value > WorldConfig.GetFloatValue(WorldCfg.StatsLimitsBlock) ? WorldConfig.GetFloatValue(WorldCfg.StatsLimitsBlock) : value; } SetUpdateFieldStatValue(m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.BlockPercentage), value); } public void UpdateCritPercentage(WeaponAttackType attType) { static float applyCritLimit(float value) { if (WorldConfig.GetBoolValue(WorldCfg.StatsLimitsEnable)) value = value > WorldConfig.GetFloatValue(WorldCfg.StatsLimitsCrit) ? WorldConfig.GetFloatValue(WorldCfg.StatsLimitsCrit) : value; return value; } switch (attType) { case WeaponAttackType.OffAttack: SetUpdateFieldStatValue(m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.OffhandCritPercentage), applyCritLimit(GetBaseModValue(BaseModGroup.OffhandCritPercentage, BaseModType.FlatMod) + GetBaseModValue(BaseModGroup.OffhandCritPercentage, BaseModType.PctMod) + GetRatingBonusValue(CombatRating.CritMelee))); break; case WeaponAttackType.RangedAttack: SetUpdateFieldStatValue(m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.RangedCritPercentage), applyCritLimit(GetBaseModValue(BaseModGroup.RangedCritPercentage, BaseModType.FlatMod) + GetBaseModValue(BaseModGroup.RangedCritPercentage, BaseModType.PctMod) + GetRatingBonusValue(CombatRating.CritRanged))); break; case WeaponAttackType.BaseAttack: default: SetUpdateFieldStatValue(m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.CritPercentage), applyCritLimit(GetBaseModValue(BaseModGroup.CritPercentage, BaseModType.FlatMod) + GetBaseModValue(BaseModGroup.CritPercentage, BaseModType.PctMod) + GetRatingBonusValue(CombatRating.CritMelee))); break; } } public void UpdateExpertise(WeaponAttackType attack) { if (attack == WeaponAttackType.RangedAttack) return; int expertise = (int)GetRatingBonusValue(CombatRating.Expertise); Item weapon = GetWeaponForAttack(attack, true); expertise += GetTotalAuraModifier(AuraType.ModExpertise, aurEff => aurEff.GetSpellInfo().IsItemFitToSpellRequirements(weapon)); if (expertise < 0) expertise = 0; switch (attack) { case WeaponAttackType.BaseAttack: SetUpdateFieldValue(m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.MainhandExpertise), expertise); break; case WeaponAttackType.OffAttack: SetUpdateFieldValue(m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.OffhandExpertise), expertise); break; default: break; } } float GetGameTableColumnForCombatRating(GtCombatRatingsRecord row, CombatRating rating) { switch (rating) { case CombatRating.Amplify: return row.Amplify; case CombatRating.DefenseSkill: return row.DefenseSkill; case CombatRating.Dodge: return row.Dodge; case CombatRating.Parry: return row.Parry; case CombatRating.Block: return row.Block; case CombatRating.HitMelee: return row.HitMelee; case CombatRating.HitRanged: return row.HitRanged; case CombatRating.HitSpell: return row.HitSpell; case CombatRating.CritMelee: return row.CritMelee; case CombatRating.CritRanged: return row.CritRanged; case CombatRating.CritSpell: return row.CritSpell; case CombatRating.Corruption: return row.Corruption; case CombatRating.CorruptionResistance: return row.CorruptionResistance; case CombatRating.Speed: return row.Speed; case CombatRating.ResilienceCritTaken: return row.ResilienceCritTaken; case CombatRating.ResiliencePlayerDamage: return row.ResiliencePlayerDamage; case CombatRating.Lifesteal: return row.Lifesteal; case CombatRating.HasteMelee: return row.HasteMelee; case CombatRating.HasteRanged: return row.HasteRanged; case CombatRating.HasteSpell: return row.HasteSpell; case CombatRating.Avoidance: return row.Avoidance; case CombatRating.Studiness: return row.Sturdiness; case CombatRating.Unused7: return row.Unused7; case CombatRating.Expertise: return row.Expertise; case CombatRating.ArmorPenetration: return row.ArmorPenetration; case CombatRating.Mastery: return row.Mastery; case CombatRating.PvpPower: return row.PvPPower; case CombatRating.Cleave: return row.Cleave; case CombatRating.VersatilityDamageDone: return row.VersatilityDamageDone; case CombatRating.VersatilityHealingDone: return row.VersatilityHealingDone; case CombatRating.VersatilityDamageTaken: return row.VersatilityDamageTaken; case CombatRating.Unused12: return row.Unused12; default: break; } return 1.0f; } public void UpdateSpellCritChance() { // For others recalculate it from: float crit = 5.0f; // Increase crit from SPELL_AURA_MOD_SPELL_CRIT_CHANCE crit += GetTotalAuraModifier(AuraType.ModSpellCritChance); // Increase crit from SPELL_AURA_MOD_CRIT_PCT crit += GetTotalAuraModifier(AuraType.ModCritPct); // Increase crit from spell crit ratings crit += GetRatingBonusValue(CombatRating.CritSpell); // Store crit value SetUpdateFieldValue(m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.SpellCritPercentage), crit); } public void UpdateMeleeHitChances() { ModMeleeHitChance = 7.5f + GetRatingBonusValue(CombatRating.HitMelee); } public void UpdateRangedHitChances() { ModRangedHitChance = 7.5f + GetRatingBonusValue(CombatRating.HitRanged); } public void UpdateSpellHitChances() { ModSpellHitChance = 15.0f + GetTotalAuraModifier(AuraType.ModSpellHitChance); ModSpellHitChance += GetRatingBonusValue(CombatRating.HitSpell); } Stats GetPrimaryStat() { byte primaryStatPriority; var specialization = CliDB.ChrSpecializationStorage.LookupByKey(GetPrimarySpecialization()); if (specialization != null) primaryStatPriority = (byte)specialization.PrimaryStatPriority; else primaryStatPriority = CliDB.ChrClassesStorage.LookupByKey(GetClass()).PrimaryStatPriority; if (primaryStatPriority >= 4) return Stats.Strength; if (primaryStatPriority >= 2) return Stats.Agility; return Stats.Intellect; } public override void UpdateMaxHealth() { UnitMods unitMod = UnitMods.Health; float value = GetFlatModifierValue(unitMod, UnitModifierFlatType.Base) + GetCreateHealth(); value *= GetPctModifierValue(unitMod, UnitModifierPctType.Base); value += GetFlatModifierValue(unitMod, UnitModifierFlatType.Total) + GetHealthBonusFromStamina(); value *= GetPctModifierValue(unitMod, UnitModifierPctType.Total); SetMaxHealth((uint)value); } float GetHealthBonusFromStamina() { // Taken from PaperDollFrame.lua - 6.0.3.19085 float ratio = 10.0f; GtHpPerStaRecord hpBase = CliDB.HpPerStaGameTable.GetRow(GetLevel()); if (hpBase != null) ratio = hpBase.Health; float stamina = GetStat(Stats.Stamina); return stamina * ratio; } public override uint GetPowerIndex(PowerType powerType) { return Global.DB2Mgr.GetPowerIndexByClass(powerType, GetClass()); } public override void UpdateMaxPower(PowerType power) { uint powerIndex = GetPowerIndex(power); if (powerIndex == (uint)PowerType.Max || powerIndex >= (uint)PowerType.MaxPerClass) return; UnitMods unitMod = UnitMods.PowerStart + (int)power; float value = GetFlatModifierValue(unitMod, UnitModifierFlatType.Base) + GetCreatePowerValue(power); value *= GetPctModifierValue(unitMod, UnitModifierPctType.Base); value += GetFlatModifierValue(unitMod, UnitModifierFlatType.Total); value *= GetPctModifierValue(unitMod, UnitModifierPctType.Total); SetMaxPower(power, (int)Math.Round(value)); } public void ApplySpellPenetrationBonus(int amount, bool apply) { ApplyModTargetResistance(-amount, apply); m_spellPenetrationItemMod += apply ? amount : -amount; } void ApplyManaRegenBonus(int amount, bool apply) { _ModifyUInt32(apply, ref m_baseManaRegen, ref amount); UpdateManaRegen(); } void ApplyHealthRegenBonus(int amount, bool apply) { _ModifyUInt32(apply, ref m_baseHealthRegen, ref amount); } void ApplySpellPowerBonus(int amount, bool apply) { if (HasAuraType(AuraType.OverrideSpellPowerByApPct)) return; apply = _ModifyUInt32(apply, ref m_baseSpellPower, ref amount); // For speed just update for client ApplyModUpdateFieldValue(m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.ModHealingDonePos), amount, apply); for (SpellSchools spellSchool = SpellSchools.Holy; spellSchool < SpellSchools.Max; ++spellSchool) ApplyModDamageDonePos(spellSchool, amount, apply); if (HasAuraType(AuraType.OverrideAttackPowerBySpPct)) { UpdateAttackPowerAndDamage(); UpdateAttackPowerAndDamage(true); } } public bool _ModifyUInt32(bool apply, ref uint baseValue, ref int amount) { // If amount is negative, change sign and value of apply. if (amount < 0) { apply = !apply; amount = -amount; } if (apply) baseValue += (uint)amount; else { // Make sure we do not get public uint overflow. if (amount > baseValue) amount = (int)baseValue; baseValue -= (uint)amount; } return apply; } } public partial class Creature { public override bool UpdateStats(Stats stat) { return true; } public override bool UpdateAllStats() { UpdateMaxHealth(); UpdateAttackPowerAndDamage(); UpdateAttackPowerAndDamage(true); for (var i = PowerType.Mana; i < PowerType.Max; ++i) UpdateMaxPower(i); UpdateAllResistances(); return true; } public override void UpdateArmor() { float baseValue = GetFlatModifierValue(UnitMods.Armor, UnitModifierFlatType.Base); float value = GetTotalAuraModValue(UnitMods.Armor); SetArmor((int)baseValue, (int)(value - baseValue)); } public override void UpdateMaxHealth() { float value = GetTotalAuraModValue(UnitMods.Health); SetMaxHealth((uint)value); } public override uint GetPowerIndex(PowerType powerType) { if (powerType == GetPowerType()) return 0; if (powerType == PowerType.AlternatePower) return 1; if (powerType == PowerType.ComboPoints) return 2; return (uint)PowerType.Max; } public override void UpdateMaxPower(PowerType power) { if (GetPowerIndex(power) == (uint)PowerType.Max) return; UnitMods unitMod = UnitMods.PowerStart + (int)power; float value = GetFlatModifierValue(unitMod, UnitModifierFlatType.Base) + GetCreatePowerValue(power); value *= GetPctModifierValue(unitMod, UnitModifierPctType.Base); value += GetFlatModifierValue(unitMod, UnitModifierFlatType.Total); value *= GetPctModifierValue(unitMod, UnitModifierPctType.Total); SetMaxPower(power, (int)Math.Round(value)); } public override void UpdateAttackPowerAndDamage(bool ranged = false) { UnitMods unitMod = ranged ? UnitMods.AttackPowerRanged : UnitMods.AttackPower; float baseAttackPower = GetFlatModifierValue(unitMod, UnitModifierFlatType.Base) * GetPctModifierValue(unitMod, UnitModifierPctType.Base); float attackPowerMultiplier = GetPctModifierValue(unitMod, UnitModifierPctType.Total) - 1.0f; if (ranged) { SetRangedAttackPower((int)baseAttackPower); SetRangedAttackPowerMultiplier(attackPowerMultiplier); } else { SetAttackPower((int)baseAttackPower); SetAttackPowerMultiplier(attackPowerMultiplier); } //automatically update weapon damage after attack power modification if (ranged) UpdateDamagePhysical(WeaponAttackType.RangedAttack); else { UpdateDamagePhysical(WeaponAttackType.BaseAttack); UpdateDamagePhysical(WeaponAttackType.OffAttack); } } public override void CalculateMinMaxDamage(WeaponAttackType attType, bool normalized, bool addTotalPct, out float minDamage, out float maxDamage) { float variance; UnitMods unitMod; switch (attType) { case WeaponAttackType.BaseAttack: default: variance = GetCreatureTemplate().BaseVariance; unitMod = UnitMods.DamageMainHand; break; case WeaponAttackType.OffAttack: variance = GetCreatureTemplate().BaseVariance; unitMod = UnitMods.DamageOffHand; break; case WeaponAttackType.RangedAttack: variance = GetCreatureTemplate().RangeVariance; unitMod = UnitMods.DamageRanged; break; } if (attType == WeaponAttackType.OffAttack && !HaveOffhandWeapon()) { minDamage = 0.0f; maxDamage = 0.0f; return; } float weaponMinDamage = GetWeaponDamageRange(attType, WeaponDamageRange.MinDamage); float weaponMaxDamage = GetWeaponDamageRange(attType, WeaponDamageRange.MaxDamage); if (!CanUseAttackType(attType)) // disarm case { weaponMinDamage = 0.0f; weaponMaxDamage = 0.0f; } float attackPower = GetTotalAttackPowerValue(attType, false); float attackSpeedMulti = Math.Max(GetAPMultiplier(attType, normalized), 0.25f); float baseValue = GetFlatModifierValue(unitMod, UnitModifierFlatType.Base) + (attackPower / 3.5f) * variance; float basePct = GetPctModifierValue(unitMod, UnitModifierPctType.Base) * attackSpeedMulti; float totalValue = GetFlatModifierValue(unitMod, UnitModifierFlatType.Total); float totalPct = addTotalPct ? GetPctModifierValue(unitMod, UnitModifierPctType.Total) : 1.0f; float dmgMultiplier = GetCreatureTemplate().ModDamage; // = ModDamage * _GetDamageMod(rank); minDamage = ((weaponMinDamage + baseValue) * dmgMultiplier * basePct + totalValue) * totalPct; maxDamage = ((weaponMaxDamage + baseValue) * dmgMultiplier * basePct + totalValue) * totalPct; } } }