Core/Spells: Implement spell queue (#29409)

This commit is contained in:
Ovahlord
2023-11-25 00:00:10 +01:00
committed by GitHub
parent 60f481db11
commit 27019a62a4
10 changed files with 388 additions and 161 deletions

View File

@@ -112,6 +112,7 @@
#include "Spell.h"
#include "SpellAuraEffects.h"
#include "SpellAuras.h"
#include "SpellCastRequest.h"
#include "SpellHistory.h"
#include "SpellMgr.h"
#include "SpellPackets.h"
@@ -944,6 +945,10 @@ void Player::Update(uint32 p_time)
Unit::Update(p_time);
SetCanDelayTeleport(false);
// Unit::Update updates the spell history and spell states. We can now check if we can launch another pending cast.
if (CanExecutePendingSpellCastRequest())
ExecutePendingSpellCastRequest();
time_t now = GameTime::GetGameTime();
UpdatePvPFlag(now);
@@ -1244,6 +1249,9 @@ void Player::setDeathState(DeathState s)
return;
}
// clear all pending spell cast requests when dying
CancelPendingCastRequest();
// drunken state is cleared on death
SetDrunkValue(0);
SetPower(POWER_COMBO_POINTS, 0);
@@ -29857,3 +29865,262 @@ uint32 TraitMgr::PlayerDataAccessor::GetPrimarySpecialization() const
{
return AsUnderlyingType(_player->GetPrimarySpecialization());
}
void Player::RequestSpellCast(std::unique_ptr<SpellCastRequest> castRequest)
{
// We are overriding an already existing spell cast request so inform the client that the old cast is being replaced
if (_pendingSpellCastRequest)
CancelPendingCastRequest();
_pendingSpellCastRequest = std::move(castRequest);
// If we can process the cast request right now, do it.
if (CanExecutePendingSpellCastRequest())
ExecutePendingSpellCastRequest();
}
void Player::CancelPendingCastRequest()
{
if (!_pendingSpellCastRequest)
return;
// We have to inform the client that the cast has been canceled. Otherwise the cast button will remain highlightened
WorldPackets::Spells::CastFailed castFailed;
castFailed.CastID = _pendingSpellCastRequest->CastRequest.CastID;
castFailed.SpellID = _pendingSpellCastRequest->CastRequest.SpellID;
castFailed.Reason = SPELL_FAILED_DONT_REPORT;
SendDirectMessage(castFailed.Write());
_pendingSpellCastRequest = nullptr;
}
// A spell can be queued up within 400 milliseconds before global cooldown expires or the cast finishes
static constexpr Milliseconds SPELL_QUEUE_TIME_WINDOW = 400ms;
bool Player::CanRequestSpellCast(SpellInfo const* spellInfo, Unit const* castingUnit) const
{
if (castingUnit->GetSpellHistory()->GetRemainingGlobalCooldown(spellInfo) > SPELL_QUEUE_TIME_WINDOW)
return false;
for (CurrentSpellTypes spellSlot : { CURRENT_MELEE_SPELL, CURRENT_GENERIC_SPELL })
if (Spell const* spell = GetCurrentSpell(spellSlot))
if (Milliseconds(spell->GetRemainingCastTime()) > SPELL_QUEUE_TIME_WINDOW)
return false;
return true;
}
void Player::ExecutePendingSpellCastRequest()
{
if (!_pendingSpellCastRequest)
return;
TriggerCastFlags triggerFlag = TRIGGERED_NONE;
Unit* castingUnit = _pendingSpellCastRequest->CastingUnitGUID == GetGUID() ? this : ObjectAccessor::GetUnit(*this, _pendingSpellCastRequest->CastingUnitGUID);
// client provided targets
SpellCastTargets targets(castingUnit, _pendingSpellCastRequest->CastRequest);
// The spell cast has been requested by using an item. Handle the cast accordingly.
if (_pendingSpellCastRequest->ItemData.has_value())
{
if (ProcessItemCast(*_pendingSpellCastRequest, targets))
_pendingSpellCastRequest = nullptr;
else
CancelPendingCastRequest();
return;
}
// check known spell or raid marker spell (which not requires player to know it)
SpellInfo const* spellInfo = sSpellMgr->AssertSpellInfo(_pendingSpellCastRequest->CastRequest.SpellID, GetMap()->GetDifficultyID());
Player* plrCaster = castingUnit->ToPlayer();
if (plrCaster && !plrCaster->HasActiveSpell(spellInfo->Id) && !spellInfo->HasAttribute(SPELL_ATTR8_SKIP_IS_KNOWN_CHECK))
{
bool allow = false;
// allow casting of unknown spells for special lock cases
if (GameObject* go = targets.GetGOTarget())
if (go->GetSpellForLock(plrCaster) == spellInfo)
allow = true;
// allow casting of spells triggered by clientside periodic trigger auras
if (castingUnit->HasAuraTypeWithTriggerSpell(SPELL_AURA_PERIODIC_TRIGGER_SPELL_FROM_CLIENT, spellInfo->Id))
{
allow = true;
triggerFlag = TRIGGERED_FULL_MASK;
}
if (!allow)
{
CancelPendingCastRequest();
return;
}
}
// Check possible spell cast overrides
spellInfo = castingUnit->GetCastSpellInfo(spellInfo, triggerFlag);
if (spellInfo->IsPassive())
{
CancelPendingCastRequest();
return;
}
// can't use our own spells when we're in possession of another unit
if (isPossessing())
{
CancelPendingCastRequest();
return;
}
// Client is resending autoshot cast opcode when other spell is cast during shoot rotation
// Skip it to prevent "interrupt" message
// Also check targets! target may have changed and we need to interrupt current spell
if (spellInfo->IsAutoRepeatRangedSpell())
{
if (Spell* spell = castingUnit->GetCurrentSpell(CURRENT_AUTOREPEAT_SPELL))
{
if (spell->m_spellInfo == spellInfo && spell->m_targets.GetUnitTargetGUID() == targets.GetUnitTargetGUID())
{
CancelPendingCastRequest();
return;
}
}
}
// auto-selection buff level base at target level (in spellInfo)
if (targets.GetUnitTarget())
{
SpellInfo const* actualSpellInfo = spellInfo->GetAuraRankForLevel(targets.GetUnitTarget()->GetLevelForTarget(this));
// if rank not found then function return NULL but in explicit cast case original spell can be cast and later failed with appropriate error message
if (actualSpellInfo)
spellInfo = actualSpellInfo;
}
Spell* spell = new Spell(castingUnit, spellInfo, triggerFlag);
WorldPackets::Spells::SpellPrepare spellPrepare;
spellPrepare.ClientCastID = _pendingSpellCastRequest->CastRequest.CastID;
spellPrepare.ServerCastID = spell->m_castId;
SendDirectMessage(spellPrepare.Write());
spell->m_fromClient = true;
spell->m_misc.Raw.Data[0] = _pendingSpellCastRequest->CastRequest.Misc[0];
spell->m_misc.Raw.Data[1] = _pendingSpellCastRequest->CastRequest.Misc[1];
spell->prepare(targets);
_pendingSpellCastRequest = nullptr;
}
bool Player::ProcessItemCast(SpellCastRequest& castRequest, SpellCastTargets const& targets)
{
Item* item = GetUseableItemByPos(castRequest.ItemData->PackSlot, castRequest.ItemData->Slot);
if (!item)
{
SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, nullptr, nullptr);
return false;
}
if (item->GetGUID() != castRequest.ItemData->CastItem)
{
SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, nullptr, nullptr);
return false;
}
ItemTemplate const* proto = item->GetTemplate();
if (!proto)
{
SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, item, nullptr);
return false;
}
// some item classes can be used only in equipped state
if (proto->GetInventoryType() != INVTYPE_NON_EQUIP && !item->IsEquipped())
{
SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, item, nullptr);
return false;
}
InventoryResult msg = CanUseItem(item);
if (msg != EQUIP_ERR_OK)
{
SendEquipError(msg, item, nullptr);
return false;
}
// only allow conjured consumable, bandage, poisons (all should have the 2^21 item flag set in DB)
if (proto->GetClass() == ITEM_CLASS_CONSUMABLE && !proto->HasFlag(ITEM_FLAG_IGNORE_DEFAULT_ARENA_RESTRICTIONS) && InArena())
{
SendEquipError(EQUIP_ERR_NOT_DURING_ARENA_MATCH, item, nullptr);
return false;
}
// don't allow items banned in arena
if (proto->HasFlag(ITEM_FLAG_NOT_USEABLE_IN_ARENA) && InArena())
{
SendEquipError(EQUIP_ERR_NOT_DURING_ARENA_MATCH, item, nullptr);
return false;
}
if (IsInCombat())
{
for (ItemEffectEntry const* effect : item->GetEffects())
{
if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(effect->SpellID, GetMap()->GetDifficultyID()))
{
if (!spellInfo->CanBeUsedInCombat(this))
{
SendEquipError(EQUIP_ERR_NOT_IN_COMBAT, item, nullptr);
return false;
}
}
}
}
// check also BIND_ON_ACQUIRE and BIND_QUEST for .additem or .additemset case by GM (not binded at adding to inventory)
if (item->GetBonding() == BIND_ON_USE || item->GetBonding() == BIND_ON_ACQUIRE || item->GetBonding() == BIND_QUEST)
{
if (!item->IsSoulBound())
{
item->SetState(ITEM_CHANGED, this);
item->SetBinding(true);
GetSession()->GetCollectionMgr()->AddItemAppearance(item);
}
}
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::ItemUse);
// Note: If script stop casting it must send appropriate data to client to prevent stuck item in gray state.
if (!sScriptMgr->OnItemUse(this, item, targets, castRequest.CastRequest.CastID))
{
// no script or script not process request by self
CastItemUseSpell(item, targets, castRequest.CastRequest.CastID, castRequest.CastRequest.Misc);
}
return true;
}
bool Player::CanExecutePendingSpellCastRequest()
{
if (!_pendingSpellCastRequest)
return false;
Unit const* castingUnit = _pendingSpellCastRequest->CastingUnitGUID == GetGUID() ? this : ObjectAccessor::GetUnit(*this, _pendingSpellCastRequest->CastingUnitGUID);
if (!castingUnit || !castingUnit->IsInWorld() || (castingUnit != this && GetUnitBeingMoved() != castingUnit))
{
// If the casting unit is no longer available, just cancel the entire spell cast request and be done with it
CancelPendingCastRequest();
return false;
}
// Generic and melee spells have to wait, channeled spells can be processed immediately.
if (!castingUnit->GetCurrentSpell(CURRENT_CHANNELED_SPELL) && castingUnit->HasUnitState(UNIT_STATE_CASTING))
return false;
// Waiting for the global cooldown to expire before attempting to execute the cast request
if (castingUnit->GetSpellHistory()->GetRemainingGlobalCooldown(sSpellMgr->AssertSpellInfo(_pendingSpellCastRequest->CastRequest.SpellID, GetMap()->GetDifficultyID())) > 0ms)
return false;
return true;
}

