1871 lines
74 KiB
C#
1871 lines
74 KiB
C#
/*
|
|
* Copyright (C) 2012-2017 CypherCore <http://github.com/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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
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.ComboPoints:
|
|
case UnitMods.Runes:
|
|
case UnitMods.RunicPower:
|
|
case UnitMods.SoulShards:
|
|
case UnitMods.Eclipse:
|
|
case UnitMods.HolyPower:
|
|
case UnitMods.Alternative:
|
|
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;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int GetMinPower(PowerType power) { return power == PowerType.LunarPower ? -100 : 0; }
|
|
|
|
// 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;
|
|
}
|
|
|
|
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.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(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;
|
|
}
|
|
}
|
|
}
|