/*
* Copyright (C)
* Copyright (C)
*
* 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 2 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 .
*/
#include "Unit.h"
#include "Common.h"
#include "Battlefield.h"
#include "BattlefieldMgr.h"
#include "Battleground.h"
#include "CellImpl.h"
#include "ConditionMgr.h"
#include "CreatureAI.h"
#include "CreatureAIImpl.h"
#include "CreatureGroups.h"
#include "Creature.h"
#include "Formulas.h"
#include "GridNotifiersImpl.h"
#include "Group.h"
#include "InstanceSaveMgr.h"
#include "InstanceScript.h"
#include "Log.h"
#include "MapManager.h"
#include "MoveSpline.h"
#include "MoveSplineInit.h"
#include "ObjectAccessor.h"
#include "ObjectMgr.h"
#include "Opcodes.h"
#include "OutdoorPvP.h"
#include "PassiveAI.h"
#include "PetAI.h"
#include "Pet.h"
#include "Player.h"
#include "QuestDef.h"
#include "ReputationMgr.h"
#include "SpellAuraEffects.h"
#include "SpellAuras.h"
#include "Spell.h"
#include "SpellInfo.h"
#include "SpellMgr.h"
#include "TemporarySummon.h"
#include "Totem.h"
#include "TotemAI.h"
#include "Transport.h"
#include "UpdateFieldFlags.h"
#include "Util.h"
#include "Vehicle.h"
#include "World.h"
#include "WorldPacket.h"
#include "WorldSession.h"
#include "ArenaSpectator.h"
#include "DynamicVisibility.h"
#include
float baseMoveSpeed[MAX_MOVE_TYPE] =
{
2.5f, // MOVE_WALK
7.0f, // MOVE_RUN
4.5f, // MOVE_RUN_BACK
4.722222f, // MOVE_SWIM
2.5f, // MOVE_SWIM_BACK
3.141594f, // MOVE_TURN_RATE
7.0f, // MOVE_FLIGHT
4.5f, // MOVE_FLIGHT_BACK
3.14f // MOVE_PITCH_RATE
};
float playerBaseMoveSpeed[MAX_MOVE_TYPE] =
{
2.5f, // MOVE_WALK
7.0f, // MOVE_RUN
4.5f, // MOVE_RUN_BACK
4.722222f, // MOVE_SWIM
2.5f, // MOVE_SWIM_BACK
3.141594f, // MOVE_TURN_RATE
7.0f, // MOVE_FLIGHT
4.5f, // MOVE_FLIGHT_BACK
3.14f // MOVE_PITCH_RATE
};
// Used for prepare can/can`t triggr aura
static bool InitTriggerAuraData();
// Define can trigger auras
static bool isTriggerAura[TOTAL_AURAS];
// Define can't trigger auras (need for disable second trigger)
static bool isNonTriggerAura[TOTAL_AURAS];
// Triggered always, even from triggered spells
static bool isAlwaysTriggeredAura[TOTAL_AURAS];
// Prepare lists
static bool procPrepared = InitTriggerAuraData();
DamageInfo::DamageInfo(Unit* _attacker, Unit* _victim, uint32 _damage, SpellInfo const* _spellInfo, SpellSchoolMask _schoolMask, DamageEffectType _damageType)
: m_attacker(_attacker), m_victim(_victim), m_damage(_damage), m_spellInfo(_spellInfo), m_schoolMask(_schoolMask),
m_damageType(_damageType), m_attackType(BASE_ATTACK)
{
m_absorb = 0;
m_resist = 0;
m_block = 0;
}
DamageInfo::DamageInfo(CalcDamageInfo& dmgInfo)
: m_attacker(dmgInfo.attacker), m_victim(dmgInfo.target), m_damage(dmgInfo.damage), m_spellInfo(NULL), m_schoolMask(SpellSchoolMask(dmgInfo.damageSchoolMask)),
m_damageType(DIRECT_DAMAGE), m_attackType(dmgInfo.attackType)
{
m_absorb = 0;
m_resist = 0;
m_block = 0;
}
void DamageInfo::ModifyDamage(int32 amount)
{
amount = std::min(amount, int32(GetDamage()));
m_damage += amount;
}
void DamageInfo::AbsorbDamage(uint32 amount)
{
amount = std::min(amount, GetDamage());
m_absorb += amount;
m_damage -= amount;
}
void DamageInfo::ResistDamage(uint32 amount)
{
amount = std::min(amount, GetDamage());
m_resist += amount;
m_damage -= amount;
}
void DamageInfo::BlockDamage(uint32 amount)
{
amount = std::min(amount, GetDamage());
m_block += amount;
m_damage -= amount;
}
ProcEventInfo::ProcEventInfo(Unit* actor, Unit* actionTarget, Unit* procTarget, uint32 typeMask, uint32 spellTypeMask, uint32 spellPhaseMask, uint32 hitMask, Spell* spell, DamageInfo* damageInfo, HealInfo* healInfo, SpellInfo const* triggeredByAuraSpell)
:_actor(actor), _actionTarget(actionTarget), _procTarget(procTarget), _typeMask(typeMask), _spellTypeMask(spellTypeMask), _spellPhaseMask(spellPhaseMask),
_hitMask(hitMask), _spell(spell), _damageInfo(damageInfo), _healInfo(healInfo), _triggeredByAuraSpell(triggeredByAuraSpell)
{
}
// we can disable this warning for this since it only
// causes undefined behavior when passed to the base class constructor
#ifdef _MSC_VER
#pragma warning(disable:4355)
#endif
Unit::Unit(bool isWorldObject): WorldObject(isWorldObject),
m_movedByPlayer(NULL), m_lastSanctuaryTime(0), IsAIEnabled(false), NeedChangeAI(false),
m_ControlledByPlayer(false), m_CreatedByPlayer(false), movespline(new Movement::MoveSpline()), i_AI(NULL),
i_disabledAI(NULL), m_procDeep(0), m_removedAurasCount(0), i_motionMaster(new MotionMaster(this)), m_regenTimer(0),
m_ThreatManager(this), m_vehicle(NULL), m_vehicleKit(NULL), m_unitTypeMask(UNIT_MASK_NONE),
m_HostileRefManager(this), m_AutoRepeatFirstCast(false)
{
#ifdef _MSC_VER
#pragma warning(default:4355)
#endif
m_objectType |= TYPEMASK_UNIT;
m_objectTypeId = TYPEID_UNIT;
m_updateFlag = (UPDATEFLAG_LIVING | UPDATEFLAG_STATIONARY_POSITION);
m_attackTimer[BASE_ATTACK] = 0;
m_attackTimer[OFF_ATTACK] = 0;
m_attackTimer[RANGED_ATTACK] = 0;
m_modAttackSpeedPct[BASE_ATTACK] = 1.0f;
m_modAttackSpeedPct[OFF_ATTACK] = 1.0f;
m_modAttackSpeedPct[RANGED_ATTACK] = 1.0f;
m_extraAttacks = 0;
m_canDualWield = false;
m_rootTimes = 0;
m_state = 0;
m_deathState = ALIVE;
for (uint8 i = 0; i < CURRENT_MAX_SPELL; ++i)
m_currentSpells[i] = NULL;
for (uint8 i = 0; i < MAX_SUMMON_SLOT; ++i)
m_SummonSlot[i] = 0;
for (uint8 i = 0; i < MAX_GAMEOBJECT_SLOT; ++i)
m_ObjectSlot[i] = 0;
m_auraUpdateIterator = m_ownedAuras.end();
m_interruptMask = 0;
m_transform = 0;
m_canModifyStats = false;
for (uint8 i = 0; i < MAX_SPELL_IMMUNITY; ++i)
m_spellImmune[i].clear();
for (uint8 i = 0; i < UNIT_MOD_END; ++i)
{
m_auraModifiersGroup[i][BASE_VALUE] = 0.0f;
m_auraModifiersGroup[i][BASE_PCT] = 1.0f;
m_auraModifiersGroup[i][TOTAL_VALUE] = 0.0f;
m_auraModifiersGroup[i][TOTAL_PCT] = 1.0f;
}
// implement 50% base damage from offhand
m_auraModifiersGroup[UNIT_MOD_DAMAGE_OFFHAND][TOTAL_PCT] = 0.5f;
for (uint8 i = 0; i < MAX_ATTACK; ++i)
{
m_weaponDamage[i][MINDAMAGE] = BASE_MINDAMAGE;
m_weaponDamage[i][MAXDAMAGE] = BASE_MAXDAMAGE;
}
for (uint8 i = 0; i < MAX_STATS; ++i)
m_createStats[i] = 0.0f;
m_attacking = NULL;
m_modMeleeHitChance = 0.0f;
m_modRangedHitChance = 0.0f;
m_modSpellHitChance = 0.0f;
m_baseSpellCritChance = 5;
m_CombatTimer = 0;
m_lastManaUse = 0;
for (uint8 i = 0; i < MAX_SPELL_SCHOOL; ++i)
m_threatModifier[i] = 1.0f;
for (uint8 i = 0; i < MAX_MOVE_TYPE; ++i)
m_speed_rate[i] = 1.0f;
m_charmInfo = NULL;
_redirectThreatInfo = RedirectThreatInfo();
// remove aurastates allowing special moves
for (uint8 i = 0; i < MAX_REACTIVE; ++i)
m_reactiveTimer[i] = 0;
m_cleanupDone = false;
m_duringRemoveFromWorld = false;
m_serverSideVisibility.SetValue(SERVERSIDE_VISIBILITY_GHOST, GHOST_VISIBILITY_ALIVE);
m_mmapNotAcceptableStartTime = 0;
m_last_notify_position.Relocate(-5000.0f, -5000.0f, -5000.0f, 0.0f);
m_last_notify_mstime = 0;
m_delayed_unit_relocation_timer = 0;
m_delayed_unit_ai_notify_timer = 0;
bRequestForcedVisibilityUpdate = false;
m_last_underwaterstate_position.Relocate(-5000.0f, -5000.0f, -5000.0f, 0.0f);
m_last_environment_position.Relocate(-5000.0f, -5000.0f, -5000.0f, 0.0f);
m_last_isinwater_status = false;
m_last_islittleabovewater_status = false;
m_last_isunderwater_status = false;
m_is_updating_environment = false;
m_last_area_position.Relocate(-5000.0f, -5000.0f, -5000.0f, 0.0f);
m_last_zone_position.Relocate(-5000.0f, -5000.0f, -5000.0f, 0.0f);
m_last_area_id = 0;
m_last_zone_id = 0;
m_last_outdoors_position.Relocate(-5000.0f, -5000.0f, -5000.0f, 0.0f);
m_last_outdoors_status = true; // true by default
m_applyResilience = false;
_instantCast = false;
_lastLiquid = NULL;
_oldFactionId = 0;
}
////////////////////////////////////////////////////////////
// Methods of class GlobalCooldownMgr
bool GlobalCooldownMgr::HasGlobalCooldown(SpellInfo const* spellInfo) const
{
GlobalCooldownList::const_iterator itr = m_GlobalCooldowns.find(spellInfo->StartRecoveryCategory);
return itr != m_GlobalCooldowns.end() && itr->second.duration && getMSTimeDiff(itr->second.cast_time, World::GetGameTimeMS()) < itr->second.duration;
}
void GlobalCooldownMgr::AddGlobalCooldown(SpellInfo const* spellInfo, uint32 gcd)
{
m_GlobalCooldowns[spellInfo->StartRecoveryCategory] = GlobalCooldown(gcd, World::GetGameTimeMS());
}
void GlobalCooldownMgr::CancelGlobalCooldown(SpellInfo const* spellInfo)
{
m_GlobalCooldowns[spellInfo->StartRecoveryCategory].duration = 0;
}
////////////////////////////////////////////////////////////
// Methods of class Unit
Unit::~Unit()
{
// set current spells as deletable
for (uint8 i = 0; i < CURRENT_MAX_SPELL; ++i)
if (m_currentSpells[i])
{
m_currentSpells[i]->SetReferencedFromCurrent(false);
m_currentSpells[i] = NULL;
}
_DeleteRemovedAuras();
delete i_motionMaster;
delete m_charmInfo;
delete movespline;
ASSERT(!m_duringRemoveFromWorld);
ASSERT(!m_attacking);
ASSERT(m_attackers.empty());
// pussywizard: clear m_sharedVision along with back references
if (!m_sharedVision.empty())
{
sLog->outMisc("Unit::~Unit (B1)");
do
{
sLog->outMisc("Unit::~Unit (B2)");
Player* p = *(m_sharedVision.begin());
p->m_isInSharedVisionOf.erase(this);
m_sharedVision.erase(p);
} while (!m_sharedVision.empty());
}
ASSERT(m_Controlled.empty());
ASSERT(m_appliedAuras.empty());
ASSERT(m_ownedAuras.empty());
ASSERT(m_removedAuras.empty());
ASSERT(m_gameObj.empty());
ASSERT(m_dynObj.empty());
if (m_movedByPlayer && m_movedByPlayer != this)
sLog->outMisc("Unit::~Unit (A1)");
HandleSafeUnitPointersOnDelete(this);
}
void Unit::Update(uint32 p_time)
{
// WARNING! Order of execution here is important, do not change.
// Spells must be processed with event system BEFORE they go to _UpdateSpells.
// Or else we may have some SPELL_STATE_FINISHED spells stalled in pointers, that is bad.
m_Events.Update(p_time);
if (!IsInWorld())
return;
// pussywizard:
if (GetTypeId() != TYPEID_PLAYER || (!ToPlayer()->IsBeingTeleported() && !bRequestForcedVisibilityUpdate))
{
if (m_delayed_unit_relocation_timer)
{
if (m_delayed_unit_relocation_timer <= p_time)
{
m_delayed_unit_relocation_timer = 0;
//ExecuteDelayedUnitRelocationEvent();
FindMap()->i_objectsForDelayedVisibility.insert(this);
}
else
m_delayed_unit_relocation_timer -= p_time;
}
if (m_delayed_unit_ai_notify_timer)
{
if (m_delayed_unit_ai_notify_timer <= p_time)
{
m_delayed_unit_ai_notify_timer = 0;
ExecuteDelayedUnitAINotifyEvent();
}
else
m_delayed_unit_ai_notify_timer -= p_time;
}
}
_UpdateSpells( p_time );
if (CanHaveThreatList() && getThreatManager().isNeedUpdateToClient(p_time))
SendThreatListUpdate();
// update combat timer only for players and pets (only pets with PetAI)
if (IsInCombat() && (GetTypeId() == TYPEID_PLAYER || (IsPet() && IsControlledByPlayer())))
{
// Check UNIT_STATE_MELEE_ATTACKING or UNIT_STATE_CHASE (without UNIT_STATE_FOLLOW in this case) so pets can reach far away
// targets without stopping half way there and running off.
// These flags are reset after target dies or another command is given.
if (m_HostileRefManager.isEmpty())
{
// m_CombatTimer set at aura start and it will be freeze until aura removing
if (m_CombatTimer <= p_time)
ClearInCombat();
else
m_CombatTimer -= p_time;
}
}
// not implemented before 3.0.2
// xinef: if attack time > 0, reduce by diff
// if on next update, attack time < 0 assume player didnt attack - set to 0
if (int32 base_attack = getAttackTimer(BASE_ATTACK))
setAttackTimer(BASE_ATTACK, base_attack > 0 ? base_attack - (int32)p_time : 0);
if (int32 ranged_attack = getAttackTimer(RANGED_ATTACK))
setAttackTimer(RANGED_ATTACK, ranged_attack > 0 ? ranged_attack - (int32)p_time : 0);
if (int32 off_attack = getAttackTimer(OFF_ATTACK))
setAttackTimer(OFF_ATTACK, off_attack > 0 ? off_attack - (int32)p_time : 0);
// update abilities available only for fraction of time
UpdateReactives(p_time);
if (IsAlive())
{
ModifyAuraState(AURA_STATE_HEALTHLESS_20_PERCENT, HealthBelowPct(20));
ModifyAuraState(AURA_STATE_HEALTHLESS_35_PERCENT, HealthBelowPct(35));
ModifyAuraState(AURA_STATE_HEALTH_ABOVE_75_PERCENT, HealthAbovePct(75));
}
UpdateSplineMovement(p_time);
GetMotionMaster()->UpdateMotion(p_time);
}
bool Unit::haveOffhandWeapon() const
{
if (Player const* player = ToPlayer())
return player->GetWeaponForAttack(OFF_ATTACK, true);
return CanDualWield();
}
void Unit::MonsterMoveWithSpeed(float x, float y, float z, float speed)
{
Movement::MoveSplineInit init(this);
init.MoveTo(x,y,z);
init.SetVelocity(speed);
init.Launch();
}
void Unit::SendMonsterMove(float NewPosX, float NewPosY, float NewPosZ, uint32 TransitTime, SplineFlags sf)
{
WorldPacket data(SMSG_MONSTER_MOVE, 1+12+4+1+4+4+4+12+GetPackGUID().size());
data.append(GetPackGUID());
data << uint8(0); // new in 3.1
data << GetPositionX() << GetPositionY() << GetPositionZ() + GetHoverHeight();
data << World::GetGameTimeMS();
data << uint8(0);
data << uint32(sf);
data << TransitTime; // Time in between points
data << uint32(1); // 1 single waypoint
data << NewPosX << NewPosY << NewPosZ; // the single waypoint Point B
SendMessageToSet(&data, true);
}
class SplineHandler
{
public:
SplineHandler(Unit* unit) : _unit(unit) { }
bool operator()(Movement::MoveSpline::UpdateResult result)
{
if ((result & (Movement::MoveSpline::Result_NextSegment|Movement::MoveSpline::Result_JustArrived)) &&
_unit->GetTypeId() == TYPEID_UNIT && _unit->GetMotionMaster()->GetCurrentMovementGeneratorType() == ESCORT_MOTION_TYPE &&
_unit->movespline->GetId() == _unit->GetMotionMaster()->GetCurrentSplineId())
{
_unit->ToCreature()->AI()->MovementInform(ESCORT_MOTION_TYPE, _unit->movespline->currentPathIdx()-1);
}
return true;
}
private:
Unit* _unit;
};
void Unit::UpdateSplineMovement(uint32 t_diff)
{
if (movespline->Finalized())
return;
// xinef: process movementinform
// this code cant be placed inside EscortMovementGenerator, because we cant delete active MoveGen while it is updated
SplineHandler handler(this);
movespline->updateState(t_diff, handler);
// Xinef: Spline was cleared by StopMoving, return
if (!movespline->Initialized())
{
DisableSpline();
return;
}
bool arrived = movespline->Finalized();
if (arrived)
DisableSpline();
// pussywizard: update always! not every 400ms, because movement generators need the actual position
//m_movesplineTimer.Update(t_diff);
//if (m_movesplineTimer.Passed() || arrived)
UpdateSplinePosition();
}
void Unit::UpdateSplinePosition()
{
//static uint32 const positionUpdateDelay = 400;
//m_movesplineTimer.Reset(positionUpdateDelay);
Movement::Location loc = movespline->ComputePosition();
if (movespline->onTransport)
{
Position& pos = m_movementInfo.transport.pos;
pos.m_positionX = loc.x;
pos.m_positionY = loc.y;
pos.m_positionZ = loc.z;
pos.m_orientation = loc.orientation;
if (TransportBase* transport = GetDirectTransport())
transport->CalculatePassengerPosition(loc.x, loc.y, loc.z, &loc.orientation);
}
// Xinef: this is bullcrap, if we had spline running update orientation along with position
//if (HasUnitState(UNIT_STATE_CANNOT_TURN))
// loc.orientation = GetOrientation();
if (GetTypeId() == TYPEID_PLAYER)
UpdatePosition(loc.x, loc.y, loc.z, loc.orientation);
else
ToCreature()->SetPosition(loc.x, loc.y, loc.z, loc.orientation);
}
void Unit::DisableSpline()
{
m_movementInfo.RemoveMovementFlag(MovementFlags(MOVEMENTFLAG_SPLINE_ENABLED|MOVEMENTFLAG_FORWARD));
movespline->_Interrupt();
}
void Unit::resetAttackTimer(WeaponAttackType type)
{
int32 time = int32(GetAttackTime(type) * m_modAttackSpeedPct[type]);
m_attackTimer[type] = std::min(m_attackTimer[type]+time, time);
}
bool Unit::IsWithinCombatRange(const Unit* obj, float dist2compare) const
{
if (!obj || !IsInMap(obj) || !InSamePhase(obj))
return false;
float dx = GetPositionX() - obj->GetPositionX();
float dy = GetPositionY() - obj->GetPositionY();
float dz = GetPositionZ() - obj->GetPositionZ();
float distsq = dx * dx + dy * dy + dz * dz;
float sizefactor = GetCombatReach() + obj->GetCombatReach();
float maxdist = dist2compare + sizefactor;
return distsq < maxdist * maxdist;
}
bool Unit::IsWithinMeleeRange(const Unit* obj, float dist) const
{
if (!obj || !IsInMap(obj) || !InSamePhase(obj))
return false;
float dx = GetPositionX() - obj->GetPositionX();
float dy = GetPositionY() - obj->GetPositionY();
float dz = GetPositionZ() - obj->GetPositionZ();
float distsq = dx*dx + dy*dy + dz*dz;
float sizefactor = GetMeleeReach() + obj->GetMeleeReach();
float maxdist = dist + sizefactor;
return distsq < maxdist * maxdist;
}
bool Unit::GetRandomContactPoint(const Unit* obj, float &x, float &y, float &z, bool force) const
{
float combat_reach = GetCombatReach();
if (combat_reach < 0.1f) // sometimes bugged for players
combat_reach = DEFAULT_COMBAT_REACH;
uint32 attacker_number = getAttackers().size();
if (attacker_number > 0)
--attacker_number;
const Creature* c = obj->ToCreature();
if (c)
if (c->isWorldBoss() || c->IsDungeonBoss() || (obj->IsPet() && const_cast(obj)->ToPet()->isControlled()))
attacker_number = 0; // pussywizard: pets and bosses just come to target from their angle
GetNearPoint(obj, x, y, z, isMoving() ? (obj->GetCombatReach() > 7.75f ? obj->GetCombatReach()-7.5f : 0.25f) : obj->GetCombatReach(), 0.0f,
GetAngle(obj) + (attacker_number ? (static_cast(M_PI/2) - static_cast(M_PI) * (float)rand_norm()) * float(attacker_number) / combat_reach * 0.3f : 0));
// pussywizard
if (fabs(this->GetPositionZ()-z) > 3.0f || !IsWithinLOS(x, y, z))
{
x = this->GetPositionX();
y = this->GetPositionY();
z = this->GetPositionZ();
obj->UpdateAllowedPositionZ(x, y, z);
}
float maxDist = MELEE_RANGE + GetMeleeReach() + obj->GetMeleeReach();
if (GetExactDistSq(x, y, z) >= maxDist*maxDist)
{
if (force)
{
x = this->GetPositionX();
y = this->GetPositionY();
z = this->GetPositionZ();
return true;
}
return false;
}
return true;
}
void Unit::UpdateInterruptMask()
{
m_interruptMask = 0;
for (AuraApplicationList::const_iterator i = m_interruptableAuras.begin(); i != m_interruptableAuras.end(); ++i)
m_interruptMask |= (*i)->GetBase()->GetSpellInfo()->AuraInterruptFlags;
if (Spell* spell = m_currentSpells[CURRENT_CHANNELED_SPELL])
if (spell->getState() == SPELL_STATE_CASTING)
m_interruptMask |= spell->m_spellInfo->ChannelInterruptFlags;
}
bool Unit::HasAuraTypeWithFamilyFlags(AuraType auraType, uint32 familyName, uint32 familyFlags) const
{
if (!HasAuraType(auraType))
return false;
AuraEffectList const& auras = GetAuraEffectsByType(auraType);
for (AuraEffectList::const_iterator itr = auras.begin(); itr != auras.end(); ++itr)
if (SpellInfo const* iterSpellProto = (*itr)->GetSpellInfo())
if (iterSpellProto->SpellFamilyName == familyName && iterSpellProto->SpellFamilyFlags[0] & familyFlags)
return true;
return false;
}
bool Unit::HasBreakableByDamageAuraType(AuraType type, uint32 excludeAura) const
{
AuraEffectList const& auras = GetAuraEffectsByType(type);
for (AuraEffectList::const_iterator itr = auras.begin(); itr != auras.end(); ++itr)
if ((!excludeAura || excludeAura != (*itr)->GetSpellInfo()->Id) && //Avoid self interrupt of channeled Crowd Control spells like Seduction
((*itr)->GetSpellInfo()->AuraInterruptFlags & AURA_INTERRUPT_FLAG_TAKE_DAMAGE))
return true;
return false;
}
bool Unit::HasBreakableByDamageCrowdControlAura(Unit* excludeCasterChannel) const
{
uint32 excludeAura = 0;
if (Spell* currentChanneledSpell = excludeCasterChannel ? excludeCasterChannel->GetCurrentSpell(CURRENT_CHANNELED_SPELL) : NULL)
excludeAura = currentChanneledSpell->GetSpellInfo()->Id; //Avoid self interrupt of channeled Crowd Control spells like Seduction
return ( HasBreakableByDamageAuraType(SPELL_AURA_MOD_CONFUSE, excludeAura)
|| HasBreakableByDamageAuraType(SPELL_AURA_MOD_FEAR, excludeAura)
|| HasBreakableByDamageAuraType(SPELL_AURA_MOD_STUN, excludeAura)
|| HasBreakableByDamageAuraType(SPELL_AURA_MOD_ROOT, excludeAura)
|| HasBreakableByDamageAuraType(SPELL_AURA_TRANSFORM, excludeAura));
}
void Unit::DealDamageMods(Unit const* victim, uint32 &damage, uint32* absorb)
{
if (!victim || !victim->IsAlive() || victim->IsInFlight() || (victim->GetTypeId() == TYPEID_UNIT && victim->ToCreature()->IsInEvadeMode()))
{
if (absorb)
*absorb += damage;
damage = 0;
}
}
uint32 Unit::DealDamage(Unit* attacker, Unit* victim, uint32 damage, CleanDamage const* cleanDamage, DamageEffectType damagetype, SpellSchoolMask damageSchoolMask, SpellInfo const* spellProto, bool durabilityLoss, bool allowGM)
{
// Xinef: initialize damage done for rage calculations
// Xinef: its rare to modify damage in hooks, however training dummy's sets damage to 0
uint32 rage_damage = damage + ((cleanDamage != NULL) ? cleanDamage->absorbed_damage : 0);
//if (attacker)
{
if (victim->IsAIEnabled)
victim->GetAI()->DamageTaken(attacker, damage, damagetype, damageSchoolMask);
if (attacker && attacker->IsAIEnabled)
attacker->GetAI()->DamageDealt(victim, damage, damagetype);
}
if (victim->GetTypeId() == TYPEID_PLAYER && attacker != victim)
{
// Signal to pets that their owner was attacked
Pet* pet = victim->ToPlayer()->GetPet();
if (pet && pet->IsAlive())
pet->AI()->OwnerAttackedBy(attacker);
}
// Signal the pet it was attacked so the AI can respond if needed
if (victim->GetTypeId() == TYPEID_UNIT && attacker != victim && victim->IsPet() && victim->IsAlive())
victim->ToPet()->AI()->AttackedBy(attacker);
if (damagetype != NODAMAGE)
{
// interrupting auras with AURA_INTERRUPT_FLAG_DAMAGE before checking !damage (absorbed damage breaks that type of auras)
if (spellProto)
{
if (!spellProto->HasAttribute(SPELL_ATTR4_DAMAGE_DOESNT_BREAK_AURAS))
victim->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TAKE_DAMAGE, spellProto->Id);
}
else
victim->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TAKE_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
AuraEffectList vCopyDamageCopy(victim->GetAuraEffectsByType(SPELL_AURA_SHARE_DAMAGE_PCT));
// copy damage to casters of this aura
for (AuraEffectList::iterator i = vCopyDamageCopy.begin(); i != vCopyDamageCopy.end(); ++i)
{
// Check if aura was removed during iteration - we don't need to work on such auras
if (!((*i)->GetBase()->IsAppliedOnTarget(victim->GetGUID())))
continue;
// check damage school mask
if (((*i)->GetMiscValue() & damageSchoolMask) == 0)
continue;
Unit* shareDamageTarget = (*i)->GetCaster();
if (!shareDamageTarget)
continue;
SpellInfo const* spell = (*i)->GetSpellInfo();
uint32 shareDamage = CalculatePct(damage, (*i)->GetAmount());
uint32 shareAbsorb = 0;
uint32 shareResist = 0;
if (shareDamageTarget->IsImmunedToDamageOrSchool(damageSchoolMask))
{
shareAbsorb = shareDamage;
shareDamage = 0;
}
else
{
Unit::DealDamageMods(shareDamageTarget, shareDamage, &shareAbsorb);
Unit::CalcAbsorbResist(attacker, shareDamageTarget, damageSchoolMask, damagetype, shareDamage, &shareAbsorb, &shareResist, spellProto, true);
shareDamage -= std::min(shareAbsorb, shareDamage);
}
if (attacker && shareDamageTarget->GetTypeId() == TYPEID_PLAYER)
attacker->SendSpellNonMeleeDamageLog(shareDamageTarget, spell->Id, shareDamage+shareAbsorb+shareResist, damageSchoolMask, shareAbsorb, shareResist, damagetype == DIRECT_DAMAGE, 0, false);
Unit::DealDamage(attacker, shareDamageTarget, shareDamage, cleanDamage, NODAMAGE, damageSchoolMask, spellProto, false);
}
}
// Rage from Damage made (only from direct weapon damage)
if (attacker && cleanDamage && damagetype == DIRECT_DAMAGE && attacker != victim && attacker->getPowerType() == POWER_RAGE)
{
uint32 weaponSpeedHitFactor;
switch (cleanDamage->attackType)
{
case BASE_ATTACK:
case OFF_ATTACK:
{
weaponSpeedHitFactor = uint32(attacker->GetAttackTime(cleanDamage->attackType) / 1000.0f * (cleanDamage->attackType == BASE_ATTACK ? 3.5f : 1.75f));
if (cleanDamage->hitOutCome == MELEE_HIT_CRIT)
weaponSpeedHitFactor *= 2;
attacker->RewardRage(rage_damage, weaponSpeedHitFactor, true);
break;
}
case RANGED_ATTACK:
break;
default:
break;
}
}
if (!damage)
{
// Rage from absorbed damage
if (cleanDamage && cleanDamage->absorbed_damage)
{
if (victim->getPowerType() == POWER_RAGE)
victim->RewardRage(cleanDamage->absorbed_damage, 0, false);
if (attacker && attacker->getPowerType() == POWER_RAGE )
attacker->RewardRage(cleanDamage->absorbed_damage, 0, true);
}
return 0;
}
;//sLog->outStaticDebug("DealDamageStart");
uint32 health = victim->GetHealth();
;//sLog->outDetail("deal dmg:%d to health:%d ", damage, health);
// duel ends when player has 1 or less hp
bool duel_hasEnded = false;
bool duel_wasMounted = false;
if (victim->GetTypeId() == TYPEID_PLAYER && victim->ToPlayer()->duel && damage >= (health-1))
{
// xinef: situation not possible earlier, just return silently.
if (!attacker)
return 0;
// prevent kill only if killed in duel and killed by opponent or opponent controlled creature
if (victim->ToPlayer()->duel->opponent == attacker || victim->ToPlayer()->duel->opponent->GetGUID() == attacker->GetOwnerGUID())
damage = health - 1;
duel_hasEnded = true;
}
else if (victim->IsVehicle() && damage >= (health-1) && victim->GetCharmer() && victim->GetCharmer()->GetTypeId() == TYPEID_PLAYER)
{
Player* victimRider = victim->GetCharmer()->ToPlayer();
if (victimRider && victimRider->duel && victimRider->duel->isMounted)
{
// xinef: situation not possible earlier, just return silently.
if (!attacker)
return 0;
// prevent kill only if killed in duel and killed by opponent or opponent controlled creature
if (victimRider->duel->opponent == attacker || victimRider->duel->opponent->GetGUID() == attacker->GetCharmerGUID())
damage = health - 1;
duel_wasMounted = true;
duel_hasEnded = true;
}
}
if (attacker && attacker != victim)
if (Player* killer = attacker->GetCharmerOrOwnerPlayerOrPlayerItself())
{
// pussywizard: don't allow GMs to deal damage in normal way (this leaves no evidence in logs!), they have commands to do so
//if (!allowGM && killer->GetSession()->GetSecurity() && killer->GetSession()->GetSecurity() <= SEC_ADMINISTRATOR)
// return 0;
if (Battleground* bg = killer->GetBattleground())
{
bg->UpdatePlayerScore(killer, SCORE_DAMAGE_DONE, damage);
killer->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_DAMAGE_DONE, damage, 0, victim); // pussywizard: InBattleground() optimization
}
//killer->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HIGHEST_HIT_DEALT, damage); // pussywizard: optimization
}
if (victim->GetTypeId() == TYPEID_PLAYER)
;//victim->ToPlayer()->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_HIGHEST_HIT_RECEIVED, damage); // pussywizard: optimization
else if (!victim->IsControlledByPlayer() || victim->IsVehicle())
{
if (!victim->ToCreature()->hasLootRecipient())
victim->ToCreature()->SetLootRecipient(attacker);
if (!attacker || attacker->IsControlledByPlayer() || attacker->IsCreatedByPlayer())
victim->ToCreature()->LowerPlayerDamageReq(health < damage ? health : damage);
}
if (health <= damage)
{
;//sLog->outStaticDebug("DealDamage: victim just died");
//if (attacker && victim->GetTypeId() == TYPEID_PLAYER && victim != attacker)
//victim->ToPlayer()->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_TOTAL_DAMAGE_RECEIVED, health); // pussywizard: optimization
Unit::Kill(attacker, victim, durabilityLoss, cleanDamage ? cleanDamage->attackType : BASE_ATTACK, spellProto);
}
else
{
;//sLog->outStaticDebug("DealDamageAlive");
//if (victim->GetTypeId() == TYPEID_PLAYER)
// victim->ToPlayer()->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_TOTAL_DAMAGE_RECEIVED, damage); // pussywizard: optimization
victim->ModifyHealth(- (int32)damage);
if (damagetype == DIRECT_DAMAGE || damagetype == SPELL_DIRECT_DAMAGE)
victim->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_DIRECT_DAMAGE, spellProto ? spellProto->Id : 0);
if (victim->GetTypeId() != TYPEID_PLAYER)
{
// Part of Evade mechanics. DoT's and Thorns / Retribution Aura do not contribute to this
if (damagetype != DOT && damage > 0 && !IS_PLAYER_GUID(victim->GetOwnerGUID()) && (!spellProto || !spellProto->HasAura(SPELL_AURA_DAMAGE_SHIELD)))
victim->ToCreature()->SetLastDamagedTime(sWorld->GetGameTime()+MAX_AGGRO_RESET_TIME);
if (attacker)
victim->AddThreat(attacker, float(damage), damageSchoolMask, spellProto);
}
else // victim is a player
{
// random durability for items (HIT TAKEN)
if (roll_chance_f(sWorld->getRate(RATE_DURABILITY_LOSS_DAMAGE)))
{
EquipmentSlots slot = EquipmentSlots(urand(0, EQUIPMENT_SLOT_END-1));
victim->ToPlayer()->DurabilityPointLossForEquipSlot(slot);
}
}
// Rage from damage received
if (attacker != victim && victim->getPowerType() == POWER_RAGE)
{
uint32 rage_damage = damage + (cleanDamage ? cleanDamage->absorbed_damage : 0);
victim->RewardRage(rage_damage, 0, false);
}
if (attacker && attacker->GetTypeId() == TYPEID_PLAYER)
{
// random durability for items (HIT DONE)
if (roll_chance_f(sWorld->getRate(RATE_DURABILITY_LOSS_DAMAGE)))
{
EquipmentSlots slot = EquipmentSlots(urand(0, EQUIPMENT_SLOT_END-1));
attacker->ToPlayer()->DurabilityPointLossForEquipSlot(slot);
}
}
if (damagetype != NODAMAGE && damage && (!spellProto || !(spellProto->HasAttribute(SPELL_ATTR3_NO_PUSHBACK) || spellProto->HasAttribute(SPELL_ATTR7_NO_PUSHBACK_ON_DAMAGE))))
{
if (victim != attacker && victim->GetTypeId() == TYPEID_PLAYER) // does not support creature push_back
{
if (damagetype != DOT)
if (Spell* spell = victim->m_currentSpells[CURRENT_GENERIC_SPELL])
if (spell->getState() == SPELL_STATE_PREPARING)
{
uint32 interruptFlags = spell->m_spellInfo->InterruptFlags;
if (interruptFlags & SPELL_INTERRUPT_FLAG_ABORT_ON_DMG)
victim->InterruptNonMeleeSpells(false);
else if (interruptFlags & SPELL_INTERRUPT_FLAG_PUSH_BACK)
spell->Delayed();
}
if (Spell* spell = victim->m_currentSpells[CURRENT_CHANNELED_SPELL])
if (spell->getState() == SPELL_STATE_CASTING)
{
uint32 channelInterruptFlags = spell->m_spellInfo->ChannelInterruptFlags;
if (((channelInterruptFlags & CHANNEL_FLAG_DELAY) != 0) && (damagetype != DOT))
spell->DelayedChannel();
}
}
}
// last damage from duel opponent
if (duel_hasEnded)
{
Player* he = duel_wasMounted ? victim->GetCharmer()->ToPlayer() : victim->ToPlayer();
ASSERT(he && he->duel);
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(DUEL_WON);
}
}
;//sLog->outStaticDebug("DealDamageEnd returned %d damage", damage);
return damage;
}
void Unit::CastStop(uint32 except_spellid, bool withInstant)
{
for (uint32 i = CURRENT_FIRST_NON_MELEE_SPELL; i < CURRENT_MAX_SPELL; i++)
if (m_currentSpells[i] && m_currentSpells[i]->m_spellInfo->Id != except_spellid)
InterruptSpell(CurrentSpellTypes(i),false, withInstant);
}
void Unit::CastSpell(SpellCastTargets const& targets, SpellInfo const* spellInfo, CustomSpellValues const* value, TriggerCastFlags triggerFlags, Item* castItem, AuraEffect const* triggeredByAura, uint64 originalCaster)
{
if (!spellInfo)
{
sLog->outError("CastSpell: unknown spell by caster: %s %u)", (GetTypeId() == TYPEID_PLAYER ? "player (GUID:" : "creature (Entry:"), (GetTypeId() == TYPEID_PLAYER ? GetGUIDLow() : GetEntry()));
return;
}
// TODO: this is a workaround - not needed anymore, but required for some scripts :(
if (!originalCaster && triggeredByAura)
originalCaster = triggeredByAura->GetCasterGUID();
Spell* spell = new Spell(this, spellInfo, triggerFlags, originalCaster);
if (value)
for (CustomSpellValues::const_iterator itr = value->begin(); itr != value->end(); ++itr)
spell->SetSpellValue(itr->first, itr->second);
spell->m_CastItem = castItem;
spell->prepare(&targets, triggeredByAura);
}
void Unit::CastSpell(Unit* victim, uint32 spellId, bool triggered, Item* castItem, AuraEffect const* triggeredByAura, uint64 originalCaster)
{
CastSpell(victim, spellId, triggered ? TRIGGERED_FULL_MASK : TRIGGERED_NONE, castItem, triggeredByAura, originalCaster);
}
void Unit::CastSpell(Unit* victim, uint32 spellId, TriggerCastFlags triggerFlags /*= TRIGGER_NONE*/, Item* castItem /*= NULL*/, AuraEffect const* triggeredByAura /*= NULL*/, uint64 originalCaster /*= 0*/)
{
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
if (!spellInfo)
{
sLog->outError("CastSpell: unknown spell id %u by caster: %s %u)", spellId, (GetTypeId() == TYPEID_PLAYER ? "player (GUID:" : "creature (Entry:"), (GetTypeId() == TYPEID_PLAYER ? GetGUIDLow() : GetEntry()));
return;
}
CastSpell(victim, spellInfo, triggerFlags, castItem, triggeredByAura, originalCaster);
}
void Unit::CastSpell(Unit* victim, SpellInfo const* spellInfo, bool triggered, Item* castItem/*= NULL*/, AuraEffect const* triggeredByAura /*= NULL*/, uint64 originalCaster /*= 0*/)
{
CastSpell(victim, spellInfo, triggered ? TRIGGERED_FULL_MASK : TRIGGERED_NONE, castItem, triggeredByAura, originalCaster);
}
void Unit::CastSpell(Unit* victim, SpellInfo const* spellInfo, TriggerCastFlags triggerFlags, Item* castItem, AuraEffect const* triggeredByAura, uint64 originalCaster)
{
SpellCastTargets targets;
targets.SetUnitTarget(victim);
CastSpell(targets, spellInfo, NULL, triggerFlags, castItem, triggeredByAura, originalCaster);
}
void Unit::CastCustomSpell(Unit* target, uint32 spellId, int32 const* bp0, int32 const* bp1, int32 const* bp2, bool triggered, Item* castItem, AuraEffect const* triggeredByAura, uint64 originalCaster)
{
CustomSpellValues values;
if (bp0)
values.AddSpellMod(SPELLVALUE_BASE_POINT0, *bp0);
if (bp1)
values.AddSpellMod(SPELLVALUE_BASE_POINT1, *bp1);
if (bp2)
values.AddSpellMod(SPELLVALUE_BASE_POINT2, *bp2);
CastCustomSpell(spellId, values, target, triggered ? TRIGGERED_FULL_MASK : TRIGGERED_NONE, castItem, triggeredByAura, originalCaster);
}
void Unit::CastCustomSpell(uint32 spellId, SpellValueMod mod, int32 value, Unit* target, bool triggered, Item* castItem, AuraEffect const* triggeredByAura, uint64 originalCaster)
{
CustomSpellValues values;
values.AddSpellMod(mod, value);
CastCustomSpell(spellId, values, target, triggered ? TRIGGERED_FULL_MASK : TRIGGERED_NONE, castItem, triggeredByAura, originalCaster);
}
void Unit::CastCustomSpell(uint32 spellId, SpellValueMod mod, int32 value, Unit* target, TriggerCastFlags triggerFlags, Item* castItem, AuraEffect const* triggeredByAura, uint64 originalCaster)
{
CustomSpellValues values;
values.AddSpellMod(mod, value);
CastCustomSpell(spellId, values, target, triggerFlags, castItem, triggeredByAura, originalCaster);
}
void Unit::CastCustomSpell(uint32 spellId, CustomSpellValues const& value, Unit* victim, TriggerCastFlags triggerFlags, Item* castItem, AuraEffect const* triggeredByAura, uint64 originalCaster)
{
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
if (!spellInfo)
{
sLog->outError("CastSpell: unknown spell id %u by caster: %s %u)", spellId, (GetTypeId() == TYPEID_PLAYER ? "player (GUID:" : "creature (Entry:"), (GetTypeId() == TYPEID_PLAYER ? GetGUIDLow() : GetEntry()));
return;
}
SpellCastTargets targets;
targets.SetUnitTarget(victim);
CastSpell(targets, spellInfo, &value, triggerFlags, castItem, triggeredByAura, originalCaster);
}
void Unit::CastSpell(float x, float y, float z, uint32 spellId, bool triggered, Item* castItem, AuraEffect const* triggeredByAura, uint64 originalCaster)
{
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
if (!spellInfo)
{
sLog->outError("CastSpell: unknown spell id %u by caster: %s %u)", spellId, (GetTypeId() == TYPEID_PLAYER ? "player (GUID:" : "creature (Entry:"), (GetTypeId() == TYPEID_PLAYER ? GetGUIDLow() : GetEntry()));
return;
}
SpellCastTargets targets;
targets.SetDst(x, y, z, GetOrientation());
CastSpell(targets, spellInfo, NULL, triggered ? TRIGGERED_FULL_MASK : TRIGGERED_NONE, castItem, triggeredByAura, originalCaster);
}
void Unit::CastSpell(GameObject* go, uint32 spellId, bool triggered, Item* castItem, AuraEffect* triggeredByAura, uint64 originalCaster)
{
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
if (!spellInfo)
{
sLog->outError("CastSpell: unknown spell id %u by caster: %s %u)", spellId, (GetTypeId() == TYPEID_PLAYER ? "player (GUID:" : "creature (Entry:"), (GetTypeId() == TYPEID_PLAYER ? GetGUIDLow() : GetEntry()));
return;
}
SpellCastTargets targets;
targets.SetGOTarget(go);
CastSpell(targets, spellInfo, NULL, triggered ? TRIGGERED_FULL_MASK : TRIGGERED_NONE, castItem, triggeredByAura, originalCaster);
}
void Unit::CalculateSpellDamageTaken(SpellNonMeleeDamage* damageInfo, int32 damage, SpellInfo const* spellInfo, WeaponAttackType attackType, bool crit)
{
if (damage < 0)
return;
Unit* victim = damageInfo->target;
if (!victim || !victim->IsAlive())
return;
SpellSchoolMask damageSchoolMask = SpellSchoolMask(damageInfo->schoolMask);
uint32 crTypeMask = victim->GetCreatureTypeMask();
if (Unit::IsDamageReducedByArmor(damageSchoolMask, spellInfo))
damage = Unit::CalcArmorReducedDamage(this, victim, damage, spellInfo, 0, attackType);
bool blocked = false;
// Per-school calc
switch (spellInfo->DmgClass)
{
// Melee and Ranged Spells
case SPELL_DAMAGE_CLASS_RANGED:
case SPELL_DAMAGE_CLASS_MELEE:
{
// Physical Damage
if (damageSchoolMask & SPELL_SCHOOL_MASK_NORMAL)
{
// Get blocked status
blocked = isSpellBlocked(victim, spellInfo, attackType);
}
if (crit)
{
damageInfo->HitInfo |= SPELL_HIT_TYPE_CRIT;
// Calculate crit bonus
uint32 crit_bonus = damage;
// Apply crit_damage bonus for melee spells
if (Player* modOwner = GetSpellModOwner())
modOwner->ApplySpellMod(spellInfo->Id, SPELLMOD_CRIT_DAMAGE_BONUS, crit_bonus);
damage += crit_bonus;
// Apply SPELL_AURA_MOD_ATTACKER_RANGED_CRIT_DAMAGE or SPELL_AURA_MOD_ATTACKER_MELEE_CRIT_DAMAGE
float critPctDamageMod = 0.0f;
if (attackType == RANGED_ATTACK)
critPctDamageMod += victim->GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_RANGED_CRIT_DAMAGE);
else
critPctDamageMod += victim->GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_MELEE_CRIT_DAMAGE);
// Increase crit damage from SPELL_AURA_MOD_CRIT_DAMAGE_BONUS
critPctDamageMod += GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_CRIT_DAMAGE_BONUS, spellInfo->GetSchoolMask());
// Increase crit damage from SPELL_AURA_MOD_CRIT_PERCENT_VERSUS
critPctDamageMod += GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_CRIT_PERCENT_VERSUS, crTypeMask);
if (critPctDamageMod != 0)
AddPct(damage, critPctDamageMod);
}
// Spell weapon based damage CAN BE crit & blocked at same time
if (blocked)
{
damageInfo->blocked = victim->GetShieldBlockValue();
// double blocked amount if block is critical
if (victim->isBlockCritical())
damageInfo->blocked += damageInfo->blocked;
if (damage < int32(damageInfo->blocked))
damageInfo->blocked = uint32(damage);
damage -= damageInfo->blocked;
}
if (CanApplyResilience())
{
if (attackType != RANGED_ATTACK)
Unit::ApplyResilience(victim, NULL, &damage, crit, CR_CRIT_TAKEN_MELEE);
else
Unit::ApplyResilience(victim, NULL, &damage, crit, CR_CRIT_TAKEN_RANGED);
}
break;
}
// Magical Attacks
case SPELL_DAMAGE_CLASS_NONE:
case SPELL_DAMAGE_CLASS_MAGIC:
{
// If crit add critical bonus
if (crit)
{
damageInfo->HitInfo |= SPELL_HIT_TYPE_CRIT;
damage = Unit::SpellCriticalDamageBonus(this, spellInfo, damage, victim);
}
if (CanApplyResilience())
Unit::ApplyResilience(victim, NULL, &damage, crit, CR_CRIT_TAKEN_SPELL);
break;
}
default:
break;
}
// Calculate absorb resist
if (damage > 0)
{
Unit::CalcAbsorbResist(this, victim, damageSchoolMask, SPELL_DIRECT_DAMAGE, damage, &damageInfo->absorb, &damageInfo->resist, spellInfo);
damage -= damageInfo->absorb + damageInfo->resist;
}
else
damage = 0;
damageInfo->damage = std::max(damage, 0);
}
void Unit::DealSpellDamage(SpellNonMeleeDamage* damageInfo, bool durabilityLoss)
{
if (damageInfo == 0)
return;
Unit* victim = damageInfo->target;
if (!victim)
return;
if (!victim->IsAlive() || victim->IsInFlight() || (victim->GetTypeId() == TYPEID_UNIT && victim->ToCreature()->IsInEvadeMode()))
return;
SpellInfo const* spellProto = sSpellMgr->GetSpellInfo(damageInfo->SpellID);
if (spellProto == NULL)
{
;//sLog->outDebug(LOG_FILTER_UNITS, "Unit::DealSpellDamage has wrong damageInfo->SpellID: %u", damageInfo->SpellID);
return;
}
// Call default DealDamage
CleanDamage cleanDamage(damageInfo->cleanDamage, damageInfo->absorb, BASE_ATTACK, MELEE_HIT_NORMAL);
Unit::DealDamage(this, victim, damageInfo->damage, &cleanDamage, SPELL_DIRECT_DAMAGE, SpellSchoolMask(damageInfo->schoolMask), spellProto, durabilityLoss);
}
// TODO for melee need create structure as in
void Unit::CalculateMeleeDamage(Unit* victim, uint32 damage, CalcDamageInfo* damageInfo, WeaponAttackType attackType)
{
damageInfo->attacker = this;
damageInfo->target = victim;
damageInfo->damageSchoolMask = GetMeleeDamageSchoolMask();
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 = PROC_FLAG_NONE;
damageInfo->procVictim = PROC_FLAG_NONE;
damageInfo->procEx = PROC_EX_NONE;
damageInfo->hitOutCome = MELEE_HIT_EVADE;
if (!victim)
return;
if (!IsAlive() || !victim->IsAlive())
return;
// Select HitInfo/procAttacker/procVictim flag based on attack type
switch (attackType)
{
case BASE_ATTACK:
damageInfo->procAttacker = PROC_FLAG_DONE_MELEE_AUTO_ATTACK | PROC_FLAG_DONE_MAINHAND_ATTACK;
damageInfo->procVictim = PROC_FLAG_TAKEN_MELEE_AUTO_ATTACK;
break;
case OFF_ATTACK:
damageInfo->procAttacker = PROC_FLAG_DONE_MELEE_AUTO_ATTACK | PROC_FLAG_DONE_OFFHAND_ATTACK;
damageInfo->procVictim = PROC_FLAG_TAKEN_MELEE_AUTO_ATTACK;
damageInfo->HitInfo = HITINFO_OFFHAND;
break;
default:
return;
}
// Physical Immune check
if (damageInfo->target->IsImmunedToDamageOrSchool(SpellSchoolMask(damageInfo->damageSchoolMask)))
{
damageInfo->HitInfo |= HITINFO_NORMALSWING;
damageInfo->TargetState = VICTIMSTATE_IS_IMMUNE;
damageInfo->procEx |= PROC_EX_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);
// Calculate armor reduction
if (IsDamageReducedByArmor((SpellSchoolMask)(damageInfo->damageSchoolMask)))
{
damageInfo->damage = Unit::CalcArmorReducedDamage(this, damageInfo->target, damage, NULL, 0, damageInfo->attackType);
damageInfo->cleanDamage += damage - damageInfo->damage;
}
else
damageInfo->damage = damage;
damageInfo->hitOutCome = RollMeleeOutcomeAgainst(damageInfo->target, damageInfo->attackType);
switch (damageInfo->hitOutCome)
{
case MELEE_HIT_EVADE:
damageInfo->HitInfo |= HITINFO_MISS | HITINFO_SWINGNOHITSOUND;
damageInfo->TargetState = VICTIMSTATE_EVADES;
damageInfo->procEx |= PROC_EX_EVADE;
damageInfo->damage = 0;
damageInfo->cleanDamage = 0;
return;
case MELEE_HIT_MISS:
damageInfo->HitInfo |= HITINFO_MISS;
damageInfo->TargetState = VICTIMSTATE_INTACT;
damageInfo->procEx |= PROC_EX_MISS;
damageInfo->damage = 0;
damageInfo->cleanDamage = 0;
break;
case MELEE_HIT_NORMAL:
damageInfo->TargetState = VICTIMSTATE_HIT;
damageInfo->procEx |= PROC_EX_NORMAL_HIT;
break;
case MELEE_HIT_CRIT:
{
damageInfo->HitInfo |= HITINFO_CRITICALHIT;
damageInfo->TargetState = VICTIMSTATE_HIT;
damageInfo->procEx |= PROC_EX_CRITICAL_HIT;
// Crit bonus calc
damageInfo->damage += damageInfo->damage;
float mod = 0.0f;
// Apply SPELL_AURA_MOD_ATTACKER_RANGED_CRIT_DAMAGE or SPELL_AURA_MOD_ATTACKER_MELEE_CRIT_DAMAGE
if (damageInfo->attackType == RANGED_ATTACK)
mod += damageInfo->target->GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_RANGED_CRIT_DAMAGE);
else
{
mod += damageInfo->target->GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_MELEE_CRIT_DAMAGE);
// Increase crit damage from SPELL_AURA_MOD_CRIT_DAMAGE_BONUS
mod += (GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_CRIT_DAMAGE_BONUS, damageInfo->damageSchoolMask) - 1.0f) * 100;
}
uint32 crTypeMask = damageInfo->target->GetCreatureTypeMask();
// Increase crit damage from SPELL_AURA_MOD_CRIT_PERCENT_VERSUS
mod += GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_CRIT_PERCENT_VERSUS, crTypeMask);
if (mod != 0)
AddPct(damageInfo->damage, mod);
break;
}
case MELEE_HIT_PARRY:
damageInfo->TargetState = VICTIMSTATE_PARRY;
damageInfo->procEx |= PROC_EX_PARRY;
damageInfo->cleanDamage += damageInfo->damage;
damageInfo->damage = 0;
break;
case MELEE_HIT_DODGE:
damageInfo->TargetState = VICTIMSTATE_DODGE;
damageInfo->procEx |= PROC_EX_DODGE;
damageInfo->cleanDamage += damageInfo->damage;
damageInfo->damage = 0;
break;
case MELEE_HIT_BLOCK:
damageInfo->TargetState = VICTIMSTATE_HIT;
damageInfo->HitInfo |= HITINFO_BLOCK;
damageInfo->procEx |= PROC_EX_BLOCK;
damageInfo->blocked_amount = damageInfo->target->GetShieldBlockValue();
// double blocked amount if block is critical
if (damageInfo->target->isBlockCritical())
damageInfo->blocked_amount+=damageInfo->blocked_amount;
if (damageInfo->blocked_amount >= damageInfo->damage)
{
damageInfo->TargetState = VICTIMSTATE_BLOCKS;
damageInfo->blocked_amount = damageInfo->damage;
damageInfo->procEx |= PROC_EX_FULL_BLOCK;
}
else
damageInfo->procEx |= PROC_EX_NORMAL_HIT;
damageInfo->damage -= damageInfo->blocked_amount;
damageInfo->cleanDamage += damageInfo->blocked_amount;
break;
case MELEE_HIT_GLANCING:
{
damageInfo->HitInfo |= HITINFO_GLANCING;
damageInfo->TargetState = VICTIMSTATE_HIT;
damageInfo->procEx |= PROC_EX_NORMAL_HIT;
int32 leveldif = int32(victim->getLevel()) - int32(getLevel());
if (leveldif > 3)
leveldif = 3;
float reducePercent = 1 - leveldif * 0.1f;
damageInfo->cleanDamage += damageInfo->damage - uint32(reducePercent * damageInfo->damage);
damageInfo->damage = uint32(reducePercent * damageInfo->damage);
break;
}
case MELEE_HIT_CRUSHING:
damageInfo->HitInfo |= HITINFO_CRUSHING;
damageInfo->TargetState = VICTIMSTATE_HIT;
damageInfo->procEx |= PROC_EX_NORMAL_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 & HITINFO_MISS))
damageInfo->HitInfo |= HITINFO_AFFECTS_VICTIM;
int32 resilienceReduction = damageInfo->damage;
// attackType is checked already for BASE_ATTACK or OFF_ATTACK so it can't be RANGED_ATTACK here
if (CanApplyResilience())
Unit::ApplyResilience(victim, NULL, &resilienceReduction, (damageInfo->hitOutCome == MELEE_HIT_CRIT), CR_CRIT_TAKEN_MELEE);
resilienceReduction = damageInfo->damage - resilienceReduction;
damageInfo->damage -= resilienceReduction;
damageInfo->cleanDamage += resilienceReduction;
// Calculate absorb resist
if (int32(damageInfo->damage) > 0)
{
damageInfo->procVictim |= PROC_FLAG_TAKEN_DAMAGE;
// Calculate absorb & resists
Unit::CalcAbsorbResist(this, damageInfo->target, SpellSchoolMask(damageInfo->damageSchoolMask), DIRECT_DAMAGE, damageInfo->damage, &damageInfo->absorb, &damageInfo->resist);
if (damageInfo->absorb)
{
damageInfo->HitInfo |= (damageInfo->damage - damageInfo->absorb == 0 ? HITINFO_FULL_ABSORB : HITINFO_PARTIAL_ABSORB);
damageInfo->procEx |= PROC_EX_ABSORB;
}
if (damageInfo->resist)
damageInfo->HitInfo |= (damageInfo->damage - damageInfo->resist == 0 ? HITINFO_FULL_RESIST : HITINFO_PARTIAL_RESIST);
damageInfo->damage -= damageInfo->absorb + damageInfo->resist;
}
else // Impossible get negative result but....
damageInfo->damage = 0;
}
void Unit::DealMeleeDamage(CalcDamageInfo* damageInfo, bool durabilityLoss)
{
Unit* victim = damageInfo->target;
if (!victim->IsAlive() || victim->IsInFlight() || (victim->GetTypeId() == TYPEID_UNIT && victim->ToCreature()->IsInEvadeMode()))
return;
// Hmmmm dont like this emotes client must by self do all animations
if (damageInfo->HitInfo & HITINFO_CRITICALHIT)
victim->HandleEmoteCommand(EMOTE_ONESHOT_WOUND_CRITICAL);
if (damageInfo->blocked_amount && damageInfo->TargetState != VICTIMSTATE_BLOCKS)
victim->HandleEmoteCommand(EMOTE_ONESHOT_PARRY_SHIELD);
if (damageInfo->TargetState == VICTIMSTATE_PARRY)
{
// Get attack timers
float offtime = float(victim->getAttackTimer(OFF_ATTACK));
float basetime = float(victim->getAttackTimer(BASE_ATTACK));
// Reduce attack time
if (victim->haveOffhandWeapon() && offtime < basetime)
{
float percent20 = victim->GetAttackTime(OFF_ATTACK) * 0.20f;
float percent60 = 3.0f * percent20;
if (offtime > percent20 && offtime <= percent60)
victim->setAttackTimer(OFF_ATTACK, uint32(percent20));
else if (offtime > percent60)
{
offtime -= 2.0f * percent20;
victim->setAttackTimer(OFF_ATTACK, uint32(offtime));
}
}
else
{
float percent20 = victim->GetAttackTime(BASE_ATTACK) * 0.20f;
float percent60 = 3.0f * percent20;
if (basetime > percent20 && basetime <= percent60)
victim->setAttackTimer(BASE_ATTACK, uint32(percent20));
else if (basetime > percent60)
{
basetime -= 2.0f * percent20;
victim->setAttackTimer(BASE_ATTACK, uint32(basetime));
}
}
}
// Call default DealDamage
CleanDamage cleanDamage(damageInfo->cleanDamage, damageInfo->absorb, damageInfo->attackType, damageInfo->hitOutCome);
Unit::DealDamage(this, victim, damageInfo->damage, &cleanDamage, DIRECT_DAMAGE, 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->damage && ((damageInfo->hitOutCome == MELEE_HIT_CRIT || damageInfo->hitOutCome == MELEE_HIT_CRUSHING || damageInfo->hitOutCome == MELEE_HIT_NORMAL || damageInfo->hitOutCome == MELEE_HIT_GLANCING) &&
GetTypeId() != TYPEID_PLAYER && !ToCreature()->IsControlledByPlayer() && !victim->HasInArc(M_PI, this)
&& (victim->GetTypeId() == 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->getLevel() + 0.5f;
uint32 VictimDefense=victim->GetDefenseSkillValue();
uint32 AttackerMeleeSkill=GetUnitMeleeSkill();
// xinef: fix daze mechanics
Probability -= ((float)VictimDefense - AttackerMeleeSkill) * 0.1428f;
if (Probability > 40.0f)
Probability = 40.0f;
if (roll_chance_f(std::max(0.0f, Probability)))
CastSpell(victim, 1604, true);
}
if (GetTypeId() == TYPEID_PLAYER)
ToPlayer()->CastItemCombatSpell(victim, damageInfo->attackType, damageInfo->procVictim, damageInfo->procEx);
// Do effect if any damage done to target
if (damageInfo->damage)
{
// 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
AuraEffectList vDamageShieldsCopy(victim->GetAuraEffectsByType(SPELL_AURA_DAMAGE_SHIELD));
for (AuraEffectList::const_iterator dmgShieldItr = vDamageShieldsCopy.begin(); dmgShieldItr != vDamageShieldsCopy.end(); ++dmgShieldItr)
{
SpellInfo const* i_spellProto = (*dmgShieldItr)->GetSpellInfo();
// Damage shield can be resisted...
if (SpellMissInfo missInfo = victim->SpellHitResult(this, i_spellProto, false))
{
victim->SendSpellMiss(this, i_spellProto->Id, missInfo);
continue;
}
// ...or immuned
if (IsImmunedToDamageOrSchool(i_spellProto))
{
victim->SendSpellDamageImmune(this, i_spellProto->Id);
continue;
}
uint32 damage = uint32(std::max(0, (*dmgShieldItr)->GetAmount())); // xinef: done calculated at amount calculation
if (Unit* caster = (*dmgShieldItr)->GetCaster())
damage = this->SpellDamageBonusTaken(caster, i_spellProto, damage, SPELL_DIRECT_DAMAGE);
// No Unit::CalcAbsorbResist here - opcode doesn't send that data - this damage is probably not affected by that
Unit::DealDamageMods(this, damage, NULL);
// TODO: Move this to a packet handler
WorldPacket data(SMSG_SPELLDAMAGESHIELD, (8+8+4+4+4+4));
data << uint64(victim->GetGUID());
data << uint64(GetGUID());
data << uint32(i_spellProto->Id);
data << uint32(damage); // Damage
int32 overkill = int32(damage) - int32(GetHealth());
data << uint32(overkill > 0 ? overkill : 0); // Overkill
data << uint32(i_spellProto->SchoolMask);
victim->SendMessageToSet(&data, true);
Unit::DealDamage(victim, this, damage, 0, SPELL_DIRECT_DAMAGE, i_spellProto->GetSchoolMask(), i_spellProto, true);
}
}
}
void Unit::HandleEmoteCommand(uint32 anim_id)
{
WorldPacket data(SMSG_EMOTE, 4 + 8);
data << uint32(anim_id);
data << uint64(GetGUID());
SendMessageToSet(&data, true);
}
bool Unit::IsDamageReducedByArmor(SpellSchoolMask schoolMask, SpellInfo const* spellInfo, uint8 effIndex)
{
// only physical spells damage gets reduced by armor
if ((schoolMask & SPELL_SCHOOL_MASK_NORMAL) == 0)
return false;
if (spellInfo)
{
// there are spells with no specific attribute but they have "ignores armor" in tooltip
if (spellInfo->HasAttribute(SPELL_ATTR0_CU_IGNORE_ARMOR))
return false;
// bleeding effects are not reduced by armor
if (effIndex != MAX_SPELL_EFFECTS)
{
if (spellInfo->Effects[effIndex].ApplyAuraName == SPELL_AURA_PERIODIC_DAMAGE ||
spellInfo->Effects[effIndex].Effect == SPELL_EFFECT_SCHOOL_DAMAGE)
if (spellInfo->GetEffectMechanicMask(effIndex) & (1<GetArmor());
// Ignore enemy armor by SPELL_AURA_MOD_TARGET_RESISTANCE aura
if (attacker)
{
armor += attacker->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_TARGET_RESISTANCE, SPELL_SCHOOL_MASK_NORMAL);
if (spellInfo)
if (Player* modOwner = attacker->GetSpellModOwner())
modOwner->ApplySpellMod(spellInfo->Id, SPELLMOD_IGNORE_ARMOR, armor);
AuraEffectList const& ResIgnoreAurasAb = attacker->GetAuraEffectsByType(SPELL_AURA_MOD_ABILITY_IGNORE_TARGET_RESIST);
for (AuraEffectList::const_iterator j = ResIgnoreAurasAb.begin(); j != ResIgnoreAurasAb.end(); ++j)
{
if ((*j)->GetMiscValue() & SPELL_SCHOOL_MASK_NORMAL
&& (*j)->IsAffectedOnSpell(spellInfo))
armor = floor(AddPct(armor, -(*j)->GetAmount()));
}
AuraEffectList const& ResIgnoreAuras = attacker->GetAuraEffectsByType(SPELL_AURA_MOD_IGNORE_TARGET_RESIST);
for (AuraEffectList::const_iterator j = ResIgnoreAuras.begin(); j != ResIgnoreAuras.end(); ++j)
{
if ((*j)->GetMiscValue() & SPELL_SCHOOL_MASK_NORMAL)
armor = floor(AddPct(armor, -(*j)->GetAmount()));
}
// Apply Player CR_ARMOR_PENETRATION rating and buffs from stances\specializations etc.
if (attacker->GetTypeId() == TYPEID_PLAYER)
{
float bonusPct = 0;
AuraEffectList const& armorPenAuras = attacker->GetAuraEffectsByType(SPELL_AURA_MOD_ARMOR_PENETRATION_PCT);
for (AuraEffectList::const_iterator itr = armorPenAuras.begin(); itr != armorPenAuras.end(); ++itr)
{
if ((*itr)->GetSpellInfo()->EquippedItemClass == -1)
{
if (!spellInfo || (*itr)->IsAffectedOnSpell(spellInfo) || (*itr)->GetMiscValue() & spellInfo->GetSchoolMask())
bonusPct += (*itr)->GetAmount();
else if (!(*itr)->GetMiscValue() && !(*itr)->HasSpellClassMask())
bonusPct += (*itr)->GetAmount();
}
else
{
if (attacker->ToPlayer()->HasItemFitToSpellRequirements((*itr)->GetSpellInfo()))
bonusPct += (*itr)->GetAmount();
}
}
float maxArmorPen = 0;
if (victim->getLevel() < 60)
maxArmorPen = float(400 + 85 * victim->getLevel());
else
maxArmorPen = 400 + 85 * victim->getLevel() + 4.5f * 85 * (victim->getLevel() - 59);
// Cap armor penetration to this number
maxArmorPen = std::min((armor + maxArmorPen) / 3, armor);
// Figure out how much armor do we ignore
float armorPen = CalculatePct(maxArmorPen, bonusPct + attacker->ToPlayer()->GetRatingBonusValue(CR_ARMOR_PENETRATION));
// Got the value, apply it
armor -= std::min(armorPen, maxArmorPen);
}
}
if (armor < 0.0f)
armor = 0.0f;
float levelModifier = attacker ? attacker->getLevel() : attackerLevel;
if (levelModifier > 59)
levelModifier = levelModifier + (4.5f * (levelModifier - 59));
float tmpvalue = 0.1f * armor / (8.5f * levelModifier + 40);
tmpvalue = tmpvalue / (1.0f + tmpvalue);
if (tmpvalue < 0.0f)
tmpvalue = 0.0f;
if (tmpvalue > 0.75f)
tmpvalue = 0.75f;
newdamage = uint32(damage - (damage * tmpvalue));
return (newdamage > 1) ? newdamage : 1;
}
float Unit::GetEffectiveResistChance(Unit const* owner, SpellSchoolMask schoolMask, Unit const* victim, SpellInfo const* spellInfo)
{
float victimResistance = float(victim->GetResistance(schoolMask));
if (owner)
{
// Xinef: pets inherit 100% of masters penetration
// Xinef: excluding traps
Player const* player = owner->GetSpellModOwner();
if (player && owner->GetEntry() != WORLD_TRIGGER)
{
victimResistance += float(player->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_TARGET_RESISTANCE, schoolMask));
victimResistance -= float(player->GetSpellPenetrationItemMod());
}
else
victimResistance += float(owner->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_TARGET_RESISTANCE, schoolMask));
}
// Chaos Bolt exception, ignore all target resistances (unknown attribute?)
if (spellInfo && spellInfo->SpellFamilyName == SPELLFAMILY_WARLOCK && spellInfo->SpellIconID == 3178)
victimResistance = 0;
victimResistance = std::max(victimResistance, 0.0f);
if (owner)
victimResistance += std::max((float(victim->getLevel())-float(owner->getLevel()))*5.0f, 0.0f);
static uint32 const BOSS_LEVEL = 83;
static float const BOSS_RESISTANCE_CONSTANT = 510.0f;
uint32 level = victim->getLevel();
float resistanceConstant = 0.0f;
if (level == BOSS_LEVEL)
resistanceConstant = BOSS_RESISTANCE_CONSTANT;
else
resistanceConstant = level * 5.0f;
return victimResistance / (victimResistance + resistanceConstant);
}
void Unit::CalcAbsorbResist(Unit* attacker, Unit* victim, SpellSchoolMask schoolMask, DamageEffectType damagetype, const uint32 damage, uint32 *absorb, uint32 *resist, SpellInfo const *spellInfo, bool Splited)
{
if (!victim || !victim->IsAlive() || !damage)
return;
DamageInfo dmgInfo = DamageInfo(attacker, victim, damage, spellInfo, schoolMask, damagetype);
// Magic damage, check for resists
// Ignore spells that cant be resisted
// Xinef: holy resistance exists for npcs
if (!(schoolMask & SPELL_SCHOOL_MASK_NORMAL) && (!(schoolMask & SPELL_SCHOOL_MASK_HOLY) || victim->GetTypeId() == TYPEID_UNIT) && (!spellInfo || (!spellInfo->HasAttribute(SPELL_ATTR0_CU_BINARY_SPELL) && !spellInfo->HasAttribute(SPELL_ATTR4_IGNORE_RESISTANCES))))
{
float averageResist = Unit::GetEffectiveResistChance(attacker, schoolMask, victim, spellInfo);
float discreteResistProbability[11];
for (uint32 i = 0; i < 11; ++i)
{
discreteResistProbability[i] = 0.5f - 2.5f * fabs(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;
}
float r = float(rand_norm());
uint32 i = 0;
float probabilitySum = discreteResistProbability[0];
while (r >= probabilitySum && i < 10)
probabilitySum += discreteResistProbability[++i];
float damageResisted = float(damage * i / 10);
if (damageResisted) // if equal to 0, checking these is pointless
{
if (attacker)
{
AuraEffectList const& ResIgnoreAurasAb = attacker->GetAuraEffectsByType(SPELL_AURA_MOD_ABILITY_IGNORE_TARGET_RESIST);
for (AuraEffectList::const_iterator j = ResIgnoreAurasAb.begin(); j != ResIgnoreAurasAb.end(); ++j)
if (((*j)->GetMiscValue() & schoolMask) && (*j)->IsAffectedOnSpell(spellInfo))
AddPct(damageResisted, -(*j)->GetAmount());
AuraEffectList const& ResIgnoreAuras = attacker->GetAuraEffectsByType(SPELL_AURA_MOD_IGNORE_TARGET_RESIST);
for (AuraEffectList::const_iterator j = ResIgnoreAuras.begin(); j != ResIgnoreAuras.end(); ++j)
if ((*j)->GetMiscValue() & schoolMask)
AddPct(damageResisted, -(*j)->GetAmount());
}
// pussywizard:
if (spellInfo && spellInfo->HasAttribute(SPELL_ATTR0_CU_SCHOOLMASK_NORMAL_WITH_MAGIC))
{
uint32 damageAfterArmor = Unit::CalcArmorReducedDamage(attacker, victim, damage, spellInfo, 0, BASE_ATTACK);
uint32 armorReduction = damage - damageAfterArmor;
if (armorReduction < damageResisted) // pick the lower one, the weakest resistance counts
damageResisted = armorReduction;
}
}
dmgInfo.ResistDamage(uint32(damageResisted));
}
// Ignore Absorption Auras
float auraAbsorbMod = 0;
if (attacker)
{
AuraEffectList const& AbsIgnoreAurasA = attacker->GetAuraEffectsByType(SPELL_AURA_MOD_TARGET_ABSORB_SCHOOL);
for (AuraEffectList::const_iterator itr = AbsIgnoreAurasA.begin(); itr != AbsIgnoreAurasA.end(); ++itr)
{
if (!((*itr)->GetMiscValue() & schoolMask))
continue;
if ((*itr)->GetAmount() > auraAbsorbMod)
auraAbsorbMod = float((*itr)->GetAmount());
}
AuraEffectList const& AbsIgnoreAurasB = attacker->GetAuraEffectsByType(SPELL_AURA_MOD_TARGET_ABILITY_ABSORB_SCHOOL);
for (AuraEffectList::const_iterator itr = AbsIgnoreAurasB.begin(); itr != AbsIgnoreAurasB.end(); ++itr)
{
if (!((*itr)->GetMiscValue() & schoolMask))
continue;
if (((*itr)->GetAmount() > auraAbsorbMod) && (*itr)->IsAffectedOnSpell(spellInfo))
auraAbsorbMod = float((*itr)->GetAmount());
}
RoundToInterval(auraAbsorbMod, 0.0f, 100.0f);
}
// 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
AuraEffectList vSchoolAbsorbCopy(victim->GetAuraEffectsByType(SPELL_AURA_SCHOOL_ABSORB));
vSchoolAbsorbCopy.sort(Trinity::AbsorbAuraOrderPred());
// absorb without mana cost
for (AuraEffectList::iterator itr = vSchoolAbsorbCopy.begin(); (itr != vSchoolAbsorbCopy.end()) && (dmgInfo.GetDamage() > 0); ++itr)
{
AuraEffect* absorbAurEff = *itr;
// Check if aura was removed during iteration - we don't need to work on such auras
AuraApplication const* aurApp = absorbAurEff->GetBase()->GetApplicationOfTarget(victim->GetGUID());
if (!aurApp)
continue;
if (!(absorbAurEff->GetMiscValue() & schoolMask))
continue;
// get amount which can be still absorbed by the aura
int32 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;
uint32 tempAbsorb = uint32(currentAbsorb);
bool defaultPrevented = false;
absorbAurEff->GetBase()->CallScriptEffectAbsorbHandlers(absorbAurEff, aurApp, dmgInfo, tempAbsorb, defaultPrevented);
currentAbsorb = tempAbsorb;
if (defaultPrevented)
continue;
// absorb must be smaller than the damage itself
currentAbsorb = RoundToInterval(currentAbsorb, 0, int32(dmgInfo.GetDamage()));
// xinef: do this after absorb is rounded to damage...
AddPct(currentAbsorb, -auraAbsorbMod);
dmgInfo.AbsorbDamage(currentAbsorb);
tempAbsorb = currentAbsorb;
absorbAurEff->GetBase()->CallScriptEffectAfterAbsorbHandlers(absorbAurEff, aurApp, dmgInfo, tempAbsorb);
// Check if our aura is using amount to count damage
if (absorbAurEff->GetAmount() >= 0)
{
// Reduce shield amount
absorbAurEff->SetAmount(absorbAurEff->GetAmount() - currentAbsorb);
// Aura cannot absorb anything more - remove it
if (absorbAurEff->GetAmount() <= 0)
absorbAurEff->GetBase()->Remove(AURA_REMOVE_BY_ENEMY_SPELL);
}
}
// absorb by mana cost
AuraEffectList vManaShieldCopy(victim->GetAuraEffectsByType(SPELL_AURA_MANA_SHIELD));
for (AuraEffectList::const_iterator itr = vManaShieldCopy.begin(); (itr != vManaShieldCopy.end()) && (dmgInfo.GetDamage() > 0); ++itr)
{
AuraEffect* absorbAurEff = *itr;
// Check if aura was removed during iteration - we don't need to work on such auras
AuraApplication const* aurApp = absorbAurEff->GetBase()->GetApplicationOfTarget(victim->GetGUID());
if (!aurApp)
continue;
// check damage school mask
if (!(absorbAurEff->GetMiscValue() & schoolMask))
continue;
// get amount which can be still absorbed by the aura
int32 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;
uint32 tempAbsorb = currentAbsorb;
bool defaultPrevented = false;
absorbAurEff->GetBase()->CallScriptEffectManaShieldHandlers(absorbAurEff, aurApp, dmgInfo, tempAbsorb, defaultPrevented);
currentAbsorb = tempAbsorb;
if (defaultPrevented)
continue;
// absorb must be smaller than the damage itself
currentAbsorb = RoundToInterval(currentAbsorb, 0, int32(dmgInfo.GetDamage()));
// xinef: do this after absorb is rounded to damage...
AddPct(currentAbsorb, -auraAbsorbMod);
int32 manaReduction = currentAbsorb;
// lower absorb amount by talents
if (float manaMultiplier = absorbAurEff->GetSpellInfo()->Effects[absorbAurEff->GetEffIndex()].CalcValueMultiplier(absorbAurEff->GetCaster()))
manaReduction = int32(float(manaReduction) * manaMultiplier);
int32 manaTaken = -victim->ModifyPower(POWER_MANA, -manaReduction);
// take case when mana has ended up into account
currentAbsorb = currentAbsorb ? int32(float(currentAbsorb) * (float(manaTaken) / float(manaReduction))) : 0;
dmgInfo.AbsorbDamage(currentAbsorb);
tempAbsorb = currentAbsorb;
absorbAurEff->GetBase()->CallScriptEffectAfterManaShieldHandlers(absorbAurEff, aurApp, dmgInfo, tempAbsorb);
// Check if our aura is using amount to count damage
if (absorbAurEff->GetAmount() >= 0)
{
absorbAurEff->SetAmount(absorbAurEff->GetAmount() - currentAbsorb);
if ((absorbAurEff->GetAmount() <= 0))
absorbAurEff->GetBase()->Remove(AURA_REMOVE_BY_ENEMY_SPELL);
}
}
// split damage auras - only when not damaging self
// Xinef: not true - Warlock Hellfire
if (/*victim != attacker &&*/ !Splited)
{
// 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
AuraEffectList vSplitDamageFlatCopy(victim->GetAuraEffectsByType(SPELL_AURA_SPLIT_DAMAGE_FLAT));
for (AuraEffectList::iterator itr = vSplitDamageFlatCopy.begin(); (itr != vSplitDamageFlatCopy.end()) && (dmgInfo.GetDamage() > 0); ++itr)
{
// Check if aura was removed during iteration - we don't need to work on such auras
if (!((*itr)->GetBase()->IsAppliedOnTarget(victim->GetGUID())))
continue;
// check damage school mask
if (!((*itr)->GetMiscValue() & schoolMask))
continue;
// Damage can be splitted only if aura has an alive caster
Unit* caster = (*itr)->GetCaster();
if (!caster || (caster == victim) || !caster->IsInWorld() || !caster->IsAlive())
continue;
int32 splitDamage = (*itr)->GetAmount();
// absorb must be smaller than the damage itself
splitDamage = RoundToInterval(splitDamage, 0, int32(dmgInfo.GetDamage()));
dmgInfo.AbsorbDamage(splitDamage);
uint32 splitted = splitDamage;
uint32 splitted_absorb = 0;
uint32 splitted_resist = 0;
uint32 procAttacker = 0, procVictim = 0, procEx = PROC_EX_NORMAL_HIT;
if (caster->IsImmunedToDamageOrSchool(schoolMask))
{
procEx |= PROC_EX_IMMUNE;
splitted_absorb = splitted;
splitted = 0;
}
else
{
Unit::DealDamageMods(caster, splitted, &splitted_absorb);
Unit::CalcAbsorbResist(attacker, caster, schoolMask, damagetype, splitted, &splitted_absorb, &splitted_resist, spellInfo, true);
splitted -= std::min(splitted_absorb, splitted);
}
// create procs
createProcFlags(spellInfo, BASE_ATTACK, false, procAttacker, procVictim);
caster->ProcDamageAndSpellFor(true, attacker, procVictim, procEx, BASE_ATTACK, spellInfo, splitted, NULL);
if (attacker)
attacker->SendSpellNonMeleeDamageLog(caster, (*itr)->GetSpellInfo()->Id, splitted+splitted_absorb, schoolMask, splitted_absorb, 0, false, 0, false);
CleanDamage cleanDamage = CleanDamage(splitted, 0, BASE_ATTACK, MELEE_HIT_NORMAL);
Unit::DealDamage(attacker, caster, splitted, &cleanDamage, DIRECT_DAMAGE, schoolMask, (*itr)->GetSpellInfo(), 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
AuraEffectList vSplitDamagePctCopy(victim->GetAuraEffectsByType(SPELL_AURA_SPLIT_DAMAGE_PCT));
for (AuraEffectList::iterator itr = vSplitDamagePctCopy.begin(), next; (itr != vSplitDamagePctCopy.end()) && (dmgInfo.GetDamage() > 0); ++itr)
{
// Check if aura was removed during iteration - we don't need to work on such auras
AuraApplication const* aurApp = (*itr)->GetBase()->GetApplicationOfTarget(victim->GetGUID());
if (!aurApp)
continue;
// check damage school mask
if (!((*itr)->GetMiscValue() & schoolMask))
continue;
// Damage can be splitted only if aura has an alive caster
Unit* caster = (*itr)->GetCaster();
if (!caster || (caster == victim) || !caster->IsInWorld() || !caster->IsAlive())
continue;
// Xinef: Single Target splits require LoS
const SpellInfo* splitSpellInfo = (*itr)->GetSpellInfo();
if (!splitSpellInfo->Effects[(*itr)->GetEffIndex()].IsAreaAuraEffect() && !splitSpellInfo->HasAttribute(SPELL_ATTR2_CAN_TARGET_NOT_IN_LOS))
if (!caster->IsWithinLOSInMap(victim) || !caster->IsWithinDist(victim, splitSpellInfo->GetMaxRange(splitSpellInfo->IsPositive(), caster)))
continue;
uint32 splitDamage = CalculatePct(dmgInfo.GetDamage(), (*itr)->GetAmount());
SpellSchoolMask splitSchoolMask = schoolMask;
(*itr)->GetBase()->CallScriptEffectSplitHandlers(*itr, aurApp, dmgInfo, splitDamage);
// absorb must be smaller than the damage itself
splitDamage = RoundToInterval(splitDamage, uint32(0), uint32(dmgInfo.GetDamage()));
// Roar of Sacrifice, dont absorb it
if (splitSpellInfo->Id != 53480)
dmgInfo.AbsorbDamage(splitDamage);
else
splitSchoolMask = SPELL_SCHOOL_MASK_NATURE;
uint32 splitted = splitDamage;
uint32 splitted_absorb = 0;
uint32 splitted_resist = 0;
uint32 procAttacker = 0, procVictim = 0, procEx = PROC_EX_NORMAL_HIT;
if (caster->IsImmunedToDamageOrSchool(schoolMask))
{
procEx |= PROC_EX_IMMUNE;
splitted_absorb = splitted;
splitted = 0;
}
else
{
Unit::DealDamageMods(caster, splitted, &splitted_absorb);
Unit::CalcAbsorbResist(attacker, caster, splitSchoolMask, damagetype, splitted, &splitted_absorb, &splitted_resist, spellInfo, true);
splitted -= std::min(splitted_absorb, splitted);
}
// create procs
createProcFlags(spellInfo, BASE_ATTACK, false, procAttacker, procVictim);
caster->ProcDamageAndSpellFor(true, attacker, procVictim, procEx, BASE_ATTACK, spellInfo, splitted, NULL);
if (attacker)
attacker->SendSpellNonMeleeDamageLog(caster, splitSpellInfo->Id, splitted+splitted_absorb, splitSchoolMask, splitted_absorb, 0, false, 0, false);
CleanDamage cleanDamage = CleanDamage(splitted, 0, BASE_ATTACK, MELEE_HIT_NORMAL);
Unit::DealDamage(attacker, caster, splitted, &cleanDamage, DIRECT_DAMAGE, splitSchoolMask, splitSpellInfo, false);
}
}
*resist = dmgInfo.GetResist();
*absorb = dmgInfo.GetAbsorb();
}
void Unit::CalcHealAbsorb(Unit const* victim, const SpellInfo* healSpell, uint32 &healAmount, uint32 &absorb)
{
if (!healAmount)
return;
int32 RemainingHeal = healAmount;
// Need remove expired auras after
bool existExpired = false;
// absorb without mana cost
AuraEffectList const& vHealAbsorb = victim->GetAuraEffectsByType(SPELL_AURA_SCHOOL_HEAL_ABSORB);
for (AuraEffectList::const_iterator i = vHealAbsorb.begin(); i != vHealAbsorb.end() && RemainingHeal > 0; ++i)
{
if (!((*i)->GetMiscValue() & healSpell->SchoolMask))
continue;
// Max Amount can be absorbed by this aura
int32 currentAbsorb = (*i)->GetAmount();
// Found empty aura (impossible but..)
if (currentAbsorb <= 0)
{
existExpired = true;
continue;
}
// currentAbsorb - damage can be absorbed by shield
// If need absorb less damage
if (RemainingHeal < currentAbsorb)
currentAbsorb = RemainingHeal;
RemainingHeal -= currentAbsorb;
// Reduce shield amount
(*i)->SetAmount((*i)->GetAmount() - currentAbsorb);
// Need remove it later
if ((*i)->GetAmount() <= 0)
existExpired = true;
}
// Remove all expired absorb auras
if (existExpired)
{
for (AuraEffectList::const_iterator i = vHealAbsorb.begin(); i != vHealAbsorb.end();)
{
AuraEffect* auraEff = *i;
++i;
if (auraEff->GetAmount() <= 0)
{
uint32 removedAuras = victim->m_removedAurasCount;
auraEff->GetBase()->Remove(AURA_REMOVE_BY_ENEMY_SPELL);
if (removedAuras+1 < victim->m_removedAurasCount)
i = vHealAbsorb.begin();
}
}
}
absorb = RemainingHeal > 0 ? (healAmount - RemainingHeal) : healAmount;
healAmount = RemainingHeal;
}
void Unit::AttackerStateUpdate (Unit* victim, WeaponAttackType attType, bool extra)
{
if (HasUnitState(UNIT_STATE_CANNOT_AUTOATTACK) || HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PACIFIED))
return;
if (!victim->IsAlive())
return;
CombatStart(victim);
RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_MELEE_ATTACK);
if (attType != BASE_ATTACK && attType != OFF_ATTACK)
return; // ignore ranged case
// melee attack spell casted at main hand attack only - no normal melee dmg dealt
if (attType == BASE_ATTACK && m_currentSpells[CURRENT_MELEE_SPELL] && !extra)
m_currentSpells[CURRENT_MELEE_SPELL]->cast();
else
{
// attack can be redirected to another target
victim = GetMeleeHitRedirectTarget(victim);
CalcDamageInfo damageInfo;
CalculateMeleeDamage(victim, 0, &damageInfo, attType);
// Send log damage message to client
Unit::DealDamageMods(victim, damageInfo.damage, &damageInfo.absorb);
SendAttackStateUpdate(&damageInfo);
//TriggerAurasProcOnEvent(damageInfo);
DealMeleeDamage(&damageInfo, true);
ProcDamageAndSpell(damageInfo.target, damageInfo.procAttacker, damageInfo.procVictim, damageInfo.procEx, damageInfo.damage, damageInfo.attackType);
/*if (GetTypeId() == TYPEID_PLAYER)
;//sLog->outStaticDebug("AttackerStateUpdate: (Player) %u attacked %u (TypeId: %u) for %u dmg, absorbed %u, blocked %u, resisted %u.",
GetGUIDLow(), victim->GetGUIDLow(), victim->GetTypeId(), damageInfo.damage, damageInfo.absorb, damageInfo.blocked_amount, damageInfo.resist);
else
;//sLog->outStaticDebug("AttackerStateUpdate: (NPC) %u attacked %u (TypeId: %u) for %u dmg, absorbed %u, blocked %u, resisted %u.",
GetGUIDLow(), victim->GetGUIDLow(), victim->GetTypeId(), damageInfo.damage, damageInfo.absorb, damageInfo.blocked_amount, damageInfo.resist);*/
}
}
void Unit::HandleProcExtraAttackFor(Unit* victim)
{
while (m_extraAttacks)
{
AttackerStateUpdate(victim, BASE_ATTACK, true);
--m_extraAttacks;
}
}
MeleeHitOutcome Unit::RollMeleeOutcomeAgainst(const Unit* victim, WeaponAttackType attType) const
{
// This is only wrapper
// Miss chance based on melee
//float miss_chance = MeleeMissChanceCalc(victim, attType);
float miss_chance = MeleeSpellMissChance(victim, attType, int32(GetWeaponSkillValue(attType, victim)) - int32(victim->GetMaxSkillValueForLevel(this)), 0);
// Critical hit chance
float crit_chance = GetUnitCriticalChance(attType, victim);
if( crit_chance < 0 )
crit_chance = 0;
float dodge_chance = victim->GetUnitDodgeChance();
float block_chance = victim->GetUnitBlockChance();
float parry_chance = victim->GetUnitParryChance();
// Useful if want to specify crit & miss chances for melee, else it could be removed
;//sLog->outStaticDebug("MELEE OUTCOME: miss %f crit %f dodge %f parry %f block %f", miss_chance, crit_chance, dodge_chance, parry_chance, block_chance);
return RollMeleeOutcomeAgainst(victim, attType, int32(crit_chance*100), int32(miss_chance*100), int32(dodge_chance*100), int32(parry_chance*100), int32(block_chance*100));
}
MeleeHitOutcome Unit::RollMeleeOutcomeAgainst (const Unit* victim, WeaponAttackType attType, int32 crit_chance, int32 miss_chance, int32 dodge_chance, int32 parry_chance, int32 block_chance) const
{
if (victim->GetTypeId() == TYPEID_UNIT && victim->ToCreature()->IsInEvadeMode())
return MELEE_HIT_EVADE;
int32 attackerMaxSkillValueForLevel = GetMaxSkillValueForLevel(victim);
int32 victimMaxSkillValueForLevel = victim->GetMaxSkillValueForLevel(this);
int32 attackerWeaponSkill = GetWeaponSkillValue(attType, victim);
int32 victimDefenseSkill = victim->GetDefenseSkillValue(this);
// bonus from skills is 0.04%
int32 skillBonus = 4 * (attackerWeaponSkill - victimMaxSkillValueForLevel);
int32 sum = 0, tmp = 0;
int32 roll = urand (0, 10000);
;//sLog->outStaticDebug ("RollMeleeOutcomeAgainst: skill bonus of %d for attacker", skillBonus);
;//sLog->outStaticDebug ("RollMeleeOutcomeAgainst: rolled %d, miss %d, dodge %d, parry %d, block %d, crit %d",
// roll, miss_chance, dodge_chance, parry_chance, block_chance, crit_chance);
tmp = miss_chance;
if (tmp > 0 && roll < (sum += tmp))
{
;//sLog->outStaticDebug ("RollMeleeOutcomeAgainst: MISS");
return MELEE_HIT_MISS;
}
// always crit against a sitting target (except 0 crit chance)
if (victim->GetTypeId() == TYPEID_PLAYER && crit_chance > 0 && !victim->IsStandState())
{
;//sLog->outStaticDebug ("RollMeleeOutcomeAgainst: CRIT (sitting victim)");
return MELEE_HIT_CRIT;
}
// Dodge chance
// only players can't dodge if attacker is behind
if (victim->GetTypeId() == TYPEID_PLAYER && !victim->HasInArc(M_PI, this) && !victim->HasAuraType(SPELL_AURA_IGNORE_HIT_DIRECTION))
{
;//sLog->outStaticDebug ("RollMeleeOutcomeAgainst: attack came from behind and victim was a player.");
}
// Xinef: do not allow to dodge with CREATURE_FLAG_EXTRA_NO_DODGE flag
else if (victim->GetTypeId() == TYPEID_PLAYER || !(victim->ToCreature()->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_DODGE))
{
// Reduce dodge chance by attacker expertise rating
if (GetTypeId() == TYPEID_PLAYER)
dodge_chance -= int32(ToPlayer()->GetExpertiseDodgeOrParryReduction(attType) * 100);
else
dodge_chance -= GetTotalAuraModifier(SPELL_AURA_MOD_EXPERTISE) * 25;
// Modify dodge chance by attacker SPELL_AURA_MOD_COMBAT_RESULT_CHANCE
dodge_chance+= GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_COMBAT_RESULT_CHANCE, VICTIMSTATE_DODGE) * 100;
dodge_chance = int32 (float (dodge_chance) * GetTotalAuraMultiplier(SPELL_AURA_MOD_ENEMY_DODGE));
tmp = dodge_chance;
// xinef: if casting or stunned - cant dodge
if (victim->IsNonMeleeSpellCast(false, false, true) || victim->HasUnitState(UNIT_STATE_CONTROLLED))
tmp = 0;
if ((tmp > 0) // check if unit _can_ dodge
&& ((tmp -= skillBonus) > 0)
&& roll < (sum += tmp))
{
;//sLog->outStaticDebug ("RollMeleeOutcomeAgainst: DODGE <%d, %d)", sum-tmp, sum);
return MELEE_HIT_DODGE;
}
}
// parry & block chances
// check if attack comes from behind, nobody can parry or block if attacker is behind
if (!victim->HasInArc(M_PI, this) && !victim->HasAuraType(SPELL_AURA_IGNORE_HIT_DIRECTION))
;//sLog->outStaticDebug ("RollMeleeOutcomeAgainst: attack came from behind.");
else
{
// Reduce parry chance by attacker expertise rating
if (GetTypeId() == TYPEID_PLAYER)
parry_chance -= int32(ToPlayer()->GetExpertiseDodgeOrParryReduction(attType) * 100);
else
parry_chance -= GetTotalAuraModifier(SPELL_AURA_MOD_EXPERTISE) * 25;
if (victim->GetTypeId() == TYPEID_PLAYER || !(victim->ToCreature()->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_PARRY))
{
tmp = parry_chance;
// xinef: cant parry while casting or while stunned
if (victim->IsNonMeleeSpellCast(false, false, true) || victim->HasUnitState(UNIT_STATE_CONTROLLED))
tmp = 0;
if (tmp > 0 // check if unit _can_ parry
&& (tmp -= skillBonus) > 0
&& roll < (sum += tmp))
{
;//sLog->outStaticDebug ("RollMeleeOutcomeAgainst: PARRY <%d, %d)", sum-tmp2, sum);
return MELEE_HIT_PARRY;
}
}
if (victim->GetTypeId() == TYPEID_PLAYER || !(victim->ToCreature()->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_BLOCK))
{
tmp = block_chance;
// xinef: cant block while casting or while stunned
if (victim->IsNonMeleeSpellCast(false, false, true) || victim->HasUnitState(UNIT_STATE_CONTROLLED))
tmp = 0;
if (tmp > 0 // check if unit _can_ block
&& (tmp -= skillBonus) > 0
&& roll < (sum += tmp))
{
;//sLog->outStaticDebug ("RollMeleeOutcomeAgainst: BLOCK <%d, %d)", sum-tmp, sum);
return MELEE_HIT_BLOCK;
}
}
}
// 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 (attType != RANGED_ATTACK &&
(GetTypeId() == TYPEID_PLAYER || IsPet()) &&
victim->GetTypeId() != TYPEID_PLAYER && !victim->IsPet() &&
getLevel() < victim->getLevelForTarget(this))
{
// cap possible value (with bonuses > max skill)
int32 skill = attackerWeaponSkill;
int32 maxskill = attackerMaxSkillValueForLevel;
skill = (skill > maxskill) ? maxskill : skill;
tmp = (10 + (victimDefenseSkill - skill)) * 100;
tmp = tmp > 4000 ? 4000 : tmp;
if (roll < (sum += tmp))
{
;//sLog->outStaticDebug ("RollMeleeOutcomeAgainst: GLANCING <%d, %d)", sum-4000, sum);
return MELEE_HIT_GLANCING;
}
}
// mobs can score crushing blows if they're 4 or more levels above victim
if (getLevelForTarget(victim) >= victim->getLevelForTarget(this) + 4 &&
// can be from by creature (if can) or from controlled player that considered as creature
!IsControlledByPlayer() &&
!(GetTypeId() == TYPEID_UNIT && ToCreature()->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_CRUSH))
{
// when their weapon skill is 15 or more above victim's defense skill
tmp = victimDefenseSkill;
int32 tmpmax = victimMaxSkillValueForLevel;
// having defense above your maximum (from items, talents etc.) has no effect
tmp = tmp > tmpmax ? tmpmax : tmp;
// tmp = mob's level * 5 - player's current defense skill
tmp = attackerMaxSkillValueForLevel - tmp;
if (tmp >= 15)
{
// add 2% chance per lacking skill point, min. is 15%
tmp = tmp * 200 - 1500;
if (roll < (sum += tmp))
{
;//sLog->outStaticDebug ("RollMeleeOutcomeAgainst: CRUSHING <%d, %d)", sum-tmp, sum);
return MELEE_HIT_CRUSHING;
}
}
}
// Critical chance
tmp = crit_chance;
if (tmp > 0 && roll < (sum += tmp))
{
;//sLog->outStaticDebug ("RollMeleeOutcomeAgainst: CRIT <%d, %d)", sum-tmp, sum);
if (GetTypeId() == TYPEID_UNIT && (ToCreature()->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_CRIT))
;//sLog->outStaticDebug ("RollMeleeOutcomeAgainst: CRIT DISABLED)");
else
return MELEE_HIT_CRIT;
}
;//sLog->outStaticDebug ("RollMeleeOutcomeAgainst: NORMAL");
return MELEE_HIT_NORMAL;
}
uint32 Unit::CalculateDamage(WeaponAttackType attType, bool normalized, bool addTotalPct)
{
float minDamage = 0.0f;
float maxDamage = 0.0f;
if (normalized || !addTotalPct)
CalculateMinMaxDamage(attType, normalized, addTotalPct, minDamage, maxDamage);
else
{
switch (attType)
{
case RANGED_ATTACK:
minDamage = GetFloatValue(UNIT_FIELD_MINRANGEDDAMAGE);
maxDamage = GetFloatValue(UNIT_FIELD_MAXRANGEDDAMAGE);
break;
case BASE_ATTACK:
minDamage = GetFloatValue(UNIT_FIELD_MINDAMAGE);
maxDamage = GetFloatValue(UNIT_FIELD_MAXDAMAGE);
break;
case OFF_ATTACK:
minDamage = GetFloatValue(UNIT_FIELD_MINOFFHANDDAMAGE);
maxDamage = GetFloatValue(UNIT_FIELD_MAXOFFHANDDAMAGE);
break;
default:
break;
}
// pussywizard: this check only in "else" section, proper check is also in CalculateMinMaxDamage
if (minDamage > maxDamage)
minDamage = maxDamage;
}
if (maxDamage == 0.0f)
maxDamage = 5.0f;
return urand(uint32(minDamage), uint32(maxDamage));
}
float Unit::CalculateLevelPenalty(SpellInfo const* spellProto) const
{
if (GetTypeId() != TYPEID_PLAYER)
return 1.0f;
if (spellProto->SpellLevel <= 0 || spellProto->SpellLevel >= spellProto->MaxLevel)
return 1.0f;
float LvlPenalty = 0.0f;
// xinef: added brackets, trinity retards...
if (spellProto->SpellLevel < 20)
LvlPenalty = (20.0f - spellProto->SpellLevel) * 3.75f;
float LvlFactor = (float(spellProto->SpellLevel) + 6.0f) / float(getLevel());
if (LvlFactor > 1.0f)
LvlFactor = 1.0f;
return AddPct(LvlFactor, -LvlPenalty);
}
void Unit::SendMeleeAttackStart(Unit* victim, Player* sendTo)
{
WorldPacket data(SMSG_ATTACKSTART, 8 + 8);
data << uint64(GetGUID());
data << uint64(victim->GetGUID());
if (sendTo)
sendTo->SendDirectMessage(&data);
else
SendMessageToSet(&data, true);
;//sLog->outStaticDebug("WORLD: Sent SMSG_ATTACKSTART");
}
void Unit::SendMeleeAttackStop(Unit* victim)
{
// pussywizard: calling SendMeleeAttackStop without clearing UNIT_STATE_MELEE_ATTACKING and then AttackStart the same player may spoil npc rotating!
// pussywizard: this happens in some boss scripts, just add clearing here
// ClearUnitState(UNIT_STATE_MELEE_ATTACKING); // ZOMG! commented out for now
WorldPacket data(SMSG_ATTACKSTOP, (8+8+4));
data.append(GetPackGUID());
data.append(victim ? victim->GetPackGUID() : 0);
data << uint32(0); //! Can also take the value 0x01, which seems related to updating rotation
SendMessageToSet(&data, true);
;//sLog->outStaticDebug("WORLD: Sent SMSG_ATTACKSTOP");
/*if (victim)
;//sLog->outDetail("%s %u stopped attacking %s %u", (GetTypeId() == TYPEID_PLAYER ? "Player" : "Creature"), GetGUIDLow(), (victim->GetTypeId() == TYPEID_PLAYER ? "player" : "creature"), victim->GetGUIDLow());
else
;//sLog->outDetail("%s %u stopped attacking", (GetTypeId() == TYPEID_PLAYER ? "Player" : "Creature"), GetGUIDLow());*/
}
bool Unit::isSpellBlocked(Unit* victim, SpellInfo const* spellProto, WeaponAttackType attackType)
{
// These spells can't be blocked
if (spellProto && spellProto->HasAttribute(SPELL_ATTR0_IMPOSSIBLE_DODGE_PARRY_BLOCK))
return false;
if (victim->HasAuraType(SPELL_AURA_IGNORE_HIT_DIRECTION) || victim->HasInArc(M_PI, this))
{
// Check creatures flags_extra for disable block
if (victim->GetTypeId() == TYPEID_UNIT &&
victim->ToCreature()->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_BLOCK)
return false;
float blockChance = victim->GetUnitBlockChance();
blockChance += (int32(GetWeaponSkillValue(attackType)) - int32(victim->GetMaxSkillValueForLevel())) * 0.04f;
// xinef: cant block while casting or while stunned
if (blockChance < 0.0f || victim->IsNonMeleeSpellCast(false, false, true) || victim->HasUnitState(UNIT_STATE_CONTROLLED))
blockChance = 0.0f;
if (roll_chance_f(blockChance))
return true;
}
return false;
}
bool Unit::isBlockCritical()
{
if (roll_chance_i(GetTotalAuraModifier(SPELL_AURA_MOD_BLOCK_CRIT_CHANCE)))
return true;
return false;
}
int32 Unit::GetMechanicResistChance(const SpellInfo* spell)
{
if (!spell)
return 0;
int32 resist_mech = 0;
for (uint8 eff = 0; eff < MAX_SPELL_EFFECTS; ++eff)
{
if (!spell->Effects[eff].IsEffect())
break;
int32 effect_mech = spell->GetEffectMechanic(eff);
if (effect_mech)
{
int32 temp = GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_MECHANIC_RESISTANCE, effect_mech);
if (resist_mech < temp)
resist_mech = temp;
}
}
return resist_mech;
}
// Melee based spells hit result calculations
SpellMissInfo Unit::MeleeSpellHitResult(Unit* victim, SpellInfo const* spell)
{
// Spells with SPELL_ATTR3_IGNORE_HIT_RESULT will additionally fully ignore
// resist and deflect chances
if (spell->HasAttribute(SPELL_ATTR3_IGNORE_HIT_RESULT))
return SPELL_MISS_NONE;
WeaponAttackType attType = BASE_ATTACK;
// Check damage class instead of attack type to correctly handle judgements
// - they are meele, but can't be dodged/parried/deflected because of ranged dmg class
if (spell->DmgClass == SPELL_DAMAGE_CLASS_RANGED)
attType = RANGED_ATTACK;
int32 attackerWeaponSkill;
// skill value for these spells (for example judgements) is 5* level
if (spell->DmgClass == SPELL_DAMAGE_CLASS_RANGED && !spell->IsRangedWeaponSpell())
attackerWeaponSkill = getLevel() * 5;
// bonus from skills is 0.04% per skill Diff
else
attackerWeaponSkill = int32(GetWeaponSkillValue(attType, victim));
int32 skillDiff = attackerWeaponSkill - int32(victim->GetMaxSkillValueForLevel(this));
uint32 roll = urand (0, 10000);
uint32 missChance = uint32(MeleeSpellMissChance(victim, attType, skillDiff, spell->Id) * 100.0f);
// Roll miss
uint32 tmp = missChance;
if (roll < tmp)
return SPELL_MISS_MISS;
bool canDodge = true;
bool canParry = true;
bool canBlock = spell->HasAttribute(SPELL_ATTR3_BLOCKABLE_SPELL) && !spell->HasAttribute(SPELL_ATTR0_CU_DIRECT_DAMAGE);
// Same spells cannot be parry/dodge
if (spell->HasAttribute(SPELL_ATTR0_IMPOSSIBLE_DODGE_PARRY_BLOCK))
return SPELL_MISS_NONE;
// Chance resist mechanic
int32 resist_chance = victim->GetMechanicResistChance(spell) * 100;
tmp += resist_chance;
if (roll < tmp)
return SPELL_MISS_RESIST;
// Ranged attacks can only miss, resist and deflect
if (attType == RANGED_ATTACK)
{
// only if in front
if (!victim->HasUnitState(UNIT_STATE_STUNNED) && (victim->HasInArc(M_PI,this) || victim->HasAuraType(SPELL_AURA_IGNORE_HIT_DIRECTION)))
{
int32 deflect_chance = victim->GetTotalAuraModifier(SPELL_AURA_DEFLECT_SPELLS) * 100;
tmp+=deflect_chance;
if (roll < tmp)
return SPELL_MISS_DEFLECT;
}
canDodge = false;
canParry = false;
}
// Check for attack from behind
// xinef: if from behind or spell requires cast from behind
if (!victim->HasInArc(M_PI, this) || spell->HasAttribute(SPELL_ATTR0_CU_REQ_CASTER_BEHIND_TARGET))
{
if (!victim->HasAuraType(SPELL_AURA_IGNORE_HIT_DIRECTION))
{
// Can`t dodge from behind in PvP (but its possible in PvE)
if (victim->GetTypeId() == TYPEID_PLAYER)
canDodge = false;
// Can`t parry or block
canParry = false;
canBlock = false;
}
}
// Check creatures flags_extra for disable parry
if (victim->GetTypeId() == TYPEID_UNIT)
{
uint32 flagEx = victim->ToCreature()->GetCreatureTemplate()->flags_extra;
// Xinef: no dodge flag
if (flagEx & CREATURE_FLAG_EXTRA_NO_DODGE)
canDodge = false;
if (flagEx & CREATURE_FLAG_EXTRA_NO_PARRY)
canParry = false;
// Check creatures flags_extra for disable block
if (flagEx & CREATURE_FLAG_EXTRA_NO_BLOCK)
canBlock = false;
}
// Ignore combat result aura
AuraEffectList const& ignore = GetAuraEffectsByType(SPELL_AURA_IGNORE_COMBAT_RESULT);
for (AuraEffectList::const_iterator i = ignore.begin(); i != ignore.end(); ++i)
{
if (!(*i)->IsAffectedOnSpell(spell))
continue;
switch ((*i)->GetMiscValue())
{
case MELEE_HIT_DODGE: canDodge = false; break;
case MELEE_HIT_BLOCK: canBlock = false; break;
case MELEE_HIT_PARRY: canParry = false; break;
default:
;//sLog->outStaticDebug("Spell %u SPELL_AURA_IGNORE_COMBAT_RESULT has unhandled state %d", (*i)->GetId(), (*i)->GetMiscValue());
break;
}
}
if (canDodge)
{
// Roll dodge
int32 dodgeChance = int32(victim->GetUnitDodgeChance() * 100.0f) - skillDiff * 4;
// Reduce enemy dodge chance by SPELL_AURA_MOD_COMBAT_RESULT_CHANCE
dodgeChance += GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_COMBAT_RESULT_CHANCE, VICTIMSTATE_DODGE) * 100;
dodgeChance = int32(float(dodgeChance) * GetTotalAuraMultiplier(SPELL_AURA_MOD_ENEMY_DODGE));
// Reduce dodge chance by attacker expertise rating
if (GetTypeId() == TYPEID_PLAYER)
dodgeChance -= int32(ToPlayer()->GetExpertiseDodgeOrParryReduction(attType) * 100.0f);
else
dodgeChance -= GetTotalAuraModifier(SPELL_AURA_MOD_EXPERTISE) * 25;
// xinef: cant dodge while casting or while stunned
if (dodgeChance < 0 || victim->IsNonMeleeSpellCast(false, false, true) || victim->HasUnitState(UNIT_STATE_CONTROLLED))
dodgeChance = 0;
tmp += dodgeChance;
if (roll < tmp)
return SPELL_MISS_DODGE;
}
if (canParry)
{
// Roll parry
int32 parryChance = int32(victim->GetUnitParryChance() * 100.0f) - skillDiff * 4;
// Reduce parry chance by attacker expertise rating
if (GetTypeId() == TYPEID_PLAYER)
parryChance -= int32(ToPlayer()->GetExpertiseDodgeOrParryReduction(attType) * 100.0f);
else
parryChance -= GetTotalAuraModifier(SPELL_AURA_MOD_EXPERTISE) * 25;
// xinef: cant parry while casting or while stunned
if (parryChance < 0 || victim->IsNonMeleeSpellCast(false, false, true) || victim->HasUnitState(UNIT_STATE_CONTROLLED))
parryChance = 0;
tmp += parryChance;
if (roll < tmp)
return SPELL_MISS_PARRY;
}
if (canBlock)
{
int32 blockChance = int32(victim->GetUnitBlockChance() * 100.0f) - skillDiff * 4;
// xinef: cant block while casting or while stunned
if (blockChance < 0 || victim->IsNonMeleeSpellCast(false, false, true) || victim->HasUnitState(UNIT_STATE_CONTROLLED))
blockChance = 0;
tmp += blockChance;
if (roll < tmp)
return SPELL_MISS_BLOCK;
}
return SPELL_MISS_NONE;
}
SpellMissInfo Unit::MagicSpellHitResult(Unit* victim, SpellInfo const* spell)
{
// Can`t miss on dead target (on skinning for example)
if (!victim->IsAlive() && victim->GetTypeId() != TYPEID_PLAYER)
return SPELL_MISS_NONE;
// vehicles cant miss
if (IsVehicle())
return SPELL_MISS_NONE;
// Spells with SPELL_ATTR3_IGNORE_HIT_RESULT will additionally fully ignore
// resist and deflect chances
// xinef: skip all calculations, proof: Toxic Tolerance quest
if (spell->HasAttribute(SPELL_ATTR3_IGNORE_HIT_RESULT))
return SPELL_MISS_NONE;
SpellSchoolMask schoolMask = spell->GetSchoolMask();
// PvP - PvE spell misschances per leveldif > 2
int32 lchance = victim->GetTypeId() == TYPEID_PLAYER ? 7 : 11;
int32 thisLevel = getLevelForTarget(victim);
if (GetTypeId() == TYPEID_UNIT && ToCreature()->IsTrigger())
thisLevel = std::max(thisLevel, spell->SpellLevel);
int32 leveldif = int32(victim->getLevelForTarget(this)) - thisLevel;
// Base hit chance from attacker and victim levels
int32 modHitChance;
if (leveldif < 3)
modHitChance = 96 - leveldif;
else
modHitChance = 94 - (leveldif - 2) * lchance;
// Spellmod from SPELLMOD_RESIST_MISS_CHANCE
if (Player* modOwner = GetSpellModOwner())
modOwner->ApplySpellMod(spell->Id, SPELLMOD_RESIST_MISS_CHANCE, modHitChance);
// Increase from attacker SPELL_AURA_MOD_INCREASES_SPELL_PCT_TO_HIT auras
modHitChance += GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_INCREASES_SPELL_PCT_TO_HIT, schoolMask);
// Spells with SPELL_ATTR3_IGNORE_HIT_RESULT will ignore target's avoidance effects
// xinef: imo it should completly ignore all calculations, eg: 14792. Hits 80 level players on blizz without any problems
//if (!spell->HasAttribute(SPELL_ATTR3_IGNORE_HIT_RESULT))
{
// Chance hit from victim SPELL_AURA_MOD_ATTACKER_SPELL_HIT_CHANCE auras
modHitChance += victim->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_ATTACKER_SPELL_HIT_CHANCE, schoolMask);
// Reduce spell hit chance for Area of effect spells from victim SPELL_AURA_MOD_AOE_AVOIDANCE aura
if (spell->IsAffectingArea())
modHitChance -= victim->GetTotalAuraModifier(SPELL_AURA_MOD_AOE_AVOIDANCE);
// Decrease hit chance from victim rating bonus
if (victim->GetTypeId() == TYPEID_PLAYER)
modHitChance -= int32(victim->ToPlayer()->GetRatingBonusValue(CR_HIT_TAKEN_SPELL));
}
int32 HitChance = modHitChance * 100;
// Increase hit chance from attacker SPELL_AURA_MOD_SPELL_HIT_CHANCE and attacker ratings
// Xinef: Totems should inherit casters ratings?
if (IsTotem())
{
if (Unit* owner = GetOwner())
HitChance += int32(owner->m_modSpellHitChance * 100.0f);
}
else
HitChance += int32(m_modSpellHitChance * 100.0f);
if (HitChance < 100)
HitChance = 100;
else if (HitChance > 10000)
HitChance = 10000;
int32 tmp = 10000 - HitChance;
int32 rand = irand(1, 10000); // Needs to be 1 to 10000 to avoid the 1/10000 chance to miss on 100% hit rating
if (rand < tmp)
return SPELL_MISS_MISS;
// Chance resist mechanic (select max value from every mechanic spell effect)
int32 resist_chance = victim->GetMechanicResistChance(spell) * 100;
tmp += resist_chance;
// Chance resist debuff
if (!spell->IsPositive() && !spell->HasAttribute(SPELL_ATTR4_IGNORE_RESISTANCES))
{
bool bNegativeAura = true;
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
{
// Xinef: Check if effect exists!
if (spell->Effects[i].IsEffect() && spell->Effects[i].ApplyAuraName == 0)
{
bNegativeAura = false;
break;
}
}
if (bNegativeAura)
{
tmp += victim->GetMaxPositiveAuraModifierByMiscValue(SPELL_AURA_MOD_DEBUFF_RESISTANCE, int32(spell->Dispel)) * 100;
tmp += victim->GetMaxNegativeAuraModifierByMiscValue(SPELL_AURA_MOD_DEBUFF_RESISTANCE, int32(spell->Dispel)) * 100;
}
// Players resistance for binary spells
if (spell->HasAttribute(SPELL_ATTR0_CU_BINARY_SPELL) && (spell->GetSchoolMask() & (SPELL_SCHOOL_MASK_NORMAL|SPELL_SCHOOL_MASK_HOLY)) == 0)
tmp += int32(Unit::GetEffectiveResistChance(this, spell->GetSchoolMask(), victim, spell) * 10000.0f); // 100 for spell calculations, and 100 for return value percentage
}
// Roll chance
if (rand < tmp)
return SPELL_MISS_RESIST;
// cast by caster in front of victim
if (!victim->HasUnitState(UNIT_STATE_STUNNED) && (victim->HasInArc(M_PI, this) || victim->HasAuraType(SPELL_AURA_IGNORE_HIT_DIRECTION)))
{
int32 deflect_chance = victim->GetTotalAuraModifier(SPELL_AURA_DEFLECT_SPELLS) * 100;
tmp += deflect_chance;
if (rand < tmp)
return SPELL_MISS_DEFLECT;
}
return SPELL_MISS_NONE;
}
// Calculate spell hit result can be:
// Every spell can: Evade/Immune/Reflect/Sucesful hit
// For melee based spells:
// Miss
// Dodge
// Parry
// For spells
// Resist
SpellMissInfo Unit::SpellHitResult(Unit* victim, SpellInfo const* spell, bool CanReflect)
{
// Check for immune
if (victim->IsImmunedToSpell(spell))
return SPELL_MISS_IMMUNE;
// All positive spells can`t miss
// TODO: client not show miss log for this spells - so need find info for this in dbc and use it!
if ((spell->IsPositive() || spell->HasEffect(SPELL_EFFECT_DISPEL))
&&(!IsHostileTo(victim))) // prevent from affecting enemy by "positive" spell
return SPELL_MISS_NONE;
// Check for immune
// xinef: check for school immunity only
if (victim->IsImmunedToSchool(spell))
return SPELL_MISS_IMMUNE;
if (this == victim)
return SPELL_MISS_NONE;
// Return evade for units in evade mode
if (victim->GetTypeId() == TYPEID_UNIT && victim->ToCreature()->IsInEvadeMode() && !spell->HasAura(SPELL_AURA_CONTROL_VEHICLE))
return SPELL_MISS_EVADE;
// Try victim reflect spell
if (CanReflect)
{
int32 reflectchance = victim->GetTotalAuraModifier(SPELL_AURA_REFLECT_SPELLS);
Unit::AuraEffectList const& mReflectSpellsSchool = victim->GetAuraEffectsByType(SPELL_AURA_REFLECT_SPELLS_SCHOOL);
for (Unit::AuraEffectList::const_iterator i = mReflectSpellsSchool.begin(); i != mReflectSpellsSchool.end(); ++i)
if ((*i)->GetMiscValue() & spell->GetSchoolMask())
reflectchance += (*i)->GetAmount();
if (reflectchance > 0 && roll_chance_i(reflectchance))
{
// Start triggers for remove charges if need (trigger only for victim, and mark as active spell)
//ProcDamageAndSpell(victim, PROC_FLAG_NONE, PROC_FLAG_TAKEN_SPELL_MAGIC_DMG_CLASS_NEG, PROC_EX_REFLECT, 1, BASE_ATTACK, spell);
return SPELL_MISS_REFLECT;
}
}
switch (spell->DmgClass)
{
case SPELL_DAMAGE_CLASS_RANGED:
case SPELL_DAMAGE_CLASS_MELEE:
return MeleeSpellHitResult(victim, spell);
case SPELL_DAMAGE_CLASS_NONE:
{
if (spell->SpellFamilyName)
return SPELL_MISS_NONE;
// Xinef: apply DAMAGE_CLASS_MAGIC conditions to damaging DAMAGE_CLASS_NONE spells
for (uint8 i = EFFECT_0; i < MAX_SPELL_EFFECTS; ++i)
if (spell->Effects[i].Effect && spell->Effects[i].Effect != SPELL_EFFECT_SCHOOL_DAMAGE)
if (spell->Effects[i].ApplyAuraName != SPELL_AURA_PERIODIC_DAMAGE)
return SPELL_MISS_NONE;
// no break intended
}
case SPELL_DAMAGE_CLASS_MAGIC:
return MagicSpellHitResult(victim, spell);
}
return SPELL_MISS_NONE;
}
uint32 Unit::GetDefenseSkillValue(Unit const* target) const
{
if (GetTypeId() == TYPEID_PLAYER)
{
// in PvP use full skill instead current skill value
uint32 value = (target && target->GetTypeId() == TYPEID_PLAYER)
? ToPlayer()->GetMaxSkillValue(SKILL_DEFENSE)
: ToPlayer()->GetSkillValue(SKILL_DEFENSE);
value += uint32(ToPlayer()->GetRatingBonusValue(CR_DEFENSE_SKILL));
return value;
}
else
return GetUnitMeleeSkill(target);
}
float Unit::GetUnitDodgeChance() const
{
if (GetTypeId() == TYPEID_PLAYER)
return ToPlayer()->GetRealDodge(); //GetFloatValue(PLAYER_DODGE_PERCENTAGE);
else
{
if (ToCreature()->IsTotem())
return 0.0f;
else
{
float dodge = ToCreature()->isWorldBoss() ? 5.85f : 5.0f; // Xinef: bosses should have 6.5% dodge (5.9 + 0.6 from defense skill difference)
dodge += GetTotalAuraModifier(SPELL_AURA_MOD_DODGE_PERCENT);
return dodge > 0.0f ? dodge : 0.0f;
}
}
}
float Unit::GetUnitParryChance() const
{
float chance = 0.0f;
if (Player const* player = ToPlayer())
{
if (player->CanParry())
{
Item* tmpitem = player->GetWeaponForAttack(BASE_ATTACK, true);
if (!tmpitem)
tmpitem = player->GetWeaponForAttack(OFF_ATTACK, true);
if (tmpitem)
chance = player->GetRealParry(); //GetFloatValue(PLAYER_PARRY_PERCENTAGE);
}
}
else if (GetTypeId() == TYPEID_UNIT)
{
if (ToCreature()->isWorldBoss())
chance = 13.4f; // + 0.6 by skill diff
else if (GetCreatureType() == CREATURE_TYPE_HUMANOID)
chance = 5.0f;
// Xinef: if aura is present, type should not matter
chance += GetTotalAuraModifier(SPELL_AURA_MOD_PARRY_PERCENT);
}
return chance > 0.0f ? chance : 0.0f;
}
float Unit::GetUnitMissChance(WeaponAttackType attType) const
{
float miss_chance = 5.00f;
if (Player const* player = ToPlayer())
miss_chance += player->GetMissPercentageFromDefence();
if (attType == RANGED_ATTACK)
miss_chance -= GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_RANGED_HIT_CHANCE);
else
miss_chance -= GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_MELEE_HIT_CHANCE);
return miss_chance;
}
float Unit::GetUnitBlockChance() const
{
if (Player const* player = ToPlayer())
{
if (player->CanBlock())
{
Item* tmpitem = player->GetUseableItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND);
if (tmpitem && !tmpitem->IsBroken() && tmpitem->GetTemplate()->Block)
return GetFloatValue(PLAYER_BLOCK_PERCENTAGE);
}
// is player but has no block ability or no not broken shield equipped
return 0.0f;
}
else
{
if (ToCreature()->IsTotem())
return 0.0f;
else
{
float block = 5.0f;
block += GetTotalAuraModifier(SPELL_AURA_MOD_BLOCK_PERCENT);
return block > 0.0f ? block : 0.0f;
}
}
}
float Unit::GetUnitCriticalChance(WeaponAttackType attackType, const Unit* victim) const
{
float crit;
if (GetTypeId() == TYPEID_PLAYER)
{
switch (attackType)
{
case BASE_ATTACK:
crit = GetFloatValue(PLAYER_CRIT_PERCENTAGE);
break;
case OFF_ATTACK:
crit = GetFloatValue(PLAYER_OFFHAND_CRIT_PERCENTAGE);
break;
case RANGED_ATTACK:
crit = GetFloatValue(PLAYER_RANGED_CRIT_PERCENTAGE);
break;
// Just for good manner
default:
crit = 0.0f;
break;
}
}
else
{
crit = 5.0f;
crit += GetTotalAuraModifier(SPELL_AURA_MOD_WEAPON_CRIT_PERCENT);
crit += GetTotalAuraModifier(SPELL_AURA_MOD_CRIT_PCT);
}
// flat aura mods
if (attackType == RANGED_ATTACK)
crit += victim->GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_RANGED_CRIT_CHANCE);
else
crit += victim->GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_MELEE_CRIT_CHANCE);
AuraEffectList const& mTotalAuraList = victim->GetAuraEffectsByType(SPELL_AURA_MOD_CRIT_CHANCE_FOR_CASTER);
for (AuraEffectList::const_iterator i = mTotalAuraList.begin(); i != mTotalAuraList.end(); ++i)
{
if (GetGUID() != (*i)->GetCasterGUID())
continue;
crit += (*i)->GetAmount();
}
// reduce crit chance from Rating for players
if (attackType != RANGED_ATTACK)
Unit::ApplyResilience(victim, &crit, NULL, false, CR_CRIT_TAKEN_MELEE);
else
Unit::ApplyResilience(victim, &crit, NULL, false, CR_CRIT_TAKEN_RANGED);
// Apply crit chance from defence skill
crit += (int32(GetMaxSkillValueForLevel(victim)) - int32(victim->GetDefenseSkillValue(this))) * 0.04f;
// xinef: SPELL_AURA_MOD_ATTACKER_SPELL_AND_WEAPON_CRIT_CHANCE should be calculated at the end
crit += victim->GetTotalAuraModifier(SPELL_AURA_MOD_ATTACKER_SPELL_AND_WEAPON_CRIT_CHANCE);
if (crit < 0.0f)
crit = 0.0f;
return crit;
}
uint32 Unit::GetWeaponSkillValue (WeaponAttackType attType, Unit const* target) const
{
uint32 value = 0;
if (Player const* player = ToPlayer())
{
Item* item = player->GetWeaponForAttack(attType, true);
// feral or unarmed skill only for base attack
if (attType != BASE_ATTACK && !item)
return 0;
if (IsInFeralForm())
return GetMaxSkillValueForLevel(); // always maximized SKILL_FERAL_COMBAT in fact
// weapon skill or (unarmed for base attack and fist weapons)
uint32 skill;
if (item && item->GetSkill() != SKILL_FIST_WEAPONS)
skill = item->GetSkill();
else
skill = SKILL_UNARMED;
// in PvP use full skill instead current skill value
value = (target && target->IsControlledByPlayer())
? player->GetMaxSkillValue(skill)
: player->GetSkillValue(skill);
// Modify value from ratings
value += uint32(player->GetRatingBonusValue(CR_WEAPON_SKILL));
switch (attType)
{
case BASE_ATTACK: value += uint32(player->GetRatingBonusValue(CR_WEAPON_SKILL_MAINHAND)); break;
case OFF_ATTACK: value += uint32(player->GetRatingBonusValue(CR_WEAPON_SKILL_OFFHAND)); break;
case RANGED_ATTACK: value += uint32(player->GetRatingBonusValue(CR_WEAPON_SKILL_RANGED)); break;
default: break;
}
}
else
value = GetUnitMeleeSkill(target);
return value;
}
void Unit::_DeleteRemovedAuras()
{
while (!m_removedAuras.empty())
{
delete m_removedAuras.front();
m_removedAuras.pop_front();
}
}
void Unit::_UpdateSpells(uint32 time)
{
if (m_currentSpells[CURRENT_AUTOREPEAT_SPELL])
_UpdateAutoRepeatSpell();
// remove finished spells from current pointers
for (uint32 i = 0; i < CURRENT_MAX_SPELL; ++i)
{
if (m_currentSpells[i] && m_currentSpells[i]->getState() == SPELL_STATE_FINISHED)
{
m_currentSpells[i]->SetReferencedFromCurrent(false);
m_currentSpells[i] = NULL; // remove pointer
}
}
// m_auraUpdateIterator can be updated in indirect called code at aura remove to skip next planned to update but removed auras
for (m_auraUpdateIterator = m_ownedAuras.begin(); m_auraUpdateIterator != m_ownedAuras.end();)
{
Aura* i_aura = m_auraUpdateIterator->second;
++m_auraUpdateIterator; // need shift to next for allow update if need into aura update
i_aura->UpdateOwner(time, this);
}
// remove expired auras - do that after updates(used in scripts?)
for (AuraMap::iterator i = m_ownedAuras.begin(); i != m_ownedAuras.end();)
{
if (i->second->IsExpired())
RemoveOwnedAura(i, AURA_REMOVE_BY_EXPIRE);
else
++i;
}
for (VisibleAuraMap::iterator itr = m_visibleAuras.begin(); itr != m_visibleAuras.end(); ++itr)
if (itr->second->IsNeedClientUpdate())
itr->second->ClientUpdate();
_DeleteRemovedAuras();
if (!m_gameObj.empty())
{
for (GameObjectList::iterator itr = m_gameObj.begin(); itr != m_gameObj.end();)
{
if (GameObject* go = ObjectAccessor::GetGameObject(*this, *itr))
if (!go->isSpawned())
{
go->SetOwnerGUID(0);
go->SetRespawnTime(0);
go->Delete();
m_gameObj.erase(itr++);
continue;
}
++itr;
}
}
}
void Unit::_UpdateAutoRepeatSpell()
{
// check "realtime" interrupts
if ((GetTypeId() == TYPEID_PLAYER && ToPlayer()->isMoving()) || IsNonMeleeSpellCast(false, false, true, m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->m_spellInfo->Id == 75))
{
// cancel wand shoot
if (m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->m_spellInfo->Id != 75)
InterruptSpell(CURRENT_AUTOREPEAT_SPELL);
m_AutoRepeatFirstCast = true;
return;
}
// apply delay (Auto Shot (spellID 75) not affected)
if (m_AutoRepeatFirstCast && getAttackTimer(RANGED_ATTACK) < 500 && m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->m_spellInfo->Id != 75)
setAttackTimer(RANGED_ATTACK, 500);
m_AutoRepeatFirstCast = false;
// castroutine
if (isAttackReady(RANGED_ATTACK))
{
// Check if able to cast
if (m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->CheckCast(true) != SPELL_CAST_OK)
{
InterruptSpell(CURRENT_AUTOREPEAT_SPELL);
return;
}
// we want to shoot
Spell* spell = new Spell(this, m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->m_spellInfo, TRIGGERED_FULL_MASK);
spell->prepare(&(m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->m_targets));
// all went good, reset attack
resetAttackTimer(RANGED_ATTACK);
}
}
void Unit::SetCurrentCastedSpell(Spell* pSpell)
{
ASSERT(pSpell); // NULL may be never passed here, use InterruptSpell or InterruptNonMeleeSpells
CurrentSpellTypes CSpellType = pSpell->GetCurrentContainer();
if (pSpell == m_currentSpells[CSpellType]) // avoid breaking self
return;
// break same type spell if it is not delayed
InterruptSpell(CSpellType, false);
// special breakage effects:
switch (CSpellType)
{
case CURRENT_GENERIC_SPELL:
{
// generic spells always break channeled not delayed spells
if (Spell* s = GetCurrentSpell(CURRENT_CHANNELED_SPELL))
if (s->GetSpellInfo()->Id != 69051) // pussywizard: FoS, boss Devourer of Souls, Mirrored Soul, does not have any special attribute
InterruptSpell(CURRENT_CHANNELED_SPELL, false);
// autorepeat breaking
if (m_currentSpells[CURRENT_AUTOREPEAT_SPELL])
{
// break autorepeat if not Auto Shot
if (m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->m_spellInfo->Id != 75)
InterruptSpell(CURRENT_AUTOREPEAT_SPELL);
m_AutoRepeatFirstCast = true;
}
if (pSpell->GetCastTime() > 0)
AddUnitState(UNIT_STATE_CASTING);
break;
}
case CURRENT_CHANNELED_SPELL:
{
// channel spells always break generic non-delayed and any channeled spells
InterruptSpell(CURRENT_GENERIC_SPELL, false);
InterruptSpell(CURRENT_CHANNELED_SPELL);
// it also does break autorepeat if not Auto Shot
if (m_currentSpells[CURRENT_AUTOREPEAT_SPELL] &&
m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->m_spellInfo->Id != 75)
InterruptSpell(CURRENT_AUTOREPEAT_SPELL);
AddUnitState(UNIT_STATE_CASTING);
break;
}
case CURRENT_AUTOREPEAT_SPELL:
{
// only Auto Shoot does not break anything
if (pSpell->m_spellInfo->Id != 75)
{
// generic autorepeats break generic non-delayed and channeled non-delayed spells
InterruptSpell(CURRENT_GENERIC_SPELL, false);
InterruptSpell(CURRENT_CHANNELED_SPELL, false);
}
// special action: set first cast flag
m_AutoRepeatFirstCast = true;
break;
}
default:
// other spell types don't break anything now
break;
}
// current spell (if it is still here) may be safely deleted now
if (m_currentSpells[CSpellType])
m_currentSpells[CSpellType]->SetReferencedFromCurrent(false);
// set new current spell
m_currentSpells[CSpellType] = pSpell;
pSpell->SetReferencedFromCurrent(true);
pSpell->m_selfContainer = &(m_currentSpells[pSpell->GetCurrentContainer()]);
}
void Unit::InterruptSpell(CurrentSpellTypes spellType, bool withDelayed, bool withInstant, bool bySelf)
{
ASSERT(spellType < CURRENT_MAX_SPELL);
//sLog->outDebug(LOG_FILTER_UNITS, "Interrupt spell for unit %u.", GetEntry());
Spell* spell = m_currentSpells[spellType];
if (spell
&& (withDelayed || spell->getState() != SPELL_STATE_DELAYED)
&& (withInstant || spell->GetCastTime() > 0 || spell->getState() == SPELL_STATE_CASTING)) // xinef: or spell is in casting state (channeled spells only)
{
// for example, do not let self-stun aura interrupt itself
if (!spell->IsInterruptable())
return;
// send autorepeat cancel message for autorepeat spells
if (spellType == CURRENT_AUTOREPEAT_SPELL)
if (GetTypeId() == TYPEID_PLAYER)
ToPlayer()->SendAutoRepeatCancel(this);
if (spell->getState() != SPELL_STATE_FINISHED)
spell->cancel(bySelf);
m_currentSpells[spellType] = NULL;
spell->SetReferencedFromCurrent(false);
}
}
void Unit::FinishSpell(CurrentSpellTypes spellType, bool ok /*= true*/)
{
Spell* spell = m_currentSpells[spellType];
if (!spell)
return;
if (spellType == CURRENT_CHANNELED_SPELL)
spell->SendChannelUpdate(0);
spell->finish(ok);
}
bool Unit::IsNonMeleeSpellCast(bool withDelayed, bool skipChanneled, bool skipAutorepeat, bool isAutoshoot, bool skipInstant) const
{
// We don't do loop here to explicitly show that melee spell is excluded.
// Maybe later some special spells will be excluded too.
// generic spells are cast when they are not finished and not delayed
if (m_currentSpells[CURRENT_GENERIC_SPELL] &&
(m_currentSpells[CURRENT_GENERIC_SPELL]->getState() != SPELL_STATE_FINISHED) &&
(withDelayed || m_currentSpells[CURRENT_GENERIC_SPELL]->getState() != SPELL_STATE_DELAYED))
{
if (!skipInstant || m_currentSpells[CURRENT_GENERIC_SPELL]->GetCastTime())
{
if (!isAutoshoot || !m_currentSpells[CURRENT_GENERIC_SPELL]->m_spellInfo->HasAttribute(SPELL_ATTR2_NOT_RESET_AUTO_ACTIONS))
return true;
}
}
// channeled spells may be delayed, but they are still considered cast
if (!skipChanneled && m_currentSpells[CURRENT_CHANNELED_SPELL] &&
(m_currentSpells[CURRENT_CHANNELED_SPELL]->getState() != SPELL_STATE_FINISHED))
{
if (!isAutoshoot || !m_currentSpells[CURRENT_CHANNELED_SPELL]->m_spellInfo->HasAttribute(SPELL_ATTR2_NOT_RESET_AUTO_ACTIONS))
return true;
}
// autorepeat spells may be finished or delayed, but they are still considered cast
if (!skipAutorepeat && m_currentSpells[CURRENT_AUTOREPEAT_SPELL])
return true;
return false;
}
void Unit::InterruptNonMeleeSpells(bool withDelayed, uint32 spell_id, bool withInstant, bool bySelf)
{
// generic spells are interrupted if they are not finished or delayed
if (m_currentSpells[CURRENT_GENERIC_SPELL] && (!spell_id || m_currentSpells[CURRENT_GENERIC_SPELL]->m_spellInfo->Id == spell_id))
InterruptSpell(CURRENT_GENERIC_SPELL, withDelayed, withInstant, bySelf);
// autorepeat spells are interrupted if they are not finished or delayed
if (m_currentSpells[CURRENT_AUTOREPEAT_SPELL] && (!spell_id || m_currentSpells[CURRENT_AUTOREPEAT_SPELL]->m_spellInfo->Id == spell_id))
InterruptSpell(CURRENT_AUTOREPEAT_SPELL, withDelayed, withInstant, bySelf);
// channeled spells are interrupted if they are not finished, even if they are delayed
if (m_currentSpells[CURRENT_CHANNELED_SPELL] && (!spell_id || m_currentSpells[CURRENT_CHANNELED_SPELL]->m_spellInfo->Id == spell_id))
InterruptSpell(CURRENT_CHANNELED_SPELL, true, true, bySelf);
}
Spell* Unit::FindCurrentSpellBySpellId(uint32 spell_id) const
{
for (uint32 i = 0; i < CURRENT_MAX_SPELL; i++)
if (m_currentSpells[i] && m_currentSpells[i]->m_spellInfo->Id == spell_id)
return m_currentSpells[i];
return NULL;
}
int32 Unit::GetCurrentSpellCastTime(uint32 spell_id) const
{
if (Spell const* spell = FindCurrentSpellBySpellId(spell_id))
return spell->GetCastTime();
return 0;
}
bool Unit::isInFrontInMap(Unit const* target, float distance, float arc) const
{
return IsWithinDistInMap(target, distance) && HasInArc(arc, target);
}
bool Unit::isInBackInMap(Unit const* target, float distance, float arc) const
{
return IsWithinDistInMap(target, distance) && !HasInArc(2 * M_PI - arc, target);
}
bool Unit::isInAccessiblePlaceFor(Creature const* c) const
{
if (c->GetMapId() == 618) // Ring of Valor
{
// skip transport check, check for being below floor level
if (this->GetPositionZ() < 28.0f)
return false;
if (BattlegroundMap* bgMap = c->GetMap()->ToBattlegroundMap())
if (Battleground* bg = bgMap->GetBG())
if (bg->GetStartTime() < 80133) // 60000ms preparation time + 20133ms elevator rise time
return false;
}
else if (c->GetMapId() == 631) // Icecrown Citadel
{
// if static transport doesn't match - return false
if (c->GetTransport() != this->GetTransport() &&
((c->GetTransport() && c->GetTransport()->IsStaticTransport()) ||
(this->GetTransport() && this->GetTransport()->IsStaticTransport())))
return false;
// special handling for ICC (map 631), for non-flying pets in Gunship Battle, for trash npcs this is done via CanAIAttack
if (IS_PLAYER_GUID(c->GetOwnerGUID()) && !c->CanFly())
{
if ((c->GetTransport() && !this->GetTransport()) ||
(!c->GetTransport() && this->GetTransport()))
return false;
if (this->GetTransport())
{
if (c->GetPositionY() < 2033.0f)
{
if (this->GetPositionY() > 2033.0f)
return false;
}
else if (c->GetPositionY() < 2438.0f)
{
if (this->GetPositionY() < 2033.0f || this->GetPositionY() > 2438.0f)
return false;
}
else if (this->GetPositionY() < 2438.0f)
return false;
}
}
}
else
{
// pussywizard: prevent any bugs by passengers exiting transports or normal creatures flying away
if (c->GetTransport() != this->GetTransport())
return false;
}
if (IsInWater())
return IsUnderWater() ? c->CanSwim() : (c->CanSwim() || c->CanFly());
else
return c->CanWalk() || c->CanFly() || (c->CanSwim() && IsInWater(true));
}
void Unit::UpdateEnvironmentIfNeeded(const uint8 option)
{
if (m_is_updating_environment)
return;
if (GetTypeId() != TYPEID_UNIT || !IsAlive() || (!IsInWorld() && option != 3) || !FindMap() || IsDuringRemoveFromWorld() || !IsPositionValid())
return;
if (option <= 2 && GetMotionMaster()->GetCleanFlags() != MMCF_NONE)
{
if (option == 2)
m_last_environment_position.Relocate(-5000.0f, -5000.0f, -5000.0f, 0.0f);
return;
}
if (option <= 1 && GetExactDistSq(&m_last_environment_position) < 2.5f*2.5f)
return;
m_last_environment_position.Relocate(GetPositionX(), GetPositionY(), GetPositionZ());
m_is_updating_environment = true;
bool changed = false;
Creature* c = this->ToCreature();
Map* baseMap = const_cast