Core/Spell: fix spell visual for other players

- Ported SMSG_SPELL_GO and SMSG_SPELL_START to new packet system
This commit is contained in:
ariel-
2018-03-10 03:55:14 -03:00
parent 4ca9d6469d
commit 0c2a6dee07
5 changed files with 390 additions and 104 deletions

View File

@@ -20,5 +20,6 @@
#include "QueryPackets.h"
#include "QuestPackets.h"
#include "SpellPackets.h"
#endif // AllPackets_h__

View File

@@ -0,0 +1,160 @@
/*
* Copyright (C) 2008-2018 TrinityCore <https://www.trinitycore.org/>
*
* 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/>.
*/
#include "SpellPackets.h"
#include "SharedDefines.h"
#include "Spell.h"
#include "SpellInfo.h"
ByteBuffer& operator<<(ByteBuffer& data, WorldPackets::Spells::SpellMissStatus const& spellMissStatus)
{
data << uint64(spellMissStatus.TargetGUID);
data << uint8(spellMissStatus.Reason);
if (spellMissStatus.Reason == SPELL_MISS_REFLECT)
data << uint8(spellMissStatus.ReflectStatus);
return data;
}
ByteBuffer& operator<<(ByteBuffer& data, WorldPackets::Spells::TargetLocation const& targetLocation)
{
data << targetLocation.Transport.WriteAsPacked(); // relative position guid here - transport for example
data << targetLocation.Location.PositionXYZStream();
return data;
}
ByteBuffer& operator<<(ByteBuffer& data, WorldPackets::Spells::SpellTargetData const& spellTargetData)
{
data << uint32(spellTargetData.Flags);
if (spellTargetData.Unit)
data << spellTargetData.Unit->WriteAsPacked();
if (spellTargetData.Item)
data << spellTargetData.Item->WriteAsPacked();
if (spellTargetData.SrcLocation)
data << *spellTargetData.SrcLocation;
if (spellTargetData.DstLocation)
data << *spellTargetData.DstLocation;
if (spellTargetData.Name)
data << *spellTargetData.Name;
return data;
}
ByteBuffer& operator<<(ByteBuffer& data, WorldPackets::Spells::RuneData const& runeData)
{
data << uint8(runeData.Start);
data << uint8(runeData.Count);
for (uint8 cooldown : runeData.Cooldowns)
data << uint8(cooldown);
return data;
}
ByteBuffer& operator<<(ByteBuffer& data, WorldPackets::Spells::MissileTrajectoryResult const& traj)
{
data << float(traj.Pitch);
data << uint32(traj.TravelTime);
return data;
}
ByteBuffer& operator<<(ByteBuffer& data, WorldPackets::Spells::SpellAmmo const& spellAmmo)
{
data << uint32(spellAmmo.DisplayID);
data << uint32(spellAmmo.InventoryType);
return data;
}
ByteBuffer& operator<<(ByteBuffer& data, WorldPackets::Spells::CreatureImmunities const& immunities)
{
data << uint32(immunities.School);
data << uint32(immunities.Value);
return data;
}
ByteBuffer& operator<<(ByteBuffer& data, WorldPackets::Spells::SpellCastData const& spellCastData)
{
data << spellCastData.CasterGUID.WriteAsPacked();
data << spellCastData.CasterUnit.WriteAsPacked();
data << uint8(spellCastData.CastID); // pending spell cast?
data << uint32(spellCastData.SpellID); // spellId
data << uint32(spellCastData.CastFlags); // cast flags
data << uint32(spellCastData.CastTime); // timestamp
if (spellCastData.HitTargets && spellCastData.MissStatus)
{
// Hit and miss target counts are both uint8, that limits us to 255 targets for each
// sending more than 255 targets crashes the client (since count sent would be wrong)
// Spells like 40647 (with a huge radius) can easily reach this limit (spell might need
// target conditions but we still need to limit the number of targets sent and keeping
// correct count for both hit and miss).
static std::size_t const PACKET_TARGET_LIMIT = std::numeric_limits<uint8>::max();
if (spellCastData.HitTargets->size() > PACKET_TARGET_LIMIT)
spellCastData.HitTargets->resize(PACKET_TARGET_LIMIT);
data << uint8(spellCastData.HitTargets->size());
for (ObjectGuid const& target : *spellCastData.HitTargets)
data << uint64(target);
if (spellCastData.MissStatus->size() > PACKET_TARGET_LIMIT)
spellCastData.MissStatus->resize(PACKET_TARGET_LIMIT);
data << uint8(spellCastData.MissStatus->size());
for (WorldPackets::Spells::SpellMissStatus const& status : *spellCastData.MissStatus)
data << status;
}
data << spellCastData.Target;
if (spellCastData.RemainingPower)
data << uint32(*spellCastData.RemainingPower);
if (spellCastData.RemainingRunes)
data << *spellCastData.RemainingRunes;
if (spellCastData.MissileTrajectory)
data << *spellCastData.MissileTrajectory;
if (spellCastData.Ammo)
data << *spellCastData.Ammo;
if (spellCastData.Immunities)
data << *spellCastData.Immunities;
if (spellCastData.CastFlags & CAST_FLAG_VISUAL_CHAIN)
{
data << uint32(0);
data << uint32(0);
}
if (spellCastData.Target.Flags & TARGET_FLAG_DEST_LOCATION)
data << uint8(0);
return data;
}
WorldPacket const* WorldPackets::Spells::SpellGo::Write()
{
_worldPacket << Cast;
return &_worldPacket;
}
WorldPacket const* WorldPackets::Spells::SpellStart::Write()
{
_worldPacket << Cast;
return &_worldPacket;
}