View File

@@ -62,6 +62,7 @@ struct PvpTalentEntry;
struct QuestPackageItemEntry;
struct RewardPackEntry;
struct SkillRaceClassInfoEntry;
struct SpellCastRequest;
struct TalentEntry;
struct TrainerSpell;
struct TransferAbortParams;
@@ -3210,6 +3211,19 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
bool _usePvpItemLevels;
ObjectGuid _areaSpiritHealerGUID;
// Spell cast request handling
public:
// Queues up a spell cast request that has been received via packet and processes it whenever possible.
void RequestSpellCast(std::unique_ptr<SpellCastRequest> castRequest);
void CancelPendingCastRequest();
bool CanRequestSpellCast(SpellInfo const* spell, Unit const* castingUnit) const;
private:
std::unique_ptr<SpellCastRequest> _pendingSpellCastRequest;
void ExecutePendingSpellCastRequest();
bool ProcessItemCast(SpellCastRequest& castRequest, SpellCastTargets const& targets);
bool CanExecutePendingSpellCastRequest();
};
TC_GAME_API void AddItemsSetItem(Player* player, Item const* item);

View File

@@ -36,6 +36,7 @@
#include "ScriptMgr.h"
#include "Spell.h"
#include "SpellAuraEffects.h"
#include "SpellCastRequest.h"
#include "SpellMgr.h"
#include "SpellPackets.h"
#include "TemporarySummon.h"
@@ -44,96 +45,22 @@
void WorldSession::HandleUseItemOpcode(WorldPackets::Spells::UseItem& packet)
{
Player* user = _player;
// ignore for remote control state
if (user->GetUnitBeingMoved() != user)
if (_player->GetUnitBeingMoved() != _player)
return;
Item* item = user->GetUseableItemByPos(packet.PackSlot, packet.Slot);
if (!item)
// Skip casting invalid spells right away
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(packet.Cast.SpellID, _player->GetMap()->GetDifficultyID());
if (!spellInfo)
{
user->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, nullptr, nullptr);
TC_LOG_ERROR("network", "WorldSession::HandleUseItemOpcode: attempted to cast a non-existing spell (Id: {})", packet.Cast.SpellID);
return;
}
if (item->GetGUID() != packet.CastItem)
{
user->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, nullptr, nullptr);
return;
}
ItemTemplate const* proto = item->GetTemplate();
if (!proto)
{
user->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, item, nullptr);
return;
}
// some item classes can be used only in equipped state
if (proto->GetInventoryType() != INVTYPE_NON_EQUIP && !item->IsEquipped())
{
user->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, item, nullptr);
return;
}
InventoryResult msg = user->CanUseItem(item);
if (msg != EQUIP_ERR_OK)
{
user->SendEquipError(msg, item, nullptr);
return;
}
// only allow conjured consumable, bandage, poisons (all should have the 2^21 item flag set in DB)
if (proto->GetClass() == ITEM_CLASS_CONSUMABLE && !proto->HasFlag(ITEM_FLAG_IGNORE_DEFAULT_ARENA_RESTRICTIONS) && user->InArena())
{
user->SendEquipError(EQUIP_ERR_NOT_DURING_ARENA_MATCH, item, nullptr);
return;
}
// don't allow items banned in arena
if (proto->HasFlag(ITEM_FLAG_NOT_USEABLE_IN_ARENA) && user->InArena())
{
user->SendEquipError(EQUIP_ERR_NOT_DURING_ARENA_MATCH, item, nullptr);
return;
}
if (user->IsInCombat())
{
for (ItemEffectEntry const* effect : item->GetEffects())
{
if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(effect->SpellID, user->GetMap()->GetDifficultyID()))
{
if (!spellInfo->CanBeUsedInCombat(user))
{
user->SendEquipError(EQUIP_ERR_NOT_IN_COMBAT, item, nullptr);
return;
}
}
}
}
// check also BIND_ON_ACQUIRE and BIND_QUEST for .additem or .additemset case by GM (not binded at adding to inventory)
if (item->GetBonding() == BIND_ON_USE || item->GetBonding() == BIND_ON_ACQUIRE || item->GetBonding() == BIND_QUEST)
{
if (!item->IsSoulBound())
{
item->SetState(ITEM_CHANGED, user);
item->SetBinding(true);
GetCollectionMgr()->AddItemAppearance(item);
}
}
user->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::ItemUse);
SpellCastTargets targets(user, packet.Cast);
// Note: If script stop casting it must send appropriate data to client to prevent stuck item in gray state.
if (!sScriptMgr->OnItemUse(user, item, targets, packet.Cast.CastID))
{
// no script or script not process request by self
user->CastItemUseSpell(item, targets, packet.Cast.CastID, packet.Cast.Misc);
}
if (_player->CanRequestSpellCast(spellInfo, _player))
_player->RequestSpellCast(std::make_unique<SpellCastRequest>(std::move(packet.Cast), _player->GetGUID(), SpellCastRequestItemData(packet.PackSlot, packet.Slot, packet.CastItem)));
else
Spell::SendCastResult(_player, spellInfo, {}, packet.Cast.CastID, SPELL_FAILED_SPELL_IN_PROGRESS);
}
void WorldSession::HandleOpenItemOpcode(WorldPackets::Spells::OpenItem& packet)
@@ -299,103 +226,46 @@ void WorldSession::HandleGameobjectReportUse(WorldPackets::GameObject::GameObjRe
void WorldSession::HandleCastSpellOpcode(WorldPackets::Spells::CastSpell& cast)
{
// Skip casting invalid spells right away
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(cast.Cast.SpellID, _player->GetMap()->GetDifficultyID());
if (!spellInfo)
{
TC_LOG_ERROR("network", "WorldSession::HandleCastSpellOpcode: attempted to cast a non-existing spell (Id: {})", cast.Cast.SpellID);
return;
}
// ignore for remote control state (for player case)
Unit* mover = _player->GetUnitBeingMoved();
if (mover != _player && mover->GetTypeId() == TYPEID_PLAYER)
return;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(cast.Cast.SpellID, mover->GetMap()->GetDifficultyID());
if (!spellInfo)
{
TC_LOG_ERROR("network", "WORLD: unknown spell id {}", cast.Cast.SpellID);
return;
}
Unit* caster = mover;
if (caster->GetTypeId() == TYPEID_UNIT && !caster->ToCreature()->HasSpell(spellInfo->Id))
Unit* castingUnit = mover;
if (castingUnit->IsCreature() && !castingUnit->ToCreature()->HasSpell(spellInfo->Id))
{
// If the vehicle creature does not have the spell but it allows the passenger to cast own spells
// change caster to player and let him cast
if (!_player->IsOnVehicle(caster) || spellInfo->CheckVehicle(_player) != SPELL_CAST_OK)
if (!_player->IsOnVehicle(castingUnit) || spellInfo->CheckVehicle(_player) != SPELL_CAST_OK)
return;
caster = _player;
castingUnit = _player;
}
TriggerCastFlags triggerFlag = TRIGGERED_NONE;
// client provided targets
SpellCastTargets targets(caster, cast.Cast);
// check known spell or raid marker spell (which not requires player to know it)
if (caster->GetTypeId() == TYPEID_PLAYER && !caster->ToPlayer()->HasActiveSpell(spellInfo->Id) && !spellInfo->HasAttribute(SPELL_ATTR8_SKIP_IS_KNOWN_CHECK))
{
bool allow = false;
// allow casting of unknown spells for special lock cases
if (GameObject* go = targets.GetGOTarget())
if (go->GetSpellForLock(caster->ToPlayer()) == spellInfo)
allow = true;
// allow casting of spells triggered by clientside periodic trigger auras
if (caster->HasAuraTypeWithTriggerSpell(SPELL_AURA_PERIODIC_TRIGGER_SPELL_FROM_CLIENT, spellInfo->Id))
{
allow = true;
triggerFlag = TRIGGERED_FULL_MASK;
}
if (!allow)
return;
}
// Check possible spell cast overrides
spellInfo = caster->GetCastSpellInfo(spellInfo, triggerFlag);
if (spellInfo->IsPassive())
return;
// can't use our own spells when we're in possession of another unit,
if (_player->isPossessing())
return;
// Client is resending autoshot cast opcode when other spell is cast during shoot rotation
// Skip it to prevent "interrupt" message
// Also check targets! target may have changed and we need to interrupt current spell
if (spellInfo->IsAutoRepeatRangedSpell())
if (Spell* spell = caster->GetCurrentSpell(CURRENT_AUTOREPEAT_SPELL))
if (spell->m_spellInfo == spellInfo && spell->m_targets.GetUnitTargetGUID() == targets.GetUnitTargetGUID())
return;
// auto-selection buff level base at target level (in spellInfo)
if (targets.GetUnitTarget())
{
SpellInfo const* actualSpellInfo = spellInfo->GetAuraRankForLevel(targets.GetUnitTarget()->GetLevelForTarget(caster));
// if rank not found then function return NULL but in explicit cast case original spell can be cast and later failed with appropriate error message
if (actualSpellInfo)
spellInfo = actualSpellInfo;
}
if (cast.Cast.MoveUpdate)
if (cast.Cast.MoveUpdate.has_value())
HandleMovementOpcode(CMSG_MOVE_STOP, *cast.Cast.MoveUpdate);
Spell* spell = new Spell(caster, spellInfo, triggerFlag);
WorldPackets::Spells::SpellPrepare spellPrepare;
spellPrepare.ClientCastID = cast.Cast.CastID;
spellPrepare.ServerCastID = spell->m_castId;
SendPacket(spellPrepare.Write());
spell->m_fromClient = true;
spell->m_misc.Raw.Data[0] = cast.Cast.Misc[0];
spell->m_misc.Raw.Data[1] = cast.Cast.Misc[1];
spell->prepare(targets);
if (_player->CanRequestSpellCast(spellInfo, castingUnit))
_player->RequestSpellCast(std::make_unique<SpellCastRequest>(std::move(cast.Cast), castingUnit->GetGUID()));
else
Spell::SendCastResult(_player, spellInfo, {}, cast.Cast.CastID, SPELL_FAILED_SPELL_IN_PROGRESS);
}
void WorldSession::HandleCancelCastOpcode(WorldPackets::Spells::CancelCast& packet)
{
if (_player->IsNonMeleeSpellCast(false))
{
_player->InterruptNonMeleeSpells(false, packet.SpellID, false);
_player->CancelPendingCastRequest(); // canceling casts also cancels pending spell cast requests
}
}
void WorldSession::HandleCancelAuraOpcode(WorldPackets::Spells::CancelAura& cancelAura)
@@ -495,6 +365,11 @@ void WorldSession::HandleCancelAutoRepeatSpellOpcode(WorldPackets::Spells::Cance
_player->InterruptSpell(CURRENT_AUTOREPEAT_SPELL);
}
void WorldSession::HandleCancelQueuedSpellOpcode(WorldPackets::Spells::CancelQueuedSpell& /*cancelQueuedSpell*/)
{
_player->CancelPendingCastRequest();
}
void WorldSession::HandleCancelChanneling(WorldPackets::Spells::CancelChannelling& cancelChanneling)
{
// ignore for remote control state (for player case)

View File

@@ -1060,6 +1060,14 @@ namespace WorldPackets
uint16 OverrideID = 0;
};
class CancelQueuedSpell final : public ClientPacket
{
public:
CancelQueuedSpell(WorldPacket&& packet) : ClientPacket(CMSG_CANCEL_QUEUED_SPELL, std::move(packet)) { }
void Read() override { }
};
ByteBuffer& operator>>(ByteBuffer& buffer, SpellCastRequest& request);
}
}

