Core/Spells: Implement spell queue
Port From (https://github.com/TrinityCore/TrinityCore/commit/27019a62a4294f8dd48d975f85b1907c5adee0e3)
This commit is contained in:
@@ -110,6 +110,7 @@ namespace Game.Entities
|
||||
public Spell m_spellModTakingSpell;
|
||||
uint m_oldpetspell;
|
||||
Dictionary<uint, StoredAuraTeleportLocation> m_storedAuraTeleportLocations = new();
|
||||
SpellCastRequest _pendingSpellCastRequest;
|
||||
|
||||
//Mail
|
||||
List<Mail> m_mail = new();
|
||||
|
||||
@@ -3759,6 +3759,269 @@ namespace Game.Entities
|
||||
SkillInfo skillInfo = m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.Skill);
|
||||
SetUpdateFieldValue(ref skillInfo.ModifyValue(skillInfo.SkillPermBonus, (int)pos), bonus);
|
||||
}
|
||||
|
||||
public void RequestSpellCast(SpellCastRequest castRequest)
|
||||
{
|
||||
// We are overriding an already existing spell cast request so inform the client that the old cast is being replaced
|
||||
if (_pendingSpellCastRequest != null)
|
||||
CancelPendingCastRequest();
|
||||
|
||||
_pendingSpellCastRequest = castRequest;
|
||||
|
||||
// If we can process the cast request right now, do it.
|
||||
if (CanExecutePendingSpellCastRequest())
|
||||
ExecutePendingSpellCastRequest();
|
||||
}
|
||||
|
||||
public void CancelPendingCastRequest()
|
||||
{
|
||||
if (_pendingSpellCastRequest == null)
|
||||
return;
|
||||
|
||||
// We have to inform the client that the cast has been canceled. Otherwise the cast button will remain highlightened
|
||||
CastFailed castFailed = new();
|
||||
castFailed.CastID = _pendingSpellCastRequest.CastRequest.CastID;
|
||||
castFailed.SpellID = (int)_pendingSpellCastRequest.CastRequest.SpellID;
|
||||
castFailed.Reason = SpellCastResult.DontReport;
|
||||
SendPacket(castFailed);
|
||||
|
||||
_pendingSpellCastRequest = null;
|
||||
}
|
||||
|
||||
// A spell can be queued up within 400 milliseconds before global cooldown expires or the cast finishes
|
||||
static TimeSpan SPELL_QUEUE_TIME_WINDOW = TimeSpan.FromMilliseconds(400);
|
||||
|
||||
public bool CanRequestSpellCast(SpellInfo spellInfo, Unit castingUnit)
|
||||
{
|
||||
if (castingUnit.GetSpellHistory().GetRemainingGlobalCooldown(spellInfo) > SPELL_QUEUE_TIME_WINDOW)
|
||||
return false;
|
||||
|
||||
foreach (CurrentSpellTypes spellSlot in new[] { CurrentSpellTypes.Melee, CurrentSpellTypes.Generic })
|
||||
{
|
||||
Spell spell = GetCurrentSpell(spellSlot);
|
||||
if (spell != null && TimeSpan.FromMilliseconds(spell.GetRemainingCastTime()) > SPELL_QUEUE_TIME_WINDOW)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ExecutePendingSpellCastRequest()
|
||||
{
|
||||
if (_pendingSpellCastRequest == null)
|
||||
return;
|
||||
|
||||
TriggerCastFlags triggerFlag = TriggerCastFlags.None;
|
||||
|
||||
Unit castingUnit = _pendingSpellCastRequest.CastingUnitGUID == GetGUID() ? this : Global.ObjAccessor.GetUnit(this, _pendingSpellCastRequest.CastingUnitGUID);
|
||||
|
||||
// client provided targets
|
||||
SpellCastTargets targets = new(castingUnit, _pendingSpellCastRequest.CastRequest);
|
||||
|
||||
// The spell cast has been requested by using an item. Handle the cast accordingly.
|
||||
if (_pendingSpellCastRequest.ItemData != null)
|
||||
{
|
||||
if (ProcessItemCast(_pendingSpellCastRequest, targets))
|
||||
_pendingSpellCastRequest = null;
|
||||
else
|
||||
CancelPendingCastRequest();
|
||||
return;
|
||||
}
|
||||
|
||||
// check known spell or raid marker spell (which not requires player to know it)
|
||||
SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(_pendingSpellCastRequest.CastRequest.SpellID, GetMap().GetDifficultyID());
|
||||
Player plrCaster = castingUnit.ToPlayer();
|
||||
if (plrCaster != null && !plrCaster.HasActiveSpell(spellInfo.Id) && !spellInfo.HasAttribute(SpellAttr8.SkipIsKnownCheck))
|
||||
{
|
||||
bool allow = false;
|
||||
|
||||
// allow casting of unknown spells for special lock cases
|
||||
GameObject go = targets.GetGOTarget();
|
||||
if (go != null && go.GetSpellForLock(plrCaster) == spellInfo)
|
||||
allow = true;
|
||||
|
||||
// allow casting of spells triggered by clientside periodic trigger auras
|
||||
if (castingUnit.HasAuraTypeWithTriggerSpell(AuraType.PeriodicTriggerSpellFromClient, spellInfo.Id))
|
||||
{
|
||||
allow = true;
|
||||
triggerFlag = TriggerCastFlags.FullMask;
|
||||
}
|
||||
|
||||
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())
|
||||
{
|
||||
Spell currentSpell = castingUnit.GetCurrentSpell(CurrentSpellTypes.AutoRepeat);
|
||||
if (currentSpell != null)
|
||||
{
|
||||
if (currentSpell.m_spellInfo == spellInfo && currentSpell.m_targets.GetUnitTargetGUID() == targets.GetUnitTargetGUID())
|
||||
{
|
||||
CancelPendingCastRequest();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// auto-selection buff level base at target level (in spellInfo)
|
||||
if (targets.GetUnitTarget() != null)
|
||||
{
|
||||
SpellInfo 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 != null)
|
||||
spellInfo = actualSpellInfo;
|
||||
}
|
||||
|
||||
Spell spell = new Spell(castingUnit, spellInfo, triggerFlag);
|
||||
|
||||
SpellPrepare spellPrepare = new();
|
||||
spellPrepare.ClientCastID = _pendingSpellCastRequest.CastRequest.CastID;
|
||||
spellPrepare.ServerCastID = spell.m_castId;
|
||||
SendPacket(spellPrepare);
|
||||
|
||||
spell.m_fromClient = true;
|
||||
spell.m_misc.Data0 = _pendingSpellCastRequest.CastRequest.Misc[0];
|
||||
spell.m_misc.Data1 = _pendingSpellCastRequest.CastRequest.Misc[1];
|
||||
spell.Prepare(targets);
|
||||
|
||||
_pendingSpellCastRequest = null;
|
||||
}
|
||||
|
||||
bool ProcessItemCast(SpellCastRequest castRequest, SpellCastTargets targets)
|
||||
{
|
||||
Item item = GetUseableItemByPos(castRequest.ItemData.PackSlot, castRequest.ItemData.Slot);
|
||||
if (item == null)
|
||||
{
|
||||
SendEquipError(InventoryResult.ItemNotFound);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (item.GetGUID() != castRequest.ItemData.CastItem)
|
||||
{
|
||||
SendEquipError(InventoryResult.ItemNotFound);
|
||||
return false;
|
||||
}
|
||||
|
||||
ItemTemplate proto = item.GetTemplate();
|
||||
if (proto == null)
|
||||
{
|
||||
SendEquipError(InventoryResult.ItemNotFound, item);
|
||||
return false;
|
||||
}
|
||||
|
||||
// some item classes can be used only in equipped state
|
||||
if (proto.GetInventoryType() != InventoryType.NonEquip && !item.IsEquipped())
|
||||
{
|
||||
SendEquipError(InventoryResult.ItemNotFound, item);
|
||||
return false;
|
||||
}
|
||||
|
||||
InventoryResult msg = CanUseItem(item);
|
||||
if (msg != InventoryResult.Ok)
|
||||
{
|
||||
SendEquipError(msg, item, null);
|
||||
return false;
|
||||
}
|
||||
|
||||
// only allow conjured consumable, bandage, poisons (all should have the 2^21 item flag set in DB)
|
||||
if (proto.GetClass() == ItemClass.Consumable && !proto.HasFlag(ItemFlags.IgnoreDefaultArenaRestrictions) && InArena())
|
||||
{
|
||||
SendEquipError(InventoryResult.NotDuringArenaMatch, item);
|
||||
return false;
|
||||
}
|
||||
|
||||
// don't allow items banned in arena
|
||||
if (proto.HasFlag(ItemFlags.NotUseableInArena) && InArena())
|
||||
{
|
||||
SendEquipError(InventoryResult.NotDuringArenaMatch, item);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsInCombat())
|
||||
{
|
||||
foreach (var effect in item.GetEffects())
|
||||
{
|
||||
SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo((uint)effect.SpellID, GetMap().GetDifficultyID());
|
||||
if (spellInfo != null)
|
||||
{
|
||||
if (!spellInfo.CanBeUsedInCombat(this))
|
||||
{
|
||||
SendEquipError(InventoryResult.NotInCombat, item);
|
||||
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() == ItemBondingType.OnUse || item.GetBonding() == ItemBondingType.OnAcquire || item.GetBonding() == ItemBondingType.Quest)
|
||||
{
|
||||
if (!item.IsSoulBound())
|
||||
{
|
||||
item.SetState(ItemUpdateState.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 (!Global.ScriptMgr.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 CanExecutePendingSpellCastRequest()
|
||||
{
|
||||
if (_pendingSpellCastRequest == null)
|
||||
return false;
|
||||
|
||||
Unit castingUnit = _pendingSpellCastRequest.CastingUnitGUID == GetGUID() ? this : Global.ObjAccessor.GetUnit(this, _pendingSpellCastRequest.CastingUnitGUID);
|
||||
if (castingUnit == null || !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(CurrentSpellTypes.Channeled) == null && castingUnit.HasUnitState(UnitState.Casting))
|
||||
return false;
|
||||
|
||||
// Waiting for the global cooldown to expire before attempting to execute the cast request
|
||||
if (castingUnit.GetSpellHistory().GetRemainingGlobalCooldown(Global.SpellMgr.GetSpellInfo(_pendingSpellCastRequest.CastRequest.SpellID, GetMap().GetDifficultyID())) > TimeSpan.Zero)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public class PlayerSpell
|
||||
|
||||
@@ -358,6 +358,10 @@ namespace Game.Entities
|
||||
base.Update(diff);
|
||||
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();
|
||||
|
||||
long now = GameTime.GetGameTime();
|
||||
|
||||
UpdatePvPFlag(now);
|
||||
@@ -642,6 +646,9 @@ namespace Game.Entities
|
||||
return;
|
||||
}
|
||||
|
||||
// clear all pending spell cast requests when dying
|
||||
CancelPendingCastRequest();
|
||||
|
||||
// drunken state is cleared on death
|
||||
SetDrunkValue(0);
|
||||
|
||||
|
||||
@@ -20,97 +20,22 @@ namespace Game
|
||||
[WorldPacketHandler(ClientOpcodes.UseItem, Processing = PacketProcessing.Inplace)]
|
||||
void HandleUseItem(UseItem packet)
|
||||
{
|
||||
Player user = GetPlayer();
|
||||
|
||||
// ignore for remote control state
|
||||
if (user.GetUnitBeingMoved() != user)
|
||||
if (_player.GetUnitBeingMoved() != _player)
|
||||
return;
|
||||
|
||||
Item item = user.GetUseableItemByPos(packet.PackSlot, packet.Slot);
|
||||
if (item == null)
|
||||
// Skip casting invalid spells right away
|
||||
SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(packet.Cast.SpellID, _player.GetMap().GetDifficultyID());
|
||||
if (spellInfo == null)
|
||||
{
|
||||
user.SendEquipError(InventoryResult.ItemNotFound);
|
||||
Log.outError(LogFilter.Network, $"WorldSession::HandleUseItemOpcode: attempted to cast a non-existing spell (Id: {packet.Cast.SpellID})");
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.GetGUID() != packet.CastItem)
|
||||
{
|
||||
user.SendEquipError(InventoryResult.ItemNotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
ItemTemplate proto = item.GetTemplate();
|
||||
if (proto == null)
|
||||
{
|
||||
user.SendEquipError(InventoryResult.ItemNotFound, item);
|
||||
return;
|
||||
}
|
||||
|
||||
// some item classes can be used only in equipped state
|
||||
if (proto.GetInventoryType() != InventoryType.NonEquip && !item.IsEquipped())
|
||||
{
|
||||
user.SendEquipError(InventoryResult.ItemNotFound, item);
|
||||
return;
|
||||
}
|
||||
|
||||
InventoryResult msg = user.CanUseItem(item);
|
||||
if (msg != InventoryResult.Ok)
|
||||
{
|
||||
user.SendEquipError(msg, item);
|
||||
return;
|
||||
}
|
||||
|
||||
// only allow conjured consumable, bandage, poisons (all should have the 2^21 item flag set in DB)
|
||||
if (proto.GetClass() == ItemClass.Consumable && !proto.HasFlag(ItemFlags.IgnoreDefaultArenaRestrictions) && user.InArena())
|
||||
{
|
||||
user.SendEquipError(InventoryResult.NotDuringArenaMatch, item);
|
||||
return;
|
||||
}
|
||||
|
||||
// don't allow items banned in arena
|
||||
if (proto.HasFlag(ItemFlags.NotUseableInArena) && user.InArena())
|
||||
{
|
||||
user.SendEquipError(InventoryResult.NotDuringArenaMatch, item);
|
||||
return;
|
||||
}
|
||||
|
||||
if (user.IsInCombat())
|
||||
{
|
||||
foreach (ItemEffectRecord effect in item.GetEffects())
|
||||
{
|
||||
SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo((uint)effect.SpellID, user.GetMap().GetDifficultyID());
|
||||
if (spellInfo != null)
|
||||
{
|
||||
if (!spellInfo.CanBeUsedInCombat(user))
|
||||
{
|
||||
user.SendEquipError(InventoryResult.NotInCombat, item);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check also BIND_WHEN_PICKED_UP and BIND_QUEST_ITEM for .additem or .additemset case by GM (not binded at adding to inventory)
|
||||
if (item.GetBonding() == ItemBondingType.OnUse || item.GetBonding() == ItemBondingType.OnAcquire || item.GetBonding() == ItemBondingType.Quest)
|
||||
{
|
||||
if (!item.IsSoulBound())
|
||||
{
|
||||
item.SetState(ItemUpdateState.Changed, user);
|
||||
item.SetBinding(true);
|
||||
GetCollectionMgr().AddItemAppearance(item);
|
||||
}
|
||||
}
|
||||
|
||||
user.RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.ItemUse);
|
||||
|
||||
SpellCastTargets targets = new(user, packet.Cast);
|
||||
|
||||
// Note: If script stop casting it must send appropriate data to client to prevent stuck item in gray state.
|
||||
if (!Global.ScriptMgr.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(new SpellCastRequest(packet.Cast, _player.GetGUID(), new SpellCastRequestItemData(packet.PackSlot, packet.Slot, packet.CastItem)));
|
||||
else
|
||||
Spell.SendCastResult(_player, spellInfo, default, packet.Cast.CastID, SpellCastResult.SpellInProgress);
|
||||
}
|
||||
|
||||
[WorldPacketHandler(ClientOpcodes.OpenItem, Processing = PacketProcessing.Inplace)]
|
||||
@@ -279,108 +204,46 @@ namespace Game
|
||||
[WorldPacketHandler(ClientOpcodes.CastSpell, Processing = PacketProcessing.ThreadSafe)]
|
||||
void HandleCastSpell(CastSpell cast)
|
||||
{
|
||||
// ignore for remote control state (for player case)
|
||||
Unit mover = GetPlayer().GetUnitBeingMoved();
|
||||
if (mover != GetPlayer() && mover.IsTypeId(TypeId.Player))
|
||||
return;
|
||||
|
||||
SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(cast.Cast.SpellID, mover.GetMap().GetDifficultyID());
|
||||
SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(cast.Cast.SpellID, _player.GetMap().GetDifficultyID());
|
||||
if (spellInfo == null)
|
||||
{
|
||||
Log.outError(LogFilter.Network, "WORLD: unknown spell id {0}", cast.Cast.SpellID);
|
||||
Log.outError(LogFilter.Network, $"WorldSession::HandleCastSpellOpcode: attempted to cast a non-existing spell (Id: {cast.Cast.SpellID})");
|
||||
return;
|
||||
}
|
||||
|
||||
Unit caster = mover;
|
||||
if (caster.IsTypeId(TypeId.Unit) && !caster.ToCreature().HasSpell(spellInfo.Id))
|
||||
// ignore for remote control state (for player case)
|
||||
Unit mover = _player.GetUnitBeingMoved();
|
||||
if (mover != _player && mover.IsPlayer())
|
||||
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 (!GetPlayer().IsOnVehicle(caster) || spellInfo.CheckVehicle(GetPlayer()) != SpellCastResult.SpellCastOk)
|
||||
if (!GetPlayer().IsOnVehicle(castingUnit) || spellInfo.CheckVehicle(GetPlayer()) != SpellCastResult.SpellCastOk)
|
||||
return;
|
||||
|
||||
caster = GetPlayer();
|
||||
}
|
||||
|
||||
TriggerCastFlags triggerFlag = TriggerCastFlags.None;
|
||||
|
||||
// client provided targets
|
||||
SpellCastTargets targets = new(caster, cast.Cast);
|
||||
|
||||
// check known spell or raid marker spell (which not requires player to know it)
|
||||
if (caster.IsTypeId(TypeId.Player) && !caster.ToPlayer().HasActiveSpell(spellInfo.Id) && !spellInfo.HasAttribute(SpellAttr8.SkipIsKnownCheck))
|
||||
{
|
||||
bool allow = false;
|
||||
|
||||
// allow casting of unknown spells for special lock cases
|
||||
GameObject go = targets.GetGOTarget();
|
||||
if (go != null)
|
||||
if (go.GetSpellForLock(caster.ToPlayer()) == spellInfo)
|
||||
allow = true;
|
||||
|
||||
// allow casting of spells triggered by clientside periodic trigger auras
|
||||
if (caster.HasAuraTypeWithTriggerSpell(AuraType.PeriodicTriggerSpellFromClient, spellInfo.Id))
|
||||
{
|
||||
allow = true;
|
||||
triggerFlag = TriggerCastFlags.FullMask;
|
||||
}
|
||||
|
||||
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 (GetPlayer().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())
|
||||
{
|
||||
Spell autoRepeatSpell = caster.GetCurrentSpell(CurrentSpellTypes.AutoRepeat);
|
||||
if (autoRepeatSpell != null)
|
||||
if (autoRepeatSpell.m_spellInfo == spellInfo && autoRepeatSpell.m_targets.GetUnitTargetGUID() == targets.GetUnitTargetGUID())
|
||||
return;
|
||||
}
|
||||
|
||||
// auto-selection buff level base at target level (in spellInfo)
|
||||
if (targets.GetUnitTarget() != null)
|
||||
{
|
||||
SpellInfo actualSpellInfo = spellInfo.GetAuraRankForLevel(targets.GetUnitTarget().GetLevelForTarget(caster));
|
||||
|
||||
// if rank not found then function return NULL but in explicit cast case original spell can be casted and later failed with appropriate error message
|
||||
if (actualSpellInfo != null)
|
||||
spellInfo = actualSpellInfo;
|
||||
castingUnit = GetPlayer();
|
||||
}
|
||||
|
||||
if (cast.Cast.MoveUpdate != null)
|
||||
HandleMovementOpcode(ClientOpcodes.MoveStop, cast.Cast.MoveUpdate);
|
||||
|
||||
Spell spell = new(caster, spellInfo, triggerFlag);
|
||||
|
||||
SpellPrepare spellPrepare = new();
|
||||
spellPrepare.ClientCastID = cast.Cast.CastID;
|
||||
spellPrepare.ServerCastID = spell.m_castId;
|
||||
SendPacket(spellPrepare);
|
||||
|
||||
spell.m_fromClient = true;
|
||||
spell.m_misc.Data0 = cast.Cast.Misc[0];
|
||||
spell.m_misc.Data1 = cast.Cast.Misc[1];
|
||||
spell.Prepare(targets);
|
||||
if (_player.CanRequestSpellCast(spellInfo, castingUnit))
|
||||
_player.RequestSpellCast(new SpellCastRequest(cast.Cast, castingUnit.GetGUID()));
|
||||
else
|
||||
Spell.SendCastResult(_player, spellInfo, default, cast.Cast.CastID, SpellCastResult.SpellInProgress);
|
||||
}
|
||||
|
||||
[WorldPacketHandler(ClientOpcodes.CancelCast, Processing = PacketProcessing.ThreadSafe)]
|
||||
void HandleCancelCast(CancelCast packet)
|
||||
{
|
||||
if (GetPlayer().IsNonMeleeSpellCast(false))
|
||||
GetPlayer().InterruptNonMeleeSpells(false, packet.SpellID, false);
|
||||
if (_player.IsNonMeleeSpellCast(false))
|
||||
{
|
||||
_player.InterruptNonMeleeSpells(false, packet.SpellID, false);
|
||||
_player.CancelPendingCastRequest(); // canceling casts also cancels pending spell cast requests
|
||||
}
|
||||
}
|
||||
|
||||
[WorldPacketHandler(ClientOpcodes.CancelAura, Processing = PacketProcessing.Inplace)]
|
||||
@@ -486,6 +349,12 @@ namespace Game
|
||||
_player.InterruptSpell(CurrentSpellTypes.AutoRepeat);
|
||||
}
|
||||
|
||||
[WorldPacketHandler(ClientOpcodes.CancelChannelling, Processing = PacketProcessing.Inplace)]
|
||||
void HandleCancelQueuedSpellOpcode(CancelQueuedSpell cancelQueuedSpell)
|
||||
{
|
||||
_player.CancelPendingCastRequest();
|
||||
}
|
||||
|
||||
[WorldPacketHandler(ClientOpcodes.CancelChannelling, Processing = PacketProcessing.Inplace)]
|
||||
void HandleCancelChanneling(CancelChannelling cancelChanneling)
|
||||
{
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace Game.Networking.Packets
|
||||
|
||||
public int ChannelSpell;
|
||||
public int Reason; // 40 = /run SpellStopCasting(), 16 = movement/AURA_INTERRUPT_FLAG_MOVE, 41 = turning/AURA_INTERRUPT_FLAG_TURNING
|
||||
// does not match SpellCastResult enum
|
||||
// does not match SpellCastResult enum
|
||||
}
|
||||
|
||||
class CancelGrowthAura : ClientPacket
|
||||
@@ -181,11 +181,11 @@ namespace Game.Networking.Packets
|
||||
|
||||
public class CastSpell : ClientPacket
|
||||
{
|
||||
public SpellCastRequest Cast;
|
||||
public SpellCastRequestPkt Cast;
|
||||
|
||||
public CastSpell(WorldPacket packet) : base(packet)
|
||||
{
|
||||
Cast = new SpellCastRequest();
|
||||
Cast = new SpellCastRequestPkt();
|
||||
}
|
||||
|
||||
public override void Read()
|
||||
@@ -197,11 +197,11 @@ namespace Game.Networking.Packets
|
||||
public class PetCastSpell : ClientPacket
|
||||
{
|
||||
public ObjectGuid PetGUID;
|
||||
public SpellCastRequest Cast;
|
||||
public SpellCastRequestPkt Cast;
|
||||
|
||||
public PetCastSpell(WorldPacket packet) : base(packet)
|
||||
{
|
||||
Cast = new SpellCastRequest();
|
||||
Cast = new SpellCastRequestPkt();
|
||||
}
|
||||
|
||||
public override void Read()
|
||||
@@ -216,11 +216,11 @@ namespace Game.Networking.Packets
|
||||
public byte PackSlot;
|
||||
public byte Slot;
|
||||
public ObjectGuid CastItem;
|
||||
public SpellCastRequest Cast;
|
||||
public SpellCastRequestPkt Cast;
|
||||
|
||||
public UseItem(WorldPacket packet) : base(packet)
|
||||
{
|
||||
Cast = new SpellCastRequest();
|
||||
Cast = new SpellCastRequestPkt();
|
||||
}
|
||||
|
||||
public override void Read()
|
||||
@@ -1162,6 +1162,13 @@ namespace Game.Networking.Packets
|
||||
public ushort OverrideID;
|
||||
}
|
||||
|
||||
class CancelQueuedSpell : ClientPacket
|
||||
{
|
||||
public CancelQueuedSpell(WorldPacket packet) : base(packet) { }
|
||||
|
||||
public override void Read() { }
|
||||
}
|
||||
|
||||
//Structs
|
||||
public struct SpellLogPowerData
|
||||
{
|
||||
@@ -1505,7 +1512,7 @@ namespace Game.Networking.Packets
|
||||
{
|
||||
public void Write(WorldPacket data)
|
||||
{
|
||||
data .WriteUInt8(Slot);
|
||||
data.WriteUInt8(Slot);
|
||||
data.WriteBit(AuraData != null);
|
||||
data.FlushBits();
|
||||
|
||||
@@ -1661,7 +1668,7 @@ namespace Game.Networking.Packets
|
||||
}
|
||||
}
|
||||
|
||||
public class SpellCastRequest
|
||||
public class SpellCastRequestPkt
|
||||
{
|
||||
public ObjectGuid CastID;
|
||||
public uint SpellID;
|
||||
@@ -2000,7 +2007,7 @@ namespace Game.Networking.Packets
|
||||
data.FlushBits();
|
||||
|
||||
if (unused622_1.HasValue)
|
||||
data .WriteUInt32(unused622_1.Value);
|
||||
data.WriteUInt32(unused622_1.Value);
|
||||
if (unused622_2.HasValue)
|
||||
data.WriteUInt32(unused622_2.Value);
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace Game.Networking.Packets
|
||||
Cast.Read(_worldPacket);
|
||||
}
|
||||
|
||||
public SpellCastRequest Cast = new();
|
||||
public SpellCastRequestPkt Cast = new();
|
||||
}
|
||||
|
||||
class AccountToyUpdate : ServerPacket
|
||||
|
||||
@@ -8121,6 +8121,8 @@ namespace Game.Spells
|
||||
return m_casttime;
|
||||
}
|
||||
|
||||
public int GetRemainingCastTime() { return m_timer; }
|
||||
|
||||
bool IsAutoRepeat()
|
||||
{
|
||||
return m_autoRepeat;
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) CypherCore <http://github.com/CypherCore> All rights reserved.
|
||||
// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information.
|
||||
|
||||
using Game.Entities;
|
||||
using Game.Networking.Packets;
|
||||
|
||||
namespace Game.Spells
|
||||
{
|
||||
public class SpellCastRequestItemData
|
||||
{
|
||||
public byte PackSlot;
|
||||
public byte Slot;
|
||||
public ObjectGuid CastItem;
|
||||
|
||||
public SpellCastRequestItemData(byte packSlot, byte slot, ObjectGuid castItem)
|
||||
{
|
||||
PackSlot = packSlot;
|
||||
Slot = slot;
|
||||
CastItem = castItem;
|
||||
}
|
||||
}
|
||||
|
||||
public class SpellCastRequest
|
||||
{
|
||||
public SpellCastRequestPkt CastRequest;
|
||||
public ObjectGuid CastingUnitGUID;
|
||||
public SpellCastRequestItemData ItemData;
|
||||
|
||||
public SpellCastRequest(SpellCastRequestPkt castRequest, ObjectGuid castingUnitGUID, SpellCastRequestItemData? itemData = null)
|
||||
{
|
||||
CastRequest = castRequest;
|
||||
CastingUnitGUID = castingUnitGUID;
|
||||
ItemData = itemData;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,7 +18,7 @@ namespace Game.Spells
|
||||
m_dst = new SpellDestination();
|
||||
}
|
||||
|
||||
public SpellCastTargets(Unit caster, SpellCastRequest spellCastRequest)
|
||||
public SpellCastTargets(Unit caster, SpellCastRequestPkt spellCastRequest)
|
||||
{
|
||||
m_targetMask = spellCastRequest.Target.Flags;
|
||||
m_objectTargetGUID = spellCastRequest.Target.Unit;
|
||||
|
||||
@@ -921,6 +921,18 @@ namespace Game.Spells
|
||||
_globalCooldowns[spellInfo.StartRecoveryCategory] = new DateTime();
|
||||
}
|
||||
|
||||
public TimeSpan GetRemainingGlobalCooldown(SpellInfo spellInfo)
|
||||
{
|
||||
if (!_globalCooldowns.TryGetValue(spellInfo.StartRecoveryCategory, out DateTime end))
|
||||
return TimeSpan.Zero;
|
||||
|
||||
DateTime now = GameTime.GetDateAndTime();
|
||||
if (end < now)
|
||||
return TimeSpan.Zero;
|
||||
|
||||
return end - now;
|
||||
}
|
||||
|
||||
public Player GetPlayerOwner()
|
||||
{
|
||||
return _owner.GetCharmerOrOwnerPlayerOrPlayerItself();
|
||||
|
||||
Reference in New Issue
Block a user