TrinityCore
Loading...
Searching...
No Matches
SpellHistory.cpp
Go to the documentation of this file.
1/*
2 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "SpellHistory.h"
19#include "CharmInfo.h"
20#include "DB2Stores.h"
21#include "DatabaseEnv.h"
22#include "Duration.h"
23#include "Item.h"
24#include "Map.h"
25#include "ObjectMgr.h"
26#include "Pet.h"
27#include "PetPackets.h"
28#include "Player.h"
29#include "Spell.h"
30#include "SpellAuraEffects.h"
31#include "SpellInfo.h"
32#include "SpellMgr.h"
33#include "SpellPackets.h"
34#include "World.h"
35#include <boost/container/small_vector.hpp>
36
37template<>
39{
40 static constexpr CharacterDatabaseStatements CooldownsDeleteStatement = CHAR_DEL_CHAR_SPELL_COOLDOWNS;
41 static constexpr CharacterDatabaseStatements CooldownsInsertStatement = CHAR_INS_CHAR_SPELL_COOLDOWN;
42 static constexpr CharacterDatabaseStatements ChargesDeleteStatement = CHAR_DEL_CHAR_SPELL_CHARGES;
43 static constexpr CharacterDatabaseStatements ChargesInsertStatement = CHAR_INS_CHAR_SPELL_CHARGES;
44
45 static void SetIdentifier(PreparedStatementBase* stmt, uint8 index, Unit const* owner) { stmt->setUInt64(index, owner->GetGUID().GetCounter()); }
46
47 static bool ReadCooldown(Field const* fields, uint32* spellId, CooldownEntry* cooldownEntry)
48 {
49 *spellId = fields[0].GetUInt32();
50 if (!sSpellMgr->GetSpellInfo(*spellId, DIFFICULTY_NONE))
51 return false;
52
53 cooldownEntry->SpellId = *spellId;
54 cooldownEntry->CooldownEnd = time_point_cast<Duration>(Clock::from_time_t(fields[2].GetInt64()));
55 cooldownEntry->ItemId = fields[1].GetUInt32();
56 cooldownEntry->CategoryId = fields[3].GetUInt32();
57 cooldownEntry->CategoryEnd = time_point_cast<Duration>(Clock::from_time_t(fields[4].GetInt64()));
58 return true;
59 }
60
61 static bool ReadCharge(Field const* fields, uint32* categoryId, ChargeEntry* chargeEntry)
62 {
63 *categoryId = fields[0].GetUInt32();
64 if (!sSpellCategoryStore.LookupEntry(*categoryId))
65 return false;
66
67 chargeEntry->RechargeStart = time_point_cast<Duration>(Clock::from_time_t(fields[1].GetInt64()));
68 chargeEntry->RechargeEnd = time_point_cast<Duration>(Clock::from_time_t(fields[2].GetInt64()));
69 return true;
70 }
71
72 static void WriteCooldown(PreparedStatementBase* stmt, uint8& index, CooldownEntry const& cooldown)
73 {
74 stmt->setUInt32(index++, cooldown.SpellId);
75 stmt->setUInt32(index++, cooldown.ItemId);
76 stmt->setInt64(index++, Clock::to_time_t(cooldown.CooldownEnd));
77 stmt->setUInt32(index++, cooldown.CategoryId);
78 stmt->setInt64(index++, Clock::to_time_t(cooldown.CategoryEnd));
79 }
80
81 static void WriteCharge(PreparedStatementBase* stmt, uint8& index, uint32 chargeCategory, ChargeEntry const& charge)
82 {
83 stmt->setUInt32(index++, chargeCategory);
84 stmt->setInt64(index++, Clock::to_time_t(charge.RechargeStart));
85 stmt->setInt64(index++, Clock::to_time_t(charge.RechargeEnd));
86 }
87};
88
89template<>
91{
92 static constexpr CharacterDatabaseStatements CooldownsDeleteStatement = CHAR_DEL_PET_SPELL_COOLDOWNS;
93 static constexpr CharacterDatabaseStatements CooldownsInsertStatement = CHAR_INS_PET_SPELL_COOLDOWN;
94 static constexpr CharacterDatabaseStatements ChargesDeleteStatement = CHAR_DEL_PET_SPELL_CHARGES;
95 static constexpr CharacterDatabaseStatements ChargesInsertStatement = CHAR_INS_PET_SPELL_CHARGES;
96
97 static void SetIdentifier(PreparedStatementBase* stmt, uint8 index, Unit* owner) { stmt->setUInt32(index, owner->GetCharmInfo()->GetPetNumber()); }
98
99 static bool ReadCooldown(Field const* fields, uint32* spellId, CooldownEntry* cooldownEntry)
100 {
101 *spellId = fields[0].GetUInt32();
102 if (!sSpellMgr->GetSpellInfo(*spellId, DIFFICULTY_NONE))
103 return false;
104
105 cooldownEntry->SpellId = *spellId;
106 cooldownEntry->CooldownEnd = time_point_cast<Duration>(Clock::from_time_t(fields[1].GetInt64()));
107 cooldownEntry->ItemId = 0;
108 cooldownEntry->CategoryId = fields[2].GetUInt32();
109 cooldownEntry->CategoryEnd = time_point_cast<Duration>(Clock::from_time_t(fields[3].GetInt64()));
110 return true;
111 }
112
113 static bool ReadCharge(Field const* fields, uint32* categoryId, ChargeEntry* chargeEntry)
114 {
115 *categoryId = fields[0].GetUInt32();
116 if (!sSpellCategoryStore.LookupEntry(*categoryId))
117 return false;
118
119 chargeEntry->RechargeStart = time_point_cast<Duration>(Clock::from_time_t(fields[1].GetInt64()));
120 chargeEntry->RechargeEnd = time_point_cast<Duration>(Clock::from_time_t(fields[2].GetInt64()));
121 return true;
122 }
123
124 static void WriteCooldown(PreparedStatementBase* stmt, uint8& index, CooldownEntry const& cooldown)
125 {
126 stmt->setUInt32(index++, cooldown.SpellId);
127 stmt->setInt64(index++, Clock::to_time_t(cooldown.CooldownEnd));
128 stmt->setUInt32(index++, cooldown.CategoryId);
129 stmt->setInt64(index++, Clock::to_time_t(cooldown.CategoryEnd));
130 }
131
132 static void WriteCharge(PreparedStatementBase* stmt, uint8& index, uint32 chargeCategory, ChargeEntry const& charge)
133 {
134 stmt->setUInt32(index++, chargeCategory);
135 stmt->setInt64(index++, Clock::to_time_t(charge.RechargeStart));
136 stmt->setInt64(index++, Clock::to_time_t(charge.RechargeEnd));
137 }
138};
139
140SpellHistory::SpellHistory(Unit* owner) : _owner(owner), _schoolLockouts()
141{
142}
143
145
146template<class OwnerType>
148{
149 using StatementInfo = PersistenceHelper<OwnerType>;
150
151 if (cooldownsResult)
152 {
153 do
154 {
155 uint32 spellId;
156 CooldownEntry cooldown;
157 if (StatementInfo::ReadCooldown(cooldownsResult->Fetch(), &spellId, &cooldown))
158 {
159 _spellCooldowns[spellId] = cooldown;
160 if (cooldown.CategoryId)
161 _categoryCooldowns[cooldown.CategoryId] = &_spellCooldowns[spellId];
162 }
163
164 } while (cooldownsResult->NextRow());
165 }
166
167 if (chargesResult)
168 {
169 do
170 {
171 Field* fields = chargesResult->Fetch();
172 uint32 categoryId = 0;
173 ChargeEntry charges;
174 if (StatementInfo::ReadCharge(fields, &categoryId, &charges))
175 _categoryCharges[categoryId].push_back(charges);
176
177 } while (chargesResult->NextRow());
178 }
179}
180
181template<class OwnerType>
183{
184 using StatementInfo = PersistenceHelper<OwnerType>;
185
186 uint8 index = 0;
187 CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(StatementInfo::CooldownsDeleteStatement);
188 StatementInfo::SetIdentifier(stmt, index++, _owner);
189 trans->Append(stmt);
190
191 for (auto const& [spellId, cooldown] : _spellCooldowns)
192 {
193 if (!cooldown.OnHold)
194 {
195 index = 0;
196 stmt = CharacterDatabase.GetPreparedStatement(StatementInfo::CooldownsInsertStatement);
197 StatementInfo::SetIdentifier(stmt, index++, _owner);
198 StatementInfo::WriteCooldown(stmt, index, cooldown);
199 trans->Append(stmt);
200 }
201 }
202
203 stmt = CharacterDatabase.GetPreparedStatement(StatementInfo::ChargesDeleteStatement);
204 StatementInfo::SetIdentifier(stmt, 0, _owner);
205 trans->Append(stmt);
206
207 for (auto const& [categoryId, consumedCharges] : _categoryCharges)
208 {
209 for (ChargeEntry const& charge : consumedCharges)
210 {
211 index = 0;
212 stmt = CharacterDatabase.GetPreparedStatement(StatementInfo::ChargesInsertStatement);
213 StatementInfo::SetIdentifier(stmt, index++, _owner);
214 StatementInfo::WriteCharge(stmt, index, categoryId, charge);
215 trans->Append(stmt);
216 }
217 }
218}
219
221{
222 TimePoint now = time_point_cast<Duration>(GameTime::GetTime<Clock>());
223 for (auto itr = _categoryCooldowns.begin(); itr != _categoryCooldowns.end();)
224 {
225 if (itr->second->CategoryEnd < now && !itr->second->OnHold)
226 itr = _categoryCooldowns.erase(itr);
227 else
228 ++itr;
229 }
230
231 for (auto itr = _spellCooldowns.begin(); itr != _spellCooldowns.end();)
232 {
233 if (itr->second.CooldownEnd < now && !itr->second.OnHold)
234 itr = EraseCooldown(itr);
235 else
236 ++itr;
237 }
238
239 for (auto& [chargeCategoryId, chargeRefreshTimes] : _categoryCharges)
240 while (!chargeRefreshTimes.empty() && chargeRefreshTimes.front().RechargeEnd <= now)
241 chargeRefreshTimes.pop_front();
242}
243
244void SpellHistory::HandleCooldowns(SpellInfo const* spellInfo, uint32 itemId, Spell* spell /*= nullptr*/)
245{
246 if (spell && spell->IsIgnoringCooldowns())
247 return;
248
250
252 return;
253
254 if (spellInfo->IsPassive())
255 return;
256
257 StartCooldown(spellInfo, itemId, spell, spellInfo->IsCooldownStartedOnEvent());
258
260 SendCooldownEvent(spellInfo, itemId, spell, true);
261}
262
263bool SpellHistory::IsReady(SpellInfo const* spellInfo, uint32 itemId /*= 0*/) const
264{
266 if (IsSchoolLocked(spellInfo->GetSchoolMask()))
267 return false;
268
269 if (HasCooldown(spellInfo, itemId))
270 return false;
271
272 if (!HasCharge(spellInfo->ChargeCategoryId))
273 return false;
274
275 return true;
276}
277
279{
280 sendSpellHistory->Entries.reserve(_spellCooldowns.size());
281
282 TimePoint now = time_point_cast<Duration>(GameTime::GetTime<Clock>());
283 for (auto const& [spellId, cooldown] : _spellCooldowns)
284 {
286 historyEntry.SpellID = spellId;
287 historyEntry.ItemID = cooldown.ItemId;
288
289 if (cooldown.OnHold)
290 historyEntry.OnHold = true;
291 else
292 {
293 Milliseconds cooldownDuration = duration_cast<Milliseconds>(cooldown.CooldownEnd - now);
294 if (cooldownDuration.count() <= 0)
295 continue;
296
297 Milliseconds categoryDuration = duration_cast<Milliseconds>(cooldown.CategoryEnd - now);
298 if (categoryDuration.count() > 0)
299 {
300 historyEntry.Category = cooldown.CategoryId;
301 historyEntry.CategoryRecoveryTime = uint32(categoryDuration.count());
302 }
303
304 if (cooldownDuration.count() > categoryDuration.count())
305 historyEntry.RecoveryTime = uint32(cooldownDuration.count());
306 }
307
308 sendSpellHistory->Entries.push_back(historyEntry);
309 }
310}
311
313{
314 sendSpellCharges->Entries.reserve(_categoryCharges.size());
315
316 TimePoint now = time_point_cast<Duration>(GameTime::GetTime<Clock>());
317 for (auto const& [categoryId, consumedCharges] : _categoryCharges)
318 {
319 if (!consumedCharges.empty())
320 {
321 Milliseconds cooldownDuration = duration_cast<Milliseconds>(consumedCharges.front().RechargeEnd - now);
322 if (cooldownDuration.count() <= 0)
323 continue;
324
326 chargeEntry.Category = categoryId;
327 chargeEntry.NextRecoveryTime = uint32(cooldownDuration.count());
328 chargeEntry.ConsumedCharges = uint8(consumedCharges.size());
329 sendSpellCharges->Entries.push_back(chargeEntry);
330 }
331 }
332}
333
335{
336 TimePoint now = time_point_cast<Duration>(GameTime::GetTime<Clock>());
337
338 petSpells->Cooldowns.reserve(_spellCooldowns.size());
339 for (auto const& [spellId, cooldown] : _spellCooldowns)
340 {
342 petSpellCooldown.SpellID = spellId;
343 petSpellCooldown.Category = cooldown.CategoryId;
344
345 if (!cooldown.OnHold)
346 {
347 Milliseconds cooldownDuration = duration_cast<Milliseconds>(cooldown.CooldownEnd - now);
348 if (cooldownDuration.count() <= 0)
349 continue;
350
351 petSpellCooldown.Duration = uint32(cooldownDuration.count());
352 Milliseconds categoryDuration = duration_cast<Milliseconds>(cooldown.CategoryEnd - now);
353 if (categoryDuration.count() > 0)
354 petSpellCooldown.CategoryDuration = uint32(categoryDuration.count());
355 }
356 else
357 petSpellCooldown.CategoryDuration = std::numeric_limits<int32>::min();
358
359 petSpells->Cooldowns.push_back(petSpellCooldown);
360 }
361
362 petSpells->SpellHistory.reserve(_categoryCharges.size());
363 for (auto const& [categoryId, consumedCharges] : _categoryCharges)
364 {
365 if (!consumedCharges.empty())
366 {
367 Milliseconds cooldownDuration = duration_cast<Milliseconds>(consumedCharges.front().RechargeEnd - now);
368 if (cooldownDuration.count() <= 0)
369 continue;
370
372 petChargeEntry.CategoryID = categoryId;
373 petChargeEntry.RecoveryTime = uint32(cooldownDuration.count());
374 petChargeEntry.ConsumedCharges = int8(consumedCharges.size());
375
376 petSpells->SpellHistory.push_back(petChargeEntry);
377 }
378 }
379}
380
381void SpellHistory::StartCooldown(SpellInfo const* spellInfo, uint32 itemId, Spell* spell /*= nullptr*/, bool onHold /*= false*/, Optional<Duration> forcedCooldown /*= {}*/)
382{
383 // init cooldown values
384 uint32 categoryId = 0;
385 Duration cooldown = Duration::zero();
386 Duration categoryCooldown = Duration::zero();
387
388 bool needsCooldownPacket = false;
389
390 if (!forcedCooldown)
391 GetCooldownDurations(spellInfo, itemId, &cooldown, &categoryId, &categoryCooldown);
392 else
393 cooldown = *forcedCooldown;
394
395 // overwrite time for selected category
396 if (!onHold)
397 {
398 if (!forcedCooldown)
399 {
400 Duration baseCooldown = cooldown;
401
402 // Now we have cooldown data (if found any), time to apply mods
403 if (Player* modOwner = _owner->GetSpellModOwner())
404 {
405 auto applySpellMod = [&](Milliseconds& value)
406 {
407 int32 intValue = value.count();
408 modOwner->ApplySpellMod(spellInfo, SpellModOp::Cooldown, intValue, spell);
409 value = Milliseconds(intValue);
410 };
411
412 if (cooldown >= Duration::zero())
413 applySpellMod(cooldown);
414
415 if (categoryCooldown >= Clock::duration::zero() && !spellInfo->HasAttribute(SPELL_ATTR6_NO_CATEGORY_COOLDOWN_MODS))
416 applySpellMod(categoryCooldown);
417 }
418
420 {
421 cooldown = Duration(int64(cooldown.count() * _owner->m_unitData->ModSpellHaste));
422 categoryCooldown = Duration(int64(categoryCooldown.count() * _owner->m_unitData->ModSpellHaste));
423 }
424
426 {
427 cooldown = Duration(int64(cooldown.count() * _owner->m_unitData->ModHasteRegen));
428 categoryCooldown = Duration(int64(categoryCooldown.count() * _owner->m_unitData->ModHasteRegen));
429 }
430
431 {
432 auto calcRecoveryRate = [&](AuraEffect const* modRecoveryRate)
433 {
434 double rate = 100.0 / (std::max(modRecoveryRate->GetAmount(), -99.0) + 100.0);
435 if (baseCooldown <= 1h
437 && !modRecoveryRate->GetSpellEffectInfo().EffectAttributes.HasFlag(SpellEffectAttributes::IgnoreDuringCooldownTimeRateCalculation))
438 rate *= *_owner->m_unitData->ModTimeRate;
439
440 return rate;
441 };
442
443 double recoveryRate = 1.0;
445 if (modRecoveryRate->IsAffectingSpell(spellInfo))
446 recoveryRate *= calcRecoveryRate(modRecoveryRate);
447
449 if (spellInfo->HasLabel(modRecoveryRate->GetMiscValue()) || (modRecoveryRate->GetMiscValueB() && spellInfo->HasLabel(modRecoveryRate->GetMiscValueB())))
450 recoveryRate *= calcRecoveryRate(modRecoveryRate);
451
452 if (recoveryRate > 0.0)
453 {
454 cooldown = Duration(int64(cooldown.count() * recoveryRate));
455 categoryCooldown = Duration(int64(categoryCooldown.count() * recoveryRate));
456 }
457 }
458
460 {
461 // Apply SPELL_AURA_MOD_COOLDOWN only to own spells
462 Player* playerOwner = GetPlayerOwner();
463 if (!playerOwner || playerOwner->HasSpell(spellInfo->Id))
464 {
465 needsCooldownPacket = true;
466 cooldown += Milliseconds(cooldownMod); // SPELL_AURA_MOD_COOLDOWN does not affect category cooldows, verified with shaman shocks
467 }
468 }
469
470 // Apply SPELL_AURA_MOD_SPELL_CATEGORY_COOLDOWN modifiers
471 // Note: This aura applies its modifiers to all cooldowns of spells with set category, not to category cooldown only
472 if (categoryId)
473 {
475 {
476 if (cooldown > Duration::zero())
477 cooldown += Milliseconds(categoryModifier);
478
479 if (categoryCooldown > Duration::zero())
480 categoryCooldown += Milliseconds(categoryModifier);
481 }
482
483 SpellCategoryEntry const* categoryEntry = sSpellCategoryStore.AssertEntry(categoryId);
484 if (categoryEntry->GetFlags().HasFlag(SpellCategoryFlags::CooldownInDays))
485 categoryCooldown = duration_cast<Milliseconds>(Clock::from_time_t(sWorld->GetNextDailyQuestsResetTime()) - GameTime::GetTime<Clock>());
486 }
487 }
488 else
489 needsCooldownPacket = true;
490
491 // replace negative cooldowns by 0
492 if (cooldown < Duration::zero())
493 cooldown = Duration::zero();
494
495 if (categoryCooldown < Duration::zero())
496 categoryCooldown = Duration::zero();
497
498 // no cooldown after applying spell mods
499 if (cooldown == Duration::zero() && categoryCooldown == Duration::zero())
500 return;
501 }
502
503 TimePoint curTime = time_point_cast<Duration>(GameTime::GetTime<Clock>());
504 TimePoint catrecTime = categoryCooldown != Duration::zero() ? curTime + categoryCooldown : curTime;
505 TimePoint recTime = cooldown != Duration::zero() ? curTime + cooldown : catrecTime;
506
507 // self spell cooldown
508 if (recTime != curTime)
509 {
510 AddCooldown(spellInfo->Id, itemId, recTime, categoryId, catrecTime, onHold);
511
512 if (needsCooldownPacket)
513 {
514 if (Player* playerOwner = GetPlayerOwner())
515 {
517 spellCooldown.Caster = _owner->GetGUID();
518 spellCooldown.Flags = SPELL_COOLDOWN_FLAG_NONE;
519 spellCooldown.SpellCooldowns.emplace_back(spellInfo->Id, uint32(cooldown.count()));
520 playerOwner->SendDirectMessage(spellCooldown.Write());
521 }
522 }
523 }
524}
525
526void SpellHistory::SendCooldownEvent(SpellInfo const* spellInfo, uint32 itemId /*= 0*/, Spell* spell /*= nullptr*/, bool startCooldown /*= true*/)
527{
528 // Send activate cooldown timer (possible 0) at client side
529 if (Player* player = GetPlayerOwner())
530 {
531 uint32 category = spellInfo->GetCategory();
532 GetCooldownDurations(spellInfo, itemId, nullptr, &category, nullptr);
533
534 auto categoryItr = _categoryCooldowns.find(category);
535 if (categoryItr != _categoryCooldowns.end() && categoryItr->second->SpellId != spellInfo->Id)
536 {
537 player->SendDirectMessage(WorldPackets::Spells::CooldownEvent(player != _owner, categoryItr->second->SpellId).Write());
538
539 if (startCooldown)
540 StartCooldown(sSpellMgr->AssertSpellInfo(categoryItr->second->SpellId, _owner->GetMap()->GetDifficultyID()), itemId, spell);
541 }
542
543 player->SendDirectMessage(WorldPackets::Spells::CooldownEvent(player != _owner, spellInfo->Id).Write());
544 }
545
546 // start cooldowns at server side, if any
547 if (startCooldown)
548 StartCooldown(spellInfo, itemId, spell);
549}
550
551void SpellHistory::AddCooldown(uint32 spellId, uint32 itemId, TimePoint cooldownEnd, uint32 categoryId, TimePoint categoryEnd, bool onHold /*= false*/)
552{
553 CooldownEntry& cooldownEntry = _spellCooldowns[spellId];
554 // scripts can start multiple cooldowns for a given spell, only store the longest one
555 if (cooldownEnd > cooldownEntry.CooldownEnd || categoryEnd > cooldownEntry.CategoryEnd || onHold)
556 {
557 cooldownEntry.SpellId = spellId;
558 cooldownEntry.CooldownEnd = cooldownEnd;
559 cooldownEntry.ItemId = itemId;
560 cooldownEntry.CategoryId = categoryId;
561 cooldownEntry.CategoryEnd = categoryEnd;
562 cooldownEntry.OnHold = onHold;
563
564 if (categoryId)
565 _categoryCooldowns[categoryId] = &cooldownEntry;
566 }
567}
568
569void SpellHistory::ModifySpellCooldown(uint32 spellId, Duration cooldownMod, bool withoutCategoryCooldown)
570{
571 auto itr = _spellCooldowns.find(spellId);
572 if (itr == _spellCooldowns.end() || itr->second.OnHold)
573 return;
574
575 ModifySpellCooldown(itr, cooldownMod, withoutCategoryCooldown);
576}
577
578void SpellHistory::ModifySpellCooldown(CooldownStorageType::iterator& itr, Duration cooldownMod, bool withoutCategoryCooldown)
579{
580 TimePoint now = time_point_cast<Duration>(GameTime::GetTime<Clock>());
581
582 itr->second.CooldownEnd += cooldownMod;
583
584 if (itr->second.CategoryId)
585 {
586 if (!withoutCategoryCooldown)
587 itr->second.CategoryEnd += cooldownMod;
588
589 // Because category cooldown existence is tied to regular cooldown, we cannot allow a situation where regular cooldown is shorter than category
590 if (itr->second.CooldownEnd < itr->second.CategoryEnd)
591 itr->second.CooldownEnd = itr->second.CategoryEnd;
592 }
593
594 if (Player* playerOwner = GetPlayerOwner())
595 {
597 modifyCooldown.IsPet = _owner != playerOwner;
598 modifyCooldown.SpellID = itr->second.SpellId;
599 modifyCooldown.DeltaTime = duration_cast<Milliseconds>(cooldownMod).count();
600 modifyCooldown.SkipCategory = withoutCategoryCooldown;
601 playerOwner->SendDirectMessage(modifyCooldown.Write());
602 }
603
604 if (itr->second.CooldownEnd <= now)
605 itr = EraseCooldown(itr);
606}
607
608void SpellHistory::UpdateCooldownRecoveryRate(CooldownStorageType::iterator& itr, float modChange, bool apply)
609{
610 if (modChange <= 0.0f)
611 return;
612
613 if (!apply)
614 modChange = 1.0f / modChange;
615
616 TimePoint now = time_point_cast<Duration>(GameTime::GetTime<Clock>());
617
618 itr->second.CooldownEnd = now + duration_cast<Duration>((itr->second.CooldownEnd - now) * modChange);
619
620 if (itr->second.CategoryId)
621 itr->second.CategoryEnd = now + duration_cast<Duration>((itr->second.CategoryEnd - now) * modChange);
622
623 if (Player* playerOwner = GetPlayerOwner())
624 {
626 updateCooldown.SpellID = itr->second.SpellId;
627 updateCooldown.ModChange = modChange;
628 playerOwner->SendDirectMessage(updateCooldown.Write());
629 }
630}
631
632void SpellHistory::ModifyCooldown(uint32 spellId, Duration cooldownMod, bool withoutCategoryCooldown)
633{
634 if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId, _owner->GetMap()->GetDifficultyID()))
635 ModifyCooldown(spellInfo, cooldownMod, withoutCategoryCooldown);
636}
637
638void SpellHistory::ModifyCooldown(SpellInfo const* spellInfo, Duration cooldownMod, bool withoutCategoryCooldown)
639{
640 if (!cooldownMod.count())
641 return;
642
643 ModifyChargeRecoveryTime(spellInfo->ChargeCategoryId, cooldownMod);
644 ModifySpellCooldown(spellInfo->Id, cooldownMod, withoutCategoryCooldown);
645}
646
647void SpellHistory::ResetCooldown(uint32 spellId, bool update /*= false*/)
648{
649 auto itr = _spellCooldowns.find(spellId);
650 if (itr == _spellCooldowns.end() || itr->second.OnHold)
651 return;
652
653 ResetCooldown(itr, update);
654}
655
656void SpellHistory::ResetCooldown(CooldownStorageType::iterator& itr, bool update /*= false*/)
657{
658 if (update)
659 {
660 if (Player* playerOwner = GetPlayerOwner())
661 {
663 clearCooldown.IsPet = _owner != playerOwner;
664 clearCooldown.SpellID = itr->first;
665 clearCooldown.ClearOnHold = false;
666 playerOwner->SendDirectMessage(clearCooldown.Write());
667 }
668 }
669
670 itr = EraseCooldown(itr);
671}
672
674{
675 ResetCooldowns([](CooldownEntry const&) { return true; }, true);
676}
677
678bool SpellHistory::HasCooldown(SpellInfo const* spellInfo, uint32 itemId /*= 0*/) const
679{
681 return false;
682
683 if (_spellCooldowns.contains(spellInfo->Id))
684 return true;
685
686 if (spellInfo->CooldownAuraSpellId && _owner->HasAura(spellInfo->CooldownAuraSpellId))
687 return true;
688
689 uint32 category = 0;
690 GetCooldownDurations(spellInfo, itemId, nullptr, &category, nullptr);
691 if (!category)
692 return false;
693
694 return _categoryCooldowns.contains(category);
695}
696
697bool SpellHistory::HasCooldown(uint32 spellId, uint32 itemId /*= 0*/) const
698{
699 return HasCooldown(sSpellMgr->AssertSpellInfo(spellId, _owner->GetMap()->GetDifficultyID()), itemId);
700}
701
703{
704 // TODO: Delete this function and make SpellHistory::IsReady return enum with reason instead of bool
705 auto itr = _spellCooldowns.find(spellId);
706 return itr != _spellCooldowns.end() && itr->second.OnHold;
707}
708
710{
711 TimePoint end;
712 auto itr = _spellCooldowns.find(spellInfo->Id);
713 if (itr != _spellCooldowns.end())
714 {
715 if (itr->second.OnHold)
716 return Duration::max();
717
718 end = itr->second.CooldownEnd;
719 }
720 else
721 {
722 auto catItr = _categoryCooldowns.find(spellInfo->GetCategory());
723 if (catItr == _categoryCooldowns.end())
724 return Duration::zero();
725
726 if (catItr->second->OnHold)
727 return Duration::max();
728
729 end = catItr->second->CategoryEnd;
730 }
731
732 TimePoint now = time_point_cast<Duration>(GameTime::GetTime<Clock>());
733 if (end < now)
734 return Duration::zero();
735
736 Clock::duration remaining = end - now;
737 return duration_cast<Milliseconds>(remaining);
738}
739
741{
742 auto catItr = _categoryCooldowns.find(categoryId);
743 if (catItr == _categoryCooldowns.end())
744 return Duration::zero();
745
746 if (catItr->second->OnHold)
747 return Duration::max();
748
749 TimePoint end = catItr->second->CategoryEnd;
750
751 TimePoint now = time_point_cast<Duration>(GameTime::GetTime<Clock>());
752 if (end < now)
753 return Duration::zero();
754
755 Clock::duration remaining = end - now;
756 return duration_cast<Milliseconds>(remaining);
757}
758
763
765{
766 TimePoint now = time_point_cast<Duration>(GameTime::GetTime<Clock>());
767 TimePoint lockoutEnd = now + lockoutTime;
768 for (uint32 i = 0; i < MAX_SPELL_SCHOOL; ++i)
769 if (SpellSchoolMask(1 << i) & schoolMask)
770 _schoolLockouts[i] = lockoutEnd;
771
772 std::set<uint32> knownSpells;
773 if (Player* plrOwner = _owner->ToPlayer())
774 {
775 for (auto const& [spellId, playerSpell] : plrOwner->GetSpellMap())
776 if (playerSpell.state != PLAYERSPELL_REMOVED)
777 knownSpells.insert(spellId);
778 }
779 else if (Pet* petOwner = _owner->ToPet())
780 {
781 for (auto const& [spellId, petSpell] : petOwner->m_spells)
782 if (petSpell.state != PETSPELL_REMOVED)
783 knownSpells.insert(spellId);
784 }
785 else
786 {
787 Creature* creatureOwner = _owner->ToCreature();
788 for (uint32 spell : creatureOwner->m_spells)
789 if (spell)
790 knownSpells.insert(spell);
791 }
792
794 spellCooldown.Caster = _owner->GetGUID();
796 for (uint32 spellId : knownSpells)
797 {
798 SpellInfo const* spellInfo = sSpellMgr->AssertSpellInfo(spellId, _owner->GetMap()->GetDifficultyID());
799 if (spellInfo->IsCooldownStartedOnEvent())
800 continue;
801
803 continue;
804
806 continue;
807
808 if (!(schoolMask & spellInfo->GetSchoolMask()))
809 continue;
810
811 if (GetRemainingCooldown(spellInfo) < lockoutTime)
812 AddCooldown(spellId, 0, lockoutEnd, 0, now);
813
814 // always send cooldown, even if it will be shorter than already existing cooldown for LossOfControl UI
815 spellCooldown.SpellCooldowns.emplace_back(spellId, lockoutTime.count());
816 }
817
818 if (Player* player = GetPlayerOwner())
819 if (!spellCooldown.SpellCooldowns.empty())
820 player->SendDirectMessage(spellCooldown.Write());
821}
822
824{
825 TimePoint now = time_point_cast<Duration>(GameTime::GetTime<Clock>());
826 for (uint32 i = 0; i < MAX_SPELL_SCHOOL; ++i)
827 if (SpellSchoolMask(1 << i) & schoolMask)
828 if (_schoolLockouts[i] > now)
829 return true;
830
831 return false;
832}
833
835{
836 if (!sSpellCategoryStore.LookupEntry(chargeCategoryId))
837 return;
838
839 int32 chargeRecovery = GetChargeRecoveryTime(chargeCategoryId);
840 if (chargeRecovery <= 0 || GetMaxCharges(chargeCategoryId) <= 0)
841 return;
842
844 return;
845
846 TimePoint recoveryStart;
847 std::deque<ChargeEntry>& charges = _categoryCharges[chargeCategoryId];
848 if (charges.empty())
849 recoveryStart = time_point_cast<Duration>(GameTime::GetTime<Clock>());
850 else
851 recoveryStart = charges.back().RechargeEnd;
852
853 charges.emplace_back(recoveryStart, Milliseconds(chargeRecovery));
854}
855
856void SpellHistory::ModifyChargeRecoveryTime(uint32 chargeCategoryId, Duration cooldownMod)
857{
858 SpellCategoryEntry const* chargeCategoryEntry = sSpellCategoryStore.LookupEntry(chargeCategoryId);
859 if (!chargeCategoryEntry)
860 return;
861
862 auto itr = _categoryCharges.find(chargeCategoryId);
863 if (itr == _categoryCharges.end() || itr->second.empty())
864 return;
865
866 TimePoint now = time_point_cast<Duration>(GameTime::GetTime<Clock>());
867
868 for (ChargeEntry& entry : itr->second)
869 {
870 entry.RechargeStart += cooldownMod;
871 entry.RechargeEnd += cooldownMod;
872 }
873
874 while (!itr->second.empty() && itr->second.front().RechargeEnd < now)
875 itr->second.pop_front();
876
877 SendSetSpellCharges(chargeCategoryId, itr->second);
878}
879
880void SpellHistory::UpdateChargeRecoveryRate(uint32 chargeCategoryId, float modChange, bool apply)
881{
882 auto itr = _categoryCharges.find(chargeCategoryId);
883 if (itr == _categoryCharges.end() || itr->second.empty())
884 return;
885
886 if (modChange <= 0.0f)
887 return;
888
889 if (!apply)
890 modChange = 1.0f / modChange;
891
892 TimePoint now = time_point_cast<Duration>(GameTime::GetTime<Clock>());
893
894 auto chargeItr = itr->second.begin();
895
896 chargeItr->RechargeEnd = now + duration_cast<Duration>((chargeItr->RechargeEnd - now) * modChange);
897
898 TimePoint prevEnd = chargeItr->RechargeEnd;
899
900 while (++chargeItr != itr->second.end())
901 {
902 Duration rechargeTime = duration_cast<Duration>((chargeItr->RechargeEnd - chargeItr->RechargeStart) * modChange);
903 chargeItr->RechargeStart = prevEnd;
904 chargeItr->RechargeEnd = prevEnd + rechargeTime;
905 prevEnd = chargeItr->RechargeEnd;
906 }
907
908 if (Player* playerOwner = GetPlayerOwner())
909 {
910 WorldPackets::Spells::UpdateChargeCategoryCooldown updateChargeCategoryCooldown;
911 updateChargeCategoryCooldown.Category = chargeCategoryId;
912 updateChargeCategoryCooldown.ModChange = modChange;
913 playerOwner->SendDirectMessage(updateChargeCategoryCooldown.Write());
914 }
915}
916
918{
919 auto itr = _categoryCharges.find(chargeCategoryId);
920 if (itr != _categoryCharges.end() && !itr->second.empty())
921 {
922 itr->second.pop_back();
923
924 SendSetSpellCharges(chargeCategoryId, itr->second);
925 }
926}
927
928void SpellHistory::ResetCharges(uint32 chargeCategoryId)
929{
930 auto itr = _categoryCharges.find(chargeCategoryId);
931 if (itr != _categoryCharges.end())
932 {
933 _categoryCharges.erase(itr);
934
935 if (Player* player = GetPlayerOwner())
936 {
938 clearSpellCharges.IsPet = _owner != player;
939 clearSpellCharges.Category = chargeCategoryId;
940 player->SendDirectMessage(clearSpellCharges.Write());
941 }
942 }
943}
944
946{
947 _categoryCharges.clear();
948
949 if (Player* player = GetPlayerOwner())
950 {
952 clearAllSpellCharges.IsPet = _owner != player;
953 player->SendDirectMessage(clearAllSpellCharges.Write());
954 }
955}
956
957bool SpellHistory::HasCharge(uint32 chargeCategoryId) const
958{
959 if (!sSpellCategoryStore.LookupEntry(chargeCategoryId))
960 return true;
961
962 // Check if the spell is currently using charges (untalented warlock Dark Soul)
963 int32 maxCharges = GetMaxCharges(chargeCategoryId);
964 if (maxCharges <= 0)
965 return true;
966
967 auto itr = _categoryCharges.find(chargeCategoryId);
968 return itr == _categoryCharges.end() || int32(itr->second.size()) < maxCharges;
969}
970
972{
973 SpellCategoryEntry const* chargeCategoryEntry = sSpellCategoryStore.LookupEntry(chargeCategoryId);
974 if (!chargeCategoryEntry)
975 return 0;
976
977 uint32 charges = chargeCategoryEntry->MaxCharges;
979 return charges;
980}
981
983{
984 SpellCategoryEntry const* chargeCategoryEntry = sSpellCategoryStore.LookupEntry(chargeCategoryId);
985 if (!chargeCategoryEntry)
986 return 0;
987
988 SpellEffectValue recoveryTime = chargeCategoryEntry->ChargeRecoveryTime;
990
992 if (modRecoveryRate->GetMiscValue() & chargeCategoryEntry->TypeMask)
993 recoveryTime += modRecoveryRate->GetAmount();
994
996
998 recoveryTime *= _owner->m_unitData->ModSpellHaste;
999
1001 recoveryTime *= _owner->m_unitData->ModHasteRegen;
1002
1004 if (modRecoveryRate->GetMiscValue() == int32(chargeCategoryId))
1005 recoveryTime *= 100.0 / (std::max(modRecoveryRate->GetAmount(), -99.0) + 100.0);
1006
1008 if (modRecoveryRate->GetMiscValue() & chargeCategoryEntry->TypeMask)
1009 recoveryTime *= 100.0 / (std::max(modRecoveryRate->GetAmount(), -99.0) + 100.0);
1010
1011 if (Milliseconds(chargeCategoryEntry->ChargeRecoveryTime) <= 1h
1012 && !(chargeCategoryEntry->GetFlags().HasFlag(SpellCategoryFlags::IgnoreForModTimeRate))
1013 && !(chargeCategoryEntry->GetFlags().HasFlag(SpellCategoryFlags::CooldownInDays)))
1014 recoveryTime *= *_owner->m_unitData->ModTimeRate;
1015
1016 return int32(std::floor(recoveryTime));
1017}
1018
1019bool SpellHistory::HasGlobalCooldown(SpellInfo const* spellInfo) const
1020{
1021 auto itr = _globalCooldowns.find(spellInfo->StartRecoveryCategory);
1022 return itr != _globalCooldowns.end() && itr->second > Clock::now();
1023}
1024
1026{
1027 _globalCooldowns[spellInfo->StartRecoveryCategory] = time_point_cast<Duration>(Clock::now() + duration);
1028}
1029
1034
1036{
1037 auto cdItr = _globalCooldowns.find(spellInfo->StartRecoveryCategory);
1038 if (cdItr == _globalCooldowns.end())
1039 return Duration::zero();
1040
1041 TimePoint end = cdItr->second;
1042 TimePoint now = time_point_cast<Duration>(GameTime::GetTime<Clock>());
1043 if (end < now)
1044 return Duration::zero();
1045
1046 Clock::duration remaining = end - now;
1047 return duration_cast<Milliseconds>(remaining);
1048}
1049
1051{
1052 _pauseTime = time_point_cast<Duration>(GameTime::GetTime<Clock>());
1053}
1054
1056{
1057 if (!_pauseTime)
1058 return;
1059
1060 Duration pausedDuration = time_point_cast<Duration>(GameTime::GetTime<Clock>()) - *_pauseTime;
1061
1062 for (auto itr = _spellCooldowns.begin(); itr != _spellCooldowns.end();)
1063 if (!itr->second.OnHold)
1064 itr->second.CooldownEnd += pausedDuration;
1065
1066 for (auto& [chargeCategoryId, chargeRefreshTimes] : _categoryCharges)
1067 for (ChargeEntry& chargeEntry : chargeRefreshTimes)
1068 chargeEntry.RechargeEnd += pausedDuration;
1069
1070 _pauseTime.reset();
1071
1072 Update();
1073}
1074
1079
1080void SpellHistory::SendClearCooldowns(std::vector<int32>&& cooldowns) const
1081{
1082 if (Player const* playerOwner = GetPlayerOwner())
1083 {
1085 clearCooldowns.IsPet = _owner != playerOwner;
1086 clearCooldowns.SpellID = std::move(cooldowns);
1087 playerOwner->SendDirectMessage(clearCooldowns.Write());
1088 }
1089}
1090
1091SpellHistory::CooldownStorageType::iterator SpellHistory::EraseCooldown(CooldownStorageType::iterator itr)
1092{
1093 _categoryCooldowns.erase(itr->second.CategoryId);
1094 return _spellCooldowns.erase(itr);
1095}
1096
1097void SpellHistory::SendSetSpellCharges(uint32 chargeCategoryId, ChargeEntryCollection const& chargeCollection) const
1098{
1099 if (Player* player = GetPlayerOwner())
1100 {
1102 setSpellCharges.Category = chargeCategoryId;
1103 if (!chargeCollection.empty())
1104 setSpellCharges.NextRecoveryTime = uint32(duration_cast<Milliseconds>(chargeCollection.front().RechargeEnd - GameTime::GetTime<Clock>()).count());
1105 setSpellCharges.ConsumedCharges = uint8(chargeCollection.size());
1106 setSpellCharges.IsPet = player != _owner;
1107 player->SendDirectMessage(setSpellCharges.Write());
1108 }
1109}
1110
1111void SpellHistory::GetCooldownDurations(SpellInfo const* spellInfo, uint32 itemId, Duration* cooldown, uint32* categoryId, Duration* categoryCooldown)
1112{
1113 ASSERT(cooldown || categoryId || categoryCooldown);
1114 Duration tmpCooldown = Duration::min();
1115 uint32 tmpCategoryId = 0;
1116 Duration tmpCategoryCooldown = Duration::min();
1117
1118 // cooldown information stored in ItemEffect.db2, overriding normal cooldown and category
1119 if (itemId)
1120 {
1121 if (ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId))
1122 {
1123 for (ItemEffectEntry const* itemEffect : proto->Effects)
1124 {
1125 if (uint32(itemEffect->SpellID) == spellInfo->Id)
1126 {
1127 tmpCooldown = Milliseconds(itemEffect->CoolDownMSec);
1128 tmpCategoryId = itemEffect->SpellCategoryID;
1129 tmpCategoryCooldown = Milliseconds(itemEffect->CategoryCoolDownMSec);
1130 break;
1131 }
1132 }
1133 }
1134 }
1135
1136 // if no cooldown found above then base at DBC data
1137 if (tmpCooldown < Duration::zero() && tmpCategoryCooldown < Duration::zero())
1138 {
1139 tmpCooldown = Milliseconds(spellInfo->RecoveryTime);
1140 tmpCategoryId = spellInfo->GetCategory();
1141 tmpCategoryCooldown = Milliseconds(spellInfo->CategoryRecoveryTime);
1142 }
1143
1144 if (cooldown)
1145 *cooldown = tmpCooldown;
1146 if (categoryId)
1147 *categoryId = tmpCategoryId;
1148 if (categoryCooldown)
1149 *categoryCooldown = tmpCategoryCooldown;
1150}
1151
1153{
1154 boost::container::small_vector<std::pair<uint32, uint32>, 2> cooldownsToStart;
1155
1156 for (auto [categoryId, cooldown] : _categoryCooldowns)
1157 if (sSpellCategoryStore.AssertEntry(categoryId)->GetFlags().HasFlag(SpellCategoryFlags::CooldownEventOnLeaveCombat))
1158 cooldownsToStart.emplace_back(cooldown->SpellId, cooldown->ItemId);
1159
1160 for (auto [spellId, itemId] : cooldownsToStart)
1161 SendCooldownEvent(sSpellMgr->GetSpellInfo(spellId, DIFFICULTY_NONE), itemId);
1162}
1163
1168
1170{
1171 if (Player* player = _owner->ToPlayer())
1172 {
1173 // add all profession CDs created while in duel (if any)
1174 for (auto const& [spellId, cooldown] : _spellCooldowns)
1175 {
1176 SpellInfo const* spellInfo = sSpellMgr->AssertSpellInfo(spellId, DIFFICULTY_NONE);
1177
1178 if (spellInfo->RecoveryTime > 10 * MINUTE * IN_MILLISECONDS ||
1179 spellInfo->CategoryRecoveryTime > 10 * MINUTE * IN_MILLISECONDS)
1180 _spellCooldownsBeforeDuel[spellId] = cooldown;
1181 }
1182
1183 // check for spell with onHold active before and during the duel
1184 for (auto const& [spellId, cooldown] : _spellCooldownsBeforeDuel)
1185 {
1186 if (cooldown.OnHold)
1187 continue;
1188
1189 auto [itr, inserted] = _spellCooldowns.try_emplace(spellId, cooldown);
1190 if (!inserted && !itr->second.OnHold /*don't override if pre-existing cooldown is on hold*/)
1191 itr->second = cooldown;
1192 }
1193
1194 // update the client: restore old cooldowns
1196 spellCooldown.Caster = _owner->GetGUID();
1198
1199 for (auto const& [spellId, cooldown] : _spellCooldowns)
1200 {
1201 TimePoint now = time_point_cast<Duration>(GameTime::GetTime<Clock>());
1202 uint32 cooldownDuration = uint32(cooldown.CooldownEnd > now ? duration_cast<Milliseconds>(cooldown.CooldownEnd - now).count() : 0);
1203
1204 // cooldownDuration must be between 0 and 10 minutes in order to avoid any visual bugs
1205 if (cooldownDuration <= 0 || cooldownDuration > 10 * MINUTE * IN_MILLISECONDS || cooldown.OnHold)
1206 continue;
1207
1208 spellCooldown.SpellCooldowns.emplace_back(spellId, cooldownDuration);
1209 }
1210
1211 player->SendDirectMessage(spellCooldown.Write());
1212 }
1213}
1214
1215template void SpellHistory::LoadFromDB<Player>(PreparedQueryResult cooldownsResult, PreparedQueryResult chargesResult);
1216template void SpellHistory::LoadFromDB<Pet>(PreparedQueryResult cooldownsResult, PreparedQueryResult chargesResult);
1217template void SpellHistory::SaveToDB<Player>(CharacterDatabaseTransaction trans);
1218template void SpellHistory::SaveToDB<Pet>(CharacterDatabaseTransaction trans);
CharacterDatabaseStatements
@ CHAR_DEL_PET_SPELL_CHARGES
@ CHAR_INS_PET_SPELL_CHARGES
@ CHAR_INS_PET_SPELL_COOLDOWN
@ CHAR_DEL_CHAR_SPELL_COOLDOWNS
@ CHAR_INS_CHAR_SPELL_COOLDOWN
@ CHAR_INS_CHAR_SPELL_CHARGES
@ CHAR_DEL_PET_SPELL_COOLDOWNS
@ CHAR_DEL_CHAR_SPELL_CHARGES
@ IN_MILLISECONDS
Definition Common.h:38
@ MINUTE
Definition Common.h:32
DB2Storage< SpellCategoryEntry > sSpellCategoryStore("SpellCategory.db2", &SpellCategoryLoadInfo::Instance)
@ DIFFICULTY_NONE
Definition DBCEnums.h:936
SQLTransaction< CharacterDatabaseConnection > CharacterDatabaseTransaction
std::shared_ptr< PreparedResultSet > PreparedQueryResult
DatabaseWorkerPool< CharacterDatabaseConnection > CharacterDatabase
Accessor to the character database.
uint8_t uint8
Definition Define.h:156
int64_t int64
Definition Define.h:149
int8_t int8
Definition Define.h:152
int32_t int32
Definition Define.h:150
uint32_t uint32
Definition Define.h:154
std::chrono::milliseconds Milliseconds
Milliseconds shorthand typedef.
Definition Duration.h:24
#define ASSERT
Definition Errors.h:72
#define sObjectMgr
Definition ObjectMgr.h:1873
std::optional< T > Optional
Optional helper class to wrap optional values within.
Definition Optional.h:25
@ PETSPELL_REMOVED
Definition PetDefines.h:73
@ PLAYERSPELL_REMOVED
Definition Player.h:208
@ SPELL_ATTR9_IGNORE_SCHOOL_LOCKOUT
@ SPELL_PREVENTION_TYPE_SILENCE
SpellSchoolMask
@ MAX_SPELL_SCHOOL
@ SPELL_ATTR6_IGNORE_FOR_MOD_TIME_RATE
@ SPELL_ATTR6_NO_CATEGORY_COOLDOWN_MODS
@ SPELL_AURA_MOD_RECOVERY_RATE_BY_SPELL_LABEL
@ SPELL_AURA_MOD_CHARGE_RECOVERY_BY_TYPE_MASK
@ SPELL_AURA_MOD_CHARGE_RECOVERY_RATE_BY_TYPE_MASK
@ SPELL_AURA_MOD_COOLDOWN_BY_HASTE_REGEN
@ SPELL_AURA_CHARGE_RECOVERY_MOD
@ SPELL_AURA_CHARGE_RECOVERY_AFFECTED_BY_HASTE
@ SPELL_AURA_MOD_RECOVERY_RATE
@ SPELL_AURA_IGNORE_SPELL_COOLDOWN
@ SPELL_AURA_MOD_SPELL_CATEGORY_COOLDOWN
@ SPELL_AURA_MOD_CHARGE_RECOVERY_RATE
@ SPELL_AURA_CHARGE_RECOVERY_MULTIPLIER
@ SPELL_AURA_MOD_COOLDOWN
@ SPELL_AURA_CHARGE_RECOVERY_AFFECTED_BY_HASTE_REGEN
@ SPELL_AURA_MOD_SPELL_COOLDOWN_BY_HASTE
@ SPELL_AURA_MOD_MAX_CHARGES
@ SPELL_AURA_IGNORE_SPELL_CHARGE_COOLDOWN
double SpellEffectValue
This is a double instead of float to be able to store full range of int32.
@ SPELL_COOLDOWN_FLAG_INCLUDE_EVENT_COOLDOWNS
Starts GCD for spells that should start their cooldown on events, requires SPELL_COOLDOWN_FLAG_INCLUD...
@ SPELL_COOLDOWN_FLAG_LOSS_OF_CONTROL_UI
Shows interrupt cooldown in loss of control ui.
@ SPELL_COOLDOWN_FLAG_NONE
#define sSpellMgr
Definition SpellMgr.h:812
ObjectGuid const & GetGUID() const
Definition BaseEntity.h:163
uint32 m_spells[MAX_CREATURE_SPELLS]
Definition Creature.h:332
Class used to access individual fields of database query result.
Definition Field.h:94
uint32 GetUInt32() const noexcept
Definition Field.cpp:57
Difficulty GetDifficultyID() const
Definition Map.h:363
LowType GetCounter() const
Definition ObjectGuid.h:336
Player * ToPlayer()
Definition Object.h:126
Creature * ToCreature()
Definition Object.h:121
Definition Pet.h:40
bool HasSpell(uint32 spell) const override
Definition Player.cpp:3626
void setUInt32(uint8 index, uint32 value)
void setInt64(uint8 index, int64 value)
void setUInt64(uint8 index, uint64 value)
void AddCooldown(uint32 spellId, uint32 itemId, Duration cooldownDuration)
void UpdateCooldownRecoveryRate(Predicate &&predicate, float modChange, bool apply)
void HandleCooldowns(SpellInfo const *spellInfo, uint32 itemId, Spell *spell=nullptr)
Duration GetRemainingGlobalCooldown(SpellInfo const *spellInfo) const
void ResetAllCharges()
void ConsumeCharge(uint32 chargeCategoryId)
GlobalCooldownStorageType _globalCooldowns
void LoadFromDB(PreparedQueryResult cooldownsResult, PreparedQueryResult chargesResult)
TimePoint _schoolLockouts[MAX_SPELL_SCHOOL]
bool HasGlobalCooldown(SpellInfo const *spellInfo) const
Duration GetRemainingCategoryCooldown(uint32 categoryId) const
int32 GetMaxCharges(uint32 chargeCategoryId) const
CooldownStorageType::iterator EraseCooldown(CooldownStorageType::iterator itr)
void ModifyChargeRecoveryTime(uint32 chargeCategoryId, Duration cooldownMod)
void SendCooldownEvent(SpellInfo const *spellInfo, uint32 itemId=0, Spell *spell=nullptr, bool startCooldown=true)
void ResetCooldown(uint32 spellId, bool update=false)
bool IsReady(SpellInfo const *spellInfo, uint32 itemId=0) const
void AddGlobalCooldown(SpellInfo const *spellInfo, Duration duration)
void SaveToDB(CharacterDatabaseTransaction trans)
void SendClearCooldowns(std::vector< int32 > &&cooldowns) const
void RestoreCharge(uint32 chargeCategoryId)
Optional< TimePoint > _pauseTime
void LockSpellSchool(SpellSchoolMask schoolMask, Duration lockoutTime)
std::deque< ChargeEntry > ChargeEntryCollection
Duration GetRemainingCooldown(SpellInfo const *spellInfo) const
SpellHistory(Unit *owner)
void ResetAllCooldowns()
CooldownStorageType _spellCooldowns
void WritePacket(WorldPackets::Spells::SendSpellHistory *sendSpellHistory) const
void ResetCharges(uint32 chargeCategoryId)
bool HasCooldownOnHold(uint32 spellId) const
static void GetCooldownDurations(SpellInfo const *spellInfo, uint32 itemId, Duration *cooldown, uint32 *categoryId, Duration *categoryCooldown)
void SendSetSpellCharges(uint32 chargeCategoryId, ChargeEntryCollection const &chargeCollection) const
void CancelGlobalCooldown(SpellInfo const *spellInfo)
void RestoreCooldownStateAfterDuel()
void UpdateChargeRecoveryRate(uint32 chargeCategoryId, float modChange, bool apply)
void ModifyCooldown(uint32 spellId, Duration cooldownMod, bool withoutCategoryCooldown=false)
CooldownStorageType _spellCooldownsBeforeDuel
bool HasCharge(uint32 chargeCategoryId) const
void SaveCooldownStateBeforeDuel()
Milliseconds Duration
std::chrono::time_point< Clock, Duration > TimePoint
void ResetCooldowns(Predicate &&predicate, bool update=false)
void ModifySpellCooldown(uint32 spellId, Duration cooldownMod, bool withoutCategoryCooldown)
void StartCooldown(SpellInfo const *spellInfo, uint32 itemId, Spell *spell=nullptr, bool onHold=false, Optional< Duration > forcedCooldown={})
Player * GetPlayerOwner() const
int32 GetChargeRecoveryTime(uint32 chargeCategoryId) const
ChargeStorageType _categoryCharges
bool IsSchoolLocked(SpellSchoolMask schoolMask) const
CategoryCooldownStorageType _categoryCooldowns
bool HasCooldown(SpellInfo const *spellInfo, uint32 itemId=0) const
uint32 PreventionType
Definition SpellInfo.h:417
uint32 GetCategory() const
uint32 const Id
Definition SpellInfo.h:328
uint32 RecoveryTime
Definition SpellInfo.h:371
bool IsCooldownStartedOnEvent() const
bool IsPassive() const
uint32 CooldownAuraSpellId
Definition SpellInfo.h:375
uint32 CategoryRecoveryTime
Definition SpellInfo.h:372
SpellSchoolMask GetSchoolMask() const
bool HasAttribute(SpellAttr0 attribute) const
Definition SpellInfo.h:456
uint32 ChargeCategoryId
Definition SpellInfo.h:420
bool IsCooldownStartedOnEventAfterCombat() const
uint32 StartRecoveryCategory
Definition SpellInfo.h:373
bool HasLabel(uint32 labelId) const
Definition Spell.h:277
bool IsIgnoringCooldowns() const
Definition Spell.cpp:8293
Definition Unit.h:647
AuraEffectList const & GetAuraEffectsByType(AuraType type) const
Definition Unit.h:1362
Pet * ToPet()
Definition Unit.h:1838
bool HasAuraTypeWithMiscvalue(AuraType auraType, int32 miscValue) const
Definition Unit.cpp:4845
CharmInfo * GetCharmInfo()
Definition Unit.h:1262
float GetTotalAuraMultiplierByMiscValue(AuraType auraType, int32 misc_value) const
Definition Unit.cpp:5157
float GetTotalAuraModifier(AuraType auraType) const
Definition Unit.cpp:5087
bool HasAuraType(AuraType auraType) const
Definition Unit.cpp:4832
float GetTotalAuraModifierByMiscValue(AuraType auraType, int32 misc_value) const
Definition Unit.cpp:5147
UF::UpdateField< UF::UnitData, int32(WowCS::EntityFragment::CGObject), TYPEID_UNIT > m_unitData
Definition Unit.h:1897
bool HasAura(uint32 spellId, ObjectGuid casterGUID=ObjectGuid::Empty, ObjectGuid itemCasterGUID=ObjectGuid::Empty, uint32 reqEffMask=0) const
Definition Unit.cpp:4822
bool HasAuraTypeWithAffectMask(AuraType auraType, SpellInfo const *affectedSpell) const
Definition Unit.cpp:4854
bool IsInCombat() const
Definition Unit.h:1073
Map * GetMap() const
Definition Object.h:411
Player * GetSpellModOwner() const
Definition Object.cpp:1646
Player * GetCharmerOrOwnerPlayerOrPlayerItself() const
Definition Object.cpp:1626
std::vector< PetSpellCooldown > Cooldowns
Definition PetPackets.h:128
std::vector< PetSpellHistory > SpellHistory
Definition PetPackets.h:129
WorldPacket const * Write() override
WorldPacket const * Write() override
WorldPacket const * Write() override
WorldPacket const * Write() override
WorldPacket const * Write() override
WorldPacket const * Write() override
std::vector< SpellChargeEntry > Entries
std::vector< SpellHistoryEntry > Entries
WorldPacket const * Write() override
WorldPacket const * Write() override
std::vector< SpellCooldownStruct > SpellCooldowns
WorldPacket const * Write() override
#define sWorld
Definition World.h:919
uint32 GetPetNumber() const
Definition CharmInfo.h:93
EnumFlag< SpellCategoryFlags > GetFlags() const
static bool ReadCooldown(Field const *fields, uint32 *spellId, CooldownEntry *cooldownEntry)
static void WriteCooldown(PreparedStatementBase *stmt, uint8 &index, CooldownEntry const &cooldown)
static bool ReadCharge(Field const *fields, uint32 *categoryId, ChargeEntry *chargeEntry)
static void SetIdentifier(PreparedStatementBase *stmt, uint8 index, Unit *owner)
static void WriteCharge(PreparedStatementBase *stmt, uint8 &index, uint32 chargeCategory, ChargeEntry const &charge)
static bool ReadCooldown(Field const *fields, uint32 *spellId, CooldownEntry *cooldownEntry)
static void WriteCharge(PreparedStatementBase *stmt, uint8 &index, uint32 chargeCategory, ChargeEntry const &charge)
static bool ReadCharge(Field const *fields, uint32 *categoryId, ChargeEntry *chargeEntry)
static void SetIdentifier(PreparedStatementBase *stmt, uint8 index, Unit const *owner)
static void WriteCooldown(PreparedStatementBase *stmt, uint8 &index, CooldownEntry const &cooldown)