3165 lines
138 KiB
C#
3165 lines
138 KiB
C#
/*
|
|
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
using Framework.Constants;
|
|
using Game.BattleFields;
|
|
using Game.BattleGrounds;
|
|
using Game.Combat;
|
|
using Game.DataStorage;
|
|
using Game.Groups;
|
|
using Game.Loots;
|
|
using Game.Maps;
|
|
using Game.Network.Packets;
|
|
using Game.PvP;
|
|
using Game.Spells;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics.Contracts;
|
|
using System.Linq;
|
|
|
|
namespace Game.Entities
|
|
{
|
|
public partial class Unit
|
|
{
|
|
// Check if unit in combat with specific unit
|
|
public bool IsInCombatWith(Unit who)
|
|
{
|
|
// Check target exists
|
|
if (!who)
|
|
return false;
|
|
|
|
// Search in threat list
|
|
ObjectGuid guid = who.GetGUID();
|
|
foreach (var refe in GetThreatManager().getThreatList())
|
|
{
|
|
// Return true if the unit matches
|
|
if (refe != null && refe.getUnitGuid() == guid)
|
|
return true;
|
|
}
|
|
|
|
// Nothing found, false.
|
|
return false;
|
|
}
|
|
|
|
public ThreatManager GetThreatManager() { return threatManager; }
|
|
|
|
public bool CanDualWield() { return m_canDualWield; }
|
|
|
|
public void SendChangeCurrentVictim(HostileReference pHostileReference)
|
|
{
|
|
if (!GetThreatManager().isThreatListEmpty())
|
|
{
|
|
HighestThreatUpdate packet = new HighestThreatUpdate();
|
|
packet.UnitGUID = GetGUID();
|
|
packet.HighestThreatGUID = pHostileReference.getUnitGuid();
|
|
|
|
var refeList = GetThreatManager().getThreatList();
|
|
foreach (var refe in refeList)
|
|
{
|
|
ThreatInfo info = new ThreatInfo();
|
|
info.UnitGUID = refe.getUnitGuid();
|
|
info.Threat = (long)refe.getThreat() * 100;
|
|
packet.ThreatList.Add(info);
|
|
}
|
|
SendMessageToSet(packet, false);
|
|
}
|
|
}
|
|
|
|
public void StopAttackFaction(uint factionId)
|
|
{
|
|
Unit victim = GetVictim();
|
|
if (victim != null)
|
|
{
|
|
if (victim.GetFactionTemplateEntry().Faction == factionId)
|
|
{
|
|
AttackStop();
|
|
if (IsNonMeleeSpellCast(false))
|
|
InterruptNonMeleeSpells(false);
|
|
|
|
// melee and ranged forced attack cancel
|
|
if (IsTypeId(TypeId.Player))
|
|
ToPlayer().SendAttackSwingCancelAttack();
|
|
}
|
|
}
|
|
|
|
var attackers = getAttackers();
|
|
for (var i = 0; i < attackers.Count;)
|
|
{
|
|
var unit = attackers[i];
|
|
if (unit.GetFactionTemplateEntry().Faction == factionId)
|
|
{
|
|
unit.AttackStop();
|
|
i = 0;
|
|
}
|
|
else
|
|
++i;
|
|
}
|
|
|
|
getHostileRefManager().deleteReferencesForFaction(factionId);
|
|
|
|
foreach (var control in m_Controlled)
|
|
control.StopAttackFaction(factionId);
|
|
}
|
|
|
|
public void HandleProcExtraAttackFor(Unit victim)
|
|
{
|
|
while (m_extraAttacks != 0)
|
|
{
|
|
AttackerStateUpdate(victim, WeaponAttackType.BaseAttack, true);
|
|
--m_extraAttacks;
|
|
}
|
|
}
|
|
|
|
public virtual void SetCanDualWield(bool value) { m_canDualWield = value; }
|
|
|
|
void SendClearThreatList()
|
|
{
|
|
ThreatClear packet = new ThreatClear();
|
|
packet.UnitGUID = GetGUID();
|
|
SendMessageToSet(packet, false);
|
|
}
|
|
|
|
public void SendRemoveFromThreatList(HostileReference pHostileReference)
|
|
{
|
|
ThreatRemove packet = new ThreatRemove();
|
|
packet.UnitGUID = GetGUID();
|
|
packet.AboutGUID = pHostileReference.getUnitGuid();
|
|
SendMessageToSet(packet, false);
|
|
}
|
|
|
|
void SendThreatListUpdate()
|
|
{
|
|
if (!GetThreatManager().isThreatListEmpty())
|
|
{
|
|
int count = GetThreatManager().getThreatList().Count;
|
|
|
|
ThreatUpdate packet = new ThreatUpdate();
|
|
packet.UnitGUID = GetGUID();
|
|
var tlist = GetThreatManager().getThreatList();
|
|
foreach (var refe in tlist)
|
|
{
|
|
ThreatInfo info = new ThreatInfo();
|
|
info.UnitGUID = refe.getUnitGuid();
|
|
info.Threat = (long)refe.getThreat() * 100;
|
|
packet.ThreatList.Add(info);
|
|
}
|
|
SendMessageToSet(packet, false);
|
|
}
|
|
}
|
|
|
|
public void DeleteThreatList()
|
|
{
|
|
if (CanHaveThreatList(true) && !threatManager.isThreatListEmpty())
|
|
SendClearThreatList();
|
|
threatManager.clearReferences();
|
|
}
|
|
|
|
public void TauntApply(Unit taunter)
|
|
{
|
|
Contract.Assert(IsTypeId(TypeId.Unit));
|
|
|
|
if (!taunter || (taunter.IsTypeId(TypeId.Player) && taunter.ToPlayer().IsGameMaster()))
|
|
return;
|
|
|
|
if (!CanHaveThreatList())
|
|
return;
|
|
|
|
Creature creature = ToCreature();
|
|
|
|
if (creature.HasReactState(ReactStates.Passive))
|
|
return;
|
|
|
|
Unit target = GetVictim();
|
|
if (target && target == taunter)
|
|
return;
|
|
|
|
SetInFront(taunter);
|
|
if (creature.IsAIEnabled)
|
|
creature.GetAI().AttackStart(taunter);
|
|
}
|
|
|
|
public void TauntFadeOut(Unit taunter)
|
|
{
|
|
Contract.Assert(IsTypeId(TypeId.Unit));
|
|
|
|
if (!taunter || (taunter.IsTypeId(TypeId.Player) && taunter.ToPlayer().IsGameMaster()))
|
|
return;
|
|
|
|
if (!CanHaveThreatList())
|
|
return;
|
|
|
|
Creature creature = ToCreature();
|
|
|
|
if (creature.HasReactState(ReactStates.Passive))
|
|
return;
|
|
|
|
Unit target = GetVictim();
|
|
if (!target || target != taunter)
|
|
return;
|
|
|
|
if (threatManager.isThreatListEmpty())
|
|
{
|
|
if (creature.IsAIEnabled)
|
|
creature.GetAI().EnterEvadeMode(EvadeReason.NoHostiles);
|
|
return;
|
|
}
|
|
|
|
target = creature.SelectVictim(); // might have more taunt auras remaining
|
|
|
|
if (target && target != taunter)
|
|
{
|
|
SetInFront(target);
|
|
if (creature.IsAIEnabled)
|
|
creature.GetAI().AttackStart(target);
|
|
}
|
|
}
|
|
|
|
public void CombatStop(bool includingCast = false)
|
|
{
|
|
if (includingCast && IsNonMeleeSpellCast(false))
|
|
InterruptNonMeleeSpells(false);
|
|
|
|
AttackStop();
|
|
RemoveAllAttackers();
|
|
if (IsTypeId(TypeId.Player))
|
|
ToPlayer().SendAttackSwingCancelAttack(); // melee and ranged forced attack cancel
|
|
ClearInCombat();
|
|
}
|
|
public void CombatStopWithPets(bool includingCast = false)
|
|
{
|
|
CombatStop(includingCast);
|
|
|
|
foreach (var control in m_Controlled)
|
|
control.CombatStop(includingCast);
|
|
}
|
|
public void ClearInCombat()
|
|
{
|
|
m_CombatTimer = 0;
|
|
RemoveFlag(UnitFields.Flags, UnitFlags.InCombat);
|
|
|
|
// Reset rune flags after combat
|
|
if (IsTypeId(TypeId.Player) && GetClass() == Class.Deathknight)
|
|
{
|
|
for (byte i = 0; i < PlayerConst.MaxRunes; ++i)
|
|
{
|
|
ToPlayer().SetRuneTimer(i, 0xFFFFFFFF);
|
|
ToPlayer().SetLastRuneGraceTimer(i, 0);
|
|
}
|
|
}
|
|
|
|
// Player's state will be cleared in Player.UpdateContestedPvP
|
|
Creature creature = ToCreature();
|
|
if (creature != null)
|
|
{
|
|
ClearUnitState(UnitState.AttackPlayer);
|
|
if (HasFlag(ObjectFields.DynamicFlags, UnitDynFlags.Tapped))
|
|
SetUInt32Value(ObjectFields.DynamicFlags, creature.GetCreatureTemplate().DynamicFlags);
|
|
|
|
if (creature.IsPet() || creature.IsGuardian())
|
|
{
|
|
Unit owner = GetOwner();
|
|
if (owner)
|
|
for (UnitMoveType i = 0; i < UnitMoveType.Max; ++i)
|
|
if (owner.GetSpeedRate(i) > GetSpeedRate(i))
|
|
SetSpeedRate(i, owner.GetSpeedRate(i));
|
|
}
|
|
else if (!IsCharmed())
|
|
return;
|
|
}
|
|
else
|
|
ToPlayer().OnCombatExit();
|
|
|
|
RemoveFlag(UnitFields.Flags, UnitFlags.PetInCombat);
|
|
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.LeaveCombat);
|
|
}
|
|
|
|
void RemoveAllAttackers()
|
|
{
|
|
while (!attackerList.Empty())
|
|
{
|
|
var iter = attackerList.First();
|
|
if (!iter.AttackStop())
|
|
{
|
|
Log.outError(LogFilter.Unit, "WORLD: Unit has an attacker that isn't attacking it!");
|
|
attackerList.Remove(iter);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void addHatedBy(HostileReference pHostileReference)
|
|
{
|
|
m_HostileRefManager.insertFirst(pHostileReference);
|
|
}
|
|
public void removeHatedBy(HostileReference pHostileReference) { } //nothing to do yet
|
|
|
|
public void AddThreat(Unit victim, float fThreat, SpellSchoolMask schoolMask = SpellSchoolMask.Normal, SpellInfo threatSpell = null)
|
|
{
|
|
// Only mobs can manage threat lists
|
|
if (CanHaveThreatList() && !HasUnitState(UnitState.Evade))
|
|
threatManager.addThreat(victim, fThreat, schoolMask, threatSpell);
|
|
}
|
|
public float ApplyTotalThreatModifier(float fThreat, SpellSchoolMask schoolMask = SpellSchoolMask.Normal)
|
|
{
|
|
if (!HasAuraType(AuraType.ModThreat) || fThreat < 0)
|
|
return fThreat;
|
|
|
|
SpellSchools school = Global.SpellMgr.GetFirstSchoolInMask(schoolMask);
|
|
|
|
return fThreat * m_threatModifier[(int)school];
|
|
}
|
|
|
|
public bool isTargetableForAttack(bool checkFakeDeath = true)
|
|
{
|
|
if (!IsAlive())
|
|
return false;
|
|
|
|
if (HasFlag(UnitFields.Flags, (UnitFlags.NonAttackable | UnitFlags.NotSelectable)))
|
|
return false;
|
|
|
|
if (IsTypeId(TypeId.Player) && ToPlayer().IsGameMaster())
|
|
return false;
|
|
|
|
return !HasUnitState(UnitState.Unattackable) && (!checkFakeDeath || !HasUnitState(UnitState.Died));
|
|
}
|
|
|
|
public DeathState getDeathState()
|
|
{
|
|
return m_deathState;
|
|
}
|
|
public bool IsInCombat()
|
|
{
|
|
return HasFlag(UnitFields.Flags, UnitFlags.InCombat);
|
|
}
|
|
public bool Attack(Unit victim, bool meleeAttack)
|
|
{
|
|
if (victim == null || victim.GetGUID() == GetGUID())
|
|
return false;
|
|
|
|
// dead units can neither attack nor be attacked
|
|
if (!IsAlive() || !victim.IsInWorld || !victim.IsAlive())
|
|
return false;
|
|
|
|
// player cannot attack in mount state
|
|
if (IsTypeId(TypeId.Player) && IsMounted())
|
|
return false;
|
|
|
|
if (HasFlag(UnitFields.Flags, UnitFlags.Pacified))
|
|
return false;
|
|
|
|
// nobody can attack GM in GM-mode
|
|
if (victim.IsTypeId(TypeId.Player))
|
|
{
|
|
if (victim.ToPlayer().IsGameMaster())
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if (victim.ToCreature().IsEvadingAttacks())
|
|
return false;
|
|
}
|
|
|
|
// remove SPELL_AURA_MOD_UNATTACKABLE at attack (in case non-interruptible spells stun aura applied also that not let attack)
|
|
if (HasAuraType(AuraType.ModUnattackable))
|
|
RemoveAurasByType(AuraType.ModUnattackable);
|
|
|
|
if (m_attacking != null)
|
|
{
|
|
if (m_attacking == victim)
|
|
{
|
|
// switch to melee attack from ranged/magic
|
|
if (meleeAttack)
|
|
{
|
|
if (!HasUnitState(UnitState.MeleeAttacking))
|
|
{
|
|
AddUnitState(UnitState.MeleeAttacking);
|
|
SendMeleeAttackStart(victim);
|
|
return true;
|
|
}
|
|
}
|
|
else if (HasUnitState(UnitState.MeleeAttacking))
|
|
{
|
|
ClearUnitState(UnitState.MeleeAttacking);
|
|
SendMeleeAttackStop(victim);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// switch target
|
|
InterruptSpell(CurrentSpellTypes.Melee);
|
|
if (!meleeAttack)
|
|
ClearUnitState(UnitState.MeleeAttacking);
|
|
}
|
|
|
|
if (m_attacking != null)
|
|
m_attacking._removeAttacker(this);
|
|
|
|
m_attacking = victim;
|
|
m_attacking._addAttacker(this);
|
|
|
|
// Set our target
|
|
SetTarget(victim.GetGUID());
|
|
|
|
if (meleeAttack)
|
|
AddUnitState(UnitState.MeleeAttacking);
|
|
|
|
if (IsTypeId(TypeId.Unit) && !IsPet())
|
|
{
|
|
// should not let player enter combat by right clicking target - doesn't helps
|
|
AddThreat(victim, 0.0f);
|
|
SetInCombatWith(victim);
|
|
|
|
if (victim.IsTypeId(TypeId.Player))
|
|
victim.SetInCombatWith(this);
|
|
|
|
ToCreature().SendAIReaction(AiReaction.Hostile);
|
|
ToCreature().CallAssistance();
|
|
|
|
// Remove emote state - will be restored on creature reset
|
|
SetUInt32Value(UnitFields.NpcEmotestate, (uint)Emote.OneshotNone);
|
|
}
|
|
|
|
// delay offhand weapon attack to next attack time
|
|
if (haveOffhandWeapon() && GetTypeId() != TypeId.Player)
|
|
resetAttackTimer(WeaponAttackType.OffAttack);
|
|
|
|
if (meleeAttack)
|
|
SendMeleeAttackStart(victim);
|
|
|
|
// Let the pet know we've started attacking someting. Handles melee attacks only
|
|
// Spells such as auto-shot and others handled in WorldSession.HandleCastSpellOpcode
|
|
if (IsTypeId(TypeId.Player))
|
|
{
|
|
Pet playerPet = ToPlayer().GetPet();
|
|
|
|
if (playerPet != null && playerPet.IsAlive())
|
|
playerPet.GetAI().OwnerAttacked(victim);
|
|
}
|
|
return true;
|
|
}
|
|
public void SendMeleeAttackStart(Unit victim)
|
|
{
|
|
AttackStart packet = new AttackStart();
|
|
packet.Attacker = GetGUID();
|
|
packet.Victim = victim.GetGUID();
|
|
SendMessageToSet(packet, true);
|
|
}
|
|
public void SendMeleeAttackStop(Unit victim = null)
|
|
{
|
|
SendMessageToSet(new SAttackStop(this, victim), true);
|
|
|
|
if (victim)
|
|
Log.outInfo(LogFilter.Unit, "{0} {1} stopped attacking {2} {3}", (IsTypeId(TypeId.Player) ? "Player" : "Creature"), GetGUID().ToString(),
|
|
(victim.IsTypeId(TypeId.Player) ? "player" : "creature"), victim.GetGUID().ToString());
|
|
else
|
|
Log.outInfo(LogFilter.Unit, "{0} {1} stopped attacking", (IsTypeId(TypeId.Player) ? "Player" : "Creature"), GetGUID().ToString());
|
|
}
|
|
public ObjectGuid GetTarget() { return GetGuidValue(UnitFields.Target); }
|
|
public virtual void SetTarget(ObjectGuid guid) { }
|
|
public bool AttackStop()
|
|
{
|
|
if (m_attacking == null)
|
|
return false;
|
|
|
|
Unit victim = m_attacking;
|
|
|
|
m_attacking._removeAttacker(this);
|
|
m_attacking = null;
|
|
|
|
// Clear our target
|
|
SetTarget(ObjectGuid.Empty);
|
|
|
|
ClearUnitState(UnitState.MeleeAttacking);
|
|
|
|
InterruptSpell(CurrentSpellTypes.Melee);
|
|
|
|
// reset only at real combat stop
|
|
Creature creature = ToCreature();
|
|
if (creature != null)
|
|
{
|
|
creature.SetNoCallAssistance(false);
|
|
|
|
if (creature.HasSearchedAssistance())
|
|
{
|
|
creature.SetNoSearchAssistance(false);
|
|
UpdateSpeed(UnitMoveType.Run);
|
|
}
|
|
}
|
|
|
|
SendMeleeAttackStop(victim);
|
|
return true;
|
|
}
|
|
void _addAttacker(Unit pAttacker)
|
|
{
|
|
attackerList.Add(pAttacker);
|
|
}
|
|
void _removeAttacker(Unit pAttacker)
|
|
{
|
|
attackerList.Remove(pAttacker);
|
|
}
|
|
public Unit GetVictim()
|
|
{
|
|
return m_attacking;
|
|
}
|
|
public Unit getAttackerForHelper()
|
|
{
|
|
if (GetVictim() != null)
|
|
return GetVictim();
|
|
|
|
if (attackerList.Count != 0)
|
|
return attackerList[0];
|
|
|
|
return null;
|
|
}
|
|
public List<Unit> getAttackers()
|
|
{
|
|
return attackerList;
|
|
}
|
|
|
|
public float GetCombatReach() { return GetFloatValue(UnitFields.CombatReach); }
|
|
float GetBoundaryRadius() { return GetFloatValue(UnitFields.BoundingRadius); }
|
|
|
|
public bool haveOffhandWeapon()
|
|
{
|
|
if (IsTypeId(TypeId.Player))
|
|
return ToPlayer().GetWeaponForAttack(WeaponAttackType.OffAttack, true) != null;
|
|
else
|
|
return m_canDualWield;
|
|
}
|
|
public void resetAttackTimer(WeaponAttackType type = WeaponAttackType.BaseAttack)
|
|
{
|
|
m_attackTimer[(int)type] = (uint)(GetBaseAttackTime(type) * m_modAttackSpeedPct[(int)type]);
|
|
}
|
|
public void setAttackTimer(WeaponAttackType type, uint time)
|
|
{
|
|
m_attackTimer[(int)type] = time;
|
|
}
|
|
public uint getAttackTimer(WeaponAttackType type)
|
|
{
|
|
return m_attackTimer[(int)type];
|
|
}
|
|
public bool isAttackReady(WeaponAttackType type = WeaponAttackType.BaseAttack)
|
|
{
|
|
return m_attackTimer[(int)type] == 0;
|
|
}
|
|
public uint GetBaseAttackTime(WeaponAttackType att)
|
|
{
|
|
return m_baseAttackSpeed[(int)att];
|
|
}
|
|
public void AttackerStateUpdate(Unit victim, WeaponAttackType attType = WeaponAttackType.BaseAttack, bool extra = false)
|
|
{
|
|
if (HasUnitState(UnitState.CannotAutoattack) || HasFlag(UnitFields.Flags, UnitFlags.Pacified))
|
|
return;
|
|
|
|
if (!victim.IsAlive())
|
|
return;
|
|
|
|
if ((attType == WeaponAttackType.BaseAttack || attType == WeaponAttackType.OffAttack) && !IsWithinLOSInMap(victim))
|
|
return;
|
|
|
|
CombatStart(victim);
|
|
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.MeleeAttack);
|
|
|
|
// ignore ranged case
|
|
if (attType != WeaponAttackType.BaseAttack && attType != WeaponAttackType.OffAttack)
|
|
return;
|
|
|
|
if (IsTypeId(TypeId.Unit) && !HasFlag(UnitFields.Flags, UnitFlags.PlayerControlled))
|
|
SetFacingToObject(victim); // update client side facing to face the target (prevents visual glitches when casting untargeted spells)
|
|
|
|
// melee attack spell casted at main hand attack only - no normal melee dmg dealt
|
|
if (attType == WeaponAttackType.BaseAttack && GetCurrentSpell(CurrentSpellTypes.Melee) != null && !extra)
|
|
m_currentSpells[CurrentSpellTypes.Melee].cast();
|
|
else
|
|
{
|
|
// attack can be redirected to another target
|
|
victim = GetMeleeHitRedirectTarget(victim);
|
|
|
|
CalcDamageInfo damageInfo;
|
|
CalculateMeleeDamage(victim, 0, out damageInfo, attType);
|
|
// Send log damage message to client
|
|
DealDamageMods(victim, ref damageInfo.damage, ref damageInfo.absorb);
|
|
SendAttackStateUpdate(damageInfo);
|
|
|
|
DamageInfo dmgInfo = new DamageInfo(damageInfo);
|
|
ProcSkillsAndAuras(damageInfo.target, damageInfo.procAttacker, damageInfo.procVictim, ProcFlagsSpellType.None, ProcFlagsSpellPhase.None, dmgInfo.GetHitMask(), null, dmgInfo, null);
|
|
|
|
DealMeleeDamage(damageInfo, true);
|
|
|
|
if (IsTypeId(TypeId.Player))
|
|
Log.outDebug(LogFilter.Unit, "AttackerStateUpdate: (Player) {0} attacked {1} (TypeId: {2}) for {3} dmg, absorbed {4}, blocked {5}, resisted {6}.",
|
|
GetGUID().ToString(), victim.GetGUID().ToString(), victim.GetTypeId(), damageInfo.damage, damageInfo.absorb, damageInfo.blocked_amount, damageInfo.resist);
|
|
else
|
|
Log.outDebug(LogFilter.Unit, "AttackerStateUpdate: (NPC) {0} attacked {1} (TypeId: {2}) for {3} dmg, absorbed {4}, blocked {5}, resisted {6}.",
|
|
GetGUID().ToString(), victim.GetGUID().ToString(), victim.GetTypeId(), damageInfo.damage, damageInfo.absorb, damageInfo.blocked_amount, damageInfo.resist);
|
|
}
|
|
}
|
|
|
|
public void FakeAttackerStateUpdate(Unit victim, WeaponAttackType attType = WeaponAttackType.BaseAttack)
|
|
{
|
|
if (HasUnitState(UnitState.CannotAutoattack) || HasFlag(UnitFields.Flags, UnitFlags.Pacified))
|
|
return;
|
|
|
|
if (!victim.IsAlive())
|
|
return;
|
|
|
|
if ((attType == WeaponAttackType.BaseAttack || attType == WeaponAttackType.OffAttack) && !IsWithinLOSInMap(victim))
|
|
return;
|
|
|
|
CombatStart(victim);
|
|
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.MeleeAttack);
|
|
|
|
if (attType != WeaponAttackType.BaseAttack && attType != WeaponAttackType.OffAttack)
|
|
return; // ignore ranged case
|
|
|
|
if (IsTypeId(TypeId.Unit) && !HasFlag(UnitFields.Flags, UnitFlags.PlayerControlled))
|
|
SetFacingToObject(victim); // update client side facing to face the target (prevents visual glitches when casting untargeted spells)
|
|
|
|
CalcDamageInfo damageInfo = new CalcDamageInfo();
|
|
damageInfo.attacker = this;
|
|
damageInfo.target = victim;
|
|
damageInfo.damageSchoolMask = (uint)GetMeleeDamageSchoolMask();
|
|
damageInfo.attackType = attType;
|
|
damageInfo.damage = 0;
|
|
damageInfo.cleanDamage = 0;
|
|
damageInfo.absorb = 0;
|
|
damageInfo.resist = 0;
|
|
damageInfo.blocked_amount = 0;
|
|
|
|
damageInfo.TargetState = VictimState.Hit;
|
|
damageInfo.HitInfo = HitInfo.AffectsVictim | HitInfo.NormalSwing | HitInfo.FakeDamage;
|
|
if (attType == WeaponAttackType.OffAttack)
|
|
damageInfo.HitInfo |= HitInfo.OffHand;
|
|
|
|
damageInfo.procAttacker = ProcFlags.None;
|
|
damageInfo.procVictim = ProcFlags.None;
|
|
damageInfo.hitOutCome = MeleeHitOutcome.Normal;
|
|
|
|
SendAttackStateUpdate(damageInfo);
|
|
}
|
|
|
|
public void SetBaseWeaponDamage(WeaponAttackType attType, WeaponDamageRange damageRange, float value) { m_weaponDamage[(int)attType][(int)damageRange] = value; }
|
|
|
|
void StartReactiveTimer(ReactiveType reactive) { m_reactiveTimer[reactive] = 4000; }
|
|
|
|
public Unit GetMagicHitRedirectTarget(Unit victim, SpellInfo spellInfo)
|
|
{
|
|
// Patch 1.2 notes: Spell Reflection no longer reflects abilities
|
|
if (spellInfo.HasAttribute(SpellAttr0.Ability) || spellInfo.HasAttribute(SpellAttr1.CantBeRedirected) || spellInfo.HasAttribute(SpellAttr0.UnaffectedByInvulnerability))
|
|
return victim;
|
|
|
|
var magnetAuras = victim.GetAuraEffectsByType(AuraType.SpellMagnet);
|
|
foreach (var eff in magnetAuras)
|
|
{
|
|
Unit magnet = eff.GetBase().GetCaster();
|
|
if (magnet != null)
|
|
{
|
|
if (spellInfo.CheckExplicitTarget(this, magnet) == SpellCastResult.SpellCastOk && _IsValidAttackTarget(magnet, spellInfo))
|
|
{
|
|
// @todo handle this charge drop by proc in cast phase on explicit target
|
|
if (spellInfo.Speed > 0.0f)
|
|
{
|
|
// Set up missile speed based delay
|
|
uint delay = (uint)Math.Floor(Math.Max(victim.GetDistance(this), 5.0f) / spellInfo.Speed * 1000.0f);
|
|
// Schedule charge drop
|
|
eff.GetBase().DropChargeDelayed(delay, AuraRemoveMode.Expire);
|
|
}
|
|
else
|
|
eff.GetBase().DropCharge(AuraRemoveMode.Expire);
|
|
return magnet;
|
|
}
|
|
}
|
|
}
|
|
return victim;
|
|
}
|
|
public Unit GetMeleeHitRedirectTarget(Unit victim, SpellInfo spellInfo = null)
|
|
{
|
|
var hitTriggerAuras = victim.GetAuraEffectsByType(AuraType.AddCasterHitTrigger);
|
|
foreach (var i in hitTriggerAuras)
|
|
{
|
|
Unit magnet = i.GetCaster();
|
|
if (magnet != null)
|
|
if (_IsValidAttackTarget(magnet, spellInfo) && magnet.IsWithinLOSInMap(this)
|
|
&& (spellInfo == null || (spellInfo.CheckExplicitTarget(this, magnet) == SpellCastResult.SpellCastOk
|
|
&& spellInfo.CheckTarget(this, magnet, false) == SpellCastResult.SpellCastOk)))
|
|
if (RandomHelper.randChance(i.GetAmount()))
|
|
{
|
|
i.GetBase().DropCharge(AuraRemoveMode.Expire);
|
|
return magnet;
|
|
}
|
|
}
|
|
return victim;
|
|
}
|
|
public bool IsValidAttackTarget(Unit target)
|
|
{
|
|
return _IsValidAttackTarget(target, null);
|
|
}
|
|
|
|
void DealDamageMods(Unit victim, ref uint damage)
|
|
{
|
|
if (victim == null || !victim.IsAlive() || victim.HasUnitState(UnitState.InFlight)
|
|
|| (victim.IsTypeId(TypeId.Unit) && victim.ToCreature().IsInEvadeMode()))
|
|
{
|
|
damage = 0;
|
|
}
|
|
}
|
|
public void DealDamageMods(Unit victim, ref uint damage, ref uint absorb)
|
|
{
|
|
if (victim == null || !victim.IsAlive() || victim.HasUnitState(UnitState.InFlight)
|
|
|| (victim.IsTypeId(TypeId.Unit) && victim.ToCreature().IsEvadingAttacks()))
|
|
{
|
|
absorb += damage;
|
|
damage = 0;
|
|
return;
|
|
}
|
|
|
|
damage *= (uint)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 CleanDamage(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())
|
|
{
|
|
// -probability is between 0% and 40%
|
|
// 20% base chance
|
|
float Probability = 20.0f;
|
|
|
|
// there is a newbie protection, at level 10 just 7% base chance; assuming linear function
|
|
if (victim.getLevel() < 30)
|
|
Probability = 0.65f * victim.GetLevelForTarget(this) + 0.5f;
|
|
|
|
uint VictimDefense = victim.GetMaxSkillValueForLevel(this);
|
|
uint AttackerMeleeSkill = GetMaxSkillValueForLevel();
|
|
|
|
Probability *= (float)(AttackerMeleeSkill / VictimDefense * 0.16);
|
|
|
|
if (Probability < 0)
|
|
Probability = 0;
|
|
|
|
if (Probability > 40.0f)
|
|
Probability = 40.0f;
|
|
|
|
if (RandomHelper.randChance(Probability))
|
|
CastSpell(victim, 1604, true);
|
|
}
|
|
|
|
if (IsTypeId(TypeId.Player))
|
|
{
|
|
DamageInfo dmgInfo = new DamageInfo(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 i_spellProto = dmgShield.GetSpellInfo();
|
|
// Damage shield can be resisted...
|
|
var missInfo = victim.SpellHitResult(this, i_spellProto, false);
|
|
if (missInfo != 0)
|
|
{
|
|
victim.SendSpellMiss(this, i_spellProto.Id, missInfo);
|
|
continue;
|
|
}
|
|
|
|
// ...or immuned
|
|
if (IsImmunedToDamage(i_spellProto))
|
|
{
|
|
victim.SendSpellDamageImmune(this, i_spellProto.Id, false);
|
|
continue;
|
|
}
|
|
|
|
uint damage = (uint)dmgShield.GetAmount();
|
|
Unit caster = dmgShield.GetCaster();
|
|
if (caster)
|
|
{
|
|
damage = caster.SpellDamageBonusDone(this, i_spellProto, damage, DamageEffectType.SpellDirect, dmgShield.GetSpellEffectInfo());
|
|
damage = SpellDamageBonusTaken(caster, i_spellProto, damage, DamageEffectType.SpellDirect, dmgShield.GetSpellEffectInfo());
|
|
}
|
|
|
|
uint absorb = 0;
|
|
uint resist = 0;
|
|
victim.CalcAbsorbResist(this, i_spellProto.SchoolMask, DamageEffectType.SpellDirect, damage, ref absorb, ref resist, i_spellProto);
|
|
// No Unit.CalcAbsorbResist here - opcode doesn't send that data - this damage is probably not affected by that
|
|
victim.DealDamageMods(this, ref damage);
|
|
|
|
SpellDamageShield damageShield = new SpellDamageShield();
|
|
damageShield.Attacker = victim.GetGUID();
|
|
damageShield.Defender = GetGUID();
|
|
damageShield.SpellID = i_spellProto.Id;
|
|
damageShield.TotalDamage = damage;
|
|
damageShield.OverKill = (uint)Math.Max(damage - GetHealth(), 0);
|
|
damageShield.SchoolMask = (uint)i_spellProto.SchoolMask;
|
|
damageShield.LogAbsorbed = absorb;
|
|
|
|
victim.DealDamage(this, damage, null, DamageEffectType.SpellDirect, i_spellProto.GetSchoolMask(), i_spellProto, 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)
|
|
{
|
|
if (victim.IsAIEnabled)
|
|
victim.GetAI().DamageTaken(this, ref damage);
|
|
|
|
if (IsAIEnabled)
|
|
GetAI().DamageDealt(victim, ref damage, damagetype);
|
|
|
|
// Hook for OnDamage Event
|
|
Global.ScriptMgr.OnDamage(this, victim, ref damage);
|
|
|
|
if (victim.IsTypeId(TypeId.Player) && this != victim)
|
|
{
|
|
// Signal to pets that their owner was attacked - except when DOT.
|
|
if (damagetype != DamageEffectType.DOT)
|
|
{
|
|
Pet pet = victim.ToPlayer().GetPet();
|
|
|
|
if (pet != null && pet.IsAlive())
|
|
pet.GetAI().OwnerAttackedBy(this);
|
|
}
|
|
|
|
if (victim.ToPlayer().GetCommandStatus(PlayerCommandStates.God))
|
|
return 0;
|
|
}
|
|
|
|
// Signal the pet it was attacked so the AI can respond if needed
|
|
if (victim.IsTypeId(TypeId.Unit) && this != victim && victim.IsPet() && victim.IsAlive())
|
|
victim.ToPet().GetAI().AttackedBy(this);
|
|
|
|
if (damagetype != DamageEffectType.NoDamage)
|
|
{
|
|
// interrupting auras with AURA_INTERRUPT_FLAG_DAMAGE before checking !damage (absorbed damage breaks that type of auras)
|
|
if (spellProto != null)
|
|
{
|
|
if (!spellProto.HasAttribute(SpellAttr4.DamageDoesntBreakAuras))
|
|
victim.RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.TakeDamage, spellProto.Id);
|
|
}
|
|
else
|
|
victim.RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.TakeDamage, 0);
|
|
|
|
// interrupt spells with SPELL_INTERRUPT_FLAG_ABORT_ON_DMG on absorbed damage (no dots)
|
|
if (damage == 0 && damagetype != DamageEffectType.DOT && cleanDamage != null && cleanDamage.absorbed_damage != 0)
|
|
{
|
|
if (victim != this && victim.IsTypeId(TypeId.Player))
|
|
{
|
|
Spell spell = victim.GetCurrentSpell(CurrentSpellTypes.Generic);
|
|
if (spell)
|
|
{
|
|
if (spell.getState() == SpellState.Preparing)
|
|
{
|
|
SpellInterruptFlags interruptFlags = spell.m_spellInfo.InterruptFlags;
|
|
if (interruptFlags.HasAnyFlag(SpellInterruptFlags.AbortOnDmg))
|
|
victim.InterruptNonMeleeSpells(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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 vCopyDamageCopy = victim.GetAuraEffectsByType(AuraType.ShareDamagePct);
|
|
// copy damage to casters of this aura
|
|
foreach (var aura in vCopyDamageCopy)
|
|
{
|
|
// Check if aura was removed during iteration - we don't need to work on such auras
|
|
if (!(aura.GetBase().IsAppliedOnTarget(victim.GetGUID())))
|
|
continue;
|
|
|
|
// check damage school mask
|
|
if ((aura.GetMiscValue() & (int)damageSchoolMask) == 0)
|
|
continue;
|
|
|
|
Unit shareDamageTarget = aura.GetCaster();
|
|
if (shareDamageTarget == null)
|
|
continue;
|
|
SpellInfo spell = aura.GetSpellInfo();
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
uint rage = (uint)(GetBaseAttackTime(cleanDamage.attackType) / 1000.0f * 1.75f);
|
|
if (cleanDamage.attackType == WeaponAttackType.OffAttack)
|
|
rage /= 2;
|
|
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))
|
|
{
|
|
// 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())
|
|
damage = health - 1;
|
|
|
|
duel_hasEnded = true;
|
|
}
|
|
else if (victim.IsVehicle() && damage >= (health - 1) && victim.GetCharmer() != null && victim.GetCharmer().IsTypeId(TypeId.Player))
|
|
{
|
|
Player victimRider = victim.GetCharmer().ToPlayer();
|
|
if (victimRider != null && victimRider.duel != null && victimRider.duel.isMounted)
|
|
{
|
|
// 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())
|
|
damage = health - 1;
|
|
|
|
duel_wasMounted = true;
|
|
duel_hasEnded = true;
|
|
}
|
|
}
|
|
|
|
if (IsTypeId(TypeId.Player) && this != victim)
|
|
{
|
|
Player killer = ToPlayer();
|
|
|
|
// in bg, count dmg if victim is also a player
|
|
if (victim.IsTypeId(TypeId.Player))
|
|
{
|
|
Battleground bg = killer.GetBattleground();
|
|
if (bg)
|
|
bg.UpdatePlayerScore(killer, ScoreType.DamageDone, damage);
|
|
}
|
|
|
|
killer.UpdateCriteria(CriteriaTypes.DamageDone, health > damage ? damage : health, 0, 0, victim);
|
|
killer.UpdateCriteria(CriteriaTypes.HighestHitDealt, damage);
|
|
}
|
|
|
|
if (victim.IsTypeId(TypeId.Player))
|
|
{
|
|
victim.ToPlayer().UpdateCriteria(CriteriaTypes.HighestHitReceived, damage);
|
|
}
|
|
else if (!victim.IsControlledByPlayer() || victim.IsVehicle())
|
|
{
|
|
if (!victim.ToCreature().hasLootRecipient())
|
|
victim.ToCreature().SetLootRecipient(this);
|
|
|
|
if (IsControlledByPlayer())
|
|
victim.ToCreature().LowerPlayerDamageReq(health < damage ? health : damage);
|
|
}
|
|
|
|
damage /= (uint)victim.GetHealthMultiplierForTarget(this);
|
|
|
|
if (health <= damage)
|
|
{
|
|
Log.outDebug(LogFilter.Unit, "DealDamage: victim just died");
|
|
|
|
if (victim.IsTypeId(TypeId.Player) && victim != this)
|
|
victim.ToPlayer().UpdateCriteria(CriteriaTypes.TotalDamageReceived, health);
|
|
|
|
Kill(victim, durabilityLoss);
|
|
}
|
|
else
|
|
{
|
|
Log.outDebug(LogFilter.Unit, "DealDamageAlive");
|
|
|
|
if (victim.IsTypeId(TypeId.Player))
|
|
victim.ToPlayer().UpdateCriteria(CriteriaTypes.TotalDamageReceived, damage);
|
|
|
|
victim.ModifyHealth(-(int)damage);
|
|
|
|
if (damagetype == DamageEffectType.Direct || damagetype == DamageEffectType.SpellDirect)
|
|
{
|
|
victim.RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.DirectDamage, spellProto != null ? spellProto.Id : 0);
|
|
victim.UpdateLastDamagedTime(spellProto);
|
|
}
|
|
|
|
if (!victim.IsTypeId(TypeId.Player))
|
|
{
|
|
victim.AddThreat(this, damage, damageSchoolMask, spellProto);
|
|
}
|
|
else // victim is a player
|
|
{
|
|
// random durability for items (HIT TAKEN)
|
|
if (WorldConfig.GetFloatValue(WorldCfg.RateDurabilityLossDamage) > RandomHelper.randChance())
|
|
{
|
|
byte slot = (byte)RandomHelper.IRand(0, EquipmentSlot.End - 1);
|
|
victim.ToPlayer().DurabilityPointLossForEquipSlot(slot);
|
|
}
|
|
}
|
|
|
|
if (IsTypeId(TypeId.Player))
|
|
{
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
if (damagetype != DamageEffectType.NoDamage && damage != 0)
|
|
{
|
|
if (victim != this && victim.IsTypeId(TypeId.Player) && // does not support creature push_back
|
|
(spellProto == null || !(spellProto.HasAttribute(SpellAttr7.NoPushbackOnDamage))))
|
|
{
|
|
if (damagetype != DamageEffectType.DOT)
|
|
{
|
|
Spell spell = victim.GetCurrentSpell(CurrentSpellTypes.Generic);
|
|
if (spell != null)
|
|
if (spell.getState() == SpellState.Preparing)
|
|
{
|
|
var interruptFlags = spell.m_spellInfo.InterruptFlags;
|
|
if (interruptFlags.HasAnyFlag(SpellInterruptFlags.AbortOnDmg))
|
|
victim.InterruptNonMeleeSpells(false);
|
|
else if (interruptFlags.HasAnyFlag(SpellInterruptFlags.PushBack))
|
|
spell.Delayed();
|
|
}
|
|
}
|
|
Spell spell1 = victim.GetCurrentSpell(CurrentSpellTypes.Channeled);
|
|
if (spell1 != null)
|
|
if (spell1.getState() == SpellState.Casting)
|
|
{
|
|
var channelInterruptFlags = spell1.m_spellInfo.ChannelInterruptFlags;
|
|
if (((channelInterruptFlags & SpellChannelInterruptFlags.Delay) != 0) && (damagetype != DamageEffectType.DOT))
|
|
spell1.DelayedChannel();
|
|
}
|
|
}
|
|
}
|
|
// last damage from duel opponent
|
|
if (duel_hasEnded)
|
|
{
|
|
Player he = duel_wasMounted ? victim.GetCharmer().ToPlayer() : victim.ToPlayer();
|
|
|
|
Contract.Assert(he && he.duel != null);
|
|
|
|
if (duel_wasMounted) // In this case victim==mount
|
|
victim.SetHealth(1);
|
|
else
|
|
he.SetHealth(1);
|
|
|
|
he.duel.opponent.CombatStopWithPets(true);
|
|
he.CombatStopWithPets(true);
|
|
|
|
he.CastSpell(he, 7267, true); // beg
|
|
he.DuelComplete(DuelCompleteType.Won);
|
|
}
|
|
}
|
|
|
|
Log.outDebug(LogFilter.Unit, "DealDamageEnd returned {0} damage", damage);
|
|
|
|
return damage;
|
|
}
|
|
|
|
public long ModifyHealth(long dVal)
|
|
{
|
|
long gain = 0;
|
|
|
|
if (dVal == 0)
|
|
return 0;
|
|
|
|
long curHealth = (long)GetHealth();
|
|
|
|
long val = dVal + curHealth;
|
|
if (val <= 0)
|
|
{
|
|
SetHealth(0);
|
|
return -curHealth;
|
|
}
|
|
|
|
long maxHealth = (long)GetMaxHealth();
|
|
if (val < maxHealth)
|
|
{
|
|
SetHealth((ulong)val);
|
|
gain = val - curHealth;
|
|
}
|
|
else if (curHealth != maxHealth)
|
|
{
|
|
SetHealth((ulong)maxHealth);
|
|
gain = maxHealth - curHealth;
|
|
}
|
|
|
|
if (dVal < 0)
|
|
{
|
|
HealthUpdate packet = new HealthUpdate();
|
|
packet.Guid = GetGUID();
|
|
packet.Health = (long)GetHealth();
|
|
|
|
Player player = GetCharmerOrOwnerPlayerOrPlayerItself();
|
|
if (player)
|
|
player.SendPacket(packet);
|
|
}
|
|
|
|
return gain;
|
|
}
|
|
public long GetHealthGain(long dVal)
|
|
{
|
|
long gain = 0;
|
|
|
|
if (dVal == 0)
|
|
return 0;
|
|
|
|
long curHealth = (long)GetHealth();
|
|
|
|
long val = dVal + curHealth;
|
|
if (val <= 0)
|
|
{
|
|
return -curHealth;
|
|
}
|
|
|
|
long maxHealth = (long)GetMaxHealth();
|
|
|
|
if (val < maxHealth)
|
|
gain = dVal;
|
|
else if (curHealth != maxHealth)
|
|
gain = maxHealth - curHealth;
|
|
|
|
return gain;
|
|
}
|
|
|
|
public void SendAttackStateUpdate(HitInfo HitInfo, Unit target, SpellSchoolMask damageSchoolMask, uint Damage, uint AbsorbDamage, uint Resist, VictimState TargetState, uint BlockedAmount)
|
|
{
|
|
CalcDamageInfo dmgInfo = new CalcDamageInfo();
|
|
dmgInfo.HitInfo = HitInfo;
|
|
dmgInfo.attacker = this;
|
|
dmgInfo.target = target;
|
|
dmgInfo.damage = Damage - AbsorbDamage - Resist - BlockedAmount;
|
|
dmgInfo.damageSchoolMask = (uint)damageSchoolMask;
|
|
dmgInfo.absorb = AbsorbDamage;
|
|
dmgInfo.resist = Resist;
|
|
dmgInfo.TargetState = TargetState;
|
|
dmgInfo.blocked_amount = BlockedAmount;
|
|
SendAttackStateUpdate(dmgInfo);
|
|
}
|
|
public void SendAttackStateUpdate(CalcDamageInfo damageInfo)
|
|
{
|
|
AttackerStateUpdate packet = new AttackerStateUpdate();
|
|
packet.hitInfo = damageInfo.HitInfo;
|
|
packet.AttackerGUID = damageInfo.attacker.GetGUID();
|
|
packet.VictimGUID = damageInfo.target.GetGUID();
|
|
packet.Damage = (int)damageInfo.damage;
|
|
int overkill = (int)(damageInfo.damage - damageInfo.target.GetHealth());
|
|
packet.OverDamage = (overkill < 0 ? -1 : overkill);
|
|
|
|
SubDamage subDmg = new SubDamage();
|
|
subDmg.SchoolMask = (int)damageInfo.damageSchoolMask; // School of sub damage
|
|
subDmg.FDamage = damageInfo.damage; // sub damage
|
|
subDmg.Damage = (int)damageInfo.damage; // Sub Damage
|
|
subDmg.Absorbed = (int)damageInfo.absorb;
|
|
subDmg.Resisted = (int)damageInfo.resist;
|
|
packet.SubDmg.Set(subDmg);
|
|
|
|
packet.VictimState = (byte)damageInfo.TargetState;
|
|
packet.BlockAmount = (int)damageInfo.blocked_amount;
|
|
packet.LogData.Initialize(damageInfo.attacker);
|
|
|
|
SandboxScalingData sandboxScalingData = new SandboxScalingData();
|
|
if (sandboxScalingData.GenerateDataForUnits(damageInfo.attacker, damageInfo.target))
|
|
packet.SandboxScaling = sandboxScalingData;
|
|
|
|
SendCombatLogMessage(packet);
|
|
}
|
|
public void CombatStart(Unit target, bool initialAggro = true)
|
|
{
|
|
if (initialAggro)
|
|
{
|
|
if (!target.IsStandState())
|
|
target.SetStandState(UnitStandStateType.Stand);
|
|
|
|
if (!target.IsInCombat() && !target.IsTypeId(TypeId.Player)
|
|
&& !target.ToCreature().HasReactState(ReactStates.Passive) && target.ToCreature().IsAIEnabled)
|
|
{
|
|
if (target.IsPet())
|
|
target.ToCreature().GetAI().AttackedBy(this); // PetAI has special handler before AttackStart()
|
|
else
|
|
target.ToCreature().GetAI().AttackStart(this);
|
|
}
|
|
|
|
SetInCombatWith(target);
|
|
target.SetInCombatWith(this);
|
|
}
|
|
Unit who = target.GetCharmerOrOwnerOrSelf();
|
|
if (who.IsTypeId(TypeId.Player))
|
|
SetContestedPvP(who.ToPlayer());
|
|
|
|
Player me = GetCharmerOrOwnerPlayerOrPlayerItself();
|
|
if (me != null && who.IsPvP() && (!who.IsTypeId(TypeId.Player) || me.duel == null || me.duel.opponent != who))
|
|
{
|
|
me.UpdatePvP(true);
|
|
me.RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.EnterPvpCombat);
|
|
}
|
|
}
|
|
public void SetInCombatWith(Unit enemy)
|
|
{
|
|
Unit eOwner = enemy.GetCharmerOrOwnerOrSelf();
|
|
if (eOwner.IsPvP())
|
|
{
|
|
SetInCombatState(true, enemy);
|
|
return;
|
|
}
|
|
|
|
// check for duel
|
|
if (eOwner.IsTypeId(TypeId.Player) && eOwner.ToPlayer().duel != null)
|
|
{
|
|
Unit myOwner = GetCharmerOrOwnerOrSelf();
|
|
if (((Player)eOwner).duel.opponent == myOwner)
|
|
{
|
|
SetInCombatState(true, enemy);
|
|
return;
|
|
}
|
|
}
|
|
SetInCombatState(false, enemy);
|
|
}
|
|
public void SetInCombatState(bool PvP, Unit enemy = null)
|
|
{
|
|
// only alive units can be in combat
|
|
if (!IsAlive())
|
|
return;
|
|
|
|
if (PvP)
|
|
m_CombatTimer = 5000;
|
|
|
|
if (IsInCombat() || HasUnitState(UnitState.Evade))
|
|
return;
|
|
|
|
SetFlag(UnitFields.Flags, 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().EnterCombat(enemy);
|
|
|
|
if (creature.GetFormation() != null)
|
|
creature.GetFormation().MemberAttackStart(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);
|
|
unit.SetFlag(UnitFields.Flags, UnitFlags.PetInCombat);
|
|
}
|
|
}
|
|
|
|
internal void SendCombatLogMessage(CombatLogServerPacket combatLog)
|
|
{
|
|
CombatLogSender notifier = new CombatLogSender(this, combatLog, GetVisibilityRange());
|
|
Cell.VisitWorldObjects(this, notifier, GetVisibilityRange());
|
|
}
|
|
|
|
public void Kill(Unit victim, bool durabilityLoss = true)
|
|
{
|
|
// Prevent killing unit twice (and giving reward from kill twice)
|
|
if (victim.GetHealth() == 0)
|
|
return;
|
|
|
|
// find player: owner of controlled `this` or `this` itself maybe
|
|
Player player = GetCharmerOrOwnerPlayerOrPlayerItself();
|
|
Creature creature = victim.ToCreature();
|
|
|
|
bool isRewardAllowed = true;
|
|
if (creature != null)
|
|
{
|
|
isRewardAllowed = creature.IsDamageEnoughForLootingAndReward();
|
|
if (!isRewardAllowed)
|
|
creature.SetLootRecipient(null);
|
|
}
|
|
|
|
if (isRewardAllowed && creature != null && creature.GetLootRecipient() != null)
|
|
player = creature.GetLootRecipient();
|
|
|
|
// Reward player, his pets, and group/raid members
|
|
// call kill spell proc event (before real die and combat stop to triggering auras removed at death/combat stop)
|
|
if (isRewardAllowed && player != null && player != victim)
|
|
{
|
|
PartyKillLog partyKillLog = new PartyKillLog();
|
|
partyKillLog.Player = player.GetGUID();
|
|
partyKillLog.Victim = victim.GetGUID();
|
|
|
|
Player looter = player;
|
|
var group = player.GetGroup();
|
|
bool hasLooterGuid = false;
|
|
if (group)
|
|
{
|
|
group.BroadcastPacket(partyKillLog, group.GetMemberGroup(player.GetGUID()) != 0);
|
|
|
|
if (creature)
|
|
{
|
|
group.UpdateLooterGuid(creature, true);
|
|
if (!group.GetLooterGuid().IsEmpty())
|
|
{
|
|
looter = Global.ObjAccessor.FindPlayer(group.GetLooterGuid());
|
|
if (looter)
|
|
{
|
|
hasLooterGuid = true;
|
|
creature.SetLootRecipient(looter); // update creature loot recipient to the allowed looter.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
player.SendPacket(partyKillLog);
|
|
|
|
if (creature != null)
|
|
{
|
|
LootList lootList = new LootList();
|
|
lootList.Owner = creature.GetGUID();
|
|
lootList.LootObj = creature.loot.GetGUID();
|
|
player.SendMessageToSet(lootList, true);
|
|
}
|
|
}
|
|
|
|
if (creature)
|
|
{
|
|
Loot loot = creature.loot;
|
|
|
|
loot.clear();
|
|
uint lootid = creature.GetCreatureTemplate().LootId;
|
|
if (lootid != 0)
|
|
loot.FillLoot(lootid, LootManager.Creature, looter, false, false, creature.GetLootMode());
|
|
|
|
loot.generateMoneyLoot(creature.GetCreatureTemplate().MinGold, creature.GetCreatureTemplate().MaxGold);
|
|
|
|
if (group)
|
|
{
|
|
if (hasLooterGuid)
|
|
group.SendLooter(creature, looter);
|
|
else
|
|
group.SendLooter(creature, null);
|
|
|
|
// Update round robin looter only if the creature had loot
|
|
if (!loot.empty())
|
|
group.UpdateLooterGuid(creature);
|
|
}
|
|
}
|
|
|
|
player.RewardPlayerAndGroupAtKill(victim, false);
|
|
}
|
|
|
|
// 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())
|
|
{
|
|
// proc only once for victim
|
|
Unit owner = GetOwner();
|
|
if (owner != null)
|
|
owner.ProcSkillsAndAuras(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);
|
|
|
|
// 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);
|
|
|
|
// 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 talent known but not triggered (check priest class for speedup check)
|
|
bool spiritOfRedemption = false;
|
|
if (victim.IsTypeId(TypeId.Player) && victim.GetClass() == Class.Priest)
|
|
{
|
|
AuraEffect spiritOfRedemptionEffect = GetAuraEffect(20711, 0);
|
|
if (spiritOfRedemptionEffect != null)
|
|
{
|
|
// save value before aura remove
|
|
uint ressSpellId = victim.GetUInt32Value(PlayerFields.SelfResSpell);
|
|
if (ressSpellId == 0)
|
|
ressSpellId = victim.ToPlayer().GetResurrectionSpellId();
|
|
// Remove all expected to remove at death auras (most important negative case like DoT or periodic triggers)
|
|
victim.RemoveAllAurasOnDeath();
|
|
// restore for use at real death
|
|
victim.SetUInt32Value(PlayerFields.SelfResSpell, ressSpellId);
|
|
|
|
// FORM_SPIRIT_OF_REDEMPTION and related auras
|
|
victim.CastSpell(victim, 27827, true, null, spiritOfRedemptionEffect);
|
|
spiritOfRedemption = true;
|
|
}
|
|
}
|
|
|
|
if (!spiritOfRedemption)
|
|
{
|
|
Log.outDebug(LogFilter.Unit, "SET JUST_DIED");
|
|
victim.setDeathState(DeathState.JustDied);
|
|
}
|
|
|
|
// Inform pets (if any) when player kills target)
|
|
// MUST come after victim.setDeathState(JUST_DIED); or pet next target
|
|
// selection will get stuck on same target and break pet react state
|
|
if (player != null)
|
|
{
|
|
Pet pet = player.GetPet();
|
|
if (pet != null && pet.IsAlive() && pet.isControlled())
|
|
pet.GetAI().KilledUnit(victim);
|
|
}
|
|
|
|
// 10% durability loss on death
|
|
// clean InHateListOf
|
|
Player plrVictim = victim.ToPlayer();
|
|
if (plrVictim != null)
|
|
{
|
|
// remember victim PvP death for corpse type and corpse reclaim delay
|
|
// at original death (not at SpiritOfRedemtionTalent timeout)
|
|
plrVictim.SetPvPDeath(player != null);
|
|
|
|
// only if not player and not controlled by player pet. And not at BG
|
|
if ((durabilityLoss && player == null && !victim.ToPlayer().InBattleground()) || (player != null && WorldConfig.GetBoolValue(WorldCfg.DurabilityLossInPvp)))
|
|
{
|
|
double baseLoss = WorldConfig.GetFloatValue(WorldCfg.RateDurabilityLossOnDeath);
|
|
uint loss = (uint)(baseLoss - (baseLoss * plrVictim.GetTotalAuraMultiplier(AuraType.ModDurabilityLoss)));
|
|
Log.outDebug(LogFilter.Unit, "We are dead, losing {0} percent durability", loss);
|
|
// Durability loss is calculated more accurately again for each item in Player.DurabilityLoss
|
|
plrVictim.DurabilityLossAll(baseLoss, false);
|
|
// durability lost message
|
|
SendDurabilityLoss(plrVictim, loss);
|
|
}
|
|
// Call KilledUnit for creatures
|
|
if (IsTypeId(TypeId.Unit) && IsAIEnabled)
|
|
ToCreature().GetAI().KilledUnit(victim);
|
|
|
|
// last damage from non duel opponent or opponent controlled creature
|
|
if (plrVictim.duel != null)
|
|
{
|
|
plrVictim.duel.opponent.CombatStopWithPets(true);
|
|
plrVictim.CombatStopWithPets(true);
|
|
plrVictim.DuelComplete(DuelCompleteType.Interrupted);
|
|
}
|
|
}
|
|
else // creature died
|
|
{
|
|
Log.outDebug(LogFilter.Unit, "DealDamageNotPlayer");
|
|
|
|
if (!creature.IsPet())
|
|
{
|
|
creature.DeleteThreatList();
|
|
|
|
// must be after setDeathState which resets dynamic flags
|
|
if (!creature.loot.isLooted())
|
|
creature.SetFlag(ObjectFields.DynamicFlags, UnitDynFlags.Lootable);
|
|
else
|
|
creature.AllLootRemovedFromCorpse();
|
|
}
|
|
|
|
// Call KilledUnit for creatures, this needs to be called after the lootable flag is set
|
|
if (IsTypeId(TypeId.Unit) && IsAIEnabled)
|
|
ToCreature().GetAI().KilledUnit(victim);
|
|
|
|
// Call creature just died function
|
|
if (creature.IsAIEnabled)
|
|
creature.GetAI().JustDied(this);
|
|
|
|
TempSummon summon = creature.ToTempSummon();
|
|
if (summon != null)
|
|
{
|
|
Unit summoner = summon.GetSummoner();
|
|
if (summoner != null)
|
|
if (summoner.IsTypeId(TypeId.Unit) && summoner.IsAIEnabled)
|
|
summoner.ToCreature().GetAI().SummonedCreatureDies(creature, this);
|
|
}
|
|
|
|
// 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))
|
|
{
|
|
if (instanceMap.IsRaidOrHeroicDungeon())
|
|
{
|
|
if (creature.GetCreatureTemplate().FlagsExtra.HasAnyFlag(CreatureFlagsExtra.InstanceBind))
|
|
((InstanceMap)instanceMap).PermBindAllPlayers();
|
|
}
|
|
else
|
|
{
|
|
// the reset time is set but not added to the scheduler
|
|
// until the players leave the instance
|
|
long resettime = creature.GetRespawnTimeEx() + 2 * Time.Hour;
|
|
InstanceSave save = Global.InstanceSaveMgr.GetInstanceSave(creature.GetInstanceId());
|
|
if (save != null)
|
|
if (save.GetResetTime() < resettime)
|
|
save.SetResetTime(resettime);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
OutdoorPvP pvp = player.GetOutdoorPvP();
|
|
if (pvp != null)
|
|
pvp.HandleKill(player, victim);
|
|
|
|
BattleField bf = Global.BattleFieldMgr.GetBattlefieldToZoneId(player.GetZoneId());
|
|
if (bf != null)
|
|
bf.HandleKill(player, victim);
|
|
}
|
|
|
|
// Battlegroundthings (do this at the end, so the death state flag will be properly set to handle in the bg.handlekill)
|
|
if (player != null && player.InBattleground())
|
|
{
|
|
Battleground bg = player.GetBattleground();
|
|
if (bg)
|
|
{
|
|
Player playerVictim = victim.ToPlayer();
|
|
if (playerVictim)
|
|
bg.HandleKillPlayer(playerVictim, player);
|
|
else
|
|
bg.HandleKillUnit(victim.ToCreature(), player);
|
|
}
|
|
}
|
|
|
|
// achievement stuff
|
|
if (victim.IsTypeId(TypeId.Player))
|
|
{
|
|
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());
|
|
}
|
|
|
|
// Hook for OnPVPKill Event
|
|
Player killerPlr = ToPlayer();
|
|
Creature killerCre = ToCreature();
|
|
if (killerPlr != 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);
|
|
}
|
|
}
|
|
|
|
public void KillSelf(bool durabilityLoss = true) { Kill(this, durabilityLoss); }
|
|
|
|
public virtual uint GetBlockPercent() { return 30; }
|
|
|
|
public void SetContestedPvP(Player attackedPlayer = null)
|
|
{
|
|
Player player = GetCharmerOrOwnerPlayerOrPlayerItself();
|
|
|
|
if (player == null || (attackedPlayer != null && (attackedPlayer == player || (player.duel != null && player.duel.opponent == attackedPlayer))))
|
|
return;
|
|
|
|
player.SetContestedPvPTimer(30000);
|
|
if (!player.HasUnitState(UnitState.AttackPlayer))
|
|
{
|
|
player.AddUnitState(UnitState.AttackPlayer);
|
|
player.SetFlag(PlayerFields.Flags, PlayerFlags.ContestedPVP);
|
|
// call MoveInLineOfSight for nearby contested guards
|
|
UpdateObjectVisibility();
|
|
}
|
|
if (!HasUnitState(UnitState.AttackPlayer))
|
|
{
|
|
AddUnitState(UnitState.AttackPlayer);
|
|
// call MoveInLineOfSight for nearby contested guards
|
|
UpdateObjectVisibility();
|
|
}
|
|
}
|
|
|
|
void UpdateReactives(uint p_time)
|
|
{
|
|
for (ReactiveType reactive = 0; reactive < ReactiveType.Max; ++reactive)
|
|
{
|
|
if (!m_reactiveTimer.ContainsKey(reactive))
|
|
continue;
|
|
|
|
if (m_reactiveTimer[reactive] <= p_time)
|
|
{
|
|
m_reactiveTimer[reactive] = 0;
|
|
|
|
switch (reactive)
|
|
{
|
|
case ReactiveType.Defense:
|
|
if (HasAuraState(AuraStateType.Defense))
|
|
ModifyAuraState(AuraStateType.Defense, false);
|
|
break;
|
|
case ReactiveType.HunterParry:
|
|
if (GetClass() == Class.Hunter && HasAuraState(AuraStateType.HunterParry))
|
|
ModifyAuraState(AuraStateType.HunterParry, false);
|
|
break;
|
|
case ReactiveType.OverPower:
|
|
if (GetClass() == Class.Warrior && IsTypeId(TypeId.Player))
|
|
ToPlayer().ClearComboPoints();
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_reactiveTimer[reactive] -= p_time;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void RewardRage(uint baseRage)
|
|
{
|
|
float addRage = baseRage;
|
|
|
|
// talent who gave more rage on attack
|
|
MathFunctions.AddPct(ref addRage, GetTotalAuraModifier(AuraType.ModRageFromDamageDealt));
|
|
|
|
addRage *= WorldConfig.GetFloatValue(WorldCfg.RatePowerRageIncome);
|
|
|
|
ModifyPower(PowerType.Rage, (int)(addRage * 10));
|
|
}
|
|
|
|
public float GetPPMProcChance(uint WeaponSpeed, float PPM, SpellInfo spellProto)
|
|
{
|
|
// proc per minute chance calculation
|
|
if (PPM <= 0)
|
|
return 0.0f;
|
|
|
|
// Apply chance modifer aura
|
|
if (spellProto != null)
|
|
{
|
|
Player modOwner = GetSpellModOwner();
|
|
if (modOwner != null)
|
|
modOwner.ApplySpellMod(spellProto.Id, SpellModOp.ProcPerMinute, ref PPM);
|
|
}
|
|
|
|
return (float)Math.Floor((WeaponSpeed * PPM) / 600.0f); // result is chance in percents (probability = Speed_in_sec * (PPM / 60))
|
|
}
|
|
|
|
public Unit GetNextRandomRaidMemberOrPet(float radius)
|
|
{
|
|
Player player = null;
|
|
if (IsTypeId(TypeId.Player))
|
|
player = ToPlayer();
|
|
// Should we enable this also for charmed units?
|
|
else if (IsTypeId(TypeId.Unit) && IsPet())
|
|
player = GetOwner().ToPlayer();
|
|
|
|
if (player == null)
|
|
return null;
|
|
Group group = player.GetGroup();
|
|
// When there is no group check pet presence
|
|
if (!group)
|
|
{
|
|
// We are pet now, return owner
|
|
if (player != this)
|
|
return IsWithinDistInMap(player, radius) ? player : null;
|
|
Unit pet = GetGuardianPet();
|
|
// No pet, no group, nothing to return
|
|
if (pet == null)
|
|
return null;
|
|
// We are owner now, return pet
|
|
return IsWithinDistInMap(pet, radius) ? pet : null;
|
|
}
|
|
|
|
List<Unit> nearMembers = new List<Unit>();
|
|
// reserve place for players and pets because resizing vector every unit push is unefficient (vector is reallocated then)
|
|
|
|
for (GroupReference refe = group.GetFirstMember(); refe != null; refe = refe.next())
|
|
{
|
|
Player Target = refe.GetSource();
|
|
if (Target)
|
|
{
|
|
// IsHostileTo check duel and controlled by enemy
|
|
if (Target != this && Target.IsAlive() && IsWithinDistInMap(Target, radius) && !IsHostileTo(Target))
|
|
nearMembers.Add(Target);
|
|
|
|
// Push player's pet to vector
|
|
Unit pet = Target.GetGuardianPet();
|
|
if (pet)
|
|
if (pet != this && pet.IsAlive() && IsWithinDistInMap(pet, radius) && !IsHostileTo(pet))
|
|
nearMembers.Add(pet);
|
|
}
|
|
}
|
|
|
|
if (nearMembers.Empty())
|
|
return null;
|
|
|
|
int randTarget = RandomHelper.IRand(0, nearMembers.Count - 1);
|
|
return nearMembers[randTarget];
|
|
}
|
|
|
|
public void ClearAllReactives()
|
|
{
|
|
for (ReactiveType i = 0; i < ReactiveType.Max; ++i)
|
|
m_reactiveTimer[i] = 0;
|
|
|
|
if (HasAuraState(AuraStateType.Defense))
|
|
ModifyAuraState(AuraStateType.Defense, false);
|
|
if (GetClass() == Class.Hunter && HasAuraState(AuraStateType.HunterParry))
|
|
ModifyAuraState(AuraStateType.HunterParry, false);
|
|
if (GetClass() == Class.Warrior && IsTypeId(TypeId.Player))
|
|
ToPlayer().ClearComboPoints();
|
|
}
|
|
|
|
// TODO for melee need create structure as in
|
|
void CalculateMeleeDamage(Unit victim, uint damage, out CalcDamageInfo damageInfo, WeaponAttackType attackType)
|
|
{
|
|
damageInfo = new CalcDamageInfo();
|
|
|
|
damageInfo.attacker = this;
|
|
damageInfo.target = victim;
|
|
damageInfo.damageSchoolMask = (uint)SpellSchoolMask.Normal;
|
|
damageInfo.attackType = attackType;
|
|
damageInfo.damage = 0;
|
|
damageInfo.cleanDamage = 0;
|
|
damageInfo.absorb = 0;
|
|
damageInfo.resist = 0;
|
|
damageInfo.blocked_amount = 0;
|
|
|
|
damageInfo.TargetState = 0;
|
|
damageInfo.HitInfo = 0;
|
|
damageInfo.procAttacker = ProcFlags.None;
|
|
damageInfo.procVictim = ProcFlags.None;
|
|
damageInfo.hitOutCome = MeleeHitOutcome.Evade;
|
|
|
|
if (victim == null)
|
|
return;
|
|
|
|
if (!IsAlive() || !victim.IsAlive())
|
|
return;
|
|
|
|
// Select HitInfo/procAttacker/procVictim flag based on attack type
|
|
switch (attackType)
|
|
{
|
|
case WeaponAttackType.BaseAttack:
|
|
damageInfo.procAttacker = ProcFlags.DoneMeleeAutoAttack | ProcFlags.DoneMainHandAttack;
|
|
damageInfo.procVictim = ProcFlags.TakenMeleeAutoAttack;
|
|
break;
|
|
case WeaponAttackType.OffAttack:
|
|
damageInfo.procAttacker = ProcFlags.DoneMeleeAutoAttack | ProcFlags.DoneOffHandAttack;
|
|
damageInfo.procVictim = ProcFlags.TakenMeleeAutoAttack;
|
|
damageInfo.HitInfo = HitInfo.OffHand;
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
// Physical Immune check
|
|
if (damageInfo.target.IsImmunedToDamage((SpellSchoolMask)damageInfo.damageSchoolMask))
|
|
{
|
|
damageInfo.HitInfo |= HitInfo.NormalSwing;
|
|
damageInfo.TargetState = VictimState.Immune;
|
|
|
|
damageInfo.damage = 0;
|
|
damageInfo.cleanDamage = 0;
|
|
return;
|
|
}
|
|
|
|
damage += CalculateDamage(damageInfo.attackType, false, true);
|
|
// Add melee damage bonus
|
|
damage = MeleeDamageBonusDone(damageInfo.target, damage, damageInfo.attackType);
|
|
damage = damageInfo.target.MeleeDamageBonusTaken(this, damage, damageInfo.attackType);
|
|
|
|
// Script Hook For CalculateMeleeDamage -- Allow scripts to change the Damage pre class mitigation calculations
|
|
Global.ScriptMgr.ModifyMeleeDamage(damageInfo.target, damageInfo.attacker, ref damage);
|
|
|
|
// Calculate armor reduction
|
|
if (IsDamageReducedByArmor((SpellSchoolMask)damageInfo.damageSchoolMask))
|
|
{
|
|
damageInfo.damage = CalcArmorReducedDamage(damageInfo.attacker, damageInfo.target, damage, null, damageInfo.attackType);
|
|
damageInfo.cleanDamage += damage - damageInfo.damage;
|
|
}
|
|
else
|
|
damageInfo.damage = damage;
|
|
|
|
damageInfo.hitOutCome = RollMeleeOutcomeAgainst(damageInfo.target, damageInfo.attackType);
|
|
|
|
switch (damageInfo.hitOutCome)
|
|
{
|
|
case MeleeHitOutcome.Evade:
|
|
damageInfo.HitInfo |= HitInfo.Miss | HitInfo.SwingNoHitSound;
|
|
damageInfo.TargetState = VictimState.Evades;
|
|
|
|
damageInfo.damage = 0;
|
|
damageInfo.cleanDamage = 0;
|
|
return;
|
|
case MeleeHitOutcome.Miss:
|
|
damageInfo.HitInfo |= HitInfo.Miss;
|
|
damageInfo.TargetState = VictimState.Intact;
|
|
damageInfo.damage = 0;
|
|
damageInfo.cleanDamage = 0;
|
|
break;
|
|
case MeleeHitOutcome.Normal:
|
|
damageInfo.TargetState = VictimState.Hit;
|
|
break;
|
|
case MeleeHitOutcome.Crit:
|
|
damageInfo.HitInfo |= HitInfo.CriticalHit;
|
|
damageInfo.TargetState = VictimState.Hit;
|
|
// Crit bonus calc
|
|
damageInfo.damage += damageInfo.damage;
|
|
|
|
// Increase crit damage from SPELL_AURA_MOD_CRIT_DAMAGE_BONUS
|
|
float mod = (GetTotalAuraMultiplierByMiscMask(AuraType.ModCritDamageBonus, damageInfo.damageSchoolMask) - 1.0f) * 100;
|
|
|
|
if (mod != 0)
|
|
MathFunctions.AddPct(ref damageInfo.damage, mod);
|
|
break;
|
|
case MeleeHitOutcome.Parry:
|
|
damageInfo.TargetState = VictimState.Parry;
|
|
damageInfo.cleanDamage += damageInfo.damage;
|
|
damageInfo.damage = 0;
|
|
break;
|
|
case MeleeHitOutcome.Dodge:
|
|
damageInfo.TargetState = VictimState.Dodge;
|
|
damageInfo.cleanDamage += damageInfo.damage;
|
|
damageInfo.damage = 0;
|
|
break;
|
|
case MeleeHitOutcome.Block:
|
|
damageInfo.TargetState = VictimState.Hit;
|
|
// 30% damage blocked, double blocked amount if block is critical
|
|
damageInfo.blocked_amount = MathFunctions.CalculatePct(damageInfo.damage, damageInfo.target.isBlockCritical() ? damageInfo.target.GetBlockPercent() * 2 : damageInfo.target.GetBlockPercent());
|
|
damageInfo.damage -= damageInfo.blocked_amount;
|
|
damageInfo.cleanDamage += damageInfo.blocked_amount;
|
|
break;
|
|
case MeleeHitOutcome.Glancing:
|
|
damageInfo.HitInfo |= HitInfo.Glancing;
|
|
damageInfo.TargetState = VictimState.Hit;
|
|
int leveldif = (int)victim.getLevel() - (int)getLevel();
|
|
if (leveldif > 3)
|
|
leveldif = 3;
|
|
|
|
float reducePercent = 1.0f - leveldif * 0.1f;
|
|
damageInfo.cleanDamage += damageInfo.damage - (uint)(reducePercent * damageInfo.damage);
|
|
damageInfo.damage = (uint)(reducePercent * damageInfo.damage);
|
|
break;
|
|
case MeleeHitOutcome.Crushing:
|
|
damageInfo.HitInfo |= HitInfo.Crushing;
|
|
damageInfo.TargetState = VictimState.Hit;
|
|
// 150% normal damage
|
|
damageInfo.damage += (damageInfo.damage / 2);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Always apply HITINFO_AFFECTS_VICTIM in case its not a miss
|
|
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;
|
|
|
|
// Calculate absorb resist
|
|
if (damageInfo.damage > 0)
|
|
{
|
|
damageInfo.procVictim |= ProcFlags.TakenDamage;
|
|
// Calculate absorb & resists
|
|
CalcAbsorbResist(damageInfo.target, (SpellSchoolMask)damageInfo.damageSchoolMask, DamageEffectType.Direct, damageInfo.damage, ref damageInfo.absorb, ref damageInfo.resist);
|
|
|
|
if (damageInfo.absorb != 0)
|
|
damageInfo.HitInfo |= (damageInfo.damage - damageInfo.absorb == 0 ? HitInfo.FullAbsorb : HitInfo.PartialAbsorb);
|
|
|
|
if (damageInfo.resist != 0)
|
|
damageInfo.HitInfo |= (damageInfo.damage - damageInfo.resist == 0 ? HitInfo.FullResist : HitInfo.PartialResist);
|
|
|
|
damageInfo.damage -= damageInfo.absorb + damageInfo.resist;
|
|
}
|
|
else // Impossible get negative result but....
|
|
damageInfo.damage = 0;
|
|
}
|
|
MeleeHitOutcome RollMeleeOutcomeAgainst(Unit victim, WeaponAttackType attType)
|
|
{
|
|
if (victim.IsTypeId(TypeId.Unit) && victim.ToCreature().IsEvadingAttacks())
|
|
return MeleeHitOutcome.Evade;
|
|
|
|
// Miss chance based on melee
|
|
int miss_chance = (int)(MeleeSpellMissChance(victim, attType, 0) * 100.0f);
|
|
|
|
// Critical hit chance
|
|
int crit_chance = (int)(GetUnitCriticalChance(attType, victim) * 100.0f);
|
|
|
|
int dodge_chance = (int)(GetUnitDodgeChance(attType, victim) * 100.0f);
|
|
int block_chance = (int)(GetUnitBlockChance(attType, victim) * 100.0f);
|
|
int parry_chance = (int)(GetUnitParryChance(attType, victim) * 100.0f);
|
|
|
|
// melee attack table implementation
|
|
// outcome priority:
|
|
// 1. > 2. > 3. > 4. > 5. > 6. > 7. > 8.
|
|
// MISS > DODGE > PARRY > GLANCING > BLOCK > CRIT > CRUSHING > HIT
|
|
|
|
int sum = 0, tmp = 0;
|
|
int roll = RandomHelper.IRand(0, 9999);
|
|
|
|
uint attackerLevel = GetLevelForTarget(victim);
|
|
uint victimLevel = GetLevelForTarget(this);
|
|
|
|
// check if attack comes from behind, nobody can parry or block if attacker is behind
|
|
bool canParryOrBlock = victim.HasInArc((float)Math.PI, this) || victim.HasAuraType(AuraType.IgnoreHitDirection);
|
|
|
|
// only creatures can dodge if attacker is behind
|
|
bool canDodge = !victim.IsTypeId(TypeId.Player) || canParryOrBlock;
|
|
|
|
// if victim is casting or cc'd it can't avoid attacks
|
|
if (victim.IsNonMeleeSpellCast(false) || victim.HasUnitState(UnitState.Controlled))
|
|
{
|
|
canDodge = false;
|
|
canParryOrBlock = false;
|
|
}
|
|
|
|
// 1. MISS
|
|
tmp = miss_chance;
|
|
if (tmp > 0 && roll < (sum += tmp))
|
|
return MeleeHitOutcome.Miss;
|
|
|
|
// always crit against a sitting target (except 0 crit chance)
|
|
if (victim.IsTypeId(TypeId.Player) && crit_chance > 0 && !victim.IsStandState())
|
|
return MeleeHitOutcome.Crit;
|
|
|
|
// 2. DODGE
|
|
if (canDodge)
|
|
{
|
|
tmp = dodge_chance;
|
|
if (tmp > 0 // check if unit _can_ dodge
|
|
&& roll < (sum += tmp))
|
|
return MeleeHitOutcome.Dodge;
|
|
}
|
|
|
|
// 3. PARRY
|
|
if (canParryOrBlock)
|
|
{
|
|
tmp = parry_chance;
|
|
if (tmp > 0 // check if unit _can_ parry
|
|
&& roll < (sum += tmp))
|
|
return MeleeHitOutcome.Parry;
|
|
}
|
|
|
|
// 4. GLANCING
|
|
// Max 40% chance to score a glancing blow against mobs that are higher level (can do only players and pets and not with ranged weapon)
|
|
if ((IsTypeId(TypeId.Player) || IsPet()) &&
|
|
!victim.IsTypeId(TypeId.Player) && !victim.IsPet() &&
|
|
attackerLevel + 3 < victimLevel)
|
|
{
|
|
// cap possible value (with bonuses > max skill)
|
|
tmp = (int)(10 + 10 * (victimLevel - attackerLevel)) * 100;
|
|
if (tmp > 0 && roll < (sum += tmp))
|
|
return MeleeHitOutcome.Glancing;
|
|
}
|
|
|
|
// 5. BLOCK
|
|
if (canParryOrBlock)
|
|
{
|
|
tmp = block_chance;
|
|
if (tmp > 0 // check if unit _can_ block
|
|
&& roll < (sum += tmp))
|
|
return MeleeHitOutcome.Block;
|
|
}
|
|
|
|
// 6.CRIT
|
|
tmp = crit_chance;
|
|
if (tmp > 0 && roll < (sum += tmp))
|
|
{
|
|
if (GetTypeId() != TypeId.Unit || !(ToCreature().GetCreatureTemplate().FlagsExtra.HasAnyFlag(CreatureFlagsExtra.NoCrit)))
|
|
return MeleeHitOutcome.Crit;
|
|
}
|
|
|
|
// 7. CRUSHING
|
|
// mobs can score crushing blows if they're 4 or more levels above victim
|
|
if (attackerLevel >= victimLevel + 4 &&
|
|
// can be from by creature (if can) or from controlled player that considered as creature
|
|
!IsControlledByPlayer() &&
|
|
!(GetTypeId() == TypeId.Unit && ToCreature().GetCreatureTemplate().FlagsExtra.HasAnyFlag(CreatureFlagsExtra.NoCrush)))
|
|
{
|
|
// add 2% chance per level, min. is 15%
|
|
tmp = (int)(attackerLevel - victimLevel * 1000 - 1500);
|
|
if (roll < (sum += tmp))
|
|
{
|
|
Log.outDebug(LogFilter.Unit, "RollMeleeOutcomeAgainst: CRUSHING <{0}, {1})", sum - tmp, sum);
|
|
return MeleeHitOutcome.Crushing;
|
|
}
|
|
}
|
|
|
|
// 8. HIT
|
|
return MeleeHitOutcome.Normal;
|
|
}
|
|
public uint CalculateDamage(WeaponAttackType attType, bool normalized, bool addTotalPct)
|
|
{
|
|
float minDamage, maxDamage = 0.0f;
|
|
|
|
if (normalized || !addTotalPct)
|
|
{
|
|
CalculateMinMaxDamage(attType, normalized, addTotalPct, out minDamage, out maxDamage);
|
|
if (IsInFeralForm() && attType == WeaponAttackType.BaseAttack)
|
|
{
|
|
float minOffhandDamage = 0.0f;
|
|
float maxOffhandDamage = 0.0f;
|
|
CalculateMinMaxDamage(WeaponAttackType.OffAttack, normalized, addTotalPct, out minOffhandDamage, out maxOffhandDamage);
|
|
minDamage += minOffhandDamage;
|
|
maxDamage += maxOffhandDamage;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (attType)
|
|
{
|
|
case WeaponAttackType.RangedAttack:
|
|
minDamage = GetFloatValue(UnitFields.MinRangedDamage);
|
|
maxDamage = GetFloatValue(UnitFields.MaxRangedDamage);
|
|
break;
|
|
case WeaponAttackType.BaseAttack:
|
|
minDamage = GetFloatValue(UnitFields.MinDamage);
|
|
maxDamage = GetFloatValue(UnitFields.MaxDamage);
|
|
if (IsInFeralForm())
|
|
{
|
|
minDamage += GetFloatValue(UnitFields.MinOffHandDamage);
|
|
maxDamage += GetFloatValue(UnitFields.MaxOffHandDamage);
|
|
}
|
|
break;
|
|
case WeaponAttackType.OffAttack:
|
|
minDamage = GetFloatValue(UnitFields.MinOffHandDamage);
|
|
maxDamage = GetFloatValue(UnitFields.MaxOffHandDamage);
|
|
break;
|
|
// Just for good manner
|
|
default:
|
|
minDamage = 0.0f;
|
|
maxDamage = 0.0f;
|
|
break;
|
|
}
|
|
}
|
|
minDamage = Math.Max(0.0f, minDamage);
|
|
maxDamage = Math.Max(0.0f, maxDamage);
|
|
|
|
|
|
if (minDamage > maxDamage)
|
|
{
|
|
minDamage = minDamage + maxDamage;
|
|
maxDamage = minDamage - maxDamage;
|
|
minDamage = minDamage - maxDamage;
|
|
}
|
|
|
|
if (maxDamage == 0.0f)
|
|
maxDamage = 5.0f;
|
|
|
|
return RandomHelper.URand(minDamage, maxDamage);
|
|
}
|
|
public float GetWeaponDamageRange(WeaponAttackType attType, WeaponDamageRange type)
|
|
{
|
|
if (attType == WeaponAttackType.OffAttack && !haveOffhandWeapon())
|
|
return 0.0f;
|
|
|
|
return m_weaponDamage[(int)attType][(int)type];
|
|
}
|
|
public float GetAPMultiplier(WeaponAttackType attType, bool normalized)
|
|
{
|
|
if (!IsTypeId(TypeId.Player))
|
|
return GetBaseAttackTime(attType) / 1000.0f;
|
|
|
|
Item weapon = ToPlayer().GetWeaponForAttack(attType, true);
|
|
if (!weapon)
|
|
return 2.0f;
|
|
|
|
if (!normalized)
|
|
return weapon.GetTemplate().GetDelay() / 1000.0f;
|
|
|
|
switch ((ItemSubClassWeapon)weapon.GetTemplate().GetSubClass())
|
|
{
|
|
case ItemSubClassWeapon.Axe2:
|
|
case ItemSubClassWeapon.Mace2:
|
|
case ItemSubClassWeapon.Polearm:
|
|
case ItemSubClassWeapon.Sword2:
|
|
case ItemSubClassWeapon.Staff:
|
|
case ItemSubClassWeapon.FishingPole:
|
|
return 3.3f;
|
|
case ItemSubClassWeapon.Axe:
|
|
case ItemSubClassWeapon.Mace:
|
|
case ItemSubClassWeapon.Sword:
|
|
case ItemSubClassWeapon.Warglaives:
|
|
case ItemSubClassWeapon.Exotic:
|
|
case ItemSubClassWeapon.Exotic2:
|
|
case ItemSubClassWeapon.Fist:
|
|
return 2.4f;
|
|
case ItemSubClassWeapon.Dagger:
|
|
return 1.7f;
|
|
case ItemSubClassWeapon.Thrown:
|
|
return 2.0f;
|
|
default:
|
|
return weapon.GetTemplate().GetDelay() / 1000.0f;
|
|
}
|
|
}
|
|
public float GetTotalAttackPowerValue(WeaponAttackType attType)
|
|
{
|
|
if (attType == WeaponAttackType.RangedAttack)
|
|
{
|
|
int ap = GetInt32Value(UnitFields.RangedAttackPower);
|
|
if (ap < 0)
|
|
return 0.0f;
|
|
return ap * (1.0f + GetFloatValue(UnitFields.RangedAttackPowerMultiplier));
|
|
}
|
|
else
|
|
{
|
|
int ap = GetInt32Value(UnitFields.AttackPower);
|
|
if (ap < 0)
|
|
return 0.0f;
|
|
return ap * (1.0f + GetFloatValue(UnitFields.AttackPowerMultiplier));
|
|
}
|
|
}
|
|
public float GetModifierValue(UnitMods unitMod, UnitModifierType modifierType)
|
|
{
|
|
if (unitMod >= UnitMods.End || modifierType >= UnitModifierType.End)
|
|
{
|
|
Log.outError(LogFilter.Unit, "attempt to access non-existing modifier value from UnitMods!");
|
|
return 0.0f;
|
|
}
|
|
|
|
if (modifierType == UnitModifierType.TotalPCT && m_auraModifiersGroup[(int)unitMod][(int)modifierType] <= 0.0f)
|
|
return 0.0f;
|
|
|
|
return m_auraModifiersGroup[(int)unitMod][(int)modifierType];
|
|
}
|
|
public bool IsWithinMeleeRange(Unit obj)
|
|
{
|
|
if (!obj || !IsInMap(obj) || !IsInPhase(obj))
|
|
return false;
|
|
|
|
float dx = GetPositionX() - obj.GetPositionX();
|
|
float dy = GetPositionY() - obj.GetPositionY();
|
|
float dz = GetPositionZMinusOffset() - obj.GetPositionZMinusOffset();
|
|
float distsq = (dx * dx) + (dy * dy) + (dz * dz);
|
|
|
|
float maxdist = GetMeleeRange(obj);
|
|
|
|
return distsq <= maxdist * maxdist;
|
|
}
|
|
|
|
public float GetMeleeRange(Unit target)
|
|
{
|
|
float range = GetCombatReach() + target.GetCombatReach() + 4.0f / 3.0f;
|
|
return Math.Max(range, SharedConst.NominalMeleeRange);
|
|
}
|
|
|
|
public void SetBaseAttackTime(WeaponAttackType att, uint val)
|
|
{
|
|
m_baseAttackSpeed[(int)att] = val;
|
|
UpdateAttackTimeField(att);
|
|
}
|
|
void UpdateAttackTimeField(WeaponAttackType att)
|
|
{
|
|
SetUInt32Value(UnitFields.BaseAttackTime + (int)att, (uint)(m_baseAttackSpeed[(int)att] * m_modAttackSpeedPct[(int)att]));
|
|
}
|
|
public virtual void SetPvP(bool state)
|
|
{
|
|
if (state)
|
|
SetByteFlag(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, UnitBytes2Flags.PvP);
|
|
else
|
|
RemoveByteFlag(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag, UnitBytes2Flags.PvP);
|
|
}
|
|
|
|
public bool CanHaveThreatList(bool skipAliveCheck = false)
|
|
{
|
|
// only creatures can have threat list
|
|
if (!IsTypeId(TypeId.Unit))
|
|
return false;
|
|
|
|
// only alive units can have threat list
|
|
if (!skipAliveCheck && !IsAlive())
|
|
return false;
|
|
|
|
// totems can not have threat list
|
|
if (IsTotem())
|
|
return false;
|
|
|
|
// summons can not have a threat list, unless they are controlled by a creature
|
|
if (HasUnitTypeMask(UnitTypeMask.Minion | UnitTypeMask.Guardian | UnitTypeMask.ControlableGuardian) && GetOwnerGUID().IsPlayer())
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
uint CalcSpellResistance(Unit victim, SpellSchoolMask schoolMask, SpellInfo spellInfo)
|
|
{
|
|
// Magic damage, check for resists
|
|
if (!Convert.ToBoolean(schoolMask & SpellSchoolMask.Spell))
|
|
return 0;
|
|
|
|
// Ignore spells that can't be resisted
|
|
if (spellInfo != null && spellInfo.HasAttribute(SpellAttr4.IgnoreResistances))
|
|
return 0;
|
|
|
|
byte bossLevel = 83;
|
|
uint bossResistanceConstant = 510;
|
|
uint resistanceConstant = 0;
|
|
uint level = victim.GetLevelForTarget(this);
|
|
|
|
if (level == bossLevel)
|
|
resistanceConstant = bossResistanceConstant;
|
|
else
|
|
resistanceConstant = level * 5;
|
|
|
|
int baseVictimResistance = (int)victim.GetResistance(schoolMask);
|
|
baseVictimResistance += GetTotalAuraModifierByMiscMask(AuraType.ModTargetResistance, (int)schoolMask);
|
|
|
|
Player player = ToPlayer();
|
|
if (player)
|
|
baseVictimResistance -= player.GetSpellPenetrationItemMod();
|
|
|
|
// Resistance can't be lower then 0
|
|
int victimResistance = Math.Max(baseVictimResistance, 0);
|
|
|
|
if (victimResistance > 0)
|
|
{
|
|
int ignoredResistance = 0;
|
|
|
|
var ResIgnoreAuras = GetAuraEffectsByType(AuraType.ModIgnoreTargetResist);
|
|
foreach (var eff in ResIgnoreAuras)
|
|
if (Convert.ToBoolean(eff.GetMiscValue() & (int)schoolMask))
|
|
ignoredResistance += eff.GetAmount();
|
|
|
|
ignoredResistance = Math.Min(ignoredResistance, 100);
|
|
MathFunctions.ApplyPct(ref victimResistance, 100 - ignoredResistance);
|
|
}
|
|
|
|
if (victimResistance <= 0)
|
|
return 0;
|
|
|
|
float averageResist = victimResistance / victimResistance + resistanceConstant;
|
|
|
|
float[] discreteResistProbability = new float[11];
|
|
for (uint i = 0; i < 11; ++i)
|
|
{
|
|
discreteResistProbability[i] = 0.5f - 2.5f * Math.Abs(0.1f * i - averageResist);
|
|
if (discreteResistProbability[i] < 0.0f)
|
|
discreteResistProbability[i] = 0.0f;
|
|
}
|
|
|
|
if (averageResist <= 0.1f)
|
|
{
|
|
discreteResistProbability[0] = 1.0f - 7.5f * averageResist;
|
|
discreteResistProbability[1] = 5.0f * averageResist;
|
|
discreteResistProbability[2] = 2.5f * averageResist;
|
|
}
|
|
|
|
uint resistance = 0;
|
|
float r = (float)RandomHelper.NextDouble();
|
|
float probabilitySum = discreteResistProbability[0];
|
|
|
|
while (r >= probabilitySum && resistance < 10)
|
|
probabilitySum += discreteResistProbability[++resistance];
|
|
|
|
return resistance * 10;
|
|
}
|
|
|
|
public void CalcAbsorbResist(Unit victim, SpellSchoolMask schoolMask, DamageEffectType damagetype, uint damage, ref uint absorb, ref uint resist, SpellInfo spellInfo = null)
|
|
{
|
|
if (victim == null || !victim.IsAlive() || damage == 0)
|
|
return;
|
|
|
|
DamageInfo dmgInfo = new DamageInfo(this, victim, damage, spellInfo, schoolMask, damagetype, WeaponAttackType.BaseAttack);
|
|
|
|
uint spellResistance = CalcSpellResistance(victim, schoolMask, spellInfo);
|
|
dmgInfo.ResistDamage(MathFunctions.CalculatePct(damage, spellResistance));
|
|
|
|
// Ignore Absorption Auras
|
|
float auraAbsorbMod = 0;
|
|
var AbsIgnoreAurasA = GetAuraEffectsByType(AuraType.ModTargetAbsorbSchool);
|
|
foreach (var eff in AbsIgnoreAurasA)
|
|
{
|
|
if (!Convert.ToBoolean(eff.GetMiscValue() & (int)schoolMask))
|
|
continue;
|
|
|
|
if (eff.GetAmount() > auraAbsorbMod)
|
|
auraAbsorbMod = eff.GetAmount();
|
|
}
|
|
|
|
var AbsIgnoreAurasB = GetAuraEffectsByType(AuraType.ModTargetAbilityAbsorbSchool);
|
|
foreach (var eff in AbsIgnoreAurasB)
|
|
{
|
|
if (!Convert.ToBoolean(eff.GetMiscValue() & (int)schoolMask))
|
|
continue;
|
|
|
|
if ((eff.GetAmount() > auraAbsorbMod) && eff.IsAffectingSpell(spellInfo))
|
|
auraAbsorbMod = eff.GetAmount();
|
|
}
|
|
MathFunctions.RoundToInterval(ref auraAbsorbMod, 0.0f, 100.0f);
|
|
|
|
int absorbIgnoringDamage = (int)MathFunctions.CalculatePct(dmgInfo.GetDamage(), auraAbsorbMod);
|
|
dmgInfo.ModifyDamage(-absorbIgnoringDamage);
|
|
|
|
// 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 vSchoolAbsorbCopy = victim.GetAuraEffectsByType(AuraType.SchoolAbsorb);
|
|
vSchoolAbsorbCopy.Sort();//new AbsorbAuraOrderPred());todo fix me
|
|
|
|
// absorb without mana cost
|
|
foreach (var eff in vSchoolAbsorbCopy)
|
|
{
|
|
// Check if aura was removed during iteration - we don't need to work on such auras
|
|
AuraApplication aurApp = eff.GetBase().GetApplicationOfTarget(victim.GetGUID());
|
|
if (aurApp == null)
|
|
continue;
|
|
if (!Convert.ToBoolean(eff.GetMiscValue() & (int)schoolMask))
|
|
continue;
|
|
|
|
// get amount which can be still absorbed by the aura
|
|
int currentAbsorb = eff.GetAmount();
|
|
// aura with infinite absorb amount - let the scripts handle absorbtion amount, set here to 0 for safety
|
|
if (currentAbsorb < 0)
|
|
currentAbsorb = 0;
|
|
|
|
uint tempAbsorb = (uint)currentAbsorb;
|
|
|
|
bool defaultPrevented = false;
|
|
|
|
eff.GetBase().CallScriptEffectAbsorbHandlers(eff, aurApp, dmgInfo, ref tempAbsorb, ref defaultPrevented);
|
|
currentAbsorb = (int)tempAbsorb;
|
|
|
|
if (defaultPrevented)
|
|
continue;
|
|
|
|
// absorb must be smaller than the damage itself
|
|
MathFunctions.RoundToInterval(ref currentAbsorb, 0, dmgInfo.GetDamage());
|
|
|
|
dmgInfo.AbsorbDamage((uint)currentAbsorb);
|
|
|
|
tempAbsorb = (uint)currentAbsorb;
|
|
eff.GetBase().CallScriptEffectAfterAbsorbHandlers(eff, aurApp, dmgInfo, ref tempAbsorb);
|
|
|
|
// Check if our aura is using amount to count damage
|
|
if (eff.GetAmount() >= 0)
|
|
{
|
|
// Reduce shield amount
|
|
eff.ChangeAmount(eff.GetAmount() - currentAbsorb);
|
|
// Aura cannot absorb anything more - remove it
|
|
if (eff.GetAmount() <= 0)
|
|
eff.GetBase().Remove(AuraRemoveMode.EnemySpell);
|
|
}
|
|
}
|
|
|
|
// absorb by mana cost
|
|
var vManaShieldCopy = victim.GetAuraEffectsByType(AuraType.ManaShield);
|
|
foreach (var absorbAurEff in vManaShieldCopy)
|
|
{
|
|
if (dmgInfo.GetDamage() <= 0)
|
|
break;
|
|
|
|
// Check if aura was removed during iteration - we don't need to work on such auras
|
|
AuraApplication aurApp = absorbAurEff.GetBase().GetApplicationOfTarget(victim.GetGUID());
|
|
if (aurApp == null)
|
|
continue;
|
|
// check damage school mask
|
|
if (!Convert.ToBoolean(absorbAurEff.GetMiscValue() & (int)schoolMask))
|
|
continue;
|
|
|
|
// get amount which can be still absorbed by the aura
|
|
int currentAbsorb = absorbAurEff.GetAmount();
|
|
// aura with infinite absorb amount - let the scripts handle absorbtion amount, set here to 0 for safety
|
|
if (currentAbsorb < 0)
|
|
currentAbsorb = 0;
|
|
|
|
uint tempAbsorb = (uint)currentAbsorb;
|
|
|
|
bool defaultPrevented = false;
|
|
|
|
absorbAurEff.GetBase().CallScriptEffectManaShieldHandlers(absorbAurEff, aurApp, dmgInfo, ref tempAbsorb, defaultPrevented);
|
|
currentAbsorb = (int)tempAbsorb;
|
|
|
|
if (defaultPrevented)
|
|
continue;
|
|
|
|
// absorb must be smaller than the damage itself
|
|
MathFunctions.RoundToInterval(ref currentAbsorb, 0, dmgInfo.GetDamage());
|
|
|
|
int manaReduction = currentAbsorb;
|
|
|
|
// lower absorb amount by talents
|
|
float manaMultiplier = absorbAurEff.GetSpellEffectInfo().CalcValueMultiplier(absorbAurEff.GetCaster());
|
|
if (manaMultiplier != 0)
|
|
manaReduction = (int)(manaReduction * manaMultiplier);
|
|
|
|
int manaTaken = -victim.ModifyPower(PowerType.Mana, -manaReduction);
|
|
|
|
// take case when mana has ended up into account
|
|
currentAbsorb = currentAbsorb != 0 ? currentAbsorb * manaTaken / manaReduction : 0;
|
|
|
|
dmgInfo.AbsorbDamage((uint)currentAbsorb);
|
|
|
|
tempAbsorb = (uint)currentAbsorb;
|
|
absorbAurEff.GetBase().CallScriptEffectAfterManaShieldHandlers(absorbAurEff, aurApp, dmgInfo, ref tempAbsorb);
|
|
|
|
// Check if our aura is using amount to count damage
|
|
if (absorbAurEff.GetAmount() >= 0)
|
|
{
|
|
absorbAurEff.ChangeAmount(absorbAurEff.GetAmount() - currentAbsorb);
|
|
if ((absorbAurEff.GetAmount() <= 0))
|
|
absorbAurEff.GetBase().Remove(AuraRemoveMode.EnemySpell);
|
|
}
|
|
}
|
|
|
|
dmgInfo.ModifyDamage(absorbIgnoringDamage);
|
|
|
|
// split damage auras - only when not damaging self
|
|
if (victim != this)
|
|
{
|
|
// 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 vSplitDamagePctCopy = victim.GetAuraEffectsByType(AuraType.SplitDamagePct);
|
|
foreach (var eff in vSplitDamagePctCopy)
|
|
{
|
|
if (dmgInfo.GetDamage() <= 0)
|
|
break;
|
|
|
|
// Check if aura was removed during iteration - we don't need to work on such auras
|
|
AuraApplication aurApp = eff.GetBase().GetApplicationOfTarget(victim.GetGUID());
|
|
if (aurApp == null)
|
|
continue;
|
|
|
|
// check damage school mask
|
|
if (!Convert.ToBoolean(eff.GetMiscValue() & (int)schoolMask))
|
|
continue;
|
|
|
|
// Damage can be splitted only if aura has an alive caster
|
|
Unit caster = eff.GetCaster();
|
|
if (caster == null || (caster == victim) || !caster.IsInWorld || !caster.IsAlive())
|
|
continue;
|
|
|
|
uint splitDamage = MathFunctions.CalculatePct(dmgInfo.GetDamage(), eff.GetAmount());
|
|
|
|
eff.GetBase().CallScriptEffectSplitHandlers(eff, aurApp, dmgInfo, splitDamage);
|
|
|
|
// absorb must be smaller than the damage itself
|
|
MathFunctions.RoundToInterval(ref splitDamage, 0, dmgInfo.GetDamage());
|
|
|
|
dmgInfo.AbsorbDamage(splitDamage);
|
|
|
|
// check if caster is immune to damage
|
|
if (caster.IsImmunedToDamage(schoolMask))
|
|
{
|
|
victim.SendSpellMiss(caster, eff.GetSpellInfo().Id, SpellMissInfo.Immune);
|
|
continue;
|
|
}
|
|
|
|
uint split_absorb = 0;
|
|
DealDamageMods(caster, ref splitDamage, ref split_absorb);
|
|
|
|
SpellNonMeleeDamage log = new SpellNonMeleeDamage(this, caster, eff.GetSpellInfo().Id, eff.GetBase().GetSpellXSpellVisualId(), schoolMask, eff.GetBase().GetCastGUID());
|
|
CleanDamage cleanDamage = new CleanDamage(splitDamage, 0, WeaponAttackType.BaseAttack, MeleeHitOutcome.Normal);
|
|
DealDamage(caster, splitDamage, cleanDamage, DamageEffectType.Direct, schoolMask, eff.GetSpellInfo(), false);
|
|
log.damage = splitDamage;
|
|
log.absorb = split_absorb;
|
|
SendSpellNonMeleeDamageLog(log);
|
|
|
|
// break 'Fear' and similar auras
|
|
DamageInfo damageInfo = new DamageInfo(caster, this, splitDamage, eff.GetSpellInfo(), schoolMask, DamageEffectType.Direct, WeaponAttackType.BaseAttack);
|
|
ProcSkillsAndAuras(caster, ProcFlags.None, ProcFlags.TakenSpellMagicDmgClassNeg, ProcFlagsSpellType.Damage, ProcFlagsSpellPhase.Hit, ProcFlagsHit.None, null, damageInfo, null);
|
|
}
|
|
}
|
|
|
|
resist = dmgInfo.GetResist();
|
|
absorb = dmgInfo.GetAbsorb();
|
|
}
|
|
|
|
public void CalcHealAbsorb(HealInfo healInfo)
|
|
{
|
|
if (healInfo.GetHeal() == 0)
|
|
return;
|
|
|
|
// Need remove expired auras after
|
|
bool existExpired = false;
|
|
|
|
// absorb without mana cost
|
|
var vHealAbsorb = healInfo.GetTarget().GetAuraEffectsByType(AuraType.SchoolHealAbsorb);
|
|
foreach (var eff in vHealAbsorb)
|
|
{
|
|
if (healInfo.GetHeal() <= 0)
|
|
break;
|
|
|
|
if (!Convert.ToBoolean(eff.GetMiscValue() & (int)healInfo.GetSpellInfo().SchoolMask))
|
|
continue;
|
|
|
|
// Max Amount can be absorbed by this aura
|
|
int currentAbsorb = eff.GetAmount();
|
|
|
|
// Found empty aura (impossible but..)
|
|
if (currentAbsorb <= 0)
|
|
{
|
|
existExpired = true;
|
|
continue;
|
|
}
|
|
|
|
// currentAbsorb - damage can be absorbed by shield
|
|
// If need absorb less damage
|
|
currentAbsorb = (int)Math.Min(healInfo.GetHeal(), currentAbsorb);
|
|
|
|
healInfo.AbsorbHeal((uint)currentAbsorb);
|
|
|
|
// Reduce shield amount
|
|
eff.ChangeAmount(eff.GetAmount() - currentAbsorb);
|
|
// Need remove it later
|
|
if (eff.GetAmount() <= 0)
|
|
existExpired = true;
|
|
}
|
|
|
|
// Remove all expired absorb auras
|
|
if (existExpired)
|
|
{
|
|
for (var i = 0; i < vHealAbsorb.Count;)
|
|
{
|
|
AuraEffect auraEff = vHealAbsorb[i];
|
|
++i;
|
|
if (auraEff.GetAmount() <= 0)
|
|
{
|
|
uint removedAuras = healInfo.GetTarget().m_removedAurasCount;
|
|
auraEff.GetBase().Remove(AuraRemoveMode.EnemySpell);
|
|
if (removedAuras + 1 < healInfo.GetTarget().m_removedAurasCount)
|
|
i = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public uint CalcArmorReducedDamage(Unit attacker, Unit victim, uint damage, SpellInfo spellInfo, WeaponAttackType attackType = WeaponAttackType.Max)
|
|
{
|
|
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)
|
|
{
|
|
Player modOwner = GetSpellModOwner();
|
|
if (modOwner != null)
|
|
modOwner.ApplySpellMod(spellInfo.Id, SpellModOp.IgnoreArmor, ref armor);
|
|
}
|
|
|
|
var resIgnoreAuras = GetAuraEffectsByType(AuraType.ModIgnoreTargetResist);
|
|
foreach (var eff in resIgnoreAuras)
|
|
{
|
|
if (eff.GetMiscValue().HasAnyFlag((int)SpellSchoolMask.Normal))
|
|
armor = (float)Math.Floor(MathFunctions.AddPct(ref armor, -eff.GetAmount()));
|
|
}
|
|
|
|
// Apply Player CR_ARMOR_PENETRATION rating
|
|
if (IsTypeId(TypeId.Player))
|
|
{
|
|
float maxArmorPen = 0;
|
|
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, armor);
|
|
// Figure out how much armor do we ignore
|
|
float armorPen = MathFunctions.CalculatePct(maxArmorPen, ToPlayer().GetRatingBonusValue(CombatRating.ArmorPenetration));
|
|
// Got the value, apply it
|
|
armor -= Math.Min(armorPen, maxArmorPen);
|
|
}
|
|
|
|
if (MathFunctions.fuzzyLe(armor, 0.0f))
|
|
return damage;
|
|
|
|
uint attackerLevel = attacker.GetLevelForTarget(victim);
|
|
if (attackerLevel > CliDB.ArmorMitigationByLvlGameTable.GetTableRowCount())
|
|
attackerLevel = (uint)CliDB.ArmorMitigationByLvlGameTable.GetTableRowCount();
|
|
|
|
GtArmorMitigationByLvlRecord ambl = CliDB.ArmorMitigationByLvlGameTable.GetRow(attackerLevel);
|
|
if (ambl == null)
|
|
return damage;
|
|
|
|
float mitigation = Math.Min(armor / (armor + ambl.Mitigation), 0.85f);
|
|
return Math.Max((uint)(damage * (1.0f - mitigation)), 1);
|
|
}
|
|
|
|
public uint MeleeDamageBonusDone(Unit victim, uint pdamage, WeaponAttackType attType, SpellInfo spellProto = null)
|
|
{
|
|
if (victim == null || pdamage == 0)
|
|
return 0;
|
|
|
|
uint creatureTypeMask = victim.GetCreatureTypeMask();
|
|
|
|
// Done fixed damage bonus auras
|
|
int DoneFlatBenefit = 0;
|
|
|
|
// ..done
|
|
var mDamageDoneCreature = GetAuraEffectsByType(AuraType.ModDamageDoneCreature);
|
|
foreach (var eff in mDamageDoneCreature)
|
|
if (Convert.ToBoolean(creatureTypeMask & eff.GetMiscValue()))
|
|
DoneFlatBenefit += eff.GetAmount();
|
|
|
|
// ..done
|
|
// SPELL_AURA_MOD_DAMAGE_DONE included in weapon damage
|
|
|
|
// ..done (base at attack power for marked target and base at attack power for creature type)
|
|
int APbonus = 0;
|
|
|
|
if (attType == WeaponAttackType.RangedAttack)
|
|
{
|
|
APbonus += victim.GetTotalAuraModifier(AuraType.RangedAttackPowerAttackerBonus);
|
|
|
|
// ..done (base at attack power and creature type)
|
|
var mCreatureAttackPower = GetAuraEffectsByType(AuraType.ModRangedAttackPowerVersus);
|
|
foreach (var eff in mCreatureAttackPower)
|
|
if (Convert.ToBoolean(creatureTypeMask & eff.GetMiscValue()))
|
|
APbonus += eff.GetAmount();
|
|
}
|
|
else
|
|
{
|
|
APbonus += victim.GetTotalAuraModifier(AuraType.MeleeAttackPowerAttackerBonus);
|
|
|
|
// ..done (base at attack power and creature type)
|
|
var mCreatureAttackPower = GetAuraEffectsByType(AuraType.ModMeleeAttackPowerVersus);
|
|
foreach (var eff in mCreatureAttackPower)
|
|
if (Convert.ToBoolean(creatureTypeMask & eff.GetMiscValue()))
|
|
APbonus += eff.GetAmount();
|
|
}
|
|
|
|
if (APbonus != 0) // Can be negative
|
|
{
|
|
bool normalized = spellProto != null && spellProto.HasEffect(GetMap().GetDifficultyID(), SpellEffectName.NormalizedWeaponDmg);
|
|
DoneFlatBenefit += (int)(APbonus / 3.5f * GetAPMultiplier(attType, normalized));
|
|
}
|
|
|
|
// Done total percent damage auras
|
|
float DoneTotalMod = 1.0f;
|
|
|
|
// Some spells don't benefit from pct done mods
|
|
if (spellProto != null)
|
|
{
|
|
// mods for SPELL_SCHOOL_MASK_NORMAL are already factored in base melee damage calculation
|
|
if (!spellProto.HasAttribute(SpellAttr6.NoDonePctDamageMods) && !spellProto.GetSchoolMask().HasAnyFlag(SpellSchoolMask.Normal))
|
|
{
|
|
float maxModDamagePercentSchool = 0.0f;
|
|
for (var i = SpellSchools.Holy; i < SpellSchools.Max; ++i)
|
|
{
|
|
if (Convert.ToBoolean((int)spellProto.GetSchoolMask() & (1 << (int)i)))
|
|
maxModDamagePercentSchool = Math.Max(maxModDamagePercentSchool, GetFloatValue(PlayerFields.ModDamageDonePct + (int)i));
|
|
}
|
|
|
|
DoneTotalMod *= maxModDamagePercentSchool;
|
|
}
|
|
}
|
|
|
|
var mDamageDoneVersus = GetAuraEffectsByType(AuraType.ModDamageDoneVersus);
|
|
foreach (var eff in mDamageDoneVersus)
|
|
if (Convert.ToBoolean(creatureTypeMask & eff.GetMiscValue()))
|
|
MathFunctions.AddPct(ref DoneTotalMod, eff.GetAmount());
|
|
|
|
// bonus against aurastate
|
|
var mDamageDoneVersusAurastate = GetAuraEffectsByType(AuraType.ModDamageDoneVersusAurastate);
|
|
foreach (var eff in mDamageDoneVersusAurastate)
|
|
if (victim.HasAuraState((AuraStateType)eff.GetMiscValue()))
|
|
MathFunctions.AddPct(ref DoneTotalMod, eff.GetAmount());
|
|
|
|
// Add SPELL_AURA_MOD_DAMAGE_DONE_FOR_MECHANIC percent bonus
|
|
if (spellProto != null)
|
|
MathFunctions.AddPct(ref DoneTotalMod, GetTotalAuraModifierByMiscValue(AuraType.ModDamageDoneForMechanic, (int)spellProto.Mechanic));
|
|
|
|
float tmpDamage = (pdamage + DoneFlatBenefit) * DoneTotalMod;
|
|
|
|
// apply spellmod to Done damage
|
|
if (spellProto != null)
|
|
{
|
|
Player modOwner = GetSpellModOwner();
|
|
if (modOwner != null)
|
|
modOwner.ApplySpellMod(spellProto.Id, SpellModOp.Damage, ref tmpDamage);
|
|
}
|
|
|
|
// bonus result can be negative
|
|
return (uint)Math.Max(tmpDamage, 0.0f);
|
|
}
|
|
public uint MeleeDamageBonusTaken(Unit attacker, uint pdamage, WeaponAttackType attType, SpellInfo spellProto = null)
|
|
{
|
|
if (pdamage == 0)
|
|
return 0;
|
|
|
|
int TakenFlatBenefit = 0;
|
|
float TakenTotalCasterMod = 0.0f;
|
|
|
|
// get all auras from caster that allow the spell to ignore resistance (sanctified wrath)
|
|
int attackSchoolMask = (int)(spellProto != null ? spellProto.GetSchoolMask() : SpellSchoolMask.Normal);
|
|
var IgnoreResistAuras = attacker.GetAuraEffectsByType(AuraType.ModIgnoreTargetResist);
|
|
foreach (var eff in IgnoreResistAuras)
|
|
{
|
|
if (eff.GetMiscValue().HasAnyFlag(attackSchoolMask))
|
|
TakenTotalCasterMod += eff.GetAmount();
|
|
}
|
|
|
|
// ..taken
|
|
var mDamageTaken = GetAuraEffectsByType(AuraType.ModDamageTaken);
|
|
foreach (var eff in mDamageTaken)
|
|
if (Convert.ToBoolean(eff.GetMiscValue() & (int)attacker.GetMeleeDamageSchoolMask()))
|
|
TakenFlatBenefit += eff.GetAmount();
|
|
|
|
if (attType != WeaponAttackType.RangedAttack)
|
|
TakenFlatBenefit += GetTotalAuraModifier(AuraType.ModMeleeDamageTaken);
|
|
else
|
|
TakenFlatBenefit += GetTotalAuraModifier(AuraType.ModRangedDamageTaken);
|
|
|
|
// Taken total percent damage auras
|
|
float TakenTotalMod = 1.0f;
|
|
|
|
// ..taken
|
|
TakenTotalMod *= GetTotalAuraMultiplierByMiscMask(AuraType.ModDamagePercentTaken, (uint)attacker.GetMeleeDamageSchoolMask());
|
|
|
|
// .. taken pct (special attacks)
|
|
if (spellProto != null)
|
|
{
|
|
// From caster spells
|
|
var mOwnerTaken = GetAuraEffectsByType(AuraType.ModSpellDamageFromCaster);
|
|
foreach (var eff in mOwnerTaken)
|
|
if (eff.GetCasterGUID() == attacker.GetGUID() && eff.IsAffectingSpell(spellProto))
|
|
MathFunctions.AddPct(ref TakenTotalMod, eff.GetAmount());
|
|
|
|
// Mod damage from spell mechanic
|
|
uint mechanicMask = spellProto.GetAllEffectsMechanicMask();
|
|
|
|
// Shred, Maul - "Effects which increase Bleed damage also increase Shred damage"
|
|
if (spellProto.SpellFamilyName == SpellFamilyNames.Druid && spellProto.SpellFamilyFlags[0].HasAnyFlag<uint>(0x00008800))
|
|
mechanicMask |= (1 << (int)Mechanics.Bleed);
|
|
|
|
if (mechanicMask != 0)
|
|
{
|
|
var mDamageDoneMechanic = GetAuraEffectsByType(AuraType.ModMechanicDamageTakenPercent);
|
|
foreach (var eff in mDamageDoneMechanic)
|
|
if (mechanicMask.HasAnyFlag((uint)(1 << eff.GetMiscValue())))
|
|
MathFunctions.AddPct(ref TakenTotalMod, eff.GetAmount());
|
|
}
|
|
}
|
|
|
|
AuraEffect cheatDeath = GetAuraEffect(45182, 0);
|
|
if (cheatDeath != null)
|
|
MathFunctions.AddPct(ref TakenTotalMod, cheatDeath.GetAmount());
|
|
|
|
if (attType != WeaponAttackType.RangedAttack)
|
|
{
|
|
var mModMeleeDamageTakenPercent = GetAuraEffectsByType(AuraType.ModMeleeDamageTakenPct);
|
|
foreach (var eff in mModMeleeDamageTakenPercent)
|
|
MathFunctions.AddPct(ref TakenTotalMod, eff.GetAmount());
|
|
}
|
|
else
|
|
{
|
|
var mModRangedDamageTakenPercent = GetAuraEffectsByType(AuraType.ModRangedDamageTakenPct);
|
|
foreach (var eff in mModRangedDamageTakenPercent)
|
|
MathFunctions.AddPct(ref TakenTotalMod, eff.GetAmount());
|
|
}
|
|
|
|
float tmpDamage = 0.0f;
|
|
|
|
if (TakenTotalCasterMod != 0)
|
|
{
|
|
if (TakenFlatBenefit < 0)
|
|
{
|
|
if (TakenTotalMod < 1)
|
|
tmpDamage = (((MathFunctions.CalculatePct(pdamage, TakenTotalCasterMod) + TakenFlatBenefit) * TakenTotalMod) + MathFunctions.CalculatePct(pdamage, TakenTotalCasterMod));
|
|
else
|
|
tmpDamage = (((MathFunctions.CalculatePct(pdamage, TakenTotalCasterMod) + TakenFlatBenefit) + MathFunctions.CalculatePct(pdamage, TakenTotalCasterMod)) * TakenTotalMod);
|
|
}
|
|
else if (TakenTotalMod < 1)
|
|
tmpDamage = ((MathFunctions.CalculatePct(pdamage + TakenFlatBenefit, TakenTotalCasterMod) * TakenTotalMod) + MathFunctions.CalculatePct(pdamage + TakenFlatBenefit, TakenTotalCasterMod));
|
|
}
|
|
if (tmpDamage == 0)
|
|
tmpDamage = (pdamage + TakenFlatBenefit) * TakenTotalMod;
|
|
|
|
// bonus result can be negative
|
|
return (uint)Math.Max(tmpDamage, 0.0f);
|
|
}
|
|
|
|
bool isBlockCritical()
|
|
{
|
|
if (RandomHelper.randChance(GetTotalAuraModifier(AuraType.ModBlockCritChance)))
|
|
return true;
|
|
return false;
|
|
}
|
|
public virtual SpellSchoolMask GetMeleeDamageSchoolMask() { return SpellSchoolMask.None; }
|
|
|
|
// Redirect Threat
|
|
public void SetRedirectThreat(ObjectGuid guid, uint pct) { _redirectThreatInfo.Set(guid, pct); }
|
|
public void ResetRedirectThreat() { SetRedirectThreat(ObjectGuid.Empty, 0); }
|
|
void ModifyRedirectThreat(int amount) { _redirectThreatInfo.ModifyThreatPct(amount); }
|
|
public uint GetRedirectThreatPercent() { return _redirectThreatInfo.GetThreatPct(); }
|
|
public Unit GetRedirectThreatTarget()
|
|
{
|
|
return Global.ObjAccessor.GetUnit(this, _redirectThreatInfo.GetTargetGUID());
|
|
}
|
|
|
|
float CalculateDefaultCoefficient(SpellInfo spellInfo, DamageEffectType damagetype)
|
|
{
|
|
// Damage over Time spells bonus calculation
|
|
float DotFactor = 1.0f;
|
|
if (damagetype == DamageEffectType.DOT)
|
|
{
|
|
|
|
int DotDuration = spellInfo.GetDuration();
|
|
if (!spellInfo.IsChanneled() && DotDuration > 0)
|
|
DotFactor = DotDuration / 15000.0f;
|
|
|
|
uint DotTicks = spellInfo.GetMaxTicks(GetMap().GetDifficultyID());
|
|
if (DotTicks != 0)
|
|
DotFactor /= DotTicks;
|
|
}
|
|
|
|
uint CastingTime = (uint)(spellInfo.IsChanneled() ? spellInfo.GetDuration() : spellInfo.CalcCastTime());
|
|
// Distribute Damage over multiple effects, reduce by AoE
|
|
CastingTime = GetCastingTimeForBonus(spellInfo, damagetype, CastingTime);
|
|
|
|
// As wowwiki says: C = (Cast Time / 3.5)
|
|
return (CastingTime / 3500.0f) * DotFactor;
|
|
}
|
|
|
|
public void ApplyAttackTimePercentMod(WeaponAttackType att, float val, bool apply)
|
|
{
|
|
float remainingTimePct = m_attackTimer[(int)att] / (m_baseAttackSpeed[(int)att] * m_modAttackSpeedPct[(int)att]);
|
|
if (val > 0)
|
|
{
|
|
MathFunctions.ApplyPercentModFloatVar(ref m_modAttackSpeedPct[(int)att], val, !apply);
|
|
|
|
if (att == WeaponAttackType.BaseAttack)
|
|
ApplyPercentModFloatValue(UnitFields.ModHaste, val, !apply);
|
|
else if (att == WeaponAttackType.RangedAttack)
|
|
ApplyPercentModFloatValue(UnitFields.ModRangedHaste, val, !apply);
|
|
}
|
|
else
|
|
{
|
|
MathFunctions.ApplyPercentModFloatVar(ref m_modAttackSpeedPct[(int)att], -val, apply);
|
|
|
|
if (att == WeaponAttackType.BaseAttack)
|
|
ApplyPercentModFloatValue(UnitFields.ModHaste, -val, apply);
|
|
else if (att == WeaponAttackType.RangedAttack)
|
|
ApplyPercentModFloatValue(UnitFields.ModRangedHaste, -val, apply);
|
|
}
|
|
|
|
UpdateAttackTimeField(att);
|
|
m_attackTimer[(int)att] = (uint)(m_baseAttackSpeed[(int)att] * m_modAttackSpeedPct[(int)att] * remainingTimePct);
|
|
}
|
|
|
|
// function based on function Unit.CanAttack from 13850 client
|
|
public bool _IsValidAttackTarget(Unit target, SpellInfo bySpell, WorldObject obj = null)
|
|
{
|
|
Contract.Assert(target != null);
|
|
|
|
// can't attack self
|
|
if (this == target)
|
|
return false;
|
|
|
|
// can't attack unattackable units or GMs
|
|
if (target.HasUnitState(UnitState.Unattackable)
|
|
|| (target.IsTypeId(TypeId.Player) && target.ToPlayer().IsGameMaster()))
|
|
return false;
|
|
|
|
// can't attack own vehicle or passenger
|
|
if (m_vehicle != null)
|
|
if (IsOnVehicle(target) || m_vehicle.GetBase().IsOnVehicle(target))
|
|
return false;
|
|
|
|
// can't attack invisible (ignore stealth for aoe spells) also if the area being looked at is from a spell use the dynamic object created instead of the casting unit. Ignore stealth if target is player and unit in combat with same player
|
|
if ((bySpell == null || !bySpell.HasAttribute(SpellAttr6.CanTargetInvisible)) && (obj ? !obj.CanSeeOrDetect(target, bySpell != null && bySpell.IsAffectingArea(GetMap().GetDifficultyID())) : !CanSeeOrDetect(target, (bySpell != null && bySpell.IsAffectingArea(GetMap().GetDifficultyID())) || (target.IsTypeId(TypeId.Player) && target.HasStealthAura() && target.IsInCombat() && IsInCombatWith(target)))))
|
|
return false;
|
|
|
|
// can't attack dead
|
|
if ((bySpell == null || !bySpell.IsAllowingDeadTarget()) && !target.IsAlive())
|
|
return false;
|
|
|
|
// can't attack untargetable
|
|
if ((bySpell == null || !bySpell.HasAttribute(SpellAttr6.CanTargetUntargetable))
|
|
&& target.HasFlag(UnitFields.Flags, UnitFlags.NotSelectable))
|
|
return false;
|
|
|
|
Player playerAttacker = ToPlayer();
|
|
if (playerAttacker != null)
|
|
{
|
|
if (playerAttacker.HasFlag(PlayerFields.Flags, PlayerFlags.Commentator2))
|
|
return false;
|
|
}
|
|
|
|
// check flags
|
|
if (target.HasFlag(UnitFields.Flags, (UnitFlags.NonAttackable | UnitFlags.TaxiFlight | UnitFlags.NotAttackable1 | UnitFlags.Unk16))
|
|
|| (!HasFlag(UnitFields.Flags, UnitFlags.PvpAttackable) && target.HasFlag(UnitFields.Flags, UnitFlags.ImmuneToNpc))
|
|
|| (!target.HasFlag(UnitFields.Flags, UnitFlags.PvpAttackable) && HasFlag(UnitFields.Flags, UnitFlags.ImmuneToNpc)))
|
|
return false;
|
|
|
|
if ((bySpell == null || !bySpell.HasAttribute(SpellAttr8.AttackIgnoreImmuneToPCFlag))
|
|
&& (HasFlag(UnitFields.Flags, UnitFlags.PvpAttackable) && target.HasFlag(UnitFields.Flags, UnitFlags.ImmuneToPc))
|
|
// check if this is a world trigger cast - GOs are using world triggers to cast their spells, so we need to ignore their immunity flag here, this is a temp workaround, needs removal when go cast is implemented properly
|
|
&& GetEntry() != SharedConst.WorldTrigger)
|
|
return false;
|
|
|
|
// CvC case - can attack each other only when one of them is hostile
|
|
if (!HasFlag(UnitFields.Flags, UnitFlags.PvpAttackable) && !target.HasFlag(UnitFields.Flags, UnitFlags.PvpAttackable))
|
|
return GetReactionTo(target) <= ReputationRank.Hostile || target.GetReactionTo(this) <= ReputationRank.Hostile;
|
|
|
|
// PvP, PvC, CvP case
|
|
// can't attack friendly targets
|
|
if (GetReactionTo(target) > ReputationRank.Neutral
|
|
|| target.GetReactionTo(this) > ReputationRank.Neutral)
|
|
return false;
|
|
|
|
Player playerAffectingAttacker = HasFlag(UnitFields.Flags, UnitFlags.PvpAttackable) ? GetAffectingPlayer() : null;
|
|
Player playerAffectingTarget = target.HasFlag(UnitFields.Flags, UnitFlags.PvpAttackable) ? target.GetAffectingPlayer() : null;
|
|
|
|
// Not all neutral creatures can be attacked (even some unfriendly faction does not react aggresive to you, like Sporaggar)
|
|
if ((playerAffectingAttacker && !playerAffectingTarget) || (!playerAffectingAttacker && playerAffectingTarget))
|
|
{
|
|
if (!(target.IsTypeId(TypeId.Player) && IsTypeId(TypeId.Player)) &&
|
|
!(target.IsTypeId(TypeId.Unit) && IsTypeId(TypeId.Unit)))
|
|
{
|
|
Player player = playerAffectingAttacker ? playerAffectingAttacker : playerAffectingTarget;
|
|
Unit creature = playerAffectingAttacker ? target : this;
|
|
|
|
var factionTemplate = creature.GetFactionTemplateEntry();
|
|
if (factionTemplate != null)
|
|
{
|
|
if (player.GetReputationMgr().GetForcedRankIfAny(factionTemplate) == ReputationRank.None)
|
|
{
|
|
var factionEntry = CliDB.FactionStorage.LookupByKey(factionTemplate.Faction);
|
|
if (factionEntry != null)
|
|
{
|
|
var repState = player.GetReputationMgr().GetState(factionEntry);
|
|
if (repState != null)
|
|
if (!Convert.ToBoolean(repState.Flags & FactionFlags.AtWar))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
Creature creatureAttacker = ToCreature();
|
|
if (creatureAttacker != null && Convert.ToBoolean(creatureAttacker.GetCreatureTemplate().TypeFlags & CreatureTypeFlags.TreatAsRaidUnit))
|
|
return false;
|
|
|
|
// check duel - before sanctuary checks
|
|
if (playerAffectingAttacker != null && playerAffectingTarget != null)
|
|
if (playerAffectingAttacker.duel != null && playerAffectingAttacker.duel.opponent == playerAffectingTarget && playerAffectingAttacker.duel.startTime != 0)
|
|
return true;
|
|
|
|
// PvP case - can't attack when attacker or target are in sanctuary
|
|
// however, 13850 client doesn't allow to attack when one of the unit's has sanctuary flag and is pvp
|
|
if (target.HasFlag(UnitFields.Flags, UnitFlags.PvpAttackable) && HasFlag(UnitFields.Flags, UnitFlags.PvpAttackable)
|
|
&& (Convert.ToBoolean(target.GetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag) & (byte)UnitBytes2Flags.Sanctuary)
|
|
|| Convert.ToBoolean(GetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag) & (byte)UnitBytes2Flags.Sanctuary)))
|
|
return false;
|
|
|
|
// additional checks - only PvP case
|
|
if (playerAffectingAttacker != null && playerAffectingTarget != null)
|
|
{
|
|
if (Convert.ToBoolean(target.GetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag) & (byte)UnitBytes2Flags.PvP))
|
|
return true;
|
|
|
|
if (Convert.ToBoolean(GetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag) & (byte)UnitBytes2Flags.FFAPvp)
|
|
&& Convert.ToBoolean(target.GetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag) & (byte)UnitBytes2Flags.FFAPvp))
|
|
return true;
|
|
|
|
return (Convert.ToBoolean(GetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag) & (byte)UnitBytes2Flags.Unk1)
|
|
|| Convert.ToBoolean(target.GetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag) & (byte)UnitBytes2Flags.Unk1));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool IsValidAssistTarget(Unit target)
|
|
{
|
|
return _IsValidAssistTarget(target, null);
|
|
}
|
|
// function based on function Unit.CanAssist from 13850 client
|
|
public bool _IsValidAssistTarget(Unit target, SpellInfo bySpell)
|
|
{
|
|
Contract.Assert(target != null);
|
|
|
|
// can assist to self
|
|
if (this == target)
|
|
return true;
|
|
|
|
// can't assist unattackable units or GMs
|
|
if (target.HasUnitState(UnitState.Unattackable)
|
|
|| (target.IsTypeId(TypeId.Player) && target.ToPlayer().IsGameMaster()))
|
|
return false;
|
|
|
|
// can't assist own vehicle or passenger
|
|
if (m_vehicle != null)
|
|
if (IsOnVehicle(target) || m_vehicle.GetBase().IsOnVehicle(target))
|
|
return false;
|
|
|
|
// can't assist invisible
|
|
if ((bySpell == null || !bySpell.HasAttribute(SpellAttr6.CanTargetInvisible)) && !CanSeeOrDetect(target, bySpell != null && bySpell.IsAffectingArea(GetMap().GetDifficultyID())))
|
|
return false;
|
|
|
|
// can't assist dead
|
|
if ((bySpell == null || !bySpell.IsAllowingDeadTarget()) && !target.IsAlive())
|
|
return false;
|
|
|
|
// can't assist untargetable
|
|
if ((bySpell == null || !bySpell.HasAttribute(SpellAttr6.CanTargetUntargetable))
|
|
&& target.HasFlag(UnitFields.Flags, UnitFlags.NotSelectable))
|
|
return false;
|
|
|
|
if (bySpell == null || !bySpell.HasAttribute(SpellAttr6.AssistIgnoreImmuneFlag))
|
|
{
|
|
if (HasFlag(UnitFields.Flags, UnitFlags.PvpAttackable))
|
|
{
|
|
if (target.HasFlag(UnitFields.Flags, UnitFlags.ImmuneToPc))
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if (target.HasFlag(UnitFields.Flags, UnitFlags.ImmuneToNpc))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// can't assist non-friendly targets
|
|
if (GetReactionTo(target) <= ReputationRank.Neutral
|
|
&& target.GetReactionTo(this) <= ReputationRank.Neutral
|
|
&& (!IsTypeId(TypeId.Unit) || !Convert.ToBoolean(ToCreature().GetCreatureTemplate().TypeFlags & CreatureTypeFlags.TreatAsRaidUnit)))
|
|
return false;
|
|
|
|
// Controlled player case, we can assist creatures (reaction already checked above, our faction == charmer faction)
|
|
if (IsTypeId(TypeId.Player) && IsCharmed() && GetCharmerGUID().IsCreature())
|
|
return true;
|
|
|
|
// PvP case
|
|
else if (target.HasFlag(UnitFields.Flags, UnitFlags.PvpAttackable))
|
|
{
|
|
Player targetPlayerOwner = target.GetAffectingPlayer();
|
|
if (HasFlag(UnitFields.Flags, UnitFlags.PvpAttackable))
|
|
{
|
|
Player selfPlayerOwner = GetAffectingPlayer();
|
|
if (selfPlayerOwner != null && targetPlayerOwner != null)
|
|
{
|
|
// can't assist player which is dueling someone
|
|
if (selfPlayerOwner != targetPlayerOwner && targetPlayerOwner.duel != null)
|
|
return false;
|
|
}
|
|
// can't assist player in ffa_pvp zone from outside
|
|
if (Convert.ToBoolean(target.GetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag) & (byte)UnitBytes2Flags.FFAPvp)
|
|
&& !Convert.ToBoolean(GetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag) & (byte)UnitBytes2Flags.FFAPvp))
|
|
return false;
|
|
// can't assist player out of sanctuary from sanctuary if has pvp enabled
|
|
if (Convert.ToBoolean(target.GetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag) & (byte)UnitBytes2Flags.PvP))
|
|
if (Convert.ToBoolean(GetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag) & (byte)UnitBytes2Flags.Sanctuary)
|
|
&& !Convert.ToBoolean(target.GetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag) & (byte)UnitBytes2Flags.Sanctuary))
|
|
return false;
|
|
}
|
|
}
|
|
// PvC case - player can assist creature only if has specific type flags
|
|
else if (HasFlag(UnitFields.Flags, UnitFlags.PvpAttackable)
|
|
&& (bySpell == null || !bySpell.HasAttribute(SpellAttr6.AssistIgnoreImmuneFlag))
|
|
&& !Convert.ToBoolean(target.GetByteValue(UnitFields.Bytes2, UnitBytes2Offsets.PvpFlag) & (byte)UnitBytes2Flags.PvP))
|
|
{
|
|
Creature creatureTarget = target.ToCreature();
|
|
if (creatureTarget != null)
|
|
return Convert.ToBoolean(creatureTarget.GetCreatureTemplate().TypeFlags & CreatureTypeFlags.TreatAsRaidUnit)
|
|
|| Convert.ToBoolean(creatureTarget.GetCreatureTemplate().TypeFlags & CreatureTypeFlags.CanAssist);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Part of Evade mechanics
|
|
public long GetLastDamagedTime() { return _lastDamagedTime; }
|
|
public void SetLastDamagedTime(long val) { _lastDamagedTime = val; }
|
|
}
|
|
}
|