View File

@@ -267,7 +267,7 @@ void OpcodeTable::Initialize()
DEFINE_HANDLER(CMSG_CANCEL_MASTER_LOOT_ROLL, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL);
DEFINE_HANDLER(CMSG_CANCEL_MOD_SPEED_NO_CONTROL_AURAS, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleCancelModSpeedNoControlAuras);
DEFINE_HANDLER(CMSG_CANCEL_MOUNT_AURA, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleCancelMountAuraOpcode);
DEFINE_HANDLER(CMSG_CANCEL_QUEUED_SPELL, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL);
DEFINE_HANDLER(CMSG_CANCEL_QUEUED_SPELL, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleCancelQueuedSpellOpcode);
DEFINE_HANDLER(CMSG_CANCEL_TEMP_ENCHANTMENT, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleCancelTempEnchantmentOpcode);
DEFINE_HANDLER(CMSG_CANCEL_TRADE, STATUS_LOGGEDIN_OR_RECENTLY_LOGGOUT, PROCESS_THREADUNSAFE, &WorldSession::HandleCancelTradeOpcode);
DEFINE_HANDLER(CMSG_CAN_DUEL, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleCanDuel);

View File

@@ -714,6 +714,7 @@ namespace WorldPackets
class CancelGrowthAura;
class CancelMountAura;
class CancelModSpeedNoControlAuras;
class CancelQueuedSpell;
class PetCancelAura;
class CancelCast;
class CastSpell;
@@ -1512,6 +1513,7 @@ class TC_GAME_API WorldSession
void HandleCancelMountAuraOpcode(WorldPackets::Spells::CancelMountAura& cancelMountAura);
void HandleCancelModSpeedNoControlAuras(WorldPackets::Spells::CancelModSpeedNoControlAuras& cancelModSpeedNoControlAuras);
void HandleCancelAutoRepeatSpellOpcode(WorldPackets::Spells::CancelAutoRepeatSpell& cancelAutoRepeatSpell);
void HandleCancelQueuedSpellOpcode(WorldPackets::Spells::CancelQueuedSpell& cancelQueuedSpell);
void HandleMissileTrajectoryCollision(WorldPackets::Spells::MissileTrajectoryCollision& packet);
void HandleUpdateMissileTrajectory(WorldPackets::Spells::UpdateMissileTrajectory& packet);

