diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/server/game/Entities/Player/Player.cpp | 267 | ||||
-rw-r--r-- | src/server/game/Entities/Player/Player.h | 14 | ||||
-rw-r--r-- | src/server/game/Handlers/SpellHandler.cpp | 195 | ||||
-rw-r--r-- | src/server/game/Server/Packets/SpellPackets.h | 8 | ||||
-rw-r--r-- | src/server/game/Server/Protocol/Opcodes.cpp | 2 | ||||
-rw-r--r-- | src/server/game/Server/WorldSession.h | 2 | ||||
-rw-r--r-- | src/server/game/Spells/Spell.h | 1 | ||||
-rw-r--r-- | src/server/game/Spells/SpellCastRequest.h | 43 | ||||
-rw-r--r-- | src/server/game/Spells/SpellHistory.cpp | 16 | ||||
-rw-r--r-- | src/server/game/Spells/SpellHistory.h | 1 |
10 files changed, 388 insertions, 161 deletions
diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 23382777d25..4fc2bdd5bb8 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -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; +} diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 9f80a9af8c7..ceadbe32831 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -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); diff --git a/src/server/game/Handlers/SpellHandler.cpp b/src/server/game/Handlers/SpellHandler.cpp index 48f51f4632c..1204a90755a 100644 --- a/src/server/game/Handlers/SpellHandler.cpp +++ b/src/server/game/Handlers/SpellHandler.cpp @@ -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) - return; - - Item* item = user->GetUseableItemByPos(packet.PackSlot, packet.Slot); - if (!item) - { - user->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, nullptr, nullptr); - 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); + if (_player->GetUnitBeingMoved() != _player) 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()) + // Skip casting invalid spells right away + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(packet.Cast.SpellID, _player->GetMap()->GetDifficultyID()); + if (!spellInfo) { - user->SendEquipError(EQUIP_ERR_NOT_DURING_ARENA_MATCH, item, nullptr); + TC_LOG_ERROR("network", "WorldSession::HandleUseItemOpcode: attempted to cast a non-existing spell (Id: {})", packet.Cast.SpellID); 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) { - // 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()); + // Skip casting invalid spells right away + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(cast.Cast.SpellID, _player->GetMap()->GetDifficultyID()); if (!spellInfo) { - TC_LOG_ERROR("network", "WORLD: unknown spell id {}", cast.Cast.SpellID); + TC_LOG_ERROR("network", "WorldSession::HandleCastSpellOpcode: attempted to cast a non-existing spell (Id: {})", cast.Cast.SpellID); return; } - Unit* caster = mover; - if (caster->GetTypeId() == TYPEID_UNIT && !caster->ToCreature()->HasSpell(spellInfo->Id)) + // ignore for remote control state (for player case) + Unit* mover = _player->GetUnitBeingMoved(); + if (mover != _player && mover->GetTypeId() == TYPEID_PLAYER) + return; + + 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) diff --git a/src/server/game/Server/Packets/SpellPackets.h b/src/server/game/Server/Packets/SpellPackets.h index bc0e8a6f24b..3273e2dd8c3 100644 --- a/src/server/game/Server/Packets/SpellPackets.h +++ b/src/server/game/Server/Packets/SpellPackets.h @@ -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); } } diff --git a/src/server/game/Server/Protocol/Opcodes.cpp b/src/server/game/Server/Protocol/Opcodes.cpp index ea2efa6c725..0494fe453f9 100644 --- a/src/server/game/Server/Protocol/Opcodes.cpp +++ b/src/server/game/Server/Protocol/Opcodes.cpp @@ -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); diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h index 1b2976431ca..782e2919024 100644 --- a/src/server/game/Server/WorldSession.h +++ b/src/server/game/Server/WorldSession.h @@ -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); diff --git a/src/server/game/Spells/Spell.h b/src/server/game/Spells/Spell.h index b16bf1eb5e6..d5037f91356 100644 --- a/src/server/game/Spells/Spell.h +++ b/src/server/game/Spells/Spell.h @@ -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; } diff --git a/src/server/game/Spells/SpellCastRequest.h b/src/server/game/Spells/SpellCastRequest.h new file mode 100644 index 00000000000..f78be8314db --- /dev/null +++ b/src/server/game/Spells/SpellCastRequest.h @@ -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__ diff --git a/src/server/game/Spells/SpellHistory.cpp b/src/server/game/Spells/SpellHistory.cpp index 934345b137d..7e090101530 100644 --- a/src/server/game/Spells/SpellHistory.cpp +++ b/src/server/game/Spells/SpellHistory.cpp @@ -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(); diff --git a/src/server/game/Spells/SpellHistory.h b/src/server/game/Spells/SpellHistory.h index f15a51c95c5..0e86193ef02 100644 --- a/src/server/game/Spells/SpellHistory.h +++ b/src/server/game/Spells/SpellHistory.h @@ -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(); |