diff --git a/Source/Game/Chat/Commands/MiscCommands.cs b/Source/Game/Chat/Commands/MiscCommands.cs index 728ff2262..3188904dd 100644 --- a/Source/Game/Chat/Commands/MiscCommands.cs +++ b/Source/Game/Chat/Commands/MiscCommands.cs @@ -568,7 +568,7 @@ namespace Game.Chat // flat melee damage without resistence/etc reduction if (string.IsNullOrEmpty(schoolStr)) { - attacker.DealDamage(target, damage_, null, DamageEffectType.Direct, SpellSchoolMask.Normal, null, false); + Unit.DealDamage(attacker, target, damage_, null, DamageEffectType.Direct, SpellSchoolMask.Normal, null, false); if (target != attacker) attacker.SendAttackStateUpdate(HitInfo.AffectsVictim, target, SpellSchoolMask.Normal, damage_, 0, 0, VictimState.Hit, 0); return true; @@ -579,8 +579,8 @@ namespace Game.Chat SpellSchoolMask schoolmask = (SpellSchoolMask)(1 << school); - if (attacker.IsDamageReducedByArmor(schoolmask)) - damage_ = attacker.CalcArmorReducedDamage(handler.GetPlayer(), target, damage_, null, WeaponAttackType.BaseAttack); + if (Unit.IsDamageReducedByArmor(schoolmask)) + damage_ = Unit.CalcArmorReducedDamage(handler.GetPlayer(), target, damage_, null, WeaponAttackType.BaseAttack); string spellStr = args.NextString(); @@ -588,7 +588,7 @@ namespace Game.Chat if (string.IsNullOrEmpty(spellStr)) { DamageInfo dmgInfo = new(attacker, target, damage_, null, schoolmask, DamageEffectType.SpellDirect, WeaponAttackType.BaseAttack); - attacker.CalcAbsorbResist(dmgInfo); + Unit.CalcAbsorbResist(dmgInfo); if (dmgInfo.GetDamage() == 0) return true; @@ -597,8 +597,8 @@ namespace Game.Chat uint absorb = dmgInfo.GetAbsorb(); uint resist = dmgInfo.GetResist(); - attacker.DealDamageMods(target, ref damage_, ref absorb); - attacker.DealDamage(target, damage_, null, DamageEffectType.Direct, schoolmask, null, false); + Unit.DealDamageMods(attacker, target, ref damage_, ref absorb); + Unit.DealDamage(attacker, target, damage_, null, DamageEffectType.Direct, schoolmask, null, false); attacker.SendAttackStateUpdate(HitInfo.AffectsVictim, target, schoolmask, damage_, absorb, resist, VictimState.Hit, 0); return true; } @@ -613,9 +613,9 @@ namespace Game.Chat if (spellInfo == null) return false; - SpellNonMeleeDamage damageInfo = new(attacker, target, spellInfo, new Networking.Packets.SpellCastVisual(spellInfo.GetSpellXSpellVisualId(attacker), 0), spellInfo.SchoolMask); + SpellNonMeleeDamage damageInfo = new(attacker, target, spellInfo, new SpellCastVisual(spellInfo.GetSpellXSpellVisualId(attacker), 0), spellInfo.SchoolMask); damageInfo.damage = damage_; - attacker.DealDamageMods(damageInfo.target, ref damageInfo.damage, ref damageInfo.absorb); + Unit.DealDamageMods(damageInfo.attacker, damageInfo.target, ref damageInfo.damage, ref damageInfo.absorb); target.DealSpellDamage(damageInfo, true); target.SendSpellNonMeleeDamageLog(damageInfo); return true; @@ -670,7 +670,7 @@ namespace Game.Chat return false; if (target.IsAlive()) - handler.GetSession().GetPlayer().Kill(target); + Unit.Kill(handler.GetSession().GetPlayer(), target); return true; } diff --git a/Source/Game/Entities/Player/Player.cs b/Source/Game/Entities/Player/Player.cs index a9e219a68..6aa8723ec 100644 --- a/Source/Game/Entities/Player/Player.cs +++ b/Source/Game/Entities/Player/Player.cs @@ -3956,7 +3956,7 @@ namespace Game.Entities break; } - DealDamageMods(this, ref damage, ref absorb); + DealDamageMods(null, this, ref damage, ref absorb); EnvironmentalDamageLog packet = new(); packet.Victim = GetGUID(); @@ -3965,7 +3965,7 @@ namespace Game.Entities packet.Absorbed = (int)absorb; packet.Resisted = (int)resist; - uint final_damage = DealDamage(this, damage, null, DamageEffectType.Self, SpellSchoolMask.Normal, null, false); + uint final_damage = DealDamage(this, this, damage, null, DamageEffectType.Self, SpellSchoolMask.Normal, null, false); packet.LogData.Initialize(this); SendCombatLogMessage(packet); diff --git a/Source/Game/Entities/StatSystem.cs b/Source/Game/Entities/StatSystem.cs index ed4880a82..de2ad0446 100644 --- a/Source/Game/Entities/StatSystem.cs +++ b/Source/Game/Entities/StatSystem.cs @@ -667,7 +667,7 @@ namespace Game.Entities return 0; } public virtual uint GetPowerIndex(PowerType powerType) { return 0; } - public float GetPowerPct(PowerType powerType) { return GetMaxPower(powerType) != 0 ? 100.0f* GetPower(powerType) / GetMaxPower(powerType) : 0.0f; } + public float GetPowerPct(PowerType powerType) { return GetMaxPower(powerType) != 0 ? 100.0f * GetPower(powerType) / GetMaxPower(powerType) : 0.0f; } void TriggerOnPowerChangeAuras(PowerType power, int oldVal, int newVal) { @@ -713,27 +713,43 @@ namespace Game.Entities } } - public void ApplyResilience(Unit victim, ref uint damage) + public bool CanApplyResilience() + { + return !IsVehicle() && GetOwnerGUID().IsPlayer(); + } + + public static void ApplyResilience(Unit victim, ref int damage) { // player mounted on multi-passenger mount is also classified as vehicle - if (IsVehicle() || (victim.IsVehicle() && !victim.IsTypeId(TypeId.Player))) - return; - - // Don't consider resilience if not in PvP - player or pet - if (!GetCharmerOrOwnerPlayerOrPlayerItself()) + if (victim.IsVehicle() && !victim.IsPlayer()) return; Unit target = null; - if (victim.IsTypeId(TypeId.Player)) + if (victim.IsPlayer()) target = victim; - else if (victim.IsTypeId(TypeId.Unit) && victim.GetOwner() && victim.GetOwner().IsTypeId(TypeId.Player)) - target = victim.GetOwner(); + else // victim->GetTypeId() == TYPEID_UNIT + { + Unit owner = victim.GetOwner(); + if (owner != null) + if (owner.IsPlayer()) + target = owner; + } if (!target) return; - damage -= target.GetDamageReduction(damage); + damage -= (int)target.GetDamageReduction((uint)damage); } + + public int CalculateAOEAvoidance(int damage, uint schoolMask, Unit caster) + { + damage = (int)((float)damage * GetTotalAuraMultiplierByMiscMask(AuraType.ModAoeDamageAvoidance, schoolMask)); + if (caster.IsCreature()) + damage = (int)((float)damage * GetTotalAuraMultiplierByMiscMask(AuraType.ModCreatureAoeDamageAvoidance, schoolMask)); + + return damage; + } + // player or player's pet resilience (-1%) uint GetDamageReduction(uint damage) { return GetCombatRatingDamageReduction(CombatRating.ResiliencePlayerDamage, 1.0f, 100.0f, damage); } @@ -802,7 +818,7 @@ namespace Game.Entities return missChance; } - float GetUnitCriticalChance(WeaponAttackType attackType, Unit victim) + float GetUnitCriticalChanceDone(WeaponAttackType attackType) { float chance = 0.0f; Player thisPlayer = ToPlayer(); @@ -830,25 +846,38 @@ namespace Game.Entities chance += GetTotalAuraModifier(AuraType.ModCritPct); } } + return chance; + } + + float GetUnitCriticalChanceTaken(Unit attacker, WeaponAttackType attackType, float critDone) + { + float chance = critDone; // flat aura mods if (attackType == WeaponAttackType.RangedAttack) - chance += victim.GetTotalAuraModifier(AuraType.ModAttackerRangedCritChance); + chance += GetTotalAuraModifier(AuraType.ModAttackerRangedCritChance); else - chance += victim.GetTotalAuraModifier(AuraType.ModAttackerMeleeCritChance); + chance += GetTotalAuraModifier(AuraType.ModAttackerMeleeCritChance); - chance += GetTotalAuraModifier(AuraType.ModCritChanceVersusTargetHealth, aurEff => !victim.HealthBelowPct(aurEff.GetMiscValueB())); + chance += GetTotalAuraModifier(AuraType.ModCritChanceVersusTargetHealth, aurEff => !HealthBelowPct(aurEff.GetMiscValueB())); - chance += victim.GetTotalAuraModifier(AuraType.ModCritChanceForCaster, aurEff => aurEff.GetCasterGUID() == GetGUID()); + chance += GetTotalAuraModifier(AuraType.ModCritChanceForCaster, aurEff => aurEff.GetCasterGUID() == attacker.GetGUID()); - TempSummon tempSummon = ToTempSummon(); - if (tempSummon) - chance += victim.GetTotalAuraModifier(AuraType.ModCritChanceForCasterPet, aurEff => aurEff.GetCasterGUID() == tempSummon.GetSummonerGUID()); + TempSummon tempSummon = attacker.ToTempSummon(); + if (tempSummon != null) + chance += GetTotalAuraModifier(AuraType.ModCritChanceForCasterPet, aurEff => aurEff.GetCasterGUID() == tempSummon.GetSummonerGUID()); - chance += victim.GetTotalAuraModifier(AuraType.ModAttackerSpellAndWeaponCritChance); + chance += GetTotalAuraModifier(AuraType.ModAttackerSpellAndWeaponCritChance); return Math.Max(chance, 0.0f); } + + float GetUnitCriticalChanceAgainst(WeaponAttackType attackType, Unit victim) + { + float chance = GetUnitCriticalChanceDone(attackType); + return victim.GetUnitCriticalChanceTaken(this, attackType, chance); + } + float GetUnitDodgeChance(WeaponAttackType attType, Unit victim) { int levelDiff = (int)(victim.GetLevelForTarget(this) - GetLevelForTarget(victim)); diff --git a/Source/Game/Entities/Unit/Unit.Combat.cs b/Source/Game/Entities/Unit/Unit.Combat.cs index 6db6fe815..1f3f1d319 100644 --- a/Source/Game/Entities/Unit/Unit.Combat.cs +++ b/Source/Game/Entities/Unit/Unit.Combat.cs @@ -46,7 +46,7 @@ namespace Game.Entities InterruptNonMeleeSpells(false); RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.EnteringCombat); - ProcSkillsAndAuras(null, ProcFlags.EnterCombat, ProcFlags.None, ProcFlagsSpellType.MaskAll, ProcFlagsSpellPhase.None, ProcFlagsHit.None, null, null, null); + ProcSkillsAndAuras(this, null, ProcFlags.EnterCombat, ProcFlags.None, ProcFlagsSpellType.MaskAll, ProcFlagsSpellPhase.None, ProcFlagsHit.None, null, null, null); } public virtual void AtExitCombat() @@ -558,13 +558,13 @@ namespace Game.Entities CalcDamageInfo damageInfo; CalculateMeleeDamage(victim, 0, out damageInfo, attType); // Send log damage message to client - DealDamageMods(victim, ref damageInfo.damage, ref damageInfo.absorb); + DealDamageMods(damageInfo.attacker, victim, ref damageInfo.damage, ref damageInfo.absorb); SendAttackStateUpdate(damageInfo); DealMeleeDamage(damageInfo, true); DamageInfo dmgInfo = new(damageInfo); - ProcSkillsAndAuras(damageInfo.target, damageInfo.procAttacker, damageInfo.procVictim, ProcFlagsSpellType.None, ProcFlagsSpellPhase.None, dmgInfo.GetHitMask(), null, dmgInfo, null); + ProcSkillsAndAuras(damageInfo.attacker, damageInfo.target, damageInfo.procAttacker, damageInfo.procVictim, ProcFlagsSpellType.None, ProcFlagsSpellPhase.None, dmgInfo.GetHitMask(), null, dmgInfo, null); Log.outDebug(LogFilter.Unit, "AttackerStateUpdate: {0} attacked {1} for {2} dmg, absorbed {3}, blocked {4}, resisted {5}.", GetGUID().ToString(), victim.GetGUID().ToString(), damageInfo.damage, damageInfo.absorb, damageInfo.blocked_amount, damageInfo.resist); } @@ -756,71 +756,6 @@ namespace Game.Entities } } - public void SetInCombatState(bool PvP, Unit enemy = null) - { - // only alive units can be in combat - if (!IsAlive()) - return; - - if (PvP) - { - combatTimer = 5000; - Player me = ToPlayer(); - if (me) - me.EnablePvpRules(true); - } - - if (IsInCombat() || HasUnitState(UnitState.Evade)) - return; - - AddUnitFlag(UnitFlags.InCombat); - - Creature creature = ToCreature(); - if (creature != null) - { - // Set home position at place of engaging combat for escorted creatures - if ((IsAIEnabled && creature.GetAI().IsEscorted()) || GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Waypoint || - GetMotionMaster().GetCurrentMovementGeneratorType() == MovementGeneratorType.Point) - creature.SetHomePosition(GetPositionX(), GetPositionY(), GetPositionZ(), Orientation); - - if (enemy != null) - { - if (IsAIEnabled) - creature.GetAI().JustEngagedWith(enemy); - - if (creature.GetFormation() != null) - creature.GetFormation().MemberEngagingTarget(creature, enemy); - } - - if (IsPet()) - { - UpdateSpeed(UnitMoveType.Run); - UpdateSpeed(UnitMoveType.Swim); - UpdateSpeed(UnitMoveType.Flight); - } - - if (!creature.GetCreatureTemplate().TypeFlags.HasAnyFlag(CreatureTypeFlags.MountedCombatAllowed)) - Dismount(); - } - - foreach (var unit in m_Controlled) - unit.SetInCombatState(PvP, enemy); - - foreach (var pair in GetAppliedAuras()) - { - AuraApplication aurApp = pair.Value; - aurApp.GetBase().CallScriptEnterLeaveCombatHandlers(aurApp, true); - } - - Spell spell = GetCurrentSpell(CurrentSpellTypes.Generic); - if (spell != null) - if (spell.GetState() == SpellState.Preparing && spell.m_spellInfo.HasAttribute(SpellAttr0.CantUsedInCombat) && spell.m_spellInfo.InterruptFlags.HasAnyFlag(SpellInterruptFlags.Combat)) - InterruptNonMeleeSpells(false); - - RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.EnteringCombat); - ProcSkillsAndAuras(enemy, ProcFlags.EnterCombat, ProcFlags.None, ProcFlagsSpellType.MaskAll, ProcFlagsSpellPhase.None, ProcFlagsHit.None, null, null, null); - } - internal void SendCombatLogMessage(CombatLogServerPacket combatLog) { CombatLogSender combatLogSender = new(combatLog); @@ -837,14 +772,20 @@ namespace Game.Entities return !m_threatManager.IsThreatListEmpty(); } - public void Kill(Unit victim, bool durabilityLoss = true, bool skipSettingDeathState = false) + public static void Kill(Unit attacker, Unit victim, bool durabilityLoss = true, bool skipSettingDeathState = false) { // Prevent killing unit twice (and giving reward from kill twice) if (victim.GetHealth() == 0) return; + if (attacker != null && !attacker.IsInMap(victim)) + attacker = null; + // find player: owner of controlled `this` or `this` itself maybe - Player player = GetCharmerOrOwnerPlayerOrPlayerItself(); + Player player = null; + if (attacker != null) + player = attacker.GetCharmerOrOwnerPlayerOrPlayerItself(); + Creature creature = victim.ToCreature(); bool isRewardAllowed = true; @@ -911,7 +852,7 @@ namespace Game.Entities loot.Clear(); uint lootid = creature.GetCreatureTemplate().LootId; if (lootid != 0) - loot.FillLoot(lootid, LootStorage.Creature, looter, false, false, creature.GetLootMode(), GetMap().GetDifficultyLootItemContext()); + loot.FillLoot(lootid, LootStorage.Creature, looter, false, false, creature.GetLootMode(), creature.GetMap().GetDifficultyLootItemContext()); if (creature.GetLootMode() > 0) loot.GenerateMoneyLoot(creature.GetCreatureTemplate().MinGold, creature.GetCreatureTemplate().MaxGold); @@ -933,24 +874,28 @@ namespace Game.Entities } // Do KILL and KILLED procs. KILL proc is called only for the unit who landed the killing blow (and its owner - for pets and totems) regardless of who tapped the victim - if (IsPet() || IsTotem()) + if (attacker != null && (attacker.IsPet() || attacker.IsTotem())) { // proc only once for victim - Unit owner = GetOwner(); + Unit owner = attacker.GetOwner(); if (owner != null) - owner.ProcSkillsAndAuras(victim, ProcFlags.Kill, ProcFlags.None, ProcFlagsSpellType.MaskAll, ProcFlagsSpellPhase.None, ProcFlagsHit.None, null, null, null); + ProcSkillsAndAuras(owner, victim, ProcFlags.Kill, ProcFlags.None, ProcFlagsSpellType.MaskAll, ProcFlagsSpellPhase.None, ProcFlagsHit.None, null, null, null); } if (!victim.IsCritter()) - ProcSkillsAndAuras(victim, ProcFlags.Kill, ProcFlags.Killed, ProcFlagsSpellType.MaskAll, ProcFlagsSpellPhase.None, ProcFlagsHit.None, null, null, null); + ProcSkillsAndAuras(attacker, victim, ProcFlags.Kill, ProcFlags.Killed, ProcFlagsSpellType.MaskAll, ProcFlagsSpellPhase.None, ProcFlagsHit.None, null, null, null); // Proc auras on death - must be before aura/combat remove - victim.ProcSkillsAndAuras(victim, ProcFlags.None, ProcFlags.Death, ProcFlagsSpellType.MaskAll, ProcFlagsSpellPhase.None, ProcFlagsHit.None, null, null, null); + ProcSkillsAndAuras(victim, victim, ProcFlags.None, ProcFlags.Death, ProcFlagsSpellType.MaskAll, ProcFlagsSpellPhase.None, ProcFlagsHit.None, null, null, null); // update get killing blow achievements, must be done before setDeathState to be able to require auras on target // and before Spirit of Redemption as it also removes auras - if (player != null) - player.UpdateCriteria(CriteriaTypes.GetKillingBlows, 1, 0, 0, victim); + if (attacker != null) + { + Player killerPlayer = attacker.GetCharmerOrOwnerPlayerOrPlayerItself(); + if (killerPlayer != null) + killerPlayer.UpdateCriteria(CriteriaTypes.GetKillingBlows, 1, 0, 0, victim); + } if (!skipSettingDeathState) { @@ -985,11 +930,11 @@ namespace Game.Entities // Durability loss is calculated more accurately again for each item in Player.DurabilityLoss plrVictim.DurabilityLossAll(baseLoss, false); // durability lost message - SendDurabilityLoss(plrVictim, loss); + plrVictim.SendDurabilityLoss(plrVictim, loss); } // Call KilledUnit for creatures - if (IsTypeId(TypeId.Unit) && IsAIEnabled) - ToCreature().GetAI().KilledUnit(victim); + if (attacker != null && attacker.IsCreature() && attacker.IsAIEnabled) + attacker.ToCreature().GetAI().KilledUnit(victim); // last damage from non duel opponent or opponent controlled creature if (plrVictim.duel != null) @@ -1015,12 +960,12 @@ namespace Game.Entities } // Call KilledUnit for creatures, this needs to be called after the lootable flag is set - if (IsTypeId(TypeId.Unit) && IsAIEnabled) - ToCreature().GetAI().KilledUnit(victim); + if (attacker != null && attacker.IsCreature() && attacker.IsAIEnabled) + attacker.ToCreature().GetAI().KilledUnit(victim); // Call creature just died function if (creature.IsAIEnabled) - creature.GetAI().JustDied(this); + creature.GetAI().JustDied(attacker); TempSummon summon = creature.ToTempSummon(); if (summon != null) @@ -1028,22 +973,21 @@ namespace Game.Entities Unit summoner = summon.GetSummoner(); if (summoner != null) if (summoner.IsTypeId(TypeId.Unit) && summoner.IsAIEnabled) - summoner.ToCreature().GetAI().SummonedCreatureDies(creature, this); + summoner.ToCreature().GetAI().SummonedCreatureDies(creature, attacker); } // Dungeon specific stuff, only applies to players killing creatures if (creature.GetInstanceId() != 0) { Map instanceMap = creature.GetMap(); - Player creditedPlayer = GetCharmerOrOwnerPlayerOrPlayerItself(); - // @todo do instance binding anyway if the charmer/owner is offline - if (instanceMap.IsDungeon() && (creditedPlayer || this == victim)) + /// @todo do instance binding anyway if the charmer/owner is offline + if (instanceMap.IsDungeon() && ((attacker != null && attacker.GetCharmerOrOwnerPlayerOrPlayerItself() != null) || attacker == victim)) { if (instanceMap.IsRaidOrHeroicDungeon()) { if (creature.GetCreatureTemplate().FlagsExtra.HasAnyFlag(CreatureFlagsExtra.InstanceBind)) - ((InstanceMap)instanceMap).PermBindAllPlayers(); + instanceMap.ToInstanceMap().PermBindAllPlayers(); } else { @@ -1061,7 +1005,7 @@ namespace Game.Entities // outdoor pvp things, do these after setting the death state, else the player activity notify won't work... doh... // handle player kill only if not suicide (spirit of redemption for example) - if (player != null && this != victim) + if (player != null && attacker != victim) { OutdoorPvP pvp = player.GetOutdoorPvP(); if (pvp != null) @@ -1087,35 +1031,44 @@ namespace Game.Entities } // achievement stuff - if (victim.IsTypeId(TypeId.Player)) + if (attacker != null && victim.IsPlayer()) { - if (IsTypeId(TypeId.Unit)) - victim.ToPlayer().UpdateCriteria(CriteriaTypes.KilledByCreature, GetEntry()); - else if (IsTypeId(TypeId.Player) && victim != this) - victim.ToPlayer().UpdateCriteria(CriteriaTypes.KilledByPlayer, 1, (ulong)ToPlayer().GetTeam()); + if (attacker.IsCreature()) + victim.ToPlayer().UpdateCriteria(CriteriaTypes.KilledByCreature, attacker.GetEntry()); + else if (attacker.IsPlayer() && victim != attacker) + victim.ToPlayer().UpdateCriteria(CriteriaTypes.KilledByPlayer, 1, (ulong)attacker.ToPlayer().GetTeam()); } // Hook for OnPVPKill Event - Player killerPlr = ToPlayer(); - Creature killerCre = ToCreature(); - if (killerPlr != null) + if (attacker != null) { - Player killedPlr = victim.ToPlayer(); - Creature killedCre = victim.ToCreature(); - if (killedPlr != null) - Global.ScriptMgr.OnPVPKill(killerPlr, killedPlr); - else if (killedCre != null) - Global.ScriptMgr.OnCreatureKill(killerPlr, killedCre); - } - else if (killerCre != null) - { - Player killed = victim.ToPlayer(); - if (killed != null) - Global.ScriptMgr.OnPlayerKilledByCreature(killerCre, killed); + Player killerPlr = attacker.ToPlayer(); + if (killerPlr != null) + { + Player killedPlr = victim.ToPlayer(); + if (killedPlr != null) + Global.ScriptMgr.OnPVPKill(killerPlr, killedPlr); + else + { + Creature killedCre = victim.ToCreature(); + if (killedCre != null) + Global.ScriptMgr.OnCreatureKill(killerPlr, killedCre); + } + } + else + { + Creature killerCre = attacker.ToCreature(); + if (killerCre != null) + { + Player killed = victim.ToPlayer(); + if (killed != null) + Global.ScriptMgr.OnPlayerKilledByCreature(killerCre, killed); + } + } } } - public void KillSelf(bool durabilityLoss = true, bool skipSettingDeathState = false) { Kill(this, durabilityLoss, skipSettingDeathState); } + public void KillSelf(bool durabilityLoss = true, bool skipSettingDeathState = false) { Kill(this, this, durabilityLoss, skipSettingDeathState); } public virtual bool CanUseAttackType(WeaponAttackType attacktype) { @@ -1289,11 +1242,13 @@ namespace Game.Entities if (!damageInfo.HitInfo.HasAnyFlag(HitInfo.Miss)) damageInfo.HitInfo |= HitInfo.AffectsVictim; - uint resilienceReduction = damageInfo.damage; - ApplyResilience(victim, ref resilienceReduction); - resilienceReduction = damageInfo.damage - resilienceReduction; - damageInfo.damage -= resilienceReduction; - damageInfo.cleanDamage += resilienceReduction; + int resilienceReduction = (int)damageInfo.damage; + if (CanApplyResilience()) + ApplyResilience(victim, ref resilienceReduction); + + resilienceReduction = (int)damageInfo.damage - resilienceReduction; + damageInfo.damage -= (uint)resilienceReduction; + damageInfo.cleanDamage += (uint)resilienceReduction; // Calculate absorb resist if (damageInfo.damage > 0) @@ -1326,7 +1281,7 @@ namespace Game.Entities int miss_chance = (int)(MeleeSpellMissChance(victim, attType, null) * 100.0f); // Critical hit chance - int crit_chance = (int)(GetUnitCriticalChance(attType, victim) + GetTotalAuraModifier(AuraType.ModAutoAttackCritChance) * 100.0f); + int crit_chance = (int)((GetUnitCriticalChanceAgainst(attType, victim) + GetTotalAuraModifier(AuraType.ModAutoAttackCritChance)) * 100.0f); int dodge_chance = (int)(GetUnitDodgeChance(attType, victim) * 100.0f); int block_chance = (int)(GetUnitBlockChance(attType, victim) * 100.0f); diff --git a/Source/Game/Entities/Unit/Unit.Spells.cs b/Source/Game/Entities/Unit/Unit.Spells.cs index 359fbe6f6..171ee019e 100644 --- a/Source/Game/Entities/Unit/Unit.Spells.cs +++ b/Source/Game/Entities/Unit/Unit.Spells.cs @@ -105,6 +105,7 @@ namespace Game.Entities } int DoneTotal = 0; + float DoneTotalMod = SpellDamagePctDone(victim, spellProto, damagetype); // Done fixed damage bonus auras int DoneAdvertisedBenefit = SpellBaseDamageBonusDone(spellProto.GetSchoolMask()); @@ -141,27 +142,21 @@ namespace Game.Entities float coeff = effect.BonusCoefficient; if (DoneAdvertisedBenefit != 0) { - Player modOwner = GetSpellModOwner(); - if (modOwner) + Player modOwner1 = GetSpellModOwner(); + if (modOwner1) { coeff *= 100.0f; - modOwner.ApplySpellMod(spellProto, SpellModOp.BonusCoefficient, ref coeff); + modOwner1.ApplySpellMod(spellProto, SpellModOp.BonusCoefficient, ref coeff); coeff /= 100.0f; } DoneTotal += (int)(DoneAdvertisedBenefit * coeff * stack); } - // Done Percentage for DOT is already calculated, no need to do it again. The percentage mod is applied in Aura.HandleAuraSpecificMods. - float tmpDamage = ((int)pdamage + DoneTotal) * (damagetype == DamageEffectType.DOT ? 1.0f : SpellDamagePctDone(victim, spellProto, damagetype)); + float tmpDamage = (float)((int)pdamage + DoneTotal) * DoneTotalMod; // apply spellmod to Done damage (flat and pct) Player _modOwner = GetSpellModOwner(); - if (_modOwner) - { - if (damagetype == DamageEffectType.DOT) - _modOwner.ApplySpellMod(spellProto, SpellModOp.PeriodicHealingAndDamage, ref tmpDamage); - else - _modOwner.ApplySpellMod(spellProto, SpellModOp.HealingAndDamage, ref tmpDamage); - } + if (_modOwner != null) + _modOwner.ApplySpellMod(spellProto, damagetype == DamageEffectType.DOT ? SpellModOp.PeriodicHealingAndDamage : SpellModOp.HealingAndDamage, ref tmpDamage); return (uint)Math.Max(tmpDamage, 0.0f); } @@ -171,13 +166,21 @@ namespace Game.Entities if (spellProto == null || !victim || damagetype == DamageEffectType.Direct) return 1.0f; + // Some spells don't benefit from done mods + if (spellProto.HasAttribute(SpellAttr3.NoDoneBonus)) + return 1.0f; + // Some spells don't benefit from pct done mods if (spellProto.HasAttribute(SpellAttr6.IgnoreCasterDamageModifiers)) return 1.0f; - // For totems pct done mods are calculated when its calculation is run on the player in SpellDamageBonusDone. - if (IsTypeId(TypeId.Unit) && IsTotem()) - return 1.0f; + // For totems get damage bonus from owner + if (IsCreature() && IsTotem()) + { + Unit owner = GetOwner(); + if (owner != null) + return owner.SpellDamagePctDone(victim, spellProto, damagetype); + } // Done total percent damage auras float DoneTotalMod = 1.0f; @@ -392,19 +395,23 @@ namespace Game.Entities return GetTotalAuraModifierByMiscMask(AuraType.ModHealing, (int)schoolMask); } - public int SpellCriticalHealingBonus(SpellInfo spellProto, int damage, Unit victim) + public static int SpellCriticalHealingBonus(Unit caster, SpellInfo spellProto, int damage, Unit victim) { // Calculate critical bonus int crit_bonus = damage; // adds additional damage to critBonus (from talents) - Player modOwner = GetSpellModOwner(); - if (modOwner != null) - modOwner.ApplySpellMod(spellProto, SpellModOp.CritDamageAndHealing, ref crit_bonus); + if (caster != null) + { + Player modOwner = caster.GetSpellModOwner(); + if (modOwner != null) + modOwner.ApplySpellMod(spellProto, SpellModOp.CritDamageAndHealing, ref crit_bonus); + } damage += crit_bonus; - damage = (int)(damage * GetTotalAuraMultiplier(AuraType.ModCriticalHealingAmount)); + if (caster != null) + damage = (int)(damage * caster.GetTotalAuraMultiplier(AuraType.ModCriticalHealingAmount)); return damage; } @@ -424,19 +431,20 @@ namespace Game.Entities return healamount; int DoneTotal = 0; + float DoneTotalMod = SpellHealingPctDone(victim, spellProto); // done scripted mod (take it from owner) Unit owner1 = GetOwner() ?? this; var mOverrideClassScript = owner1.GetAuraEffectsByType(AuraType.OverrideClassScripts); - foreach (var eff in mOverrideClassScript) + foreach (var aurEff in mOverrideClassScript) { - if (!eff.IsAffectingSpell(spellProto)) + if (!aurEff.IsAffectingSpell(spellProto)) continue; - switch (eff.GetMiscValue()) + switch (aurEff.GetMiscValue()) { case 3736: // Hateful Totem of the Third Wind / Increased Lesser Healing Wave / LK Arena (4/5/6) Totem of the Third Wind / Savage Totem of the Third Wind - DoneTotal += eff.GetAmount(); + DoneTotal += aurEff.GetAmount(); break; default: break; @@ -491,25 +499,32 @@ namespace Game.Entities DoneTotal = 0; } - // use float as more appropriate for negative values and percent applying - float heal = (healamount + DoneTotal) * (damagetype == DamageEffectType.DOT ? 1.0f : SpellHealingPctDone(victim, spellProto)); + float heal = (float)((int)healamount + DoneTotal) * DoneTotalMod; + // apply spellmod to Done amount Player _modOwner = GetSpellModOwner(); if (_modOwner) - { - if (damagetype == DamageEffectType.DOT) - _modOwner.ApplySpellMod(spellProto, SpellModOp.PeriodicHealingAndDamage, ref heal); - else - _modOwner.ApplySpellMod(spellProto, SpellModOp.HealingAndDamage, ref heal); - } + _modOwner.ApplySpellMod(spellProto, damagetype == DamageEffectType.DOT ? SpellModOp.PeriodicHealingAndDamage : SpellModOp.HealingAndDamage, ref heal); return (uint)Math.Max(heal, 0.0f); } public float SpellHealingPctDone(Unit victim, SpellInfo spellProto) { - // For totems pct done mods are calculated when its calculation is run on the player in SpellHealingBonusDone. - if (IsTypeId(TypeId.Unit) && IsTotem()) + // For totems get healing bonus from owner + if (IsCreature() && IsTotem()) + { + Unit owner = GetOwner(); + if (owner != null) + return owner.SpellHealingPctDone(victim, spellProto); + } + + // Some spells don't benefit from done mods + if (spellProto.HasAttribute(SpellAttr3.NoDoneBonus)) + return 1.0f; + + // Some spells don't benefit from done mods + if (spellProto.HasAttribute(SpellAttr6.IgnoreHealingModifiers)) return 1.0f; // No bonus healing for potion spells @@ -647,40 +662,19 @@ namespace Game.Entities return (uint)Math.Max(heal, 0.0f); } - public bool IsSpellCrit(Unit victim, Spell spell, AuraEffect aurEff, SpellSchoolMask schoolMask, WeaponAttackType attackType = WeaponAttackType.BaseAttack) + public float SpellCritChanceDone(SpellInfo spellInfo, SpellSchoolMask schoolMask, WeaponAttackType attackType = WeaponAttackType.BaseAttack) { - return RandomHelper.randChance(GetUnitSpellCriticalChance(victim, spell, aurEff, schoolMask, attackType)); - } - - public float GetUnitSpellCriticalChance(Unit victim, Spell spell, AuraEffect aurEff, SpellSchoolMask schoolMask, WeaponAttackType attackType = WeaponAttackType.BaseAttack) - { - SpellInfo spellProto = spell?.GetSpellInfo() ?? aurEff?.GetSpellInfo(); - //! Mobs can't crit with spells. Player Totems can - //! Fire Elemental (from totem) can too - but this part is a hack and needs more research - if (GetGUID().IsCreatureOrVehicle() && !(IsTotem() && GetOwnerGUID().IsPlayer()) && GetEntry() != 15438) + //! Mobs can't crit with spells. (Except player controlled) + if (IsCreature() && !GetSpellModOwner()) return 0.0f; // not critting spell - if (spellProto.HasAttribute(SpellAttr2.CantCrit)) + if (spellInfo.HasAttribute(SpellAttr2.CantCrit)) return 0.0f; float crit_chance = 0.0f; - switch (spellProto.DmgClass) + switch (spellInfo.DmgClass) { - case SpellDmgClass.None: - // We need more spells to find a general way (if there is any) - switch (spellProto.Id) - { - case 379: // Earth Shield - case 33778: // Lifebloom Final Bloom - case 64844: // Divine Hymn - case 71607: // Item - Bauble of True Blood 10m - case 71646: // Item - Bauble of True Blood 25m - break; - default: - return 0.0f; - } - goto case SpellDmgClass.Magic; case SpellDmgClass.Magic: { if (schoolMask.HasAnyFlag(SpellSchoolMask.Normal)) @@ -690,26 +684,58 @@ namespace Game.Entities crit_chance = ToPlayer().m_activePlayerData.SpellCritPercentage; else crit_chance = BaseSpellCritChance; + break; + } + case SpellDmgClass.Melee: + case SpellDmgClass.Ranged: + crit_chance += GetUnitCriticalChanceDone(attackType); + break; + case SpellDmgClass.None: + default: + return 0f; + } + // percent done + // only players use intelligence for critical chance computations + Player modOwner = GetSpellModOwner(); + if (modOwner != null) + modOwner.ApplySpellMod(spellInfo, SpellModOp.CritChance, ref crit_chance); + + return Math.Max(crit_chance, 0.0f); + } + + public float SpellCritChanceTaken(Unit caster, Spell spell, AuraEffect aurEff, SpellSchoolMask schoolMask, float doneChance, WeaponAttackType attackType = WeaponAttackType.BaseAttack) + { + SpellInfo spellInfo = spell != null ? spell.GetSpellInfo() : aurEff.GetSpellInfo(); + // not critting spell + if (spellInfo.HasAttribute(SpellAttr2.CantCrit)) + return 0.0f; + + float crit_chance = doneChance; + switch (spellInfo.DmgClass) + { + case SpellDmgClass.Magic: + { // taken - if (victim) + if (!spellInfo.IsPositive()) + { + // Modify critical chance by victim SPELL_AURA_MOD_ATTACKER_SPELL_AND_WEAPON_CRIT_CHANCE + crit_chance += GetTotalAuraModifier(AuraType.ModAttackerSpellAndWeaponCritChance); + } + + if (caster) { - if (!spellProto.IsPositive()) - { - // Modify critical chance by victim SPELL_AURA_MOD_ATTACKER_SPELL_AND_WEAPON_CRIT_CHANCE - crit_chance += victim.GetTotalAuraModifier(AuraType.ModAttackerSpellAndWeaponCritChance); - } // scripted (increase crit chance ... against ... target by x% - var mOverrideClassScript = GetAuraEffectsByType(AuraType.OverrideClassScripts); + var mOverrideClassScript = caster.GetAuraEffectsByType(AuraType.OverrideClassScripts); foreach (var eff in mOverrideClassScript) { - if (!eff.IsAffectingSpell(spellProto)) + if (!eff.IsAffectingSpell(spellInfo)) continue; switch (eff.GetMiscValue()) { case 911: // Shatter - if (victim.HasAuraState(AuraStateType.Frozen, spellProto, this)) + if (HasAuraState(AuraStateType.Frozen, spellInfo, this)) { crit_chance *= 1.5f; AuraEffect _eff = eff.GetBase().GetEffect(1); @@ -722,19 +748,19 @@ namespace Game.Entities } } // Custom crit by class - switch (spellProto.SpellFamilyName) + switch (spellInfo.SpellFamilyName) { case SpellFamilyNames.Rogue: // Shiv-applied poisons can't crit - if (FindCurrentSpellBySpellId(5938) != null) + if (caster.FindCurrentSpellBySpellId(5938) != null) crit_chance = 0.0f; break; case SpellFamilyNames.Shaman: // Lava Burst - if (spellProto.SpellFamilyFlags[1].HasAnyFlag(0x00001000u)) + if (spellInfo.SpellFamilyFlags[1].HasAnyFlag(0x00001000u)) { - if (victim.GetAuraEffect(AuraType.PeriodicDamage, SpellFamilyNames.Shaman, new FlagArray128(0x10000000, 0, 0), GetGUID()) != null) - if (victim.GetTotalAuraModifier(AuraType.ModAttackerSpellAndWeaponCritChance) > -100) + if (GetAuraEffect(AuraType.PeriodicDamage, SpellFamilyNames.Shaman, new FlagArray128(0x10000000, 0, 0), caster.GetGUID()) != null) + if (GetTotalAuraModifier(AuraType.ModAttackerSpellAndWeaponCritChance) > -100) return 100.0f; break; } @@ -742,9 +768,9 @@ namespace Game.Entities } // Spell crit suppression - if (victim.GetTypeId() == TypeId.Unit) + if (IsCreature()) { - int levelDiff = (int)(victim.GetLevelForTarget(this) - GetLevel()); + int levelDiff = (int)(GetLevelForTarget(this) - caster.GetLevel()); crit_chance -= levelDiff * 1.0f; } } @@ -753,38 +779,34 @@ namespace Game.Entities case SpellDmgClass.Melee: case SpellDmgClass.Ranged: { - if (victim) - crit_chance += GetUnitCriticalChance(attackType, victim); + if (caster != null) + crit_chance += GetUnitCriticalChanceTaken(caster, attackType, crit_chance); break; } + case SpellDmgClass.None: default: - return 0.0f; + return 0f; } - // percent done - // only players use intelligence for critical chance computations - Player modOwner = GetSpellModOwner(); - if (modOwner != null) - modOwner.ApplySpellMod(spellProto, SpellModOp.CritChance, ref crit_chance); // for this types the bonus was already added in GetUnitCriticalChance, do not add twice - if (spellProto.DmgClass != SpellDmgClass.Melee && spellProto.DmgClass != SpellDmgClass.Ranged) + if (caster != null && spellInfo.DmgClass != SpellDmgClass.Melee && spellInfo.DmgClass != SpellDmgClass.Ranged) { - crit_chance += victim.GetTotalAuraModifier(AuraType.ModCritChanceForCasterWithAbilities, aurEff => aurEff.GetCasterGUID() == GetGUID() && aurEff.IsAffectingSpell(spellProto)); + crit_chance += GetTotalAuraModifier(AuraType.ModCritChanceForCasterWithAbilities, aurEff => aurEff.GetCasterGUID() == caster.GetGUID() && aurEff.IsAffectingSpell(spellInfo)); - crit_chance += victim.GetTotalAuraModifier(AuraType.ModCritChanceForCaster, aurEff => aurEff.GetCasterGUID() != GetGUID()); + crit_chance += GetTotalAuraModifier(AuraType.ModCritChanceForCaster, aurEff => aurEff.GetCasterGUID() == caster.GetGUID()); - crit_chance += GetTotalAuraModifier(AuraType.ModCritChanceVersusTargetHealth, aurEff => !victim.HealthBelowPct(aurEff.GetMiscValueB())); + crit_chance += caster.GetTotalAuraModifier(AuraType.ModCritChanceVersusTargetHealth, aurEff => !HealthBelowPct(aurEff.GetMiscValueB())); - TempSummon tempSummon = ToTempSummon(); + TempSummon tempSummon = caster.ToTempSummon(); if (tempSummon != null) - crit_chance += victim.GetTotalAuraModifier(AuraType.ModCritChanceForCasterPet, aurEff => aurEff.GetCasterGUID() == tempSummon.GetSummonerGUID()); + crit_chance += GetTotalAuraModifier(AuraType.ModCritChanceForCasterPet, aurEff => aurEff.GetCasterGUID() == tempSummon.GetSummonerGUID()); } // call script handlers if (spell) - spell.CallScriptCalcCritChanceHandlers(victim, ref crit_chance); + spell.CallScriptCalcCritChanceHandlers(this, ref crit_chance); else - aurEff.GetBase().CallScriptEffectCalcCritChanceHandlers(aurEff, aurEff.GetBase().GetApplicationOfTarget(victim.GetGUID()), victim, ref crit_chance); + aurEff.GetBase().CallScriptEffectCalcCritChanceHandlers(aurEff, aurEff.GetBase().GetApplicationOfTarget(GetGUID()), this, ref crit_chance); return Math.Max(crit_chance, 0.0f); } @@ -1752,16 +1774,17 @@ namespace Game.Entities return false; } - public void ProcSkillsAndAuras(Unit actionTarget, ProcFlags typeMaskActor, ProcFlags typeMaskActionTarget, ProcFlagsSpellType spellTypeMask, ProcFlagsSpellPhase spellPhaseMask, ProcFlagsHit hitMask, Spell spell, DamageInfo damageInfo, HealInfo healInfo) + public static void ProcSkillsAndAuras(Unit actor, Unit actionTarget, ProcFlags typeMaskActor, ProcFlags typeMaskActionTarget, ProcFlagsSpellType spellTypeMask, ProcFlagsSpellPhase spellPhaseMask, ProcFlagsHit hitMask, Spell spell, DamageInfo damageInfo, HealInfo healInfo) { WeaponAttackType attType = damageInfo != null ? damageInfo.GetAttackType() : WeaponAttackType.BaseAttack; - if (typeMaskActor != 0) - ProcSkillsAndReactives(false, actionTarget, typeMaskActor, hitMask, attType); + if (typeMaskActor != 0 && actor != null) + actor.ProcSkillsAndReactives(false, actionTarget, typeMaskActor, hitMask, attType); if (typeMaskActionTarget != 0 && actionTarget) - actionTarget.ProcSkillsAndReactives(true, this, typeMaskActionTarget, hitMask, attType); + actionTarget.ProcSkillsAndReactives(true, actor, typeMaskActionTarget, hitMask, attType); - TriggerAurasProcOnEvent(null, null, actionTarget, typeMaskActor, typeMaskActionTarget, spellTypeMask, spellPhaseMask, hitMask, spell, damageInfo, healInfo); + if (actor != null) + actor.TriggerAurasProcOnEvent(null, null, actionTarget, typeMaskActor, typeMaskActionTarget, spellTypeMask, spellPhaseMask, hitMask, spell, damageInfo, healInfo); } void ProcSkillsAndReactives(bool isVictim, Unit procTarget, ProcFlags typeMask, ProcFlagsHit hitMask, WeaponAttackType attType) @@ -2125,30 +2148,33 @@ namespace Game.Entities return false; } - public uint SpellCriticalDamageBonus(SpellInfo spellProto, uint damage, Unit victim = null) + public static uint SpellCriticalDamageBonus(Unit caster, SpellInfo spellProto, uint damage, Unit victim = null) { // Calculate critical bonus int crit_bonus = (int)damage * 2; float crit_mod = 0.0f; - crit_mod += (GetTotalAuraMultiplierByMiscMask(AuraType.ModCritDamageBonus, (uint)spellProto.GetSchoolMask()) - 1.0f) * 100; - - if (crit_bonus != 0) - MathFunctions.AddPct(ref crit_bonus, (int)crit_mod); - - MathFunctions.AddPct(ref crit_bonus, victim.GetTotalAuraModifier(AuraType.ModCriticalDamageTakenFromCaster, aurEff => + if (caster != null) { - return aurEff.GetCasterGUID() == GetGUID(); - })); + crit_mod += (caster.GetTotalAuraMultiplierByMiscMask(AuraType.ModCritDamageBonus, (uint)spellProto.GetSchoolMask()) - 1.0f) * 100; - crit_bonus -= (int)damage; + if (crit_bonus != 0) + MathFunctions.AddPct(ref crit_bonus, (int)crit_mod); - // adds additional damage to critBonus (from talents) - Player modOwner = GetSpellModOwner(); - if (modOwner != null) - modOwner.ApplySpellMod(spellProto, SpellModOp.CritDamageAndHealing, ref crit_bonus); + MathFunctions.AddPct(ref crit_bonus, victim.GetTotalAuraModifier(AuraType.ModCriticalDamageTakenFromCaster, aurEff => + { + return aurEff.GetCasterGUID() == caster.GetGUID(); + })); - crit_bonus += (int)damage; + crit_bonus -= (int)damage; + + // adds additional damage to critBonus (from talents) + Player modOwner = caster.GetSpellModOwner(); + if (modOwner != null) + modOwner.ApplySpellMod(spellProto, SpellModOp.CritDamageAndHealing, ref crit_bonus); + + crit_bonus += (int)damage; + } return (uint)crit_bonus; } @@ -2197,46 +2223,53 @@ namespace Game.Entities return spellInfo.GetSpellSpecific() == SpellSpecificType.MagePolymorph; } - public void DealHeal(HealInfo healInfo) + public static void DealHeal(HealInfo healInfo) { int gain = 0; + Unit healer = healInfo.GetHealer(); Unit victim = healInfo.GetTarget(); uint addhealth = healInfo.GetHeal(); - if (victim.IsAIEnabled) - victim.GetAI().HealReceived(this, addhealth); + if (healer) + { + if (victim.IsAIEnabled) + victim.GetAI().HealReceived(healer, addhealth); - if (IsAIEnabled) - GetAI().HealDone(victim, addhealth); + if (healer.IsAIEnabled) + healer.GetAI().HealDone(victim, addhealth); + } if (addhealth != 0) gain = (int)victim.ModifyHealth(addhealth); // Hook for OnHeal Event uint tempGain = (uint)gain; - Global.ScriptMgr.OnHeal(this, victim, ref tempGain); + Global.ScriptMgr.OnHeal(healer, victim, ref tempGain); gain = (int)tempGain; - Unit unit = this; + Unit unit = healer; + if (healer != null && healer.IsCreature() && healer.IsTotem()) + unit = healer.GetOwner(); - if (IsTypeId(TypeId.Unit) && IsTotem()) - unit = GetOwner(); - - Player player = unit.ToPlayer(); - if (player != null) + if (unit) { - Battleground bg = player.GetBattleground(); - if (bg) - bg.UpdatePlayerScore(player, ScoreType.HealingDone, (uint)gain); + Player bgPlayer = unit.ToPlayer(); + if (bgPlayer != null) + { + Battleground bg = bgPlayer.GetBattleground(); + if (bg) + bg.UpdatePlayerScore(bgPlayer, ScoreType.HealingDone, (uint)gain); - // use the actual gain, as the overheal shall not be counted, skip gain 0 (it ignored anyway in to criteria) - if (gain != 0) - player.UpdateCriteria(CriteriaTypes.HealingDone, (uint)gain, 0, 0, victim); + // use the actual gain, as the overheal shall not be counted, skip gain 0 (it ignored anyway in to criteria) + if (gain != 0) + bgPlayer.UpdateCriteria(CriteriaTypes.HealingDone, (uint)gain, 0, 0, victim); - player.UpdateCriteria(CriteriaTypes.HighestHealCasted, addhealth); + bgPlayer.UpdateCriteria(CriteriaTypes.HighestHealCasted, addhealth); + } } - if ((player = victim.ToPlayer()) != null) + Player player = victim.ToPlayer(); + if (player != null) { player.UpdateCriteria(CriteriaTypes.TotalHealingReceived, (uint)gain); player.UpdateCriteria(CriteriaTypes.HighestHealingReceived, addhealth); @@ -2267,8 +2300,8 @@ namespace Game.Entities { // calculate heal absorb and reduce healing CalcHealAbsorb(healInfo); - DealHeal(healInfo); + SendHealSpellLog(healInfo, critical); return healInfo.GetEffectiveHeal(); } @@ -2329,84 +2362,90 @@ namespace Game.Entities SpellSchoolMask damageSchoolMask = damageInfo.schoolMask; - if (IsDamageReducedByArmor(damageSchoolMask, spellInfo)) - damage = (int)CalcArmorReducedDamage(damageInfo.attacker, victim, (uint)damage, spellInfo, attackType); - - bool blocked = false; - // Per-school calc - switch (spellInfo.DmgClass) + // Spells with SPELL_ATTR4_FIXED_DAMAGE ignore resilience because their damage is based off another spell's damage. + if (!spellInfo.HasAttribute(SpellAttr4.FixedDamage)) { - // Melee and Ranged Spells - case SpellDmgClass.Ranged: - case SpellDmgClass.Melee: - { - // Physical Damage - if (damageSchoolMask.HasAnyFlag(SpellSchoolMask.Normal)) + if (IsDamageReducedByArmor(damageSchoolMask, spellInfo)) + damage = (int)CalcArmorReducedDamage(damageInfo.attacker, victim, (uint)damage, spellInfo, attackType); + + bool blocked = false; + // Per-school calc + switch (spellInfo.DmgClass) + { + // Melee and Ranged Spells + case SpellDmgClass.Ranged: + case SpellDmgClass.Melee: { - // Spells with this attribute were already calculated in MeleeSpellHitResult - if (!spellInfo.HasAttribute(SpellAttr3.BlockableSpell)) + // Physical Damage + if (damageSchoolMask.HasAnyFlag(SpellSchoolMask.Normal)) { - // Get blocked status - blocked = IsSpellBlocked(victim, spellInfo, attackType); + // Spells with this attribute were already calculated in MeleeSpellHitResult + if (!spellInfo.HasAttribute(SpellAttr3.BlockableSpell)) + { + // Get blocked status + blocked = IsSpellBlocked(victim, spellInfo, attackType); + } } - } - if (crit) - { - damageInfo.HitInfo |= HitInfo.CriticalHit; - - // Calculate crit bonus - uint crit_bonus = (uint)damage; - // Apply crit_damage bonus for melee spells - Player modOwner = GetSpellModOwner(); - if (modOwner != null) - modOwner.ApplySpellMod(spellInfo, SpellModOp.CritDamageAndHealing, ref crit_bonus); - damage += (int)crit_bonus; - - // Increase crit damage from SPELL_AURA_MOD_CRIT_DAMAGE_BONUS - float critPctDamageMod = (GetTotalAuraMultiplierByMiscMask(AuraType.ModCritDamageBonus, (uint)spellInfo.GetSchoolMask()) - 1.0f) * 100; - - if (critPctDamageMod != 0) - MathFunctions.AddPct(ref damage, (int)critPctDamageMod); - } - - // Spell weapon based damage CAN BE crit & blocked at same time - if (blocked) - { - // double blocked amount if block is critical - float value = victim.GetBlockPercent(GetLevel()); - if (victim.IsBlockCritical()) - value *= 2; // double blocked percent - damageInfo.blocked = (uint)MathFunctions.CalculatePct(damage, value); - if (damage <= damageInfo.blocked) + if (crit) { - damageInfo.blocked = (uint)damage; - damageInfo.fullBlock = true; + damageInfo.HitInfo |= HitInfo.CriticalHit; + + // Calculate crit bonus + uint crit_bonus = (uint)damage; + // Apply crit_damage bonus for melee spells + Player modOwner = GetSpellModOwner(); + if (modOwner != null) + modOwner.ApplySpellMod(spellInfo, SpellModOp.CritDamageAndHealing, ref crit_bonus); + damage += (int)crit_bonus; + + // Increase crit damage from SPELL_AURA_MOD_CRIT_DAMAGE_BONUS + float critPctDamageMod = (GetTotalAuraMultiplierByMiscMask(AuraType.ModCritDamageBonus, (uint)spellInfo.GetSchoolMask()) - 1.0f) * 100; + + if (critPctDamageMod != 0) + MathFunctions.AddPct(ref damage, (int)critPctDamageMod); } - damage -= (int)damageInfo.blocked; + + // Spell weapon based damage CAN BE crit & blocked at same time + if (blocked) + { + // double blocked amount if block is critical + float value = victim.GetBlockPercent(GetLevel()); + if (victim.IsBlockCritical()) + value *= 2; // double blocked percent + damageInfo.blocked = (uint)MathFunctions.CalculatePct(damage, value); + if (damage <= damageInfo.blocked) + { + damageInfo.blocked = (uint)damage; + damageInfo.fullBlock = true; + } + damage -= (int)damageInfo.blocked; + } + + if (CanApplyResilience()) + ApplyResilience(victim, ref damage); + + break; } - uint dmg = (uint)damage; - ApplyResilience(victim, ref dmg); - damage = (int)dmg; - break; - } - // Magical Attacks - case SpellDmgClass.None: - case SpellDmgClass.Magic: - { - // If crit add critical bonus - if (crit) + // Magical Attacks + case SpellDmgClass.None: + case SpellDmgClass.Magic: { - damageInfo.HitInfo |= HitInfo.CriticalHit; - damage = (int)SpellCriticalDamageBonus(spellInfo, (uint)damage, victim); + // If crit add critical bonus + if (crit) + { + damageInfo.HitInfo |= HitInfo.CriticalHit; + damage = (int)SpellCriticalDamageBonus(this, spellInfo, (uint)damage, victim); + } + + if (CanApplyResilience()) + ApplyResilience(victim, ref damage); + + break; } - uint dmg = (uint)damage; - ApplyResilience(victim, ref dmg); - damage = (int)dmg; + default: break; - } - default: - break; + } } // Script Hook For CalculateSpellDamageTaken -- Allow scripts to change the Damage post class mitigation calculations @@ -2431,6 +2470,7 @@ namespace Game.Entities damageInfo.damage = dmgInfo.GetDamage(); } + public void DealSpellDamage(SpellNonMeleeDamage damageInfo, bool durabilityLoss) { if (damageInfo == null) @@ -2451,7 +2491,7 @@ namespace Game.Entities // Call default DealDamage CleanDamage cleanDamage = new(damageInfo.cleanDamage, damageInfo.absorb, WeaponAttackType.BaseAttack, MeleeHitOutcome.Normal); - DealDamage(victim, damageInfo.damage, cleanDamage, DamageEffectType.SpellDirect, damageInfo.schoolMask, damageInfo.Spell, durabilityLoss); + DealDamage(this, victim, damageInfo.damage, cleanDamage, DamageEffectType.SpellDirect, damageInfo.schoolMask, damageInfo.Spell, durabilityLoss); } public void SendSpellNonMeleeDamageLog(SpellNonMeleeDamage log) @@ -4181,6 +4221,7 @@ namespace Game.Entities else aurApp._HandleEffect(effIndex, true); } + // handles effects of aura application // should be done after registering aura in lists public void _ApplyAura(AuraApplication aurApp, uint effMask) @@ -4219,7 +4260,6 @@ namespace Game.Entities if (aurApp.HasRemoveMode()) return; - aura.HandleAuraSpecificPeriodics(aurApp, caster); aura.HandleAuraSpecificMods(aurApp, caster, true, false); // apply effects of the aura @@ -4229,6 +4269,7 @@ namespace Game.Entities aurApp._HandleEffect(i, true); } } + public void _AddAura(UnitAura aura, Unit caster) { Cypher.Assert(!m_cleanupDone); @@ -4261,6 +4302,7 @@ namespace Game.Entities } } } + public Aura _TryStackingOrRefreshingExistingAura(SpellInfo newAura, uint effMask, Unit caster, int[] baseAmount = null, Item castItem = null, ObjectGuid casterGUID = default, bool resetPeriodicTimer = true, ObjectGuid castItemGuid = default, uint castItemId = 0, int castItemLevel = -1) { Cypher.Assert(!casterGUID.IsEmpty() || caster); diff --git a/Source/Game/Entities/Unit/Unit.cs b/Source/Game/Entities/Unit/Unit.cs index 5165bf15c..b3a9c1d17 100644 --- a/Source/Game/Entities/Unit/Unit.cs +++ b/Source/Game/Entities/Unit/Unit.cs @@ -2388,11 +2388,12 @@ namespace Game.Entities RemoveDynamicUpdateFieldValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.ChannelObjects), index); } - public bool IsDamageReducedByArmor(SpellSchoolMask schoolMask, SpellInfo spellInfo = null, sbyte effIndex = -1) + public static bool IsDamageReducedByArmor(SpellSchoolMask schoolMask, SpellInfo spellInfo = null, sbyte effIndex = -1) { // only physical spells damage gets reduced by armor if ((schoolMask & SpellSchoolMask.Normal) == 0) return false; + if (spellInfo != null) { // there are spells with no specific attribute but they have "ignores armor" in tooltip @@ -2411,6 +2412,7 @@ namespace Game.Entities } } } + return true; } @@ -2542,7 +2544,7 @@ namespace Game.Entities void StartReactiveTimer(ReactiveType reactive) { m_reactiveTimer[reactive] = 4000; } - void DealDamageMods(Unit victim, ref uint damage) + public static void DealDamageMods(Unit attacker, Unit victim, ref uint damage) { if (victim == null || !victim.IsAlive() || victim.HasUnitState(UnitState.InFlight) || (victim.IsTypeId(TypeId.Unit) && victim.ToCreature().IsInEvadeMode())) @@ -2550,7 +2552,8 @@ namespace Game.Entities damage = 0; } } - public void DealDamageMods(Unit victim, ref uint damage, ref uint absorb) + + public static void DealDamageMods(Unit attacker, Unit victim, ref uint damage, ref uint absorb) { if (victim == null || !victim.IsAlive() || victim.HasUnitState(UnitState.InFlight) || (victim.IsTypeId(TypeId.Unit) && victim.ToCreature().IsEvadingAttacks())) @@ -2560,156 +2563,22 @@ namespace Game.Entities return; } - damage = (uint)(damage * GetDamageMultiplierForTarget(victim)); + if (attacker != null) + damage *= (uint)attacker.GetDamageMultiplierForTarget(victim); } - void DealMeleeDamage(CalcDamageInfo damageInfo, bool durabilityLoss) - { - Unit victim = damageInfo.target; - if (!victim.IsAlive() || victim.HasUnitState(UnitState.InFlight) || (victim.IsTypeId(TypeId.Unit) && victim.ToCreature().IsEvadingAttacks())) - return; - - // Hmmmm dont like this emotes client must by self do all animations - if (damageInfo.HitInfo.HasAnyFlag(HitInfo.CriticalHit)) - victim.HandleEmoteCommand(Emote.OneshotWoundCritical); - if (damageInfo.blocked_amount != 0 && damageInfo.TargetState != VictimState.Blocks) - victim.HandleEmoteCommand(Emote.OneshotParryShield); - - if (damageInfo.TargetState == VictimState.Parry && - (!IsTypeId(TypeId.Unit) || !ToCreature().GetCreatureTemplate().FlagsExtra.HasAnyFlag(CreatureFlagsExtra.NoParryHasten))) - { - // Get attack timers - float offtime = victim.GetAttackTimer(WeaponAttackType.OffAttack); - float basetime = victim.GetAttackTimer(WeaponAttackType.BaseAttack); - // Reduce attack time - if (victim.HaveOffhandWeapon() && offtime < basetime) - { - float percent20 = victim.GetBaseAttackTime(WeaponAttackType.OffAttack) * 0.20f; - float percent60 = 3.0f * percent20; - if (offtime > percent20 && offtime <= percent60) - victim.SetAttackTimer(WeaponAttackType.OffAttack, (uint)percent20); - else if (offtime > percent60) - { - offtime -= 2.0f * percent20; - victim.SetAttackTimer(WeaponAttackType.OffAttack, (uint)offtime); - } - } - else - { - float percent20 = victim.GetBaseAttackTime(WeaponAttackType.BaseAttack) * 0.20f; - float percent60 = 3.0f * percent20; - if (basetime > percent20 && basetime <= percent60) - victim.SetAttackTimer(WeaponAttackType.BaseAttack, (uint)percent20); - else if (basetime > percent60) - { - basetime -= 2.0f * percent20; - victim.SetAttackTimer(WeaponAttackType.BaseAttack, (uint)basetime); - } - } - } - - // Call default DealDamage - CleanDamage cleanDamage = new(damageInfo.cleanDamage, damageInfo.absorb, damageInfo.attackType, damageInfo.hitOutCome); - DealDamage(victim, damageInfo.damage, cleanDamage, DamageEffectType.Direct, (SpellSchoolMask)damageInfo.damageSchoolMask, null, durabilityLoss); - - // If this is a creature and it attacks from behind it has a probability to daze it's victim - if ((damageInfo.hitOutCome == MeleeHitOutcome.Crit || damageInfo.hitOutCome == MeleeHitOutcome.Crushing || damageInfo.hitOutCome == MeleeHitOutcome.Normal || damageInfo.hitOutCome == MeleeHitOutcome.Glancing) && - !IsTypeId(TypeId.Player) && !ToCreature().IsControlledByPlayer() && !victim.HasInArc(MathFunctions.PI, this) - && (victim.IsTypeId(TypeId.Player) || !victim.ToCreature().IsWorldBoss()) && !victim.IsVehicle()) - { - // 20% base chance - float chance = 20.0f; - - // there is a newbie protection, at level 10 just 7% base chance; assuming linear function - if (victim.GetLevel() < 30) - chance = 0.65f * victim.GetLevelForTarget(this) + 0.5f; - - uint victimDefense = victim.GetMaxSkillValueForLevel(this); - uint attackerMeleeSkill = GetMaxSkillValueForLevel(); - - chance *= attackerMeleeSkill / (float)victimDefense * 0.16f; - - // -probability is between 0% and 40% - MathFunctions.RoundToInterval(ref chance, 0.0f, 40.0f); - - if (RandomHelper.randChance(chance)) - CastSpell(victim, 1604, true); - } - - if (IsTypeId(TypeId.Player)) - { - DamageInfo dmgInfo = new(damageInfo); - ToPlayer().CastItemCombatSpell(dmgInfo); - } - - // Do effect if any damage done to target - if (damageInfo.damage != 0) - { - // We're going to call functions which can modify content of the list during iteration over it's elements - // Let's copy the list so we can prevent iterator invalidation - var vDamageShieldsCopy = victim.GetAuraEffectsByType(AuraType.DamageShield); - foreach (var dmgShield in vDamageShieldsCopy) - { - SpellInfo spellInfo = dmgShield.GetSpellInfo(); - - // Damage shield can be resisted... - var missInfo = victim.SpellHitResult(this, spellInfo, false); - if (missInfo != SpellMissInfo.None) - { - victim.SendSpellMiss(this, spellInfo.Id, missInfo); - continue; - } - - // ...or immuned - if (IsImmunedToDamage(spellInfo)) - { - victim.SendSpellDamageImmune(this, spellInfo.Id, false); - continue; - } - - uint damage = (uint)dmgShield.GetAmount(); - Unit caster = dmgShield.GetCaster(); - if (caster) - { - damage = caster.SpellDamageBonusDone(this, spellInfo, damage, DamageEffectType.SpellDirect, dmgShield.GetSpellEffectInfo()); - damage = SpellDamageBonusTaken(caster, spellInfo, damage, DamageEffectType.SpellDirect, dmgShield.GetSpellEffectInfo()); - } - - DamageInfo damageInfo1 = new(this, victim, damage, spellInfo, spellInfo.GetSchoolMask(), DamageEffectType.SpellDirect, WeaponAttackType.BaseAttack); - victim.CalcAbsorbResist(damageInfo1); - damage = damageInfo1.GetDamage(); - - victim.DealDamageMods(this, ref damage); - - SpellDamageShield damageShield = new(); - damageShield.Attacker = victim.GetGUID(); - damageShield.Defender = GetGUID(); - damageShield.SpellID = spellInfo.Id; - damageShield.TotalDamage = damage; - damageShield.OriginalDamage = (int)damageInfo.originalDamage; - damageShield.OverKill = (uint)Math.Max(damage - GetHealth(), 0); - damageShield.SchoolMask = (uint)spellInfo.SchoolMask; - damageShield.LogAbsorbed = damageInfo1.GetAbsorb(); - - victim.DealDamage(this, damage, null, DamageEffectType.SpellDirect, spellInfo.GetSchoolMask(), spellInfo, true); - damageShield.LogData.Initialize(this); - - victim.SendCombatLogMessage(damageShield); - } - } - } - public uint DealDamage(Unit victim, uint damage, CleanDamage cleanDamage = null, DamageEffectType damagetype = DamageEffectType.Direct, SpellSchoolMask damageSchoolMask = SpellSchoolMask.Normal, SpellInfo spellProto = null, bool durabilityLoss = true) + public static uint DealDamage(Unit attacker, Unit victim, uint damage, CleanDamage cleanDamage = null, DamageEffectType damagetype = DamageEffectType.Direct, SpellSchoolMask damageSchoolMask = SpellSchoolMask.Normal, SpellInfo spellProto = null, bool durabilityLoss = true) { if (victim.IsAIEnabled) - victim.GetAI().DamageTaken(this, ref damage); + victim.GetAI().DamageTaken(attacker, ref damage); - if (IsAIEnabled) - GetAI().DamageDealt(victim, ref damage, damagetype); + if (attacker != null && attacker.IsAIEnabled) + attacker.GetAI().DamageDealt(victim, ref damage, damagetype); // Hook for OnDamage Event - Global.ScriptMgr.OnDamage(this, victim, ref damage); + Global.ScriptMgr.OnDamage(attacker, victim, ref damage); - if (victim.IsTypeId(TypeId.Player) && this != victim) + if (victim.IsTypeId(TypeId.Player) && attacker != victim) { // Signal to pets that their owner was attacked - except when DOT. if (damagetype != DamageEffectType.DOT) @@ -2719,7 +2588,7 @@ namespace Game.Entities Creature cControlled = controlled.ToCreature(); if (cControlled != null) if (cControlled.IsAIEnabled) - cControlled.GetAI().OwnerAttackedBy(this); + cControlled.GetAI().OwnerAttackedBy(attacker); } } @@ -2740,7 +2609,7 @@ namespace Game.Entities if (damage == 0 && damagetype != DamageEffectType.DOT && cleanDamage != null && cleanDamage.absorbed_damage != 0) { - if (victim != this && victim.IsPlayer()) + if (victim != attacker && victim.IsPlayer()) { Spell spell = victim.GetCurrentSpell(CurrentSpellTypes.Generic); if (spell != null) @@ -2772,35 +2641,36 @@ namespace Game.Entities uint share = MathFunctions.CalculatePct(damage, aura.GetAmount()); // @todo check packets if damage is done by victim, or by attacker of victim - DealDamageMods(shareDamageTarget, ref share); - DealDamage(shareDamageTarget, share, null, DamageEffectType.NoDamage, spell.GetSchoolMask(), spell, false); + DealDamageMods(attacker, shareDamageTarget, ref share); + DealDamage(attacker, shareDamageTarget, share, null, DamageEffectType.NoDamage, spell.GetSchoolMask(), spell, false); } } // Rage from Damage made (only from direct weapon damage) - if (cleanDamage != null && (cleanDamage.attackType == WeaponAttackType.BaseAttack || cleanDamage.attackType == WeaponAttackType.OffAttack) && damagetype == DamageEffectType.Direct && this != victim && GetPowerType() == PowerType.Rage) + if (attacker != null && cleanDamage != null && (cleanDamage.attackType == WeaponAttackType.BaseAttack || cleanDamage.attackType == WeaponAttackType.OffAttack) && damagetype == DamageEffectType.Direct && attacker != victim && attacker.GetPowerType() == PowerType.Rage) { - uint rage = (uint)(GetBaseAttackTime(cleanDamage.attackType) / 1000.0f * 1.75f); + uint rage = (uint)(attacker.GetBaseAttackTime(cleanDamage.attackType) / 1000.0f * 1.75f); if (cleanDamage.attackType == WeaponAttackType.OffAttack) rage /= 2; - RewardRage(rage); + + attacker.RewardRage(rage); } if (damage == 0) return 0; - Log.outDebug(LogFilter.Unit, "DealDamageStart"); - uint health = (uint)victim.GetHealth(); - Log.outDebug(LogFilter.Unit, "Unit {0} dealt {1} damage to unit {2}", GetGUID(), damage, victim.GetGUID()); // duel ends when player has 1 or less hp bool duel_hasEnded = false; bool duel_wasMounted = false; - if (victim.IsTypeId(TypeId.Player) && victim.ToPlayer().duel != null && damage >= (health - 1)) + if (victim.IsPlayer() && victim.ToPlayer().duel != null && damage >= (health - 1)) { + if (!attacker) + return 0; + // prevent kill only if killed in duel and killed by opponent or opponent controlled creature - if (victim.ToPlayer().duel.opponent == this || victim.ToPlayer().duel.opponent.GetGUID() == GetOwnerGUID()) + if (victim.ToPlayer().duel.opponent == attacker || victim.ToPlayer().duel.opponent.GetGUID() == attacker.GetOwnerGUID()) damage = health - 1; duel_hasEnded = true; @@ -2810,8 +2680,11 @@ namespace Game.Entities Player victimRider = victim.GetCharmer().ToPlayer(); if (victimRider != null && victimRider.duel != null && victimRider.duel.isMounted) { + if (!attacker) + return 0; + // prevent kill only if killed in duel and killed by opponent or opponent controlled creature - if (victimRider.duel.opponent == this || victimRider.duel.opponent.GetGUID() == GetCharmerGUID()) + if (victimRider.duel.opponent == attacker || victimRider.duel.opponent.GetGUID() == attacker.GetCharmerGUID()) damage = health - 1; duel_wasMounted = true; @@ -2819,35 +2692,36 @@ namespace Game.Entities } } - if (IsTypeId(TypeId.Player) && this != victim) + if (attacker != null && attacker != victim) { - Player killer = ToPlayer(); - - // in bg, count dmg if victim is also a player - if (victim.IsTypeId(TypeId.Player)) + Player killer = attacker.ToPlayer(); + if (killer != null) { - Battleground bg = killer.GetBattleground(); - if (bg) - bg.UpdatePlayerScore(killer, ScoreType.DamageDone, damage); + // in bg, count dmg if victim is also a player + if (victim.IsPlayer()) + { + Battleground bg = killer.GetBattleground(); + if (bg != null) + bg.UpdatePlayerScore(killer, ScoreType.DamageDone, damage); + } + + killer.UpdateCriteria(CriteriaTypes.DamageDone, health > damage ? damage : health, 0, 0, victim); + killer.UpdateCriteria(CriteriaTypes.HighestHitDealt, damage); } - - killer.UpdateCriteria(CriteriaTypes.DamageDone, health > damage ? damage : health, 0, 0, victim); - killer.UpdateCriteria(CriteriaTypes.HighestHitDealt, damage); } - if (victim.IsTypeId(TypeId.Player)) - { + if (victim.IsPlayer()) victim.ToPlayer().UpdateCriteria(CriteriaTypes.HighestHitReceived, damage); - } - damage /= (uint)victim.GetHealthMultiplierForTarget(this); + if (attacker != null) + damage /= (uint)victim.GetHealthMultiplierForTarget(attacker); if (victim.GetTypeId() != TypeId.Player && (!victim.IsControlledByPlayer() || victim.IsVehicle())) { if (!victim.ToCreature().HasLootRecipient()) - victim.ToCreature().SetLootRecipient(this); + victim.ToCreature().SetLootRecipient(attacker); - if (IsControlledByPlayer()) + if (attacker == null || attacker.IsControlledByPlayer()) victim.ToCreature().LowerPlayerDamageReq(health < damage ? health : damage); } @@ -2858,15 +2732,13 @@ namespace Game.Entities { killed = true; - Log.outDebug(LogFilter.Unit, "DealDamage: victim just died"); - - if (victim.IsTypeId(TypeId.Player) && victim != this) + if (victim.IsPlayer() && victim != attacker) victim.ToPlayer().UpdateCriteria(CriteriaTypes.TotalDamageReceived, health); if (damagetype != DamageEffectType.NoDamage && damagetype != DamageEffectType.Self && victim.HasAuraType(AuraType.SchoolAbsorbOverkill)) { var vAbsorbOverkill = victim.GetAuraEffectsByType(AuraType.SchoolAbsorbOverkill); - DamageInfo damageInfo = new(this, victim, damage, spellProto, damageSchoolMask, damagetype, cleanDamage != null ? cleanDamage.attackType : WeaponAttackType.BaseAttack); + DamageInfo damageInfo = new(attacker, victim, damage, spellProto, damageSchoolMask, damagetype, cleanDamage != null ? cleanDamage.attackType : WeaponAttackType.BaseAttack); foreach (var absorbAurEff in vAbsorbOverkill) { @@ -2909,7 +2781,7 @@ namespace Game.Entities if (currentAbsorb != 0) { SpellAbsorbLog absorbLog = new(); - absorbLog.Attacker = GetGUID(); + absorbLog.Attacker = attacker != null ? attacker.GetGUID() : ObjectGuid.Empty; absorbLog.Victim = victim.GetGUID(); absorbLog.Caster = baseAura.GetCasterGUID(); absorbLog.AbsorbedSpellID = spellProto != null ? spellProto.Id : 0; @@ -2917,7 +2789,7 @@ namespace Game.Entities absorbLog.Absorbed = currentAbsorb; absorbLog.OriginalDamage = damageInfo.GetOriginalDamage(); absorbLog.LogData.Initialize(victim); - SendCombatLogMessage(absorbLog); + victim.SendCombatLogMessage(absorbLog); } } @@ -2926,11 +2798,9 @@ namespace Game.Entities } if (killed) - Kill(victim, durabilityLoss, skipSettingDeathState); + Kill(attacker, victim, durabilityLoss, skipSettingDeathState); else { - Log.outDebug(LogFilter.Unit, "DealDamageAlive"); - if (victim.IsTypeId(TypeId.Player)) victim.ToPlayer().UpdateCriteria(CriteriaTypes.TotalDamageReceived, damage); @@ -2945,7 +2815,8 @@ namespace Game.Entities if (damagetype != DamageEffectType.DOT && damage > 0 && !victim.GetOwnerGUID().IsPlayer() && (spellProto == null || !spellProto.HasAura(AuraType.DamageShield))) victim.ToCreature().SetLastDamagedTime(GameTime.GetGameTime() + SharedConst.MaxAggroResetTime); - victim.GetThreatManager().AddThreat(this, damage, spellProto); + if (attacker != null) + victim.GetThreatManager().AddThreat(attacker, damage, spellProto); } else // victim is a player { @@ -2957,19 +2828,19 @@ namespace Game.Entities } } - if (IsTypeId(TypeId.Player)) + if (attacker != null && attacker.IsPlayer()) { // random durability for items (HIT DONE) if (RandomHelper.randChance(WorldConfig.GetFloatValue(WorldCfg.RateDurabilityLossDamage))) { byte slot = (byte)RandomHelper.IRand(0, EquipmentSlot.End - 1); - ToPlayer().DurabilityPointLossForEquipSlot(slot); + attacker.ToPlayer().DurabilityPointLossForEquipSlot(slot); } } if (damagetype != DamageEffectType.NoDamage) { - if (victim != this && (spellProto == null || !(spellProto.HasAttribute(SpellAttr7.NoPushbackOnDamage) || spellProto.HasAttribute(SpellAttr3.TreatAsPeriodic)))) + if (victim != attacker && (spellProto == null || !(spellProto.HasAttribute(SpellAttr7.NoPushbackOnDamage) || spellProto.HasAttribute(SpellAttr3.TreatAsPeriodic)))) { if (damagetype != DamageEffectType.DOT) { @@ -3044,11 +2915,147 @@ namespace Game.Entities } } - Log.outDebug(LogFilter.Unit, "DealDamageEnd returned {0} damage", damage); - return damage; } + void DealMeleeDamage(CalcDamageInfo damageInfo, bool durabilityLoss) + { + Unit victim = damageInfo.target; + + if (!victim.IsAlive() || victim.HasUnitState(UnitState.InFlight) || (victim.IsTypeId(TypeId.Unit) && victim.ToCreature().IsEvadingAttacks())) + return; + + // Hmmmm dont like this emotes client must by self do all animations + if (damageInfo.HitInfo.HasAnyFlag(HitInfo.CriticalHit)) + victim.HandleEmoteCommand(Emote.OneshotWoundCritical); + if (damageInfo.blocked_amount != 0 && damageInfo.TargetState != VictimState.Blocks) + victim.HandleEmoteCommand(Emote.OneshotParryShield); + + if (damageInfo.TargetState == VictimState.Parry && + (!IsTypeId(TypeId.Unit) || !ToCreature().GetCreatureTemplate().FlagsExtra.HasAnyFlag(CreatureFlagsExtra.NoParryHasten))) + { + // Get attack timers + float offtime = victim.GetAttackTimer(WeaponAttackType.OffAttack); + float basetime = victim.GetAttackTimer(WeaponAttackType.BaseAttack); + // Reduce attack time + if (victim.HaveOffhandWeapon() && offtime < basetime) + { + float percent20 = victim.GetBaseAttackTime(WeaponAttackType.OffAttack) * 0.20f; + float percent60 = 3.0f * percent20; + if (offtime > percent20 && offtime <= percent60) + victim.SetAttackTimer(WeaponAttackType.OffAttack, (uint)percent20); + else if (offtime > percent60) + { + offtime -= 2.0f * percent20; + victim.SetAttackTimer(WeaponAttackType.OffAttack, (uint)offtime); + } + } + else + { + float percent20 = victim.GetBaseAttackTime(WeaponAttackType.BaseAttack) * 0.20f; + float percent60 = 3.0f * percent20; + if (basetime > percent20 && basetime <= percent60) + victim.SetAttackTimer(WeaponAttackType.BaseAttack, (uint)percent20); + else if (basetime > percent60) + { + basetime -= 2.0f * percent20; + victim.SetAttackTimer(WeaponAttackType.BaseAttack, (uint)basetime); + } + } + } + + // Call default DealDamage + CleanDamage cleanDamage = new(damageInfo.cleanDamage, damageInfo.absorb, damageInfo.attackType, damageInfo.hitOutCome); + DealDamage(this, victim, damageInfo.damage, cleanDamage, DamageEffectType.Direct, (SpellSchoolMask)damageInfo.damageSchoolMask, null, durabilityLoss); + + // If this is a creature and it attacks from behind it has a probability to daze it's victim + if ((damageInfo.hitOutCome == MeleeHitOutcome.Crit || damageInfo.hitOutCome == MeleeHitOutcome.Crushing || damageInfo.hitOutCome == MeleeHitOutcome.Normal || damageInfo.hitOutCome == MeleeHitOutcome.Glancing) && + !IsTypeId(TypeId.Player) && !ToCreature().IsControlledByPlayer() && !victim.HasInArc(MathFunctions.PI, this) + && (victim.IsTypeId(TypeId.Player) || !victim.ToCreature().IsWorldBoss()) && !victim.IsVehicle()) + { + // 20% base chance + float chance = 20.0f; + + // there is a newbie protection, at level 10 just 7% base chance; assuming linear function + if (victim.GetLevel() < 30) + chance = 0.65f * victim.GetLevelForTarget(this) + 0.5f; + + uint victimDefense = victim.GetMaxSkillValueForLevel(this); + uint attackerMeleeSkill = GetMaxSkillValueForLevel(); + + chance *= attackerMeleeSkill / (float)victimDefense * 0.16f; + + // -probability is between 0% and 40% + MathFunctions.RoundToInterval(ref chance, 0.0f, 40.0f); + + if (RandomHelper.randChance(chance)) + CastSpell(victim, 1604, true); + } + + if (IsTypeId(TypeId.Player)) + { + DamageInfo dmgInfo = new(damageInfo); + ToPlayer().CastItemCombatSpell(dmgInfo); + } + + // Do effect if any damage done to target + if (damageInfo.damage != 0) + { + // We're going to call functions which can modify content of the list during iteration over it's elements + // Let's copy the list so we can prevent iterator invalidation + var vDamageShieldsCopy = victim.GetAuraEffectsByType(AuraType.DamageShield); + foreach (var dmgShield in vDamageShieldsCopy) + { + SpellInfo spellInfo = dmgShield.GetSpellInfo(); + + // Damage shield can be resisted... + var missInfo = victim.SpellHitResult(this, spellInfo, false); + if (missInfo != SpellMissInfo.None) + { + victim.SendSpellMiss(this, spellInfo.Id, missInfo); + continue; + } + + // ...or immuned + if (IsImmunedToDamage(spellInfo)) + { + victim.SendSpellDamageImmune(this, spellInfo.Id, false); + continue; + } + + uint damage = (uint)dmgShield.GetAmount(); + Unit caster = dmgShield.GetCaster(); + if (caster) + { + damage = caster.SpellDamageBonusDone(this, spellInfo, damage, DamageEffectType.SpellDirect, dmgShield.GetSpellEffectInfo()); + damage = SpellDamageBonusTaken(caster, spellInfo, damage, DamageEffectType.SpellDirect, dmgShield.GetSpellEffectInfo()); + } + + DamageInfo damageInfo1 = new(this, victim, damage, spellInfo, spellInfo.GetSchoolMask(), DamageEffectType.SpellDirect, WeaponAttackType.BaseAttack); + CalcAbsorbResist(damageInfo1); + damage = damageInfo1.GetDamage(); + + DealDamageMods(victim, this, ref damage); + + SpellDamageShield damageShield = new(); + damageShield.Attacker = victim.GetGUID(); + damageShield.Defender = GetGUID(); + damageShield.SpellID = spellInfo.Id; + damageShield.TotalDamage = damage; + damageShield.OriginalDamage = (int)damageInfo.originalDamage; + damageShield.OverKill = (uint)Math.Max(damage - GetHealth(), 0); + damageShield.SchoolMask = (uint)spellInfo.SchoolMask; + damageShield.LogAbsorbed = damageInfo1.GetAbsorb(); + + DealDamage(victim, this, damage, null, DamageEffectType.SpellDirect, spellInfo.GetSchoolMask(), spellInfo, true); + damageShield.LogData.Initialize(this); + + victim.SendCombatLogMessage(damageShield); + } + } + } + + public long ModifyHealth(long dVal) { long gain = 0; @@ -3338,7 +3345,7 @@ namespace Game.Entities RemovePvpFlag(UnitPVPStateFlags.PvP); } - uint CalcSpellResistedDamage(Unit attacker, Unit victim, uint damage, SpellSchoolMask schoolMask, SpellInfo spellInfo) + static uint CalcSpellResistedDamage(Unit attacker, Unit victim, uint damage, SpellSchoolMask schoolMask, SpellInfo spellInfo) { // Magic damage, check for resists if (!Convert.ToBoolean(schoolMask & SpellSchoolMask.Magic)) @@ -3359,7 +3366,7 @@ namespace Game.Entities return 0; } - float averageResist = CalculateAverageResistReduction(schoolMask, victim, spellInfo); + float averageResist = CalculateAverageResistReduction(attacker, schoolMask, victim, spellInfo); float[] discreteResistProbability = new float[11]; if (averageResist <= 0.1f) @@ -3387,7 +3394,8 @@ namespace Game.Entities { int ignoredResistance = 0; - ignoredResistance += GetTotalAuraModifierByMiscMask(AuraType.ModIgnoreTargetResist, (int)schoolMask); + if (attacker != null) + ignoredResistance += attacker.GetTotalAuraModifierByMiscMask(AuraType.ModIgnoreTargetResist, (int)schoolMask); ignoredResistance = Math.Min(ignoredResistance, 100); MathFunctions.ApplyPct(ref damageResisted, 100 - ignoredResistance); @@ -3407,20 +3415,23 @@ namespace Game.Entities return (uint)damageResisted; } - float CalculateAverageResistReduction(SpellSchoolMask schoolMask, Unit victim, SpellInfo spellInfo) + static float CalculateAverageResistReduction(Unit attacker, SpellSchoolMask schoolMask, Unit victim, SpellInfo spellInfo = null) { - float victimResistance = (float)victim.GetResistance(schoolMask); + float victimResistance = victim.GetResistance(schoolMask); - // pets inherit 100% of masters penetration - // excluding traps - Player player = GetSpellModOwner(); - if (player != null && GetEntry() != SharedConst.WorldTrigger) + if (attacker != null) { - victimResistance += (float)player.GetTotalAuraModifierByMiscMask(AuraType.ModTargetResistance, (int)schoolMask); - victimResistance -= (float)player.GetSpellPenetrationItemMod(); + // pets inherit 100% of masters penetration + // excluding traps + Player player = attacker.GetSpellModOwner(); + if (player != null && attacker.GetEntry() != SharedConst.WorldTrigger) + { + victimResistance += player.GetTotalAuraModifierByMiscMask(AuraType.ModTargetResistance, (int)schoolMask); + victimResistance -= player.GetSpellPenetrationItemMod(); + } + else + victimResistance += attacker.GetTotalAuraModifierByMiscMask(AuraType.ModTargetResistance, (int)schoolMask); } - else - victimResistance += (float)GetTotalAuraModifierByMiscMask(AuraType.ModTargetResistance, (int)schoolMask); // holy resistance exists in pve and comes from level difference, ignore template values if (schoolMask.HasAnyFlag(SpellSchoolMask.Holy)) @@ -3433,12 +3444,12 @@ namespace Game.Entities victimResistance = Math.Max(victimResistance, 0.0f); // level-based resistance does not apply to binary spells, and cannot be overcome by spell penetration - if (spellInfo == null || !spellInfo.HasAttribute(SpellCustomAttributes.BinarySpell)) - victimResistance += Math.Max(((float)victim.GetLevelForTarget(this) - (float)GetLevelForTarget(victim)) * 5.0f, 0.0f); + if (attacker != null && (spellInfo == null || !spellInfo.HasAttribute(SpellCustomAttributes.BinarySpell))) + victimResistance += Math.Max(((float)victim.GetLevelForTarget(attacker) - (float)attacker.GetLevelForTarget(victim)) * 5.0f, 0.0f); uint bossLevel = 83; float bossResistanceConstant = 510.0f; - uint level = victim.GetLevelForTarget(this); + uint level = attacker != null ? victim.GetLevelForTarget(attacker) : attacker.GetLevel(); float resistanceConstant; if (level == bossLevel) @@ -3449,7 +3460,7 @@ namespace Game.Entities return victimResistance / (victimResistance + resistanceConstant); } - public void CalcAbsorbResist(DamageInfo damageInfo) + public static void CalcAbsorbResist(DamageInfo damageInfo) { if (!damageInfo.GetVictim() || !damageInfo.GetVictim().IsAlive() || damageInfo.GetDamage() == 0) return; @@ -3458,7 +3469,11 @@ namespace Game.Entities damageInfo.ResistDamage(resistedDamage); // Ignore Absorption Auras - float auraAbsorbMod = GetMaxPositiveAuraModifierByMiscMask(AuraType.ModTargetAbsorbSchool, (uint)damageInfo.GetSchoolMask()); + float auraAbsorbMod = 0f; + + Unit attacker = damageInfo.GetAttacker(); + if (attacker != null) + auraAbsorbMod = attacker.GetMaxPositiveAuraModifierByMiscMask(AuraType.ModTargetAbsorbSchool, (uint)damageInfo.GetSchoolMask()); MathFunctions.RoundToInterval(ref auraAbsorbMod, 0.0f, 100.0f); @@ -3522,7 +3537,7 @@ namespace Game.Entities if (currentAbsorb != 0) { SpellAbsorbLog absorbLog = new(); - absorbLog.Attacker = damageInfo.GetAttacker().GetGUID(); + absorbLog.Attacker = damageInfo.GetAttacker() != null ? damageInfo.GetAttacker().GetGUID() : ObjectGuid.Empty; absorbLog.Victim = damageInfo.GetVictim().GetGUID(); absorbLog.Caster = absorbAurEff.GetBase().GetCasterGUID(); absorbLog.AbsorbedSpellID = damageInfo.GetSpellInfo() != null ? damageInfo.GetSpellInfo().Id : 0; @@ -3530,7 +3545,7 @@ namespace Game.Entities absorbLog.Absorbed = currentAbsorb; absorbLog.OriginalDamage = damageInfo.GetOriginalDamage(); absorbLog.LogData.Initialize(damageInfo.GetVictim()); - SendCombatLogMessage(absorbLog); + damageInfo.GetVictim().SendCombatLogMessage(absorbLog); } } @@ -3596,7 +3611,7 @@ namespace Game.Entities if (currentAbsorb != 0) { SpellAbsorbLog absorbLog = new(); - absorbLog.Attacker = damageInfo.GetAttacker().GetGUID(); + absorbLog.Attacker = damageInfo.GetAttacker() != null ? damageInfo.GetAttacker().GetGUID() : ObjectGuid.Empty; absorbLog.Victim = damageInfo.GetVictim().GetGUID(); absorbLog.Caster = absorbAurEff.GetBase().GetCasterGUID(); absorbLog.AbsorbedSpellID = damageInfo.GetSpellInfo() != null ? damageInfo.GetSpellInfo().Id : 0; @@ -3604,14 +3619,14 @@ namespace Game.Entities absorbLog.Absorbed = currentAbsorb; absorbLog.OriginalDamage = damageInfo.GetOriginalDamage(); absorbLog.LogData.Initialize(damageInfo.GetVictim()); - SendCombatLogMessage(absorbLog); + damageInfo.GetVictim().SendCombatLogMessage(absorbLog); } } damageInfo.ModifyDamage(absorbIgnoringDamage); // split damage auras - only when not damaging self - if (damageInfo.GetVictim() != this) + if (damageInfo.GetVictim() != damageInfo.GetAttacker()) { // We're going to call functions which can modify content of the list during iteration over it's elements // Let's copy the list so we can prevent iterator invalidation @@ -3652,23 +3667,23 @@ namespace Game.Entities } uint split_absorb = 0; - DealDamageMods(caster, ref splitDamage, ref split_absorb); + DealDamageMods(damageInfo.GetAttacker(), caster, ref splitDamage, ref split_absorb); - SpellNonMeleeDamage log = new(this, caster, itr.GetSpellInfo(), itr.GetBase().GetSpellVisual(), damageInfo.GetSchoolMask(), itr.GetBase().GetCastGUID()); + SpellNonMeleeDamage log = new(damageInfo.GetAttacker(), caster, itr.GetSpellInfo(), itr.GetBase().GetSpellVisual(), damageInfo.GetSchoolMask(), itr.GetBase().GetCastGUID()); CleanDamage cleanDamage = new(splitDamage, 0, WeaponAttackType.BaseAttack, MeleeHitOutcome.Normal); - DealDamage(caster, splitDamage, cleanDamage, DamageEffectType.Direct, damageInfo.GetSchoolMask(), itr.GetSpellInfo(), false); + DealDamage(damageInfo.GetAttacker(), caster, splitDamage, cleanDamage, DamageEffectType.Direct, damageInfo.GetSchoolMask(), itr.GetSpellInfo(), false); log.damage = splitDamage; log.originalDamage = splitDamage; log.absorb = split_absorb; - SendSpellNonMeleeDamageLog(log); + caster.SendSpellNonMeleeDamageLog(log); // break 'Fear' and similar auras - ProcSkillsAndAuras(caster, ProcFlags.None, ProcFlags.TakenSpellMagicDmgClassNeg, ProcFlagsSpellType.Damage, ProcFlagsSpellPhase.Hit, ProcFlagsHit.None, null, damageInfo, null); + ProcSkillsAndAuras(damageInfo.GetAttacker(), caster, ProcFlags.None, ProcFlags.TakenSpellMagicDmgClassNeg, ProcFlagsSpellType.Damage, ProcFlagsSpellPhase.Hit, ProcFlagsHit.None, null, damageInfo, null); } } } - public void CalcHealAbsorb(HealInfo healInfo) + public static void CalcHealAbsorb(HealInfo healInfo) { if (healInfo.GetHeal() == 0) return; @@ -3728,61 +3743,67 @@ namespace Game.Entities } } - public uint CalcArmorReducedDamage(Unit attacker, Unit victim, uint damage, SpellInfo spellInfo, WeaponAttackType attackType = WeaponAttackType.Max) + public static uint CalcArmorReducedDamage(Unit attacker, Unit victim, uint damage, SpellInfo spellInfo, WeaponAttackType attackType = WeaponAttackType.Max, uint attackerLevel = 0) { float armor = victim.GetArmor(); - armor *= victim.GetArmorMultiplierForTarget(attacker); - - // bypass enemy armor by SPELL_AURA_BYPASS_ARMOR_FOR_CASTER - int armorBypassPct = 0; - var reductionAuras = victim.GetAuraEffectsByType(AuraType.BypassArmorForCaster); - foreach (var eff in reductionAuras) - if (eff.GetCasterGUID() == GetGUID()) - armorBypassPct += eff.GetAmount(); - armor = MathFunctions.CalculatePct(armor, 100 - Math.Min(armorBypassPct, 100)); - - // Ignore enemy armor by SPELL_AURA_MOD_TARGET_RESISTANCE aura - armor += GetTotalAuraModifierByMiscMask(AuraType.ModTargetResistance, (int)SpellSchoolMask.Normal); - - if (spellInfo != null) + if (attacker != null) { - Player modOwner = GetSpellModOwner(); - if (modOwner != null) - modOwner.ApplySpellMod(spellInfo, SpellModOp.TargetResistance, ref armor); - } + armor *= victim.GetArmorMultiplierForTarget(attacker); - var resIgnoreAuras = GetAuraEffectsByType(AuraType.ModIgnoreTargetResist); - foreach (var eff in resIgnoreAuras) - { - if (eff.GetMiscValue().HasAnyFlag((int)SpellSchoolMask.Normal) && eff.IsAffectingSpell(spellInfo)) - armor = (float)Math.Floor(MathFunctions.AddPct(ref armor, -eff.GetAmount())); - } + // bypass enemy armor by SPELL_AURA_BYPASS_ARMOR_FOR_CASTER + int armorBypassPct = 0; + var reductionAuras = victim.GetAuraEffectsByType(AuraType.BypassArmorForCaster); + foreach (var eff in reductionAuras) + if (eff.GetCasterGUID() == attacker.GetGUID()) + armorBypassPct += eff.GetAmount(); - // Apply Player CR_ARMOR_PENETRATION rating - if (IsTypeId(TypeId.Player)) - { - float arpPct = ToPlayer().GetRatingBonusValue(CombatRating.ArmorPenetration); + armor = MathFunctions.CalculatePct(armor, 100 - Math.Min(armorBypassPct, 100)); - // no more than 100% - MathFunctions.RoundToInterval(ref arpPct, 0.0f, 100.0f); + // Ignore enemy armor by SPELL_AURA_MOD_TARGET_RESISTANCE aura + armor += attacker.GetTotalAuraModifierByMiscMask(AuraType.ModTargetResistance, (int)SpellSchoolMask.Normal); - float maxArmorPen; - if (victim.GetLevelForTarget(attacker) < 60) - maxArmorPen = 400 + 85 * victim.GetLevelForTarget(attacker); - else - maxArmorPen = 400 + 85 * victim.GetLevelForTarget(attacker) + 4.5f * 85 * (victim.GetLevelForTarget(attacker) - 59); + if (spellInfo != null) + { + Player modOwner = attacker.GetSpellModOwner(); + if (modOwner != null) + modOwner.ApplySpellMod(spellInfo, SpellModOp.TargetResistance, ref armor); + } - // Cap armor penetration to this number - maxArmorPen = Math.Min((armor + maxArmorPen) / 3.0f, armor); - // Figure out how much armor do we ignore - armor -= MathFunctions.CalculatePct(maxArmorPen, arpPct); + var resIgnoreAuras = attacker.GetAuraEffectsByType(AuraType.ModIgnoreTargetResist); + foreach (var eff in resIgnoreAuras) + { + if (eff.GetMiscValue().HasAnyFlag((int)SpellSchoolMask.Normal) && eff.IsAffectingSpell(spellInfo)) + armor = (float)Math.Floor(MathFunctions.AddPct(ref armor, -eff.GetAmount())); + } + + // Apply Player CR_ARMOR_PENETRATION rating + if (attacker.IsPlayer()) + { + float arpPct = attacker.ToPlayer().GetRatingBonusValue(CombatRating.ArmorPenetration); + + // no more than 100% + MathFunctions.RoundToInterval(ref arpPct, 0.0f, 100.0f); + + float maxArmorPen; + if (victim.GetLevelForTarget(attacker) < 60) + maxArmorPen = 400 + 85 * victim.GetLevelForTarget(attacker); + else + maxArmorPen = 400 + 85 * victim.GetLevelForTarget(attacker) + 4.5f * 85 * (victim.GetLevelForTarget(attacker) - 59); + + // Cap armor penetration to this number + maxArmorPen = Math.Min((armor + maxArmorPen) / 3.0f, armor); + // Figure out how much armor do we ignore + armor -= MathFunctions.CalculatePct(maxArmorPen, arpPct); + } } if (MathFunctions.fuzzyLe(armor, 0.0f)) return damage; - uint attackerLevel = attacker.GetLevelForTarget(victim); + if (attacker != null) + attackerLevel = attacker.GetLevelForTarget(victim); + // Expansion and ContentTuningID necessary? Does Player get a ContentTuningID too ? float armorConstant = Global.DB2Mgr.EvaluateExpectedStat(ExpectedStatType.ArmorConstant, attackerLevel, -2, 0, attacker.GetClass()); if ((armor + armorConstant) == 0) diff --git a/Source/Game/Handlers/MovementHandler.cs b/Source/Game/Handlers/MovementHandler.cs index feeacfd7e..a61a502ef 100644 --- a/Source/Game/Handlers/MovementHandler.cs +++ b/Source/Game/Handlers/MovementHandler.cs @@ -207,7 +207,7 @@ namespace Game if (opcode == ClientOpcodes.MoveJump) { plrMover.RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags2.Jump); // Mind Control - plrMover.ProcSkillsAndAuras(null, ProcFlags.Jump, ProcFlags.None, ProcFlagsSpellType.MaskAll, ProcFlagsSpellPhase.None, ProcFlagsHit.None, null, null, null); + Unit.ProcSkillsAndAuras(plrMover, null, ProcFlags.Jump, ProcFlags.None, ProcFlagsSpellType.MaskAll, ProcFlagsSpellPhase.None, ProcFlagsHit.None, null, null, null); } } } diff --git a/Source/Game/Maps/Instances/InstanceScript.cs b/Source/Game/Maps/Instances/InstanceScript.cs index 9ed2b5035..26ee36971 100644 --- a/Source/Game/Maps/Instances/InstanceScript.cs +++ b/Source/Game/Maps/Instances/InstanceScript.cs @@ -367,7 +367,7 @@ namespace Game.Maps var playerList = instance.GetPlayers(); foreach (var player in playerList) if (player.IsAlive()) - player.ProcSkillsAndAuras(null, ProcFlags.EncounterStart, ProcFlags.None, ProcFlagsSpellType.MaskAll, ProcFlagsSpellPhase.None, ProcFlagsHit.None, null, null, null); + Unit.ProcSkillsAndAuras(player, null, ProcFlags.EncounterStart, ProcFlags.None, ProcFlagsSpellType.MaskAll, ProcFlagsSpellPhase.None, ProcFlagsHit.None, null, null, null); break; } case EncounterState.Fail: diff --git a/Source/Game/Spells/Auras/Aura.cs b/Source/Game/Spells/Auras/Aura.cs index a9628fa96..d6975a784 100644 --- a/Source/Game/Spells/Auras/Aura.cs +++ b/Source/Game/Spells/Auras/Aura.cs @@ -347,6 +347,19 @@ namespace Game.Spells } } + public float CalcPeriodicCritChance(Unit caster) + { + if (!caster) + return 0.0f; + + Player modOwner = caster.GetSpellModOwner(); + if (modOwner == null) + return 0.0f; + + float critChance = modOwner.SpellCritChanceDone(GetSpellInfo(), GetSpellInfo().GetSchoolMask(), GetSpellInfo().GetAttackType()); + return Math.Max(0.0f, critChance); + } + public Unit GetCaster() { if (m_owner.GetGUID() == m_casterGuid) @@ -854,13 +867,8 @@ namespace Game.Spells effect.ChangeAmount(effect.CalculateAmount(caster), false, true); foreach (var app in applications) - { if (!app.HasRemoveMode()) - { - HandleAuraSpecificPeriodics(app, caster); HandleAuraSpecificMods(app, caster, true, true); - } - } SetNeedClientUpdateForTargets(); } @@ -1406,55 +1414,6 @@ namespace Game.Spells } } - public void HandleAuraSpecificPeriodics(AuraApplication aurApp, Unit caster) - { - Unit target = aurApp.GetTarget(); - - if (!caster || aurApp.HasRemoveMode()) - return; - - foreach (AuraEffect effect in GetAuraEffects()) - { - if (effect == null || effect.IsAreaAuraEffect() || effect.IsEffect(SpellEffectName.PersistentAreaAura)) - continue; - - switch (effect.GetSpellEffectInfo().ApplyAuraName) - { - case AuraType.PeriodicDamage: - case AuraType.PeriodicDamagePercent: - case AuraType.PeriodicLeech: - { - // ignore non positive values (can be result apply spellmods to aura damage - uint damage = (uint)Math.Max(effect.GetAmount(), 0); - - // Script Hook For HandlePeriodicDamageAurasTick -- Allow scripts to change the Damage pre class mitigation calculations - Global.ScriptMgr.ModifyPeriodicDamageAurasTick(target, caster, ref damage); - - effect.SetDonePct(caster.SpellDamagePctDone(target, m_spellInfo, DamageEffectType.DOT)); // Calculate done percentage first! - effect.SetDamage((int)(caster.SpellDamageBonusDone(target, m_spellInfo, damage, DamageEffectType.DOT, effect.GetSpellEffectInfo(), GetStackAmount()) * effect.GetDonePct())); - effect.SetCritChance(caster.GetUnitSpellCriticalChance(target, null, effect, m_spellInfo.GetSchoolMask())); - break; - } - case AuraType.PeriodicHeal: - case AuraType.ObsModHealth: - { - // ignore non positive values (can be result apply spellmods to aura damage - uint damage = (uint)Math.Max(effect.GetAmount(), 0); - - // Script Hook For HandlePeriodicDamageAurasTick -- Allow scripts to change the Damage pre class mitigation calculations - Global.ScriptMgr.ModifyPeriodicDamageAurasTick(target, caster, ref damage); - - effect.SetDonePct(caster.SpellHealingPctDone(target, m_spellInfo)); // Calculate done percentage first! - effect.SetDamage((int)(caster.SpellHealingBonusDone(target, m_spellInfo, damage, DamageEffectType.DOT, effect.GetSpellEffectInfo(), GetStackAmount()) * effect.GetDonePct())); - effect.SetCritChance(caster.GetUnitSpellCriticalChance(target, null, effect, m_spellInfo.GetSchoolMask())); - break; - } - default: - break; - } - } - } - bool CanBeAppliedOn(Unit target) { // unit not in world or during remove from world @@ -1842,7 +1801,7 @@ namespace Game.Spells public float CalcPPMProcChance(Unit actor) { // Formula see http://us.battle.net/wow/en/forum/topic/8197741003#1 - float ppm = m_spellInfo.CalcProcPPM(actor, m_castItemLevel); + float ppm = m_spellInfo.CalcProcPPM(actor, GetCastItemLevel()); float averageProcInterval = 60.0f / ppm; var currentTime = GameTime.GetGameTimeSteadyPoint(); diff --git a/Source/Game/Spells/Auras/AuraEffect.cs b/Source/Game/Spells/Auras/AuraEffect.cs index 83005d68a..cecc40b5c 100644 --- a/Source/Game/Spells/Auras/AuraEffect.cs +++ b/Source/Game/Spells/Auras/AuraEffect.cs @@ -39,12 +39,11 @@ namespace Game.Spells m_spellInfo = baseAura.GetSpellInfo(); _effectInfo = spellEfffectInfo; m_baseAmount = baseAmount.HasValue ? baseAmount.Value : _effectInfo.CalcBaseValue(caster, baseAura.GetAuraType() == AuraObjectType.Unit ? baseAura.GetOwner().ToUnit() : null, baseAura.GetCastItemId(), baseAura.GetCastItemLevel()); - m_donePct = 1.0f; m_canBeRecalculated = true; m_isPeriodic = false; CalculatePeriodic(caster, true, false); - m_amount = CalculateAmount(caster); + _amount = CalculateAmount(caster); CalculateSpellMod(); } @@ -267,7 +266,7 @@ namespace Game.Spells if (Convert.ToBoolean(handleMask & AuraEffectHandleModes.ChangeAmount)) { if (!mark) - m_amount = newAmount; + _amount = newAmount; else SetAmount(newAmount); CalculateSpellMod(); @@ -427,6 +426,11 @@ namespace Game.Spells } } + public float GetCritChanceFor(Unit caster, Unit target) + { + return target.SpellCritChanceTaken(caster, null, this, GetSpellInfo().GetSchoolMask(), GetBase().CalcPeriodicCritChance(caster), GetSpellInfo().GetAttackType()); + } + public bool IsAffectingSpell(SpellInfo spell) { if (spell == null) @@ -766,9 +770,9 @@ namespace Game.Spells public int GetMiscValueB() { return GetSpellEffectInfo().MiscValueB; } public int GetMiscValue() { return GetSpellEffectInfo().MiscValue; } public AuraType GetAuraType() { return GetSpellEffectInfo().ApplyAuraName; } - public int GetAmount() { return m_amount; } - public bool HasAmount() { return m_amount != 0; } - public void SetAmount(int _amount) { m_amount = _amount; m_canBeRecalculated = false; } + public int GetAmount() { return _amount; } + public bool HasAmount() { return _amount != 0; } + public void SetAmount(int amount) { _amount = amount; m_canBeRecalculated = false; } public int GetPeriodicTimer() { return _periodicTimer; } public void SetPeriodicTimer(int periodicTimer) { _periodicTimer = periodicTimer; } @@ -791,13 +795,6 @@ namespace Game.Spells public bool CanBeRecalculated() { return m_canBeRecalculated; } public void SetCanBeRecalculated(bool val) { m_canBeRecalculated = val; } - public void SetDamage(int val) { m_damage = val; } - public int GetDamage() { return m_damage; } - public void SetCritChance(float val) { m_critChance = val; } - public float GetCritChance() { return m_critChance; } - public void SetDonePct(float val) { m_donePct = val; } - public float GetDonePct() { return m_donePct; } - public void ResetTicks() { _ticksDone = 0; } public uint GetTickNumber() { return _ticksDone; } public uint GetRemainingTicks() { return GetTotalTicks() - _ticksDone; } @@ -822,10 +819,7 @@ namespace Game.Spells SpellModifier m_spellmod; public int m_baseAmount; - int m_amount; - int m_damage; - float m_critChance; - float m_donePct; + int _amount; // periodic stuff int _periodicTimer; @@ -2518,7 +2512,7 @@ namespace Game.Spells // so this break such spells or most of them. // Current formula about m_amount: effect base points + dieside - 1 // TO DO: Reasearch more about 0/0 and fix it. - caster._EnterVehicle(target.GetVehicleKit(), (sbyte)(m_amount - 1), aurApp); + caster._EnterVehicle(target.GetVehicleKit(), (sbyte)(GetAmount() - 1), aurApp); } else { @@ -2527,7 +2521,7 @@ namespace Game.Spells if (GetId() == 53111) // Devour Humanoid { - target.Kill(caster); + Unit.Kill(target, caster); if (caster.IsTypeId(TypeId.Unit)) caster.ToCreature().DespawnOrUnsummon(); } @@ -2985,7 +2979,7 @@ namespace Game.Spells if (GetMiscValue() == i || GetMiscValue() == -1) { if (apply) - target.ApplyStatPctModifier(UnitMods.StatStart + i, UnitModifierPctType.Base, m_amount); + target.ApplyStatPctModifier(UnitMods.StatStart + i, UnitModifierPctType.Base, GetAmount()); else { float amount = target.GetTotalAuraMultiplier(AuraType.ModPercentStat, aurEff => @@ -3154,7 +3148,7 @@ namespace Game.Spells { if (GetMiscValue() == (int)stat || GetMiscValue() == -1) { - target.HandleStatFlatModifier(UnitMods.StatStart + (int)stat, UnitModifierFlatType.BasePCTExcludeCreate, m_amount, apply); + target.HandleStatFlatModifier(UnitMods.StatStart + (int)stat, UnitModifierFlatType.BasePCTExcludeCreate, GetAmount(), apply); target.UpdateStatBuffMod(stat); } } @@ -3170,7 +3164,7 @@ namespace Game.Spells if (!target) return; - target.ApplyModOverrideSpellPowerByAPPercent(m_amount, apply); + target.ApplyModOverrideSpellPowerByAPPercent(GetAmount(), apply); target.UpdateSpellDamageAndHealingBonus(); } @@ -3184,7 +3178,7 @@ namespace Game.Spells if (!target) return; - target.ApplyModOverrideAPBySpellPowerPercent(m_amount, apply); + target.ApplyModOverrideAPBySpellPowerPercent(GetAmount(), apply); target.UpdateAttackPowerAndDamage(); target.UpdateAttackPowerAndDamage(true); } @@ -3730,7 +3724,7 @@ namespace Game.Spells target.ApplyAttackTimePercentMod(WeaponAttackType.RangedAttack, spellGroupVal, !apply); } - target.ApplyCastTimePercentMod(m_amount, apply); + target.ApplyCastTimePercentMod(GetAmount(), apply); target.ApplyAttackTimePercentMod(WeaponAttackType.BaseAttack, GetAmount(), apply); target.ApplyAttackTimePercentMod(WeaponAttackType.OffAttack, GetAmount(), apply); target.ApplyAttackTimePercentMod(WeaponAttackType.RangedAttack, GetAmount(), apply); @@ -4443,7 +4437,7 @@ namespace Game.Spells //Adding items uint noSpaceForCount; - uint count = (uint)m_amount; + uint count = (uint)GetAmount(); List dest = new(); InventoryResult msg = plCaster.CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, GetSpellEffectInfo().ItemType, count, out noSpaceForCount); @@ -4493,7 +4487,7 @@ namespace Game.Spells return; uint factionId = (uint)GetMiscValue(); - ReputationRank factionRank = (ReputationRank)m_amount; + ReputationRank factionRank = (ReputationRank)GetAmount(); player.GetReputationMgr().ApplyForceReaction(factionId, factionRank, apply); player.GetReputationMgr().SendForceReactions(); @@ -4880,7 +4874,10 @@ namespace Game.Spells void HandlePeriodicDamageAurasTick(Unit target, Unit caster) { - if (!caster || !target.IsAlive()) + // dynobj auras must always have a caster + Cypher.Assert(GetSpellEffectInfo().Effect != SpellEffectName.PersistentAreaAura || caster != null); + + if (!target.IsAlive()) return; if (target.HasUnitState(UnitState.Isolated) || target.IsImmunedToDamage(GetSpellInfo())) @@ -4896,38 +4893,28 @@ namespace Game.Spells CleanDamage cleanDamage = new(0, 0, WeaponAttackType.BaseAttack, MeleeHitOutcome.Normal); - // AOE spells are not affected by the new periodic system. - bool isAreaAura = GetSpellEffectInfo().IsAreaAuraEffect() || GetSpellEffectInfo().IsEffect(SpellEffectName.PersistentAreaAura); // ignore non positive values (can be result apply spellmods to aura damage - uint damage = (uint)(isAreaAura ? Math.Max(GetAmount(), 0) : m_damage); + uint damage = (uint)Math.Max(GetAmount(), 0); // Script Hook For HandlePeriodicDamageAurasTick -- Allow scripts to change the Damage pre class mitigation calculations - if (isAreaAura) - Global.ScriptMgr.ModifyPeriodicDamageAurasTick(target, caster, ref damage); + Global.ScriptMgr.ModifyPeriodicDamageAurasTick(target, caster, ref damage); switch (GetAuraType()) { case AuraType.PeriodicDamage: { - if (isAreaAura) - damage = (uint)(caster.SpellDamageBonusDone(target, GetSpellInfo(), damage, DamageEffectType.DOT, GetSpellEffectInfo(), GetBase().GetStackAmount()) * caster.SpellDamagePctDone(target, m_spellInfo, DamageEffectType.DOT)); + if (caster != null) + damage = (uint)caster.SpellDamageBonusDone(target, GetSpellInfo(), damage, DamageEffectType.DOT, GetSpellEffectInfo(), GetBase().GetStackAmount()); + damage = target.SpellDamageBonusTaken(caster, GetSpellInfo(), damage, DamageEffectType.DOT, GetSpellEffectInfo(), GetBase().GetStackAmount()); - // Calculate armor mitigation - if (caster.IsDamageReducedByArmor(GetSpellInfo().GetSchoolMask(), GetSpellInfo(), (sbyte)GetEffIndex())) - { - uint damageReductedArmor = caster.CalcArmorReducedDamage(caster, target, damage, GetSpellInfo()); - cleanDamage.mitigated_damage += damage - damageReductedArmor; - damage = damageReductedArmor; - } // There is a Chance to make a Soul Shard when Drain soul does damage - if (GetSpellInfo().SpellFamilyName == SpellFamilyNames.Warlock && GetSpellInfo().SpellFamilyFlags[0].HasAnyFlag(0x00004000u)) + if (caster != null && GetSpellInfo().SpellFamilyName == SpellFamilyNames.Warlock && GetSpellInfo().SpellFamilyFlags[0].HasAnyFlag(0x00004000u)) { if (caster.IsTypeId(TypeId.Player) && caster.ToPlayer().IsHonorOrXPTarget(target)) caster.CastSpell(caster, 95810, new CastSpellExtraArgs(this)); } - - if (GetSpellInfo().SpellFamilyName == SpellFamilyNames.Generic) + else if (GetSpellInfo().SpellFamilyName == SpellFamilyNames.Generic) { switch (GetId()) { @@ -4947,48 +4934,54 @@ namespace Game.Spells { WeaponAttackType attackType = GetSpellInfo().GetAttackType(); - uint weaponDamage = MathFunctions.CalculatePct(caster.CalculateDamage(attackType, false, true), GetAmount()); + damage = MathFunctions.CalculatePct(caster.CalculateDamage(attackType, false, true), GetAmount()); // Add melee damage bonuses (also check for negative) - uint damageBonusDone = caster.MeleeDamageBonusDone(target, Math.Max(weaponDamage, 0), attackType, DamageEffectType.DOT, GetSpellInfo()); + if (caster != null) + damage = caster.MeleeDamageBonusDone(target, damage, attackType, DamageEffectType.DOT, GetSpellInfo()); - damage = target.MeleeDamageBonusTaken(caster, damageBonusDone, attackType, DamageEffectType.DOT, GetSpellInfo()); + damage = target.MeleeDamageBonusTaken(caster, damage, attackType, DamageEffectType.DOT, GetSpellInfo()); break; } case AuraType.PeriodicDamagePercent: // ceil obtained value, it may happen that 10 ticks for 10% damage may not kill owner damage = (uint)Math.Ceiling(MathFunctions.CalculatePct((float)target.GetMaxHealth(), (float)damage)); + damage = target.SpellDamageBonusTaken(caster, GetSpellInfo(), damage, DamageEffectType.DOT, GetSpellEffectInfo(), GetBase().GetStackAmount()); break; default: break; } - if (!m_spellInfo.HasAttribute(SpellAttr4.FixedDamage)) + bool crit = RandomHelper.randChance(GetCritChanceFor(caster, target)); + if (crit) + damage = Unit.SpellCriticalDamageBonus(caster, m_spellInfo, damage, target); + + // Calculate armor mitigation + if (Unit.IsDamageReducedByArmor(GetSpellInfo().GetSchoolMask(), GetSpellInfo(), (sbyte)GetEffIndex())) { - if (GetSpellEffectInfo().IsTargetingArea() || isAreaAura) - { - damage = (uint)(damage * target.GetTotalAuraMultiplierByMiscMask(AuraType.ModAoeDamageAvoidance, (uint)m_spellInfo.SchoolMask)); - if (!caster.IsTypeId(TypeId.Player)) - damage = (uint)(damage * target.GetTotalAuraMultiplierByMiscMask(AuraType.ModCreatureAoeDamageAvoidance, (uint)m_spellInfo.SchoolMask)); - } + uint damageReductedArmor = Unit.CalcArmorReducedDamage(caster, target, damage, GetSpellInfo(), GetSpellInfo().GetAttackType(), GetBase().GetCasterLevel()); + cleanDamage.mitigated_damage += damage - damageReductedArmor; + damage = damageReductedArmor; } - bool crit = RandomHelper.randChance(isAreaAura ? caster.GetUnitSpellCriticalChance(target, null, this, m_spellInfo.GetSchoolMask()) : m_critChance); - if (crit) - damage = caster.SpellCriticalDamageBonus(m_spellInfo, damage, target); - - uint dmg = damage; if (!GetSpellInfo().HasAttribute(SpellAttr4.FixedDamage)) - caster.ApplyResilience(target, ref dmg); - damage = dmg; + { + if (GetSpellEffectInfo().IsTargetingArea() || GetSpellEffectInfo().IsAreaAuraEffect() || GetSpellEffectInfo().Effect == SpellEffectName.PersistentAreaAura) + damage = (uint)target.CalculateAOEAvoidance((int)damage, (uint)m_spellInfo.SchoolMask, caster); + } + + int dmg = (int)damage; + if (!GetSpellInfo().HasAttribute(SpellAttr4.FixedDamage) && caster != null && caster.CanApplyResilience()) + Unit.ApplyResilience(target, ref dmg); + damage = (uint)dmg; DamageInfo damageInfo = new(caster, target, damage, GetSpellInfo(), GetSpellInfo().GetSchoolMask(), DamageEffectType.DOT, WeaponAttackType.BaseAttack); - caster.CalcAbsorbResist(damageInfo); + Unit.CalcAbsorbResist(damageInfo); damage = damageInfo.GetDamage(); uint absorb = damageInfo.GetAbsorb(); uint resist = damageInfo.GetResist(); - caster.DealDamageMods(target, ref damage, ref absorb); + Unit.DealDamageMods(caster, target, ref damage, ref absorb); // Set trigger flag ProcFlags procAttacker = ProcFlags.DonePeriodic; @@ -5005,17 +4998,20 @@ namespace Game.Spells if (overkill < 0) overkill = 0; - SpellPeriodicAuraLogInfo pInfo = new(this, damage, dmg, (uint)overkill, absorb, resist, 0.0f, crit); + SpellPeriodicAuraLogInfo pInfo = new(this, damage, (uint)dmg, (uint)overkill, absorb, resist, 0.0f, crit); - caster.DealDamage(target, damage, cleanDamage, DamageEffectType.DOT, GetSpellInfo().GetSchoolMask(), GetSpellInfo(), true); + Unit.DealDamage(caster, target, damage, cleanDamage, DamageEffectType.DOT, GetSpellInfo().GetSchoolMask(), GetSpellInfo(), true); - caster.ProcSkillsAndAuras(target, procAttacker, procVictim, ProcFlagsSpellType.Damage, ProcFlagsSpellPhase.None, hitMask, null, damageInfo, null); + Unit.ProcSkillsAndAuras(caster, target, procAttacker, procVictim, ProcFlagsSpellType.Damage, ProcFlagsSpellPhase.None, hitMask, null, damageInfo, null); target.SendPeriodicAuraLog(pInfo); } void HandlePeriodicHealthLeechAuraTick(Unit target, Unit caster) { - if (!caster || !target.IsAlive()) + // dynobj auras must always have a caster + Cypher.Assert(GetSpellEffectInfo().Effect != SpellEffectName.PersistentAreaAura || caster != null); + + if (!target.IsAlive()) return; if (target.HasUnitState(UnitState.Isolated) || target.IsImmunedToDamage(GetSpellInfo())) @@ -5030,47 +5026,40 @@ namespace Game.Spells CleanDamage cleanDamage = new(0, 0, WeaponAttackType.BaseAttack, MeleeHitOutcome.Normal); - bool isAreaAura = GetSpellEffectInfo().IsAreaAuraEffect() || GetSpellEffectInfo().IsEffect(SpellEffectName.PersistentAreaAura); // ignore negative values (can be result apply spellmods to aura damage - uint damage = (uint)(isAreaAura ? Math.Max(GetAmount(), 0) : m_damage); + uint damage = (uint)Math.Max(GetAmount(), 0); + + if (caster) + damage = caster.SpellDamageBonusDone(target, GetSpellInfo(), damage, DamageEffectType.DOT, GetSpellEffectInfo(), GetBase().GetStackAmount()); - if (isAreaAura) - { - // Script Hook For HandlePeriodicDamageAurasTick -- Allow scripts to change the Damage pre class mitigation calculations - Global.ScriptMgr.ModifyPeriodicDamageAurasTick(target, caster, ref damage); - damage = (uint)(caster.SpellDamageBonusDone(target, GetSpellInfo(), damage, DamageEffectType.DOT, GetSpellEffectInfo(), GetBase().GetStackAmount()) * caster.SpellDamagePctDone(target, m_spellInfo, DamageEffectType.DOT)); - } damage = target.SpellDamageBonusTaken(caster, GetSpellInfo(), damage, DamageEffectType.DOT, GetSpellEffectInfo(), GetBase().GetStackAmount()); + bool crit = RandomHelper.randChance(GetCritChanceFor(caster, target)); + if (crit) + damage = Unit.SpellCriticalDamageBonus(caster, m_spellInfo, damage, target); + // Calculate armor mitigation - if (caster.IsDamageReducedByArmor(GetSpellInfo().GetSchoolMask(), GetSpellInfo(), (sbyte)GetEffIndex())) + if (Unit.IsDamageReducedByArmor(GetSpellInfo().GetSchoolMask(), GetSpellInfo(), (sbyte)GetEffIndex())) { - uint damageReductedArmor = caster.CalcArmorReducedDamage(caster, target, damage, GetSpellInfo()); + uint damageReductedArmor = Unit.CalcArmorReducedDamage(caster, target, damage, GetSpellInfo(), GetSpellInfo().GetAttackType(), GetBase().GetCasterLevel()); cleanDamage.mitigated_damage += damage - damageReductedArmor; damage = damageReductedArmor; } - if (!m_spellInfo.HasAttribute(SpellAttr4.FixedDamage)) + if (!GetSpellInfo().HasAttribute(SpellAttr4.FixedDamage)) { - if (GetSpellEffectInfo().IsTargetingArea() || isAreaAura) - { - damage = (uint)(damage * target.GetTotalAuraMultiplierByMiscMask(AuraType.ModAoeDamageAvoidance, (uint)m_spellInfo.SchoolMask)); - if (!caster.IsTypeId(TypeId.Player)) - damage = (uint)(damage * target.GetTotalAuraMultiplierByMiscMask(AuraType.ModCreatureAoeDamageAvoidance, (uint)m_spellInfo.SchoolMask)); - } + if (GetSpellEffectInfo().IsTargetingArea() || GetSpellEffectInfo().IsAreaAuraEffect() || GetSpellEffectInfo().Effect == SpellEffectName.PersistentAreaAura) + damage = (uint)target.CalculateAOEAvoidance((int)damage, (uint)m_spellInfo.SchoolMask, caster); } - bool crit = RandomHelper.randChance(isAreaAura ? caster.GetUnitSpellCriticalChance(target, null, this, m_spellInfo.GetSchoolMask()) : m_critChance); - if (crit) - damage = caster.SpellCriticalDamageBonus(m_spellInfo, damage, target); + int dmg = (int)damage; + if (!GetSpellInfo().HasAttribute(SpellAttr4.FixedDamage) && caster != null && caster.CanApplyResilience()) + Unit.ApplyResilience(target, ref dmg); - uint dmg = damage; - if (!GetSpellInfo().HasAttribute(SpellAttr4.FixedDamage)) - caster.ApplyResilience(target, ref dmg); - damage = dmg; + damage = (uint)dmg; DamageInfo damageInfo = new(caster, target, damage, GetSpellInfo(), GetSpellInfo().GetSchoolMask(), DamageEffectType.DOT, WeaponAttackType.BaseAttack); - caster.CalcAbsorbResist(damageInfo); + Unit.CalcAbsorbResist(damageInfo); uint absorb = damageInfo.GetAbsorb(); uint resist = damageInfo.GetResist(); @@ -5078,7 +5067,7 @@ namespace Game.Spells // SendSpellNonMeleeDamageLog expects non-absorbed/non-resisted damage SpellNonMeleeDamage log = new(caster, target, GetSpellInfo(), GetBase().GetSpellVisual(), GetSpellInfo().GetSchoolMask(), GetBase().GetCastGUID()); log.damage = damage; - log.originalDamage = dmg; + log.originalDamage = (uint)dmg; log.absorb = absorb; log.resist = resist; log.periodicLog = true; @@ -5096,22 +5085,22 @@ namespace Game.Spells procVictim |= ProcFlags.TakenDamage; } - int new_damage = (int)caster.DealDamage(target, damage, cleanDamage, DamageEffectType.DOT, GetSpellInfo().GetSchoolMask(), GetSpellInfo(), false); - if (caster.IsAlive()) - { - caster.ProcSkillsAndAuras(target, procAttacker, procVictim, ProcFlagsSpellType.Damage, ProcFlagsSpellPhase.None, hitMask, null, damageInfo, null); + int new_damage = (int)Unit.DealDamage(caster, target, damage, cleanDamage, DamageEffectType.DOT, GetSpellInfo().GetSchoolMask(), GetSpellInfo(), false); + if (!caster || !caster.IsAlive()) + return; - float gainMultiplier = GetSpellEffectInfo().CalcValueMultiplier(caster); + Unit.ProcSkillsAndAuras(caster, target, procAttacker, procVictim, ProcFlagsSpellType.Damage, ProcFlagsSpellPhase.None, hitMask, null, damageInfo, null); - uint heal = (caster.SpellHealingBonusDone(caster, GetSpellInfo(), (uint)(new_damage * gainMultiplier), DamageEffectType.DOT, GetSpellEffectInfo(), GetBase().GetStackAmount())); - heal = (caster.SpellHealingBonusTaken(caster, GetSpellInfo(), heal, DamageEffectType.DOT, GetSpellEffectInfo(), GetBase().GetStackAmount())); + float gainMultiplier = GetSpellEffectInfo().CalcValueMultiplier(caster); - HealInfo healInfo = new(caster, caster, heal, GetSpellInfo(), GetSpellInfo().GetSchoolMask()); - caster.HealBySpell(healInfo); + uint heal = (caster.SpellHealingBonusDone(caster, GetSpellInfo(), (uint)(new_damage * gainMultiplier), DamageEffectType.DOT, GetSpellEffectInfo(), GetBase().GetStackAmount())); + heal = caster.SpellHealingBonusTaken(caster, GetSpellInfo(), heal, DamageEffectType.DOT, GetSpellEffectInfo(), GetBase().GetStackAmount()); - caster.GetThreatManager().ForwardThreatForAssistingMe(caster, healInfo.GetEffectiveHeal() * 0.5f, GetSpellInfo()); - caster.ProcSkillsAndAuras(caster, ProcFlags.DonePeriodic, ProcFlags.TakenPeriodic, ProcFlagsSpellType.Heal, ProcFlagsSpellPhase.None, hitMask, null, null, healInfo); - } + HealInfo healInfo = new(caster, caster, heal, GetSpellInfo(), GetSpellInfo().GetSchoolMask()); + caster.HealBySpell(healInfo); + + caster.GetThreatManager().ForwardThreatForAssistingMe(caster, healInfo.GetEffectiveHeal() * 0.5f, GetSpellInfo()); + Unit.ProcSkillsAndAuras(caster, caster, ProcFlags.DonePeriodic, ProcFlags.TakenPeriodic, ProcFlagsSpellType.Heal, ProcFlagsSpellPhase.None, hitMask, null, null, healInfo); caster.SendSpellNonMeleeDamageLog(log); } @@ -5143,12 +5132,15 @@ namespace Game.Spells HealInfo healInfo = new(caster, target, damage, GetSpellInfo(), GetSpellInfo().GetSchoolMask()); caster.HealBySpell(healInfo); - caster.ProcSkillsAndAuras(target, ProcFlags.DonePeriodic, ProcFlags.TakenPeriodic, ProcFlagsSpellType.Heal, ProcFlagsSpellPhase.None, ProcFlagsHit.Normal, null, null, healInfo); + Unit.ProcSkillsAndAuras(caster, target, ProcFlags.DonePeriodic, ProcFlags.TakenPeriodic, ProcFlagsSpellType.Heal, ProcFlagsSpellPhase.None, ProcFlagsHit.Normal, null, null, healInfo); } void HandlePeriodicHealAurasTick(Unit target, Unit caster) { - if (!caster || !target.IsAlive()) + // dynobj auras must always have a caster + Cypher.Assert(GetSpellEffectInfo().Effect != SpellEffectName.PersistentAreaAura || caster != null); + + if (!target.IsAlive()) return; if (target.HasUnitState(UnitState.Isolated)) @@ -5158,51 +5150,28 @@ namespace Game.Spells } // heal for caster damage (must be alive) - if (target != caster && GetSpellInfo().HasAttribute(SpellAttr2.HealthFunnel) && !caster.IsAlive()) + if (target != caster && GetSpellInfo().HasAttribute(SpellAttr2.HealthFunnel) && (caster == null || !caster.IsAlive())) return; // don't regen when permanent aura target has full power if (GetBase().IsPermanent() && target.IsFullHealth()) return; - bool isAreaAura = GetSpellEffectInfo().IsAreaAuraEffect() || GetSpellEffectInfo().IsEffect(SpellEffectName.PersistentAreaAura); // ignore negative values (can be result apply spellmods to aura damage - int damage = isAreaAura ? Math.Max(GetAmount(), 0) : m_damage; + uint damage = (uint)Math.Max(GetAmount(), 0); if (GetAuraType() == AuraType.ObsModHealth) { - // Taken mods - float TakenTotalMod = 1.0f; - - // Tenacity increase healing % taken - AuraEffect Tenacity = target.GetAuraEffect(58549, 0); - if (Tenacity != null) - MathFunctions.AddPct(ref TakenTotalMod, Tenacity.GetAmount()); - - // Healing taken percent - float minval = target.GetMaxNegativeAuraModifier(AuraType.ModHealingPct); - if (minval != 0) - MathFunctions.AddPct(ref TakenTotalMod, minval); - - float maxval = target.GetMaxPositiveAuraModifier(AuraType.ModHealingPct); - if (maxval != 0) - MathFunctions.AddPct(ref TakenTotalMod, maxval); - - TakenTotalMod = Math.Max(TakenTotalMod, 0.0f); - - damage = (int)target.CountPctFromMaxHealth(damage); - damage = (int)(damage * TakenTotalMod); - } - else - { - if (isAreaAura) - damage = (int)(caster.SpellHealingBonusDone(target, GetSpellInfo(), (uint)damage, DamageEffectType.DOT, GetSpellEffectInfo(), GetBase().GetStackAmount()) * caster.SpellHealingPctDone(target, m_spellInfo)); - damage = (int)target.SpellHealingBonusTaken(caster, GetSpellInfo(), (uint)damage, DamageEffectType.DOT, GetSpellEffectInfo(), GetBase().GetStackAmount()); + damage = (uint)target.CountPctFromMaxHealth((int)damage); } + else if (caster != null) + damage = caster.SpellHealingBonusDone(target, GetSpellInfo(), damage, DamageEffectType.DOT, GetSpellEffectInfo(), GetBase().GetStackAmount()); - bool crit = RandomHelper.randChance(isAreaAura ? caster.GetUnitSpellCriticalChance(target, null, this, m_spellInfo.GetSchoolMask()) : m_critChance); + damage = target.SpellHealingBonusTaken(caster, GetSpellInfo(), damage, DamageEffectType.DOT, GetSpellEffectInfo(), GetBase().GetStackAmount()); + + bool crit = RandomHelper.randChance(GetCritChanceFor(caster, target)); if (crit) - damage = caster.SpellCriticalHealingBonus(m_spellInfo, damage, target); + damage = (uint)Unit.SpellCriticalHealingBonus(caster, m_spellInfo, (int)damage, target); Log.outDebug(LogFilter.Spells, "PeriodicTick: {0} (TypeId: {1}) heal of {2} (TypeId: {3}) for {4} health inflicted by {5}", GetCasterGUID().ToString(), GetCaster().GetTypeId(), target.GetGUID().ToString(), target.GetTypeId(), damage, GetId()); @@ -5210,13 +5179,14 @@ namespace Game.Spells uint heal = (uint)damage; HealInfo healInfo = new(caster, target, heal, GetSpellInfo(), GetSpellInfo().GetSchoolMask()); - caster.CalcHealAbsorb(healInfo); - caster.DealHeal(healInfo); + Unit.CalcHealAbsorb(healInfo); + Unit.DealHeal(healInfo); SpellPeriodicAuraLogInfo pInfo = new(this, heal, (uint)damage, heal - healInfo.GetEffectiveHeal(), healInfo.GetAbsorb(), 0, 0.0f, crit); target.SendPeriodicAuraLog(pInfo); - target.GetThreatManager().ForwardThreatForAssistingMe(caster, healInfo.GetEffectiveHeal() * 0.5f, GetSpellInfo()); + if (caster != null) + target.GetThreatManager().ForwardThreatForAssistingMe(caster, healInfo.GetEffectiveHeal() * 0.5f, GetSpellInfo()); // %-based heal - does not proc auras if (GetAuraType() == AuraType.ObsModHealth) @@ -5227,7 +5197,7 @@ namespace Game.Spells ProcFlagsHit hitMask = crit ? ProcFlagsHit.Critical : ProcFlagsHit.Normal; // ignore item heals if (GetBase().GetCastItemGUID().IsEmpty()) - caster.ProcSkillsAndAuras(target, procAttacker, procVictim, ProcFlagsSpellType.Heal, ProcFlagsSpellPhase.None, hitMask, null, null, healInfo); + Unit.ProcSkillsAndAuras(caster, target, procAttacker, procVictim, ProcFlagsSpellType.Heal, ProcFlagsSpellPhase.None, hitMask, null, null, healInfo); } void HandlePeriodicManaLeechAuraTick(Unit target, Unit caster) @@ -5248,7 +5218,7 @@ namespace Game.Spells return; // ignore negative values (can be result apply spellmods to aura damage - int drainAmount = Math.Max(m_amount, 0); + int drainAmount = Math.Max(GetAmount(), 0); int drainedAmount = -target.ModifyPower(powerType, -drainAmount); float gainMultiplier = GetSpellEffectInfo().CalcValueMultiplier(caster); @@ -5307,7 +5277,7 @@ namespace Game.Spells return; // ignore negative values (can be result apply spellmods to aura damage - int amount = Math.Max(m_amount, 0) * target.GetMaxPower(powerType) / 100; + int amount = Math.Max(GetAmount(), 0) * target.GetMaxPower(powerType) / 100; SpellPeriodicAuraLogInfo pInfo = new(this, (uint)amount, (uint)amount, 0, 0, 0, 0.0f, false); @@ -5336,7 +5306,7 @@ namespace Game.Spells return; // ignore negative values (can be result apply spellmods to aura damage - int amount = Math.Max(m_amount, 0); + int amount = Math.Max(GetAmount(), 0); SpellPeriodicAuraLogInfo pInfo = new(this, (uint)amount, (uint)amount, 0, 0, 0, 0.0f, false); int gain = target.ModifyPower(powerType, amount); @@ -5361,7 +5331,7 @@ namespace Game.Spells } // ignore negative values (can be result apply spellmods to aura damage - int damage = Math.Max(m_amount, 0); + int damage = Math.Max(GetAmount(), 0); uint gain = (uint)(-target.ModifyPower(powerType, -damage)); @@ -5373,7 +5343,7 @@ namespace Game.Spells // no SpellDamageBonus for burn mana caster.CalculateSpellDamageTaken(damageInfo, (int)(gain * dmgMultiplier), spellProto); - caster.DealDamageMods(damageInfo.target, ref damageInfo.damage, ref damageInfo.absorb); + Unit.DealDamageMods(damageInfo.attacker, damageInfo.target, ref damageInfo.damage, ref damageInfo.absorb); // Set trigger flag ProcFlags procAttacker = ProcFlags.DonePeriodic; @@ -5389,7 +5359,7 @@ namespace Game.Spells caster.DealSpellDamage(damageInfo, true); DamageInfo dotDamageInfo = new(damageInfo, DamageEffectType.DOT, WeaponAttackType.BaseAttack, hitMask); - caster.ProcSkillsAndAuras(target, procAttacker, procVictim, spellTypeMask, ProcFlagsSpellPhase.None, hitMask, null, dotDamageInfo, null); + Unit.ProcSkillsAndAuras(caster, target, procAttacker, procVictim, spellTypeMask, ProcFlagsSpellPhase.None, hitMask, null, dotDamageInfo, null); caster.SendSpellNonMeleeDamageLog(damageInfo); } @@ -5452,7 +5422,7 @@ namespace Game.Spells int damage = (int)target.SpellDamageBonusDone(triggerTarget, GetSpellInfo(), (uint)GetAmount(), DamageEffectType.SpellDirect, GetSpellEffectInfo()); damage = (int)triggerTarget.SpellDamageBonusTaken(target, GetSpellInfo(), (uint)damage, DamageEffectType.SpellDirect, GetSpellEffectInfo()); target.CalculateSpellDamageTaken(damageInfo, damage, GetSpellInfo()); - target.DealDamageMods(damageInfo.target, ref damageInfo.damage, ref damageInfo.absorb); + Unit.DealDamageMods(damageInfo.attacker, damageInfo.target, ref damageInfo.damage, ref damageInfo.absorb); target.DealSpellDamage(damageInfo, true); target.SendSpellNonMeleeDamageLog(damageInfo); } diff --git a/Source/Game/Spells/Spell.cs b/Source/Game/Spells/Spell.cs index 3b48fd68e..b37431a54 100644 --- a/Source/Game/Spells/Spell.cs +++ b/Source/Game/Spells/Spell.cs @@ -1947,7 +1947,7 @@ namespace Game.Spells if (crit) { hitMask |= ProcFlagsHit.Critical; - addhealth = (uint)caster.SpellCriticalHealingBonus(m_spellInfo, (int)addhealth, null); + addhealth = (uint)Unit.SpellCriticalHealingBonus(caster, m_spellInfo, (int)addhealth, null); } else hitMask |= ProcFlagsHit.Normal; @@ -1959,7 +1959,7 @@ namespace Game.Spells // Do triggers for unit if (canEffectTrigger) - caster.ProcSkillsAndAuras(unitTarget, procAttacker, procVictim, ProcFlagsSpellType.Heal, ProcFlagsSpellPhase.Hit, hitMask, this, null, healInfo); + Unit.ProcSkillsAndAuras(caster, unitTarget, procAttacker, procVictim, ProcFlagsSpellType.Heal, ProcFlagsSpellPhase.Hit, hitMask, this, null, healInfo); } // Do damage and triggers else if (m_damage > 0) @@ -1978,7 +1978,7 @@ namespace Game.Spells { // Add bonuses and fill damageInfo struct caster.CalculateSpellDamageTaken(damageInfo, m_damage, m_spellInfo, m_attackType, target.crit); - caster.DealDamageMods(damageInfo.target, ref damageInfo.damage, ref damageInfo.absorb); + Unit.DealDamageMods(damageInfo.attacker, damageInfo.target, ref damageInfo.damage, ref damageInfo.absorb); hitMask |= Unit.CreateProcHitMask(damageInfo, missInfo); procVictim |= ProcFlags.TakenDamage; @@ -1995,7 +1995,7 @@ namespace Game.Spells if (canEffectTrigger) { DamageInfo spellDamageInfo = new(damageInfo, DamageEffectType.SpellDirect, m_attackType, hitMask); - caster.ProcSkillsAndAuras(unitTarget, procAttacker, procVictim, ProcFlagsSpellType.Damage, ProcFlagsSpellPhase.Hit, hitMask, this, spellDamageInfo, null); + Unit.ProcSkillsAndAuras(caster, unitTarget, procAttacker, procVictim, ProcFlagsSpellType.Damage, ProcFlagsSpellPhase.Hit, hitMask, this, spellDamageInfo, null); if (caster.IsPlayer() && !m_spellInfo.HasAttribute(SpellAttr0.StopAttackTarget) && !m_spellInfo.HasAttribute(SpellAttr4.SuppressWeaponProcs) && (m_spellInfo.DmgClass == SpellDmgClass.Melee || m_spellInfo.DmgClass == SpellDmgClass.Ranged)) @@ -2012,7 +2012,7 @@ namespace Game.Spells if (canEffectTrigger) { DamageInfo spellNoDamageInfo = new(damageInfo, DamageEffectType.NoDamage, m_attackType, hitMask); - caster.ProcSkillsAndAuras(unitTarget, procAttacker, procVictim, ProcFlagsSpellType.NoDmgHeal, ProcFlagsSpellPhase.Hit, hitMask, this, spellNoDamageInfo, null); + Unit.ProcSkillsAndAuras(caster, unitTarget, procAttacker, procVictim, ProcFlagsSpellType.NoDmgHeal, ProcFlagsSpellPhase.Hit, hitMask, this, spellNoDamageInfo, null); if (caster.IsPlayer() && !m_spellInfo.HasAttribute(SpellAttr0.StopAttackTarget) && !m_spellInfo.HasAttribute(SpellAttr4.SuppressWeaponProcs) && (m_spellInfo.DmgClass == SpellDmgClass.Melee || m_spellInfo.DmgClass == SpellDmgClass.Ranged)) @@ -2960,7 +2960,7 @@ namespace Game.Spells if (!_triggeredCastFlags.HasAnyFlag(TriggerCastFlags.IgnoreAuraInterruptFlags) && !m_spellInfo.HasAttribute(SpellAttr2.IgnoreActionAuraInterruptFlags)) m_originalCaster.RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.ActionDelayed); - m_originalCaster.ProcSkillsAndAuras(null, procAttacker, ProcFlags.None, ProcFlagsSpellType.MaskAll, ProcFlagsSpellPhase.Cast, hitMask, this, null, null); + Unit.ProcSkillsAndAuras(m_originalCaster, null, procAttacker, ProcFlags.None, ProcFlagsSpellType.MaskAll, ProcFlagsSpellPhase.Cast, hitMask, this, null, null); // Call CreatureAI hook OnSuccessfulSpellCast Creature caster = m_originalCaster.ToCreature(); @@ -3187,7 +3187,7 @@ namespace Game.Spells procAttacker = IsPositive() ? ProcFlags.DoneSpellNoneDmgClassPos : ProcFlags.DoneSpellNoneDmgClassNeg; } - m_originalCaster.ProcSkillsAndAuras(null, procAttacker, ProcFlags.None, ProcFlagsSpellType.MaskAll, ProcFlagsSpellPhase.Finish, m_hitMask, this, null, null); + Unit.ProcSkillsAndAuras(m_originalCaster, null, procAttacker, ProcFlags.None, ProcFlagsSpellType.MaskAll, ProcFlagsSpellPhase.Finish, m_hitMask, this, null, null); } void SendSpellCooldown() @@ -6865,11 +6865,9 @@ namespace Game.Spells { if (effect.IsTargetingArea() || effect.IsAreaAuraEffect() || effect.IsEffect(SpellEffectName.PersistentAreaAura)) { - m_damage = (int)(m_damage * unit.GetTotalAuraMultiplierByMiscMask(AuraType.ModAoeDamageAvoidance, (uint)m_spellInfo.SchoolMask)); - if (!m_caster.IsTypeId(TypeId.Player)) - m_damage = (int)(m_damage * unit.GetTotalAuraMultiplierByMiscMask(AuraType.ModCreatureAoeDamageAvoidance, (uint)m_spellInfo.SchoolMask)); + m_damage = unit.CalculateAOEAvoidance(m_damage, (uint)m_spellInfo.SchoolMask, m_caster); - if (m_caster.IsTypeId(TypeId.Player)) + if (m_caster.IsPlayer()) { long targetAmount = GetUnitTargetCountForEffect(effect.EffectIndex); if (targetAmount > 20) @@ -6887,7 +6885,8 @@ namespace Game.Spells } } - targetInfo.crit = m_caster.IsSpellCrit(unit, this, null, m_spellSchoolMask, m_attackType); + float critChance = m_caster.SpellCritChanceDone(m_spellInfo, m_spellSchoolMask, m_attackType); + targetInfo.crit = RandomHelper.randChance(unit.SpellCritChanceTaken(m_caster, this, null, m_spellSchoolMask, critChance, m_attackType)); } SpellCastResult CanOpenLock(uint effIndex, uint lockId, ref SkillType skillId, ref int reqSkillValue, ref int skillValue) @@ -8163,7 +8162,7 @@ namespace Game.Spells ProcFlagsSpellPhase spellPhaseMask = ProcFlagsSpellPhase.None; ProcFlagsHit hitMask = ProcFlagsHit.Reflect; - caster.ProcSkillsAndAuras(_victim, typeMaskActor, typeMaskActionTarget, spellTypeMask, spellPhaseMask, hitMask, null, null, null); + Unit.ProcSkillsAndAuras(caster, _victim, typeMaskActor, typeMaskActionTarget, spellTypeMask, spellPhaseMask, hitMask, null, null, null); return true; } diff --git a/Source/Game/Spells/SpellEffects.cs b/Source/Game/Spells/SpellEffects.cs index 9c8d356a7..e6f282980 100644 --- a/Source/Game/Spells/SpellEffects.cs +++ b/Source/Game/Spells/SpellEffects.cs @@ -110,7 +110,7 @@ namespace Game.Spells data.SpellID = m_spellInfo.Id; m_caster.SendMessageToSet(data, true); - m_caster.DealDamage(unitTarget, (uint)unitTarget.GetHealth(), null, DamageEffectType.NoDamage, SpellSchoolMask.Normal, null, false); + Unit.DealDamage(m_caster, unitTarget, (uint)unitTarget.GetHealth(), null, DamageEffectType.NoDamage, SpellSchoolMask.Normal, null, false); } [SpellEffectHandler(SpellEffectName.EnvironmentalDamage)] @@ -128,7 +128,7 @@ namespace Game.Spells else { DamageInfo damageInfo = new(m_caster, unitTarget, (uint)damage, m_spellInfo, m_spellInfo.GetSchoolMask(), DamageEffectType.SpellDirect, WeaponAttackType.BaseAttack); - m_caster.CalcAbsorbResist(damageInfo); + Unit.CalcAbsorbResist(damageInfo); SpellNonMeleeDamage log = new(m_caster, unitTarget, m_spellInfo, m_SpellVisual, m_spellInfo.GetSchoolMask(), m_castId); log.damage = damageInfo.GetDamage(); @@ -908,9 +908,8 @@ namespace Game.Spells addhealth = (int)caster.SpellHealingBonusDone(unitTarget, m_spellInfo, (uint)caster.CountPctFromMaxHealth(damage), DamageEffectType.Heal, effectInfo); else { - addhealth = (int)caster.SpellHealingBonusDone(unitTarget, m_spellInfo, (uint)addhealth, DamageEffectType.Heal, effectInfo); uint bonus = caster.SpellHealingBonusDone(unitTarget, m_spellInfo, (uint)addhealth, DamageEffectType.Heal, effectInfo); - damage = (int)(bonus + (bonus * _variance)); + addhealth = (int)(bonus + (uint)(bonus * _variance)); } addhealth = (int)unitTarget.SpellHealingBonusTaken(caster, m_spellInfo, (uint)addhealth, DamageEffectType.Heal, effectInfo); @@ -937,9 +936,7 @@ namespace Game.Spells return; uint heal = m_originalCaster.SpellHealingBonusDone(unitTarget, m_spellInfo, (uint)unitTarget.CountPctFromMaxHealth(damage), DamageEffectType.Heal, effectInfo); - heal = unitTarget.SpellHealingBonusTaken(m_originalCaster, m_spellInfo, heal, DamageEffectType.Heal, effectInfo); - - m_healing += (int)heal; + m_healing += (int)unitTarget.SpellHealingBonusTaken(m_originalCaster, m_spellInfo, heal, DamageEffectType.Heal, effectInfo); } [SpellEffectHandler(SpellEffectName.HealMechanical)] @@ -2705,9 +2702,9 @@ namespace Game.Spells int duration = m_spellInfo.GetDuration(); unitTarget.GetSpellHistory().LockSpellSchool(curSpellInfo.GetSchoolMask(), (uint)unitTarget.ModSpellDuration(m_spellInfo, unitTarget, duration, false, (uint)(1 << (int)effIndex))); if (m_spellInfo.DmgClass == SpellDmgClass.Magic) - m_originalCaster.ProcSkillsAndAuras(unitTarget, ProcFlags.DoneSpellMagicDmgClassNeg, ProcFlags.TakenSpellMagicDmgClassNeg, ProcFlagsSpellType.MaskAll, ProcFlagsSpellPhase.Hit, ProcFlagsHit.Interrupt, null, null, null); + Unit.ProcSkillsAndAuras(m_originalCaster, unitTarget, ProcFlags.DoneSpellMagicDmgClassNeg, ProcFlags.TakenSpellMagicDmgClassNeg, ProcFlagsSpellType.MaskAll, ProcFlagsSpellPhase.Hit, ProcFlagsHit.Interrupt, null, null, null); else if (m_spellInfo.DmgClass == SpellDmgClass.Melee) - m_originalCaster.ProcSkillsAndAuras(unitTarget, ProcFlags.DoneSpellMeleeDmgClass, ProcFlags.TakenSpellMeleeDmgClass, ProcFlagsSpellType.MaskAll, ProcFlagsSpellPhase.Hit, ProcFlagsHit.Interrupt, null, null, null); + Unit.ProcSkillsAndAuras(m_originalCaster, unitTarget, ProcFlags.DoneSpellMeleeDmgClass, ProcFlags.TakenSpellMeleeDmgClass, ProcFlagsSpellType.MaskAll, ProcFlagsSpellPhase.Hit, ProcFlagsHit.Interrupt, null, null, null); } ExecuteLogEffectInterruptCast(effIndex, unitTarget, curSpellInfo.Id); unitTarget.InterruptSpell(i, false); diff --git a/Source/Game/Spells/SpellManager.cs b/Source/Game/Spells/SpellManager.cs index 73259b16c..143394703 100644 --- a/Source/Game/Spells/SpellManager.cs +++ b/Source/Game/Spells/SpellManager.cs @@ -2986,6 +2986,14 @@ namespace Game.Entities spellInfo.GetEffect(0).TriggerSpell = 46737; spellInfo.GetEffect(0).ApplyAuraName = AuraType.PeriodicTriggerSpell; break; + case 379: // Earth Shield + case 71607: // Item - Bauble of True Blood 10m + case 71646: // Item - Bauble of True Blood 25m + case 71610: // Item - Althor's Abacus trigger 10m + case 71641: // Item - Althor's Abacus trigger 25m + // We need more spells to find a general way (if there is any) + spellInfo.DmgClass = SpellDmgClass.Magic; + break; case 63026: // Summon Aspirant Test NPC (HACK: Target shouldn't be changed) case 63137: // Summon Valiant Test (HACK: Target shouldn't be changed; summon position should be untied from spell destination) spellInfo.GetEffect(0).TargetA = new SpellImplicitTargetInfo(Targets.DestDb); diff --git a/Source/Scripts/Spells/DeathKnight.cs b/Source/Scripts/Spells/DeathKnight.cs index 0448eb8c8..4de6e7633 100644 --- a/Source/Scripts/Spells/DeathKnight.cs +++ b/Source/Scripts/Spells/DeathKnight.cs @@ -271,7 +271,7 @@ namespace Scripts.Spells.DeathKnight int amount = (int)damageInfo.GetDamage() / 2; SpellNonMeleeDamage log = new SpellNonMeleeDamage(drw, drw.GetVictim(), spellInfo, new SpellCastVisual(spellInfo.GetSpellXSpellVisualId(drw), 0), spellInfo.GetSchoolMask()); log.damage = (uint)amount; - drw.DealDamage(drw.GetVictim(), (uint)amount, null, DamageEffectType.SpellDirect, spellInfo.GetSchoolMask(), spellInfo, true); + Unit.DealDamage(drw, drw.GetVictim(), (uint)amount, null, DamageEffectType.SpellDirect, spellInfo.GetSchoolMask(), spellInfo, true); drw.SendSpellNonMeleeDamageLog(log); } diff --git a/Source/Scripts/Spells/Monk.cs b/Source/Scripts/Spells/Monk.cs index 1ae961eda..dc68a517e 100644 --- a/Source/Scripts/Spells/Monk.cs +++ b/Source/Scripts/Spells/Monk.cs @@ -287,7 +287,7 @@ namespace Scripts.Spells.Monk if (auraEff != null) { float total = auraEff.GetAmount(); - float tickDamage = aurEff.GetDamage(); + float tickDamage = aurEff.GetAmount(); auraEff.ChangeAmount((int)(total - tickDamage)); } } @@ -359,7 +359,7 @@ namespace Scripts.Spells.Monk { AuraEffect eff = auraDamage.GetEffect(0); if (eff != null) - eff.SetDamage((int)tickDamage); + eff.ChangeAmount((int)tickDamage); } } }