View File

@@ -0,0 +1,124 @@
/*
* Copyright (C) 2008-2018 TrinityCore <https://www.trinitycore.org/>
*
* 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/>.
*/
/*
* Scripts for spells with SPELLFAMILY_GENERIC which cannot be included in AI script file
* of creature using it or can't be bound to any player class.
* Ordered alphabetically using scriptname.
* Scriptnames of files in this file should be prefixed with "spell_gen_"
*/
#ifndef SpellPackets_h__
#define SpellPackets_h__
#include "Packet.h"
#include "ObjectGuid.h"
#include "Optional.h"
namespace WorldPackets
{
namespace Spells
{
struct SpellMissStatus
{
ObjectGuid TargetGUID;
uint8 Reason = 0;
uint8 ReflectStatus = 0;
};
struct RuneData
{
uint8 Start = 0;
uint8 Count = 0;
std::vector<uint8> Cooldowns;
};
struct MissileTrajectoryResult
{
uint32 TravelTime = 0;
float Pitch = 0.0f;
};
struct SpellAmmo
{
uint32 DisplayID = 0;
uint32 InventoryType = 0;
};
struct CreatureImmunities
{
uint32 School = 0;
uint32 Value = 0;
};
struct TargetLocation
{
ObjectGuid Transport;
Position Location;
};
struct SpellTargetData
{
uint32 Flags = 0;
Optional<ObjectGuid> Unit;
Optional<ObjectGuid> Item;
Optional<TargetLocation> SrcLocation;
Optional<TargetLocation> DstLocation;
Optional<std::string> Name;
};
struct SpellCastData
{
ObjectGuid CasterGUID;
ObjectGuid CasterUnit;
uint8 CastID = 0;
uint32 SpellID = 0;
uint32 CastFlags = 0;
uint32 CastTime = 0;
mutable Optional<std::vector<ObjectGuid>> HitTargets;
mutable Optional<std::vector<SpellMissStatus>> MissStatus;
SpellTargetData Target;
Optional<uint32> RemainingPower;
Optional<RuneData> RemainingRunes;
Optional<MissileTrajectoryResult> MissileTrajectory;
Optional<SpellAmmo> Ammo;
Optional<CreatureImmunities> Immunities;
};
class SpellGo final : public ServerPacket
{
public:
SpellGo() : ServerPacket(SMSG_SPELL_GO) { }
WorldPacket const* Write() override;
SpellCastData Cast;
};
class SpellStart final : public ServerPacket
{
public:
SpellStart() : ServerPacket(SMSG_SPELL_START) { }
WorldPacket const* Write() override;
SpellCastData Cast;
};
}
}
#endif // SpellPackets_h__

