mirror of
https://github.com/TrinityCore/TrinityCore.git
synced 2026-01-15 23:20:36 +01:00
Core/Spells: Implement spell queue (#29409)
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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; }
|
||||
|
||||
43
src/server/game/Spells/SpellCastRequest.h
Normal file
43
src/server/game/Spells/SpellCastRequest.h
Normal 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__
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user