diff options
author | Shauren <shauren.trinity@gmail.com> | 2015-02-17 01:01:44 +0100 |
---|---|---|
committer | Shauren <shauren.trinity@gmail.com> | 2015-02-17 01:01:44 +0100 |
commit | 56186319bdd41dd26b6cc14f84e6e41eef5d953b (patch) | |
tree | 7b531cdf71b59170f9fa65120c09935ce51f3307 /src | |
parent | 7829412416c9709991fd686030ab77908c27922b (diff) |
Core/Spells: Cooldown updates
* Refactored cooldown handling to separate class shared by creatures and players
* Updated and enabled cooldown packets
* Implemented creature school lockouts
* Implemented spell charges
* Fixed AuraUpdate structure
* Fixed aura flag AFLAG_NOCASTER handling
* Implemented spell charge related auras
Diffstat (limited to 'src')
46 files changed, 1559 insertions, 1091 deletions
diff --git a/src/server/game/AI/CoreAI/PetAI.cpp b/src/server/game/AI/CoreAI/PetAI.cpp index 3e2087cd017..7d36fd9de67 100644 --- a/src/server/game/AI/CoreAI/PetAI.cpp +++ b/src/server/game/AI/CoreAI/PetAI.cpp @@ -29,6 +29,7 @@ #include "Util.h" #include "Group.h" #include "SpellInfo.h" +#include "SpellHistory.h" int PetAI::Permissible(const Creature* creature) { @@ -147,7 +148,7 @@ 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()) @@ -155,7 +156,7 @@ void PetAI::UpdateAI(uint32 diff) if (spellInfo->CanBeUsedInCombat()) { // check spell cooldown - if (me->HasSpellCooldown(spellInfo->Id)) + if (!me->GetSpellHistory()->IsReady(spellInfo)) continue; // Check if we're in combat or commanded to attack diff --git a/src/server/game/DataStores/DBCEnums.h b/src/server/game/DataStores/DBCEnums.h index 72994461add..cc97e50fa3e 100644 --- a/src/server/game/DataStores/DBCEnums.h +++ b/src/server/game/DataStores/DBCEnums.h @@ -494,7 +494,7 @@ enum SpellCategoryFlags { SPELL_CATEGORY_FLAG_COOLDOWN_SCALES_WITH_WEAPON_SPEED = 0x01, // unused SPELL_CATEGORY_FLAG_COOLDOWN_STARTS_ON_EVENT = 0x04, - SPELL_CATEGORY_FLAG_COOLDOWN_EXPIRES_AT_MIDNIGHT = 0x08 + SPELL_CATEGORY_FLAG_COOLDOWN_EXPIRES_AT_DAILY_RESET = 0x08 }; enum TotemCategoryType diff --git a/src/server/game/DataStores/DBCStructure.h b/src/server/game/DataStores/DBCStructure.h index 0bad082801d..c5b6fe52ae4 100644 --- a/src/server/game/DataStores/DBCStructure.h +++ b/src/server/game/DataStores/DBCStructure.h @@ -1577,7 +1577,7 @@ struct SpellCategoriesEntry uint32 Mechanic; // 6 uint32 PreventionType; // 7 uint32 StartRecoveryCategory; // 8 - //uint32 ChargeCategory; // 9 + uint32 ChargeCategory; // 9 }; typedef std::set<uint32> SpellCategorySet; @@ -1601,8 +1601,8 @@ struct SpellCategoryEntry //uint8 UsesPerWeek; // 2 //uint8 Padding[3]; // 2 //char* Name_lang; // 3 - //uint32 MaxCharges; // 4 - //uint32 ChargeRecoveryTime; // 5 + int32 MaxCharges; // 4 + int32 ChargeRecoveryTime; // 5 }; struct SpellFocusObjectEntry diff --git a/src/server/game/DataStores/DBCfmt.h b/src/server/game/DataStores/DBCfmt.h index 1d404d66311..f6612fca298 100644 --- a/src/server/game/DataStores/DBCfmt.h +++ b/src/server/game/DataStores/DBCfmt.h @@ -137,8 +137,8 @@ char const SkillTiersfmt[] = "niiiiiiiiiiiiiiii"; char const SoundEntriesfmt[] = "nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; char const SpecializationSpellsEntryfmt[] = "niiix"; char const SpellCastTimefmt[] = "nixx"; -char const SpellCategoriesEntryfmt[] = "diiiiiiiix"; -char const SpellCategoryfmt[] = "nixxxx"; +char const SpellCategoriesEntryfmt[] = "diiiiiiiii"; +char const SpellCategoryfmt[] = "nixxii"; char const SpellDurationfmt[] = "niii"; char const SpellEffectEntryfmt[] = "iiifiiiffiiiiiifiifiiiiifiiiiif"; const std::string CustomSpellEffectEntryfmt = "ppppppppppppppappppppppppp"; diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index f6a3a5c600c..096b4b749be 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -157,8 +157,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); @@ -2181,83 +2179,6 @@ void Creature::SetInCombatWithZone() } } -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->AssertSpellInfo(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 a3230dd98ba..43de647c00a 100644 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -529,14 +529,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); @@ -610,8 +602,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/Pet/Pet.cpp b/src/server/game/Entities/Pet/Pet.cpp index 429af16f132..a59866c402f 100644 --- a/src/server/game/Entities/Pet/Pet.cpp +++ b/src/server/game/Entities/Pet/Pet.cpp @@ -26,6 +26,7 @@ #include "Formulas.h" #include "SpellAuras.h" #include "SpellAuraEffects.h" +#include "SpellHistory.h" #include "CreatureAI.h" #include "Unit.h" #include "Util.h" @@ -408,7 +409,7 @@ void Pet::SavePetToDB(PetSaveMode mode) RemoveAllAuras(); _SaveSpells(trans); - _SaveSpellCooldowns(trans); + GetSpellHistory()->SaveToDB<Pet>(trans); CharacterDatabase.CommitTransaction(trans); // current/stable/not_in_slot @@ -511,6 +512,10 @@ void Pet::DeleteFromDB(uint32 guidlow) stmt->setUInt32(0, guidlow); trans->Append(stmt); + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PET_SPELL_CHARGES); + stmt->setUInt32(0, guidlow); + trans->Append(stmt); + CharacterDatabase.CommitTransaction(trans); } @@ -1072,77 +1077,15 @@ 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; - } - - // 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); - } - } -} + PreparedQueryResult cooldownsResult = CharacterDatabase.Query(stmt); -void Pet::_SaveSpellCooldowns(SQLTransaction& trans) -{ - PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PET_SPELL_COOLDOWNS); + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_PET_SPELL_CHARGES); stmt->setUInt32(0, m_charmInfo->GetPetNumber()); - trans->Append(stmt); - - time_t curTime = time(NULL); + PreparedQueryResult chargesResult = CharacterDatabase.Query(stmt); - // 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, chargesResult); } void Pet::_LoadSpells() @@ -1983,40 +1926,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->AssertSpellInfo(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 e9ace117dde..eb8868bf175 100644 --- a/src/server/game/Entities/Pet/Pet.h +++ b/src/server/game/Entities/Pet/Pet.h @@ -106,7 +106,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(); @@ -119,7 +118,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 7c5b8c6dca1..d5d550ba4b9 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -74,6 +74,7 @@ #include "Spell.h" #include "SpellAuraEffects.h" #include "SpellAuras.h" +#include "SpellHistory.h" #include "SpellMgr.h" #include "SpellPackets.h" #include "Transport.h" @@ -3939,152 +3940,19 @@ void Player::RemoveSpell(uint32 spell_id, bool disabled, bool learn_low_rank) } } -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) { - 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->AssertSpellInfo(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()) - { - SendClearAllCooldowns(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", "%s has unknown spell %u in `character_spell_cooldown`, skipping.", GetGUID().ToString().c_str(), 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 (%s) spell %u, item %u cooldown loaded (%u secs).", GetGUID().ToString().c_str(), 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->setUInt64(0, GetGUID().GetCounter()); - 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 << '(' << GetGUID().GetCounter() << ',' << 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::GetNextResetTalentsCost() const @@ -4578,7 +4446,11 @@ void Player::DeleteFromDB(ObjectGuid playerguid, uint32 accountId, bool updateRe stmt->setUInt64(0, guid); trans->Append(stmt); - stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_SPELL_COOLDOWN); + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_SPELL_COOLDOWNS); + stmt->setUInt64(0, guid); + trans->Append(stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_SPELL_CHARGES); stmt->setUInt64(0, guid); trans->Append(stmt); @@ -11800,11 +11672,13 @@ 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); - GetSession()->SendPacket(&data); + WorldPackets::Spells::SpellCooldown spellCooldown; + spellCooldown.Caster = GetGUID(); + spellCooldown.Flags = SPELL_COOLDOWN_FLAG_INCLUDE_GCD; + spellCooldown.SpellCooldowns.emplace_back(cooldownSpell, 0); + GetSession()->SendPacket(spellCooldown.Write()); } } } @@ -17203,7 +17077,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), holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_SPELL_CHARGES)); // 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 @@ -19091,7 +18965,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); @@ -20390,41 +20264,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(&petSpells); GetSession()->SendPacket(&data); } @@ -20452,7 +20293,9 @@ void Player::PossessSpellInitialize() charmInfo->BuildActionBar(&data); data << uint8(0); // spells count - data << uint8(0); // cooldowns count + + // Cooldowns + //charm->GetSpellHistory()->WritePacket(&petSpells); GetSession()->SendPacket(&data); } @@ -20463,7 +20306,7 @@ void Player::VehicleSpellInitialize() if (!vehicle) return; - uint8 cooldownCount = vehicle->m_CreatureSpellCooldowns.size(); + uint8 cooldownCount = 0; WorldPacket data(SMSG_PET_SPELLS, 8 + 2 + 4 + 4 + 4 * 10 + 1 + 1 + cooldownCount * (4 + 2 + 4 + 4)); data << vehicle->GetGUID(); // Guid @@ -20504,41 +20347,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(&petSpells); GetSession()->SendPacket(&data); } @@ -20591,7 +20400,8 @@ void Player::CharmSpellInitialize() } } - data << uint8(0); // cooldowns count + // Cooldowns + //charm->GetSpellHistory()->WritePacket(&petSpells); GetSession()->SendPacket(&data); } @@ -21172,39 +20982,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->AssertSpellInfo(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(); @@ -21829,228 +21606,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 < proto->Effects.size(); ++idx) - { - if (uint32(proto->Effects[idx].SpellID) == spellInfo->Id) - { - cat = proto->Effects[idx].Category; - rec = proto->Effects[idx].Cooldown; - catrec = proto->Effects[idx].CategoryCooldown; - 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 - } - } - - // Apply SPELL_AURA_MOD_SPELL_CATEGORY_COOLDOWN modifiers - // Note: This aura applies its modifiers to all cooldowns of spells with set category, not to category cooldown only - if (cat) - { - if (int32 categoryModifier = GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_SPELL_CATEGORY_COOLDOWN, cat)) - { - if (rec > 0) - rec += categoryModifier; - - if (catrec > 0) - catrec += categoryModifier; - } - - SpellCategoryEntry const* categoryEntry = sSpellCategoryStore.LookupEntry(cat); - ASSERT(categoryEntry); - if (categoryEntry->Flags & SPELL_CATEGORY_FLAG_COOLDOWN_EXPIRES_AT_MIDNIGHT) - { - tm date; - localtime_r(&curTime, &date); - catrec = catrec * DAY - (date.tm_hour * HOUR + date.tm_min * MINUTE + date.tm_sec) * IN_MILLISECONDS; - } - } - - // 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 << GetGUID(); // Player GUID - data << int32(cooldown); // Cooldown mod in milliseconds - GetSession()->SendPacket(&data); - - TC_LOG_DEBUG("misc", "ModifySpellCooldown:: Player: %s (%s) Spell: %u cooldown: %u", GetName().c_str(), GetGUID().ToString().c_str(), 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 - WorldPackets::Spells::CooldownEvent packet(GetGUID(), spellInfo->Id); - SendDirectMessage(packet.Write()); - - 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 - WorldPackets::Spells::CooldownEvent packet(GetGUID(), i->first); - SendDirectMessage(packet.Write()); - } - } - } - } -} - void Player::UpdatePotionCooldown(Spell* spell) { // no potion used i combat or still in combat @@ -22065,11 +21620,11 @@ void Player::UpdatePotionCooldown(Spell* spell) for (uint8 idx = 0; idx < proto->Effects.size(); ++idx) if (proto->Effects[idx].Trigger == ITEM_SPELLTRIGGER_ON_USE) if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(proto->Effects[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; } @@ -22823,8 +22378,15 @@ void Player::SendInitialPacketsBeforeAddToMap() /// SMSG_SEND_UNLEARN_SPELLS SendDirectMessage(WorldPackets::Spells::SendUnlearnSpells().Write()); - /// @todo: SMSG_SEND_SPELL_HISTORY - /// @todo: SMSG_SEND_SPELL_CHARGES + /// SMSG_SEND_SPELL_HISTORY + WorldPackets::Spells::SendSpellHistory sendSpellHistory; + GetSpellHistory()->WritePacket(&sendSpellHistory); + SendDirectMessage(sendSpellHistory.Write()); + + /// SMSG_SEND_SPELL_CHARGES + WorldPackets::Spells::SendSpellCharges sendSpellCharges; + GetSpellHistory()->WritePacket(&sendSpellCharges); + SendDirectMessage(sendSpellCharges.Write()); /// SMSG_ACTION_BUTTONS SendInitialActionButtons(); @@ -22992,7 +22554,7 @@ void Player::ApplyEquipCooldown(Item* pItem) if (itr != m_spellCooldowns.end() && itr->second.itemid == pItem->GetEntry() && itr->second.end > time(NULL) + 30) continue; - AddSpellCooldown(effectData.SpellID, pItem->GetEntry(), time(NULL) + 30); + GetSpellHistory()->AddCooldown(effectData.SpellID, pItem->GetEntry(), std::chrono::seconds(30)); WorldPacket data(SMSG_ITEM_COOLDOWN, 12); data << pItem->GetGUID(); @@ -23943,7 +23505,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; @@ -25715,47 +25277,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 << target->GetGUID(); - SendDirectMessage(&data); -} - -void Player::SendClearAllCooldowns(Unit* target) -{ - uint32 spellCount = m_spellCooldowns.size(); - ObjectGuid guid = target ? target->GetGUID() : ObjectGuid::Empty; - - WorldPacket data(SMSG_CLEAR_COOLDOWNS, 4+8); - data.WriteBit(guid[1]); - data.WriteBit(guid[3]); - data.WriteBit(guid[6]); - data.WriteBits(spellCount, 24); // Spell Count - data.WriteBit(guid[7]); - data.WriteBit(guid[5]); - data.WriteBit(guid[2]); - data.WriteBit(guid[4]); - data.WriteBit(guid[0]); - - data.FlushBits(); - - data.WriteByteSeq(guid[7]); - data.WriteByteSeq(guid[2]); - data.WriteByteSeq(guid[4]); - data.WriteByteSeq(guid[5]); - data.WriteByteSeq(guid[1]); - data.WriteByteSeq(guid[3]); - for (SpellCooldowns::const_iterator itr = m_spellCooldowns.begin(); itr != m_spellCooldowns.end(); ++itr) - data << uint32(itr->first); // Spell ID - - data.WriteByteSeq(guid[0]); - data.WriteByteSeq(guid[6]); - - 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 b0a7b48ca84..b981b316d0c 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -975,6 +975,7 @@ enum PlayerLoginQueryIndex PLAYER_LOGIN_QUERY_LOAD_SOCIAL_LIST, PLAYER_LOGIN_QUERY_LOAD_HOME_BIND, PLAYER_LOGIN_QUERY_LOAD_SPELL_COOLDOWNS, + PLAYER_LOGIN_QUERY_LOAD_SPELL_CHARGES, PLAYER_LOGIN_QUERY_LOAD_DECLINED_NAMES, PLAYER_LOGIN_QUERY_LOAD_GUILD, PLAYER_LOGIN_QUERY_LOAD_ARENA_INFO, @@ -1948,27 +1949,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); - void SendClearAllCooldowns(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); @@ -2844,8 +2825,6 @@ class Player : public Unit, public GridObject<Player> std::unordered_map<uint32 /*overridenSpellId*/, std::unordered_set<uint32> /*newSpellId*/> m_overrideSpells; uint32 m_lastPotionId; // last used health/mana potion in combat, that block next potion use - GlobalCooldownMgr m_GlobalCooldownMgr; - PlayerTalentInfo* _talentMgr; ActionButtonList m_actionButtons; diff --git a/src/server/game/Entities/Totem/Totem.cpp b/src/server/game/Entities/Totem/Totem.cpp index 1a7a7fbb00a..724c1a3c3d9 100644 --- a/src/server/game/Entities/Totem/Totem.cpp +++ b/src/server/game/Entities/Totem/Totem.cpp @@ -22,6 +22,7 @@ #include "ObjectMgr.h" #include "Opcodes.h" #include "Player.h" +#include "SpellHistory.h" #include "SpellMgr.h" #include "SpellInfo.h" #include "WorldPacket.h" @@ -124,7 +125,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 fe16759ff98..76ff57361ea 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" @@ -193,7 +194,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), _spellHistory(new SpellHistory(this)) { m_objectType |= TYPEMASK_UNIT; m_objectTypeId = TYPEID_UNIT; @@ -290,24 +291,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() { @@ -324,6 +307,7 @@ Unit::~Unit() delete i_motionMaster; delete m_charmInfo; delete movespline; + delete _spellHistory; ASSERT(!m_duringRemoveFromWorld); ASSERT(!m_attacking); @@ -2708,6 +2692,8 @@ void Unit::_UpdateSpells(uint32 time) ++itr; } } + + _spellHistory->Update(); } void Unit::_UpdateAutoRepeatSpell() @@ -4647,13 +4633,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); } } @@ -4678,14 +4664,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); @@ -5223,8 +5206,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; } @@ -5269,8 +5252,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; } @@ -5850,7 +5833,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()) @@ -5897,7 +5880,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) @@ -6036,10 +6019,7 @@ bool Unit::HandleDummyAuraProc(Unit* victim, uint32 damage, AuraEffect* triggere { uint32 spell = 26364; - // custom cooldown processing case - if (GetTypeId() == TYPEID_PLAYER && ToPlayer()->HasSpellCooldown(spell)) - ToPlayer()->RemoveSpellCooldown(spell); - + GetSpellHistory()->ResetCooldown(spell); CastSpell(target, spell, true, castItem, triggeredByAura); return true; } @@ -6138,7 +6118,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) @@ -6146,8 +6126,8 @@ bool Unit::HandleDummyAuraProc(Unit* victim, uint32 damage, AuraEffect* triggere else CastSpell(target, triggered_spell_id, true, castItem, triggeredByAura, originalCaster); - if (cooldown && GetTypeId() == TYPEID_PLAYER) - ToPlayer()->AddSpellCooldown(cooldown_spell_id, 0, time(NULL) + cooldown); + if (cooldown) + GetSpellHistory()->AddCooldown(cooldown_spell_id, 0, std::chrono::seconds(cooldown)); return true; } @@ -6294,9 +6274,10 @@ bool Unit::HandleAuraProc(Unit* victim, uint32 /*damage*/, Aura* triggeredByAura *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; } @@ -6810,7 +6791,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 @@ -6826,8 +6807,8 @@ bool Unit::HandleProcTriggerSpell(Unit* victim, uint32 damage, AuraEffect* trigg else CastSpell(target, trigger_spell_id, true, castItem, triggeredByAura); - if (cooldown && GetTypeId() == TYPEID_PLAYER) - ToPlayer()->AddSpellCooldown(trigger_spell_id, 0, time(NULL) + cooldown); + if (cooldown) + GetSpellHistory()->AddCooldown(trigger_spell_id, 0, std::chrono::seconds(cooldown)); return true; } @@ -6882,13 +6863,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 && ToPlayer()->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); + if (cooldown) + GetSpellHistory()->AddCooldown(triggered_spell_id, 0, std::chrono::seconds(cooldown)); return true; } @@ -7621,14 +7602,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 { @@ -7662,13 +7640,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)) { @@ -16404,27 +16379,6 @@ void Unit::DestroyForPlayer(Player* target) const WorldObject::DestroyForPlayer(target); } -void Unit::BuildCooldownPacket(WorldPacket& data, uint8 flags, uint32 spellId, uint32 cooldown) -{ - data.Initialize(SMSG_SPELL_COOLDOWN, 8 + 1 + 4 + 4); - data << 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 << 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 82f7240bc76..3f9570b548f 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -397,6 +397,7 @@ class Creature; class Spell; class SpellInfo; class SpellEffectInfo; +class SpellHistory; class DynamicObject; class GameObject; class Item; @@ -1105,30 +1106,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 @@ -1246,8 +1223,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); @@ -1280,8 +1255,6 @@ struct CharmInfo float _stayX; float _stayY; float _stayZ; - - GlobalCooldownMgr m_GlobalCooldownMgr; }; // for clearing special attacks @@ -1312,16 +1285,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 @@ -1658,8 +1621,6 @@ class Unit : public WorldObject Aura* AddAura(SpellInfo const* spellInfo, uint32 effMask, Unit* target); void SetAuraStack(uint32 spellId, Unit* target, uint32 stack); void SendPlaySpellVisualKit(uint32 id, uint32 unkParam); - void BuildCooldownPacket(WorldPacket& data, uint8 flags, uint32 spellId, uint32 cooldown); - void BuildCooldownPacket(WorldPacket& data, uint8 flags, PacketCooldowns const& cooldowns); void DeMorph(); @@ -1920,7 +1881,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); @@ -1939,6 +1899,9 @@ class Unit : public WorldObject int32 GetCurrentSpellCastTime(uint32 spell_id) const; virtual SpellInfo const* GetCastSpellInfo(SpellInfo const* spellInfo) const; + SpellHistory* GetSpellHistory() { return _spellHistory; } + SpellHistory const* GetSpellHistory() const { return _spellHistory; } + ObjectGuid m_SummonSlot[MAX_SUMMON_SLOT]; ObjectGuid m_ObjectSlot[MAX_GAMEOBJECT_SLOT]; @@ -2374,6 +2337,8 @@ class Unit : public WorldObject uint16 _aiAnimKitId; uint16 _movementAnimKitId; uint16 _meleeAnimKitId; + + SpellHistory* _spellHistory; }; namespace Trinity diff --git a/src/server/game/Handlers/CharacterHandler.cpp b/src/server/game/Handlers/CharacterHandler.cpp index 255eea8fbe6..0385b8f1a39 100644 --- a/src/server/game/Handlers/CharacterHandler.cpp +++ b/src/server/game/Handlers/CharacterHandler.cpp @@ -162,6 +162,10 @@ bool LoginQueryHolder::Initialize() stmt->setUInt64(0, lowGuid); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_SPELL_COOLDOWNS, stmt); + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_SPELL_CHARGES); + stmt->setUInt64(0, lowGuid); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_SPELL_CHARGES, stmt); + if (sWorld->getBoolConfig(CONFIG_DECLINED_NAMES_USED)) { stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_DECLINEDNAMES); diff --git a/src/server/game/Handlers/PetHandler.cpp b/src/server/game/Handlers/PetHandler.cpp index c1ac3cb18b2..193cc951a48 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" @@ -361,8 +362,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 @@ -396,8 +395,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; @@ -804,7 +803,6 @@ void WorldSession::HandlePetCastSpellOpcode(WorldPackets::Spells::PetCastSpell& { if (Creature* creature = caster->ToCreature()) { - creature->AddCreatureSpellCooldown(spellId); if (Pet* pet = creature->ToPet()) { // 10% chance to play special pet attack talk, else growl @@ -822,16 +820,8 @@ void WorldSession::HandlePetCastSpellOpcode(WorldPackets::Spells::PetCastSpell& { 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 3a173e4e582..c86ff147559 100644 --- a/src/server/game/Handlers/SpellHandler.cpp +++ b/src/server/game/Handlers/SpellHandler.cpp @@ -418,8 +418,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/Server/Packets/SpellPackets.cpp b/src/server/game/Server/Packets/SpellPackets.cpp index 50251c71c8b..a79ff8d4c96 100644 --- a/src/server/game/Server/Packets/SpellPackets.cpp +++ b/src/server/game/Server/Packets/SpellPackets.cpp @@ -112,15 +112,15 @@ WorldPacket const* WorldPackets::Spells::AuraUpdate::Write() _worldPacket << uint32(data.ActiveFlags); _worldPacket << uint16(data.CastLevel); _worldPacket << uint8(data.Applications); - _worldPacket << uint32(data.EstimatedPoints.size()); _worldPacket << uint32(data.Points.size()); - - if (!data.EstimatedPoints.empty()) - _worldPacket.append(data.EstimatedPoints.data(), data.EstimatedPoints.size()); + _worldPacket << uint32(data.EstimatedPoints.size()); if (!data.Points.empty()) _worldPacket.append(data.Points.data(), data.Points.size()); + if (!data.EstimatedPoints.empty()) + _worldPacket.append(data.EstimatedPoints.data(), data.EstimatedPoints.size()); + _worldPacket.WriteBit(data.CastUnit.HasValue); _worldPacket.WriteBit(data.Duration.HasValue); _worldPacket.WriteBit(data.Remaining.HasValue); @@ -487,3 +487,114 @@ WorldPacket const* WorldPackets::Spells::CooldownEvent::Write() return &_worldPacket; } + +WorldPacket const* WorldPackets::Spells::ClearCooldowns::Write() +{ + _worldPacket << Guid; + _worldPacket << uint32(SpellID.size()); + if (!SpellID.empty()) + _worldPacket.append(SpellID.data(), SpellID.size()); + + return &_worldPacket; +} + +WorldPacket const* WorldPackets::Spells::ClearCooldown::Write() +{ + _worldPacket << CasterGUID; + _worldPacket << uint32(SpellID); + _worldPacket.WriteBit(ClearOnHold); + _worldPacket.FlushBits(); + + return &_worldPacket; +} + +WorldPacket const* WorldPackets::Spells::ModifyCooldown::Write() +{ + _worldPacket << int32(SpellID); + _worldPacket << UnitGUID; + _worldPacket << int32(DeltaTime); + + return &_worldPacket; +} + +ByteBuffer& operator<<(ByteBuffer& data, WorldPackets::Spells::SpellCooldownStruct const& cooldown) +{ + data << uint32(cooldown.SrecID); + data << uint32(cooldown.ForcedCooldown); + return data; +} + +WorldPacket const* WorldPackets::Spells::SpellCooldown::Write() +{ + _worldPacket << Caster; + _worldPacket << uint8(Flags); + _worldPacket << uint32(SpellCooldowns.size()); + for (SpellCooldownStruct const& cooldown : SpellCooldowns) + _worldPacket << cooldown; + + return &_worldPacket; +} + +ByteBuffer& operator<<(ByteBuffer& data, WorldPackets::Spells::SpellHistoryEntry const& historyEntry) +{ + data << uint32(historyEntry.SpellID); + data << uint32(historyEntry.ItemID); + data << uint32(historyEntry.Category); + data << int32(historyEntry.RecoveryTime); + data << int32(historyEntry.CategoryRecoveryTime); + data.WriteBit(historyEntry.OnHold); + data.FlushBits(); + + return data; +} + +WorldPacket const* WorldPackets::Spells::SendSpellHistory::Write() +{ + _worldPacket << uint32(Entries.size()); + for (SpellHistoryEntry const& historyEntry : Entries) + _worldPacket << historyEntry; + + return &_worldPacket; +} + +WorldPacket const* WorldPackets::Spells::ClearAllSpellCharges::Write() +{ + _worldPacket << Unit; + + return &_worldPacket; +} + +WorldPacket const* WorldPackets::Spells::ClearSpellCharges::Write() +{ + _worldPacket << Unit; + _worldPacket << int32(Category); + + return &_worldPacket; +} + +WorldPacket const* WorldPackets::Spells::SetSpellCharges::Write() +{ + _worldPacket << int32(Category); + _worldPacket << float(Count); + _worldPacket.WriteBit(IsPet); + _worldPacket.FlushBits(); + + return &_worldPacket; +} + +ByteBuffer& operator<<(ByteBuffer& data, WorldPackets::Spells::SpellChargeEntry const& chargeEntry) +{ + data << uint32(chargeEntry.Category); + data << uint32(chargeEntry.NextRecoveryTime); + data << uint8(chargeEntry.ConsumedCharges); + return data; +} + +WorldPacket const* WorldPackets::Spells::SendSpellCharges::Write() +{ + _worldPacket << uint32(Entries.size()); + for (SpellChargeEntry const& chargeEntry : Entries) + _worldPacket << chargeEntry; + + return &_worldPacket; +} diff --git a/src/server/game/Server/Packets/SpellPackets.h b/src/server/game/Server/Packets/SpellPackets.h index ed84885f0a0..da877afbf8d 100644 --- a/src/server/game/Server/Packets/SpellPackets.h +++ b/src/server/game/Server/Packets/SpellPackets.h @@ -420,6 +420,132 @@ namespace WorldPackets ObjectGuid CasterGUID; int32 SpellID; }; + + class ClearCooldowns final : public ServerPacket + { + public: + ClearCooldowns() : ServerPacket(SMSG_CLEAR_COOLDOWNS, 4 + 16) { } + + WorldPacket const* Write() override; + + std::vector<int32> SpellID; + ObjectGuid Guid; + }; + + class ClearCooldown final : public ServerPacket + { + public: + ClearCooldown() : ServerPacket(SMSG_CLEAR_COOLDOWN, 16 + 4 + 1) { } + + WorldPacket const* Write() override; + + ObjectGuid CasterGUID; + int32 SpellID = 0; + bool ClearOnHold = false; + }; + + class ModifyCooldown final : public ServerPacket + { + public: + ModifyCooldown() : ServerPacket(SMSG_MODIFY_COOLDOWN, 16 + 4 + 4) { } + + WorldPacket const* Write() override; + + ObjectGuid UnitGUID; + int32 DeltaTime = 0; + int32 SpellID = 0; + }; + + struct SpellCooldownStruct + { + SpellCooldownStruct() { } + SpellCooldownStruct(uint32 spellId, uint32 forcedCooldown) : SrecID(spellId), ForcedCooldown(forcedCooldown) { } + + uint32 SrecID = 0; + uint32 ForcedCooldown = 0; + }; + + class SpellCooldown : public ServerPacket + { + public: + SpellCooldown() : ServerPacket(SMSG_SPELL_COOLDOWN, 4 + 16 + 1) { } + + WorldPacket const* Write() override; + + std::vector<SpellCooldownStruct> SpellCooldowns; + ObjectGuid Caster; + uint8 Flags = 0; + }; + + struct SpellHistoryEntry + { + uint32 SpellID = 0; + uint32 ItemID = 0; + uint32 Category = 0; + int32 RecoveryTime = 0; + int32 CategoryRecoveryTime = 0; + bool OnHold = false; + }; + + class SendSpellHistory final : public ServerPacket + { + public: + SendSpellHistory() : ServerPacket(SMSG_SEND_SPELL_HISTORY, 4) { } + + WorldPacket const* Write() override; + + std::vector<SpellHistoryEntry> Entries; + }; + + class ClearAllSpellCharges final : public ServerPacket + { + public: + ClearAllSpellCharges() : ServerPacket(SMSG_CLEAR_ALL_SPELL_CHARGES, 16) { } + + WorldPacket const* Write() override; + + ObjectGuid Unit; + }; + + class ClearSpellCharges final : public ServerPacket + { + public: + ClearSpellCharges() : ServerPacket(SMSG_CLEAR_SPELL_CHARGES, 20) { } + + WorldPacket const* Write() override; + + ObjectGuid Unit; + int32 Category = 0; + }; + + class SetSpellCharges final : public ServerPacket + { + public: + SetSpellCharges() : ServerPacket(SMSG_SET_SPELL_CHARGES, 1 + 4 + 4) { } + + WorldPacket const* Write() override; + + bool IsPet = false; + float Count = 0.0f; + int32 Category = 0; + }; + + struct SpellChargeEntry + { + uint32 Category = 0; + uint32 NextRecoveryTime = 0; + uint8 ConsumedCharges = 0; + }; + + class SendSpellCharges final : public ServerPacket + { + public: + SendSpellCharges() : ServerPacket(SMSG_SEND_SPELL_CHARGES, 4) { } + + WorldPacket const* Write() override; + + std::vector<SpellChargeEntry> Entries; + }; } } diff --git a/src/server/game/Server/Protocol/Opcodes.cpp b/src/server/game/Server/Protocol/Opcodes.cpp index 08f008d673c..882e04cbf63 100644 --- a/src/server/game/Server/Protocol/Opcodes.cpp +++ b/src/server/game/Server/Protocol/Opcodes.cpp @@ -1119,15 +1119,15 @@ void OpcodeTable::Initialize() DEFINE_SERVER_OPCODE_HANDLER(SMSG_CHEAT_IGNORE_DIMISHING_RETURNS, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_CHECK_FOR_BOTS, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_CHECK_WARGAME_ENTRY, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); - DEFINE_SERVER_OPCODE_HANDLER(SMSG_CLEAR_ALL_SPELL_CHARGE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); + DEFINE_SERVER_OPCODE_HANDLER(SMSG_CLEAR_ALL_SPELL_CHARGES, STATUS_NEVER, CONNECTION_TYPE_INSTANCE); DEFINE_SERVER_OPCODE_HANDLER(SMSG_CLEAR_BOSS_EMOTES, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); - DEFINE_SERVER_OPCODE_HANDLER(SMSG_CLEAR_COOLDOWN, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); - DEFINE_SERVER_OPCODE_HANDLER(SMSG_CLEAR_COOLDOWNS, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); + DEFINE_SERVER_OPCODE_HANDLER(SMSG_CLEAR_COOLDOWN, STATUS_NEVER, CONNECTION_TYPE_INSTANCE); + DEFINE_SERVER_OPCODE_HANDLER(SMSG_CLEAR_COOLDOWNS, STATUS_NEVER, CONNECTION_TYPE_INSTANCE); DEFINE_SERVER_OPCODE_HANDLER(SMSG_CLEAR_FAR_SIGHT_IMMEDIATE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_CLEAR_LOSS_OF_CONTROL, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_CLEAR_QUEST_COMPLETED_BIT, STATUS_UNHANDLED, CONNECTION_TYPE_INSTANCE); DEFINE_SERVER_OPCODE_HANDLER(SMSG_CLEAR_QUEST_COMPLETED_BITS, STATUS_NEVER, CONNECTION_TYPE_INSTANCE); - DEFINE_SERVER_OPCODE_HANDLER(SMSG_CLEAR_SPELL_CHARGES, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); + DEFINE_SERVER_OPCODE_HANDLER(SMSG_CLEAR_SPELL_CHARGES, STATUS_NEVER, CONNECTION_TYPE_INSTANCE); DEFINE_SERVER_OPCODE_HANDLER(SMSG_CLEAR_TARGET, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_CLIENTCACHE_VERSION, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_CLIENT_CONTROL_UPDATE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); @@ -1153,7 +1153,7 @@ void OpcodeTable::Initialize() DEFINE_SERVER_OPCODE_HANDLER(SMSG_CONTACT_STATUS, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_CONVERT_RUNE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_COOLDOWN_CHEAT, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); - DEFINE_SERVER_OPCODE_HANDLER(SMSG_COOLDOWN_EVENT, STATUS_NEVER, CONNECTION_TYPE_REALM); + DEFINE_SERVER_OPCODE_HANDLER(SMSG_COOLDOWN_EVENT, STATUS_NEVER, CONNECTION_TYPE_INSTANCE); DEFINE_SERVER_OPCODE_HANDLER(SMSG_CORPSE_LOCATION, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_CORPSE_RECLAIM_DELAY, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_CORPSE_TRANSPORT_QUERY, STATUS_NEVER, CONNECTION_TYPE_REALM); @@ -1469,7 +1469,7 @@ void OpcodeTable::Initialize() DEFINE_SERVER_OPCODE_HANDLER(SMSG_MIRROR_IMAGE_COMPONENTED_DATA, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_MIRROR_IMAGE_CREATURE_DATA, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_MISSILE_CANCEL, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); - DEFINE_SERVER_OPCODE_HANDLER(SMSG_MODIFY_COOLDOWN, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); + DEFINE_SERVER_OPCODE_HANDLER(SMSG_MODIFY_COOLDOWN, STATUS_NEVER, CONNECTION_TYPE_INSTANCE); DEFINE_SERVER_OPCODE_HANDLER(SMSG_MONEY_NOTIFY, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_MOTD, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_MOUNT_RESULT, STATUS_UNHANDLED, CONNECTION_TYPE_INSTANCE); @@ -1758,8 +1758,8 @@ void OpcodeTable::Initialize() DEFINE_SERVER_OPCODE_HANDLER(SMSG_SEND_MAIL_RESULT, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_SEND_RAID_TARGET_UPDATE_ALL, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_SEND_RAID_TARGET_UPDATE_SINGLE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); - DEFINE_SERVER_OPCODE_HANDLER(SMSG_SEND_SPELL_CHARGES, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); - DEFINE_SERVER_OPCODE_HANDLER(SMSG_SEND_SPELL_HISTORY, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); + DEFINE_SERVER_OPCODE_HANDLER(SMSG_SEND_SPELL_CHARGES, STATUS_NEVER, CONNECTION_TYPE_INSTANCE); + DEFINE_SERVER_OPCODE_HANDLER(SMSG_SEND_SPELL_HISTORY, STATUS_NEVER, CONNECTION_TYPE_INSTANCE); DEFINE_SERVER_OPCODE_HANDLER(SMSG_SEND_UNLEARN_SPELLS, STATUS_NEVER, CONNECTION_TYPE_INSTANCE); DEFINE_SERVER_OPCODE_HANDLER(SMSG_SERVER_FIRST_ACHIEVEMENT, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_SERVER_FIRST_ACHIEVEMENTS, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); @@ -1795,7 +1795,7 @@ void OpcodeTable::Initialize() DEFINE_SERVER_OPCODE_HANDLER(SMSG_SET_PROJECTILE_POSITION, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_SET_QUEST_COMPLETED_BIT, STATUS_NEVER, CONNECTION_TYPE_INSTANCE); DEFINE_SERVER_OPCODE_HANDLER(SMSG_SET_RAID_DIFFICULTY, STATUS_NEVER, CONNECTION_TYPE_REALM); - DEFINE_SERVER_OPCODE_HANDLER(SMSG_SET_SPELL_CHARGES, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); + DEFINE_SERVER_OPCODE_HANDLER(SMSG_SET_SPELL_CHARGES, STATUS_NEVER, CONNECTION_TYPE_INSTANCE); DEFINE_SERVER_OPCODE_HANDLER(SMSG_SET_TASK_COMPLETE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_SET_TIME_ZONE_INFORMATION, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_SET_VEHICLE_REC_ID, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); @@ -1814,7 +1814,7 @@ void OpcodeTable::Initialize() DEFINE_SERVER_OPCODE_HANDLER(SMSG_SPELLSTEALLOG, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_SPELL_ABSORB_LOG, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_SPELL_CATEGORY_COOLDOWN, STATUS_NEVER, CONNECTION_TYPE_INSTANCE); - DEFINE_SERVER_OPCODE_HANDLER(SMSG_SPELL_COOLDOWN, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); + DEFINE_SERVER_OPCODE_HANDLER(SMSG_SPELL_COOLDOWN, STATUS_NEVER, CONNECTION_TYPE_INSTANCE); DEFINE_SERVER_OPCODE_HANDLER(SMSG_SPELL_DAMAGE_SHIELD, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_SPELL_DELAYED, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_SPELL_DISPEL_LOG, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); diff --git a/src/server/game/Server/Protocol/Opcodes.h b/src/server/game/Server/Protocol/Opcodes.h index bb8ae55c0f9..50c2daa05aa 100644 --- a/src/server/game/Server/Protocol/Opcodes.h +++ b/src/server/game/Server/Protocol/Opcodes.h @@ -1031,7 +1031,7 @@ enum OpcodeServer : uint32 SMSG_CHEAT_IGNORE_DIMISHING_RETURNS = 0x194C, SMSG_CHECK_FOR_BOTS = 0xBADD, SMSG_CHECK_WARGAME_ENTRY = 0x1203, - SMSG_CLEAR_ALL_SPELL_CHARGE = 0x088B, + SMSG_CLEAR_ALL_SPELL_CHARGES = 0x088B, SMSG_CLEAR_BOSS_EMOTES = 0x118B, SMSG_CLEAR_COOLDOWN = 0x0226, SMSG_CLEAR_COOLDOWNS = 0x0BFA, diff --git a/src/server/game/Spells/Auras/SpellAuraDefines.h b/src/server/game/Spells/Auras/SpellAuraDefines.h index 42d2ec72c8e..112893fbc75 100644 --- a/src/server/game/Spells/Auras/SpellAuraDefines.h +++ b/src/server/game/Spells/Auras/SpellAuraDefines.h @@ -468,7 +468,7 @@ enum AuraType SPELL_AURA_408 = 408, SPELL_AURA_409 = 409, SPELL_AURA_410 = 410, - SPELL_AURA_MOD_CHARGES = 411, // NYI + SPELL_AURA_MOD_MAX_CHARGES = 411, SPELL_AURA_412 = 412, SPELL_AURA_413 = 413, SPELL_AURA_414 = 414, @@ -508,19 +508,19 @@ enum AuraType SPELL_AURA_448 = 448, SPELL_AURA_449 = 449, SPELL_AURA_450 = 450, - SPELL_AURA_451 = 451, + SPELL_AURA_OVERRIDE_PET_SPECS = 451, // NYI SPELL_AURA_452 = 452, - SPELL_AURA_453 = 453, - SPELL_AURA_454 = 454, + SPELL_AURA_CHARGE_RECOVERY_MOD = 453, + SPELL_AURA_CHARGE_RECOVERY_MULTIPLIER = 454, SPELL_AURA_455 = 455, - SPELL_AURA_456 = 456, - SPELL_AURA_457 = 457, - SPELL_AURA_458 = 458, + SPELL_AURA_CHARGE_RECOVERY_AFFECTED_BY_HASTE = 456, + SPELL_AURA_CHARGE_RECOVERY_AFFECTED_BY_HASTE_REGEN = 457, + SPELL_AURA_IGNORE_DUAL_WIELD_HIT_PENALTY = 458, // NYI SPELL_AURA_459 = 459, SPELL_AURA_460 = 460, SPELL_AURA_461 = 461, SPELL_AURA_462 = 462, - SPELL_AURA_463 = 463, + SPELL_AURA_CONVER_CRIT_RATING_PCT_TO_PARRY_RATING = 463, // NYI SPELL_AURA_464 = 464, SPELL_AURA_465 = 465, SPELL_AURA_466 = 466, diff --git a/src/server/game/Spells/Auras/SpellAuraEffects.cpp b/src/server/game/Spells/Auras/SpellAuraEffects.cpp index b4c27f0214b..24d142da1a3 100644 --- a/src/server/game/Spells/Auras/SpellAuraEffects.cpp +++ b/src/server/game/Spells/Auras/SpellAuraEffects.cpp @@ -42,6 +42,7 @@ #include "Pet.h" #include "ReputationMgr.h" #include "MiscPackets.h" +#include "SpellHistory.h" class Aura; // @@ -470,7 +471,7 @@ pAuraEffectHandler AuraEffectHandler[TOTAL_AURAS]= &AuraEffect::HandleNULL, //408 &AuraEffect::HandleNULL, //409 &AuraEffect::HandleNULL, //410 - &AuraEffect::HandleNULL, //411 SPELL_AURA_MOD_CHARGES + &AuraEffect::HandleNoImmediateEffect, //411 SPELL_AURA_MOD_MAX_CHARGES implemented in SpellHistory::GetMaxCharges &AuraEffect::HandleNULL, //412 &AuraEffect::HandleNULL, //413 &AuraEffect::HandleNULL, //414 @@ -510,19 +511,19 @@ pAuraEffectHandler AuraEffectHandler[TOTAL_AURAS]= &AuraEffect::HandleNULL, //448 &AuraEffect::HandleNULL, //449 &AuraEffect::HandleNULL, //450 - &AuraEffect::HandleNULL, //451 + &AuraEffect::HandleNULL, //451 SPELL_AURA_OVERRIDE_PET_SPECS &AuraEffect::HandleNULL, //452 - &AuraEffect::HandleNULL, //453 - &AuraEffect::HandleNULL, //454 + &AuraEffect::HandleNoImmediateEffect, //453 SPELL_AURA_CHARGE_RECOVERY_MOD implemented in SpellHistory::GetChargeRecoveryTime + &AuraEffect::HandleNoImmediateEffect, //454 SPELL_AURA_CHARGE_RECOVERY_MULTIPLIER implemented in SpellHistory::GetChargeRecoveryTime &AuraEffect::HandleNULL, //455 - &AuraEffect::HandleNULL, //456 - &AuraEffect::HandleNULL, //457 - &AuraEffect::HandleNULL, //458 + &AuraEffect::HandleNoImmediateEffect, //456 SPELL_AURA_CHARGE_RECOVERY_AFFECTED_BY_HASTE implemented in SpellHistory::GetChargeRecoveryTime + &AuraEffect::HandleNoImmediateEffect, //457 SPELL_AURA_CHARGE_RECOVERY_AFFECTED_BY_HASTE_REGEN implemented in SpellHistory::GetChargeRecoveryTime + &AuraEffect::HandleNULL, //458 SPELL_AURA_IGNORE_DUAL_WIELD_HIT_PENALTY &AuraEffect::HandleNULL, //459 &AuraEffect::HandleNULL, //460 &AuraEffect::HandleNULL, //461 &AuraEffect::HandleNULL, //462 - &AuraEffect::HandleNULL, //463 + &AuraEffect::HandleNULL, //463 SPELL_AURA_CRIT_RATING_AFFECTS_PARRY used by Riposte &AuraEffect::HandleNULL, //464 &AuraEffect::HandleNULL, //465 &AuraEffect::HandleNULL, //466 @@ -1321,15 +1322,13 @@ void AuraEffect::HandleShapeshiftBoosts(Unit* target, bool apply) const // Remove cooldown of spells triggered on stance change - they may share cooldown with stance spell if (spellId) { - if (target->GetTypeId() == TYPEID_PLAYER) - target->ToPlayer()->RemoveSpellCooldown(spellId); + target->GetSpellHistory()->ResetCooldown(spellId); target->CastSpell(target, spellId, true, NULL, this); } if (spellId2) { - if (target->GetTypeId() == TYPEID_PLAYER) - target->ToPlayer()->RemoveSpellCooldown(spellId2); + target->GetSpellHistory()->ResetCooldown(spellId2); target->CastSpell(target, spellId2, true, NULL, this); } diff --git a/src/server/game/Spells/Auras/SpellAuras.cpp b/src/server/game/Spells/Auras/SpellAuras.cpp index 4e239f8a373..cd3b22dfa11 100644 --- a/src/server/game/Spells/Auras/SpellAuras.cpp +++ b/src/server/game/Spells/Auras/SpellAuras.cpp @@ -26,6 +26,7 @@ #include "Unit.h" #include "Spell.h" #include "SpellAuraEffects.h" +#include "SpellHistory.h" #include "SpellPackets.h" #include "DynamicObject.h" #include "ObjectAccessor.h" @@ -117,7 +118,7 @@ void AuraApplication::_Remove() void AuraApplication::_InitFlags(Unit* caster, uint32 effMask) { // mark as selfcast if needed - _flags |= (GetBase()->GetCasterGUID() == GetTarget()->GetGUID()) ? AFLAG_NONE : AFLAG_NOCASTER; + _flags |= (GetBase()->GetCasterGUID() == GetTarget()->GetGUID()) ? AFLAG_NOCASTER : AFLAG_NONE; // aura is cast by self or an enemy // one negative effect and we know aura is negative @@ -150,7 +151,10 @@ void AuraApplication::_InitFlags(Unit* caster, uint32 effMask) _flags |= positiveFound ? AFLAG_POSITIVE : AFLAG_NEGATIVE; } - if (GetBase()->GetSpellInfo()->AttributesEx8 & SPELL_ATTR8_AURA_SEND_AMOUNT) + if (GetBase()->GetSpellInfo()->AttributesEx8 & SPELL_ATTR8_AURA_SEND_AMOUNT || + GetBase()->HasEffectType(SPELL_AURA_MOD_MAX_CHARGES) || + GetBase()->HasEffectType(SPELL_AURA_CHARGE_RECOVERY_MOD) || + GetBase()->HasEffectType(SPELL_AURA_CHARGE_RECOVERY_MULTIPLIER)) _flags |= AFLAG_SCALABLE; } @@ -182,6 +186,7 @@ void AuraApplication::_HandleEffect(uint8 effIndex, bool apply) // Remove all triggered by aura spells vs unlimited duration aurEff->CleanupTriggeredSpells(GetTarget()); } + SetNeedClientUpdate(); } @@ -218,10 +223,10 @@ void AuraApplication::BuildUpdatePacket(WorldPackets::Spells::AuraInfo& auraInfo if (auraData.Flags & AFLAG_SCALABLE) { - auraData.Points.reserve(aura->GetAuraEffects().size()); + auraData.Points.resize(aura->GetAuraEffects().size(), 0.0f); for (AuraEffect const* effect : GetBase()->GetAuraEffects()) if (effect && HasEffect(effect->GetEffIndex())) // Not all of aura's effects have to be applied on every target - auraData.Points.push_back(float(effect->GetAmount())); + auraData.Points[effect->GetEffIndex()] = float(effect->GetAmount()); } auraInfo.AuraData.Set(auraData); @@ -462,7 +467,7 @@ void Aura::_ApplyForTarget(Unit* target, Unit* caster, AuraApplication * auraApp if (m_spellInfo->IsCooldownStartedOnEvent()) { Item* castItem = !m_castItemGuid.IsEmpty() ? caster->ToPlayer()->GetItemByGuid(m_castItemGuid) : NULL; - caster->ToPlayer()->AddSpellAndCategoryCooldowns(m_spellInfo, castItem ? castItem->GetEntry() : 0, NULL, true); + caster->GetSpellHistory()->StartCooldown(m_spellInfo, castItem ? castItem->GetEntry() : 0, nullptr, true); } } } @@ -490,12 +495,9 @@ void Aura::_UnapplyForTarget(Unit* target, Unit* caster, AuraApplication * auraA m_removedApplications.push_back(auraApp); // reset cooldown state for spells - if (caster && caster->GetTypeId() == TYPEID_PLAYER) - { - if (GetSpellInfo()->IsCooldownStartedOnEvent()) - // note: item based cooldowns and cooldown spell mods with charges ignored (unknown existed cases) - caster->ToPlayer()->SendCooldownEvent(GetSpellInfo()); - } + if (caster && GetSpellInfo()->IsCooldownStartedOnEvent()) + // note: item based cooldowns and cooldown spell mods with charges ignored (unknown existed cases) + caster->GetSpellHistory()->SendCooldownEvent(GetSpellInfo()); } // removes aura from all targets @@ -1055,7 +1057,12 @@ bool Aura::CanBeSaved() const bool Aura::CanBeSentToClient() const { - return !IsPassive() || GetSpellInfo()->HasAreaAuraEffect(GetOwner() ? GetOwner()->GetMap()->GetDifficultyID() : DIFFICULTY_NONE) || HasEffectType(SPELL_AURA_ABILITY_IGNORE_AURASTATE) || HasEffectType(SPELL_AURA_CAST_WHILE_WALKING); + return !IsPassive() || GetSpellInfo()->HasAreaAuraEffect(GetOwner() ? GetOwner()->GetMap()->GetDifficultyID() : DIFFICULTY_NONE) + || HasEffectType(SPELL_AURA_ABILITY_IGNORE_AURASTATE) + || HasEffectType(SPELL_AURA_CAST_WHILE_WALKING) + || HasEffectType(SPELL_AURA_MOD_MAX_CHARGES) + || HasEffectType(SPELL_AURA_CHARGE_RECOVERY_MOD) + || HasEffectType(SPELL_AURA_CHARGE_RECOVERY_MULTIPLIER); } bool Aura::IsSingleTargetWith(Aura const* aura) const @@ -1310,7 +1317,7 @@ void Aura::HandleAuraSpecificMods(AuraApplication const* aurApp, Unit* caster, b break; case 60970: // Heroic Fury (remove Intercept cooldown) if (target->GetTypeId() == TYPEID_PLAYER) - target->ToPlayer()->RemoveSpellCooldown(20252, true); + target->GetSpellHistory()->ResetCooldown(20252, true); break; } break; @@ -1470,15 +1477,15 @@ void Aura::HandleAuraSpecificMods(AuraApplication const* aurApp, Unit* caster, b // check cooldown if (caster->GetTypeId() == TYPEID_PLAYER) { - if (caster->ToPlayer()->HasSpellCooldown(aura->GetId())) + if (caster->GetSpellHistory()->HasCooldown(aura->GetId())) { // This additional check is needed to add a minimal delay before cooldown in in effect // to allow all bubbles broken by a single damage source proc mana return - if (caster->ToPlayer()->GetSpellCooldownDelay(aura->GetId()) <= 11) + if (caster->GetSpellHistory()->GetRemainingCooldown(aura->GetId()) <= 11) break; } else // and add if needed - caster->ToPlayer()->AddSpellCooldown(aura->GetId(), 0, uint32(time(NULL) + 12)); + caster->GetSpellHistory()->AddCooldown(aura->GetId(), 0, std::chrono::seconds(12)); } // effect on caster diff --git a/src/server/game/Spells/Auras/SpellAuras.h b/src/server/game/Spells/Auras/SpellAuras.h index 3a05da9cd1e..8710f312dda 100644 --- a/src/server/game/Spells/Auras/SpellAuras.h +++ b/src/server/game/Spells/Auras/SpellAuras.h @@ -79,7 +79,7 @@ class AuraApplication uint32 GetEffectMask() const { return _effectMask; } bool HasEffect(uint8 effect) const { ASSERT(effect < MAX_SPELL_EFFECTS); return (_effectMask & (1 << effect)) != 0; } bool IsPositive() const { return (_flags & AFLAG_POSITIVE) != 0; } - bool IsSelfcast() const { return (_flags & AFLAG_NOCASTER) == 0; } + bool IsSelfcast() const { return (_flags & AFLAG_NOCASTER) != 0; } uint8 GetEffectsToApply() const { return _effectsToApply; } void SetRemoveMode(AuraRemoveMode mode) { _removeMode = mode; } diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index 760ae42bbfd..3279e524b0c 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -58,6 +58,7 @@ #include "Battlefield.h" #include "BattlefieldMgr.h" #include "SpellPackets.h" +#include "SpellHistory.h" extern pEffect SpellEffects[TOTAL_SPELL_EFFECTS]; @@ -3318,7 +3319,7 @@ void Spell::cast(bool skipCheck) //Clear spell cooldowns after every spell is cast if .cheat cooldown is enabled. if (m_caster->ToPlayer()->GetCommandStatus(CHEAT_COOLDOWN)) - m_caster->ToPlayer()->RemoveSpellCooldown(m_spellInfo->Id, true); + m_caster->GetSpellHistory()->ResetCooldown(m_spellInfo->Id, true); } SetExecutedCurrently(false); @@ -3521,30 +3522,7 @@ void Spell::_handle_finish_phase() void Spell::SendSpellCooldown() { - Player* _player = m_caster->ToPlayer(); - if (!_player) - { - // Handle pet cooldowns here if needed instead of in PetAI to avoid hidden cooldown restarts - Creature* _creature = m_caster->ToCreature(); - if (_creature && (_creature->IsPet() || _creature->IsGuardian())) - _creature->AddCreatureSpellCooldown(m_spellInfo->Id); - - return; - } - - // mana/health/etc potions, disabled by client (until combat out as declarate) - if (m_CastItem && (m_CastItem->IsPotion() || m_spellInfo->IsCooldownStartedOnEvent())) - { - // need in some way provided data for Spell::finish SendCooldownEvent - _player->SetLastPotionId(m_CastItem->GetEntry()); - return; - } - - // have infinity cooldown but set at aura apply // do not set cooldown for triggered spells (needed by reincarnation) - if (m_spellInfo->IsCooldownStartedOnEvent() || m_spellInfo->IsPassive() || (_triggeredCastFlags & TRIGGERED_IGNORE_SPELL_AND_CATEGORY_CD)) - return; - - _player->AddSpellAndCategoryCooldowns(m_spellInfo, m_CastItem ? m_CastItem->GetEntry() : 0, this); + m_caster->GetSpellHistory()->HandleCooldowns(m_spellInfo, m_CastItem, this); } void Spell::update(uint32 difftime) @@ -4809,23 +4787,26 @@ SpellCastResult Spell::CheckCast(bool strict) return SPELL_FAILED_CASTER_DEAD; // check cooldowns to prevent cheating - if (m_caster->GetTypeId() == TYPEID_PLAYER && !m_spellInfo->HasAttribute(SPELL_ATTR0_PASSIVE)) + if (!m_spellInfo->IsPassive()) { - //can cast triggered (by aura only?) spells while have this flag - if (!(_triggeredCastFlags & TRIGGERED_IGNORE_CASTER_AURASTATE) && m_caster->ToPlayer()->HasFlag(PLAYER_FLAGS, PLAYER_ALLOW_ONLY_ABILITY)) - return SPELL_FAILED_SPELL_IN_PROGRESS; + if (m_caster->GetTypeId() == TYPEID_PLAYER) + { + //can cast triggered (by aura only?) spells while have this flag + if (!(_triggeredCastFlags & TRIGGERED_IGNORE_CASTER_AURASTATE) && m_caster->ToPlayer()->HasFlag(PLAYER_FLAGS, PLAYER_ALLOW_ONLY_ABILITY)) + return SPELL_FAILED_SPELL_IN_PROGRESS; - if (m_caster->ToPlayer()->HasSpellCooldown(m_spellInfo->Id)) + // check if we are using a potion in combat for the 2nd+ time. Cooldown is added only after caster gets out of combat + if (m_caster->ToPlayer()->GetLastPotionId() && m_CastItem && (m_CastItem->IsPotion() || m_spellInfo->IsCooldownStartedOnEvent())) + return SPELL_FAILED_NOT_READY; + } + + if (!m_caster->GetSpellHistory()->IsReady(m_spellInfo)) { if (m_triggeredByAuraSpell) return SPELL_FAILED_DONT_REPORT; else return SPELL_FAILED_NOT_READY; } - - // check if we are using a potion in combat for the 2nd+ time. Cooldown is added only after caster gets out of combat - if (m_caster->ToPlayer()->GetLastPotionId() && m_CastItem && (m_CastItem->IsPotion() || m_spellInfo->IsCooldownStartedOnEvent())) - return SPELL_FAILED_NOT_READY; } if (m_spellInfo->HasAttribute(SPELL_ATTR7_IS_CHEAT_SPELL) && !m_caster->HasFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_ALLOW_CHEAT_SPELLS)) @@ -5510,7 +5491,7 @@ SpellCastResult Spell::CheckCast(bool strict) TalentEntry const* talent = sTalentStore.LookupEntry(m_misc.TalentId); if (!talent) return SPELL_FAILED_DONT_REPORT; - if (m_caster->ToPlayer()->HasSpellCooldown(talent->SpellID)) + if (m_caster->GetSpellHistory()->HasCooldown(talent->SpellID)) return SPELL_FAILED_CANT_UNTALENT; break; } @@ -5695,13 +5676,13 @@ SpellCastResult Spell::CheckPetCast(Unit* target) } // cooldown - if (Creature const* creatureCaster = m_caster->ToCreature()) - if (creatureCaster->HasSpellCooldown(m_spellInfo->Id)) + if (Creature* creatureCaster = m_caster->ToCreature()) + if (!creatureCaster->GetSpellHistory()->IsReady(m_spellInfo)) return SPELL_FAILED_NOT_READY; // Check if spell is affected by GCD if (m_spellInfo->StartRecoveryCategory > 0) - if (m_caster->GetCharmInfo() && m_caster->GetCharmInfo()->GetGlobalCooldownMgr().HasGlobalCooldown(m_spellInfo)) + if (m_caster->GetCharmInfo() && m_caster->GetSpellHistory()->HasGlobalCooldown(m_spellInfo)) return SPELL_FAILED_NOT_READY; return CheckCast(true); @@ -7415,13 +7396,11 @@ enum GCDLimits bool Spell::HasGlobalCooldown() const { - // Only player or controlled units have global cooldown - if (m_caster->GetCharmInfo()) - return m_caster->GetCharmInfo()->GetGlobalCooldownMgr().HasGlobalCooldown(m_spellInfo); - else if (m_caster->GetTypeId() == TYPEID_PLAYER) - return m_caster->ToPlayer()->GetGlobalCooldownMgr().HasGlobalCooldown(m_spellInfo); - else + // Only players or controlled units have global cooldown + if (m_caster->GetTypeId() != TYPEID_PLAYER && !m_caster->GetCharmInfo()) return false; + + return m_caster->GetSpellHistory()->HasGlobalCooldown(m_spellInfo); } void Spell::TriggerGlobalCooldown() @@ -7430,6 +7409,10 @@ void Spell::TriggerGlobalCooldown() if (!gcd) return; + // Only players or controlled units have global cooldown + if (m_caster->GetTypeId() != TYPEID_PLAYER && !m_caster->GetCharmInfo()) + return; + if (m_caster->GetTypeId() == TYPEID_PLAYER) if (m_caster->ToPlayer()->GetCommandStatus(CHEAT_COOLDOWN)) return; @@ -7451,11 +7434,7 @@ void Spell::TriggerGlobalCooldown() gcd = MAX_GCD; } - // Only players or controlled units have global cooldown - if (m_caster->GetCharmInfo()) - m_caster->GetCharmInfo()->GetGlobalCooldownMgr().AddGlobalCooldown(m_spellInfo, gcd); - else if (m_caster->GetTypeId() == TYPEID_PLAYER) - m_caster->ToPlayer()->GetGlobalCooldownMgr().AddGlobalCooldown(m_spellInfo, gcd); + m_caster->GetSpellHistory()->AddGlobalCooldown(m_spellInfo, gcd); } void Spell::CancelGlobalCooldown() @@ -7468,10 +7447,10 @@ void Spell::CancelGlobalCooldown() return; // Only players or controlled units have global cooldown - if (m_caster->GetCharmInfo()) - m_caster->GetCharmInfo()->GetGlobalCooldownMgr().CancelGlobalCooldown(m_spellInfo); - else if (m_caster->GetTypeId() == TYPEID_PLAYER) - m_caster->ToPlayer()->GetGlobalCooldownMgr().CancelGlobalCooldown(m_spellInfo); + if (m_caster->GetTypeId() != TYPEID_PLAYER && !m_caster->GetCharmInfo()) + return; + + m_caster->GetSpellHistory()->CancelGlobalCooldown(m_spellInfo); } bool Spell::HasEffect(SpellEffectName effect) const diff --git a/src/server/game/Spells/Spell.h b/src/server/game/Spells/Spell.h index e751f9915cd..42c5258f4f8 100644 --- a/src/server/game/Spells/Spell.h +++ b/src/server/game/Spells/Spell.h @@ -498,6 +498,7 @@ class Spell void ReSetTimer() { m_timer = m_casttime > 0 ? m_casttime : 0; } bool IsNextMeleeSwingSpell() const; bool IsTriggered() const { return (_triggeredCastFlags & TRIGGERED_FULL_MASK) != 0; } + bool IsIgnoringCooldowns() const { return (_triggeredCastFlags & TRIGGERED_IGNORE_SPELL_AND_CATEGORY_CD) != 0; } bool IsChannelActive() const { return m_caster->GetUInt32Value(UNIT_CHANNEL_SPELL) != 0; } bool IsAutoActionResetSpell() const; diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp index 73217e6a9e3..c08b3e6ec23 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -32,6 +32,7 @@ #include "DynamicObject.h" #include "SpellAuras.h" #include "SpellAuraEffects.h" +#include "SpellHistory.h" #include "Group.h" #include "UpdateData.h" #include "MapManager.h" @@ -672,9 +673,7 @@ void Spell::EffectTriggerSpell(SpellEffIndex /*effIndex*/) return; // Reset cooldown on stealth if needed - if (unitTarget->ToPlayer()->HasSpellCooldown(1784)) - unitTarget->ToPlayer()->RemoveSpellCooldown(1784); - + unitTarget->GetSpellHistory()->ResetCooldown(1784); unitTarget->CastSpell(unitTarget, 1784, true); return; } @@ -778,7 +777,7 @@ void Spell::EffectTriggerSpell(SpellEffIndex /*effIndex*/) // Remove spell cooldown (not category) if spell triggering spell with cooldown and same category if (m_caster->GetTypeId() == TYPEID_PLAYER && m_spellInfo->CategoryRecoveryTime && spellInfo->CategoryRecoveryTime && m_spellInfo->GetCategory() == spellInfo->GetCategory()) - m_caster->ToPlayer()->RemoveSpellCooldown(spellInfo->Id); + m_caster->GetSpellHistory()->ResetCooldown(spellInfo->Id); // original caster guid only for GO cast m_caster->CastSpell(targets, spellInfo, &values, TRIGGERED_FULL_MASK, NULL, NULL, m_originalCasterGUID); @@ -831,7 +830,7 @@ void Spell::EffectTriggerMissileSpell(SpellEffIndex /*effIndex*/) // Remove spell cooldown (not category) if spell triggering spell with cooldown and same category if (m_caster->GetTypeId() == TYPEID_PLAYER && m_spellInfo->CategoryRecoveryTime && spellInfo->CategoryRecoveryTime && m_spellInfo->GetCategory() == spellInfo->GetCategory()) - m_caster->ToPlayer()->RemoveSpellCooldown(spellInfo->Id); + m_caster->GetSpellHistory()->ResetCooldown(spellInfo->Id); // original caster guid only for GO cast m_caster->CastSpell(targets, spellInfo, &values, TRIGGERED_FULL_MASK, NULL, NULL, m_originalCasterGUID); @@ -3175,7 +3174,7 @@ void Spell::EffectInterruptCast(SpellEffIndex effIndex) if (m_originalCaster) { int32 duration = m_spellInfo->GetDuration(); - unitTarget->ProhibitSpellSchool(curSpellInfo->GetSchoolMask(), unitTarget->ModSpellDuration(m_spellInfo, unitTarget, duration, false, 1 << effIndex)); + unitTarget->GetSpellHistory()->LockSpellSchool(curSpellInfo->GetSchoolMask(), unitTarget->ModSpellDuration(m_spellInfo, unitTarget, duration, false, 1 << effIndex)); } ExecuteLogEffectInterruptCast(effIndex, unitTarget, curSpellInfo->Id); unitTarget->InterruptSpell(CurrentSpellTypes(i), false); @@ -3944,7 +3943,7 @@ void Spell::EffectStuck(SpellEffIndex /*effIndex*/) } // the player dies if hearthstone is in cooldown, else the player is teleported to home - if (player->HasSpellCooldown(8690)) + if (player->GetSpellHistory()->HasCooldown(8690)) { player->Kill(player); return; @@ -5616,7 +5615,7 @@ void Spell::EffectCastButtons(SpellEffIndex /*effIndex*/) if (!spellInfo) continue; - if (!p_caster->HasSpell(spell_id) || p_caster->HasSpellCooldown(spell_id)) + if (!p_caster->HasSpell(spell_id) || p_caster->GetSpellHistory()->HasCooldown(spell_id)) continue; if (!spellInfo->HasAttribute(SPELL_ATTR9_SUMMON_PLAYER_TOTEM)) diff --git a/src/server/game/Spells/SpellHistory.cpp b/src/server/game/Spells/SpellHistory.cpp new file mode 100644 index 00000000000..4060122630d --- /dev/null +++ b/src/server/game/Spells/SpellHistory.cpp @@ -0,0 +1,854 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "SpellHistory.h" +#include "Pet.h" +#include "Player.h" +#include "SpellInfo.h" +#include "SpellPackets.h" +#include "World.h" + +SpellHistory::Clock::duration const SpellHistory::InfinityCooldownDelay = std::chrono::duration_cast<SpellHistory::Clock::duration>(std::chrono::seconds(MONTH)); + +template<> +struct SpellHistory::PersistenceHelper<Player> +{ + static CharacterDatabaseStatements const CooldownsDeleteStatement = CHAR_DEL_CHAR_SPELL_COOLDOWNS; + static CharacterDatabaseStatements const CooldownsInsertStatement = CHAR_INS_CHAR_SPELL_COOLDOWN; + static CharacterDatabaseStatements const ChargesDeleteStatement = CHAR_DEL_CHAR_SPELL_CHARGES; + static CharacterDatabaseStatements const ChargesInsertStatement = CHAR_INS_CHAR_SPELL_CHARGES; + + static void SetIdentifier(PreparedStatement* stmt, uint8 index, Unit* owner) { stmt->setUInt64(index, owner->GetGUID().GetCounter()); } + + static bool ReadCooldown(Field* fields, uint32* spellId, CooldownEntry* cooldownEntry) + { + *spellId = fields[0].GetUInt32(); + if (!sSpellMgr->GetSpellInfo(*spellId)) + return false; + + cooldownEntry->CooldownEnd = Clock::from_time_t(time_t(fields[2].GetUInt32())); + cooldownEntry->ItemId = fields[1].GetUInt32(); + return true; + } + + static bool ReadCharge(Field* fields, uint32* categoryId, ChargeEntry* chargeEntry) + { + *categoryId = fields[0].GetUInt32(); + if (!sSpellCategoryStore.LookupEntry(*categoryId)) + return false; + + chargeEntry->RechargeStart = Clock::from_time_t(time_t(fields[1].GetUInt32())); + chargeEntry->RechargeEnd = Clock::from_time_t(time_t(fields[2].GetUInt32())); + return true; + } + + static void WriteCooldown(PreparedStatement* stmt, uint8& index, CooldownStorageType::value_type const& cooldown) + { + stmt->setUInt32(index++, cooldown.first); + stmt->setUInt32(index++, cooldown.second.ItemId); + stmt->setUInt32(index++, uint32(Clock::to_time_t(cooldown.second.CooldownEnd))); + } + + static void WriteCharge(PreparedStatement* stmt, uint8& index, uint32 chargeCategory, ChargeEntry const& charge) + { + stmt->setUInt32(index++, chargeCategory); + stmt->setUInt32(index++, uint32(Clock::to_time_t(charge.RechargeStart))); + stmt->setUInt32(index++, uint32(Clock::to_time_t(charge.RechargeEnd))); + } +}; + +template<> +struct SpellHistory::PersistenceHelper<Pet> +{ + static CharacterDatabaseStatements const CooldownsDeleteStatement = CHAR_DEL_PET_SPELL_COOLDOWNS; + static CharacterDatabaseStatements const CooldownsInsertStatement = CHAR_INS_PET_SPELL_COOLDOWN; + static CharacterDatabaseStatements const ChargesDeleteStatement = CHAR_DEL_PET_SPELL_CHARGES; + static CharacterDatabaseStatements const ChargesInsertStatement = CHAR_INS_PET_SPELL_CHARGES; + + static void SetIdentifier(PreparedStatement* stmt, uint8 index, Unit* owner) { stmt->setUInt32(index, owner->GetCharmInfo()->GetPetNumber()); } + + static bool ReadCooldown(Field* fields, uint32* spellId, CooldownEntry* cooldownEntry) + { + *spellId = fields[0].GetUInt32(); + if (!sSpellMgr->GetSpellInfo(*spellId)) + return false; + + cooldownEntry->CooldownEnd = Clock::from_time_t(time_t(fields[1].GetUInt32())); + cooldownEntry->ItemId = 0; + return true; + } + + static bool ReadCharge(Field* fields, uint32* categoryId, ChargeEntry* chargeEntry) + { + *categoryId = fields[0].GetUInt32(); + if (!sSpellCategoryStore.LookupEntry(*categoryId)) + return false; + + chargeEntry->RechargeStart = Clock::from_time_t(time_t(fields[1].GetUInt32())); + chargeEntry->RechargeEnd = Clock::from_time_t(time_t(fields[2].GetUInt32())); + return true; + } + + static void WriteCooldown(PreparedStatement* stmt, uint8& index, CooldownStorageType::value_type const& cooldown) + { + stmt->setUInt32(index++, cooldown.first); + stmt->setUInt32(index++, uint32(Clock::to_time_t(cooldown.second.CooldownEnd))); + } + + static void WriteCharge(PreparedStatement* stmt, uint8& index, uint32 chargeCategory, ChargeEntry const& charge) + { + stmt->setUInt32(index++, chargeCategory); + stmt->setUInt32(index++, uint32(Clock::to_time_t(charge.RechargeStart))); + stmt->setUInt32(index++, uint32(Clock::to_time_t(charge.RechargeEnd))); + } +}; + +template<class OwnerType> +void SpellHistory::LoadFromDB(PreparedQueryResult cooldownsResult, PreparedQueryResult chargesResult) +{ + typedef PersistenceHelper<OwnerType> StatementInfo; + + if (cooldownsResult) + { + do + { + uint32 spellId; + CooldownEntry cooldown; + if (StatementInfo::ReadCooldown(cooldownsResult->Fetch(), &spellId, &cooldown)) + _spellCooldowns[spellId] = cooldown; + + } while (cooldownsResult->NextRow()); + } + + if (chargesResult) + { + do + { + Field* fields = chargesResult->Fetch(); + uint32 categoryId = 0; + ChargeEntry charges; + if (StatementInfo::ReadCharge(fields, &categoryId, &charges)) + _categoryCharges[categoryId].push_back(charges); + + } while (chargesResult->NextRow()); + } +} + +template<class OwnerType> +void SpellHistory::SaveToDB(SQLTransaction& trans) +{ + typedef PersistenceHelper<OwnerType> StatementInfo; + + uint8 index = 0; + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(StatementInfo::CooldownsDeleteStatement); + StatementInfo::SetIdentifier(stmt, index++, _owner); + trans->Append(stmt); + + for (auto const& p : _spellCooldowns) + { + if (!p.second.OnHold) + { + index = 0; + stmt = CharacterDatabase.GetPreparedStatement(StatementInfo::CooldownsInsertStatement); + StatementInfo::SetIdentifier(stmt, index++, _owner); + StatementInfo::WriteCooldown(stmt, index, p); + trans->Append(stmt); + } + } + + stmt = CharacterDatabase.GetPreparedStatement(StatementInfo::ChargesDeleteStatement); + StatementInfo::SetIdentifier(stmt, 0, _owner); + trans->Append(stmt); + + for (auto const& p : _categoryCharges) + { + for (ChargeEntry const& charge : p.second) + { + index = 0; + stmt = CharacterDatabase.GetPreparedStatement(StatementInfo::ChargesInsertStatement); + StatementInfo::SetIdentifier(stmt, index++, _owner); + StatementInfo::WriteCharge(stmt, index, p.first, charge); + trans->Append(stmt); + } + } +} + +void SpellHistory::Update() +{ + SQLTransaction t; + Clock::time_point now = Clock::now(); + for (auto itr = _spellCooldowns.begin(); itr != _spellCooldowns.end();) + { + if (itr->second.CooldownEnd < now) + itr = _spellCooldowns.erase(itr); + else + ++itr; + } + + for (auto& p : _categoryCharges) + { + std::deque<ChargeEntry>& chargeRefreshTimes = p.second; + while (!chargeRefreshTimes.empty() && chargeRefreshTimes.front().RechargeEnd <= now) + chargeRefreshTimes.pop_front(); + } +} + +void SpellHistory::HandleCooldowns(SpellInfo const* spellInfo, Item const* item, Spell* spell /*= nullptr*/) +{ + if (ConsumeCharge(spellInfo->ChargeCategoryEntry)) + return; + + if (Player* player = _owner->ToPlayer()) + { + // potions start cooldown until exiting combat + if (item && (item->IsPotion() || spellInfo->IsCooldownStartedOnEvent())) + { + player->SetLastPotionId(item->GetEntry()); + return; + } + } + + if (spellInfo->IsCooldownStartedOnEvent() || spellInfo->IsPassive() || (spell && spell->IsIgnoringCooldowns())) + return; + + StartCooldown(spellInfo, item ? item->GetEntry() : 0, spell); +} + +bool SpellHistory::IsReady(SpellInfo const* spellInfo) const +{ + if (spellInfo->PreventionType == SPELL_PREVENTION_TYPE_SILENCE) + if (IsSchoolLocked(spellInfo->GetSchoolMask())) + return false; + + if (HasCooldown(spellInfo->Id)) + return false; + + if (!HasCharge(spellInfo->ChargeCategoryEntry)) + return false; + + return true; +} + +template<class PacketType> +void SpellHistory::WritePacket(PacketType* packet) const +{ + static_assert(!std::is_same<PacketType, PacketType>::value /*static_assert(false)*/, "This packet is not supported."); +} + +template<> +void SpellHistory::WritePacket(WorldPackets::Spells::SendSpellHistory* sendSpellHistory) const +{ + sendSpellHistory->Entries.reserve(_spellCooldowns.size()); + + Clock::time_point now = Clock::now(); + for (auto const& p : _spellCooldowns) + { + SpellInfo const* spellInfo = sSpellMgr->AssertSpellInfo(p.first); + WorldPackets::Spells::SpellHistoryEntry historyEntry; + historyEntry.SpellID = p.first; + historyEntry.ItemID = p.second.ItemId; + historyEntry.Category = spellInfo->GetCategory(); + + if (p.second.OnHold) + historyEntry.OnHold = true; + else + { + std::chrono::milliseconds cooldownDuration = std::chrono::duration_cast<std::chrono::milliseconds>(p.second.CooldownEnd - now); + if (cooldownDuration.count() <= 0) + continue; + + if (spellInfo->GetCategory()) + historyEntry.CategoryRecoveryTime = uint32(cooldownDuration.count()); + else + historyEntry.RecoveryTime = uint32(cooldownDuration.count()); + } + + sendSpellHistory->Entries.push_back(historyEntry); + } +} + +template<> +void SpellHistory::WritePacket(WorldPackets::Spells::SendSpellCharges* sendSpellCharges) const +{ + sendSpellCharges->Entries.reserve(_categoryCharges.size()); + + Clock::time_point now = Clock::now(); + for (auto const& p : _categoryCharges) + { + if (!p.second.empty()) + { + std::chrono::milliseconds cooldownDuration = std::chrono::duration_cast<std::chrono::milliseconds>(p.second.front().RechargeEnd - now); + if (cooldownDuration.count() <= 0) + continue; + + WorldPackets::Spells::SpellChargeEntry chargeEntry; + chargeEntry.Category = p.first; + chargeEntry.NextRecoveryTime = uint32(cooldownDuration.count()); + chargeEntry.ConsumedCharges = p.second.size(); + sendSpellCharges->Entries.push_back(chargeEntry); + } + } +} + +/* +template<> +void SpellHistory::WritePacket(WorldPackets::Pet::PetSpells* petSpells) +{ + Clock::time_point now = Clock::now(); + + petSpells->Cooldowns.reserve(_spellCooldowns.size()); + for (auto const& p : _spellCooldowns) + { + SpellInfo const* spellInfo = sSpellMgr->AssertSpellInfo(p.first); + WorldPackets::Pet::PetSpellCooldown petSpellCooldown; + petSpellCooldown.SpellID = p.first; + petSpellCooldown.Category = spellInfo->GetCategory(); + + if (!p.second.OnHold) + { + std::chrono::milliseconds cooldownDuration = std::chrono::duration_cast<std::chrono::milliseconds>(p.second.CooldownEnd - now); + if (cooldownDuration.count() <= 0) + continue; + + if (spellInfo->GetCategory()) + petSpellCooldown.CategoryDuration = uint32(cooldownDuration.count()); + else + petSpellCooldown.Duration = uint32(cooldownDuration.count()); + } + + petSpells->Cooldowns.push_back(historyEntry); + } + + petSpells->SpellHistory.reserve(_categoryCharges.size()); + for (auto const& p : _categoryCharges) + { + if (!p.second.empty()) + { + std::chrono::milliseconds cooldownDuration = std::chrono::duration_cast<std::chrono::milliseconds>(p.second.front().RechargeEnd - now); + if (cooldownDuration.count() <= 0) + continue; + + WorldPackets::Pet::PetSpellHistory petChargeEntry; + petChargeEntry.CategoryID = p.first; + petChargeEntry.RecoveryTime = uint32(cooldownDuration.count()); + petChargeEntry.ConsumedCharges = p.second.size(); + + petSpells->SpellHistory.push_back(petChargeEntry); + } + } +} +*/ + +void SpellHistory::StartCooldown(SpellInfo const* spellInfo, uint32 itemId, Spell* spell /*= nullptr*/, bool onHold /*= false*/) +{ + // init cooldown values + uint32 categoryId = 0; + int32 cooldown = -1; + int32 categoryCooldown = -1; + + // some special item spells without correct cooldown in SpellInfo + // cooldown information stored in item prototype + if (itemId) + { + if (ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId)) + { + for (uint8 idx = 0; idx < proto->Effects.size(); ++idx) + { + if (uint32(proto->Effects[idx].SpellID) == spellInfo->Id) + { + categoryId = proto->Effects[idx].Category; + cooldown = proto->Effects[idx].Cooldown; + categoryCooldown = proto->Effects[idx].CategoryCooldown; + break; + } + } + } + } + + // if no cooldown found above then base at DBC data + if (cooldown < 0 && categoryCooldown < 0) + { + categoryId = spellInfo->GetCategory(); + cooldown = spellInfo->RecoveryTime; + categoryCooldown = spellInfo->CategoryRecoveryTime; + } + + Clock::time_point curTime = Clock::now(); + Clock::time_point catrecTime; + Clock::time_point recTime; + bool needsCooldownPacket = false; + + // overwrite time for selected category + if (onHold) + { + // use +MONTH as infinite cooldown marker + catrecTime = categoryCooldown > 0 ? (curTime + InfinityCooldownDelay) : curTime; + recTime = cooldown > 0 ? (curTime + InfinityCooldownDelay) : catrecTime; + } + else + { + // shoot spells used equipped item cooldown values already assigned in GetAttackTime(RANGED_ATTACK) + // prevent 0 cooldowns set by another way + if (cooldown <= 0 && categoryCooldown <= 0 && (categoryId == 76 || (spellInfo->IsAutoRepeatRangedSpell() && spellInfo->Id != 75))) + cooldown = _owner->GetAttackTime(RANGED_ATTACK); + + // Now we have cooldown data (if found any), time to apply mods + if (Player* modOwner = _owner->GetSpellModOwner()) + { + if (cooldown > 0) + modOwner->ApplySpellMod(spellInfo->Id, SPELLMOD_COOLDOWN, cooldown, spell); + + if (categoryCooldown > 0 && !spellInfo->HasAttribute(SPELL_ATTR6_IGNORE_CATEGORY_COOLDOWN_MODS)) + modOwner->ApplySpellMod(spellInfo->Id, SPELLMOD_COOLDOWN, categoryCooldown, spell); + } + + if (int32 cooldownMod = _owner->GetTotalAuraModifier(SPELL_AURA_MOD_COOLDOWN)) + { + // Apply SPELL_AURA_MOD_COOLDOWN only to own spells + Player* playerOwner = GetPlayerOwner(); + if (!playerOwner || playerOwner->HasSpell(spellInfo->Id)) + { + needsCooldownPacket = true; + cooldown += cooldownMod * IN_MILLISECONDS; // SPELL_AURA_MOD_COOLDOWN does not affect category cooldows, verified with shaman shocks + } + } + + // Apply SPELL_AURA_MOD_SPELL_CATEGORY_COOLDOWN modifiers + // Note: This aura applies its modifiers to all cooldowns of spells with set category, not to category cooldown only + if (categoryId) + { + if (int32 categoryModifier = _owner->GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_SPELL_CATEGORY_COOLDOWN, categoryId)) + { + if (cooldown > 0) + cooldown += categoryModifier; + + if (categoryCooldown > 0) + categoryCooldown += categoryModifier; + } + + SpellCategoryEntry const* categoryEntry = sSpellCategoryStore.AssertEntry(categoryId); + if (categoryEntry->Flags & SPELL_CATEGORY_FLAG_COOLDOWN_EXPIRES_AT_DAILY_RESET) + categoryCooldown = int32(std::chrono::duration_cast<std::chrono::milliseconds>(Clock::from_time_t(sWorld->GetNextDailyQuestsResetTime()) - Clock::now()).count()); + } + + // replace negative cooldowns by 0 + if (cooldown < 0) + cooldown = 0; + + if (categoryCooldown < 0) + categoryCooldown = 0; + + // no cooldown after applying spell mods + if (cooldown == 0 && categoryCooldown == 0) + return; + + catrecTime = categoryCooldown ? curTime + std::chrono::duration_cast<Clock::duration>(std::chrono::milliseconds(categoryCooldown)) : curTime; + recTime = cooldown ? curTime + std::chrono::duration_cast<Clock::duration>(std::chrono::milliseconds(cooldown)) : catrecTime; + } + + // self spell cooldown + if (recTime != curTime) + { + AddCooldown(spellInfo->Id, itemId, recTime, onHold); + + if (needsCooldownPacket) + { + if (Player* playerOwner = GetPlayerOwner()) + { + WorldPackets::Spells::SpellCooldown spellCooldown; + spellCooldown.Caster = _owner->GetGUID(); + spellCooldown.Flags = SPELL_COOLDOWN_FLAG_NONE; + spellCooldown.SpellCooldowns.emplace_back(spellInfo->Id, uint32(cooldown)); + playerOwner->SendDirectMessage(spellCooldown.Write()); + } + } + } + + // category spells + if (categoryId && catrecTime != curTime) + { + SpellCategoryStore::const_iterator i_scstore = sSpellsByCategoryStore.find(categoryId); + if (i_scstore != sSpellsByCategoryStore.end()) + { + for (SpellCategorySet::const_iterator i_scset = i_scstore->second.begin(); i_scset != i_scstore->second.end(); ++i_scset) + { + if (*i_scset == spellInfo->Id) // skip main spell, already handled above + continue; + + AddCooldown(*i_scset, itemId, catrecTime, onHold); + } + } + } +} + +void SpellHistory::SendCooldownEvent(SpellInfo const* spellInfo, uint32 itemId /*= 0*/, Spell* spell /*= nullptr*/, bool startCooldown /*= true*/) +{ + // start cooldowns at server side, if any + if (startCooldown) + StartCooldown(spellInfo, itemId, spell); + + if (Player* player = GetPlayerOwner()) + { + // Send activate cooldown timer (possible 0) at client side + player->SendDirectMessage(WorldPackets::Spells::CooldownEvent(_owner->GetGUID(), spellInfo->Id).Write()); + + uint32 category = spellInfo->GetCategory(); + if (category && spellInfo->CategoryRecoveryTime) + { + SpellCategoryStore::const_iterator ct = sSpellsByCategoryStore.find(category); + if (ct != sSpellsByCategoryStore.end()) + { + for (auto const& cooldownPair : _spellCooldowns) + { + uint32 categorySpell = cooldownPair.first; + if (!ct->second.count(categorySpell)) + continue; + + if (categorySpell == spellInfo->Id) // skip main spell, already handled above + continue; + + SpellInfo const* spellInfo2 = sSpellMgr->AssertSpellInfo(categorySpell); + if (!spellInfo2->IsCooldownStartedOnEvent()) + continue; + + player->SendDirectMessage(WorldPackets::Spells::CooldownEvent(_owner->GetGUID(), categorySpell).Write()); + } + } + } + } +} + +void SpellHistory::AddCooldown(uint32 spellId, uint32 itemId, Clock::time_point cooldownEnd, bool onHold /*= false*/) +{ + CooldownEntry& cooldownEntry = _spellCooldowns[spellId]; + cooldownEntry.CooldownEnd = cooldownEnd; + cooldownEntry.ItemId = itemId; + cooldownEntry.OnHold = onHold; +} + +void SpellHistory::ModifyCooldown(uint32 spellId, int32 cooldownModMs) +{ + auto itr = _spellCooldowns.find(spellId); + if (!cooldownModMs || itr == _spellCooldowns.end()) + return; + + Clock::time_point now = Clock::now(); + Clock::duration offset = std::chrono::duration_cast<Clock::duration>(std::chrono::milliseconds(cooldownModMs)); + if (itr->second.CooldownEnd + offset > now) + itr->second.CooldownEnd += offset; + else + _spellCooldowns.erase(itr); + + if (Player* playerOwner = GetPlayerOwner()) + { + WorldPackets::Spells::ModifyCooldown modifyCooldown; + modifyCooldown.UnitGUID = _owner->GetGUID(); + modifyCooldown.SpellID = spellId; + modifyCooldown.DeltaTime = cooldownModMs; + playerOwner->SendDirectMessage(modifyCooldown.Write()); + } +} + +void SpellHistory::ResetCooldown(uint32 spellId, bool update /*= false*/) +{ + auto itr = _spellCooldowns.find(spellId); + if (itr == _spellCooldowns.end()) + return; + + ResetCooldown(itr, update); +} + +void SpellHistory::ResetCooldown(CooldownStorageType::iterator& itr, bool update /*= false*/) +{ + if (update) + { + if (Player* playerOwner = GetPlayerOwner()) + { + WorldPackets::Spells::ClearCooldown clearCooldown; + clearCooldown.CasterGUID = _owner->GetGUID(); + clearCooldown.SpellID = itr->first; + clearCooldown.ClearOnHold = false; + playerOwner->SendDirectMessage(clearCooldown.Write()); + } + } + + itr = _spellCooldowns.erase(itr); +} + +void SpellHistory::ResetAllCooldowns() +{ + if (Player* playerOwner = GetPlayerOwner()) + { + std::vector<int32> cooldowns; + cooldowns.reserve(_spellCooldowns.size()); + for (auto const& p : _spellCooldowns) + cooldowns.push_back(p.first); + + SendClearCooldowns(cooldowns); + } + + _spellCooldowns.clear(); +} + +bool SpellHistory::HasCooldown(uint32 spellId) const +{ + return _spellCooldowns.count(spellId) != 0; +} + +uint32 SpellHistory::GetRemainingCooldown(uint32 spellId) const +{ + auto itr = _spellCooldowns.find(spellId); + if (itr == _spellCooldowns.end()) + return 0; + + Clock::time_point now = Clock::now(); + if (itr->second.CooldownEnd < now) + return 0; + + Clock::duration remaining = itr->second.CooldownEnd - now; + return uint32(std::chrono::duration_cast<std::chrono::milliseconds>(remaining).count()); +} + +void SpellHistory::LockSpellSchool(SpellSchoolMask schoolMask, uint32 lockoutTime) +{ + Clock::time_point lockoutEnd = Clock::now() + std::chrono::duration_cast<Clock::duration>(std::chrono::milliseconds(lockoutTime)); + for (uint32 i = 0; i < MAX_SPELL_SCHOOL; ++i) + if (SpellSchoolMask(1 << i) & schoolMask) + _schoolLockouts[i] = lockoutEnd; + + std::set<uint32> knownSpells; + if (Player* plrOwner = _owner->ToPlayer()) + { + for (auto const& p : plrOwner->GetSpellMap()) + if (p.second->state != PLAYERSPELL_REMOVED) + knownSpells.insert(p.first); + } + else if (Pet* petOwner = _owner->ToPet()) + { + for (auto const& p : petOwner->m_spells) + if (p.second.state != PLAYERSPELL_REMOVED) + knownSpells.insert(p.first); + } + else + { + Creature* creatureOwner = _owner->ToCreature(); + for (uint8 i = 0; i < CREATURE_MAX_SPELLS; ++i) + if (creatureOwner->m_spells[i]) + knownSpells.insert(creatureOwner->m_spells[i]); + } + + WorldPackets::Spells::SpellCooldown spellCooldown; + spellCooldown.Caster = _owner->GetGUID(); + spellCooldown.Flags = SPELL_COOLDOWN_FLAG_NONE; + for (uint32 spellId : knownSpells) + { + SpellInfo const* spellInfo = sSpellMgr->AssertSpellInfo(spellId); + if (spellInfo->IsCooldownStartedOnEvent()) + continue; + + if (spellInfo->PreventionType != SPELL_PREVENTION_TYPE_SILENCE) + continue; + + if ((schoolMask & spellInfo->GetSchoolMask()) && GetRemainingCooldown(spellId) < lockoutTime) + { + spellCooldown.SpellCooldowns.emplace_back(spellId, lockoutTime); + AddCooldown(spellId, 0, lockoutEnd); + } + } + + if (Player* player = GetPlayerOwner()) + if (!spellCooldown.SpellCooldowns.empty()) + player->SendDirectMessage(spellCooldown.Write()); +} + +bool SpellHistory::IsSchoolLocked(SpellSchoolMask schoolMask) const +{ + Clock::time_point now = Clock::now(); + for (uint32 i = 0; i < MAX_SPELL_SCHOOL; ++i) + if (SpellSchoolMask(1 << i) & schoolMask) + if (_schoolLockouts[i] > now) + return true; + + return false; +} + +bool SpellHistory::ConsumeCharge(SpellCategoryEntry const* chargeCategoryEntry) +{ + if (!chargeCategoryEntry) + return false; + + int32 chargeRecovery = GetChargeRecoveryTime(chargeCategoryEntry); + if (chargeRecovery > 0 && GetMaxCharges(chargeCategoryEntry) > 0) + { + Clock::time_point recoveryStart; + std::deque<ChargeEntry>& charges = _categoryCharges[chargeCategoryEntry->ID]; + if (charges.empty()) + recoveryStart = Clock::now(); + else + recoveryStart = charges.back().RechargeEnd; + + charges.emplace_back(recoveryStart, std::chrono::milliseconds(chargeRecovery)); + return true; + } + + return false; +} + +void SpellHistory::RestoreCharge(SpellCategoryEntry const* chargeCategoryEntry) +{ + if (!chargeCategoryEntry) + return; + + auto itr = _categoryCharges.find(chargeCategoryEntry->ID); + if (itr != _categoryCharges.end() && !itr->second.empty()) + { + itr->second.pop_back(); + + if (Player* player = GetPlayerOwner()) + { + int32 maxCharges = GetMaxCharges(chargeCategoryEntry); + int32 usedCharges = itr->second.size(); + float count = float(maxCharges - usedCharges); + if (usedCharges) + { + ChargeEntry& charge = itr->second.front(); + std::chrono::milliseconds remaining = std::chrono::duration_cast<std::chrono::milliseconds>(charge.RechargeEnd - Clock::now()); + std::chrono::milliseconds recharge = std::chrono::duration_cast<std::chrono::milliseconds>(charge.RechargeEnd - charge.RechargeStart); + count += 1.0f - float(remaining.count()) / float(recharge.count()); + } + + WorldPackets::Spells::SetSpellCharges setSpellCharges; + setSpellCharges.IsPet = player == _owner; + setSpellCharges.Count = count; + setSpellCharges.Category = chargeCategoryEntry->ID; + player->SendDirectMessage(setSpellCharges.Write()); + } + } +} + +void SpellHistory::ResetCharges(SpellCategoryEntry const* chargeCategoryEntry) +{ + if (!chargeCategoryEntry) + return; + + auto itr = _categoryCharges.find(chargeCategoryEntry->ID); + if (itr != _categoryCharges.end()) + { + _categoryCharges.erase(itr); + + if (Player* player = GetPlayerOwner()) + { + WorldPackets::Spells::ClearSpellCharges clearSpellCharges; + clearSpellCharges.Unit = _owner->GetGUID(); + clearSpellCharges.Category = chargeCategoryEntry->ID; + player->SendDirectMessage(clearSpellCharges.Write()); + } + } +} + +void SpellHistory::ResetAllCharges() +{ + _categoryCharges.clear(); + + if (Player* player = GetPlayerOwner()) + { + WorldPackets::Spells::ClearAllSpellCharges clearAllSpellCharges; + clearAllSpellCharges.Unit = _owner->GetGUID(); + player->SendDirectMessage(clearAllSpellCharges.Write()); + } +} + +bool SpellHistory::HasCharge(SpellCategoryEntry const* chargeCategoryEntry) const +{ + if (!chargeCategoryEntry) + return true; + + // Check if the spell is currently using charges (untalented warlock Dark Soul) + int32 maxCharges = GetMaxCharges(chargeCategoryEntry); + if (maxCharges <= 0) + return true; + + auto itr = _categoryCharges.find(chargeCategoryEntry->ID); + return itr == _categoryCharges.end() || itr->second.size() < maxCharges; +} + +int32 SpellHistory::GetMaxCharges(SpellCategoryEntry const* chargeCategoryEntry) const +{ + if (!chargeCategoryEntry) + return 0; + + uint32 charges = chargeCategoryEntry->MaxCharges; + charges += _owner->GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_MAX_CHARGES, chargeCategoryEntry->ID); + return charges; +} + +int32 SpellHistory::GetChargeRecoveryTime(SpellCategoryEntry const* chargeCategoryEntry) const +{ + if (!chargeCategoryEntry) + return 0; + + int32 recoveryTime = chargeCategoryEntry->ChargeRecoveryTime; + recoveryTime += _owner->GetTotalAuraModifierByMiscValue(SPELL_AURA_CHARGE_RECOVERY_MOD, chargeCategoryEntry->ID); + + float recoveryTimeF = recoveryTime; + recoveryTimeF *= _owner->GetTotalAuraMultiplierByMiscValue(SPELL_AURA_CHARGE_RECOVERY_MULTIPLIER, chargeCategoryEntry->ID); + + if (_owner->HasAuraType(SPELL_AURA_CHARGE_RECOVERY_AFFECTED_BY_HASTE)) + recoveryTimeF *= _owner->GetFloatValue(UNIT_MOD_CAST_HASTE); + + if (_owner->HasAuraType(SPELL_AURA_CHARGE_RECOVERY_AFFECTED_BY_HASTE_REGEN)) + recoveryTimeF *= _owner->GetFloatValue(UNIT_FIELD_MOD_HASTE_REGEN); + + return int32(std::floor(recoveryTimeF)); +} + +bool SpellHistory::HasGlobalCooldown(SpellInfo const* spellInfo) const +{ + auto itr = _globalCooldowns.find(spellInfo->StartRecoveryCategory); + return itr != _globalCooldowns.end() && itr->second > Clock::now(); +} + +void SpellHistory::AddGlobalCooldown(SpellInfo const* spellInfo, uint32 duration) +{ + _globalCooldowns[spellInfo->StartRecoveryCategory] = Clock::now() + std::chrono::duration_cast<Clock::duration>(std::chrono::milliseconds(duration)); +} + +void SpellHistory::CancelGlobalCooldown(SpellInfo const* spellInfo) +{ + _globalCooldowns[spellInfo->StartRecoveryCategory] = Clock::time_point(Clock::duration(0)); +} + +Player* SpellHistory::GetPlayerOwner() const +{ + return _owner->GetCharmerOrOwnerPlayerOrPlayerItself(); +} + +void SpellHistory::SendClearCooldowns(std::vector<int32> const& cooldowns) const +{ + if (Player const* playerOwner = GetPlayerOwner()) + { + WorldPackets::Spells::ClearCooldowns clearCooldowns; + clearCooldowns.Guid = _owner->GetGUID(); + clearCooldowns.SpellID = cooldowns; + playerOwner->SendDirectMessage(clearCooldowns.Write()); + } +} + +template void SpellHistory::LoadFromDB<Player>(PreparedQueryResult cooldownsResult, PreparedQueryResult chargesResult); +template void SpellHistory::LoadFromDB<Pet>(PreparedQueryResult cooldownsResult, PreparedQueryResult chargesResult); +template void SpellHistory::SaveToDB<Player>(SQLTransaction& trans); +template void SpellHistory::SaveToDB<Pet>(SQLTransaction& trans); diff --git a/src/server/game/Spells/SpellHistory.h b/src/server/game/Spells/SpellHistory.h new file mode 100644 index 00000000000..38ae922750c --- /dev/null +++ b/src/server/game/Spells/SpellHistory.h @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SpellHistory_h__ +#define SpellHistory_h__ + +#include "SharedDefines.h" +#include "QueryResult.h" +#include "Transaction.h" +#include <chrono> +#include <deque> + +class Item; +class Player; +class Spell; +class SpellInfo; +class Unit; +struct SpellCategoryEntry; + +/// Spell cooldown flags sent in SMSG_SPELL_COOLDOWN +enum SpellCooldownFlags +{ + SPELL_COOLDOWN_FLAG_NONE = 0x0, + SPELL_COOLDOWN_FLAG_INCLUDE_GCD = 0x1, ///< Starts GCD in addition to normal cooldown specified in the packet + SPELL_COOLDOWN_FLAG_INCLUDE_EVENT_COOLDOWNS = 0x2 ///< Starts GCD for spells that should start their cooldown on events, requires SPELL_COOLDOWN_FLAG_INCLUDE_GCD set +}; + +class SpellHistory +{ +public: + typedef std::chrono::system_clock Clock; + + struct CooldownEntry + { + CooldownEntry() : ItemId(0), OnHold(false) { } + CooldownEntry(Clock::time_point endTime, uint32 itemId) : CooldownEnd(endTime), ItemId(itemId), OnHold(false) { } + + Clock::time_point CooldownEnd; + uint32 ItemId; + bool OnHold; + }; + + struct ChargeEntry + { + ChargeEntry() { } + ChargeEntry(Clock::time_point startTime, std::chrono::milliseconds rechargeTime) : RechargeStart(startTime), RechargeEnd(startTime + rechargeTime) { } + ChargeEntry(Clock::time_point startTime, Clock::time_point endTime) : RechargeStart(startTime), RechargeEnd(endTime) { } + + Clock::time_point RechargeStart; + Clock::time_point RechargeEnd; + }; + + typedef std::unordered_map<uint32 /*spellId*/, CooldownEntry> CooldownStorageType; + typedef std::unordered_map<uint32 /*categoryId*/, std::deque<ChargeEntry>> ChargeStorageType; + typedef std::unordered_map<uint32 /*categoryId*/, Clock::time_point> GlobalCooldownStorageType; + + explicit SpellHistory(Unit* owner) : _owner(owner), _schoolLockouts() { } + + template<class OwnerType> + void LoadFromDB(PreparedQueryResult cooldownsResult, PreparedQueryResult chargesResult); + + template<class OwnerType> + void SaveToDB(SQLTransaction& trans); + + void Update(); + + void HandleCooldowns(SpellInfo const* spellInfo, Item const* item, Spell* spell = nullptr); + bool IsReady(SpellInfo const* spellInfo) const; + template<class PacketType> + void WritePacket(PacketType* packet) const; + + // Cooldowns + static Clock::duration const InfinityCooldownDelay; // used for set "infinity cooldowns" for spells and check + + void StartCooldown(SpellInfo const* spellInfo, uint32 itemId, Spell* spell = nullptr, bool onHold = false); + void SendCooldownEvent(SpellInfo const* spellInfo, uint32 itemId = 0, Spell* spell = nullptr, bool startCooldown = true); + + template<class Type, class Period> + void AddCooldown(uint32 spellId, uint32 itemId, std::chrono::duration<Type, Period> cooldownDuration) + { + AddCooldown(spellId, itemId, Clock::now() + std::chrono::duration_cast<Clock::duration>(cooldownDuration)); + } + + void AddCooldown(uint32 spellId, uint32 itemId, Clock::time_point cooldownEnd, bool onHold = false); + void ModifyCooldown(uint32 spellId, int32 cooldownModMs); + void ResetCooldown(uint32 spellId, bool update = false); + void ResetCooldown(CooldownStorageType::iterator& itr, bool update = false); + template<typename Predicate> + void ResetCooldowns(Predicate predicate, bool update = false) + { + std::vector<int32> resetCooldowns; + resetCooldowns.reserve(_spellCooldowns.size()); + for (auto itr = _spellCooldowns.begin(); itr != _spellCooldowns.end();) + { + if (predicate(itr)) + ResetCooldown(itr, false); + else + ++itr; + } + + if (update && !resetCooldowns.empty()) + SendClearCooldowns(resetCooldowns); + } + + void ResetAllCooldowns(); + bool HasCooldown(uint32 spellId) const; + uint32 GetRemainingCooldown(uint32 spellId) const; + + // School lockouts + void LockSpellSchool(SpellSchoolMask schoolMask, uint32 lockoutTime); + bool IsSchoolLocked(SpellSchoolMask schoolMask) const; + + // Charges + bool ConsumeCharge(SpellCategoryEntry const* chargeCategoryEntry); + void RestoreCharge(SpellCategoryEntry const* chargeCategoryEntry); + void ResetCharges(SpellCategoryEntry const* chargeCategoryEntry); + void ResetAllCharges(); + bool HasCharge(SpellCategoryEntry const* chargeCategoryEntry) const; + int32 GetMaxCharges(SpellCategoryEntry const* chargeCategoryEntry) const; + int32 GetChargeRecoveryTime(SpellCategoryEntry const* chargeCategoryEntry) const; + + // Global cooldown + bool HasGlobalCooldown(SpellInfo const* spellInfo) const; + void AddGlobalCooldown(SpellInfo const* spellInfo, uint32 duration); + void CancelGlobalCooldown(SpellInfo const* spellInfo); + +private: + Player* GetPlayerOwner() const; + void SendClearCooldowns(std::vector<int32> const& cooldowns) const; + + Unit* _owner; + CooldownStorageType _spellCooldowns; + Clock::time_point _schoolLockouts[MAX_SPELL_SCHOOL]; + ChargeStorageType _categoryCharges; + GlobalCooldownStorageType _globalCooldowns; + + template<class T> + struct PersistenceHelper { }; +}; + +#endif // SpellHistory_h__ diff --git a/src/server/game/Spells/SpellInfo.cpp b/src/server/game/Spells/SpellInfo.cpp index a70c73de6df..ab4ac032345 100644 --- a/src/server/game/Spells/SpellInfo.cpp +++ b/src/server/game/Spells/SpellInfo.cpp @@ -1054,6 +1054,7 @@ SpellInfo::SpellInfo(SpellEntry const* spellEntry, SpellEffectEntryMap effects) StartRecoveryCategory = _categorie ? _categorie->StartRecoveryCategory : 0; DmgClass = _categorie ? _categorie->DefenseType : 0; PreventionType = _categorie ? _categorie->PreventionType : 0; + ChargeCategoryEntry = _categorie ? sSpellCategoryStore.LookupEntry(_categorie->ChargeCategory) : 0; // SpellClassOptionsEntry SpellClassOptionsEntry const* _class = GetSpellClassOptions(); diff --git a/src/server/game/Spells/SpellInfo.h b/src/server/game/Spells/SpellInfo.h index b585588726e..c9b87e3abf7 100644 --- a/src/server/game/Spells/SpellInfo.h +++ b/src/server/game/Spells/SpellInfo.h @@ -399,6 +399,7 @@ public: uint32 PreventionType; int32 RequiredAreasID; uint32 SchoolMask; + SpellCategoryEntry const* ChargeCategoryEntry; uint32 SpellDifficultyId; uint32 SpellScalingId; uint32 SpellAuraOptionsId; diff --git a/src/server/scripts/Commands/cs_misc.cpp b/src/server/scripts/Commands/cs_misc.cpp index 889b24480e6..65b2d20c9d2 100644 --- a/src/server/scripts/Commands/cs_misc.cpp +++ b/src/server/scripts/Commands/cs_misc.cpp @@ -36,6 +36,7 @@ #include "GroupMgr.h" #include "MMapFactory.h" #include "DisableMgr.h" +#include "SpellHistory.h" class misc_commandscript : public CommandScript { @@ -713,7 +714,8 @@ public: if (!*args) { - target->RemoveAllSpellCooldown(); + target->GetSpellHistory()->ResetAllCooldowns(); + target->GetSpellHistory()->ResetAllCharges(); handler->PSendSysMessage(LANG_REMOVEALL_COOLDOWN, nameLink.c_str()); } else @@ -723,14 +725,16 @@ public: if (!spellIid) return false; - if (!sSpellMgr->GetSpellInfo(spellIid)) + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellIid); + if (!spellInfo) { handler->PSendSysMessage(LANG_UNKNOWN_SPELL, target == handler->GetSession()->GetPlayer() ? handler->GetTrinityString(LANG_YOU) : nameLink.c_str()); handler->SetSentErrorMessage(true); return false; } - target->RemoveSpellCooldown(spellIid, true); + target->GetSpellHistory()->ResetCooldown(spellIid, true); + target->GetSpellHistory()->ResetCharges(spellInfo->ChargeCategoryEntry); handler->PSendSysMessage(LANG_REMOVE_COOLDOWN, spellIid, target == handler->GetSession()->GetPlayer() ? handler->GetTrinityString(LANG_YOU) : nameLink.c_str()); } return true; diff --git a/src/server/scripts/Northrend/IcecrownCitadel/boss_icecrown_gunship_battle.cpp b/src/server/scripts/Northrend/IcecrownCitadel/boss_icecrown_gunship_battle.cpp index 754b22620d0..511a2044f6a 100644 --- a/src/server/scripts/Northrend/IcecrownCitadel/boss_icecrown_gunship_battle.cpp +++ b/src/server/scripts/Northrend/IcecrownCitadel/boss_icecrown_gunship_battle.cpp @@ -25,6 +25,7 @@ #include "ScriptMgr.h" #include "ScriptedCreature.h" #include "SpellAuraEffects.h" +#include "SpellHistory.h" #include "SpellScript.h" #include "Transport.h" #include "TransportMgr.h" @@ -644,10 +645,10 @@ protected: { if (Instance->GetBossState(DATA_ICECROWN_GUNSHIP_BATTLE) == IN_PROGRESS && !me->HasUnitState(UNIT_STATE_CASTING) && !me->HasReactState(REACT_PASSIVE) && - !me->HasSpellCooldown(BurningPitchId)) + !me->GetSpellHistory()->HasCooldown(BurningPitchId)) { DoCastAOE(BurningPitchId, true); - me->_AddCreatureSpellCooldown(BurningPitchId, time(NULL) + urand(3000, 4000) / IN_MILLISECONDS); + me->GetSpellHistory()->AddCooldown(BurningPitchId, 0, std::chrono::milliseconds(urand(3000, 4000))); } } @@ -1469,7 +1470,7 @@ struct npc_gunship_boarding_addAI : public gunship_npc_AI DoCast(me, SPELL_BATTLE_EXPERIENCE, true); DoCast(me, SPELL_TELEPORT_TO_ENEMY_SHIP, true); DoCast(me, Instance->GetData(DATA_TEAM_IN_INSTANCE) == HORDE ? SPELL_MELEE_TARGETING_ON_ORGRIMS_HAMMER : SPELL_MELEE_TARGETING_ON_SKYBREAKER, true); - me->_AddCreatureSpellCooldown(BurningPitchId, time(NULL) + 3); + me->GetSpellHistory()->AddCooldown(BurningPitchId, 0, std::chrono::seconds(3)); std::list<Player*> players; Trinity::UnitAuraCheck check(true, Instance->GetData(DATA_TEAM_IN_INSTANCE) == HORDE ? SPELL_ON_ORGRIMS_HAMMER_DECK : SPELL_ON_SKYBREAKER_DECK); @@ -1698,11 +1699,11 @@ class npc_gunship_rocketeer : public CreatureScript return; uint32 spellId = me->GetEntry() == NPC_SKYBREAKER_MORTAR_SOLDIER ? SPELL_ROCKET_ARTILLERY_A : SPELL_ROCKET_ARTILLERY_H; - if (me->HasSpellCooldown(spellId)) + if (me->GetSpellHistory()->HasCooldown(spellId)) return; DoCastAOE(spellId, true); - me->_AddCreatureSpellCooldown(spellId, time(NULL) + 9); + me->GetSpellHistory()->AddCooldown(spellId, 0, std::chrono::seconds(9)); } }; diff --git a/src/server/scripts/Spells/spell_dk.cpp b/src/server/scripts/Spells/spell_dk.cpp index 9edf3f42b88..98c6781e866 100644 --- a/src/server/scripts/Spells/spell_dk.cpp +++ b/src/server/scripts/Spells/spell_dk.cpp @@ -25,6 +25,7 @@ #include "ScriptMgr.h" #include "SpellScript.h" #include "SpellAuraEffects.h" +#include "SpellHistory.h" #include "Containers.h" enum DeathKnightSpells @@ -148,7 +149,7 @@ class spell_dk_anti_magic_shell : public SpellScriptLoader { // Cannot reduce cooldown by more than 50% int32 val = std::min(glyph->GetAmount(), int32(absorbedAmount) * 100 / maxHealth); - player->ModifySpellCooldown(GetId(), -int32(player->GetSpellCooldownDelay(GetId()) * val / 100)); + player->GetSpellHistory()->ModifyCooldown(GetId(), -int32(player->GetSpellHistory()->GetRemainingCooldown(GetId()) * val / 100)); } } diff --git a/src/server/scripts/Spells/spell_druid.cpp b/src/server/scripts/Spells/spell_druid.cpp index dd002dedee5..2418e75d22c 100644 --- a/src/server/scripts/Spells/spell_druid.cpp +++ b/src/server/scripts/Spells/spell_druid.cpp @@ -25,6 +25,7 @@ #include "ScriptMgr.h" #include "SpellScript.h" #include "SpellAuraEffects.h" +#include "SpellHistory.h" #include "Containers.h" enum DruidSpells @@ -122,8 +123,8 @@ class spell_dru_eclipse : public SpellScriptLoader if (!caster) return; - if (caster->ToPlayer()->GetAuraOfRankedSpell(SPELL_DRUID_NATURES_GRACE)) - caster->ToPlayer()->RemoveSpellCooldown(SPELL_DRUID_NATURES_GRACE_TRIGGER, true); + if (caster->GetAuraOfRankedSpell(SPELL_DRUID_NATURES_GRACE)) + caster->GetSpellHistory()->ResetCooldown(SPELL_DRUID_NATURES_GRACE_TRIGGER, true); } void Register() override diff --git a/src/server/scripts/Spells/spell_generic.cpp b/src/server/scripts/Spells/spell_generic.cpp index a05eab1cd08..31779c4df22 100644 --- a/src/server/scripts/Spells/spell_generic.cpp +++ b/src/server/scripts/Spells/spell_generic.cpp @@ -36,6 +36,7 @@ #include "SkillDiscovery.h" #include "SpellScript.h" #include "SpellAuraEffects.h" +#include "SpellHistory.h" #include "Vehicle.h" class spell_gen_absorb0_hitlimit1 : public SpellScriptLoader @@ -1313,9 +1314,7 @@ class spell_gen_divine_storm_cd_reset : public SpellScriptLoader void HandleScript(SpellEffIndex /*effIndex*/) { - Player* caster = GetCaster()->ToPlayer(); - if (caster->HasSpellCooldown(SPELL_DIVINE_STORM)) - caster->RemoveSpellCooldown(SPELL_DIVINE_STORM, true); + GetCaster()->GetSpellHistory()->ResetCooldown(SPELL_DIVINE_STORM, true); } void Register() override @@ -3324,7 +3323,7 @@ class spell_pvp_trinket_wotf_shared_cd : public SpellScriptLoader { // This is only needed because spells cast from spell_linked_spell are triggered by default // Spell::SendSpellCooldown() skips all spells with TRIGGERED_IGNORE_SPELL_AND_CATEGORY_CD - GetCaster()->ToPlayer()->AddSpellAndCategoryCooldowns(GetSpellInfo(), GetCastItem() ? GetCastItem()->GetEntry() : 0, GetSpell()); + GetCaster()->GetSpellHistory()->StartCooldown(GetSpellInfo(), 0, GetSpell()); } void Register() override diff --git a/src/server/scripts/Spells/spell_hunter.cpp b/src/server/scripts/Spells/spell_hunter.cpp index 205965910c1..a0c01479cdb 100644 --- a/src/server/scripts/Spells/spell_hunter.cpp +++ b/src/server/scripts/Spells/spell_hunter.cpp @@ -29,6 +29,7 @@ #include "GridNotifiersImpl.h" #include "SpellScript.h" #include "SpellAuraEffects.h" +#include "SpellHistory.h" enum HunterSpells { @@ -641,24 +642,20 @@ class spell_hun_readiness : public SpellScriptLoader void HandleDummy(SpellEffIndex /*effIndex*/) { - Player* caster = GetCaster()->ToPlayer(); // immediately finishes the cooldown on your other Hunter abilities except Bestial Wrath - const SpellCooldowns& cm = caster->ToPlayer()->GetSpellCooldownMap(); - for (SpellCooldowns::const_iterator itr = cm.begin(); itr != cm.end();) + GetCaster()->GetSpellHistory()->ResetCooldowns([](SpellHistory::CooldownStorageType::iterator itr) { - SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(itr->first); + SpellInfo const* spellInfo = sSpellMgr->AssertSpellInfo(itr->first); ///! If spellId in cooldown map isn't valid, the above will return a null pointer. - if (spellInfo && - spellInfo->SpellFamilyName == SPELLFAMILY_HUNTER && + if (spellInfo->SpellFamilyName == SPELLFAMILY_HUNTER && spellInfo->Id != SPELL_HUNTER_READINESS && spellInfo->Id != SPELL_HUNTER_BESTIAL_WRATH && spellInfo->Id != SPELL_DRAENEI_GIFT_OF_THE_NAARU && spellInfo->GetRecoveryTime() > 0) - caster->RemoveSpellCooldown((itr++)->first, true); - else - ++itr; - } + return true; + return false; + }, true); } void Register() override diff --git a/src/server/scripts/Spells/spell_item.cpp b/src/server/scripts/Spells/spell_item.cpp index eb5c4c147a8..66c90c85e55 100644 --- a/src/server/scripts/Spells/spell_item.cpp +++ b/src/server/scripts/Spells/spell_item.cpp @@ -26,6 +26,7 @@ #include "ScriptedCreature.h" #include "SpellScript.h" #include "SpellAuraEffects.h" +#include "SpellHistory.h" #include "SkillDiscovery.h" #include "Battleground.h" @@ -1355,7 +1356,7 @@ class spell_item_red_rider_air_rifle : public SpellScriptLoader caster->CastSpell(caster, SPELL_AIR_RIFLE_HOLD_VISUAL, true); // needed because this spell shares GCD with its triggered spells (which must not be cast with triggered flag) if (Player* player = caster->ToPlayer()) - player->GetGlobalCooldownMgr().CancelGlobalCooldown(GetSpellInfo()); + player->GetSpellHistory()->CancelGlobalCooldown(GetSpellInfo()); if (urand(0, 4)) caster->CastSpell(target, SPELL_AIR_RIFLE_SHOOT, false); else @@ -2374,7 +2375,7 @@ class spell_item_rocket_boots : public SpellScriptLoader if (Battleground* bg = caster->GetBattleground()) bg->EventPlayerDroppedFlag(caster); - caster->RemoveSpellCooldown(SPELL_ROCKET_BOOTS_PROC); + caster->GetSpellHistory()->ResetCooldown(SPELL_ROCKET_BOOTS_PROC); caster->CastSpell(caster, SPELL_ROCKET_BOOTS_PROC, true, NULL); } diff --git a/src/server/scripts/Spells/spell_mage.cpp b/src/server/scripts/Spells/spell_mage.cpp index ea8630acc59..16e819430d2 100644 --- a/src/server/scripts/Spells/spell_mage.cpp +++ b/src/server/scripts/Spells/spell_mage.cpp @@ -24,6 +24,7 @@ #include "Player.h" #include "ScriptMgr.h" #include "SpellScript.h" +#include "SpellHistory.h" #include "SpellAuraEffects.h" #include "Pet.h" #include "GridNotifiers.h" @@ -326,22 +327,12 @@ class spell_mage_cold_snap : public SpellScriptLoader void HandleDummy(SpellEffIndex /*effIndex*/) { - Player* caster = GetCaster()->ToPlayer(); - // immediately finishes the cooldown on Frost spells - const SpellCooldowns& cm = caster->GetSpellCooldownMap(); - for (SpellCooldowns::const_iterator itr = cm.begin(); itr != cm.end();) + GetCaster()->GetSpellHistory()->ResetCooldowns([](SpellHistory::CooldownStorageType::iterator itr) { SpellInfo const* spellInfo = sSpellMgr->AssertSpellInfo(itr->first); - - if (spellInfo->SpellFamilyName == SPELLFAMILY_MAGE && - (spellInfo->GetSchoolMask() & SPELL_SCHOOL_MASK_FROST) && - spellInfo->Id != SPELL_MAGE_COLD_SNAP && spellInfo->GetRecoveryTime() > 0) - { - caster->RemoveSpellCooldown((itr++)->first, true); - } - else - ++itr; - } + return spellInfo->SpellFamilyName == SPELLFAMILY_MAGE && (spellInfo->GetSchoolMask() & SPELL_SCHOOL_MASK_FROST) && + spellInfo->Id != SPELL_MAGE_COLD_SNAP && spellInfo->GetRecoveryTime() > 0; + }, true); } void Register() override @@ -703,7 +694,7 @@ class spell_mage_glyph_of_ice_block : public SpellScriptLoader { PreventDefaultAction(); // Remove Frost Nova cooldown - GetTarget()->ToPlayer()->RemoveSpellCooldown(SPELL_MAGE_FROST_NOVA, true); + GetTarget()->GetSpellHistory()->ResetCooldown(SPELL_MAGE_FROST_NOVA, true); } void Register() override diff --git a/src/server/scripts/Spells/spell_paladin.cpp b/src/server/scripts/Spells/spell_paladin.cpp index 305f1cce9e3..2af1b8c14f2 100644 --- a/src/server/scripts/Spells/spell_paladin.cpp +++ b/src/server/scripts/Spells/spell_paladin.cpp @@ -25,6 +25,7 @@ #include "ScriptMgr.h" #include "SpellScript.h" #include "SpellAuraEffects.h" +#include "SpellHistory.h" #include "Group.h" enum PaladinSpells @@ -600,7 +601,7 @@ class spell_pal_grand_crusader : public SpellScriptLoader void HandleEffectProc(AuraEffect const* /*aurEff*/, ProcEventInfo& /*eventInfo*/) { - GetTarget()->ToPlayer()->RemoveSpellCooldown(SPELL_PALADIN_AVENGERS_SHIELD, true); + GetTarget()->GetSpellHistory()->ResetCooldown(SPELL_PALADIN_AVENGERS_SHIELD, true); } void Register() override diff --git a/src/server/scripts/Spells/spell_rogue.cpp b/src/server/scripts/Spells/spell_rogue.cpp index 902e89f6c56..b70ae177928 100644 --- a/src/server/scripts/Spells/spell_rogue.cpp +++ b/src/server/scripts/Spells/spell_rogue.cpp @@ -25,6 +25,7 @@ #include "ScriptMgr.h" #include "SpellScript.h" #include "SpellAuraEffects.h" +#include "SpellHistory.h" #include "Containers.h" enum RogueSpells @@ -159,11 +160,11 @@ class spell_rog_cheat_death : public SpellScriptLoader void Absorb(AuraEffect* /*aurEff*/, DamageInfo & dmgInfo, uint32 & absorbAmount) { Player* target = GetTarget()->ToPlayer(); - if (dmgInfo.GetDamage() < target->GetHealth() || target->HasSpellCooldown(SPELL_ROGUE_CHEAT_DEATH_COOLDOWN) || !roll_chance_i(absorbChance)) + if (dmgInfo.GetDamage() < target->GetHealth() || target->GetSpellHistory()->HasCooldown(SPELL_ROGUE_CHEAT_DEATH_COOLDOWN) || !roll_chance_i(absorbChance)) return; target->CastSpell(target, SPELL_ROGUE_CHEAT_DEATH_COOLDOWN, true); - target->AddSpellCooldown(SPELL_ROGUE_CHEAT_DEATH_COOLDOWN, 0, time(NULL) + 60); + target->GetSpellHistory()->AddCooldown(SPELL_ROGUE_CHEAT_DEATH_COOLDOWN, 0, std::chrono::minutes(1)); uint32 health10 = target->CountPctFromMaxHealth(10); @@ -550,31 +551,21 @@ class spell_rog_preparation : public SpellScriptLoader void HandleDummy(SpellEffIndex /*effIndex*/) { - Player* caster = GetCaster()->ToPlayer(); - - // immediately finishes the cooldown on certain Rogue abilities - SpellCooldowns const& cm = caster->GetSpellCooldownMap(); - for (SpellCooldowns::const_iterator itr = cm.begin(); itr != cm.end();) + Unit* caster = GetCaster(); + caster->GetSpellHistory()->ResetCooldowns([caster](SpellHistory::CooldownStorageType::iterator itr) { SpellInfo const* spellInfo = sSpellMgr->AssertSpellInfo(itr->first); if (spellInfo->SpellFamilyName != SPELLFAMILY_ROGUE) - { - ++itr; - continue; - } + return false; - if ((spellInfo->SpellFamilyFlags[1] & SPELLFAMILYFLAG1_ROGUE_SHADOWSTEP || // Shadowstep + return (spellInfo->SpellFamilyFlags[1] & SPELLFAMILYFLAG1_ROGUE_SHADOWSTEP || // Shadowstep spellInfo->SpellFamilyFlags[0] & SPELLFAMILYFLAG0_ROGUE_VAN_SPRINT) || // Vanish, Sprint // Glyph of Preparation (caster->HasAura(SPELL_ROGUE_GLYPH_OF_PREPARATION) && (spellInfo->SpellFamilyFlags[1] & SPELLFAMILYFLAG1_ROGUE_DISMANTLE_SMOKE_BOMB || // Dismantle, Smoke Bomb - spellInfo->SpellFamilyFlags[0] & SPELLFAMILYFLAG0_ROGUE_KICK))) // Kick - { - caster->RemoveSpellCooldown((itr++)->first, true); - } - else - ++itr; - } + spellInfo->SpellFamilyFlags[0] & SPELLFAMILYFLAG0_ROGUE_KICK)); // Kick + + }, true); } void Register() override diff --git a/src/server/scripts/Spells/spell_shaman.cpp b/src/server/scripts/Spells/spell_shaman.cpp index b5c9c23fa5c..589f67fc39b 100644 --- a/src/server/scripts/Spells/spell_shaman.cpp +++ b/src/server/scripts/Spells/spell_shaman.cpp @@ -26,6 +26,7 @@ #include "GridNotifiers.h" #include "Unit.h" #include "SpellScript.h" +#include "SpellHistory.h" #include "SpellAuraEffects.h" enum ShamanSpells @@ -310,7 +311,7 @@ class spell_sha_earth_shield : public SpellScriptLoader { //! HACK due to currenct proc system implementation if (Player* player = GetTarget()->ToPlayer()) - if (player->HasSpellCooldown(SPELL_SHAMAN_EARTH_SHIELD_HEAL)) + if (player->GetSpellHistory()->HasCooldown(SPELL_SHAMAN_EARTH_SHIELD_HEAL)) return false; return true; } @@ -323,7 +324,7 @@ class spell_sha_earth_shield : public SpellScriptLoader /// @hack: due to currenct proc system implementation if (Player* player = GetTarget()->ToPlayer()) - player->AddSpellCooldown(SPELL_SHAMAN_EARTH_SHIELD_HEAL, 0, time(NULL) + 3); + player->GetSpellHistory()->AddCooldown(SPELL_SHAMAN_EARTH_SHIELD_HEAL, 0, std::chrono::seconds(3)); } void Register() override @@ -462,7 +463,7 @@ class spell_sha_feedback : public SpellScriptLoader { PreventDefaultAction(); // will prevent default effect execution if (Player* target = GetTarget()->ToPlayer()) - target->ModifySpellCooldown(SPELL_SHAMAN_ELEMENTAL_MASTERY, aurEff->GetBaseAmount()); + target->GetSpellHistory()->ModifyCooldown(SPELL_SHAMAN_ELEMENTAL_MASTERY, aurEff->GetBaseAmount()); } void Register() override @@ -832,7 +833,7 @@ class spell_sha_item_t10_elemental_2p_bonus : public SpellScriptLoader { PreventDefaultAction(); if (Player* target = GetTarget()->ToPlayer()) - target->ModifySpellCooldown(SPELL_SHAMAN_ELEMENTAL_MASTERY, -aurEff->GetAmount()); + target->GetSpellHistory()->ModifyCooldown(SPELL_SHAMAN_ELEMENTAL_MASTERY, -aurEff->GetAmount()); } void Register() override @@ -949,7 +950,7 @@ class spell_sha_lava_surge_proc : public SpellScriptLoader void HandleDummy(SpellEffIndex /*effIndex*/) { - GetCaster()->ToPlayer()->RemoveSpellCooldown(SPELL_SHAMAN_LAVA_BURST, true); + GetCaster()->GetSpellHistory()->ResetCooldown(SPELL_SHAMAN_LAVA_BURST, true); } void Register() override @@ -1017,7 +1018,7 @@ class spell_sha_nature_guardian : public SpellScriptLoader { //! HACK due to currenct proc system implementation if (Player* player = GetTarget()->ToPlayer()) - if (player->HasSpellCooldown(GetSpellInfo()->Id)) + if (player->GetSpellHistory()->HasCooldown(GetSpellInfo()->Id)) return false; return GetTarget()->HealthBelowPctDamaged(30, eventInfo.GetDamageInfo()->GetDamage()); @@ -1034,7 +1035,7 @@ class spell_sha_nature_guardian : public SpellScriptLoader eventInfo.GetProcTarget()->getThreatManager().modifyThreatPercent(GetTarget(), -10); if (Player* player = GetTarget()->ToPlayer()) - player->AddSpellCooldown(GetSpellInfo()->Id, 0, time(NULL) + aurEff->GetSpellInfo()->GetEffect(EFFECT_1)->CalcValue()); + player->GetSpellHistory()->AddCooldown(GetSpellInfo()->Id, 0, std::chrono::seconds(aurEff->GetSpellInfo()->GetEffect(EFFECT_1)->CalcValue())); } void Register() override diff --git a/src/server/scripts/Spells/spell_warrior.cpp b/src/server/scripts/Spells/spell_warrior.cpp index b3caff679df..80e738c7442 100644 --- a/src/server/scripts/Spells/spell_warrior.cpp +++ b/src/server/scripts/Spells/spell_warrior.cpp @@ -23,6 +23,7 @@ #include "Player.h" #include "ScriptMgr.h" +#include "SpellHistory.h" #include "SpellScript.h" #include "SpellAuraEffects.h" @@ -697,7 +698,7 @@ class spell_warr_sudden_death : public SpellScriptLoader { // Remove cooldown on Colossus Smash if (Player* player = GetTarget()->ToPlayer()) - player->RemoveSpellCooldown(SPELL_WARRIOR_COLOSSUS_SMASH, true); + player->GetSpellHistory()->ResetCooldown(SPELL_WARRIOR_COLOSSUS_SMASH, true); } void Register() override @@ -799,7 +800,7 @@ class spell_warr_sword_and_board : public SpellScriptLoader { // Remove cooldown on Shield Slam if (Player* player = GetTarget()->ToPlayer()) - player->RemoveSpellCooldown(SPELL_WARRIOR_SHIELD_SLAM, true); + player->GetSpellHistory()->ResetCooldown(SPELL_WARRIOR_SHIELD_SLAM, true); } void Register() override @@ -932,7 +933,7 @@ class spell_warr_vigilance_trigger : public SpellScriptLoader // Remove Taunt cooldown if (Player* target = GetHitPlayer()) - target->RemoveSpellCooldown(SPELL_WARRIOR_TAUNT, true); + target->GetSpellHistory()->ResetCooldown(SPELL_WARRIOR_TAUNT, true); } void Register() override diff --git a/src/server/scripts/World/npcs_special.cpp b/src/server/scripts/World/npcs_special.cpp index d4a73ab161f..121417717a4 100644 --- a/src/server/scripts/World/npcs_special.cpp +++ b/src/server/scripts/World/npcs_special.cpp @@ -56,6 +56,7 @@ EndContentData */ #include "SpellAuras.h" #include "Pet.h" #include "CreatureTextMgr.h" +#include "SpellHistory.h" /*######## # npc_air_force_bots @@ -1208,14 +1209,14 @@ public: if (creature->IsQuestGiver()) player->PrepareQuestMenu(creature->GetGUID()); - if (player->HasSpellCooldown(SPELL_INT) || - player->HasSpellCooldown(SPELL_ARM) || - player->HasSpellCooldown(SPELL_DMG) || - player->HasSpellCooldown(SPELL_RES) || - player->HasSpellCooldown(SPELL_STR) || - player->HasSpellCooldown(SPELL_AGI) || - player->HasSpellCooldown(SPELL_STM) || - player->HasSpellCooldown(SPELL_SPI)) + if (player->GetSpellHistory()->HasCooldown(SPELL_INT) || + player->GetSpellHistory()->HasCooldown(SPELL_ARM) || + player->GetSpellHistory()->HasCooldown(SPELL_DMG) || + player->GetSpellHistory()->HasCooldown(SPELL_RES) || + player->GetSpellHistory()->HasCooldown(SPELL_STR) || + player->GetSpellHistory()->HasCooldown(SPELL_AGI) || + player->GetSpellHistory()->HasCooldown(SPELL_STM) || + player->GetSpellHistory()->HasCooldown(SPELL_SPI)) player->SEND_GOSSIP_MENU(7393, creature->GetGUID()); else { @@ -1282,42 +1283,42 @@ public: break; case GOSSIP_SENDER_MAIN + 1: creature->CastSpell(player, SPELL_DMG, false); - player->AddSpellCooldown(SPELL_DMG, 0, time(NULL) + 7200); + player->GetSpellHistory()->AddCooldown(SPELL_DMG, 0, std::chrono::hours(2)); SendAction(player, creature, action); break; case GOSSIP_SENDER_MAIN + 2: creature->CastSpell(player, SPELL_RES, false); - player->AddSpellCooldown(SPELL_RES, 0, time(NULL) + 7200); + player->GetSpellHistory()->AddCooldown(SPELL_RES, 0, std::chrono::hours(2)); SendAction(player, creature, action); break; case GOSSIP_SENDER_MAIN + 3: creature->CastSpell(player, SPELL_ARM, false); - player->AddSpellCooldown(SPELL_ARM, 0, time(NULL) + 7200); + player->GetSpellHistory()->AddCooldown(SPELL_ARM, 0, std::chrono::hours(2)); SendAction(player, creature, action); break; case GOSSIP_SENDER_MAIN + 4: creature->CastSpell(player, SPELL_SPI, false); - player->AddSpellCooldown(SPELL_SPI, 0, time(NULL) + 7200); + player->GetSpellHistory()->AddCooldown(SPELL_SPI, 0, std::chrono::hours(2)); SendAction(player, creature, action); break; case GOSSIP_SENDER_MAIN + 5: creature->CastSpell(player, SPELL_INT, false); - player->AddSpellCooldown(SPELL_INT, 0, time(NULL) + 7200); + player->GetSpellHistory()->AddCooldown(SPELL_INT, 0, std::chrono::hours(2)); SendAction(player, creature, action); break; case GOSSIP_SENDER_MAIN + 6: creature->CastSpell(player, SPELL_STM, false); - player->AddSpellCooldown(SPELL_STM, 0, time(NULL) + 7200); + player->GetSpellHistory()->AddCooldown(SPELL_STM, 0, std::chrono::hours(2)); SendAction(player, creature, action); break; case GOSSIP_SENDER_MAIN + 7: creature->CastSpell(player, SPELL_STR, false); - player->AddSpellCooldown(SPELL_STR, 0, time(NULL) + 7200); + player->GetSpellHistory()->AddCooldown(SPELL_STR, 0, std::chrono::hours(2)); SendAction(player, creature, action); break; case GOSSIP_SENDER_MAIN + 8: creature->CastSpell(player, SPELL_AGI, false); - player->AddSpellCooldown(SPELL_AGI, 0, time(NULL) + 7200); + player->GetSpellHistory()->AddCooldown(SPELL_AGI, 0, std::chrono::hours(2)); SendAction(player, creature, action); break; } diff --git a/src/server/shared/Database/Implementation/CharacterDatabase.cpp b/src/server/shared/Database/Implementation/CharacterDatabase.cpp index 4cd4803997c..e95b05bc418 100644 --- a/src/server/shared/Database/Implementation/CharacterDatabase.cpp +++ b/src/server/shared/Database/Implementation/CharacterDatabase.cpp @@ -112,7 +112,8 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_SEL_MAIL_COUNT, "SELECT COUNT(*) FROM mail WHERE receiver = ?", CONNECTION_SYNCH); PrepareStatement(CHAR_SEL_CHARACTER_SOCIALLIST, "SELECT friend, flags, note FROM character_social JOIN characters ON characters.guid = character_social.friend WHERE character_social.guid = ? AND deleteinfos_name IS NULL LIMIT 255", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_CHARACTER_HOMEBIND, "SELECT mapId, zoneId, posX, posY, posZ FROM character_homebind WHERE guid = ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_SEL_CHARACTER_SPELLCOOLDOWNS, "SELECT spell, item, time FROM character_spell_cooldown WHERE guid = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_SEL_CHARACTER_SPELLCOOLDOWNS, "SELECT spell, item, time FROM character_spell_cooldown WHERE guid = ? AND time > UNIX_TIMESTAMP()", CONNECTION_ASYNC); + PrepareStatement(CHAR_SEL_CHARACTER_SPELL_CHARGES, "SELECT categoryId, rechargeStart, rechargeEnd FROM character_spell_charges WHERE guid = ? AND rechargeEnd > UNIX_TIMESTAMP() ORDER BY rechargeEnd", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_CHARACTER_DECLINEDNAMES, "SELECT genitive, dative, accusative, instrumental, prepositional FROM character_declinedname WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_GUILD_MEMBER, "SELECT guildid, rank FROM guild_member WHERE guid = ?", CONNECTION_BOTH); PrepareStatement(CHAR_SEL_GUILD_MEMBER_EXTENDED, "SELECT g.guildid, g.name, gr.rname, gr.rid, gm.pnote, gm.offnote " @@ -511,7 +512,10 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_UPD_CHAR_REP_FACTION_CHANGE, "UPDATE character_reputation SET faction = ?, standing = ? WHERE faction = ? AND guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_CHAR_TITLES_FACTION_CHANGE, "UPDATE characters SET knownTitles = ? WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_RES_CHAR_TITLES_FACTION_CHANGE, "UPDATE characters SET chosenTitle = 0 WHERE guid = ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_DEL_CHAR_SPELL_COOLDOWN, "DELETE FROM character_spell_cooldown WHERE guid = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_DEL_CHAR_SPELL_COOLDOWNS, "DELETE FROM character_spell_cooldown WHERE guid = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_INS_CHAR_SPELL_COOLDOWN, "INSERT INTO character_spell_cooldown (guid, spell, item, time) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC); + PrepareStatement(CHAR_DEL_CHAR_SPELL_CHARGES, "DELETE FROM character_spell_charges WHERE guid = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_INS_CHAR_SPELL_CHARGES, "INSERT INTO character_spell_charges (guid, categoryId, rechargeStart, rechargeEnd) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_CHARACTER, "DELETE FROM characters WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_CHAR_ACTION, "DELETE FROM character_action WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_CHAR_AURA, "DELETE FROM character_aura WHERE guid = ?", CONNECTION_ASYNC); @@ -608,13 +612,16 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_SEL_PET_AURA, "SELECT casterGuid, spell, effectMask, recalculateMask, stackCount, maxDuration, remainTime, remainCharges FROM pet_aura WHERE guid = ?", CONNECTION_SYNCH); PrepareStatement(CHAR_SEL_PET_AURA_EFFECT, "SELECT casterGuid, spell, effectMask, effectIndex, amount, baseAmount FROM pet_aura_effect WHERE guid = ?", CONNECTION_SYNCH); PrepareStatement(CHAR_SEL_PET_SPELL, "SELECT spell, active FROM pet_spell WHERE guid = ?", CONNECTION_SYNCH); - PrepareStatement(CHAR_SEL_PET_SPELL_COOLDOWN, "SELECT spell, time FROM pet_spell_cooldown WHERE guid = ?", CONNECTION_SYNCH); + PrepareStatement(CHAR_SEL_PET_SPELL_COOLDOWN, "SELECT spell, time FROM pet_spell_cooldown WHERE guid = ? AND time > UNIX_TIMESTAMP()", CONNECTION_SYNCH); PrepareStatement(CHAR_SEL_PET_DECLINED_NAME, "SELECT genitive, dative, accusative, instrumental, prepositional FROM character_pet_declinedname WHERE owner = ? AND id = ?", CONNECTION_SYNCH); PrepareStatement(CHAR_DEL_PET_AURAS, "DELETE FROM pet_aura WHERE guid = ?", CONNECTION_BOTH); PrepareStatement(CHAR_DEL_PET_AURA_EFFECTS, "DELETE FROM pet_aura_effect WHERE guid = ?", CONNECTION_BOTH); PrepareStatement(CHAR_DEL_PET_SPELLS, "DELETE FROM pet_spell WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_PET_SPELL_COOLDOWNS, "DELETE FROM pet_spell_cooldown WHERE guid = ?", CONNECTION_BOTH); PrepareStatement(CHAR_INS_PET_SPELL_COOLDOWN, "INSERT INTO pet_spell_cooldown (guid, spell, time) VALUES (?, ?, ?)", CONNECTION_BOTH); + PrepareStatement(CHAR_SEL_PET_SPELL_CHARGES, "SELECT categoryId, rechargeStart, rechargeEnd FROM pet_spell_charges WHERE guid = ? AND rechargeEnd > UNIX_TIMESTAMP() ORDER BY rechargeEnd", CONNECTION_SYNCH); + PrepareStatement(CHAR_DEL_PET_SPELL_CHARGES, "DELETE FROM pet_spell_charges WHERE guid = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_INS_PET_SPELL_CHARGES, "INSERT INTO pet_spell_charges (guid, categoryId, rechargeStart, rechargeEnd) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_PET_SPELL_BY_SPELL, "DELETE FROM pet_spell WHERE guid = ? and spell = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_INS_PET_SPELL, "INSERT INTO pet_spell (guid, spell, active) VALUES (?, ?, ?)", CONNECTION_BOTH); PrepareStatement(CHAR_INS_PET_AURA, "INSERT INTO pet_aura (guid, casterGuid, spell, effectMask, recalculateMask, stackCount, maxDuration, remainTime, remainCharges) " diff --git a/src/server/shared/Database/Implementation/CharacterDatabase.h b/src/server/shared/Database/Implementation/CharacterDatabase.h index 3c232c52a6d..045a4c1fde6 100644 --- a/src/server/shared/Database/Implementation/CharacterDatabase.h +++ b/src/server/shared/Database/Implementation/CharacterDatabase.h @@ -111,6 +111,7 @@ enum CharacterDatabaseStatements CHAR_SEL_CHARACTER_SOCIALLIST, CHAR_SEL_CHARACTER_HOMEBIND, CHAR_SEL_CHARACTER_SPELLCOOLDOWNS, + CHAR_SEL_CHARACTER_SPELL_CHARGES, CHAR_SEL_CHARACTER_DECLINEDNAMES, CHAR_SEL_GUILD_MEMBER, CHAR_SEL_GUILD_MEMBER_EXTENDED, @@ -441,7 +442,10 @@ enum CharacterDatabaseStatements CHAR_UPD_CHAR_REP_FACTION_CHANGE, CHAR_UPD_CHAR_TITLES_FACTION_CHANGE, CHAR_RES_CHAR_TITLES_FACTION_CHANGE, - CHAR_DEL_CHAR_SPELL_COOLDOWN, + CHAR_DEL_CHAR_SPELL_COOLDOWNS, + CHAR_INS_CHAR_SPELL_COOLDOWN, + CHAR_DEL_CHAR_SPELL_CHARGES, + CHAR_INS_CHAR_SPELL_CHARGES, CHAR_DEL_CHARACTER, CHAR_DEL_CHAR_ACTION, CHAR_DEL_CHAR_AURA, @@ -518,6 +522,9 @@ enum CharacterDatabaseStatements CHAR_DEL_PET_AURA_EFFECTS, CHAR_DEL_PET_SPELL_COOLDOWNS, CHAR_INS_PET_SPELL_COOLDOWN, + CHAR_SEL_PET_SPELL_CHARGES, + CHAR_DEL_PET_SPELL_CHARGES, + CHAR_INS_PET_SPELL_CHARGES, CHAR_DEL_PET_SPELL_BY_SPELL, CHAR_INS_PET_SPELL, CHAR_INS_PET_AURA, |