View File

@@ -47,6 +47,7 @@
#include "SpellHistory.h"
#include "SpellInfo.h"
#include "SpellMgr.h"
#include "SpellPackets.h"
#include "SpellScript.h"
#include "TemporarySummon.h"
#include "TradeData.h"
@@ -175,41 +176,42 @@ void SpellCastTargets::Read(ByteBuffer& data, Unit* caster)
Update(caster);
}
void SpellCastTargets::Write(ByteBuffer& data)
void SpellCastTargets::Write(WorldPackets::Spells::SpellTargetData& data)
{
data << uint32(m_targetMask);
data.Flags = m_targetMask;
if (m_targetMask & (TARGET_FLAG_UNIT | TARGET_FLAG_CORPSE_ALLY | TARGET_FLAG_GAMEOBJECT | TARGET_FLAG_CORPSE_ENEMY | TARGET_FLAG_UNIT_MINIPET))
data << m_objectTargetGUID.WriteAsPacked();
data.Unit = m_objectTargetGUID;
if (m_targetMask & (TARGET_FLAG_ITEM | TARGET_FLAG_TRADE_ITEM))
{
data.Item = boost::in_place();
if (m_itemTarget)
data << m_itemTarget->GetPackGUID();
else
data << uint8(0);
data.Item = m_itemTarget->GetGUID();
}
if (m_targetMask & TARGET_FLAG_SOURCE_LOCATION)
{
data << m_src._transportGUID.WriteAsPacked(); // relative position guid here - transport for example
if (m_src._transportGUID)
data << m_src._transportOffset.PositionXYZStream();
data.SrcLocation = boost::in_place();
data.SrcLocation->Transport = m_src._transportGUID;
if (!m_src._transportGUID.IsEmpty())
data.SrcLocation->Location = m_src._transportOffset;
else
data << m_src._position.PositionXYZStream();
data.SrcLocation->Location = m_src._position;
}
if (m_targetMask & TARGET_FLAG_DEST_LOCATION)
{
data << m_dst._transportGUID.WriteAsPacked(); // relative position guid here - transport for example
data.DstLocation = boost::in_place();
data.DstLocation->Transport = m_dst._transportGUID;
if (m_dst._transportGUID)
data << m_dst._transportOffset.PositionXYZStream();
data.DstLocation->Location = m_dst._transportOffset;
else
data << m_dst._position.PositionXYZStream();
data.DstLocation->Location = m_dst._position;
}
if (m_targetMask & TARGET_FLAG_STRING)
data << m_strTarget;
data.Name = m_strTarget;
}
ObjectGuid SpellCastTargets::GetOrigUnitTargetGUID() const
@@ -4082,33 +4084,39 @@ void Spell::SendSpellStart()
if (m_spellInfo->RuneCostID && m_spellInfo->PowerType == POWER_RUNE)
castFlags |= CAST_FLAG_NO_GCD; // not needed, but Blizzard sends it
WorldPacket data(SMSG_SPELL_START, (8+8+4+4+2));
WorldPackets::Spells::SpellStart packet;
WorldPackets::Spells::SpellCastData& castData = packet.Cast;
if (m_CastItem)
data << m_CastItem->GetPackGUID();
castData.CasterGUID = m_CastItem->GetGUID();
else
data << m_caster->GetPackGUID();
castData.CasterGUID = m_caster->GetGUID();
data << m_caster->GetPackGUID();
data << uint8(m_cast_count); // pending spell cast?
data << uint32(m_spellInfo->Id); // spellId
data << uint32(castFlags); // cast flags
data << int32(m_timer); // delay?
castData.CasterUnit = m_caster->GetGUID();
castData.CastID = m_cast_count;
castData.SpellID = m_spellInfo->Id;
castData.CastFlags = castFlags;
castData.CastTime = m_timer;
m_targets.Write(data);
m_targets.Write(castData.Target);
if (castFlags & CAST_FLAG_POWER_LEFT_SELF)
data << uint32(ASSERT_NOTNULL(m_caster->ToUnit())->GetPower((Powers)m_spellInfo->PowerType));
castData.RemainingPower = ASSERT_NOTNULL(m_caster->ToUnit())->GetPower(static_cast<Powers>(m_spellInfo->PowerType));
if (castFlags & CAST_FLAG_AMMO)
WriteAmmoToPacket(&data);
{
castData.Ammo = boost::in_place();
UpdateSpellCastDataAmmo(*castData.Ammo);
}
if (castFlags & CAST_FLAG_IMMUNITY)
{
data << uint32(schoolImmunityMask);
data << uint32(mechanicImmunityMask);
castData.Immunities = boost::in_place();
castData.Immunities->School = schoolImmunityMask;
castData.Immunities->Value = mechanicImmunityMask;
}
m_caster->SendMessageToSet(&data, true);
m_caster->SendMessageToSet(packet.Write(), true);
}
void Spell::SendSpellGo()
@@ -4117,8 +4125,6 @@ void Spell::SendSpellGo()
if (!IsNeedSendToClient())
return;
//TC_LOG_DEBUG("spells", "Sending SMSG_SPELL_GO id=%u", m_spellInfo->Id);
uint32 castFlags = CAST_FLAG_UNKNOWN_9;
// triggered spells with spell visual != 0
@@ -4152,78 +4158,82 @@ void Spell::SendSpellGo()
if (!m_spellInfo->StartRecoveryTime)
castFlags |= CAST_FLAG_NO_GCD;
WorldPacket data(SMSG_SPELL_GO, 50); // guess size
WorldPackets::Spells::SpellGo packet;
WorldPackets::Spells::SpellCastData& castData = packet.Cast;
if (m_CastItem)
data << m_CastItem->GetPackGUID();
castData.CasterGUID = m_CastItem->GetGUID();
else
data << m_caster->GetPackGUID();
castData.CasterGUID = m_caster->GetGUID();
data << m_caster->GetPackGUID();
data << uint8(m_cast_count); // pending spell cast?
data << uint32(m_spellInfo->Id); // spellId
data << uint32(castFlags); // cast flags
data << uint32(GameTime::GetGameTimeMS()); // timestamp
castData.CasterUnit = m_caster->GetGUID();
castData.CastID = m_cast_count;
castData.SpellID = m_spellInfo->Id;
castData.CastFlags = castFlags;
castData.CastTime = GameTime::GetGameTimeMS();
WriteSpellGoTargets(&data);
UpdateSpellCastDataTargets(castData);
m_targets.Write(data);
m_targets.Write(castData.Target);
if (castFlags & CAST_FLAG_POWER_LEFT_SELF)
data << uint32(ASSERT_NOTNULL(m_caster->ToUnit())->GetPower((Powers)m_spellInfo->PowerType));
castData.RemainingPower = ASSERT_NOTNULL(m_caster->ToUnit())->GetPower(static_cast<Powers>(m_spellInfo->PowerType));
if (castFlags & CAST_FLAG_RUNE_LIST) // rune cooldowns list
{
castData.RemainingRunes = boost::in_place();
/// @todo There is a crash caused by a spell with CAST_FLAG_RUNE_LIST cast by a creature
//The creature is the mover of a player, so HandleCastSpellOpcode uses it as the caster
if (Player* player = m_caster->ToPlayer())
{
uint8 runeMaskInitial = m_runesState;
uint8 runeMaskAfterCast = player->GetRunesState();
data << uint8(runeMaskInitial); // runes state before
data << uint8(runeMaskAfterCast); // runes state after
castData.RemainingRunes->Start = runeMaskInitial; // runes state before
castData.RemainingRunes->Count = runeMaskAfterCast; // runes state after
for (uint8 i = 0; i < MAX_RUNES; ++i)
{
uint8 mask = (1 << i);
if (mask & runeMaskInitial && !(mask & runeMaskAfterCast)) // usable before andon cooldown now...
if ((mask & runeMaskInitial) && !(mask & runeMaskAfterCast)) // usable before and on cooldown now...
{
// float casts ensure the division is performed on floats as we need float result
float baseCd = float(player->GetRuneBaseCooldown(i));
data << uint8((baseCd - float(player->GetRuneCooldown(i))) / baseCd * 255); // rune cooldown passed
castData.RemainingRunes->Cooldowns.push_back(uint8((baseCd - float(player->GetRuneCooldown(i))) / baseCd * 255));
}
}
}
}
if (castFlags & CAST_FLAG_ADJUST_MISSILE)
{
data << m_targets.GetElevation();
data << uint32(m_delayMoment);
castData.MissileTrajectory = boost::in_place();
castData.MissileTrajectory->Pitch = m_targets.GetElevation();
castData.MissileTrajectory->TravelTime = m_delayMoment;
}
if (castFlags & CAST_FLAG_AMMO)
WriteAmmoToPacket(&data);
if (castFlags & CAST_FLAG_VISUAL_CHAIN)
{
data << uint32(0);
data << uint32(0);
}
if (m_targets.GetTargetMask() & TARGET_FLAG_DEST_LOCATION)
{
data << uint8(0);
castData.Ammo = boost::in_place();
UpdateSpellCastDataAmmo(*castData.Ammo);
}
// should be sent to self only
if (castFlags & CAST_FLAG_POWER_LEFT_SELF)
{
if (Player* player = m_caster->GetAffectingPlayer())
player->SendDirectMessage(&data);
player->SendDirectMessage(packet.Write());
// update nearby players (remove flag)
castData.CastFlags &= ~CAST_FLAG_POWER_LEFT_SELF;
castData.RemainingPower = boost::none;
m_caster->SendMessageToSet(packet.Write(), false);
}
else
m_caster->SendMessageToSet(&data, true);
m_caster->SendMessageToSet(packet.Write(), true);
}
void Spell::WriteAmmoToPacket(WorldPacket* data)
void Spell::UpdateSpellCastDataAmmo(WorldPackets::Spells::SpellAmmo& ammo)
{
uint32 ammoInventoryType = 0;
uint32 ammoDisplayID = 0;
@@ -4291,13 +4301,16 @@ void Spell::WriteAmmoToPacket(WorldPacket* data)
}
}
*data << uint32(ammoDisplayID);
*data << uint32(ammoInventoryType);
ammo.DisplayID = ammoDisplayID;
ammo.InventoryType = ammoInventoryType;
}
/// Writes miss and hit targets for a SMSG_SPELL_GO packet
void Spell::WriteSpellGoTargets(WorldPacket* data)
void Spell::UpdateSpellCastDataTargets(WorldPackets::Spells::SpellCastData& data)
{
data.HitTargets = boost::in_place();
data.MissStatus = boost::in_place();
// This function also fill data for channeled spells:
// m_needAliveTargetMask req for stop channelig if one target die
for (TargetInfo& targetInfo : m_UniqueTargetInfo)
@@ -4305,53 +4318,31 @@ void Spell::WriteSpellGoTargets(WorldPacket* data)
if (targetInfo.EffectMask == 0) // No effect apply - all immuned add state
// possibly SPELL_MISS_IMMUNE2 for this??
targetInfo.MissCondition = SPELL_MISS_IMMUNE2;
}
// Hit and miss target counts are both uint8, that limits us to 255 targets for each
// sending more than 255 targets crashes the client (since count sent would be wrong)
// Spells like 40647 (with a huge radius) can easily reach this limit (spell might need
// target conditions but we still need to limit the number of targets sent and keeping
// correct count for both hit and miss).
uint32 hit = 0;
size_t hitPos = data->wpos();
*data << (uint8)0; // placeholder
for (auto ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end() && hit < 255; ++ihit)
{
if (ihit->MissCondition == SPELL_MISS_NONE) // Add only hits
if (targetInfo.MissCondition == SPELL_MISS_NONE) // Add only hits
{
*data << uint64(ihit->TargetGUID);
m_channelTargetEffectMask |= ihit->EffectMask;
++hit;
data.HitTargets->push_back(targetInfo.TargetGUID);
m_channelTargetEffectMask |= targetInfo.EffectMask;
}
else // misses
{
WorldPackets::Spells::SpellMissStatus missStatus;
missStatus.TargetGUID = targetInfo.TargetGUID;
missStatus.Reason = targetInfo.MissCondition;
if (targetInfo.MissCondition == SPELL_MISS_REFLECT)
missStatus.ReflectStatus = targetInfo.ReflectResult;
data.MissStatus->push_back(missStatus);
}
}
for (auto ighit = m_UniqueGOTargetInfo.begin(); ighit != m_UniqueGOTargetInfo.end() && hit < 255; ++ighit)
{
*data << uint64(ighit->TargetGUID); // Always hits
++hit;
}
for (GOTargetInfo const& targetInfo : m_UniqueGOTargetInfo)
data.HitTargets->push_back(targetInfo.TargetGUID); // Always hits
uint32 miss = 0;
size_t missPos = data->wpos();
*data << (uint8)0; // placeholder
for (auto ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end() && miss < 255; ++ihit)
{
if (ihit->MissCondition != SPELL_MISS_NONE) // Add only miss
{
*data << uint64(ihit->TargetGUID);
*data << uint8(ihit->MissCondition);
if (ihit->MissCondition == SPELL_MISS_REFLECT)
*data << uint8(ihit->ReflectResult);
++miss;
}
}
// Reset m_needAliveTargetMask for non channeled spell
if (!m_spellInfo->IsChanneled())
m_channelTargetEffectMask = 0;
data->put<uint8>(hitPos, (uint8)hit);
data->put<uint8>(missPos, (uint8)miss);
}
void Spell::SendLogExecute()

