diff options
| author | Shauren <shauren.trinity@gmail.com> | 2015-02-17 01:01:44 +0100 |
|---|---|---|
| committer | Shauren <shauren.trinity@gmail.com> | 2015-02-17 01:01:44 +0100 |
| commit | 56186319bdd41dd26b6cc14f84e6e41eef5d953b (patch) | |
| tree | 7b531cdf71b59170f9fa65120c09935ce51f3307 /src/server/game/Spells | |
| parent | 7829412416c9709991fd686030ab77908c27922b (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.h | 16 | ||||
| -rw-r--r-- | src/server/game/Spells/Auras/SpellAuraEffects.cpp | 23 | ||||
| -rw-r--r-- | src/server/game/Spells/Auras/SpellAuras.cpp | 39 | ||||
| -rw-r--r-- | src/server/game/Spells/Auras/SpellAuras.h | 2 | ||||
| -rw-r--r-- | src/server/game/Spells/Spell.cpp | 85 | ||||
| -rw-r--r-- | src/server/game/Spells/Spell.h | 1 | ||||
| -rw-r--r-- | src/server/game/Spells/SpellEffects.cpp | 15 | ||||
| -rw-r--r-- | src/server/game/Spells/SpellHistory.cpp | 854 | ||||
| -rw-r--r-- | src/server/game/Spells/SpellHistory.h | 155 | ||||
| -rw-r--r-- | src/server/game/Spells/SpellInfo.cpp | 1 | ||||
| -rw-r--r-- | src/server/game/Spells/SpellInfo.h | 1 |
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; |