View File

@@ -601,6 +601,7 @@ class TC_GAME_API Spell
UsedSpellMods m_appliedMods;
int32 GetCastTime() const { return m_casttime; }
int32 GetRemainingCastTime() const { return m_timer; }
bool IsAutoRepeat() const { return m_autoRepeat; }
void SetAutoRepeat(bool rep) { m_autoRepeat = rep; }
void ReSetTimer() { m_timer = m_casttime > 0 ? m_casttime : 0; }

View File

@@ -0,0 +1,43 @@
/*
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
*
* 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 SpellCastRequest_h__
#define SpellCastRequest_h__
#include "SpellPackets.h"
struct SpellCastRequestItemData
{
SpellCastRequestItemData(uint8 packSlot, uint8 slot, ObjectGuid castItem) :
PackSlot(packSlot), Slot(slot), CastItem(castItem) { }
uint8 PackSlot = 0;
uint8 Slot = 0;
ObjectGuid CastItem;
};
struct SpellCastRequest
{
SpellCastRequest(WorldPackets::Spells::SpellCastRequest&& castRequest, ObjectGuid castingUnitGUID, Optional<SpellCastRequestItemData> itemData = {}) :
CastRequest(castRequest), CastingUnitGUID(castingUnitGUID), ItemData(itemData) { }
WorldPackets::Spells::SpellCastRequest CastRequest;
ObjectGuid CastingUnitGUID;
Optional<SpellCastRequestItemData> ItemData;
};
#endif // SpellCastRequest_h__

View File

@@ -950,6 +950,22 @@ void SpellHistory::CancelGlobalCooldown(SpellInfo const* spellInfo)
_globalCooldowns[spellInfo->StartRecoveryCategory] = Clock::time_point(Clock::duration(0));
}
SpellHistory::Duration SpellHistory::GetRemainingGlobalCooldown(SpellInfo const* spellInfo) const
{
Clock::time_point end;
auto cdItr = _globalCooldowns.find(spellInfo->StartRecoveryCategory);
if (cdItr == _globalCooldowns.end())
return Duration::zero();
end = cdItr->second;
Clock::time_point now = GameTime::GetTime<Clock>();
if (end < now)
return Duration::zero();
Clock::duration remaining = end - now;
return std::chrono::duration_cast<std::chrono::milliseconds>(remaining);
}
Player* SpellHistory::GetPlayerOwner() const
{
return _owner->GetCharmerOrOwnerPlayerOrPlayerItself();

View File

@@ -171,6 +171,7 @@ public:
bool HasGlobalCooldown(SpellInfo const* spellInfo) const;
void AddGlobalCooldown(SpellInfo const* spellInfo, Duration duration);
void CancelGlobalCooldown(SpellInfo const* spellInfo);
Duration GetRemainingGlobalCooldown(SpellInfo const* spellInfo) const;
void SaveCooldownStateBeforeDuel();
void RestoreCooldownStateAfterDuel();