View File

@@ -26,6 +26,16 @@
#include "SharedDefines.h"
#include <memory>
namespace WorldPackets
{
namespace Spells
{
struct SpellTargetData;
struct SpellAmmo;
struct SpellCastData;
}
}
class Aura;
class AuraEffect;
class Corpse;
@@ -125,7 +135,7 @@ class TC_GAME_API SpellCastTargets
~SpellCastTargets();
void Read(ByteBuffer& data, Unit* caster);
void Write(ByteBuffer& data);
void Write(WorldPackets::Spells::SpellTargetData& data);
uint32 GetTargetMask() const { return m_targetMask; }
void SetTargetMask(uint32 newMask) { m_targetMask = newMask; }
@@ -449,8 +459,8 @@ class TC_GAME_API Spell
void setState(uint32 state) { m_spellState = state; }
void DoCreateItem(uint32 i, uint32 itemtype);
void WriteSpellGoTargets(WorldPacket* data);
void WriteAmmoToPacket(WorldPacket* data);
void UpdateSpellCastDataTargets(WorldPackets::Spells::SpellCastData& data);
void UpdateSpellCastDataAmmo(WorldPackets::Spells::SpellAmmo& data);
bool CheckEffectTarget(Unit const* target, uint32 eff, Position const* losPosition) const;
bool CanAutoCast(Unit* target);