diff options
| author | Duarte Duarte <dnpd.dd@gmail.com> | 2015-05-19 18:52:05 +0100 |
|---|---|---|
| committer | Duarte Duarte <dnpd.dd@gmail.com> | 2015-05-19 18:52:05 +0100 |
| commit | f4c1a8fb2d6aff13f67dd2494dc18b481c548af9 (patch) | |
| tree | 55c290aa51f46ac14296439e4a33eb6c77dfd722 /src/server/game | |
| parent | 6dcfe9ba178a93046104439b43d84ad1e9f6969d (diff) | |
| parent | e70790576414dde16b56fd828d52a79ef540d3e9 (diff) | |
Merge pull request #14454 from ariel-/cd
Port Core/Spells: Cooldown updates (6.x branch)
Diffstat (limited to 'src/server/game')
21 files changed, 950 insertions, 979 deletions
diff --git a/src/server/game/AI/CoreAI/PetAI.cpp b/src/server/game/AI/CoreAI/PetAI.cpp index abb6126ca2c..2522c97de25 100644 --- a/src/server/game/AI/CoreAI/PetAI.cpp +++ b/src/server/game/AI/CoreAI/PetAI.cpp @@ -22,6 +22,7 @@ #include "Player.h" #include "Spell.h" #include "ObjectAccessor.h" +#include "SpellHistory.h" #include "SpellMgr.h" #include "Creature.h" #include "Util.h" @@ -146,15 +147,15 @@ void PetAI::UpdateAI(uint32 diff) if (!spellInfo) continue; - if (me->GetCharmInfo() && me->GetCharmInfo()->GetGlobalCooldownMgr().HasGlobalCooldown(spellInfo)) + if (me->GetCharmInfo() && me->GetSpellHistory()->HasGlobalCooldown(spellInfo)) continue; if (spellInfo->IsPositive()) { if (spellInfo->CanBeUsedInCombat()) { - // check spell cooldown - if (me->HasSpellCooldown(spellInfo->Id)) + // check spell cooldown & school lock + if (!me->GetSpellHistory()->IsReady(spellInfo)) continue; // Check if we're in combat or commanded to attack diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index 05de671830e..7f00fc7f9d8 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -150,8 +150,6 @@ m_originalEntry(0), m_homePosition(), m_transportHomePosition(), m_creatureInfo( for (uint8 i = 0; i < CREATURE_MAX_SPELLS; ++i) m_spells[i] = 0; - m_CreatureSpellCooldowns.clear(); - m_CreatureCategoryCooldowns.clear(); DisableReputationGain = false; m_SightDistance = sWorld->getFloatConfig(CONFIG_SIGHT_MONSTER); @@ -2156,83 +2154,6 @@ uint32 Creature::GetShieldBlockValue() const //dunno mob block return (getLevel()/2 + uint32(GetStat(STAT_STRENGTH)/20)); } -void Creature::_AddCreatureSpellCooldown(uint32 spell_id, time_t end_time) -{ - m_CreatureSpellCooldowns[spell_id] = end_time; -} - -void Creature::_AddCreatureCategoryCooldown(uint32 category, time_t apply_time) -{ - m_CreatureCategoryCooldowns[category] = apply_time; -} - -void Creature::AddCreatureSpellCooldown(uint32 spellid) -{ - SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellid); - if (!spellInfo) - return; - - uint32 cooldown = spellInfo->GetRecoveryTime(); - if (Player* modOwner = GetSpellModOwner()) - modOwner->ApplySpellMod(spellid, SPELLMOD_COOLDOWN, cooldown); - - if (cooldown) - _AddCreatureSpellCooldown(spellid, time(NULL) + cooldown/IN_MILLISECONDS); - - if (spellInfo->GetCategory()) - _AddCreatureCategoryCooldown(spellInfo->GetCategory(), time(NULL)); -} - -bool Creature::HasCategoryCooldown(uint32 spell_id) const -{ - SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spell_id); - if (!spellInfo) - return false; - - CreatureSpellCooldowns::const_iterator itr = m_CreatureCategoryCooldowns.find(spellInfo->GetCategory()); - return(itr != m_CreatureCategoryCooldowns.end() && time_t(itr->second + (spellInfo->CategoryRecoveryTime / IN_MILLISECONDS)) > time(NULL)); -} - -uint32 Creature::GetCreatureSpellCooldownDelay(uint32 spellId) const -{ - CreatureSpellCooldowns::const_iterator itr = m_CreatureSpellCooldowns.find(spellId); - time_t t = time(NULL); - return uint32(itr != m_CreatureSpellCooldowns.end() && itr->second > t ? itr->second - t : 0); -} - -bool Creature::HasSpellCooldown(uint32 spell_id) const -{ - CreatureSpellCooldowns::const_iterator itr = m_CreatureSpellCooldowns.find(spell_id); - return (itr != m_CreatureSpellCooldowns.end() && itr->second > time(NULL)) || HasCategoryCooldown(spell_id); -} - -void Creature::ProhibitSpellSchool(SpellSchoolMask idSchoolMask, uint32 unTimeMs) -{ - time_t curTime = time(NULL); - for (uint8 i = 0; i < CREATURE_MAX_SPELLS; ++i) - { - if (m_spells[i] == 0) - continue; - - uint32 unSpellId = m_spells[i]; - SpellInfo const* spellInfo = sSpellMgr->EnsureSpellInfo(unSpellId); - - // Not send cooldown for this spells - if (spellInfo->IsCooldownStartedOnEvent()) - continue; - - if (spellInfo->PreventionType != SPELL_PREVENTION_TYPE_SILENCE) - continue; - - if ((idSchoolMask & spellInfo->GetSchoolMask()) && GetCreatureSpellCooldownDelay(unSpellId) < unTimeMs) - { - _AddCreatureSpellCooldown(unSpellId, curTime + unTimeMs/IN_MILLISECONDS); - if (UnitAI* ai = GetAI()) - ai->SpellInterrupted(unSpellId, unTimeMs); - } - } -} - bool Creature::HasSpell(uint32 spellID) const { uint8 i; diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h index ec06bf90595..433d4a21b27 100644 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -414,8 +414,6 @@ struct TrainerSpellData TrainerSpell const* Find(uint32 spell_id) const; }; -typedef std::map<uint32, time_t> CreatureSpellCooldowns; - // max different by z coordinate for creature aggro reaction #define CREATURE_Z_ATTACK_RANGE 3 @@ -494,14 +492,6 @@ class Creature : public Unit, public GridObject<Creature>, public MapObject SpellSchoolMask GetMeleeDamageSchoolMask() const override { return m_meleeDamageSchoolMask; } void SetMeleeDamageSchool(SpellSchools school) { m_meleeDamageSchoolMask = SpellSchoolMask(1 << school); } - void _AddCreatureSpellCooldown(uint32 spell_id, time_t end_time); - void _AddCreatureCategoryCooldown(uint32 category, time_t apply_time); - void AddCreatureSpellCooldown(uint32 spellid); - bool HasSpellCooldown(uint32 spell_id) const; - bool HasCategoryCooldown(uint32 spell_id) const; - uint32 GetCreatureSpellCooldownDelay(uint32 spellId) const; - virtual void ProhibitSpellSchool(SpellSchoolMask idSchoolMask, uint32 unTimeMs) override; - bool HasSpell(uint32 spellID) const override; bool UpdateEntry(uint32 entry, CreatureData const* data = nullptr); @@ -575,8 +565,6 @@ class Creature : public Unit, public GridObject<Creature>, public MapObject SpellInfo const* reachWithSpellCure(Unit* victim); uint32 m_spells[CREATURE_MAX_SPELLS]; - CreatureSpellCooldowns m_CreatureSpellCooldowns; - CreatureSpellCooldowns m_CreatureCategoryCooldowns; bool CanStartAttack(Unit const* u, bool force) const; float GetAttackDistance(Unit const* player) const; diff --git a/src/server/game/Entities/Item/Item.h b/src/server/game/Entities/Item/Item.h index c82ae723f4c..b832aeb5614 100644 --- a/src/server/game/Entities/Item/Item.h +++ b/src/server/game/Entities/Item/Item.h @@ -201,8 +201,6 @@ enum ItemUpdateState ITEM_REMOVED = 3 }; -#define MAX_ITEM_SPELLS 5 - bool ItemCanGoIntoBag(ItemTemplate const* proto, ItemTemplate const* pBagProto); class Item : public Object diff --git a/src/server/game/Entities/Pet/Pet.cpp b/src/server/game/Entities/Pet/Pet.cpp index d5d6bdf9831..97cdb0cf1df 100644 --- a/src/server/game/Entities/Pet/Pet.cpp +++ b/src/server/game/Entities/Pet/Pet.cpp @@ -24,6 +24,7 @@ #include "SpellMgr.h" #include "Pet.h" #include "Formulas.h" +#include "SpellHistory.h" #include "SpellAuras.h" #include "SpellAuraEffects.h" #include "Unit.h" @@ -407,7 +408,7 @@ void Pet::SavePetToDB(PetSaveMode mode) RemoveAllAuras(); _SaveSpells(trans); - _SaveSpellCooldowns(trans); + GetSpellHistory()->SaveToDB<Pet>(trans); CharacterDatabase.CommitTransaction(trans); // current/stable/not_in_slot @@ -1106,77 +1107,11 @@ uint32 Pet::GetCurrentFoodBenefitLevel(uint32 itemlevel) const void Pet::_LoadSpellCooldowns() { - m_CreatureSpellCooldowns.clear(); - m_CreatureCategoryCooldowns.clear(); - PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_SPELL_COOLDOWN); stmt->setUInt32(0, m_charmInfo->GetPetNumber()); - PreparedQueryResult result = CharacterDatabase.Query(stmt); - - if (result) - { - time_t curTime = time(NULL); - - PacketCooldowns cooldowns; - WorldPacket data; - - do - { - Field* fields = result->Fetch(); - - uint32 spell_id = fields[0].GetUInt32(); - time_t db_time = time_t(fields[1].GetUInt32()); - - if (!sSpellMgr->GetSpellInfo(spell_id)) - { - TC_LOG_ERROR("entities.pet", "Pet %u have unknown spell %u in `pet_spell_cooldown`, skipping.", m_charmInfo->GetPetNumber(), spell_id); - continue; - } + PreparedQueryResult cooldownsResult = CharacterDatabase.Query(stmt); - // skip outdated cooldown - if (db_time <= curTime) - continue; - - cooldowns[spell_id] = uint32(db_time - curTime)*IN_MILLISECONDS; - - _AddCreatureSpellCooldown(spell_id, db_time); - - TC_LOG_DEBUG("entities.pet", "Pet (Number: %u) spell %u cooldown loaded (%u secs).", m_charmInfo->GetPetNumber(), spell_id, uint32(db_time-curTime)); - } - while (result->NextRow()); - - if (!cooldowns.empty()) - { - BuildCooldownPacket(data, SPELL_COOLDOWN_FLAG_NONE, cooldowns); - GetOwner()->GetSession()->SendPacket(&data); - } - } -} - -void Pet::_SaveSpellCooldowns(SQLTransaction& trans) -{ - PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PET_SPELL_COOLDOWNS); - stmt->setUInt32(0, m_charmInfo->GetPetNumber()); - trans->Append(stmt); - - time_t curTime = time(NULL); - - // remove oudated and save active - for (CreatureSpellCooldowns::iterator itr = m_CreatureSpellCooldowns.begin(); itr != m_CreatureSpellCooldowns.end();) - { - if (itr->second <= curTime) - m_CreatureSpellCooldowns.erase(itr++); - else - { - stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_PET_SPELL_COOLDOWN); - stmt->setUInt32(0, m_charmInfo->GetPetNumber()); - stmt->setUInt32(1, itr->first); - stmt->setUInt32(2, uint32(itr->second)); - trans->Append(stmt); - - ++itr; - } - } + GetSpellHistory()->LoadFromDB<Pet>(cooldownsResult); } void Pet::_LoadSpells() @@ -2025,40 +1960,6 @@ void Pet::SynchronizeLevelWithOwner() } } -void Pet::ProhibitSpellSchool(SpellSchoolMask idSchoolMask, uint32 unTimeMs) -{ - PacketCooldowns cooldowns; - WorldPacket data; - time_t curTime = time(NULL); - for (PetSpellMap::const_iterator itr = m_spells.begin(); itr != m_spells.end(); ++itr) - { - if (itr->second.state == PETSPELL_REMOVED) - continue; - - uint32 unSpellId = itr->first; - SpellInfo const* spellInfo = sSpellMgr->EnsureSpellInfo(unSpellId); - - // Not send cooldown for this spells - if (spellInfo->IsCooldownStartedOnEvent()) - continue; - - if (spellInfo->PreventionType != SPELL_PREVENTION_TYPE_SILENCE) - continue; - - if ((idSchoolMask & spellInfo->GetSchoolMask()) && GetCreatureSpellCooldownDelay(unSpellId) < unTimeMs) - { - cooldowns[unSpellId] = unTimeMs; - _AddCreatureSpellCooldown(unSpellId, curTime + unTimeMs/IN_MILLISECONDS); - } - } - - if (!cooldowns.empty()) - { - BuildCooldownPacket(data, SPELL_COOLDOWN_FLAG_NONE, cooldowns); - GetOwner()->GetSession()->SendPacket(&data); - } -} - Player* Pet::GetOwner() const { return Minion::GetOwner()->ToPlayer(); diff --git a/src/server/game/Entities/Pet/Pet.h b/src/server/game/Entities/Pet/Pet.h index 62d82aaab1b..1cc86ea2a20 100644 --- a/src/server/game/Entities/Pet/Pet.h +++ b/src/server/game/Entities/Pet/Pet.h @@ -108,7 +108,6 @@ class Pet : public Guardian bool IsPetAura(Aura const* aura); void _LoadSpellCooldowns(); - void _SaveSpellCooldowns(SQLTransaction& trans); void _LoadAuras(uint32 timediff); void _SaveAuras(SQLTransaction& trans); void _LoadSpells(); @@ -121,7 +120,6 @@ class Pet : public Guardian bool unlearnSpell(uint32 spell_id, bool learn_prev, bool clear_ab = true); bool removeSpell(uint32 spell_id, bool learn_prev, bool clear_ab = true); void CleanupActionBar(); - virtual void ProhibitSpellSchool(SpellSchoolMask idSchoolMask, uint32 unTimeMs) override; PetSpellMap m_spells; AutoSpellList m_autospells; diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 3533364566a..030c2a25110 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -67,6 +67,7 @@ #include "SpellAuraEffects.h" #include "SpellAuras.h" #include "SpellMgr.h" +#include "SpellHistory.h" #include "Transport.h" #include "UpdateData.h" #include "UpdateFieldFlags.h" @@ -3345,12 +3346,10 @@ void Player::InitStatsForLevel(bool reapplyMods) void Player::SendInitialSpells() { - time_t curTime = time(NULL); - time_t infTime = curTime + infinityCooldownDelayCheck; - + uint16 spellCooldowns = GetSpellHistory()->GetCooldownsSizeForPacket(); uint16 spellCount = 0; - WorldPacket data(SMSG_INITIAL_SPELLS, (1+2+4*m_spells.size()+2+m_spellCooldowns.size()*(2+2+2+4+4))); + WorldPacket data(SMSG_INITIAL_SPELLS, (1 + 2 + 4 * m_spells.size() + 2 + spellCooldowns * (2 + 2 + 2 + 4 + 4))); data << uint8(0); size_t countPos = data.wpos(); @@ -3372,40 +3371,7 @@ void Player::SendInitialSpells() data.put<uint16>(countPos, spellCount); // write real count value - uint16 spellCooldowns = m_spellCooldowns.size(); - data << uint16(spellCooldowns); - for (SpellCooldowns::const_iterator itr = m_spellCooldowns.begin(); itr != m_spellCooldowns.end(); ++itr) - { - SpellInfo const* sEntry = sSpellMgr->GetSpellInfo(itr->first); - if (!sEntry) - continue; - - data << uint32(itr->first); - - data << uint16(itr->second.itemid); // cast item id - data << uint16(sEntry->GetCategory()); // spell category - - // send infinity cooldown in special format - if (itr->second.end >= infTime) - { - data << uint32(1); // cooldown - data << uint32(0x80000000); // category cooldown - continue; - } - - time_t cooldown = itr->second.end > curTime ? (itr->second.end-curTime)*IN_MILLISECONDS : 0; - - if (sEntry->GetCategory()) // may be wrong, but anyway better than nothing... - { - data << uint32(0); // cooldown - data << uint32(cooldown); // category cooldown - } - else - { - data << uint32(cooldown); // cooldown - data << uint32(0); // category cooldown - } - } + GetSpellHistory()->WritePacket<Player>(data); GetSession()->SendPacket(&data); @@ -4202,154 +4168,19 @@ bool Player::Has310Flyer(bool checkAllSpells, uint32 excludeSpellId) return false; } -void Player::RemoveSpellCooldown(uint32 spell_id, bool update /* = false */) -{ - m_spellCooldowns.erase(spell_id); - - if (update) - SendClearCooldown(spell_id, this); -} - -// I am not sure which one is more efficient -void Player::RemoveCategoryCooldown(uint32 cat) -{ - SpellCategoryStore::const_iterator i_scstore = sSpellsByCategoryStore.find(cat); - if (i_scstore != sSpellsByCategoryStore.end()) - for (SpellCategorySet::const_iterator i_scset = i_scstore->second.begin(); i_scset != i_scstore->second.end(); ++i_scset) - RemoveSpellCooldown(*i_scset, true); -} - -void Player::RemoveSpellCategoryCooldown(uint32 cat, bool update /* = false */) -{ - SpellCategoryStore::const_iterator ct = sSpellsByCategoryStore.find(cat); - if (ct == sSpellsByCategoryStore.end()) - return; - - const SpellCategorySet& ct_set = ct->second; - for (SpellCooldowns::const_iterator i = m_spellCooldowns.begin(); i != m_spellCooldowns.end();) - { - if (ct_set.find(i->first) != ct_set.end()) - RemoveSpellCooldown((i++)->first, update); - else - ++i; - } -} - void Player::RemoveArenaSpellCooldowns(bool removeActivePetCooldowns) { // remove cooldowns on spells that have < 10 min CD - - SpellCooldowns::iterator itr, next; - for (itr = m_spellCooldowns.begin(); itr != m_spellCooldowns.end(); itr = next) + GetSpellHistory()->ResetCooldowns([](SpellHistory::CooldownStorageType::iterator itr) -> bool { - next = itr; - ++next; - SpellInfo const* entry = sSpellMgr->GetSpellInfo(itr->first); - // check if spellentry is present and if the cooldown is less than 10 min - if (entry && - entry->RecoveryTime < 10 * MINUTE * IN_MILLISECONDS && - entry->CategoryRecoveryTime < 10 * MINUTE * IN_MILLISECONDS) - { - // remove & notify - RemoveSpellCooldown(itr->first, true); - } - } + SpellInfo const* spellInfo = sSpellMgr->EnsureSpellInfo(itr->first); + return spellInfo->RecoveryTime < 10 * MINUTE * IN_MILLISECONDS && spellInfo->CategoryRecoveryTime < 10 * MINUTE * IN_MILLISECONDS; + }, true); // pet cooldowns if (removeActivePetCooldowns) if (Pet* pet = GetPet()) - { - // notify player - for (CreatureSpellCooldowns::const_iterator itr2 = pet->m_CreatureSpellCooldowns.begin(); itr2 != pet->m_CreatureSpellCooldowns.end(); ++itr2) - SendClearCooldown(itr2->first, pet); - - // actually clear cooldowns - pet->m_CreatureSpellCooldowns.clear(); - } -} - -void Player::RemoveAllSpellCooldown() -{ - if (!m_spellCooldowns.empty()) - { - for (SpellCooldowns::const_iterator itr = m_spellCooldowns.begin(); itr != m_spellCooldowns.end(); ++itr) - SendClearCooldown(itr->first, this); - - m_spellCooldowns.clear(); - } -} - -void Player::_LoadSpellCooldowns(PreparedQueryResult result) -{ - // some cooldowns can be already set at aura loading... - - //QueryResult* result = CharacterDatabase.PQuery("SELECT spell, item, time FROM character_spell_cooldown WHERE guid = '%u'", GetGUIDLow()); - - if (result) - { - time_t curTime = time(NULL); - - do - { - Field* fields = result->Fetch(); - uint32 spell_id = fields[0].GetUInt32(); - uint32 item_id = fields[1].GetUInt32(); - time_t db_time = time_t(fields[2].GetUInt32()); - - if (!sSpellMgr->GetSpellInfo(spell_id)) - { - TC_LOG_ERROR("entities.player.loading", "Player %u has unknown spell %u in `character_spell_cooldown`, skipping.", GetGUIDLow(), spell_id); - continue; - } - - // skip outdated cooldown - if (db_time <= curTime) - continue; - - AddSpellCooldown(spell_id, item_id, db_time); - - TC_LOG_DEBUG("entities.player.loading", "Player (GUID: %u) spell %u, item %u cooldown loaded (%u secs).", GetGUIDLow(), spell_id, item_id, uint32(db_time-curTime)); - } - while (result->NextRow()); - } -} - -void Player::_SaveSpellCooldowns(SQLTransaction& trans) -{ - PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_SPELL_COOLDOWN); - stmt->setUInt32(0, GetGUIDLow()); - trans->Append(stmt); - - time_t curTime = time(NULL); - time_t infTime = curTime + infinityCooldownDelayCheck; - - bool first_round = true; - std::ostringstream ss; - - // remove outdated and save active - for (SpellCooldowns::iterator itr = m_spellCooldowns.begin(); itr != m_spellCooldowns.end();) - { - if (itr->second.end <= curTime) - m_spellCooldowns.erase(itr++); - else if (itr->second.end <= infTime) // not save locked cooldowns, it will be reset or set at reload - { - if (first_round) - { - ss << "INSERT INTO character_spell_cooldown (guid, spell, item, time) VALUES "; - first_round = false; - } - // next new/changed record prefix - else - ss << ','; - ss << '(' << GetGUIDLow() << ',' << itr->first << ',' << itr->second.itemid << ',' << uint64(itr->second.end) << ')'; - ++itr; - } - else - ++itr; - } - // if something changed execute - if (!first_round) - trans->Append(ss.str().c_str()); + pet->GetSpellHistory()->ResetAllCooldowns(); } uint32 Player::ResetTalentsCost() const @@ -4897,7 +4728,7 @@ void Player::DeleteFromDB(ObjectGuid playerguid, uint32 accountId, bool updateRe stmt->setUInt32(0, guid); trans->Append(stmt); - stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_SPELL_COOLDOWN); + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_SPELL_COOLDOWNS); stmt->setUInt32(0, guid); trans->Append(stmt); @@ -8382,7 +8213,7 @@ void Player::CastItemCombatSpell(Unit* target, WeaponAttackType attType, uint32 if (procVictim & PROC_FLAG_TAKEN_DAMAGE) //if (damageInfo->procVictim & PROC_FLAG_TAKEN_ANY_DAMAGE) { - for (uint8 i = 0; i < MAX_ITEM_SPELLS; ++i) + for (uint8 i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i) { _Spell const& spellData = proto->Spells[i]; @@ -12425,10 +12256,9 @@ Item* Player::EquipItem(uint16 pos, Item* pItem, bool update) { m_weaponChangeTimer = spellProto->StartRecoveryTime; - GetGlobalCooldownMgr().AddGlobalCooldown(spellProto, m_weaponChangeTimer); - + GetSpellHistory()->AddGlobalCooldown(spellProto, m_weaponChangeTimer); WorldPacket data; - BuildCooldownPacket(data, SPELL_COOLDOWN_FLAG_INCLUDE_GCD, cooldownSpell, 0); + GetSpellHistory()->BuildCooldownPacket(data, SPELL_COOLDOWN_FLAG_INCLUDE_GCD, cooldownSpell, 0); GetSession()->SendPacket(&data); } } @@ -17811,7 +17641,7 @@ bool Player::LoadFromDB(ObjectGuid guid, SQLQueryHolder *holder) // has to be called after last Relocate() in Player::LoadFromDB SetFallInformation(0, GetPositionZ()); - _LoadSpellCooldowns(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_SPELL_COOLDOWNS)); + GetSpellHistory()->LoadFromDB<Player>(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_SPELL_COOLDOWNS)); // Spell code allow apply any auras to dead character in load time in aura/spell/item loading // Do now before stats re-calculation cleanup for ghost state unexpected auras @@ -19517,7 +19347,7 @@ void Player::SaveToDB(bool create /*=false*/) _SaveMonthlyQuestStatus(trans); _SaveTalents(trans); _SaveSpells(trans); - _SaveSpellCooldowns(trans); + GetSpellHistory()->SaveToDB<Player>(trans); _SaveActions(trans); _SaveAuras(trans); _SaveSkills(trans); @@ -20754,41 +20584,8 @@ void Player::PetSpellInitialize() data.put<uint8>(spellsCountPos, addlist); - uint8 cooldownsCount = pet->m_CreatureSpellCooldowns.size() + pet->m_CreatureCategoryCooldowns.size(); - data << uint8(cooldownsCount); - - time_t curTime = time(NULL); - - for (CreatureSpellCooldowns::const_iterator itr = pet->m_CreatureSpellCooldowns.begin(); itr != pet->m_CreatureSpellCooldowns.end(); ++itr) - { - SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(itr->first); - if (!spellInfo) - { - data << uint32(0); - data << uint16(0); - data << uint32(0); - data << uint32(0); - continue; - } - - time_t cooldown = (itr->second > curTime) ? (itr->second - curTime) * IN_MILLISECONDS : 0; - data << uint32(itr->first); // spell ID - - CreatureSpellCooldowns::const_iterator categoryitr = pet->m_CreatureCategoryCooldowns.find(spellInfo->GetCategory()); - if (categoryitr != pet->m_CreatureCategoryCooldowns.end()) - { - time_t categoryCooldown = (categoryitr->second > curTime) ? (categoryitr->second - curTime) * IN_MILLISECONDS : 0; - data << uint16(spellInfo->GetCategory()); // spell category - data << uint32(cooldown); // spell cooldown - data << uint32(categoryCooldown); // category cooldown - } - else - { - data << uint16(0); - data << uint32(cooldown); - data << uint32(0); - } - } + //Cooldowns + pet->GetSpellHistory()->WritePacket<Pet>(data); GetSession()->SendPacket(&data); } @@ -20827,7 +20624,7 @@ void Player::VehicleSpellInitialize() if (!vehicle) return; - uint8 cooldownCount = vehicle->m_CreatureSpellCooldowns.size(); + uint8 cooldownCount = vehicle->GetSpellHistory()->GetCooldownsSizeForPacket(); WorldPacket data(SMSG_PET_SPELLS, 8 + 2 + 4 + 4 + 4 * 10 + 1 + 1 + cooldownCount * (4 + 2 + 4 + 4)); data << uint64(vehicle->GetGUID()); // Guid @@ -20868,41 +20665,7 @@ void Player::VehicleSpellInitialize() data << uint8(0); // Auras? // Cooldowns - data << uint8(cooldownCount); - - time_t now = sWorld->GetGameTime(); - - for (CreatureSpellCooldowns::const_iterator itr = vehicle->m_CreatureSpellCooldowns.begin(); itr != vehicle->m_CreatureSpellCooldowns.end(); ++itr) - { - SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(itr->first); - if (!spellInfo) - { - data << uint32(0); - data << uint16(0); - data << uint32(0); - data << uint32(0); - continue; - } - - time_t cooldown = (itr->second > now) ? (itr->second - now) * IN_MILLISECONDS : 0; - data << uint32(itr->first); // spell ID - - CreatureSpellCooldowns::const_iterator categoryitr = vehicle->m_CreatureCategoryCooldowns.find(spellInfo->GetCategory()); - if (categoryitr != vehicle->m_CreatureCategoryCooldowns.end()) - { - time_t categoryCooldown = (categoryitr->second > now) ? (categoryitr->second - now) * IN_MILLISECONDS : 0; - data << uint16(spellInfo->GetCategory()); // spell category - data << uint32(cooldown); // spell cooldown - data << uint32(categoryCooldown); // category cooldown - } - else - { - data << uint16(0); - data << uint32(cooldown); - data << uint32(0); - } - } - + vehicle->GetSpellHistory()->WritePacket<Pet>(data); GetSession()->SendPacket(&data); } @@ -21557,39 +21320,6 @@ void Player::ContinueTaxiFlight() GetSession()->SendDoFlight(mountDisplayId, path, startNode); } -void Player::ProhibitSpellSchool(SpellSchoolMask idSchoolMask, uint32 unTimeMs) -{ - PacketCooldowns cooldowns; - WorldPacket data; - time_t curTime = time(NULL); - for (PlayerSpellMap::const_iterator itr = m_spells.begin(); itr != m_spells.end(); ++itr) - { - if (itr->second->state == PLAYERSPELL_REMOVED) - continue; - uint32 unSpellId = itr->first; - SpellInfo const* spellInfo = sSpellMgr->EnsureSpellInfo(unSpellId); - - // Not send cooldown for this spells - if (spellInfo->IsCooldownStartedOnEvent()) - continue; - - if (spellInfo->PreventionType != SPELL_PREVENTION_TYPE_SILENCE) - continue; - - if ((idSchoolMask & spellInfo->GetSchoolMask()) && GetSpellCooldownDelay(unSpellId) < unTimeMs) - { - cooldowns[unSpellId] = unTimeMs; - AddSpellCooldown(unSpellId, 0, curTime + unTimeMs/IN_MILLISECONDS); - } - } - - if (!cooldowns.empty()) - { - BuildCooldownPacket(data, SPELL_COOLDOWN_FLAG_NONE, cooldowns); - GetSession()->SendPacket(&data); - } -} - void Player::InitDataForForm(bool reapplyMods) { ShapeshiftForm form = GetShapeshiftForm(); @@ -22011,206 +21741,6 @@ void Player::UpdatePvP(bool state, bool _override) } } -bool Player::HasSpellCooldown(uint32 spell_id) const -{ - SpellCooldowns::const_iterator itr = m_spellCooldowns.find(spell_id); - return itr != m_spellCooldowns.end() && itr->second.end > time(NULL); -} - -uint32 Player::GetSpellCooldownDelay(uint32 spell_id) const -{ - SpellCooldowns::const_iterator itr = m_spellCooldowns.find(spell_id); - time_t t = time(NULL); - return uint32(itr != m_spellCooldowns.end() && itr->second.end > t ? itr->second.end - t : 0); -} - -void Player::AddSpellAndCategoryCooldowns(SpellInfo const* spellInfo, uint32 itemId, Spell* spell, bool infinityCooldown) -{ - // init cooldown values - uint32 cat = 0; - int32 rec = -1; - int32 catrec = -1; - - // some special item spells without correct cooldown in SpellInfo - // cooldown information stored in item prototype - // This used in same way in WorldSession::HandleItemQuerySingleOpcode data sending to client. - - if (itemId) - { - if (ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId)) - { - for (uint8 idx = 0; idx < MAX_ITEM_SPELLS; ++idx) - { - if (uint32(proto->Spells[idx].SpellId) == spellInfo->Id) - { - cat = proto->Spells[idx].SpellCategory; - rec = proto->Spells[idx].SpellCooldown; - catrec = proto->Spells[idx].SpellCategoryCooldown; - break; - } - } - } - } - - // if no cooldown found above then base at DBC data - if (rec < 0 && catrec < 0) - { - cat = spellInfo->GetCategory(); - rec = spellInfo->RecoveryTime; - catrec = spellInfo->CategoryRecoveryTime; - } - - time_t curTime = time(NULL); - - time_t catrecTime; - time_t recTime; - - bool needsCooldownPacket = false; - - // overwrite time for selected category - if (infinityCooldown) - { - // use +MONTH as infinity mark for spell cooldown (will checked as MONTH/2 at save ans skipped) - // but not allow ignore until reset or re-login - catrecTime = catrec > 0 ? curTime+infinityCooldownDelay : 0; - recTime = rec > 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 (rec <= 0 && catrec <= 0 && (cat == 76 || (spellInfo->IsAutoRepeatRangedSpell() && spellInfo->Id != 75))) - rec = GetAttackTime(RANGED_ATTACK); - - // Now we have cooldown data (if found any), time to apply mods - if (rec > 0) - ApplySpellMod(spellInfo->Id, SPELLMOD_COOLDOWN, rec, spell); - - if (catrec > 0 && !spellInfo->HasAttribute(SPELL_ATTR6_IGNORE_CATEGORY_COOLDOWN_MODS)) - ApplySpellMod(spellInfo->Id, SPELLMOD_COOLDOWN, catrec, spell); - - if (int32 cooldownMod = GetTotalAuraModifier(SPELL_AURA_MOD_COOLDOWN)) - { - // Apply SPELL_AURA_MOD_COOLDOWN only to own spells - if (HasSpell(spellInfo->Id)) - { - needsCooldownPacket = true; - rec += cooldownMod * IN_MILLISECONDS; // SPELL_AURA_MOD_COOLDOWN does not affect category cooldows, verified with shaman shocks - } - } - - // replace negative cooldowns by 0 - if (rec < 0) rec = 0; - if (catrec < 0) catrec = 0; - - // no cooldown after applying spell mods - if (rec == 0 && catrec == 0) - return; - - catrecTime = catrec ? curTime+catrec/IN_MILLISECONDS : 0; - recTime = rec ? curTime+rec/IN_MILLISECONDS : catrecTime; - } - - // self spell cooldown - if (recTime > 0) - { - AddSpellCooldown(spellInfo->Id, itemId, recTime); - - if (needsCooldownPacket) - { - WorldPacket data; - BuildCooldownPacket(data, SPELL_COOLDOWN_FLAG_NONE, spellInfo->Id, rec); - SendDirectMessage(&data); - } - } - - // category spells - if (cat && catrec > 0) - { - SpellCategoryStore::const_iterator i_scstore = sSpellsByCategoryStore.find(cat); - 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; - - AddSpellCooldown(*i_scset, itemId, catrecTime); - } - } - } -} - -void Player::AddSpellCooldown(uint32 spellid, uint32 itemid, time_t end_time) -{ - SpellCooldown sc; - sc.end = end_time; - sc.itemid = itemid; - m_spellCooldowns[spellid] = sc; -} - -void Player::ModifySpellCooldown(uint32 spellId, int32 cooldown) -{ - SpellCooldowns::iterator itr = m_spellCooldowns.find(spellId); - if (itr == m_spellCooldowns.end()) - return; - - time_t now = time(NULL); - if (itr->second.end + (cooldown / IN_MILLISECONDS) > now) - itr->second.end += (cooldown / IN_MILLISECONDS); - else - m_spellCooldowns.erase(itr); - - WorldPacket data(SMSG_MODIFY_COOLDOWN, 4 + 8 + 4); - data << uint32(spellId); // Spell ID - data << uint64(GetGUID()); // Player GUID - data << int32(cooldown); // Cooldown mod in milliseconds - GetSession()->SendPacket(&data); - - TC_LOG_DEBUG("misc", "ModifySpellCooldown:: Player: %s (GUID: %u) Spell: %u cooldown: %u", GetName().c_str(), GetGUIDLow(), spellId, GetSpellCooldownDelay(spellId)); -} - -void Player::SendCooldownEvent(SpellInfo const* spellInfo, uint32 itemId /*= 0*/, Spell* spell /*= NULL*/, bool setCooldown /*= true*/) -{ - // start cooldowns at server side, if any - if (setCooldown) - AddSpellAndCategoryCooldowns(spellInfo, itemId, spell); - - // Send activate cooldown timer (possible 0) at client side - WorldPacket data(SMSG_COOLDOWN_EVENT, 4 + 8); - data << uint32(spellInfo->Id); - data << uint64(GetGUID()); - SendDirectMessage(&data); - - uint32 cat = spellInfo->GetCategory(); - if (cat && spellInfo->CategoryRecoveryTime) - { - SpellCategoryStore::const_iterator ct = sSpellsByCategoryStore.find(cat); - if (ct != sSpellsByCategoryStore.end()) - { - SpellCategorySet const& catSet = ct->second; - for (SpellCooldowns::const_iterator i = m_spellCooldowns.begin(); i != m_spellCooldowns.end(); ++i) - { - if (i->first == spellInfo->Id) // skip main spell, already handled above - continue; - - SpellInfo const* spellInfo2 = sSpellMgr->GetSpellInfo(i->first); - if (!spellInfo2 || !spellInfo2->IsCooldownStartedOnEvent()) - continue; - - if (catSet.find(i->first) != catSet.end()) - { - // Send activate cooldown timer (possible 0) at client side - WorldPacket data(SMSG_COOLDOWN_EVENT, 4 + 8); - data << uint32(i->first); - data << uint64(GetGUID()); - SendDirectMessage(&data); - } - } - } - } -} - void Player::UpdatePotionCooldown(Spell* spell) { // no potion used i combat or still in combat @@ -22222,14 +21752,14 @@ void Player::UpdatePotionCooldown(Spell* spell) { // spell/item pair let set proper cooldown (except not existed charged spell cooldown spellmods for potions) if (ItemTemplate const* proto = sObjectMgr->GetItemTemplate(m_lastPotionId)) - for (uint8 idx = 0; idx < MAX_ITEM_SPELLS; ++idx) + for (uint8 idx = 0; idx < MAX_ITEM_PROTO_SPELLS; ++idx) if (proto->Spells[idx].SpellId && proto->Spells[idx].SpellTrigger == ITEM_SPELLTRIGGER_ON_USE) if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(proto->Spells[idx].SpellId)) - SendCooldownEvent(spellInfo, m_lastPotionId); + GetSpellHistory()->SendCooldownEvent(spellInfo, m_lastPotionId); } // from spell cases (m_lastPotionId set in Spell::SendSpellCooldown) else - SendCooldownEvent(spell->m_spellInfo, m_lastPotionId, spell); + GetSpellHistory()->SendCooldownEvent(spell->m_spellInfo, m_lastPotionId, spell); m_lastPotionId = 0; } @@ -23139,14 +22669,13 @@ void Player::ApplyEquipCooldown(Item* pItem) continue; // Don't replace longer cooldowns by equip cooldown if we have any. - SpellCooldowns::iterator itr = m_spellCooldowns.find(spellData.SpellId); - if (itr != m_spellCooldowns.end() && itr->second.itemid == pItem->GetEntry() && itr->second.end > time(NULL) + 30) + if (GetSpellHistory()->GetRemainingCooldown(spellData.SpellId) > 30 * IN_MILLISECONDS) continue; - AddSpellCooldown(spellData.SpellId, pItem->GetEntry(), time(NULL) + 30); + GetSpellHistory()->AddCooldown(spellData.SpellId, pItem->GetEntry(), std::chrono::seconds(30)); - WorldPacket data(SMSG_ITEM_COOLDOWN, 12); - data << pItem->GetGUID(); + WorldPacket data(SMSG_ITEM_COOLDOWN, 8 + 4); + data << uint64(pItem->GetGUID()); data << uint32(spellData.SpellId); GetSession()->SendPacket(&data); } @@ -24017,7 +23546,7 @@ uint32 Player::GetResurrectionSpellId() } // Reincarnation (passive spell) // prio: 1 // Glyph of Renewed Life - if (prio < 1 && HasSpell(20608) && !HasSpellCooldown(21169) && (HasAura(58059) || HasItemCount(17030))) + if (prio < 1 && HasSpell(20608) && !GetSpellHistory()->HasCooldown(21169) && (HasAura(58059) || HasItemCount(17030))) spell_id = 21169; return spell_id; @@ -26072,14 +25601,6 @@ void Player::RemoveAtLoginFlag(AtLoginFlags flags, bool persist /*= false*/) } } -void Player::SendClearCooldown(uint32 spell_id, Unit* target) -{ - WorldPacket data(SMSG_CLEAR_COOLDOWN, 4+8); - data << uint32(spell_id); - data << uint64(target->GetGUID()); - SendDirectMessage(&data); -} - void Player::ResetMap() { // this may be called during Map::Update diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index e3a7f39b2ba..af640e4c73c 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -128,13 +128,6 @@ typedef std::unordered_map<uint32, PlayerTalent*> PlayerTalentMap; typedef std::unordered_map<uint32, PlayerSpell*> PlayerSpellMap; typedef std::list<SpellModifier*> SpellModList; -struct SpellCooldown -{ - time_t end; - uint16 itemid; -}; - -typedef std::map<uint32, SpellCooldown> SpellCooldowns; typedef std::unordered_map<uint32 /*instanceId*/, time_t/*releaseTime*/> InstanceTimeMap; enum TrainerSpellState @@ -1655,8 +1648,6 @@ class Player : public Unit, public GridObject<Player> PlayerSpellMap const& GetSpellMap() const { return m_spells; } PlayerSpellMap & GetSpellMap() { return m_spells; } - SpellCooldowns const& GetSpellCooldownMap() const { return m_spellCooldowns; } - void AddSpellMod(SpellModifier* mod, bool apply); bool IsAffectedBySpellmod(SpellInfo const* spellInfo, SpellModifier* mod, Spell* spell = NULL); template <class T> T ApplySpellMod(uint32 spellId, SpellModOp op, T &basevalue, Spell* spell = NULL); @@ -1666,26 +1657,7 @@ class Player : public Unit, public GridObject<Player> void DropModCharge(SpellModifier* mod, Spell* spell); void SetSpellModTakingSpell(Spell* spell, bool apply); - static uint32 const infinityCooldownDelay = MONTH; // used for set "infinity cooldowns" for spells and check - static uint32 const infinityCooldownDelayCheck = MONTH/2; - bool HasSpellCooldown(uint32 spell_id) const; - uint32 GetSpellCooldownDelay(uint32 spell_id) const; - void AddSpellAndCategoryCooldowns(SpellInfo const* spellInfo, uint32 itemId, Spell* spell = NULL, bool infinityCooldown = false); - void AddSpellCooldown(uint32 spell_id, uint32 itemid, time_t end_time); - void ModifySpellCooldown(uint32 spellId, int32 cooldown); - void SendCooldownEvent(SpellInfo const* spellInfo, uint32 itemId = 0, Spell* spell = NULL, bool setCooldown = true); - void ProhibitSpellSchool(SpellSchoolMask idSchoolMask, uint32 unTimeMs) override; - void RemoveSpellCooldown(uint32 spell_id, bool update = false); - void RemoveSpellCategoryCooldown(uint32 cat, bool update = false); - void SendClearCooldown(uint32 spell_id, Unit* target); - - GlobalCooldownMgr& GetGlobalCooldownMgr() { return m_GlobalCooldownMgr; } - - void RemoveCategoryCooldown(uint32 cat); void RemoveArenaSpellCooldowns(bool removeActivePetCooldowns = false); - void RemoveAllSpellCooldown(); - void _LoadSpellCooldowns(PreparedQueryResult result); - void _SaveSpellCooldowns(SQLTransaction& trans); uint32 GetLastPotionId() { return m_lastPotionId; } void SetLastPotionId(uint32 item_id) { m_lastPotionId = item_id; } void UpdatePotionCooldown(Spell* spell = NULL); @@ -1975,8 +1947,6 @@ class Player : public Unit, public GridObject<Player> //End of PvP System - inline SpellCooldowns GetSpellCooldowns() const { return m_spellCooldowns; } - void SetDrunkValue(uint8 newDrunkValue, uint32 itemId = 0); uint8 GetDrunkValue() const { return GetByteValue(PLAYER_BYTES_3, 1); } static DrunkenState GetDrunkenstateByValue(uint8 value); @@ -2493,8 +2463,6 @@ class Player : public Unit, public GridObject<Player> PlayerTalentMap* m_talents[MAX_TALENT_SPECS]; uint32 m_lastPotionId; // last used health/mana potion in combat, that block next potion use - GlobalCooldownMgr m_GlobalCooldownMgr; - uint8 m_activeSpec; uint8 m_specsCount; @@ -2662,8 +2630,6 @@ class Player : public Unit, public GridObject<Player> AchievementMgr* m_achievementMgr; ReputationMgr* m_reputationMgr; - SpellCooldowns m_spellCooldowns; - uint32 m_ChampioningFaction; uint32 m_timeSyncCounter; diff --git a/src/server/game/Entities/Totem/Totem.cpp b/src/server/game/Entities/Totem/Totem.cpp index 39a078a907f..85ee51aebf5 100644 --- a/src/server/game/Entities/Totem/Totem.cpp +++ b/src/server/game/Entities/Totem/Totem.cpp @@ -20,6 +20,7 @@ #include "Group.h" #include "Opcodes.h" #include "Player.h" +#include "SpellHistory.h" #include "SpellMgr.h" #include "SpellInfo.h" #include "WorldPacket.h" @@ -128,7 +129,7 @@ void Totem::UnSummon(uint32 msTime) owner->SendAutoRepeatCancel(this); if (SpellInfo const* spell = sSpellMgr->GetSpellInfo(GetUInt32Value(UNIT_CREATED_BY_SPELL))) - owner->SendCooldownEvent(spell, 0, NULL, false); + GetSpellHistory()->SendCooldownEvent(spell, 0, nullptr, false); if (Group* group = owner->GetGroup()) { diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 653426e5687..b33402b75c1 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -51,6 +51,7 @@ #include "SpellAuras.h" #include "Spell.h" #include "SpellInfo.h" +#include "SpellHistory.h" #include "SpellMgr.h" #include "TemporarySummon.h" #include "Totem.h" @@ -163,7 +164,7 @@ Unit::Unit(bool isWorldObject) : i_AI(NULL), i_disabledAI(NULL), m_AutoRepeatFirstCast(false), m_procDeep(0), m_removedAurasCount(0), i_motionMaster(new MotionMaster(this)), m_regenTimer(0), m_ThreatManager(this), m_vehicle(NULL), m_vehicleKit(NULL), m_unitTypeMask(UNIT_MASK_NONE), - m_HostileRefManager(this), _lastDamagedTime(0) + m_HostileRefManager(this), _lastDamagedTime(0), m_spellHistory(new SpellHistory(this)) { m_objectType |= TYPEMASK_UNIT; m_objectTypeId = TYPEID_UNIT; @@ -257,24 +258,6 @@ Unit::Unit(bool isWorldObject) : } //////////////////////////////////////////////////////////// -// Methods of class GlobalCooldownMgr -bool GlobalCooldownMgr::HasGlobalCooldown(SpellInfo const* spellInfo) const -{ - GlobalCooldownList::const_iterator itr = m_GlobalCooldowns.find(spellInfo->StartRecoveryCategory); - return itr != m_GlobalCooldowns.end() && itr->second.duration && getMSTimeDiff(itr->second.cast_time, getMSTime()) < itr->second.duration; -} - -void GlobalCooldownMgr::AddGlobalCooldown(SpellInfo const* spellInfo, uint32 gcd) -{ - m_GlobalCooldowns[spellInfo->StartRecoveryCategory] = GlobalCooldown(gcd, getMSTime()); -} - -void GlobalCooldownMgr::CancelGlobalCooldown(SpellInfo const* spellInfo) -{ - m_GlobalCooldowns[spellInfo->StartRecoveryCategory].duration = 0; -} - -//////////////////////////////////////////////////////////// // Methods of class Unit Unit::~Unit() { @@ -291,6 +274,7 @@ Unit::~Unit() delete i_motionMaster; delete m_charmInfo; delete movespline; + delete m_spellHistory; ASSERT(!m_duringRemoveFromWorld); ASSERT(!m_attacking); @@ -2925,6 +2909,8 @@ void Unit::_UpdateSpells(uint32 time) ++itr; } } + + m_spellHistory->Update(); } void Unit::_UpdateAutoRepeatSpell() @@ -4860,13 +4846,13 @@ void Unit::AddGameObject(GameObject* gameObj) m_gameObj.push_back(gameObj); gameObj->SetOwnerGUID(GetGUID()); - if (GetTypeId() == TYPEID_PLAYER && gameObj->GetSpellId()) + if (gameObj->GetSpellId()) { SpellInfo const* createBySpell = sSpellMgr->GetSpellInfo(gameObj->GetSpellId()); // Need disable spell use for owner if (createBySpell && createBySpell->IsCooldownStartedOnEvent()) // note: item based cooldowns and cooldown spell mods with charges ignored (unknown existing cases) - ToPlayer()->AddSpellAndCategoryCooldowns(createBySpell, 0, NULL, true); + GetSpellHistory()->StartCooldown(createBySpell, 0, nullptr, true); } } @@ -4891,14 +4877,11 @@ void Unit::RemoveGameObject(GameObject* gameObj, bool del) { RemoveAurasDueToSpell(spellid); - if (GetTypeId() == TYPEID_PLAYER) - { - SpellInfo const* createBySpell = sSpellMgr->GetSpellInfo(spellid); - // Need activate spell use for owner - if (createBySpell && createBySpell->IsCooldownStartedOnEvent()) - // note: item based cooldowns and cooldown spell mods with charges ignored (unknown existing cases) - ToPlayer()->SendCooldownEvent(createBySpell); - } + SpellInfo const* createBySpell = sSpellMgr->GetSpellInfo(spellid); + // Need activate spell use for owner + if (createBySpell && createBySpell->IsCooldownStartedOnEvent()) + // note: item based cooldowns and cooldown spell mods with charges ignored (unknown existing cases) + GetSpellHistory()->SendCooldownEvent(createBySpell); } m_gameObj.remove(gameObj); @@ -5488,8 +5471,8 @@ bool Unit::HandleDummyAuraProc(Unit* victim, uint32 damage, AuraEffect* triggere CastSpell(target, RandomSpells[rand_spell], true, castItem, triggeredByAura, originalCaster); for (std::vector<uint32>::iterator itr = RandomSpells.begin(); itr != RandomSpells.end(); ++itr) { - if (!ToPlayer()->HasSpellCooldown(*itr)) - ToPlayer()->AddSpellCooldown(*itr, 0, time(NULL) + cooldown); + if (!GetSpellHistory()->HasCooldown(*itr)) + GetSpellHistory()->AddCooldown(*itr, 0, std::chrono::seconds(cooldown)); } break; } @@ -5534,8 +5517,8 @@ bool Unit::HandleDummyAuraProc(Unit* victim, uint32 damage, AuraEffect* triggere CastSpell(target, RandomSpells[rand_spell], true, castItem, triggeredByAura, originalCaster); for (std::vector<uint32>::iterator itr = RandomSpells.begin(); itr != RandomSpells.end(); ++itr) { - if (!ToPlayer()->HasSpellCooldown(*itr)) - ToPlayer()->AddSpellCooldown(*itr, 0, time(NULL) + cooldown); + if (!GetSpellHistory()->HasCooldown(*itr)) + GetSpellHistory()->AddCooldown(*itr, 0, std::chrono::seconds(cooldown)); } break; } @@ -5639,20 +5622,17 @@ bool Unit::HandleDummyAuraProc(Unit* victim, uint32 damage, AuraEffect* triggere // Glyph of Ice Block case 56372: { - Player* player = ToPlayer(); - if (!player) + if (GetTypeId() != TYPEID_PLAYER) return false; - SpellCooldowns const cooldowns = player->GetSpellCooldowns(); - // remove cooldowns on all ranks of Frost Nova - for (SpellCooldowns::const_iterator itr = cooldowns.begin(); itr != cooldowns.end(); ++itr) + GetSpellHistory()->ResetCooldowns([](SpellHistory::CooldownStorageType::iterator itr) -> bool { SpellInfo const* cdSpell = sSpellMgr->GetSpellInfo(itr->first); - // Frost Nova - if (cdSpell && cdSpell->SpellFamilyName == SPELLFAMILY_MAGE - && cdSpell->SpellFamilyFlags[0] & 0x00000040) - player->RemoveSpellCooldown(cdSpell->Id, true); - } + if (!cdSpell || cdSpell->SpellFamilyName != SPELLFAMILY_MAGE + || !(cdSpell->SpellFamilyFlags[0] & 0x00000040)) + return false; + return true; + }, true); break; } case 47020: // Enter vehicle XT-002 (Scrapbot) @@ -6061,7 +6041,7 @@ bool Unit::HandleDummyAuraProc(Unit* victim, uint32 damage, AuraEffect* triggere int32 basepoints1 = CalculatePct(GetMaxPower(Powers(POWER_MANA)), triggerAmount * 2); // Improved Leader of the Pack // Check cooldown of heal spell cooldown - if (GetTypeId() == TYPEID_PLAYER && !ToPlayer()->HasSpellCooldown(34299)) + if (!GetSpellHistory()->HasCooldown(34299)) CastCustomSpell(this, 68285, &basepoints1, 0, 0, true, 0, triggeredByAura); break; } @@ -6732,7 +6712,7 @@ bool Unit::HandleDummyAuraProc(Unit* victim, uint32 damage, AuraEffect* triggere return false; // custom cooldown processing case - if (cooldown && player->HasSpellCooldown(dummySpell->Id)) + if (cooldown && GetSpellHistory()->HasCooldown(dummySpell->Id)) return false; if (triggeredByAura->GetBase() && castItem->GetGUID() != triggeredByAura->GetBase()->GetCastItemGUID()) @@ -6798,7 +6778,7 @@ bool Unit::HandleDummyAuraProc(Unit* victim, uint32 damage, AuraEffect* triggere // apply cooldown before cast to prevent processing itself if (cooldown) - player->AddSpellCooldown(dummySpell->Id, 0, time(NULL) + cooldown); + player->GetSpellHistory()->AddCooldown(dummySpell->Id, 0, std::chrono::seconds(cooldown)); // Attack Twice for (uint32 i = 0; i < 2; ++i) @@ -7040,7 +7020,7 @@ bool Unit::HandleDummyAuraProc(Unit* victim, uint32 damage, AuraEffect* triggere return false; // custom cooldown processing case - if (cooldown && GetTypeId() == TYPEID_PLAYER && ToPlayer()->HasSpellCooldown(dummySpell->Id)) + if (cooldown && GetTypeId() == TYPEID_PLAYER && GetSpellHistory()->HasCooldown(dummySpell->Id)) return false; uint32 spellId = 0; @@ -7087,13 +7067,13 @@ bool Unit::HandleDummyAuraProc(Unit* victim, uint32 damage, AuraEffect* triggere return false; // Remove cooldown (Chain Lightning - has Category Recovery time) - ToPlayer()->RemoveSpellCooldown(spellId); + GetSpellHistory()->ResetCooldown(spellId); } CastSpell(victim, spellId, true, castItem, triggeredByAura); if (cooldown && GetTypeId() == TYPEID_PLAYER) - ToPlayer()->AddSpellCooldown(dummySpell->Id, 0, time(NULL) + cooldown); + GetSpellHistory()->AddCooldown(dummySpell->Id, 0, std::chrono::seconds(cooldown)); return true; } @@ -7106,8 +7086,8 @@ bool Unit::HandleDummyAuraProc(Unit* victim, uint32 damage, AuraEffect* triggere uint32 spell = sSpellMgr->GetSpellWithRank(26364, aurEff->GetSpellInfo()->GetRank()); // custom cooldown processing case - if (GetTypeId() == TYPEID_PLAYER && ToPlayer()->HasSpellCooldown(spell)) - ToPlayer()->RemoveSpellCooldown(spell); + if (GetTypeId() == TYPEID_PLAYER && GetSpellHistory()->HasCooldown(spell)) + GetSpellHistory()->ResetCooldown(spell); CastSpell(target, spell, true, castItem, triggeredByAura); aurEff->GetBase()->DropCharge(); @@ -7399,7 +7379,7 @@ bool Unit::HandleDummyAuraProc(Unit* victim, uint32 damage, AuraEffect* triggere if (cooldown_spell_id == 0) cooldown_spell_id = triggered_spell_id; - if (cooldown && GetTypeId() == TYPEID_PLAYER && ToPlayer()->HasSpellCooldown(cooldown_spell_id)) + if (cooldown && GetTypeId() == TYPEID_PLAYER && GetSpellHistory()->HasCooldown(cooldown_spell_id)) return false; if (basepoints0) @@ -7408,7 +7388,7 @@ bool Unit::HandleDummyAuraProc(Unit* victim, uint32 damage, AuraEffect* triggere CastSpell(target, triggered_spell_id, true, castItem, triggeredByAura, originalCaster); if (cooldown && GetTypeId() == TYPEID_PLAYER) - ToPlayer()->AddSpellCooldown(cooldown_spell_id, 0, time(NULL) + cooldown); + GetSpellHistory()->AddCooldown(cooldown_spell_id, 0, std::chrono::seconds(cooldown)); return true; } @@ -7629,9 +7609,9 @@ bool Unit::HandleAuraProc(Unit* victim, uint32 damage, Aura* triggeredByAura, Sp *handled = true; if (cooldown && GetTypeId() == TYPEID_PLAYER) { - if (ToPlayer()->HasSpellCooldown(100000)) + if (GetSpellHistory()->HasCooldown(100000)) return false; - ToPlayer()->AddSpellCooldown(100000, 0, time(NULL) + cooldown); + GetSpellHistory()->AddCooldown(100000, 0, std::chrono::seconds(cooldown)); } return true; } @@ -8300,7 +8280,11 @@ bool Unit::HandleProcTriggerSpell(Unit* victim, uint32 damage, AuraEffect* trigg return false; // Howling Blast - ToPlayer()->RemoveSpellCategoryCooldown(1248, true); + GetSpellHistory()->ResetCooldowns([](SpellHistory::CooldownStorageType::iterator itr) -> bool + { + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(itr->first); + return spellInfo && spellInfo->GetCategory() == 1248; + }, true); } // Custom basepoints/target for exist spell @@ -8319,13 +8303,13 @@ bool Unit::HandleProcTriggerSpell(Unit* victim, uint32 damage, AuraEffect* trigg if (!target) return false; - if (cooldown && target->GetTypeId() == TYPEID_PLAYER && target->ToPlayer()->HasSpellCooldown(trigger_spell_id)) + if (cooldown && target->GetTypeId() == TYPEID_PLAYER && target->GetSpellHistory()->HasCooldown(trigger_spell_id)) return false; target->CastSpell(target, trigger_spell_id, true, castItem, triggeredByAura); if (cooldown && GetTypeId() == TYPEID_PLAYER) - ToPlayer()->AddSpellCooldown(trigger_spell_id, 0, time(NULL) + cooldown); + GetSpellHistory()->AddCooldown(trigger_spell_id, 0, std::chrono::seconds(cooldown)); return true; } // Cast positive spell on enemy target @@ -8408,8 +8392,11 @@ bool Unit::HandleProcTriggerSpell(Unit* victim, uint32 damage, AuraEffect* trigg case 50227: { // Remove cooldown on Shield Slam - if (GetTypeId() == TYPEID_PLAYER) - ToPlayer()->RemoveSpellCategoryCooldown(1209, true); + GetSpellHistory()->ResetCooldowns([](SpellHistory::CooldownStorageType::iterator itr) -> bool + { + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(itr->first); + return spellInfo && spellInfo->GetCategory() == 1209; + }, true); break; } // Maelstrom Weapon @@ -8469,8 +8456,7 @@ bool Unit::HandleProcTriggerSpell(Unit* victim, uint32 damage, AuraEffect* trigg case 58628: { // remove cooldown of Death Grip - if (GetTypeId() == TYPEID_PLAYER) - ToPlayer()->RemoveSpellCooldown(49576, true); + GetSpellHistory()->ResetCooldown(49576, true); return true; } // Savage Defense @@ -8501,7 +8487,7 @@ bool Unit::HandleProcTriggerSpell(Unit* victim, uint32 damage, AuraEffect* trigg } } - if (cooldown && GetTypeId() == TYPEID_PLAYER && ToPlayer()->HasSpellCooldown(trigger_spell_id)) + if (cooldown && GetTypeId() == TYPEID_PLAYER && GetSpellHistory()->HasCooldown(trigger_spell_id)) return false; // extra attack should hit same target @@ -8518,7 +8504,7 @@ bool Unit::HandleProcTriggerSpell(Unit* victim, uint32 damage, AuraEffect* trigg CastSpell(target, trigger_spell_id, true, castItem, triggeredByAura); if (cooldown && GetTypeId() == TYPEID_PLAYER) - ToPlayer()->AddSpellCooldown(trigger_spell_id, 0, time(NULL) + cooldown); + GetSpellHistory()->AddCooldown(trigger_spell_id, 0, std::chrono::seconds(cooldown)); return true; } @@ -8614,13 +8600,13 @@ bool Unit::HandleOverrideClassScriptAuraProc(Unit* victim, uint32 /*damage*/, Au return false; } - if (cooldown && GetTypeId() == TYPEID_PLAYER && ToPlayer()->HasSpellCooldown(triggered_spell_id)) + if (cooldown && GetTypeId() == TYPEID_PLAYER && GetSpellHistory()->HasCooldown(triggered_spell_id)) return false; CastSpell(victim, triggered_spell_id, true, castItem, triggeredByAura); if (cooldown && GetTypeId() == TYPEID_PLAYER) - ToPlayer()->AddSpellCooldown(triggered_spell_id, 0, time(NULL) + cooldown); + GetSpellHistory()->AddCooldown(triggered_spell_id, 0, std::chrono::seconds(cooldown)); return true; } @@ -9355,14 +9341,11 @@ void Unit::SetMinion(Minion *minion, bool apply) if (minion->IsPetGhoul()) minion->setPowerType(POWER_ENERGY); - if (GetTypeId() == TYPEID_PLAYER) - { - // Send infinity cooldown - client does that automatically but after relog cooldown needs to be set again - SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(minion->GetUInt32Value(UNIT_CREATED_BY_SPELL)); + // Send infinity cooldown - client does that automatically but after relog cooldown needs to be set again + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(minion->GetUInt32Value(UNIT_CREATED_BY_SPELL)); - if (spellInfo && (spellInfo->IsCooldownStartedOnEvent())) - ToPlayer()->AddSpellAndCategoryCooldowns(spellInfo, 0, NULL, true); - } + if (spellInfo && (spellInfo->IsCooldownStartedOnEvent())) + GetSpellHistory()->StartCooldown(spellInfo, 0, nullptr, true); } else { @@ -9396,13 +9379,10 @@ void Unit::SetMinion(Minion *minion, bool apply) } } - if (GetTypeId() == TYPEID_PLAYER) - { - SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(minion->GetUInt32Value(UNIT_CREATED_BY_SPELL)); - // Remove infinity cooldown - if (spellInfo && (spellInfo->IsCooldownStartedOnEvent())) - ToPlayer()->SendCooldownEvent(spellInfo); - } + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(minion->GetUInt32Value(UNIT_CREATED_BY_SPELL)); + // Remove infinity cooldown + if (spellInfo && (spellInfo->IsCooldownStartedOnEvent())) + GetSpellHistory()->SendCooldownEvent(spellInfo); //if (minion->HasUnitTypeMask(UNIT_MASK_GUARDIAN)) { @@ -17778,27 +17758,6 @@ void Unit::BuildValuesUpdate(uint8 updateType, ByteBuffer* data, Player* target) data->append(fieldBuffer); } -void Unit::BuildCooldownPacket(WorldPacket& data, uint8 flags, uint32 spellId, uint32 cooldown) -{ - data.Initialize(SMSG_SPELL_COOLDOWN, 8 + 1 + 4 + 4); - data << uint64(GetGUID()); - data << uint8(flags); - data << uint32(spellId); - data << uint32(cooldown); -} - -void Unit::BuildCooldownPacket(WorldPacket& data, uint8 flags, PacketCooldowns const& cooldowns) -{ - data.Initialize(SMSG_SPELL_COOLDOWN, 8 + 1 + (4 + 4) * cooldowns.size()); - data << uint64(GetGUID()); - data << uint8(flags); - for (std::unordered_map<uint32, uint32>::const_iterator itr = cooldowns.begin(); itr != cooldowns.end(); ++itr) - { - data << uint32(itr->first); - data << uint32(itr->second); - } -} - int32 Unit::GetHighestExclusiveSameEffectSpellGroupValue(AuraEffect const* aurEff, AuraType auraType, bool checkMiscValue /*= false*/, int32 miscValue /*= 0*/) const { int32 val = 0; diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h index ddf43f30971..088a0bbdf73 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -339,6 +339,7 @@ class AuraEffect; class Creature; class Spell; class SpellInfo; +class SpellHistory; class DynamicObject; class GameObject; class Item; @@ -1031,30 +1032,6 @@ enum CurrentSpellTypes #define CURRENT_FIRST_NON_MELEE_SPELL 1 #define CURRENT_MAX_SPELL 4 -struct GlobalCooldown -{ - explicit GlobalCooldown(uint32 _dur = 0, uint32 _time = 0) : duration(_dur), cast_time(_time) { } - - uint32 duration; - uint32 cast_time; -}; - -typedef std::unordered_map<uint32 /*category*/, GlobalCooldown> GlobalCooldownList; - -class GlobalCooldownMgr // Shared by Player and CharmInfo -{ -public: - GlobalCooldownMgr() { } - -public: - bool HasGlobalCooldown(SpellInfo const* spellInfo) const; - void AddGlobalCooldown(SpellInfo const* spellInfo, uint32 gcd); - void CancelGlobalCooldown(SpellInfo const* spellInfo); - -private: - GlobalCooldownList m_GlobalCooldowns; -}; - enum ActiveStates { ACT_PASSIVE = 0x01, // 0x01 - passive @@ -1171,8 +1148,6 @@ struct CharmInfo CharmSpellInfo* GetCharmSpell(uint8 index) { return &(_charmspells[index]); } - GlobalCooldownMgr& GetGlobalCooldownMgr() { return m_GlobalCooldownMgr; } - void SetIsCommandAttack(bool val); bool IsCommandAttack(); void SetIsCommandFollow(bool val); @@ -1205,8 +1180,6 @@ struct CharmInfo float _stayX; float _stayY; float _stayZ; - - GlobalCooldownMgr m_GlobalCooldownMgr; }; // for clearing special attacks @@ -1237,16 +1210,6 @@ enum PlayerTotemType SUMMON_TYPE_TOTEM_AIR = 83 }; -/// 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 -}; - -typedef std::unordered_map<uint32, uint32> PacketCooldowns; - // delay time next attack to prevent client attack animation problems #define ATTACK_DISPLAY_DELAY 200 #define MAX_PLAYER_STEALTH_DETECT_RANGE 30.0f // max distance for detection targets by player @@ -1585,8 +1548,6 @@ class Unit : public WorldObject void SetAuraStack(uint32 spellId, Unit* target, uint32 stack); void SendPlaySpellVisual(uint32 id); void SendPlaySpellImpact(ObjectGuid guid, uint32 id); - void BuildCooldownPacket(WorldPacket& data, uint8 flags, uint32 spellId, uint32 cooldown); - void BuildCooldownPacket(WorldPacket& data, uint8 flags, PacketCooldowns const& cooldowns); void DeMorph(); @@ -1845,7 +1806,6 @@ class Unit : public WorldObject void SetChannelObjectGuid(ObjectGuid guid) { SetGuidValue(UNIT_FIELD_CHANNEL_OBJECT, guid); } void SetCurrentCastSpell(Spell* pSpell); - virtual void ProhibitSpellSchool(SpellSchoolMask /*idSchoolMask*/, uint32 /*unTimeMs*/) { } void InterruptSpell(CurrentSpellTypes spellType, bool withDelayed = true, bool withInstant = true); void FinishSpell(CurrentSpellTypes spellType, bool ok = true); @@ -1863,6 +1823,9 @@ class Unit : public WorldObject Spell* FindCurrentSpellBySpellId(uint32 spell_id) const; int32 GetCurrentSpellCastTime(uint32 spell_id) const; + SpellHistory* GetSpellHistory() { return m_spellHistory; } + SpellHistory const* GetSpellHistory() const { return m_spellHistory; } + ObjectGuid m_SummonSlot[MAX_SUMMON_SLOT]; ObjectGuid m_ObjectSlot[MAX_GAMEOBJECT_SLOT]; @@ -2293,6 +2256,8 @@ class Unit : public WorldObject bool _isWalkingBeforeCharm; ///< Are we walking before we were charmed? time_t _lastDamagedTime; // Part of Evade mechanics + + SpellHistory* m_spellHistory; }; namespace Trinity diff --git a/src/server/game/Handlers/PetHandler.cpp b/src/server/game/Handlers/PetHandler.cpp index 9ff2041ee92..0bf33eee234 100644 --- a/src/server/game/Handlers/PetHandler.cpp +++ b/src/server/game/Handlers/PetHandler.cpp @@ -30,6 +30,7 @@ #include "Pet.h" #include "World.h" #include "Group.h" +#include "SpellHistory.h" #include "SpellInfo.h" #include "Player.h" @@ -344,8 +345,6 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe if (result == SPELL_CAST_OK) { - pet->ToCreature()->AddCreatureSpellCooldown(spellid); - unit_target = spell->m_targets.GetUnitTarget(); //10% chance to play special pet attack talk, else growl @@ -379,8 +378,8 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe else spell->SendPetCastResult(result); - if (!pet->ToCreature()->HasSpellCooldown(spellid)) - GetPlayer()->SendClearCooldown(spellid, pet); + if (!pet->GetSpellHistory()->HasCooldown(spellid)) + pet->GetSpellHistory()->ResetCooldown(spellid, true); spell->finish(false); delete spell; @@ -794,7 +793,6 @@ void WorldSession::HandlePetCastSpellOpcode(WorldPacket& recvPacket) { if (Creature* creature = caster->ToCreature()) { - creature->AddCreatureSpellCooldown(spellId); if (Pet* pet = creature->ToPet()) { // 10% chance to play special pet attack talk, else growl @@ -812,16 +810,8 @@ void WorldSession::HandlePetCastSpellOpcode(WorldPacket& recvPacket) { spell->SendPetCastResult(result); - if (caster->GetTypeId() == TYPEID_PLAYER) - { - if (!caster->ToPlayer()->HasSpellCooldown(spellId)) - GetPlayer()->SendClearCooldown(spellId, caster); - } - else - { - if (!caster->ToCreature()->HasSpellCooldown(spellId)) - GetPlayer()->SendClearCooldown(spellId, caster); - } + if (!caster->GetSpellHistory()->HasCooldown(spellId)) + caster->GetSpellHistory()->ResetCooldown(spellId, true); spell->finish(false); delete spell; diff --git a/src/server/game/Handlers/SpellHandler.cpp b/src/server/game/Handlers/SpellHandler.cpp index 5f641bf713f..a6108b36c13 100644 --- a/src/server/game/Handlers/SpellHandler.cpp +++ b/src/server/game/Handlers/SpellHandler.cpp @@ -491,8 +491,6 @@ void WorldSession::HandlePetCancelAuraOpcode(WorldPacket& recvPacket) } pet->RemoveOwnedAura(spellId, ObjectGuid::Empty, 0, AURA_REMOVE_BY_CANCEL); - - pet->AddCreatureSpellCooldown(spellId); } void WorldSession::HandleCancelGrowthAuraOpcode(WorldPacket& /*recvPacket*/) { } diff --git a/src/server/game/Miscellaneous/SharedDefines.h b/src/server/game/Miscellaneous/SharedDefines.h index 685bcd338a9..06c58a408da 100644 --- a/src/server/game/Miscellaneous/SharedDefines.h +++ b/src/server/game/Miscellaneous/SharedDefines.h @@ -3599,4 +3599,12 @@ enum DiminishingLevels DIMINISHING_LEVEL_TAUNT_IMMUNE = 4 }; +/// 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 +}; + #endif diff --git a/src/server/game/Spells/Auras/SpellAuraEffects.cpp b/src/server/game/Spells/Auras/SpellAuraEffects.cpp index de2dc26b643..fa40d4c97a1 100644 --- a/src/server/game/Spells/Auras/SpellAuraEffects.cpp +++ b/src/server/game/Spells/Auras/SpellAuraEffects.cpp @@ -27,6 +27,7 @@ #include "ObjectAccessor.h" #include "Util.h" #include "Spell.h" +#include "SpellHistory.h" #include "SpellAuraEffects.h" #include "Battleground.h" #include "OutdoorPvPMgr.h" @@ -1108,15 +1109,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 0ba059b1952..5479dcdbf00 100644 --- a/src/server/game/Spells/Auras/SpellAuras.cpp +++ b/src/server/game/Spells/Auras/SpellAuras.cpp @@ -25,6 +25,7 @@ #include "Player.h" #include "Unit.h" #include "Spell.h" +#include "SpellHistory.h" #include "SpellAuraEffects.h" #include "DynamicObject.h" #include "ObjectAccessor.h" @@ -418,7 +419,7 @@ void Aura::_ApplyForTarget(Unit* target, Unit* caster, AuraApplication * auraApp if (m_spellInfo->IsCooldownStartedOnEvent()) { Item* castItem = m_castItemGuid ? 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); } } } @@ -446,12 +447,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 @@ -1215,7 +1213,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; @@ -1497,15 +1495,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 @@ -1558,14 +1556,14 @@ void Aura::HandleAuraSpecificMods(AuraApplication const* aurApp, Unit* caster, b // Glyph of Guardian Spirit if (AuraEffect* aurEff = player->GetAuraEffect(63231, 0)) { - if (!player->HasSpellCooldown(47788)) + if (!player->GetSpellHistory()->HasCooldown(47788)) break; - player->RemoveSpellCooldown(GetSpellInfo()->Id, true); - player->AddSpellCooldown(GetSpellInfo()->Id, 0, uint32(time(NULL) + aurEff->GetAmount())); + player->GetSpellHistory()->ResetCooldown(GetSpellInfo()->Id, true); + player->GetSpellHistory()->AddCooldown(GetSpellInfo()->Id, 0, std::chrono::seconds(aurEff->GetAmount())); WorldPacket data; - player->BuildCooldownPacket(data, SPELL_COOLDOWN_FLAG_NONE, GetSpellInfo()->Id, aurEff->GetAmount()*IN_MILLISECONDS); + player->GetSpellHistory()->BuildCooldownPacket(data, SPELL_COOLDOWN_FLAG_NONE, GetSpellInfo()->Id, aurEff->GetAmount() * IN_MILLISECONDS); player->SendDirectMessage(&data); } break; diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index dbe87daa1a1..11ec5bf05a5 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -50,6 +50,7 @@ #include "SpellScript.h" #include "InstanceScript.h" #include "SpellInfo.h" +#include "SpellHistory.h" #include "Battlefield.h" #include "BattlefieldMgr.h" @@ -3226,7 +3227,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); @@ -3430,30 +3431,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) @@ -4635,23 +4613,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)) @@ -5504,13 +5485,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); @@ -5801,7 +5782,7 @@ SpellCastResult Spell::CheckItems() if (!proto) return SPELL_FAILED_ITEM_NOT_READY; - for (uint8 i = 0; i < MAX_ITEM_SPELLS; ++i) + for (uint8 i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i) if (proto->Spells[i].SpellCharges) if (m_CastItem->GetSpellCharges(i) == 0) return SPELL_FAILED_NO_CHARGES_REMAIN; @@ -7230,13 +7211,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() @@ -7245,6 +7224,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; @@ -7266,11 +7249,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() @@ -7283,10 +7262,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); } namespace Trinity diff --git a/src/server/game/Spells/Spell.h b/src/server/game/Spells/Spell.h index 2ae2930aeb0..1aac88ac602 100644 --- a/src/server/game/Spells/Spell.h +++ b/src/server/game/Spells/Spell.h @@ -473,6 +473,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 cb518ee6099..3829e12b790 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -48,6 +48,7 @@ #include "GridNotifiers.h" #include "Formulas.h" #include "ScriptMgr.h" +#include "SpellHistory.h" #include "GameObjectAI.h" #include "AccountMgr.h" #include "InstanceScript.h" @@ -769,8 +770,8 @@ void Spell::EffectTriggerSpell(SpellEffIndex effIndex) return; // Reset cooldown on stealth if needed - if (unitTarget->ToPlayer()->HasSpellCooldown(1784)) - unitTarget->ToPlayer()->RemoveSpellCooldown(1784); + if (unitTarget->GetSpellHistory()->HasCooldown(1784)) + unitTarget->GetSpellHistory()->ResetCooldown(1784); unitTarget->CastSpell(unitTarget, 1784, true); return; @@ -879,7 +880,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); @@ -932,7 +933,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); @@ -3472,7 +3473,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); @@ -5839,7 +5840,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_ATTR7_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..b16993f28a3 --- /dev/null +++ b/src/server/game/Spells/SpellHistory.cpp @@ -0,0 +1,641 @@ +/* + * 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 "WorldPacket.h" +#include "SpellHistory.h" +#include "Pet.h" +#include "Player.h" +#include "SpellInfo.h" +#include "Spell.h" +#include "World.h" + +SpellHistory::Clock::duration const SpellHistory::InfinityCooldownDelay = std::chrono::duration_cast<SpellHistory::Clock::duration>(std::chrono::seconds(MONTH)); +SpellHistory::Clock::duration const SpellHistory::InfinityCooldownDelayCheck = std::chrono::duration_cast<SpellHistory::Clock::duration>(std::chrono::seconds(MONTH / 2)); + +template<> +struct SpellHistory::PersistenceHelper<Player> +{ + static CharacterDatabaseStatements const CooldownsDeleteStatement = CHAR_DEL_CHAR_SPELL_COOLDOWNS; + static CharacterDatabaseStatements const CooldownsInsertStatement = CHAR_INS_CHAR_SPELL_COOLDOWN; + + static void SetIdentifier(PreparedStatement* stmt, uint8 index, Unit* owner) { stmt->setUInt32(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 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))); + } +}; + +template<> +struct SpellHistory::PersistenceHelper<Pet> +{ + static CharacterDatabaseStatements const CooldownsDeleteStatement = CHAR_DEL_PET_SPELL_COOLDOWNS; + static CharacterDatabaseStatements const CooldownsInsertStatement = CHAR_INS_PET_SPELL_COOLDOWN; + + 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 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))); + } +}; + +template<class OwnerType> +void SpellHistory::LoadFromDB(PreparedQueryResult cooldownsResult) +{ + typedef PersistenceHelper<OwnerType> StatementInfo; + + if (cooldownsResult) + { + do + { + uint32 spellId; + CooldownEntry cooldown; + if (StatementInfo::ReadCooldown(cooldownsResult->Fetch(), &spellId, &cooldown)) + _spellCooldowns[spellId] = cooldown; + + } while (cooldownsResult->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); + } + } +} + +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; + } +} + +void SpellHistory::HandleCooldowns(SpellInfo const* spellInfo, Item const* item, Spell* spell /*= nullptr*/) +{ + 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; + + return true; +} + +template<> +void SpellHistory::WritePacket<Pet>(WorldPacket& packet) const +{ + Clock::time_point now = Clock::now(); + + uint8 cooldownsCount = _spellCooldowns.size(); + packet << uint8(cooldownsCount); + + for (auto const& spellCooldown : _spellCooldowns) + { + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellCooldown.first); + if (!spellInfo) + { + packet << uint32(0); + packet << uint16(0); + packet << uint32(0); + packet << uint32(0); + continue; + } + + packet << uint32(spellCooldown.first); // spell ID + packet << uint16(spellInfo->GetCategory()); // spell category + if (!spellCooldown.second.OnHold) + { + uint32 cooldownDuration = spellCooldown.second.CooldownEnd > now ? std::chrono::duration_cast<std::chrono::milliseconds>(spellCooldown.second.CooldownEnd - now).count() : 0; + if (cooldownDuration <= 0) + { + packet << uint32(0); + packet << uint32(0); + continue; + } + + if (spellInfo->GetCategory()) + { + packet << uint32(0); + packet << uint32(cooldownDuration); + } + else + { + packet << uint32(cooldownDuration); + packet << uint32(0); + } + } + } +} + +template<> +void SpellHistory::WritePacket<Player>(WorldPacket& packet) const +{ + Clock::time_point now = Clock::now(); + Clock::time_point infTime = now + InfinityCooldownDelayCheck; + + uint16 cooldownsCount = _spellCooldowns.size(); + size_t dataPos = packet.wpos(); + packet << uint16(cooldownsCount); + + for (auto const& spellCooldown : _spellCooldowns) + { + SpellInfo const* sEntry = sSpellMgr->GetSpellInfo(spellCooldown.first); + if (!sEntry) + { + --cooldownsCount; + continue; + } + + packet << uint32(spellCooldown.first); + + packet << uint16(spellCooldown.second.ItemId); // cast item id + packet << uint16(sEntry->GetCategory()); // spell category + + // send infinity cooldown in special format + if (spellCooldown.second.CooldownEnd >= infTime) + { + packet << uint32(1); // cooldown + packet << uint32(0x80000000); // category cooldown + continue; + } + + uint32 cooldownDuration = spellCooldown.second.CooldownEnd > now ? std::chrono::duration_cast<std::chrono::milliseconds>(spellCooldown.second.CooldownEnd - now).count() : 0; + + if (sEntry->GetCategory()) // may be wrong, but anyway better than nothing... + { + packet << uint32(0); // cooldown + packet << uint32(cooldownDuration); // category cooldown + } + else + { + packet << uint32(cooldownDuration); // cooldown + packet << uint32(0); // category cooldown + } + } + + packet.put<uint16>(dataPos, cooldownsCount); +} + +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 < MAX_ITEM_PROTO_SPELLS; ++idx) + { + if (uint32(proto->Spells[idx].SpellId) == spellInfo->Id) + { + categoryId = proto->Spells[idx].SpellCategory; + cooldown = proto->Spells[idx].SpellCooldown; + categoryCooldown = proto->Spells[idx].SpellCategoryCooldown; + 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 + } + } + + // 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()) + { + WorldPacket spellCooldown; + BuildCooldownPacket(spellCooldown, SPELL_COOLDOWN_FLAG_NONE, spellInfo->Id, cooldown); + playerOwner->SendDirectMessage(&spellCooldown); + } + } + } + + // 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 + WorldPacket data(SMSG_COOLDOWN_EVENT, 4 + 8); + data << uint32(spellInfo->Id); + data << uint64(_owner->GetGUID()); + player->SendDirectMessage(&data); + + 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->EnsureSpellInfo(categorySpell); + if (!spellInfo2->IsCooldownStartedOnEvent()) + continue; + + data.Initialize(SMSG_COOLDOWN_EVENT, 4 + 8); + data << uint32(categorySpell); + data << uint64(_owner->GetGUID()); + player->SendDirectMessage(&data); + } + } + } + } +} + +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()) + { + WorldPacket modifyCooldown(SMSG_MODIFY_COOLDOWN, 4 + 8 + 4); + modifyCooldown << uint32(spellId); + modifyCooldown << uint64(_owner->GetGUID()); + modifyCooldown << int32(cooldownModMs); + playerOwner->SendDirectMessage(&modifyCooldown); + } +} + +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()) + { + WorldPacket data(SMSG_CLEAR_COOLDOWN, 4 + 8); + data << uint32(itr->first); + data << uint64(_owner->GetGUID()); + playerOwner->SendDirectMessage(&data); + } + } + + itr = _spellCooldowns.erase(itr); +} + +void SpellHistory::ResetAllCooldowns() +{ + if (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 != PETSPELL_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]); + } + + PacketCooldowns cooldowns; + WorldPacket spellCooldowns; + for (uint32 spellId : knownSpells) + { + SpellInfo const* spellInfo = sSpellMgr->EnsureSpellInfo(spellId); + if (spellInfo->IsCooldownStartedOnEvent()) + continue; + + if (spellInfo->PreventionType != SPELL_PREVENTION_TYPE_SILENCE) + continue; + + if ((schoolMask & spellInfo->GetSchoolMask()) && GetRemainingCooldown(spellId) < lockoutTime) + { + cooldowns[spellId] = lockoutTime; + AddCooldown(spellId, 0, lockoutEnd); + } + } + + if (Player* player = GetPlayerOwner()) + { + if (!cooldowns.empty()) + { + BuildCooldownPacket(spellCooldowns, SPELL_COOLDOWN_FLAG_NONE, cooldowns); + player->SendDirectMessage(&spellCooldowns); + } + } +} + +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::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* playerOwner = GetPlayerOwner()) + { + for (int32 spell : cooldowns) + { + WorldPacket data(SMSG_CLEAR_COOLDOWN, 4 + 8); + data << uint32(spell); + data << uint64(_owner->GetGUID()); + playerOwner->SendDirectMessage(&data); + } + } +} + +void SpellHistory::BuildCooldownPacket(WorldPacket& data, uint8 flags, uint32 spellId, uint32 cooldown) const +{ + data.Initialize(SMSG_SPELL_COOLDOWN, 8 + 1 + 4 + 4); + data << uint64(_owner->GetGUID()); + data << uint8(flags); + data << uint32(spellId); + data << uint32(cooldown); +} + +void SpellHistory::BuildCooldownPacket(WorldPacket& data, uint8 flags, PacketCooldowns const& cooldowns) const +{ + data.Initialize(SMSG_SPELL_COOLDOWN, 8 + 1 + (4 + 4) * cooldowns.size()); + data << uint8(flags); + for (auto const& cooldown : cooldowns) + { + data << cooldown.first; + data << cooldown.second; + } +} + +template void SpellHistory::LoadFromDB<Player>(PreparedQueryResult cooldownsResult); +template void SpellHistory::LoadFromDB<Pet>(PreparedQueryResult cooldownsResult); +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..f1533d57aef --- /dev/null +++ b/src/server/game/Spells/SpellHistory.h @@ -0,0 +1,137 @@ +/* + * 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; + +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; + }; + + typedef std::unordered_map<uint32 /*spellId*/, CooldownEntry> CooldownStorageType; + typedef std::unordered_map<uint32 /*categoryId*/, Clock::time_point> GlobalCooldownStorageType; + + explicit SpellHistory(Unit* owner) : _owner(owner), _schoolLockouts() { } + + template<class OwnerType> + void LoadFromDB(PreparedQueryResult cooldownsResult); + + 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 OwnerType> + void WritePacket(WorldPacket& packet) const; + + // Cooldowns + static Clock::duration const InfinityCooldownDelay; // used for set "infinity cooldowns" for spells and check + static Clock::duration const InfinityCooldownDelayCheck; + + 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)) + { + resetCooldowns.push_back(itr->first); + 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; + + // Global cooldown + bool HasGlobalCooldown(SpellInfo const* spellInfo) const; + void AddGlobalCooldown(SpellInfo const* spellInfo, uint32 duration); + void CancelGlobalCooldown(SpellInfo const* spellInfo); + + void BuildCooldownPacket(WorldPacket& data, uint8 flags, uint32 spellId, uint32 cooldown) const; + + CooldownStorageType::size_type GetCooldownsSizeForPacket() const { return _spellCooldowns.size(); } + +private: + Player* GetPlayerOwner() const; + void SendClearCooldowns(std::vector<int32> const& cooldowns) const; + + typedef std::unordered_map<uint32, uint32> PacketCooldowns; + void BuildCooldownPacket(WorldPacket& data, uint8 flags, PacketCooldowns const& cooldowns) const; + + Unit* _owner; + CooldownStorageType _spellCooldowns; + Clock::time_point _schoolLockouts[MAX_SPELL_SCHOOL]; + GlobalCooldownStorageType _globalCooldowns; + + template<class T> + struct PersistenceHelper { }; +}; + +#endif // SpellHistory_h__ |
