/* * Copyright (C) 2012-2017 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.Network.Packets; using Game.Spells; using System; using System.Collections.Generic; using System.Linq; namespace Game.Entities { public partial class Unit { public bool HandleStatModifier(UnitMods unitMod, UnitModifierType modifierType, int amount, bool apply) { return HandleStatModifier(unitMod, modifierType, (float)amount, apply); } public bool HandleStatModifier(UnitMods unitMod, UnitModifierType modifierType, float amount, bool apply) { if (unitMod >= UnitMods.End || modifierType >= UnitModifierType.End) { Log.outError(LogFilter.Unit, "ERROR in HandleStatModifier(): non-existing UnitMods or wrong UnitModifierType!"); return false; } switch (modifierType) { case UnitModifierType.BaseValue: case UnitModifierType.BasePCTExcludeCreate: case UnitModifierType.TotalValue: m_auraModifiersGroup[(int)unitMod][(int)modifierType] += apply ? amount : -amount; break; case UnitModifierType.BasePCT: case UnitModifierType.TotalPCT: MathFunctions.ApplyPercentModFloatVar(ref m_auraModifiersGroup[(int)unitMod][(int)modifierType], amount, apply); break; default: break; } if (!CanModifyStats()) return false; 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.Rune: case UnitMods.RunicPower: UpdateMaxPower(GetPowerTypeByAuraGroup(unitMod)); 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; } return true; } int GetMinPower(PowerType power) { return power == PowerType.LunarPower ? -100 : 0; } // returns negative amount on power reduction public int ModifyPowerPct(PowerType power, float pct, bool apply) { float amount = GetMaxPower(power); MathFunctions.ApplyPercentModFloatVar(ref amount, pct, apply); return ModifyPower(power, (int)amount - GetMaxPower(power)); } // returns negative amount on power reduction public int ModifyPower(PowerType power, int dVal) { int gain = 0; if (dVal == 0) return 0; int curPower = GetPower(power); int val = (dVal + curPower); if (val <= GetMinPower(power)) { SetPower(power, GetMinPower(power)); return -curPower; } int maxPower = GetMaxPower(power); if (val < maxPower) { SetPower(power, val); gain = val - curPower; } else if (curPower != maxPower) { SetPower(power, maxPower); 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; } PowerType GetPowerTypeByAuraGroup(UnitMods unitMod) { switch (unitMod) { case UnitMods.Rage: return PowerType.Rage; case UnitMods.Focus: return PowerType.Focus; case UnitMods.Energy: return PowerType.Energy; case UnitMods.Rune: return PowerType.Runes; case UnitMods.RunicPower: return PowerType.RunicPower; default: case UnitMods.Mana: return PowerType.Mana; } } public void ApplyStatBuffMod(Stats stat, float val, bool apply) { ApplyModSignedFloatValue((val > 0 ? UnitFields.PosStat + (int)stat : UnitFields.NegStat + (int)stat), val, apply); } public void ApplyStatPercentBuffMod(Stats stat, float val, bool apply) { ApplyPercentModFloatValue(UnitFields.PosStat + (int)stat, val, apply); ApplyPercentModFloatValue(UnitFields.NegStat + (int)stat, val, apply); } public virtual bool UpdateStats(Stats stat) { return false; } public virtual bool UpdateAllStats() { return false; } public virtual void UpdateResistances(SpellSchools school) { } 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) { float minDamage = 0.0f; float maxDamage = 0.0f; CalculateMinMaxDamage(attType, false, true, out minDamage, out maxDamage); switch (attType) { case WeaponAttackType.BaseAttack: default: SetStatFloatValue(UnitFields.MinDamage, minDamage); SetStatFloatValue(UnitFields.MaxDamage, maxDamage); break; case WeaponAttackType.OffAttack: SetStatFloatValue(UnitFields.MinOffHandDamage, minDamage); SetStatFloatValue(UnitFields.MaxOffHandDamage, maxDamage); break; case WeaponAttackType.RangedAttack: SetStatFloatValue(UnitFields.MinRangedDamage, minDamage); SetStatFloatValue(UnitFields.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 GetFloatValue(UnitFields.Stat + (int)stat); } public void SetCreateStat(Stats stat, float val) { CreateStats[(int)stat] = val; } public void SetStat(Stats stat, int val) { SetStatInt32Value(UnitFields.Stat + (int)stat, val); } public void SetCreateMana(uint val) { SetUInt32Value(UnitFields.BaseMana, val); } public uint GetCreateMana() { return GetUInt32Value(UnitFields.BaseMana); } public uint GetArmor() { return GetResistance(SpellSchools.Normal); } public void SetArmor(int val) { SetResistance(SpellSchools.Normal, val); } public uint GetResistance(SpellSchools school) { return GetUInt32Value(UnitFields.Resistances + (int)school); } public uint GetResistance(SpellSchoolMask mask) { int resist = -1; for (int i = (int)SpellSchools.Normal; i < (int)SpellSchools.Max; ++i) if (Convert.ToBoolean((int)mask & (1 << i)) && (resist < 0 || resist > GetResistance((SpellSchools)i))) resist = (int)GetResistance((SpellSchools)i); // resist value will never be negative here return (uint)resist; } public void SetResistance(SpellSchools school, int val) { SetStatInt32Value(UnitFields.Resistances + (int)school, val); } public float GetCreateStat(Stats stat) { return CreateStats[(int)stat]; } public void InitStatBuffMods() { for (var i = Stats.Strength; i < Stats.Max; ++i) { SetFloatValue(UnitFields.PosStat + (int)i, 0); SetFloatValue(UnitFields.NegStat + (int)i, 0); } } public float GetResistanceBuffMods(SpellSchools school, bool positive) { return GetFloatValue((positive ? UnitFields.ResistanceBuffModsPositive : UnitFields.ResistanceBuffModsNegative) + (int)school); } public void SetResistanceBuffMods(SpellSchools school, bool positive, float val) { SetFloatValue((positive ? UnitFields.ResistanceBuffModsPositive : UnitFields.ResistanceBuffModsNegative) + (int)school, val); } public void ApplyResistanceBuffModsMod(SpellSchools school, bool positive, float val, bool apply) { ApplyModSignedFloatValue((positive ? UnitFields.ResistanceBuffModsPositive : UnitFields.ResistanceBuffModsNegative) + (int)school, val, apply); } public void ApplyResistanceBuffModsPercentMod(SpellSchools school, bool positive, float val, bool apply) { ApplyPercentModFloatValue((positive ? UnitFields.ResistanceBuffModsPositive : UnitFields.ResistanceBuffModsNegative) + (int)school, val, apply); } public bool CanModifyStats() { return canModifyStats; } public void SetCanModifyStats(bool modifyStats) { canModifyStats = modifyStats; } public float GetTotalStatValue(Stats stat) { UnitMods unitMod = UnitMods.StatStart + (int)stat; if (m_auraModifiersGroup[(int)unitMod][(int)UnitModifierType.TotalPCT] <= 0.0f) return 0.0f; float value = MathFunctions.CalculatePct(m_auraModifiersGroup[(int)unitMod][(int)UnitModifierType.BaseValue], Math.Max(m_auraModifiersGroup[(int)unitMod][(int)UnitModifierType.BasePCTExcludeCreate], -100.0f)); value += GetCreateStat(stat); value *= m_auraModifiersGroup[(int)unitMod][(int)UnitModifierType.BasePCT]; value += m_auraModifiersGroup[(int)unitMod][(int)UnitModifierType.TotalValue]; value *= m_auraModifiersGroup[(int)unitMod][(int)UnitModifierType.TotalPCT]; return value; } //Health public uint GetCreateHealth() { return GetUInt32Value(UnitFields.BaseHealth); } public ulong GetHealth() { return GetUInt64Value(UnitFields.Health); } public ulong GetMaxHealth() { return GetUInt64Value(UnitFields.MaxHealth); } public float GetHealthPct() { return GetMaxHealth() != 0 ? 100.0f * GetHealth() / GetMaxHealth() : 0.0f; } public void SetCreateHealth(uint val) { SetUInt32Value(UnitFields.BaseHealth, val); } public void SetHealth(ulong val) { if (getDeathState() == DeathState.JustDied) val = 0; else if (IsTypeId(TypeId.Player) && getDeathState() == DeathState.Dead) val = 1; else { ulong maxHealth = GetMaxHealth(); if (maxHealth < val) val = maxHealth; } SetUInt64Value(UnitFields.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 void SetMaxHealth(ulong val) { if (val == 0) val = 1; ulong health = GetHealth(); SetUInt64Value(UnitFields.MaxHealth, val); // 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 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); } bool HealthAbovePctHealed(int pct, uint heal) { return GetHealth() + heal > CountPctFromMaxHealth(pct); } public ulong CountPctFromMaxHealth(int pct) { return MathFunctions.CalculatePct(GetMaxHealth(), pct); } 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)GetUInt32Value(UnitFields.DisplayPower); } public void setPowerType(PowerType newPowerType) { if (getPowerType() == newPowerType) return; SetUInt32Value(UnitFields.DisplayPower, (uint)newPowerType); if (IsTypeId(TypeId.Player)) { if (ToPlayer().GetGroup()) ToPlayer().SetGroupUpdateFlag(GroupUpdateFlags.PowerType); } /*else if (IsPet()) TODO 6.x { Pet pet = ToCreature().ToPet(); if (pet.isControlled()) pet.SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_POWER_TYPE); }*/ float powerMultiplier = 1.0f; if (!IsPet()) { Creature creature = ToCreature(); if (creature) powerMultiplier = creature.GetCreatureTemplate().ModMana; } switch (newPowerType) { default: case PowerType.Mana: break; case PowerType.Rage: SetMaxPower(PowerType.Rage, (int)Math.Ceiling(GetCreatePowers(PowerType.Rage) * powerMultiplier)); SetPower(PowerType.Rage, 0); break; case PowerType.Focus: SetMaxPower(PowerType.Focus, (int)Math.Ceiling(GetCreatePowers(PowerType.Focus) * powerMultiplier)); SetPower(PowerType.Focus, (int)Math.Ceiling(GetCreatePowers(PowerType.Focus) * powerMultiplier)); break; case PowerType.Energy: SetMaxPower(PowerType.Energy, (int)Math.Ceiling(GetCreatePowers(PowerType.Energy) * powerMultiplier)); break; } } public void SetMaxPower(PowerType power, int val) { uint powerIndex = GetPowerIndex(power); if (powerIndex == (int)PowerType.Max || powerIndex >= (int)PowerType.MaxPerClass) return; int cur_power = GetPower(power); SetInt32Value(UnitFields.MaxPower + (int)powerIndex, 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(power, val); } public void SetPower(PowerType powerType, int val) { uint powerIndex = GetPowerIndex(powerType); if (powerIndex == (int)PowerType.Max || powerIndex >= (int)PowerType.MaxPerClass) return; int maxPower = GetMaxPower(powerType); if (maxPower < val) val = maxPower; SetInt32Value(UnitFields.Power + (int)powerIndex, val); if (IsInWorld) { PowerUpdate packet = new PowerUpdate(); packet.Guid = GetGUID(); packet.Powers.Add(new PowerUpdatePower(val, (byte)powerType)); SendMessageToSet(packet, IsTypeId(TypeId.Player)); } // 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 int GetPower(PowerType power) { uint powerIndex = GetPowerIndex(power); if (powerIndex == (int)PowerType.Max || powerIndex >= (int)PowerType.MaxPerClass) return 0; return GetInt32Value(UnitFields.Power + (int)powerIndex); } public int GetMaxPower(PowerType power) { uint powerIndex = GetPowerIndex(power); if (powerIndex == (int)PowerType.Max || powerIndex >= (int)PowerType.MaxPerClass) return 0; return GetInt32Value(UnitFields.MaxPower + (int)powerIndex); } public int GetCreatePowers(PowerType power) { if (power == PowerType.Mana) return (int)GetCreateMana(); PowerTypeRecord powerType = Global.DB2Mgr.GetPowerTypeEntry(power); if (powerType != null) return powerType.MaxPower; return 0; } public uint GetPowerIndex(PowerType powerType) { // This is here because hunter pets are of the warrior class. // With the current implementation, the core only gives them // POWER_RAGE, so we enforce the class to hunter so that they // effectively get focus power. Class _class = GetClass(); if (IsPet() && ToPet().getPetType() == PetType.Hunter) _class = Class.Hunter; return Global.DB2Mgr.GetPowerIndexByClass(powerType, _class); } public void ApplyResilience(Unit victim, ref uint damage) { // player mounted on multi-passenger mount is also classified as vehicle if (IsVehicle() || (victim.IsVehicle() && !victim.IsTypeId(TypeId.Player))) return; // Don't consider resilience if not in PvP - player or pet if (!GetCharmerOrOwnerPlayerOrPlayerItself()) return; Unit target = null; if (victim.IsTypeId(TypeId.Player)) target = victim; else if (victim.IsTypeId(TypeId.Unit) && victim.GetOwner() && victim.GetOwner().IsTypeId(TypeId.Player)) target = victim.GetOwner(); if (!target) return; damage -= target.GetDamageReduction(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); } //Chances float MeleeSpellMissChance(Unit victim, WeaponAttackType attType, uint spellId) { //calculate miss chance float missChance = victim.GetUnitMissChance(attType); if (spellId == 0 && haveOffhandWeapon() && !IsInFeralForm()) missChance += 19; // Calculate hit chance float hitChance = 100.0f; // Spellmod from SPELLMOD_RESIST_MISS_CHANCE if (spellId != 0) { Player modOwner = GetSpellModOwner(); if (modOwner != null) modOwner.ApplySpellMod(spellId, SpellModOp.ResistMissChance, ref hitChance); } missChance += hitChance - 100.0f; if (attType == WeaponAttackType.RangedAttack) missChance -= m_modRangedHitChance; else missChance -= m_modMeleeHitChance; // Limit miss chance from 0 to 77% if (missChance < 0.0f) return 0.0f; if (missChance > 77.0f) return 77.0f; return missChance; } float GetUnitCriticalChance(WeaponAttackType attackType, Unit victim) { float chance = 0.0f; if (IsTypeId(TypeId.Player)) { switch (attackType) { case WeaponAttackType.BaseAttack: chance = GetFloatValue(PlayerFields.CritPercentage); break; case WeaponAttackType.OffAttack: chance = GetFloatValue(PlayerFields.OffhandCritPercentage); break; case WeaponAttackType.RangedAttack: chance = GetFloatValue(PlayerFields.RangedCritPercentage); break; } } else { if (!ToCreature().GetCreatureTemplate().FlagsExtra.HasAnyFlag(CreatureFlagsExtra.NoCrit)) { chance = 5.0f; chance += GetTotalAuraModifier(AuraType.ModWeaponCritPercent); chance += GetTotalAuraModifier(AuraType.ModCritPct); } } // flat aura mods if (attackType == WeaponAttackType.RangedAttack) chance += victim.GetTotalAuraModifier(AuraType.ModAttackerRangedCritChance); else chance += victim.GetTotalAuraModifier(AuraType.ModAttackerMeleeCritChance); var critChanceForCaster = victim.GetAuraEffectsByType(AuraType.ModCritChanceForCaster); foreach (AuraEffect aurEff in critChanceForCaster) { if (aurEff.GetCasterGUID() != GetGUID()) continue; chance += aurEff.GetAmount(); } chance += victim.GetTotalAuraModifier(AuraType.ModAttackerSpellAndWeaponCritChance); return Math.Max(chance, 0.0f); } float GetUnitDodgeChance(WeaponAttackType attType, Unit victim) { int levelDiff = (int)(victim.GetLevelForTarget(this) - GetLevelForTarget(victim)); float chance = 0.0f; float levelBonus = 0.0f; if (victim.IsTypeId(TypeId.Player)) chance = victim.GetFloatValue(PlayerFields.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.GetFloatValue(PlayerFields.ParryPercentage); } } else if (victim.IsTypeId(TypeId.Unit) && !(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(WeaponAttackType attType) { float miss_chance = 5.00f; if (attType == WeaponAttackType.RangedAttack) miss_chance -= GetTotalAuraModifier(AuraType.ModAttackerRangedHitChance); else miss_chance -= GetTotalAuraModifier(AuraType.ModAttackerMeleeHitChance); 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.GetFloatValue(PlayerFields.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); } int GetMechanicResistChance(SpellInfo spellInfo) { if (spellInfo == null) return 0; int resistMech = 0; foreach (SpellEffectInfo effect in spellInfo.GetEffectsForDifficulty(GetMap().GetDifficultyID())) { if (effect == null || !effect.IsEffect()) break; int effect_mech = (int)spellInfo.GetEffectMechanic(effect.EffectIndex, GetMap().GetDifficultyID()); if (effect_mech != 0) { int temp = GetTotalAuraModifierByMiscValue(AuraType.ModMechanicResistance, effect_mech); if (resistMech < temp) resistMech = temp; } } return Math.Max(resistMech, 0); } } 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: UpdateArmor(); UpdateAllCritPercentages(); UpdateDodgePercentage(); break; case Stats.Stamina: UpdateMaxHealth(); break; case Stats.Intellect: UpdateSpellCritChance(); UpdateArmor(); //SPELL_AURA_MOD_RESISTANCE_OF_INTELLECT_PERCENT, only armor currently break; default: break; } if (stat == Stats.Strength) UpdateAttackPowerAndDamage(false); else if (stat == Stats.Agility) { UpdateAttackPowerAndDamage(false); UpdateAttackPowerAndDamage(true); } UpdateSpellDamageAndHealingBonus(); UpdateManaRegen(); // Update ratings in exist SPELL_AURA_MOD_RATING_FROM_STAT and only depends from stat uint mask = 0; var modRatingFromStat = GetAuraEffectsByType(AuraType.ModRatingFromStat); foreach (var eff in modRatingFromStat) if ((Stats)eff.GetMiscValueB() == stat) mask |= (uint)eff.GetMiscValue(); if (mask != 0) { for (int rating = 0; rating < (int)CombatRating.Max; ++rating) if (Convert.ToBoolean(mask & (1 << rating))) ApplyRatingMod((CombatRating)rating, 0, true); } return true; } public override void UpdateResistances(SpellSchools school) { if (school > SpellSchools.Normal) { float value = GetTotalAuraModValue(UnitMods.ResistanceStart + (int)school); SetResistance(school, (int)value); Pet pet = GetPet(); if (pet != null) pet.UpdateResistances(school); } else UpdateArmor(); } void RecalculateRating(CombatRating cr) { ApplyRatingMod(cr, 0, true); } 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 = GetModifierValue(unitMod, UnitModifierType.BaseValue) + GetTotalAttackPowerValue(attType) / 3.5f * attackPowerMod; float basePct = GetModifierValue(unitMod, UnitModifierType.BasePCT); float totalValue = GetModifierValue(unitMod, UnitModifierType.TotalValue); float totalPct = addTotalPct ? GetModifierValue(unitMod, UnitModifierType.TotalPCT) : 1.0f; float weaponMinDamage = GetWeaponDamageRange(attType, WeaponDamageRange.MinDamage); float weaponMaxDamage = GetWeaponDamageRange(attType, WeaponDamageRange.MaxDamage); 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; max_damage = ((baseValue + weaponMaxDamage) * basePct + totalValue) * totalPct; } void UpdateAllCritPercentages() { float value = 5.0f; SetBaseModValue(BaseModGroup.CritPercentage, BaseModType.PCTmod, value); SetBaseModValue(BaseModGroup.OffhandCritPercentage, BaseModType.PCTmod, value); SetBaseModValue(BaseModGroup.RangedCritPercentage, BaseModType.PCTmod, value); UpdateCritPercentage(WeaponAttackType.BaseAttack); UpdateCritPercentage(WeaponAttackType.OffAttack); UpdateCritPercentage(WeaponAttackType.RangedAttack); } public void UpdateManaRegen() { int manaIndex = (int)GetPowerIndex(PowerType.Mana); if (manaIndex == (int)PowerType.Max) return; // Mana regen from spirit float spirit_regen = 0.0f; // Apply PCT bonus from SPELL_AURA_MOD_POWER_REGEN_PERCENT aura on spirit base regen spirit_regen *= GetTotalAuraMultiplierByMiscValue(AuraType.ModPowerRegenPercent, (int)PowerType.Mana); // CombatRegen = 5% of Base Mana float base_regen = GetCreateMana() * 0.02f + GetTotalAuraModifierByMiscValue(AuraType.ModPowerRegen, (int)PowerType.Mana) / 5.0f; // Set regen rate in cast state apply only on spirit based regen int modManaRegenInterrupt = GetTotalAuraModifier(AuraType.ModManaRegenInterrupt); SetFloatValue(UnitFields.PowerRegenInterruptedFlatModifier + manaIndex, base_regen + MathFunctions.CalculatePct(spirit_regen, modManaRegenInterrupt)); SetFloatValue(UnitFields.PowerRegenFlatModifier + manaIndex, 0.001f + spirit_regen + 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 SetStatInt32Value(PlayerFields.ModHealingDonePos, (int)SpellBaseHealingBonusDone(SpellSchoolMask.All)); // Get damage bonus for all schools var modDamageAuras = GetAuraEffectsByType(AuraType.ModDamageDone); for (var i = SpellSchools.Holy; i < SpellSchools.Max; ++i) { SetInt32Value(PlayerFields.ModDamageDoneNeg + (int)i, modDamageAuras.Aggregate(0, (negativeMod, aurEff) => { if (aurEff.GetAmount() < 0 && Convert.ToBoolean(aurEff.GetMiscValue() & (1 << (int)i))) negativeMod += aurEff.GetAmount(); return negativeMod; })); SetStatInt32Value(PlayerFields.ModDamageDonePos + (int)i, SpellBaseDamageBonusDone((SpellSchoolMask)(1 << (int)i)) - GetInt32Value(PlayerFields.ModDamageDoneNeg + (int)i)); } if (HasAuraType(AuraType.OverrideAttackPowerBySpPct)) { UpdateAttackPowerAndDamage(); UpdateAttackPowerAndDamage(true); } } public uint GetBaseSpellPowerBonus() { return m_baseSpellPower; } public override void UpdateAttackPowerAndDamage(bool ranged = false) { float val2 = 0.0f; float level = getLevel(); var entry = CliDB.ChrClassesStorage.LookupByKey(GetClass()); UnitMods unitMod = ranged ? UnitMods.AttackPowerRanged : UnitMods.AttackPower; UnitFields index = UnitFields.AttackPower; UnitFields index_mod = UnitFields.AttackPowerModPos; UnitFields index_mult = UnitFields.AttackPowerMultiplier; if (ranged) { index = UnitFields.RangedAttackPower; index_mod = UnitFields.RangedAttackPowerModPos; index_mult = UnitFields.RangedAttackPowerMultiplier; } 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 = GetInt32Value(PlayerFields.ModHealingDonePos); for (var i = SpellSchools.Holy; i < SpellSchools.Max; ++i) minSpellPower = Math.Min(minSpellPower, GetInt32Value(PlayerFields.ModDamageDonePos + (int)i)); val2 = MathFunctions.CalculatePct(minSpellPower, GetFloatValue(PlayerFields.OverrideApBySpellPowerPercent)); } SetModifierValue(unitMod, UnitModifierType.BaseValue, val2); float base_attPower = GetModifierValue(unitMod, UnitModifierType.BaseValue) * GetModifierValue(unitMod, UnitModifierType.BasePCT); float attPowerMod = GetModifierValue(unitMod, UnitModifierType.TotalValue); float attPowerMultiplier = GetModifierValue(unitMod, UnitModifierType.TotalPCT) - 1.0f; //add dynamic flat mods if (!ranged) { var mAPbyArmor = GetAuraEffectsByType(AuraType.ModAttackPowerOfArmor); foreach (var iter in mAPbyArmor) // always: ((*i).GetModifier().m_miscvalue == 1 == SPELL_SCHOOL_MASK_NORMAL) attPowerMod += GetArmor() / iter.GetAmount(); } SetUInt32Value(index, (uint)base_attPower); //UNIT_FIELD_(RANGED)_ATTACK_POWER field SetUInt32Value(index_mod, (uint)attPowerMod); //UNIT_FIELD_(RANGED)_ATTACK_POWER_MOD_POS field SetFloatValue(index_mult, attPowerMultiplier); //UNIT_FIELD_(RANGED)_ATTACK_POWER_MULTIPLIER field 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().GetFlags3().HasAnyFlag(ItemFlags3.AlwaysAllowDualWield)) UpdateDamagePhysical(WeaponAttackType.OffAttack); if (HasAuraType(AuraType.ModSpellDamageOfAttackPower) || HasAuraType(AuraType.ModSpellHealingOfAttackPower) || 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 = GetModifierValue(unitMod, UnitModifierType.BaseValue); // base armor (from items) value *= GetModifierValue(unitMod, UnitModifierType.BasePCT); // armor percent from items value += GetModifierValue(unitMod, UnitModifierType.TotalValue); //add dynamic flat mods var mResbyIntellect = GetAuraEffectsByType(AuraType.ModResistanceOfStatPercent); foreach (var i in mResbyIntellect) { if (Convert.ToBoolean(i.GetMiscValue() & (int)SpellSchoolMask.Normal)) value += MathFunctions.CalculatePct(GetStat((Stats)i.GetMiscValueB()), i.GetAmount()); } value *= GetModifierValue(unitMod, UnitModifierType.TotalPCT); SetArmor((int)value); 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(); SetCanModifyStats(true); UpdateAllStats(); } void _RemoveAllStatBonuses() { SetCanModifyStats(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]; // Apply bonus from SPELL_AURA_MOD_RATING_FROM_STAT // stat used stored in miscValueB for this aura var modRatingFromStat = GetAuraEffectsByType(AuraType.ModRatingFromStat); foreach (var i in modRatingFromStat) if (Convert.ToBoolean(i.GetMiscValue() & (1 << (int)cr))) amount += (int)MathFunctions.CalculatePct(GetStat((Stats)i.GetMiscValueB()), i.GetAmount()); var modRatingPct = GetAuraEffectsByType(AuraType.ModRatingPct); foreach (var i in modRatingPct) if (Convert.ToBoolean(i.GetMiscValue() & (1 << (int)cr))) amount += MathFunctions.CalculatePct(amount, i.GetAmount()); if (amount < 0) amount = 0; uint oldRating = GetUInt32Value(PlayerFields.CombatRating1 + (int)cr); SetUInt32Value(PlayerFields.CombatRating1 + (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.HasteMelee: case CombatRating.HasteRanged: case CombatRating.HasteSpell: { // explicit affected values float multiplier = GetRatingMultiplier(cr); float oldVal = oldRating * multiplier; float newVal = 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; } } public void UpdateMastery() { if (!CanUseMastery()) { SetFloatValue(PlayerFields.Mastery, 0.0f); return; } float value = GetTotalAuraModifier(AuraType.Mastery); value += GetRatingBonusValue(CombatRating.Mastery); SetFloatValue(PlayerFields.Mastery, value); ChrSpecializationRecord chrSpec = CliDB.ChrSpecializationStorage.LookupByKey(GetUInt32Value(PlayerFields.CurrentSpecId)); if (chrSpec == null) return; for (uint i = 0; i < PlayerConst.MaxMasterySpells; ++i) { Aura aura = GetAura(chrSpec.MasterySpellID[i]); if (aura != null) { foreach (SpellEffectInfo effect in aura.GetSpellEffectInfos()) { if (effect == null) continue; float mult = effect.BonusCoefficient; if (MathFunctions.fuzzyEq(mult, 0.0f)) continue; aura.GetEffect(effect.EffectIndex).ChangeAmount((int)(value * mult)); } } } } void UpdateArmorPenetration(int amount) { // Store Rating Value SetInt32Value(PlayerFields.CombatRating1 + (int)CombatRating.ArmorPenetration, amount); } public void UpdateParryPercentage() { 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 }; // 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 = nondiminishing + diminishing * parry_cap[pclass] / (diminishing + parry_cap[pclass] * m_diminishing_k[pclass]); if (WorldConfig.GetBoolValue(WorldCfg.StatsLimitsEnable)) value = value > WorldConfig.GetFloatValue(WorldCfg.StatsLimitsParry) ? WorldConfig.GetFloatValue(WorldCfg.StatsLimitsParry) : value; value = value < 0.0f ? 0.0f : value; } SetFloatValue(PlayerFields.ParryPercentage, value); } public void UpdateDodgePercentage() { 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 }; 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 int pclass = (int)GetClass() - 1; float value = nondiminishing + (diminishing * dodge_cap[pclass] / (diminishing + dodge_cap[pclass] * m_diminishing_k[pclass])); if (WorldConfig.GetBoolValue(WorldCfg.StatsLimitsEnable)) value = value > WorldConfig.GetFloatValue(WorldCfg.StatsLimitsDodge) ? WorldConfig.GetFloatValue(WorldCfg.StatsLimitsDodge) : value; value = value < 0.0f ? 0.0f : value; SetStatFloatValue(PlayerFields.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; value = value < 0.0f ? 0.0f : value; } SetFloatValue(PlayerFields.BlockPercentage, value); } public void UpdateCritPercentage(WeaponAttackType attType) { BaseModGroup modGroup; PlayerFields index; CombatRating cr; switch (attType) { case WeaponAttackType.OffAttack: modGroup = BaseModGroup.OffhandCritPercentage; index = PlayerFields.OffhandCritPercentage; cr = CombatRating.CritMelee; break; case WeaponAttackType.RangedAttack: modGroup = BaseModGroup.RangedCritPercentage; index = PlayerFields.RangedCritPercentage; cr = CombatRating.CritRanged; break; case WeaponAttackType.BaseAttack: default: modGroup = BaseModGroup.CritPercentage; index = PlayerFields.CritPercentage; cr = CombatRating.CritMelee; break; } float value = GetTotalPercentageModValue(modGroup) + GetRatingBonusValue(cr); // Modify crit from weapon skill and maximized defense skill of same level victim difference value += (GetMaxSkillValueForLevel() - GetMaxSkillValueForLevel()) * 0.04f; if (WorldConfig.GetBoolValue(WorldCfg.StatsLimitsEnable)) value = value > WorldConfig.GetFloatValue(WorldCfg.StatsLimitsCrit) ? WorldConfig.GetFloatValue(WorldCfg.StatsLimitsCrit) : value; value = value < 0.0f ? 0.0f : value; SetFloatValue(index, value); } public void UpdateExpertise(WeaponAttackType attack) { if (attack == WeaponAttackType.RangedAttack) return; int expertise = (int)GetRatingBonusValue(CombatRating.Expertise); Item weapon = GetWeaponForAttack(attack, true); var expAuras = GetAuraEffectsByType(AuraType.ModExpertise); foreach (var eff in expAuras) { // item neutral spell if ((int)eff.GetSpellInfo().EquippedItemClass == -1) expertise += eff.GetAmount(); // item dependent spell else if (weapon != null && weapon.IsFitToSpellRequirements(eff.GetSpellInfo())) expertise += eff.GetAmount(); } if (expertise < 0) expertise = 0; switch (attack) { case WeaponAttackType.BaseAttack: SetInt32Value(PlayerFields.Expertise, expertise); break; case WeaponAttackType.OffAttack: SetInt32Value(PlayerFields.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.Multistrike: return row.MultiStrike; case CombatRating.Readiness: return row.Readiness; 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 SetFloatValue(PlayerFields.SpellCritPercentage1, crit); } public void UpdateMeleeHitChances() { m_modMeleeHitChance = 7.5f + GetTotalAuraModifier(AuraType.ModHitChance); m_modMeleeHitChance += GetRatingBonusValue(CombatRating.HitMelee); } public void UpdateRangedHitChances() { m_modRangedHitChance = 7.5f + GetTotalAuraModifier(AuraType.ModHitChance); m_modRangedHitChance += GetRatingBonusValue(CombatRating.HitRanged); } public void UpdateSpellHitChances() { m_modSpellHitChance = 15.0f + GetTotalAuraModifier(AuraType.ModSpellHitChance); m_modSpellHitChance += GetRatingBonusValue(CombatRating.HitSpell); } public override void UpdateMaxHealth() { UnitMods unitMod = UnitMods.Health; float value = GetModifierValue(unitMod, UnitModifierType.BaseValue) + GetCreateHealth(); value *= GetModifierValue(unitMod, UnitModifierType.BasePCT); value += GetModifierValue(unitMod, UnitModifierType.TotalValue) + GetHealthBonusFromStamina(); value *= GetModifierValue(unitMod, UnitModifierType.TotalPCT); 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 void UpdateMaxPower(PowerType power) { UnitMods unitMod = UnitMods.PowerStart + (int)power; float value = GetModifierValue(unitMod, UnitModifierType.BaseValue) + GetCreatePowers(power); value *= GetModifierValue(unitMod, UnitModifierType.BasePCT); value += GetModifierValue(unitMod, UnitModifierType.TotalValue); value *= GetModifierValue(unitMod, UnitModifierType.TotalPCT); SetMaxPower(power, (int)value); } public void ApplySpellPenetrationBonus(int amount, bool apply) { ApplyModInt32Value(PlayerFields.ModTargetResistance, -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 ApplyModUInt32Value(PlayerFields.ModHealingDonePos, amount, apply); for (int i = (int)SpellSchools.Holy; i < (int)SpellSchools.Max; ++i) ApplyModUInt32Value(PlayerFields.ModDamageDonePos + i, 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; } 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 }; void SetBaseModValue(BaseModGroup modGroup, BaseModType modType, float value) { m_auraBaseMod[(int)modGroup][(int)modType] = value; } } 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 UpdateResistances(SpellSchools school) { if (school > SpellSchools.Normal) { float value = GetTotalAuraModValue(UnitMods.ResistanceStart + (int)school); SetResistance(school, (int)value); } else UpdateArmor(); } public override void UpdateArmor() { float value = GetTotalAuraModValue(UnitMods.Armor); SetArmor((int)value); } public override void UpdateMaxHealth() { float value = GetTotalAuraModValue(UnitMods.Health); SetMaxHealth((uint)value); } public override void UpdateMaxPower(PowerType power) { UnitMods unitMod = UnitMods.PowerStart + (int)power; float value = GetTotalAuraModValue(unitMod); SetMaxPower(power, (int)value); } public override void UpdateAttackPowerAndDamage(bool ranged = false) { UnitMods unitMod = ranged ? UnitMods.AttackPowerRanged : UnitMods.AttackPower; UnitFields index = UnitFields.AttackPower; UnitFields index_mult = UnitFields.AttackPowerMultiplier; if (ranged) { index = UnitFields.RangedAttackPower; index_mult = UnitFields.RangedAttackPowerMultiplier; } float base_attPower = GetModifierValue(unitMod, UnitModifierType.BaseValue) * GetModifierValue(unitMod, UnitModifierType.BasePCT); float attPowerMultiplier = GetModifierValue(unitMod, UnitModifierType.TotalPCT) - 1.0f; SetInt32Value(index, (int)base_attPower); //UNIT_FIELD_(RANGED)_ATTACK_POWER field SetFloatValue(index_mult, attPowerMultiplier); //UNIT_FIELD_(RANGED)_ATTACK_POWER_MULTIPLIER field //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 = 1.0f; 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); float attackSpeedMulti = Math.Max(GetAPMultiplier(attType, normalized), 0.25f); float baseValue = GetModifierValue(unitMod, UnitModifierType.BaseValue) + (attackPower / 3.5f) * variance; float basePct = GetModifierValue(unitMod, UnitModifierType.BasePCT) * attackSpeedMulti; float totalValue = GetModifierValue(unitMod, UnitModifierType.TotalValue); float totalPct = addTotalPct ? GetModifierValue(unitMod, UnitModifierType.TotalPCT) : 1.0f; float dmgMultiplier = GetCreatureTemplate().ModDamage; // = ModDamage * _GetDamageMod(rank); minDamage = ((weaponMinDamage + baseValue) * dmgMultiplier * basePct + totalValue) * totalPct; maxDamage = ((weaponMaxDamage + baseValue) * dmgMultiplier * basePct + totalValue) * totalPct; } } }