aboutsummaryrefslogtreecommitdiff
path: root/src/server/game/Spells
diff options
context:
space:
mode:
authorShauren <shauren.trinity@gmail.com>2015-02-17 01:01:44 +0100
committerShauren <shauren.trinity@gmail.com>2015-02-17 01:01:44 +0100
commit56186319bdd41dd26b6cc14f84e6e41eef5d953b (patch)
tree7b531cdf71b59170f9fa65120c09935ce51f3307 /src/server/game/Spells
parent7829412416c9709991fd686030ab77908c27922b (diff)
Core/Spells: Cooldown updates
* Refactored cooldown handling to separate class shared by creatures and players * Updated and enabled cooldown packets * Implemented creature school lockouts * Implemented spell charges * Fixed AuraUpdate structure * Fixed aura flag AFLAG_NOCASTER handling * Implemented spell charge related auras
Diffstat (limited to 'src/server/game/Spells')
-rw-r--r--src/server/game/Spells/Auras/SpellAuraDefines.h16
-rw-r--r--src/server/game/Spells/Auras/SpellAuraEffects.cpp23
-rw-r--r--src/server/game/Spells/Auras/SpellAuras.cpp39
-rw-r--r--src/server/game/Spells/Auras/SpellAuras.h2
-rw-r--r--src/server/game/Spells/Spell.cpp85
-rw-r--r--src/server/game/Spells/Spell.h1
-rw-r--r--src/server/game/Spells/SpellEffects.cpp15
-rw-r--r--src/server/game/Spells/SpellHistory.cpp854
-rw-r--r--src/server/game/Spells/SpellHistory.h155
-rw-r--r--src/server/game/Spells/SpellInfo.cpp1
-rw-r--r--src/server/game/Spells/SpellInfo.h1
11 files changed, 1094 insertions, 98 deletions
diff --git a/src/server/game/Spells/Auras/SpellAuraDefines.h b/src/server/game/Spells/Auras/SpellAuraDefines.h
index 42d2ec72c8e..112893fbc75 100644
--- a/src/server/game/Spells/Auras/SpellAuraDefines.h
+++ b/src/server/game/Spells/Auras/SpellAuraDefines.h
@@ -468,7 +468,7 @@ enum AuraType
SPELL_AURA_408 = 408,
SPELL_AURA_409 = 409,
SPELL_AURA_410 = 410,
- SPELL_AURA_MOD_CHARGES = 411, // NYI
+ SPELL_AURA_MOD_MAX_CHARGES = 411,
SPELL_AURA_412 = 412,
SPELL_AURA_413 = 413,
SPELL_AURA_414 = 414,
@@ -508,19 +508,19 @@ enum AuraType
SPELL_AURA_448 = 448,
SPELL_AURA_449 = 449,
SPELL_AURA_450 = 450,
- SPELL_AURA_451 = 451,
+ SPELL_AURA_OVERRIDE_PET_SPECS = 451, // NYI
SPELL_AURA_452 = 452,
- SPELL_AURA_453 = 453,
- SPELL_AURA_454 = 454,
+ SPELL_AURA_CHARGE_RECOVERY_MOD = 453,
+ SPELL_AURA_CHARGE_RECOVERY_MULTIPLIER = 454,
SPELL_AURA_455 = 455,
- SPELL_AURA_456 = 456,
- SPELL_AURA_457 = 457,
- SPELL_AURA_458 = 458,
+ SPELL_AURA_CHARGE_RECOVERY_AFFECTED_BY_HASTE = 456,
+ SPELL_AURA_CHARGE_RECOVERY_AFFECTED_BY_HASTE_REGEN = 457,
+ SPELL_AURA_IGNORE_DUAL_WIELD_HIT_PENALTY = 458, // NYI
SPELL_AURA_459 = 459,
SPELL_AURA_460 = 460,
SPELL_AURA_461 = 461,
SPELL_AURA_462 = 462,
- SPELL_AURA_463 = 463,
+ SPELL_AURA_CONVER_CRIT_RATING_PCT_TO_PARRY_RATING = 463, // NYI
SPELL_AURA_464 = 464,
SPELL_AURA_465 = 465,
SPELL_AURA_466 = 466,
diff --git a/src/server/game/Spells/Auras/SpellAuraEffects.cpp b/src/server/game/Spells/Auras/SpellAuraEffects.cpp
index b4c27f0214b..24d142da1a3 100644
--- a/src/server/game/Spells/Auras/SpellAuraEffects.cpp
+++ b/src/server/game/Spells/Auras/SpellAuraEffects.cpp
@@ -42,6 +42,7 @@
#include "Pet.h"
#include "ReputationMgr.h"
#include "MiscPackets.h"
+#include "SpellHistory.h"
class Aura;
//
@@ -470,7 +471,7 @@ pAuraEffectHandler AuraEffectHandler[TOTAL_AURAS]=
&AuraEffect::HandleNULL, //408
&AuraEffect::HandleNULL, //409
&AuraEffect::HandleNULL, //410
- &AuraEffect::HandleNULL, //411 SPELL_AURA_MOD_CHARGES
+ &AuraEffect::HandleNoImmediateEffect, //411 SPELL_AURA_MOD_MAX_CHARGES implemented in SpellHistory::GetMaxCharges
&AuraEffect::HandleNULL, //412
&AuraEffect::HandleNULL, //413
&AuraEffect::HandleNULL, //414
@@ -510,19 +511,19 @@ pAuraEffectHandler AuraEffectHandler[TOTAL_AURAS]=
&AuraEffect::HandleNULL, //448
&AuraEffect::HandleNULL, //449
&AuraEffect::HandleNULL, //450
- &AuraEffect::HandleNULL, //451
+ &AuraEffect::HandleNULL, //451 SPELL_AURA_OVERRIDE_PET_SPECS
&AuraEffect::HandleNULL, //452
- &AuraEffect::HandleNULL, //453
- &AuraEffect::HandleNULL, //454
+ &AuraEffect::HandleNoImmediateEffect, //453 SPELL_AURA_CHARGE_RECOVERY_MOD implemented in SpellHistory::GetChargeRecoveryTime
+ &AuraEffect::HandleNoImmediateEffect, //454 SPELL_AURA_CHARGE_RECOVERY_MULTIPLIER implemented in SpellHistory::GetChargeRecoveryTime
&AuraEffect::HandleNULL, //455
- &AuraEffect::HandleNULL, //456
- &AuraEffect::HandleNULL, //457
- &AuraEffect::HandleNULL, //458
+ &AuraEffect::HandleNoImmediateEffect, //456 SPELL_AURA_CHARGE_RECOVERY_AFFECTED_BY_HASTE implemented in SpellHistory::GetChargeRecoveryTime
+ &AuraEffect::HandleNoImmediateEffect, //457 SPELL_AURA_CHARGE_RECOVERY_AFFECTED_BY_HASTE_REGEN implemented in SpellHistory::GetChargeRecoveryTime
+ &AuraEffect::HandleNULL, //458 SPELL_AURA_IGNORE_DUAL_WIELD_HIT_PENALTY
&AuraEffect::HandleNULL, //459
&AuraEffect::HandleNULL, //460
&AuraEffect::HandleNULL, //461
&AuraEffect::HandleNULL, //462
- &AuraEffect::HandleNULL, //463
+ &AuraEffect::HandleNULL, //463 SPELL_AURA_CRIT_RATING_AFFECTS_PARRY used by Riposte
&AuraEffect::HandleNULL, //464
&AuraEffect::HandleNULL, //465
&AuraEffect::HandleNULL, //466
@@ -1321,15 +1322,13 @@ void AuraEffect::HandleShapeshiftBoosts(Unit* target, bool apply) const
// Remove cooldown of spells triggered on stance change - they may share cooldown with stance spell
if (spellId)
{
- if (target->GetTypeId() == TYPEID_PLAYER)
- target->ToPlayer()->RemoveSpellCooldown(spellId);
+ target->GetSpellHistory()->ResetCooldown(spellId);
target->CastSpell(target, spellId, true, NULL, this);
}
if (spellId2)
{
- if (target->GetTypeId() == TYPEID_PLAYER)
- target->ToPlayer()->RemoveSpellCooldown(spellId2);
+ target->GetSpellHistory()->ResetCooldown(spellId2);
target->CastSpell(target, spellId2, true, NULL, this);
}
diff --git a/src/server/game/Spells/Auras/SpellAuras.cpp b/src/server/game/Spells/Auras/SpellAuras.cpp
index 4e239f8a373..cd3b22dfa11 100644
--- a/src/server/game/Spells/Auras/SpellAuras.cpp
+++ b/src/server/game/Spells/Auras/SpellAuras.cpp
@@ -26,6 +26,7 @@
#include "Unit.h"
#include "Spell.h"
#include "SpellAuraEffects.h"
+#include "SpellHistory.h"
#include "SpellPackets.h"
#include "DynamicObject.h"
#include "ObjectAccessor.h"
@@ -117,7 +118,7 @@ void AuraApplication::_Remove()
void AuraApplication::_InitFlags(Unit* caster, uint32 effMask)
{
// mark as selfcast if needed
- _flags |= (GetBase()->GetCasterGUID() == GetTarget()->GetGUID()) ? AFLAG_NONE : AFLAG_NOCASTER;
+ _flags |= (GetBase()->GetCasterGUID() == GetTarget()->GetGUID()) ? AFLAG_NOCASTER : AFLAG_NONE;
// aura is cast by self or an enemy
// one negative effect and we know aura is negative
@@ -150,7 +151,10 @@ void AuraApplication::_InitFlags(Unit* caster, uint32 effMask)
_flags |= positiveFound ? AFLAG_POSITIVE : AFLAG_NEGATIVE;
}
- if (GetBase()->GetSpellInfo()->AttributesEx8 & SPELL_ATTR8_AURA_SEND_AMOUNT)
+ if (GetBase()->GetSpellInfo()->AttributesEx8 & SPELL_ATTR8_AURA_SEND_AMOUNT ||
+ GetBase()->HasEffectType(SPELL_AURA_MOD_MAX_CHARGES) ||
+ GetBase()->HasEffectType(SPELL_AURA_CHARGE_RECOVERY_MOD) ||
+ GetBase()->HasEffectType(SPELL_AURA_CHARGE_RECOVERY_MULTIPLIER))
_flags |= AFLAG_SCALABLE;
}
@@ -182,6 +186,7 @@ void AuraApplication::_HandleEffect(uint8 effIndex, bool apply)
// Remove all triggered by aura spells vs unlimited duration
aurEff->CleanupTriggeredSpells(GetTarget());
}
+
SetNeedClientUpdate();
}
@@ -218,10 +223,10 @@ void AuraApplication::BuildUpdatePacket(WorldPackets::Spells::AuraInfo& auraInfo
if (auraData.Flags & AFLAG_SCALABLE)
{
- auraData.Points.reserve(aura->GetAuraEffects().size());
+ auraData.Points.resize(aura->GetAuraEffects().size(), 0.0f);
for (AuraEffect const* effect : GetBase()->GetAuraEffects())
if (effect && HasEffect(effect->GetEffIndex())) // Not all of aura's effects have to be applied on every target
- auraData.Points.push_back(float(effect->GetAmount()));
+ auraData.Points[effect->GetEffIndex()] = float(effect->GetAmount());
}
auraInfo.AuraData.Set(auraData);
@@ -462,7 +467,7 @@ void Aura::_ApplyForTarget(Unit* target, Unit* caster, AuraApplication * auraApp
if (m_spellInfo->IsCooldownStartedOnEvent())
{
Item* castItem = !m_castItemGuid.IsEmpty() ? caster->ToPlayer()->GetItemByGuid(m_castItemGuid) : NULL;
- caster->ToPlayer()->AddSpellAndCategoryCooldowns(m_spellInfo, castItem ? castItem->GetEntry() : 0, NULL, true);
+ caster->GetSpellHistory()->StartCooldown(m_spellInfo, castItem ? castItem->GetEntry() : 0, nullptr, true);
}
}
}
@@ -490,12 +495,9 @@ void Aura::_UnapplyForTarget(Unit* target, Unit* caster, AuraApplication * auraA
m_removedApplications.push_back(auraApp);
// reset cooldown state for spells
- if (caster && caster->GetTypeId() == TYPEID_PLAYER)
- {
- if (GetSpellInfo()->IsCooldownStartedOnEvent())
- // note: item based cooldowns and cooldown spell mods with charges ignored (unknown existed cases)
- caster->ToPlayer()->SendCooldownEvent(GetSpellInfo());
- }
+ if (caster && GetSpellInfo()->IsCooldownStartedOnEvent())
+ // note: item based cooldowns and cooldown spell mods with charges ignored (unknown existed cases)
+ caster->GetSpellHistory()->SendCooldownEvent(GetSpellInfo());
}
// removes aura from all targets
@@ -1055,7 +1057,12 @@ bool Aura::CanBeSaved() const
bool Aura::CanBeSentToClient() const
{
- return !IsPassive() || GetSpellInfo()->HasAreaAuraEffect(GetOwner() ? GetOwner()->GetMap()->GetDifficultyID() : DIFFICULTY_NONE) || HasEffectType(SPELL_AURA_ABILITY_IGNORE_AURASTATE) || HasEffectType(SPELL_AURA_CAST_WHILE_WALKING);
+ return !IsPassive() || GetSpellInfo()->HasAreaAuraEffect(GetOwner() ? GetOwner()->GetMap()->GetDifficultyID() : DIFFICULTY_NONE)
+ || HasEffectType(SPELL_AURA_ABILITY_IGNORE_AURASTATE)
+ || HasEffectType(SPELL_AURA_CAST_WHILE_WALKING)
+ || HasEffectType(SPELL_AURA_MOD_MAX_CHARGES)
+ || HasEffectType(SPELL_AURA_CHARGE_RECOVERY_MOD)
+ || HasEffectType(SPELL_AURA_CHARGE_RECOVERY_MULTIPLIER);
}
bool Aura::IsSingleTargetWith(Aura const* aura) const
@@ -1310,7 +1317,7 @@ void Aura::HandleAuraSpecificMods(AuraApplication const* aurApp, Unit* caster, b
break;
case 60970: // Heroic Fury (remove Intercept cooldown)
if (target->GetTypeId() == TYPEID_PLAYER)
- target->ToPlayer()->RemoveSpellCooldown(20252, true);
+ target->GetSpellHistory()->ResetCooldown(20252, true);
break;
}
break;
@@ -1470,15 +1477,15 @@ void Aura::HandleAuraSpecificMods(AuraApplication const* aurApp, Unit* caster, b
// check cooldown
if (caster->GetTypeId() == TYPEID_PLAYER)
{
- if (caster->ToPlayer()->HasSpellCooldown(aura->GetId()))
+ if (caster->GetSpellHistory()->HasCooldown(aura->GetId()))
{
// This additional check is needed to add a minimal delay before cooldown in in effect
// to allow all bubbles broken by a single damage source proc mana return
- if (caster->ToPlayer()->GetSpellCooldownDelay(aura->GetId()) <= 11)
+ if (caster->GetSpellHistory()->GetRemainingCooldown(aura->GetId()) <= 11)
break;
}
else // and add if needed
- caster->ToPlayer()->AddSpellCooldown(aura->GetId(), 0, uint32(time(NULL) + 12));
+ caster->GetSpellHistory()->AddCooldown(aura->GetId(), 0, std::chrono::seconds(12));
}
// effect on caster
diff --git a/src/server/game/Spells/Auras/SpellAuras.h b/src/server/game/Spells/Auras/SpellAuras.h
index 3a05da9cd1e..8710f312dda 100644
--- a/src/server/game/Spells/Auras/SpellAuras.h
+++ b/src/server/game/Spells/Auras/SpellAuras.h
@@ -79,7 +79,7 @@ class AuraApplication
uint32 GetEffectMask() const { return _effectMask; }
bool HasEffect(uint8 effect) const { ASSERT(effect < MAX_SPELL_EFFECTS); return (_effectMask & (1 << effect)) != 0; }
bool IsPositive() const { return (_flags & AFLAG_POSITIVE) != 0; }
- bool IsSelfcast() const { return (_flags & AFLAG_NOCASTER) == 0; }
+ bool IsSelfcast() const { return (_flags & AFLAG_NOCASTER) != 0; }
uint8 GetEffectsToApply() const { return _effectsToApply; }
void SetRemoveMode(AuraRemoveMode mode) { _removeMode = mode; }
diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp
index 760ae42bbfd..3279e524b0c 100644
--- a/src/server/game/Spells/Spell.cpp
+++ b/src/server/game/Spells/Spell.cpp
@@ -58,6 +58,7 @@
#include "Battlefield.h"
#include "BattlefieldMgr.h"
#include "SpellPackets.h"
+#include "SpellHistory.h"
extern pEffect SpellEffects[TOTAL_SPELL_EFFECTS];
@@ -3318,7 +3319,7 @@ void Spell::cast(bool skipCheck)
//Clear spell cooldowns after every spell is cast if .cheat cooldown is enabled.
if (m_caster->ToPlayer()->GetCommandStatus(CHEAT_COOLDOWN))
- m_caster->ToPlayer()->RemoveSpellCooldown(m_spellInfo->Id, true);
+ m_caster->GetSpellHistory()->ResetCooldown(m_spellInfo->Id, true);
}
SetExecutedCurrently(false);
@@ -3521,30 +3522,7 @@ void Spell::_handle_finish_phase()
void Spell::SendSpellCooldown()
{
- Player* _player = m_caster->ToPlayer();
- if (!_player)
- {
- // Handle pet cooldowns here if needed instead of in PetAI to avoid hidden cooldown restarts
- Creature* _creature = m_caster->ToCreature();
- if (_creature && (_creature->IsPet() || _creature->IsGuardian()))
- _creature->AddCreatureSpellCooldown(m_spellInfo->Id);
-
- return;
- }
-
- // mana/health/etc potions, disabled by client (until combat out as declarate)
- if (m_CastItem && (m_CastItem->IsPotion() || m_spellInfo->IsCooldownStartedOnEvent()))
- {
- // need in some way provided data for Spell::finish SendCooldownEvent
- _player->SetLastPotionId(m_CastItem->GetEntry());
- return;
- }
-
- // have infinity cooldown but set at aura apply // do not set cooldown for triggered spells (needed by reincarnation)
- if (m_spellInfo->IsCooldownStartedOnEvent() || m_spellInfo->IsPassive() || (_triggeredCastFlags & TRIGGERED_IGNORE_SPELL_AND_CATEGORY_CD))
- return;
-
- _player->AddSpellAndCategoryCooldowns(m_spellInfo, m_CastItem ? m_CastItem->GetEntry() : 0, this);
+ m_caster->GetSpellHistory()->HandleCooldowns(m_spellInfo, m_CastItem, this);
}
void Spell::update(uint32 difftime)
@@ -4809,23 +4787,26 @@ SpellCastResult Spell::CheckCast(bool strict)
return SPELL_FAILED_CASTER_DEAD;
// check cooldowns to prevent cheating
- if (m_caster->GetTypeId() == TYPEID_PLAYER && !m_spellInfo->HasAttribute(SPELL_ATTR0_PASSIVE))
+ if (!m_spellInfo->IsPassive())
{
- //can cast triggered (by aura only?) spells while have this flag
- if (!(_triggeredCastFlags & TRIGGERED_IGNORE_CASTER_AURASTATE) && m_caster->ToPlayer()->HasFlag(PLAYER_FLAGS, PLAYER_ALLOW_ONLY_ABILITY))
- return SPELL_FAILED_SPELL_IN_PROGRESS;
+ if (m_caster->GetTypeId() == TYPEID_PLAYER)
+ {
+ //can cast triggered (by aura only?) spells while have this flag
+ if (!(_triggeredCastFlags & TRIGGERED_IGNORE_CASTER_AURASTATE) && m_caster->ToPlayer()->HasFlag(PLAYER_FLAGS, PLAYER_ALLOW_ONLY_ABILITY))
+ return SPELL_FAILED_SPELL_IN_PROGRESS;
- if (m_caster->ToPlayer()->HasSpellCooldown(m_spellInfo->Id))
+ // check if we are using a potion in combat for the 2nd+ time. Cooldown is added only after caster gets out of combat
+ if (m_caster->ToPlayer()->GetLastPotionId() && m_CastItem && (m_CastItem->IsPotion() || m_spellInfo->IsCooldownStartedOnEvent()))
+ return SPELL_FAILED_NOT_READY;
+ }
+
+ if (!m_caster->GetSpellHistory()->IsReady(m_spellInfo))
{
if (m_triggeredByAuraSpell)
return SPELL_FAILED_DONT_REPORT;
else
return SPELL_FAILED_NOT_READY;
}
-
- // check if we are using a potion in combat for the 2nd+ time. Cooldown is added only after caster gets out of combat
- if (m_caster->ToPlayer()->GetLastPotionId() && m_CastItem && (m_CastItem->IsPotion() || m_spellInfo->IsCooldownStartedOnEvent()))
- return SPELL_FAILED_NOT_READY;
}
if (m_spellInfo->HasAttribute(SPELL_ATTR7_IS_CHEAT_SPELL) && !m_caster->HasFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_ALLOW_CHEAT_SPELLS))
@@ -5510,7 +5491,7 @@ SpellCastResult Spell::CheckCast(bool strict)
TalentEntry const* talent = sTalentStore.LookupEntry(m_misc.TalentId);
if (!talent)
return SPELL_FAILED_DONT_REPORT;
- if (m_caster->ToPlayer()->HasSpellCooldown(talent->SpellID))
+ if (m_caster->GetSpellHistory()->HasCooldown(talent->SpellID))
return SPELL_FAILED_CANT_UNTALENT;
break;
}
@@ -5695,13 +5676,13 @@ SpellCastResult Spell::CheckPetCast(Unit* target)
}
// cooldown
- if (Creature const* creatureCaster = m_caster->ToCreature())
- if (creatureCaster->HasSpellCooldown(m_spellInfo->Id))
+ if (Creature* creatureCaster = m_caster->ToCreature())
+ if (!creatureCaster->GetSpellHistory()->IsReady(m_spellInfo))
return SPELL_FAILED_NOT_READY;
// Check if spell is affected by GCD
if (m_spellInfo->StartRecoveryCategory > 0)
- if (m_caster->GetCharmInfo() && m_caster->GetCharmInfo()->GetGlobalCooldownMgr().HasGlobalCooldown(m_spellInfo))
+ if (m_caster->GetCharmInfo() && m_caster->GetSpellHistory()->HasGlobalCooldown(m_spellInfo))
return SPELL_FAILED_NOT_READY;
return CheckCast(true);
@@ -7415,13 +7396,11 @@ enum GCDLimits
bool Spell::HasGlobalCooldown() const
{
- // Only player or controlled units have global cooldown
- if (m_caster->GetCharmInfo())
- return m_caster->GetCharmInfo()->GetGlobalCooldownMgr().HasGlobalCooldown(m_spellInfo);
- else if (m_caster->GetTypeId() == TYPEID_PLAYER)
- return m_caster->ToPlayer()->GetGlobalCooldownMgr().HasGlobalCooldown(m_spellInfo);
- else
+ // Only players or controlled units have global cooldown
+ if (m_caster->GetTypeId() != TYPEID_PLAYER && !m_caster->GetCharmInfo())
return false;
+
+ return m_caster->GetSpellHistory()->HasGlobalCooldown(m_spellInfo);
}
void Spell::TriggerGlobalCooldown()
@@ -7430,6 +7409,10 @@ void Spell::TriggerGlobalCooldown()
if (!gcd)
return;
+ // Only players or controlled units have global cooldown
+ if (m_caster->GetTypeId() != TYPEID_PLAYER && !m_caster->GetCharmInfo())
+ return;
+
if (m_caster->GetTypeId() == TYPEID_PLAYER)
if (m_caster->ToPlayer()->GetCommandStatus(CHEAT_COOLDOWN))
return;
@@ -7451,11 +7434,7 @@ void Spell::TriggerGlobalCooldown()
gcd = MAX_GCD;
}
- // Only players or controlled units have global cooldown
- if (m_caster->GetCharmInfo())
- m_caster->GetCharmInfo()->GetGlobalCooldownMgr().AddGlobalCooldown(m_spellInfo, gcd);
- else if (m_caster->GetTypeId() == TYPEID_PLAYER)
- m_caster->ToPlayer()->GetGlobalCooldownMgr().AddGlobalCooldown(m_spellInfo, gcd);
+ m_caster->GetSpellHistory()->AddGlobalCooldown(m_spellInfo, gcd);
}
void Spell::CancelGlobalCooldown()
@@ -7468,10 +7447,10 @@ void Spell::CancelGlobalCooldown()
return;
// Only players or controlled units have global cooldown
- if (m_caster->GetCharmInfo())
- m_caster->GetCharmInfo()->GetGlobalCooldownMgr().CancelGlobalCooldown(m_spellInfo);
- else if (m_caster->GetTypeId() == TYPEID_PLAYER)
- m_caster->ToPlayer()->GetGlobalCooldownMgr().CancelGlobalCooldown(m_spellInfo);
+ if (m_caster->GetTypeId() != TYPEID_PLAYER && !m_caster->GetCharmInfo())
+ return;
+
+ m_caster->GetSpellHistory()->CancelGlobalCooldown(m_spellInfo);
}
bool Spell::HasEffect(SpellEffectName effect) const
diff --git a/src/server/game/Spells/Spell.h b/src/server/game/Spells/Spell.h
index e751f9915cd..42c5258f4f8 100644
--- a/src/server/game/Spells/Spell.h
+++ b/src/server/game/Spells/Spell.h
@@ -498,6 +498,7 @@ class Spell
void ReSetTimer() { m_timer = m_casttime > 0 ? m_casttime : 0; }
bool IsNextMeleeSwingSpell() const;
bool IsTriggered() const { return (_triggeredCastFlags & TRIGGERED_FULL_MASK) != 0; }
+ bool IsIgnoringCooldowns() const { return (_triggeredCastFlags & TRIGGERED_IGNORE_SPELL_AND_CATEGORY_CD) != 0; }
bool IsChannelActive() const { return m_caster->GetUInt32Value(UNIT_CHANNEL_SPELL) != 0; }
bool IsAutoActionResetSpell() const;
diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp
index 73217e6a9e3..c08b3e6ec23 100644
--- a/src/server/game/Spells/SpellEffects.cpp
+++ b/src/server/game/Spells/SpellEffects.cpp
@@ -32,6 +32,7 @@
#include "DynamicObject.h"
#include "SpellAuras.h"
#include "SpellAuraEffects.h"
+#include "SpellHistory.h"
#include "Group.h"
#include "UpdateData.h"
#include "MapManager.h"
@@ -672,9 +673,7 @@ void Spell::EffectTriggerSpell(SpellEffIndex /*effIndex*/)
return;
// Reset cooldown on stealth if needed
- if (unitTarget->ToPlayer()->HasSpellCooldown(1784))
- unitTarget->ToPlayer()->RemoveSpellCooldown(1784);
-
+ unitTarget->GetSpellHistory()->ResetCooldown(1784);
unitTarget->CastSpell(unitTarget, 1784, true);
return;
}
@@ -778,7 +777,7 @@ void Spell::EffectTriggerSpell(SpellEffIndex /*effIndex*/)
// Remove spell cooldown (not category) if spell triggering spell with cooldown and same category
if (m_caster->GetTypeId() == TYPEID_PLAYER && m_spellInfo->CategoryRecoveryTime && spellInfo->CategoryRecoveryTime
&& m_spellInfo->GetCategory() == spellInfo->GetCategory())
- m_caster->ToPlayer()->RemoveSpellCooldown(spellInfo->Id);
+ m_caster->GetSpellHistory()->ResetCooldown(spellInfo->Id);
// original caster guid only for GO cast
m_caster->CastSpell(targets, spellInfo, &values, TRIGGERED_FULL_MASK, NULL, NULL, m_originalCasterGUID);
@@ -831,7 +830,7 @@ void Spell::EffectTriggerMissileSpell(SpellEffIndex /*effIndex*/)
// Remove spell cooldown (not category) if spell triggering spell with cooldown and same category
if (m_caster->GetTypeId() == TYPEID_PLAYER && m_spellInfo->CategoryRecoveryTime && spellInfo->CategoryRecoveryTime
&& m_spellInfo->GetCategory() == spellInfo->GetCategory())
- m_caster->ToPlayer()->RemoveSpellCooldown(spellInfo->Id);
+ m_caster->GetSpellHistory()->ResetCooldown(spellInfo->Id);
// original caster guid only for GO cast
m_caster->CastSpell(targets, spellInfo, &values, TRIGGERED_FULL_MASK, NULL, NULL, m_originalCasterGUID);
@@ -3175,7 +3174,7 @@ void Spell::EffectInterruptCast(SpellEffIndex effIndex)
if (m_originalCaster)
{
int32 duration = m_spellInfo->GetDuration();
- unitTarget->ProhibitSpellSchool(curSpellInfo->GetSchoolMask(), unitTarget->ModSpellDuration(m_spellInfo, unitTarget, duration, false, 1 << effIndex));
+ unitTarget->GetSpellHistory()->LockSpellSchool(curSpellInfo->GetSchoolMask(), unitTarget->ModSpellDuration(m_spellInfo, unitTarget, duration, false, 1 << effIndex));
}
ExecuteLogEffectInterruptCast(effIndex, unitTarget, curSpellInfo->Id);
unitTarget->InterruptSpell(CurrentSpellTypes(i), false);
@@ -3944,7 +3943,7 @@ void Spell::EffectStuck(SpellEffIndex /*effIndex*/)
}
// the player dies if hearthstone is in cooldown, else the player is teleported to home
- if (player->HasSpellCooldown(8690))
+ if (player->GetSpellHistory()->HasCooldown(8690))
{
player->Kill(player);
return;
@@ -5616,7 +5615,7 @@ void Spell::EffectCastButtons(SpellEffIndex /*effIndex*/)
if (!spellInfo)
continue;
- if (!p_caster->HasSpell(spell_id) || p_caster->HasSpellCooldown(spell_id))
+ if (!p_caster->HasSpell(spell_id) || p_caster->GetSpellHistory()->HasCooldown(spell_id))
continue;
if (!spellInfo->HasAttribute(SPELL_ATTR9_SUMMON_PLAYER_TOTEM))
diff --git a/src/server/game/Spells/SpellHistory.cpp b/src/server/game/Spells/SpellHistory.cpp
new file mode 100644
index 00000000000..4060122630d
--- /dev/null
+++ b/src/server/game/Spells/SpellHistory.cpp
@@ -0,0 +1,854 @@
+/*
+ * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "SpellHistory.h"
+#include "Pet.h"
+#include "Player.h"
+#include "SpellInfo.h"
+#include "SpellPackets.h"
+#include "World.h"
+
+SpellHistory::Clock::duration const SpellHistory::InfinityCooldownDelay = std::chrono::duration_cast<SpellHistory::Clock::duration>(std::chrono::seconds(MONTH));
+
+template<>
+struct SpellHistory::PersistenceHelper<Player>
+{
+ static CharacterDatabaseStatements const CooldownsDeleteStatement = CHAR_DEL_CHAR_SPELL_COOLDOWNS;
+ static CharacterDatabaseStatements const CooldownsInsertStatement = CHAR_INS_CHAR_SPELL_COOLDOWN;
+ static CharacterDatabaseStatements const ChargesDeleteStatement = CHAR_DEL_CHAR_SPELL_CHARGES;
+ static CharacterDatabaseStatements const ChargesInsertStatement = CHAR_INS_CHAR_SPELL_CHARGES;
+
+ static void SetIdentifier(PreparedStatement* stmt, uint8 index, Unit* owner) { stmt->setUInt64(index, owner->GetGUID().GetCounter()); }
+
+ static bool ReadCooldown(Field* fields, uint32* spellId, CooldownEntry* cooldownEntry)
+ {
+ *spellId = fields[0].GetUInt32();
+ if (!sSpellMgr->GetSpellInfo(*spellId))
+ return false;
+
+ cooldownEntry->CooldownEnd = Clock::from_time_t(time_t(fields[2].GetUInt32()));
+ cooldownEntry->ItemId = fields[1].GetUInt32();
+ return true;
+ }
+
+ static bool ReadCharge(Field* fields, uint32* categoryId, ChargeEntry* chargeEntry)
+ {
+ *categoryId = fields[0].GetUInt32();
+ if (!sSpellCategoryStore.LookupEntry(*categoryId))
+ return false;
+
+ chargeEntry->RechargeStart = Clock::from_time_t(time_t(fields[1].GetUInt32()));
+ chargeEntry->RechargeEnd = Clock::from_time_t(time_t(fields[2].GetUInt32()));
+ return true;
+ }
+
+ static void WriteCooldown(PreparedStatement* stmt, uint8& index, CooldownStorageType::value_type const& cooldown)
+ {
+ stmt->setUInt32(index++, cooldown.first);
+ stmt->setUInt32(index++, cooldown.second.ItemId);
+ stmt->setUInt32(index++, uint32(Clock::to_time_t(cooldown.second.CooldownEnd)));
+ }
+
+ static void WriteCharge(PreparedStatement* stmt, uint8& index, uint32 chargeCategory, ChargeEntry const& charge)
+ {
+ stmt->setUInt32(index++, chargeCategory);
+ stmt->setUInt32(index++, uint32(Clock::to_time_t(charge.RechargeStart)));
+ stmt->setUInt32(index++, uint32(Clock::to_time_t(charge.RechargeEnd)));
+ }
+};
+
+template<>
+struct SpellHistory::PersistenceHelper<Pet>
+{
+ static CharacterDatabaseStatements const CooldownsDeleteStatement = CHAR_DEL_PET_SPELL_COOLDOWNS;
+ static CharacterDatabaseStatements const CooldownsInsertStatement = CHAR_INS_PET_SPELL_COOLDOWN;
+ static CharacterDatabaseStatements const ChargesDeleteStatement = CHAR_DEL_PET_SPELL_CHARGES;
+ static CharacterDatabaseStatements const ChargesInsertStatement = CHAR_INS_PET_SPELL_CHARGES;
+
+ static void SetIdentifier(PreparedStatement* stmt, uint8 index, Unit* owner) { stmt->setUInt32(index, owner->GetCharmInfo()->GetPetNumber()); }
+
+ static bool ReadCooldown(Field* fields, uint32* spellId, CooldownEntry* cooldownEntry)
+ {
+ *spellId = fields[0].GetUInt32();
+ if (!sSpellMgr->GetSpellInfo(*spellId))
+ return false;
+
+ cooldownEntry->CooldownEnd = Clock::from_time_t(time_t(fields[1].GetUInt32()));
+ cooldownEntry->ItemId = 0;
+ return true;
+ }
+
+ static bool ReadCharge(Field* fields, uint32* categoryId, ChargeEntry* chargeEntry)
+ {
+ *categoryId = fields[0].GetUInt32();
+ if (!sSpellCategoryStore.LookupEntry(*categoryId))
+ return false;
+
+ chargeEntry->RechargeStart = Clock::from_time_t(time_t(fields[1].GetUInt32()));
+ chargeEntry->RechargeEnd = Clock::from_time_t(time_t(fields[2].GetUInt32()));
+ return true;
+ }
+
+ static void WriteCooldown(PreparedStatement* stmt, uint8& index, CooldownStorageType::value_type const& cooldown)
+ {
+ stmt->setUInt32(index++, cooldown.first);
+ stmt->setUInt32(index++, uint32(Clock::to_time_t(cooldown.second.CooldownEnd)));
+ }
+
+ static void WriteCharge(PreparedStatement* stmt, uint8& index, uint32 chargeCategory, ChargeEntry const& charge)
+ {
+ stmt->setUInt32(index++, chargeCategory);
+ stmt->setUInt32(index++, uint32(Clock::to_time_t(charge.RechargeStart)));
+ stmt->setUInt32(index++, uint32(Clock::to_time_t(charge.RechargeEnd)));
+ }
+};
+
+template<class OwnerType>
+void SpellHistory::LoadFromDB(PreparedQueryResult cooldownsResult, PreparedQueryResult chargesResult)
+{
+ typedef PersistenceHelper<OwnerType> StatementInfo;
+
+ if (cooldownsResult)
+ {
+ do
+ {
+ uint32 spellId;
+ CooldownEntry cooldown;
+ if (StatementInfo::ReadCooldown(cooldownsResult->Fetch(), &spellId, &cooldown))
+ _spellCooldowns[spellId] = cooldown;
+
+ } while (cooldownsResult->NextRow());
+ }
+
+ if (chargesResult)
+ {
+ do
+ {
+ Field* fields = chargesResult->Fetch();
+ uint32 categoryId = 0;
+ ChargeEntry charges;
+ if (StatementInfo::ReadCharge(fields, &categoryId, &charges))
+ _categoryCharges[categoryId].push_back(charges);
+
+ } while (chargesResult->NextRow());
+ }
+}
+
+template<class OwnerType>
+void SpellHistory::SaveToDB(SQLTransaction& trans)
+{
+ typedef PersistenceHelper<OwnerType> StatementInfo;
+
+ uint8 index = 0;
+ PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(StatementInfo::CooldownsDeleteStatement);
+ StatementInfo::SetIdentifier(stmt, index++, _owner);
+ trans->Append(stmt);
+
+ for (auto const& p : _spellCooldowns)
+ {
+ if (!p.second.OnHold)
+ {
+ index = 0;
+ stmt = CharacterDatabase.GetPreparedStatement(StatementInfo::CooldownsInsertStatement);
+ StatementInfo::SetIdentifier(stmt, index++, _owner);
+ StatementInfo::WriteCooldown(stmt, index, p);
+ trans->Append(stmt);
+ }
+ }
+
+ stmt = CharacterDatabase.GetPreparedStatement(StatementInfo::ChargesDeleteStatement);
+ StatementInfo::SetIdentifier(stmt, 0, _owner);
+ trans->Append(stmt);
+
+ for (auto const& p : _categoryCharges)
+ {
+ for (ChargeEntry const& charge : p.second)
+ {
+ index = 0;
+ stmt = CharacterDatabase.GetPreparedStatement(StatementInfo::ChargesInsertStatement);
+ StatementInfo::SetIdentifier(stmt, index++, _owner);
+ StatementInfo::WriteCharge(stmt, index, p.first, charge);
+ trans->Append(stmt);
+ }
+ }
+}
+
+void SpellHistory::Update()
+{
+ SQLTransaction t;
+ Clock::time_point now = Clock::now();
+ for (auto itr = _spellCooldowns.begin(); itr != _spellCooldowns.end();)
+ {
+ if (itr->second.CooldownEnd < now)
+ itr = _spellCooldowns.erase(itr);
+ else
+ ++itr;
+ }
+
+ for (auto& p : _categoryCharges)
+ {
+ std::deque<ChargeEntry>& chargeRefreshTimes = p.second;
+ while (!chargeRefreshTimes.empty() && chargeRefreshTimes.front().RechargeEnd <= now)
+ chargeRefreshTimes.pop_front();
+ }
+}
+
+void SpellHistory::HandleCooldowns(SpellInfo const* spellInfo, Item const* item, Spell* spell /*= nullptr*/)
+{
+ if (ConsumeCharge(spellInfo->ChargeCategoryEntry))
+ return;
+
+ if (Player* player = _owner->ToPlayer())
+ {
+ // potions start cooldown until exiting combat
+ if (item && (item->IsPotion() || spellInfo->IsCooldownStartedOnEvent()))
+ {
+ player->SetLastPotionId(item->GetEntry());
+ return;
+ }
+ }
+
+ if (spellInfo->IsCooldownStartedOnEvent() || spellInfo->IsPassive() || (spell && spell->IsIgnoringCooldowns()))
+ return;
+
+ StartCooldown(spellInfo, item ? item->GetEntry() : 0, spell);
+}
+
+bool SpellHistory::IsReady(SpellInfo const* spellInfo) const
+{
+ if (spellInfo->PreventionType == SPELL_PREVENTION_TYPE_SILENCE)
+ if (IsSchoolLocked(spellInfo->GetSchoolMask()))
+ return false;
+
+ if (HasCooldown(spellInfo->Id))
+ return false;
+
+ if (!HasCharge(spellInfo->ChargeCategoryEntry))
+ return false;
+
+ return true;
+}
+
+template<class PacketType>
+void SpellHistory::WritePacket(PacketType* packet) const
+{
+ static_assert(!std::is_same<PacketType, PacketType>::value /*static_assert(false)*/, "This packet is not supported.");
+}
+
+template<>
+void SpellHistory::WritePacket(WorldPackets::Spells::SendSpellHistory* sendSpellHistory) const
+{
+ sendSpellHistory->Entries.reserve(_spellCooldowns.size());
+
+ Clock::time_point now = Clock::now();
+ for (auto const& p : _spellCooldowns)
+ {
+ SpellInfo const* spellInfo = sSpellMgr->AssertSpellInfo(p.first);
+ WorldPackets::Spells::SpellHistoryEntry historyEntry;
+ historyEntry.SpellID = p.first;
+ historyEntry.ItemID = p.second.ItemId;
+ historyEntry.Category = spellInfo->GetCategory();
+
+ if (p.second.OnHold)
+ historyEntry.OnHold = true;
+ else
+ {
+ std::chrono::milliseconds cooldownDuration = std::chrono::duration_cast<std::chrono::milliseconds>(p.second.CooldownEnd - now);
+ if (cooldownDuration.count() <= 0)
+ continue;
+
+ if (spellInfo->GetCategory())
+ historyEntry.CategoryRecoveryTime = uint32(cooldownDuration.count());
+ else
+ historyEntry.RecoveryTime = uint32(cooldownDuration.count());
+ }
+
+ sendSpellHistory->Entries.push_back(historyEntry);
+ }
+}
+
+template<>
+void SpellHistory::WritePacket(WorldPackets::Spells::SendSpellCharges* sendSpellCharges) const
+{
+ sendSpellCharges->Entries.reserve(_categoryCharges.size());
+
+ Clock::time_point now = Clock::now();
+ for (auto const& p : _categoryCharges)
+ {
+ if (!p.second.empty())
+ {
+ std::chrono::milliseconds cooldownDuration = std::chrono::duration_cast<std::chrono::milliseconds>(p.second.front().RechargeEnd - now);
+ if (cooldownDuration.count() <= 0)
+ continue;
+
+ WorldPackets::Spells::SpellChargeEntry chargeEntry;
+ chargeEntry.Category = p.first;
+ chargeEntry.NextRecoveryTime = uint32(cooldownDuration.count());
+ chargeEntry.ConsumedCharges = p.second.size();
+ sendSpellCharges->Entries.push_back(chargeEntry);
+ }
+ }
+}
+
+/*
+template<>
+void SpellHistory::WritePacket(WorldPackets::Pet::PetSpells* petSpells)
+{
+ Clock::time_point now = Clock::now();
+
+ petSpells->Cooldowns.reserve(_spellCooldowns.size());
+ for (auto const& p : _spellCooldowns)
+ {
+ SpellInfo const* spellInfo = sSpellMgr->AssertSpellInfo(p.first);
+ WorldPackets::Pet::PetSpellCooldown petSpellCooldown;
+ petSpellCooldown.SpellID = p.first;
+ petSpellCooldown.Category = spellInfo->GetCategory();
+
+ if (!p.second.OnHold)
+ {
+ std::chrono::milliseconds cooldownDuration = std::chrono::duration_cast<std::chrono::milliseconds>(p.second.CooldownEnd - now);
+ if (cooldownDuration.count() <= 0)
+ continue;
+
+ if (spellInfo->GetCategory())
+ petSpellCooldown.CategoryDuration = uint32(cooldownDuration.count());
+ else
+ petSpellCooldown.Duration = uint32(cooldownDuration.count());
+ }
+
+ petSpells->Cooldowns.push_back(historyEntry);
+ }
+
+ petSpells->SpellHistory.reserve(_categoryCharges.size());
+ for (auto const& p : _categoryCharges)
+ {
+ if (!p.second.empty())
+ {
+ std::chrono::milliseconds cooldownDuration = std::chrono::duration_cast<std::chrono::milliseconds>(p.second.front().RechargeEnd - now);
+ if (cooldownDuration.count() <= 0)
+ continue;
+
+ WorldPackets::Pet::PetSpellHistory petChargeEntry;
+ petChargeEntry.CategoryID = p.first;
+ petChargeEntry.RecoveryTime = uint32(cooldownDuration.count());
+ petChargeEntry.ConsumedCharges = p.second.size();
+
+ petSpells->SpellHistory.push_back(petChargeEntry);
+ }
+ }
+}
+*/
+
+void SpellHistory::StartCooldown(SpellInfo const* spellInfo, uint32 itemId, Spell* spell /*= nullptr*/, bool onHold /*= false*/)
+{
+ // init cooldown values
+ uint32 categoryId = 0;
+ int32 cooldown = -1;
+ int32 categoryCooldown = -1;
+
+ // some special item spells without correct cooldown in SpellInfo
+ // cooldown information stored in item prototype
+ if (itemId)
+ {
+ if (ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId))
+ {
+ for (uint8 idx = 0; idx < proto->Effects.size(); ++idx)
+ {
+ if (uint32(proto->Effects[idx].SpellID) == spellInfo->Id)
+ {
+ categoryId = proto->Effects[idx].Category;
+ cooldown = proto->Effects[idx].Cooldown;
+ categoryCooldown = proto->Effects[idx].CategoryCooldown;
+ break;
+ }
+ }
+ }
+ }
+
+ // if no cooldown found above then base at DBC data
+ if (cooldown < 0 && categoryCooldown < 0)
+ {
+ categoryId = spellInfo->GetCategory();
+ cooldown = spellInfo->RecoveryTime;
+ categoryCooldown = spellInfo->CategoryRecoveryTime;
+ }
+
+ Clock::time_point curTime = Clock::now();
+ Clock::time_point catrecTime;
+ Clock::time_point recTime;
+ bool needsCooldownPacket = false;
+
+ // overwrite time for selected category
+ if (onHold)
+ {
+ // use +MONTH as infinite cooldown marker
+ catrecTime = categoryCooldown > 0 ? (curTime + InfinityCooldownDelay) : curTime;
+ recTime = cooldown > 0 ? (curTime + InfinityCooldownDelay) : catrecTime;
+ }
+ else
+ {
+ // shoot spells used equipped item cooldown values already assigned in GetAttackTime(RANGED_ATTACK)
+ // prevent 0 cooldowns set by another way
+ if (cooldown <= 0 && categoryCooldown <= 0 && (categoryId == 76 || (spellInfo->IsAutoRepeatRangedSpell() && spellInfo->Id != 75)))
+ cooldown = _owner->GetAttackTime(RANGED_ATTACK);
+
+ // Now we have cooldown data (if found any), time to apply mods
+ if (Player* modOwner = _owner->GetSpellModOwner())
+ {
+ if (cooldown > 0)
+ modOwner->ApplySpellMod(spellInfo->Id, SPELLMOD_COOLDOWN, cooldown, spell);
+
+ if (categoryCooldown > 0 && !spellInfo->HasAttribute(SPELL_ATTR6_IGNORE_CATEGORY_COOLDOWN_MODS))
+ modOwner->ApplySpellMod(spellInfo->Id, SPELLMOD_COOLDOWN, categoryCooldown, spell);
+ }
+
+ if (int32 cooldownMod = _owner->GetTotalAuraModifier(SPELL_AURA_MOD_COOLDOWN))
+ {
+ // Apply SPELL_AURA_MOD_COOLDOWN only to own spells
+ Player* playerOwner = GetPlayerOwner();
+ if (!playerOwner || playerOwner->HasSpell(spellInfo->Id))
+ {
+ needsCooldownPacket = true;
+ cooldown += cooldownMod * IN_MILLISECONDS; // SPELL_AURA_MOD_COOLDOWN does not affect category cooldows, verified with shaman shocks
+ }
+ }
+
+ // Apply SPELL_AURA_MOD_SPELL_CATEGORY_COOLDOWN modifiers
+ // Note: This aura applies its modifiers to all cooldowns of spells with set category, not to category cooldown only
+ if (categoryId)
+ {
+ if (int32 categoryModifier = _owner->GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_SPELL_CATEGORY_COOLDOWN, categoryId))
+ {
+ if (cooldown > 0)
+ cooldown += categoryModifier;
+
+ if (categoryCooldown > 0)
+ categoryCooldown += categoryModifier;
+ }
+
+ SpellCategoryEntry const* categoryEntry = sSpellCategoryStore.AssertEntry(categoryId);
+ if (categoryEntry->Flags & SPELL_CATEGORY_FLAG_COOLDOWN_EXPIRES_AT_DAILY_RESET)
+ categoryCooldown = int32(std::chrono::duration_cast<std::chrono::milliseconds>(Clock::from_time_t(sWorld->GetNextDailyQuestsResetTime()) - Clock::now()).count());
+ }
+
+ // replace negative cooldowns by 0
+ if (cooldown < 0)
+ cooldown = 0;
+
+ if (categoryCooldown < 0)
+ categoryCooldown = 0;
+
+ // no cooldown after applying spell mods
+ if (cooldown == 0 && categoryCooldown == 0)
+ return;
+
+ catrecTime = categoryCooldown ? curTime + std::chrono::duration_cast<Clock::duration>(std::chrono::milliseconds(categoryCooldown)) : curTime;
+ recTime = cooldown ? curTime + std::chrono::duration_cast<Clock::duration>(std::chrono::milliseconds(cooldown)) : catrecTime;
+ }
+
+ // self spell cooldown
+ if (recTime != curTime)
+ {
+ AddCooldown(spellInfo->Id, itemId, recTime, onHold);
+
+ if (needsCooldownPacket)
+ {
+ if (Player* playerOwner = GetPlayerOwner())
+ {
+ WorldPackets::Spells::SpellCooldown spellCooldown;
+ spellCooldown.Caster = _owner->GetGUID();
+ spellCooldown.Flags = SPELL_COOLDOWN_FLAG_NONE;
+ spellCooldown.SpellCooldowns.emplace_back(spellInfo->Id, uint32(cooldown));
+ playerOwner->SendDirectMessage(spellCooldown.Write());
+ }
+ }
+ }
+
+ // category spells
+ if (categoryId && catrecTime != curTime)
+ {
+ SpellCategoryStore::const_iterator i_scstore = sSpellsByCategoryStore.find(categoryId);
+ if (i_scstore != sSpellsByCategoryStore.end())
+ {
+ for (SpellCategorySet::const_iterator i_scset = i_scstore->second.begin(); i_scset != i_scstore->second.end(); ++i_scset)
+ {
+ if (*i_scset == spellInfo->Id) // skip main spell, already handled above
+ continue;
+
+ AddCooldown(*i_scset, itemId, catrecTime, onHold);
+ }
+ }
+ }
+}
+
+void SpellHistory::SendCooldownEvent(SpellInfo const* spellInfo, uint32 itemId /*= 0*/, Spell* spell /*= nullptr*/, bool startCooldown /*= true*/)
+{
+ // start cooldowns at server side, if any
+ if (startCooldown)
+ StartCooldown(spellInfo, itemId, spell);
+
+ if (Player* player = GetPlayerOwner())
+ {
+ // Send activate cooldown timer (possible 0) at client side
+ player->SendDirectMessage(WorldPackets::Spells::CooldownEvent(_owner->GetGUID(), spellInfo->Id).Write());
+
+ uint32 category = spellInfo->GetCategory();
+ if (category && spellInfo->CategoryRecoveryTime)
+ {
+ SpellCategoryStore::const_iterator ct = sSpellsByCategoryStore.find(category);
+ if (ct != sSpellsByCategoryStore.end())
+ {
+ for (auto const& cooldownPair : _spellCooldowns)
+ {
+ uint32 categorySpell = cooldownPair.first;
+ if (!ct->second.count(categorySpell))
+ continue;
+
+ if (categorySpell == spellInfo->Id) // skip main spell, already handled above
+ continue;
+
+ SpellInfo const* spellInfo2 = sSpellMgr->AssertSpellInfo(categorySpell);
+ if (!spellInfo2->IsCooldownStartedOnEvent())
+ continue;
+
+ player->SendDirectMessage(WorldPackets::Spells::CooldownEvent(_owner->GetGUID(), categorySpell).Write());
+ }
+ }
+ }
+ }
+}
+
+void SpellHistory::AddCooldown(uint32 spellId, uint32 itemId, Clock::time_point cooldownEnd, bool onHold /*= false*/)
+{
+ CooldownEntry& cooldownEntry = _spellCooldowns[spellId];
+ cooldownEntry.CooldownEnd = cooldownEnd;
+ cooldownEntry.ItemId = itemId;
+ cooldownEntry.OnHold = onHold;
+}
+
+void SpellHistory::ModifyCooldown(uint32 spellId, int32 cooldownModMs)
+{
+ auto itr = _spellCooldowns.find(spellId);
+ if (!cooldownModMs || itr == _spellCooldowns.end())
+ return;
+
+ Clock::time_point now = Clock::now();
+ Clock::duration offset = std::chrono::duration_cast<Clock::duration>(std::chrono::milliseconds(cooldownModMs));
+ if (itr->second.CooldownEnd + offset > now)
+ itr->second.CooldownEnd += offset;
+ else
+ _spellCooldowns.erase(itr);
+
+ if (Player* playerOwner = GetPlayerOwner())
+ {
+ WorldPackets::Spells::ModifyCooldown modifyCooldown;
+ modifyCooldown.UnitGUID = _owner->GetGUID();
+ modifyCooldown.SpellID = spellId;
+ modifyCooldown.DeltaTime = cooldownModMs;
+ playerOwner->SendDirectMessage(modifyCooldown.Write());
+ }
+}
+
+void SpellHistory::ResetCooldown(uint32 spellId, bool update /*= false*/)
+{
+ auto itr = _spellCooldowns.find(spellId);
+ if (itr == _spellCooldowns.end())
+ return;
+
+ ResetCooldown(itr, update);
+}
+
+void SpellHistory::ResetCooldown(CooldownStorageType::iterator& itr, bool update /*= false*/)
+{
+ if (update)
+ {
+ if (Player* playerOwner = GetPlayerOwner())
+ {
+ WorldPackets::Spells::ClearCooldown clearCooldown;
+ clearCooldown.CasterGUID = _owner->GetGUID();
+ clearCooldown.SpellID = itr->first;
+ clearCooldown.ClearOnHold = false;
+ playerOwner->SendDirectMessage(clearCooldown.Write());
+ }
+ }
+
+ itr = _spellCooldowns.erase(itr);
+}
+
+void SpellHistory::ResetAllCooldowns()
+{
+ if (Player* playerOwner = GetPlayerOwner())
+ {
+ std::vector<int32> cooldowns;
+ cooldowns.reserve(_spellCooldowns.size());
+ for (auto const& p : _spellCooldowns)
+ cooldowns.push_back(p.first);
+
+ SendClearCooldowns(cooldowns);
+ }
+
+ _spellCooldowns.clear();
+}
+
+bool SpellHistory::HasCooldown(uint32 spellId) const
+{
+ return _spellCooldowns.count(spellId) != 0;
+}
+
+uint32 SpellHistory::GetRemainingCooldown(uint32 spellId) const
+{
+ auto itr = _spellCooldowns.find(spellId);
+ if (itr == _spellCooldowns.end())
+ return 0;
+
+ Clock::time_point now = Clock::now();
+ if (itr->second.CooldownEnd < now)
+ return 0;
+
+ Clock::duration remaining = itr->second.CooldownEnd - now;
+ return uint32(std::chrono::duration_cast<std::chrono::milliseconds>(remaining).count());
+}
+
+void SpellHistory::LockSpellSchool(SpellSchoolMask schoolMask, uint32 lockoutTime)
+{
+ Clock::time_point lockoutEnd = Clock::now() + std::chrono::duration_cast<Clock::duration>(std::chrono::milliseconds(lockoutTime));
+ for (uint32 i = 0; i < MAX_SPELL_SCHOOL; ++i)
+ if (SpellSchoolMask(1 << i) & schoolMask)
+ _schoolLockouts[i] = lockoutEnd;
+
+ std::set<uint32> knownSpells;
+ if (Player* plrOwner = _owner->ToPlayer())
+ {
+ for (auto const& p : plrOwner->GetSpellMap())
+ if (p.second->state != PLAYERSPELL_REMOVED)
+ knownSpells.insert(p.first);
+ }
+ else if (Pet* petOwner = _owner->ToPet())
+ {
+ for (auto const& p : petOwner->m_spells)
+ if (p.second.state != PLAYERSPELL_REMOVED)
+ knownSpells.insert(p.first);
+ }
+ else
+ {
+ Creature* creatureOwner = _owner->ToCreature();
+ for (uint8 i = 0; i < CREATURE_MAX_SPELLS; ++i)
+ if (creatureOwner->m_spells[i])
+ knownSpells.insert(creatureOwner->m_spells[i]);
+ }
+
+ WorldPackets::Spells::SpellCooldown spellCooldown;
+ spellCooldown.Caster = _owner->GetGUID();
+ spellCooldown.Flags = SPELL_COOLDOWN_FLAG_NONE;
+ for (uint32 spellId : knownSpells)
+ {
+ SpellInfo const* spellInfo = sSpellMgr->AssertSpellInfo(spellId);
+ if (spellInfo->IsCooldownStartedOnEvent())
+ continue;
+
+ if (spellInfo->PreventionType != SPELL_PREVENTION_TYPE_SILENCE)
+ continue;
+
+ if ((schoolMask & spellInfo->GetSchoolMask()) && GetRemainingCooldown(spellId) < lockoutTime)
+ {
+ spellCooldown.SpellCooldowns.emplace_back(spellId, lockoutTime);
+ AddCooldown(spellId, 0, lockoutEnd);
+ }
+ }
+
+ if (Player* player = GetPlayerOwner())
+ if (!spellCooldown.SpellCooldowns.empty())
+ player->SendDirectMessage(spellCooldown.Write());
+}
+
+bool SpellHistory::IsSchoolLocked(SpellSchoolMask schoolMask) const
+{
+ Clock::time_point now = Clock::now();
+ for (uint32 i = 0; i < MAX_SPELL_SCHOOL; ++i)
+ if (SpellSchoolMask(1 << i) & schoolMask)
+ if (_schoolLockouts[i] > now)
+ return true;
+
+ return false;
+}
+
+bool SpellHistory::ConsumeCharge(SpellCategoryEntry const* chargeCategoryEntry)
+{
+ if (!chargeCategoryEntry)
+ return false;
+
+ int32 chargeRecovery = GetChargeRecoveryTime(chargeCategoryEntry);
+ if (chargeRecovery > 0 && GetMaxCharges(chargeCategoryEntry) > 0)
+ {
+ Clock::time_point recoveryStart;
+ std::deque<ChargeEntry>& charges = _categoryCharges[chargeCategoryEntry->ID];
+ if (charges.empty())
+ recoveryStart = Clock::now();
+ else
+ recoveryStart = charges.back().RechargeEnd;
+
+ charges.emplace_back(recoveryStart, std::chrono::milliseconds(chargeRecovery));
+ return true;
+ }
+
+ return false;
+}
+
+void SpellHistory::RestoreCharge(SpellCategoryEntry const* chargeCategoryEntry)
+{
+ if (!chargeCategoryEntry)
+ return;
+
+ auto itr = _categoryCharges.find(chargeCategoryEntry->ID);
+ if (itr != _categoryCharges.end() && !itr->second.empty())
+ {
+ itr->second.pop_back();
+
+ if (Player* player = GetPlayerOwner())
+ {
+ int32 maxCharges = GetMaxCharges(chargeCategoryEntry);
+ int32 usedCharges = itr->second.size();
+ float count = float(maxCharges - usedCharges);
+ if (usedCharges)
+ {
+ ChargeEntry& charge = itr->second.front();
+ std::chrono::milliseconds remaining = std::chrono::duration_cast<std::chrono::milliseconds>(charge.RechargeEnd - Clock::now());
+ std::chrono::milliseconds recharge = std::chrono::duration_cast<std::chrono::milliseconds>(charge.RechargeEnd - charge.RechargeStart);
+ count += 1.0f - float(remaining.count()) / float(recharge.count());
+ }
+
+ WorldPackets::Spells::SetSpellCharges setSpellCharges;
+ setSpellCharges.IsPet = player == _owner;
+ setSpellCharges.Count = count;
+ setSpellCharges.Category = chargeCategoryEntry->ID;
+ player->SendDirectMessage(setSpellCharges.Write());
+ }
+ }
+}
+
+void SpellHistory::ResetCharges(SpellCategoryEntry const* chargeCategoryEntry)
+{
+ if (!chargeCategoryEntry)
+ return;
+
+ auto itr = _categoryCharges.find(chargeCategoryEntry->ID);
+ if (itr != _categoryCharges.end())
+ {
+ _categoryCharges.erase(itr);
+
+ if (Player* player = GetPlayerOwner())
+ {
+ WorldPackets::Spells::ClearSpellCharges clearSpellCharges;
+ clearSpellCharges.Unit = _owner->GetGUID();
+ clearSpellCharges.Category = chargeCategoryEntry->ID;
+ player->SendDirectMessage(clearSpellCharges.Write());
+ }
+ }
+}
+
+void SpellHistory::ResetAllCharges()
+{
+ _categoryCharges.clear();
+
+ if (Player* player = GetPlayerOwner())
+ {
+ WorldPackets::Spells::ClearAllSpellCharges clearAllSpellCharges;
+ clearAllSpellCharges.Unit = _owner->GetGUID();
+ player->SendDirectMessage(clearAllSpellCharges.Write());
+ }
+}
+
+bool SpellHistory::HasCharge(SpellCategoryEntry const* chargeCategoryEntry) const
+{
+ if (!chargeCategoryEntry)
+ return true;
+
+ // Check if the spell is currently using charges (untalented warlock Dark Soul)
+ int32 maxCharges = GetMaxCharges(chargeCategoryEntry);
+ if (maxCharges <= 0)
+ return true;
+
+ auto itr = _categoryCharges.find(chargeCategoryEntry->ID);
+ return itr == _categoryCharges.end() || itr->second.size() < maxCharges;
+}
+
+int32 SpellHistory::GetMaxCharges(SpellCategoryEntry const* chargeCategoryEntry) const
+{
+ if (!chargeCategoryEntry)
+ return 0;
+
+ uint32 charges = chargeCategoryEntry->MaxCharges;
+ charges += _owner->GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_MAX_CHARGES, chargeCategoryEntry->ID);
+ return charges;
+}
+
+int32 SpellHistory::GetChargeRecoveryTime(SpellCategoryEntry const* chargeCategoryEntry) const
+{
+ if (!chargeCategoryEntry)
+ return 0;
+
+ int32 recoveryTime = chargeCategoryEntry->ChargeRecoveryTime;
+ recoveryTime += _owner->GetTotalAuraModifierByMiscValue(SPELL_AURA_CHARGE_RECOVERY_MOD, chargeCategoryEntry->ID);
+
+ float recoveryTimeF = recoveryTime;
+ recoveryTimeF *= _owner->GetTotalAuraMultiplierByMiscValue(SPELL_AURA_CHARGE_RECOVERY_MULTIPLIER, chargeCategoryEntry->ID);
+
+ if (_owner->HasAuraType(SPELL_AURA_CHARGE_RECOVERY_AFFECTED_BY_HASTE))
+ recoveryTimeF *= _owner->GetFloatValue(UNIT_MOD_CAST_HASTE);
+
+ if (_owner->HasAuraType(SPELL_AURA_CHARGE_RECOVERY_AFFECTED_BY_HASTE_REGEN))
+ recoveryTimeF *= _owner->GetFloatValue(UNIT_FIELD_MOD_HASTE_REGEN);
+
+ return int32(std::floor(recoveryTimeF));
+}
+
+bool SpellHistory::HasGlobalCooldown(SpellInfo const* spellInfo) const
+{
+ auto itr = _globalCooldowns.find(spellInfo->StartRecoveryCategory);
+ return itr != _globalCooldowns.end() && itr->second > Clock::now();
+}
+
+void SpellHistory::AddGlobalCooldown(SpellInfo const* spellInfo, uint32 duration)
+{
+ _globalCooldowns[spellInfo->StartRecoveryCategory] = Clock::now() + std::chrono::duration_cast<Clock::duration>(std::chrono::milliseconds(duration));
+}
+
+void SpellHistory::CancelGlobalCooldown(SpellInfo const* spellInfo)
+{
+ _globalCooldowns[spellInfo->StartRecoveryCategory] = Clock::time_point(Clock::duration(0));
+}
+
+Player* SpellHistory::GetPlayerOwner() const
+{
+ return _owner->GetCharmerOrOwnerPlayerOrPlayerItself();
+}
+
+void SpellHistory::SendClearCooldowns(std::vector<int32> const& cooldowns) const
+{
+ if (Player const* playerOwner = GetPlayerOwner())
+ {
+ WorldPackets::Spells::ClearCooldowns clearCooldowns;
+ clearCooldowns.Guid = _owner->GetGUID();
+ clearCooldowns.SpellID = cooldowns;
+ playerOwner->SendDirectMessage(clearCooldowns.Write());
+ }
+}
+
+template void SpellHistory::LoadFromDB<Player>(PreparedQueryResult cooldownsResult, PreparedQueryResult chargesResult);
+template void SpellHistory::LoadFromDB<Pet>(PreparedQueryResult cooldownsResult, PreparedQueryResult chargesResult);
+template void SpellHistory::SaveToDB<Player>(SQLTransaction& trans);
+template void SpellHistory::SaveToDB<Pet>(SQLTransaction& trans);
diff --git a/src/server/game/Spells/SpellHistory.h b/src/server/game/Spells/SpellHistory.h
new file mode 100644
index 00000000000..38ae922750c
--- /dev/null
+++ b/src/server/game/Spells/SpellHistory.h
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SpellHistory_h__
+#define SpellHistory_h__
+
+#include "SharedDefines.h"
+#include "QueryResult.h"
+#include "Transaction.h"
+#include <chrono>
+#include <deque>
+
+class Item;
+class Player;
+class Spell;
+class SpellInfo;
+class Unit;
+struct SpellCategoryEntry;
+
+/// Spell cooldown flags sent in SMSG_SPELL_COOLDOWN
+enum SpellCooldownFlags
+{
+ SPELL_COOLDOWN_FLAG_NONE = 0x0,
+ SPELL_COOLDOWN_FLAG_INCLUDE_GCD = 0x1, ///< Starts GCD in addition to normal cooldown specified in the packet
+ SPELL_COOLDOWN_FLAG_INCLUDE_EVENT_COOLDOWNS = 0x2 ///< Starts GCD for spells that should start their cooldown on events, requires SPELL_COOLDOWN_FLAG_INCLUDE_GCD set
+};
+
+class SpellHistory
+{
+public:
+ typedef std::chrono::system_clock Clock;
+
+ struct CooldownEntry
+ {
+ CooldownEntry() : ItemId(0), OnHold(false) { }
+ CooldownEntry(Clock::time_point endTime, uint32 itemId) : CooldownEnd(endTime), ItemId(itemId), OnHold(false) { }
+
+ Clock::time_point CooldownEnd;
+ uint32 ItemId;
+ bool OnHold;
+ };
+
+ struct ChargeEntry
+ {
+ ChargeEntry() { }
+ ChargeEntry(Clock::time_point startTime, std::chrono::milliseconds rechargeTime) : RechargeStart(startTime), RechargeEnd(startTime + rechargeTime) { }
+ ChargeEntry(Clock::time_point startTime, Clock::time_point endTime) : RechargeStart(startTime), RechargeEnd(endTime) { }
+
+ Clock::time_point RechargeStart;
+ Clock::time_point RechargeEnd;
+ };
+
+ typedef std::unordered_map<uint32 /*spellId*/, CooldownEntry> CooldownStorageType;
+ typedef std::unordered_map<uint32 /*categoryId*/, std::deque<ChargeEntry>> ChargeStorageType;
+ typedef std::unordered_map<uint32 /*categoryId*/, Clock::time_point> GlobalCooldownStorageType;
+
+ explicit SpellHistory(Unit* owner) : _owner(owner), _schoolLockouts() { }
+
+ template<class OwnerType>
+ void LoadFromDB(PreparedQueryResult cooldownsResult, PreparedQueryResult chargesResult);
+
+ template<class OwnerType>
+ void SaveToDB(SQLTransaction& trans);
+
+ void Update();
+
+ void HandleCooldowns(SpellInfo const* spellInfo, Item const* item, Spell* spell = nullptr);
+ bool IsReady(SpellInfo const* spellInfo) const;
+ template<class PacketType>
+ void WritePacket(PacketType* packet) const;
+
+ // Cooldowns
+ static Clock::duration const InfinityCooldownDelay; // used for set "infinity cooldowns" for spells and check
+
+ void StartCooldown(SpellInfo const* spellInfo, uint32 itemId, Spell* spell = nullptr, bool onHold = false);
+ void SendCooldownEvent(SpellInfo const* spellInfo, uint32 itemId = 0, Spell* spell = nullptr, bool startCooldown = true);
+
+ template<class Type, class Period>
+ void AddCooldown(uint32 spellId, uint32 itemId, std::chrono::duration<Type, Period> cooldownDuration)
+ {
+ AddCooldown(spellId, itemId, Clock::now() + std::chrono::duration_cast<Clock::duration>(cooldownDuration));
+ }
+
+ void AddCooldown(uint32 spellId, uint32 itemId, Clock::time_point cooldownEnd, bool onHold = false);
+ void ModifyCooldown(uint32 spellId, int32 cooldownModMs);
+ void ResetCooldown(uint32 spellId, bool update = false);
+ void ResetCooldown(CooldownStorageType::iterator& itr, bool update = false);
+ template<typename Predicate>
+ void ResetCooldowns(Predicate predicate, bool update = false)
+ {
+ std::vector<int32> resetCooldowns;
+ resetCooldowns.reserve(_spellCooldowns.size());
+ for (auto itr = _spellCooldowns.begin(); itr != _spellCooldowns.end();)
+ {
+ if (predicate(itr))
+ ResetCooldown(itr, false);
+ else
+ ++itr;
+ }
+
+ if (update && !resetCooldowns.empty())
+ SendClearCooldowns(resetCooldowns);
+ }
+
+ void ResetAllCooldowns();
+ bool HasCooldown(uint32 spellId) const;
+ uint32 GetRemainingCooldown(uint32 spellId) const;
+
+ // School lockouts
+ void LockSpellSchool(SpellSchoolMask schoolMask, uint32 lockoutTime);
+ bool IsSchoolLocked(SpellSchoolMask schoolMask) const;
+
+ // Charges
+ bool ConsumeCharge(SpellCategoryEntry const* chargeCategoryEntry);
+ void RestoreCharge(SpellCategoryEntry const* chargeCategoryEntry);
+ void ResetCharges(SpellCategoryEntry const* chargeCategoryEntry);
+ void ResetAllCharges();
+ bool HasCharge(SpellCategoryEntry const* chargeCategoryEntry) const;
+ int32 GetMaxCharges(SpellCategoryEntry const* chargeCategoryEntry) const;
+ int32 GetChargeRecoveryTime(SpellCategoryEntry const* chargeCategoryEntry) const;
+
+ // Global cooldown
+ bool HasGlobalCooldown(SpellInfo const* spellInfo) const;
+ void AddGlobalCooldown(SpellInfo const* spellInfo, uint32 duration);
+ void CancelGlobalCooldown(SpellInfo const* spellInfo);
+
+private:
+ Player* GetPlayerOwner() const;
+ void SendClearCooldowns(std::vector<int32> const& cooldowns) const;
+
+ Unit* _owner;
+ CooldownStorageType _spellCooldowns;
+ Clock::time_point _schoolLockouts[MAX_SPELL_SCHOOL];
+ ChargeStorageType _categoryCharges;
+ GlobalCooldownStorageType _globalCooldowns;
+
+ template<class T>
+ struct PersistenceHelper { };
+};
+
+#endif // SpellHistory_h__
diff --git a/src/server/game/Spells/SpellInfo.cpp b/src/server/game/Spells/SpellInfo.cpp
index a70c73de6df..ab4ac032345 100644
--- a/src/server/game/Spells/SpellInfo.cpp
+++ b/src/server/game/Spells/SpellInfo.cpp
@@ -1054,6 +1054,7 @@ SpellInfo::SpellInfo(SpellEntry const* spellEntry, SpellEffectEntryMap effects)
StartRecoveryCategory = _categorie ? _categorie->StartRecoveryCategory : 0;
DmgClass = _categorie ? _categorie->DefenseType : 0;
PreventionType = _categorie ? _categorie->PreventionType : 0;
+ ChargeCategoryEntry = _categorie ? sSpellCategoryStore.LookupEntry(_categorie->ChargeCategory) : 0;
// SpellClassOptionsEntry
SpellClassOptionsEntry const* _class = GetSpellClassOptions();
diff --git a/src/server/game/Spells/SpellInfo.h b/src/server/game/Spells/SpellInfo.h
index b585588726e..c9b87e3abf7 100644
--- a/src/server/game/Spells/SpellInfo.h
+++ b/src/server/game/Spells/SpellInfo.h
@@ -399,6 +399,7 @@ public:
uint32 PreventionType;
int32 RequiredAreasID;
uint32 SchoolMask;
+ SpellCategoryEntry const* ChargeCategoryEntry;
uint32 SpellDifficultyId;
uint32 SpellScalingId;
uint32 SpellAuraOptionsId;