From 190af8d3f64aae8d305467a0214453ae74672f72 Mon Sep 17 00:00:00 2001 From: dannuic Date: Wed, 29 Apr 2026 22:14:47 -0600 Subject: [PATCH] Reorganization and PR comments --- common/patches/IBuff.h | 41 +++++-- common/patches/IMessage.h | 3 +- common/patches/client_version.cpp | 38 +++++-- common/patches/client_version.h | 28 +++-- common/patches/rof.cpp | 101 +---------------- common/patches/rof.h | 2 +- common/patches/rof2.cpp | 98 +---------------- common/patches/rof2.h | 2 +- common/patches/rof2_ops.h | 3 - common/patches/rof_ops.h | 3 - common/patches/sod.cpp | 169 ++++++++-------------------- common/patches/sod.h | 5 +- common/patches/sod_ops.h | 2 - common/patches/sof.cpp | 39 +------ common/patches/sof.h | 2 +- common/patches/sof_ops.h | 1 - common/patches/titanium.cpp | 82 +++++++------- common/patches/titanium.h | 9 +- common/patches/titanium_ops.h | 1 - common/patches/tob.cpp | 74 +++++++------ common/patches/tob.h | 27 ++++- common/patches/tob_limits.h | 6 +- common/patches/tob_structs.h | 26 ++++- common/patches/uf.cpp | 176 ++++++----------------------- common/patches/uf.h | 5 +- common/patches/uf_ops.h | 3 - zone/client_version.cpp | 109 ++++++++++++++++++ zone/client_version.h | 177 ++++++++---------------------- 28 files changed, 456 insertions(+), 776 deletions(-) diff --git a/common/patches/IBuff.h b/common/patches/IBuff.h index 0f3beb4ac..b16078ff4 100644 --- a/common/patches/IBuff.h +++ b/common/patches/IBuff.h @@ -1,15 +1,27 @@ -// -// Created by dannu on 4/24/2026. -// +/* EQEmu: EQEmulator + + Copyright (C) 2001-2026 EQEmu Development Team + + 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 3 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 . +*/ #pragma once -#include "client_version.h" #include "common/emu_opcodes.h" #include - -#include "common/types.h" +#include class Client; class Mob; @@ -21,14 +33,27 @@ namespace ClientPatch { class IBuff { public: - IBuff() = default; + using BuffSequenceFunc = std::function(const Client*)>; + + IBuff(uint32_t maxLongBuffs, uint32_t maxShortBuffs) + : m_maxLongBuffs(maxLongBuffs) + , m_maxShortBuffs(maxShortBuffs) + {} + + IBuff() = delete; virtual ~IBuff() = default; - virtual std::unique_ptr BuffDefinition(Mob* mob, const Buffs_Struct& buff, int slot, + virtual std::unique_ptr BuffDefinition(Mob* mob, const Buffs_Struct& buff, uint32_t slot, bool fade) const = 0; virtual std::unique_ptr RefreshBuffs(EmuOpcode opcode, Mob* mob, bool remove, bool buff_timers_suspended, const std::vector& slots) const = 0; virtual void SetRefreshType(std::unique_ptr& packet, Mob* source, Client* target) const = 0; + + uint32_t ServerToPatchBuffSlot(uint32_t slot) const; + +protected: + uint32_t m_maxLongBuffs; + uint32_t m_maxShortBuffs; }; } // namespace Buff diff --git a/common/patches/IMessage.h b/common/patches/IMessage.h index 96723ee9d..948163e69 100644 --- a/common/patches/IMessage.h +++ b/common/patches/IMessage.h @@ -15,9 +15,8 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -#pragma once -#include "client_version.h" +#pragma once // Migration path: replace string_ids.h usage with ID enum values one call site at a time. diff --git a/common/patches/client_version.cpp b/common/patches/client_version.cpp index 0416e05c2..f71700398 100644 --- a/common/patches/client_version.cpp +++ b/common/patches/client_version.cpp @@ -18,13 +18,22 @@ #include "client_version.h" +#include "common/emu_constants.h" + #include "common/patches/titanium.h" +#include "common/patches/titanium_limits.h" #include "common/patches/sof.h" +#include "common/patches/sof_limits.h" #include "common/patches/sod.h" +#include "common/patches/sod_limits.h" #include "common/patches/uf.h" +#include "common/patches/uf_limits.h" #include "common/patches/rof.h" +#include "common/patches/rof_limits.h" #include "common/patches/rof2.h" +#include "common/patches/rof2_limits.h" #include "common/patches/tob.h" +#include "common/patches/tob_limits.h" #include @@ -36,31 +45,31 @@ struct ClientComponents { switch (version) { case Version::TOB: - buffComponent = std::make_unique(); + buffComponent = std::make_unique(TOB::spells::LONG_BUFFS, TOB::spells::SHORT_BUFFS); messageComponent = std::make_unique(); break; case Version::RoF2: - buffComponent = std::make_unique(); + buffComponent = std::make_unique(RoF2::spells::LONG_BUFFS, RoF2::spells::SHORT_BUFFS); messageComponent = std::make_unique(); break; case Version::RoF: - buffComponent = std::make_unique(); + buffComponent = std::make_unique(RoF::spells::LONG_BUFFS, RoF::spells::SHORT_BUFFS); messageComponent = std::make_unique(); break; case Version::UF: - buffComponent = std::make_unique(); + buffComponent = std::make_unique(UF::spells::LONG_BUFFS, UF::spells::SHORT_BUFFS); messageComponent = std::make_unique(); break; case Version::SoD: - buffComponent = std::make_unique(); + buffComponent = std::make_unique(SoD::spells::LONG_BUFFS, SoD::spells::SHORT_BUFFS); messageComponent = std::make_unique(); break; case Version::SoF: - buffComponent = std::make_unique(); + buffComponent = std::make_unique(SoF::spells::LONG_BUFFS, SoF::spells::SHORT_BUFFS); messageComponent = std::make_unique(); break; case Version::Titanium: - buffComponent = std::make_unique(); + buffComponent = std::make_unique(Titanium::spells::LONG_BUFFS, Titanium::spells::SHORT_BUFFS); messageComponent = std::make_unique(); break; default: @@ -69,7 +78,7 @@ struct ClientComponents } const Version version; - std::unique_ptr buffComponent; + std::unique_ptr buffComponent; std::unique_ptr messageComponent; }; @@ -99,3 +108,16 @@ const std::unique_ptr& GetComponent(Version version) { return s_patches.at(static_cast(version)).messageComponent; } + +uint32_t ClientPatch::IBuff::ServerToPatchBuffSlot(uint32_t slot) const +{ + // we're a disc + if (slot >= EQ::spells::LONG_BUFFS + EQ::spells::SHORT_BUFFS) + return slot - EQ::spells::LONG_BUFFS - EQ::spells::SHORT_BUFFS + + m_maxLongBuffs + m_maxShortBuffs; + // we're a song + if (slot >= EQ::spells::LONG_BUFFS) + return slot - EQ::spells::LONG_BUFFS + m_maxLongBuffs; + // we're a normal buff + return slot; // as long as we guard against bad slots server side, we should be fine +} diff --git a/common/patches/client_version.h b/common/patches/client_version.h index 02c809f13..bc84976d6 100644 --- a/common/patches/client_version.h +++ b/common/patches/client_version.h @@ -1,14 +1,26 @@ -// -// Created by dannu on 4/21/2026. -// +/* EQEmu: EQEmulator + + Copyright (C) 2001-2026 EQEmu Development Team + + 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 3 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 . +*/ #pragma once #include "common/emu_versions.h" #include -#include "zone/client.h" - namespace ClientPatch { class IBuff; class IMessage; @@ -24,9 +36,3 @@ const std::unique_ptr& GetComponent(EQ::versions::ClientVers template <> const std::unique_ptr& GetComponent(EQ::versions::ClientVersion version); - -template -static Component* GetClientComponent(const Client* client) -{ - return GetComponent(client->GetClientVersion()).get(); -} diff --git a/common/patches/rof.cpp b/common/patches/rof.cpp index 7667083c7..d5e9f3ca8 100644 --- a/common/patches/rof.cpp +++ b/common/patches/rof.cpp @@ -67,7 +67,6 @@ namespace RoF static inline spells::CastingSlot ServerToRoFCastingSlot(EQ::spells::CastingSlot slot); static inline EQ::spells::CastingSlot RoFToServerCastingSlot(spells::CastingSlot slot); - static inline int ServerToRoFBuffSlot(int index); static inline int RoFToServerBuffSlot(int index); void Register(EQStreamIdentifier &into) @@ -410,8 +409,7 @@ namespace RoF OUT(entityid); OUT(buff.effect_type); OUT(buff.level); - // just so we're 100% sure we get a 1.0f ... - eq->buff.bard_modifier = emu->buff.bard_modifier == 10 ? 1.0f : emu->buff.bard_modifier / 10.0f; + OUT(buff.bard_modifier); OUT(buff.spellid); OUT(buff.duration); OUT(buff.player_id); @@ -420,59 +418,13 @@ namespace RoF OUT(buff.x); OUT(buff.z); // TODO: implement slot_data stuff - eq->slotid = ServerToRoFBuffSlot(emu->slotid); + OUT(slotid); if (emu->bufffade == 1) eq->bufffade = 1; else eq->bufffade = 2; - // Bit of a hack. OP_BuffDefinition appears to add/remove the buff while OP_RefreshBuffs adds/removes the actual buff icon - EQApplicationPacket *outapp = nullptr; - if (eq->bufffade == 1) - { - outapp = new EQApplicationPacket(OP_RefreshBuffs, 29); - outapp->WriteUInt32(emu->entityid); - outapp->WriteUInt32(0); // tic timer - outapp->WriteUInt8(0); // Type of OP_RefreshBuffs packet ? - outapp->WriteUInt16(1); // 1 buff in this packet - outapp->WriteUInt32(eq->slotid); - outapp->WriteUInt32(0xffffffff); // SpellID (0xffff to remove) - outapp->WriteUInt32(0); // Duration - outapp->WriteUInt32(0); // numhits - outapp->WriteUInt8(0); // Caster name - outapp->WriteUInt8(0); // Type - } - FINISH_ENCODE(); - - if (outapp) - dest->FastQueuePacket(&outapp); // Send the OP_RefreshBuffs to remove the buff - } - - ENCODE(OP_RefreshBuffs) - { - SETUP_VAR_ENCODE(BuffIcon_Struct); - - uint32 sz = 12 + (17 * emu->count) + emu->name_lengths; // 17 includes nullterm - __packet->size = sz; - __packet->pBuffer = new unsigned char[sz]; - memset(__packet->pBuffer, 0, sz); - - __packet->WriteUInt32(emu->entity_id); - __packet->WriteUInt32(emu->tic_timer); - __packet->WriteUInt8(emu->all_buffs); // 1 indicates all buffs on the player (0 to add or remove a single buff) - __packet->WriteUInt16(emu->count); - - for (int i = 0; i < emu->count; ++i) - { - __packet->WriteUInt32(emu->type == 0 ? ServerToRoFBuffSlot(emu->entries[i].buff_slot) : emu->entries[i].buff_slot); - __packet->WriteSInt32 (emu->entries[i].spell_id); - __packet->WriteUInt32(emu->entries[i].tics_remaining); - __packet->WriteUInt32(emu->entries[i].num_hits); // Unknown - __packet->WriteString(emu->entries[i].caster); - } - __packet->WriteUInt8(emu->type); // Unknown - FINISH_ENCODE(); } @@ -1858,38 +1810,6 @@ namespace RoF FINISH_ENCODE(); } - ENCODE(OP_RefreshPetBuffs) - { - // The format of the RoF packet is identical to the OP_RefreshBuffs packet. - - SETUP_VAR_ENCODE(PetBuff_Struct); - - uint32 sz = 12 + (17 * emu->buffcount); - __packet->size = sz; - __packet->pBuffer = new unsigned char[sz]; - memset(__packet->pBuffer, 0, sz); - - __packet->WriteUInt32(emu->petid); - __packet->WriteUInt32(0); // PlayerID ? - __packet->WriteUInt8(1); // 1 indicates all buffs on the pet (0 to add or remove a single buff) - __packet->WriteUInt16(emu->buffcount); - - for (uint16 i = 0; i < PET_BUFF_COUNT; ++i) - { - if (emu->spellid[i]) - { - __packet->WriteUInt32(i); - __packet->WriteSInt32 (emu->spellid[i]); - __packet->WriteUInt32(emu->ticsremaining[i]); - __packet->WriteUInt32(0); // numhits - __packet->WriteString(""); - } - } - __packet->WriteUInt8(0); // some sort of type - - FINISH_ENCODE(); - } - ENCODE(OP_PlayerProfile) { EQApplicationPacket *in = *p; @@ -3239,8 +3159,6 @@ namespace RoF FINISH_ENCODE(); } - ENCODE(OP_RefreshTargetBuffs) { ENCODE_FORWARD(OP_RefreshBuffs); } - ENCODE(OP_TaskDescription) { EQApplicationPacket *in = *p; @@ -6263,21 +6181,6 @@ namespace RoF } } - // these should be optimized out for RoF since they should all boil down to return index :P - // but lets leave it here for future proofing - static inline int ServerToRoFBuffSlot(int index) - { - // we're a disc - if (index >= EQ::spells::LONG_BUFFS + EQ::spells::SHORT_BUFFS) - return index - EQ::spells::LONG_BUFFS - EQ::spells::SHORT_BUFFS + - spells::LONG_BUFFS + spells::SHORT_BUFFS; - // we're a song - if (index >= EQ::spells::LONG_BUFFS) - return index - EQ::spells::LONG_BUFFS + spells::LONG_BUFFS; - // we're a normal buff - return index; // as long as we guard against bad slots server side, we should be fine - } - static inline int RoFToServerBuffSlot(int index) { // we're a disc diff --git a/common/patches/rof.h b/common/patches/rof.h index 3201750c4..b6d374935 100644 --- a/common/patches/rof.h +++ b/common/patches/rof.h @@ -17,8 +17,8 @@ */ #pragma once -#include "uf.h" #include "common/struct_strategy.h" +#include "common/patches/uf.h" class EQStreamIdentifier; diff --git a/common/patches/rof2.cpp b/common/patches/rof2.cpp index f6050d40d..daa937133 100644 --- a/common/patches/rof2.cpp +++ b/common/patches/rof2.cpp @@ -69,7 +69,6 @@ namespace RoF2 static inline spells::CastingSlot ServerToRoF2CastingSlot(EQ::spells::CastingSlot slot); static inline EQ::spells::CastingSlot RoF2ToServerCastingSlot(spells::CastingSlot slot); - static inline int ServerToRoF2BuffSlot(int index); static inline int RoF2ToServerBuffSlot(int index); void Register(EQStreamIdentifier &into) @@ -678,59 +677,13 @@ namespace RoF2 OUT(buff.y); OUT(buff.x); OUT(buff.z); - eq->slotid = ServerToRoF2BuffSlot(emu->slotid); + OUT(slotid); // TODO: implement slot_data stuff if (emu->bufffade == 1) eq->bufffade = 1; else eq->bufffade = 2; - // Bit of a hack. OP_BuffDefinition appears to add/remove the buff while OP_RefreshBuffs adds/removes the actual buff icon - EQApplicationPacket *outapp = nullptr; - if (eq->bufffade == 1) - { - outapp = new EQApplicationPacket(OP_RefreshBuffs, 29u); - outapp->WriteUInt32(emu->entityid); - outapp->WriteUInt32(0); // tic timer - outapp->WriteUInt8(0); // Type of OP_RefreshBuffs packet ? - outapp->WriteUInt16(1); // 1 buff in this packet - outapp->WriteUInt32(eq->slotid); - outapp->WriteUInt32(0xffffffff); // SpellID (0xffff to remove) - outapp->WriteUInt32(0); // Duration - outapp->WriteUInt32(0); // numhits - outapp->WriteUInt8(0); // Caster name - outapp->WriteUInt8(0); // Type - } - FINISH_ENCODE(); - - if (outapp) - dest->FastQueuePacket(&outapp); // Send the OP_RefreshBuffs to remove the buff - } - - ENCODE(OP_RefreshBuffs) - { - SETUP_VAR_ENCODE(BuffIcon_Struct); - - uint32 sz = 12 + (17 * emu->count) + emu->name_lengths; // 17 includes nullterm - __packet->size = sz; - __packet->pBuffer = new unsigned char[sz]; - memset(__packet->pBuffer, 0, sz); - - __packet->WriteUInt32(emu->entity_id); - __packet->WriteUInt32(emu->tic_timer); - __packet->WriteUInt8(emu->all_buffs); // 1 indicates all buffs on the player (0 to add or remove a single buff) - __packet->WriteUInt16(emu->count); - - for (int i = 0; i < emu->count; ++i) - { - __packet->WriteUInt32(emu->type == 0 ? ServerToRoF2BuffSlot(emu->entries[i].buff_slot) : emu->entries[i].buff_slot); - __packet->WriteSInt32 (emu->entries[i].spell_id); - __packet->WriteUInt32(emu->entries[i].tics_remaining); - __packet->WriteUInt32(emu->entries[i].num_hits); // Unknown - __packet->WriteString(emu->entries[i].caster); - } - __packet->WriteUInt8(emu->type); // Unknown - FINISH_ENCODE(); } @@ -2464,38 +2417,6 @@ namespace RoF2 FINISH_ENCODE(); } - ENCODE(OP_RefreshPetBuffs) - { - // The format of the RoF2 packet is identical to the OP_RefreshBuffs packet. - - SETUP_VAR_ENCODE(PetBuff_Struct); - - uint32 sz = 12 + (17 * emu->buffcount); - __packet->size = sz; - __packet->pBuffer = new unsigned char[sz]; - memset(__packet->pBuffer, 0, sz); - - __packet->WriteUInt32(emu->petid); - __packet->WriteUInt32(0); // PlayerID ? - __packet->WriteUInt8(1); // 1 indicates all buffs on the pet (0 to add or remove a single buff) - __packet->WriteUInt16(emu->buffcount); - - for (uint16 i = 0; i < PET_BUFF_COUNT; ++i) - { - if (emu->spellid[i]) - { - __packet->WriteUInt32(i); - __packet->WriteSInt32 (emu->spellid[i]); - __packet->WriteUInt32(emu->ticsremaining[i]); - __packet->WriteUInt32(0); // num hits - __packet->WriteString(""); - } - } - __packet->WriteUInt8(0); // some sort of type - - FINISH_ENCODE(); - } - ENCODE(OP_PlayerProfile) { EQApplicationPacket *in = *p; @@ -3841,8 +3762,6 @@ namespace RoF2 FINISH_ENCODE(); } - ENCODE(OP_RefreshTargetBuffs) { ENCODE_FORWARD(OP_RefreshBuffs); } - ENCODE(OP_TaskDescription) { EQApplicationPacket *in = *p; @@ -7495,21 +7414,6 @@ namespace RoF2 } } - // these should be optimized out for RoF2 since they should all boil down to return index :P - // but lets leave it here for future proofing - static inline int ServerToRoF2BuffSlot(int index) - { - // we're a disc - if (index >= EQ::spells::LONG_BUFFS + EQ::spells::SHORT_BUFFS) - return index - EQ::spells::LONG_BUFFS - EQ::spells::SHORT_BUFFS + - spells::LONG_BUFFS + spells::SHORT_BUFFS; - // we're a song - if (index >= EQ::spells::LONG_BUFFS) - return index - EQ::spells::LONG_BUFFS + spells::LONG_BUFFS; - // we're a normal buff - return index; // as long as we guard against bad slots server side, we should be fine - } - static inline int RoF2ToServerBuffSlot(int index) { // we're a disc diff --git a/common/patches/rof2.h b/common/patches/rof2.h index 7386e1394..6f284c551 100644 --- a/common/patches/rof2.h +++ b/common/patches/rof2.h @@ -17,8 +17,8 @@ */ #pragma once -#include "rof.h" #include "common/struct_strategy.h" +#include "common/patches/rof.h" class EQStreamIdentifier; diff --git a/common/patches/rof2_ops.h b/common/patches/rof2_ops.h index e07baf245..4ea866aec 100644 --- a/common/patches/rof2_ops.h +++ b/common/patches/rof2_ops.h @@ -44,7 +44,6 @@ E(OP_BecomeTrader) E(OP_BeginCast) E(OP_BlockedBuffs) E(OP_BuffDefinition) -E(OP_RefreshBuffs) E(OP_BuyerItems) E(OP_CancelTrade) E(OP_CastSpell) @@ -100,7 +99,6 @@ E(OP_MoveItem) E(OP_NewSpawn) E(OP_NewZone) E(OP_OnLevelMessage) -E(OP_RefreshPetBuffs) E(OP_PlayerProfile) E(OP_RaidJoin) E(OP_RaidUpdate) @@ -123,7 +121,6 @@ E(OP_SpawnAppearance) E(OP_SpawnDoor) E(OP_SpecialMesg) E(OP_Stun) -E(OP_RefreshTargetBuffs) E(OP_TaskDescription) E(OP_TaskHistoryReply) E(OP_Track) diff --git a/common/patches/rof_ops.h b/common/patches/rof_ops.h index b931f0a81..ac554b6bd 100644 --- a/common/patches/rof_ops.h +++ b/common/patches/rof_ops.h @@ -28,7 +28,6 @@ E(OP_BazaarSearch) E(OP_BeginCast) E(OP_BlockedBuffs) E(OP_BuffDefinition) -E(OP_RefreshBuffs) E(OP_CancelTrade) E(OP_CastSpell) E(OP_ChannelMessage) @@ -81,7 +80,6 @@ E(OP_MoveItem) E(OP_NewSpawn) E(OP_NewZone) E(OP_OnLevelMessage) -E(OP_RefreshPetBuffs) E(OP_PlayerProfile) E(OP_RaidJoin) E(OP_RaidUpdate) @@ -104,7 +102,6 @@ E(OP_SpawnAppearance) E(OP_SpawnDoor) E(OP_SpecialMesg) E(OP_Stun) -E(OP_RefreshTargetBuffs) E(OP_TaskDescription) E(OP_TaskHistoryReply) E(OP_Track) diff --git a/common/patches/sod.cpp b/common/patches/sod.cpp index fd016bfe9..2ebec80a9 100644 --- a/common/patches/sod.cpp +++ b/common/patches/sod.cpp @@ -64,7 +64,6 @@ namespace SoD static inline spells::CastingSlot ServerToSoDCastingSlot(EQ::spells::CastingSlot slot); static inline EQ::spells::CastingSlot SoDToServerCastingSlot(spells::CastingSlot slot); - static inline int ServerToSoDBuffSlot(int index); static inline int SoDToServerBuffSlot(int index); void Register(EQStreamIdentifier &into) @@ -309,7 +308,7 @@ namespace SoD OUT(buff.duration); OUT(buff.counters); OUT(buff.player_id); - eq->slotid = ServerToSoDBuffSlot(emu->slotid); + OUT(slotid); OUT(bufffade); FINISH_ENCODE(); @@ -1378,38 +1377,6 @@ namespace SoD FINISH_ENCODE(); } - ENCODE(OP_RefreshPetBuffs) - { - EQApplicationPacket *in = *p; - *p = nullptr; - - unsigned char *__emu_buffer = in->pBuffer; - PetBuff_Struct *emu = (PetBuff_Struct *)__emu_buffer; - int PacketSize = 7 + (emu->buffcount * 13); - in->size = PacketSize; - in->pBuffer = new unsigned char[in->size]; - char *Buffer = (char *)in->pBuffer; - - VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->petid); - VARSTRUCT_ENCODE_TYPE(uint16, Buffer, emu->buffcount); - - for (unsigned int i = 0; i < PET_BUFF_COUNT; ++i) - { - if (emu->spellid[i]) - { - VARSTRUCT_ENCODE_TYPE(uint32, Buffer, i); - VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->spellid[i]); - VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->ticsremaining[i]); - VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // This is a string. Name of the caster of the buff. - } - } - - VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->buffcount); // I think this is actually some sort of type - - delete[] __emu_buffer; - dest->FastQueuePacket(&in, ack_req); - } - ENCODE(OP_PlayerProfile) { SETUP_DIRECT_ENCODE(PlayerProfile_Struct, structs::PlayerProfile_Struct); @@ -2148,35 +2115,6 @@ namespace SoD FINISH_ENCODE(); } - ENCODE(OP_RefreshTargetBuffs) - { - SETUP_VAR_ENCODE(BuffIcon_Struct); - - uint32 sz = 7 + (13 * emu->count); - __packet->size = sz; - __packet->pBuffer = new unsigned char[sz]; - memset(__packet->pBuffer, 0, sz); - - uchar *ptr = __packet->pBuffer; - *((uint32*)ptr) = emu->entity_id; - ptr += sizeof(uint32); - - *((uint16*)ptr) = emu->count; - ptr += sizeof(uint16); - - for (uint16 i = 0; i < emu->count; ++i) - { - *((uint32*)ptr) = emu->entries[i].buff_slot; - ptr += sizeof(uint32); - *((uint32*)ptr) = emu->entries[i].spell_id; - ptr += sizeof(uint32); - *((uint32*)ptr) = emu->entries[i].tics_remaining; - ptr += sizeof(uint32); - ptr += 1; - } - FINISH_ENCODE(); - } - ENCODE(OP_TaskDescription) { EQApplicationPacket *in = *p; @@ -4262,19 +4200,6 @@ namespace SoD } } - static inline int ServerToSoDBuffSlot(int index) - { - // we're a disc - if (index >= EQ::spells::LONG_BUFFS + EQ::spells::SHORT_BUFFS) - return index - EQ::spells::LONG_BUFFS - EQ::spells::SHORT_BUFFS + - spells::LONG_BUFFS + spells::SHORT_BUFFS; - // we're a song - if (index >= EQ::spells::LONG_BUFFS) - return index - EQ::spells::LONG_BUFFS + spells::LONG_BUFFS; - // we're a normal buff - return index; // as long as we guard against bad slots server side, we should be fine - } - static inline int SoDToServerBuffSlot(int index) { // we're a disc @@ -4288,71 +4213,65 @@ namespace SoD return index; // as long as we guard against bad slots server side, we should be fine } - std::unique_ptr BuffComponent::RefreshBuffs(EmuOpcode opcode, Mob* mob, - bool remove, + std::unique_ptr BuffComponent::RefreshBuffs(EmuOpcode opcode, Mob* mob, bool remove, bool buff_timers_suspended, const std::vector& slots) const -{ - // SoD only supports target refresh, not self refresh packets - if (opcode == OP_RefreshTargetBuffs) { - uint32 count = 0; - uint32 buff_count; + { + if (opcode == OP_RefreshPetBuffs || opcode == OP_RefreshTargetBuffs) { + Buffs_Struct* buffs = mob->GetBuffs(); - buff_count = mob->GetMaxTotalSlots(); - - Buffs_Struct* buffs = mob->GetBuffs(); - - for(int i = 0; i < buff_count; ++i) { - if (buffs[i].spellid > 1) { - ++count; + size_t buffer_size = 7; // 7 bytes outside the list + std::vector send_slots; + if (slots.empty()) { + for (uint32_t slot = 0; slot < mob->GetMaxTotalSlots(); ++slot) + if (buffs[slot].spellid > 1) { + buffer_size += 13 + strlen(buffs[slot].caster_name); // 13 includes the null terminator + send_slots.push_back(slot); + } + } else { + for (uint32_t slot : slots) + if (slot < mob->GetMaxTotalSlots() && buffs[slot].spellid > 1) { + buffer_size += 13 + strlen(buffs[slot].caster_name); + send_slots.push_back(slot); + } } + + // SoD only supports target and pet refresh, not self refresh packets + SerializeBuffer buffer(buffer_size); + + buffer.WriteUInt32(mob->GetID()); + buffer.WriteUInt16(send_slots.size()); + + for (uint32_t slot : send_slots) { + buffer.WriteUInt32(ServerToPatchBuffSlot(slot)); + buffer.WriteInt32(remove ? -1 : buffs[slot].spellid); + buffer.WriteInt32(buffs[slot].ticsremaining); + buffer.WriteString(buffs[slot].caster_name); + } + + buffer.WriteUInt8(opcode == OP_RefreshPetBuffs ? 2 : 0); + + return std::make_unique(opcode, std::move(buffer)); } - auto outapp = std::make_unique(opcode, - sizeof(BuffIcon_Struct) + sizeof(BuffIconEntry_Struct) * count); - - BuffIcon_Struct *buff = (BuffIcon_Struct*)outapp->pBuffer; - buff->entity_id = mob->GetID(); - buff->count = count; - buff->all_buffs = 1; - buff->tic_timer = mob->GetRemainingTicTime(); - // (see comment in common/eq_packet_structs.h), mutated in SetRefreshType - buff->type = mob->IsClient() ? 5 : 7; - - buff->name_lengths = 0; // hacky shit - uint32 index = 0; - for(int i = 0; i < buff_count; ++i) { - if (buffs[i].spellid > 1) { - buff->entries[index].buff_slot = i; - buff->entries[index].spell_id = buffs[i].spellid; - buff->entries[index].tics_remaining = buffs[i].ticsremaining; - buff->entries[index].num_hits = buffs[i].hit_number; - strn0cpy(buff->entries[index].caster, buffs[i].caster_name, 64); - buff->name_lengths += strlen(buff->entries[index].caster); - ++index; - } - } - - return outapp; + return nullptr; } - return nullptr; -} - // 0 = self buff window, 1 = self target window, 2 = pet buff or target window, 4 = group, 5 = PC, 7 = NPC void BuffComponent::SetRefreshType(std::unique_ptr& packet, Mob* source, Client* target) const { if (packet) { - BuffIcon_Struct *buff = (BuffIcon_Struct*)packet->pBuffer; + unsigned char* type = &packet->pBuffer[packet->size - 1]; + if (target->GetID() == source->GetID()) - buff->type = 1; + *type = 1; else if (target->IsPet()) - buff->type = 2; + *type = 2; else if (target->HasGroup() && source->GetGroup() == target->GetGroup()) - buff->type = 4; + *type = 4; else if (target->IsClient()) - buff->type = 5; + *type = 5; else - buff->type = 7; + *type = 7; } } diff --git a/common/patches/sod.h b/common/patches/sod.h index 711734581..d507c94c4 100644 --- a/common/patches/sod.h +++ b/common/patches/sod.h @@ -17,8 +17,8 @@ */ #pragma once -#include "sof.h" #include "common/struct_strategy.h" +#include "common/patches/sof.h" class EQStreamIdentifier; @@ -44,7 +44,8 @@ protected: class BuffComponent : public Titanium::BuffComponent { public: - BuffComponent() = default; + BuffComponent(uint32_t maxLongBuffs, uint32_t maxShortBuffs) : Titanium::BuffComponent(maxLongBuffs, maxShortBuffs) {} + BuffComponent() = delete; ~BuffComponent() override = default; std::unique_ptr RefreshBuffs(EmuOpcode opcode, Mob* mob, bool remove, diff --git a/common/patches/sod_ops.h b/common/patches/sod_ops.h index 37528836e..cf4f48ab4 100644 --- a/common/patches/sod_ops.h +++ b/common/patches/sod_ops.h @@ -65,7 +65,6 @@ E(OP_MoveItem) E(OP_NewSpawn) E(OP_NewZone) E(OP_OnLevelMessage) -E(OP_RefreshPetBuffs) E(OP_PlayerProfile) E(OP_RaidJoin) E(OP_RaidUpdate) @@ -80,7 +79,6 @@ E(OP_SomeItemPacketMaybe) E(OP_SpawnDoor) E(OP_SpecialMesg) E(OP_Stun) -E(OP_RefreshTargetBuffs) E(OP_TaskDescription) E(OP_Track) E(OP_Trader) diff --git a/common/patches/sof.cpp b/common/patches/sof.cpp index 0d2106e20..519bdda13 100644 --- a/common/patches/sof.cpp +++ b/common/patches/sof.cpp @@ -60,7 +60,6 @@ namespace SoF static inline spells::CastingSlot ServerToSoFCastingSlot(EQ::spells::CastingSlot slot); static inline EQ::spells::CastingSlot SoFToServerCastingSlot(spells::CastingSlot slot, uint32 item_location); - static inline int ServerToSoFBuffSlot(int index); static inline int SoFToServerBuffSlot(int index); void Register(EQStreamIdentifier &into) @@ -285,7 +284,7 @@ namespace SoF OUT(buff.duration); OUT(buff.counters); OUT(buff.player_id); - eq->slotid = ServerToSoFBuffSlot(emu->slotid); + eq->slotid = SoFToServerBuffSlot(emu->slotid); OUT(bufffade); FINISH_ENCODE(); @@ -1049,28 +1048,6 @@ namespace SoF FINISH_ENCODE(); } - ENCODE(OP_RefreshPetBuffs) - { - ENCODE_LENGTH_EXACT(PetBuff_Struct); - SETUP_DIRECT_ENCODE(PetBuff_Struct, PetBuff_Struct); - - OUT(petid); - OUT(buffcount); - - int EQBuffSlot = 0; // do we really want to shuffle them around like this? - - for (uint32 EmuBuffSlot = 0; EmuBuffSlot < PET_BUFF_COUNT; ++EmuBuffSlot) - { - if (emu->spellid[EmuBuffSlot]) - { - eq->spellid[EQBuffSlot] = emu->spellid[EmuBuffSlot]; - eq->ticsremaining[EQBuffSlot++] = emu->ticsremaining[EmuBuffSlot]; - } - } - - FINISH_ENCODE(); - } - ENCODE(OP_PlayerProfile) { SETUP_DIRECT_ENCODE(PlayerProfile_Struct, structs::PlayerProfile_Struct); @@ -2334,7 +2311,7 @@ namespace SoF IN(buff.duration); IN(buff.counters); IN(buff.player_id); - emu->slotid = SoFToServerBuffSlot(eq->slotid); + IN(slotid); IN(bufffade); FINISH_DIRECT_DECODE(); @@ -3657,18 +3634,6 @@ namespace SoF } } - static inline int ServerToSoFBuffSlot(int index) { - // we're a disc - if (index >= EQ::spells::LONG_BUFFS + EQ::spells::SHORT_BUFFS) - return index - EQ::spells::LONG_BUFFS - EQ::spells::SHORT_BUFFS + - spells::LONG_BUFFS + spells::SHORT_BUFFS; - // we're a song - if (index >= EQ::spells::LONG_BUFFS) - return index - EQ::spells::LONG_BUFFS + spells::LONG_BUFFS; - // we're a normal buff - return index; // as long as we guard against bad slots server side, we should be fine - } - static inline int SoFToServerBuffSlot(int index) { // we're a disc diff --git a/common/patches/sof.h b/common/patches/sof.h index aae84d562..9bb0b6be5 100644 --- a/common/patches/sof.h +++ b/common/patches/sof.h @@ -17,8 +17,8 @@ */ #pragma once -#include "titanium.h" #include "common/struct_strategy.h" +#include "common/patches/titanium.h" class EQStreamIdentifier; diff --git a/common/patches/sof_ops.h b/common/patches/sof_ops.h index cd8e4bc67..84704178c 100644 --- a/common/patches/sof_ops.h +++ b/common/patches/sof_ops.h @@ -60,7 +60,6 @@ E(OP_MoveItem) E(OP_NewSpawn) E(OP_NewZone) E(OP_OnLevelMessage) -E(OP_RefreshPetBuffs) E(OP_PlayerProfile) E(OP_RaidJoin) E(OP_RaidUpdate) diff --git a/common/patches/titanium.cpp b/common/patches/titanium.cpp index d97d46a1d..a785cf968 100644 --- a/common/patches/titanium.cpp +++ b/common/patches/titanium.cpp @@ -64,7 +64,6 @@ namespace Titanium static inline spells::CastingSlot ServerToTitaniumCastingSlot(EQ::spells::CastingSlot slot); static inline EQ::spells::CastingSlot TitaniumToServerCastingSlot(spells::CastingSlot slot, uint32 item_location); - static inline int ServerToTitaniumBuffSlot(int index); static inline int TitaniumToServerBuffSlot(int index); void Register(EQStreamIdentifier &into) @@ -341,7 +340,7 @@ namespace Titanium OUT(buff.duration); OUT(buff.counters); OUT(buff.player_id); - eq->slotid = ServerToTitaniumBuffSlot(emu->slotid); + OUT(slotid); OUT(bufffade); FINISH_ENCODE(); @@ -1309,28 +1308,6 @@ namespace Titanium FINISH_ENCODE(); } - ENCODE(OP_RefreshPetBuffs) - { - ENCODE_LENGTH_EXACT(PetBuff_Struct); - SETUP_DIRECT_ENCODE(PetBuff_Struct, PetBuff_Struct); - - OUT(petid); - OUT(buffcount); - - int EQBuffSlot = 0; // do we really want to shuffle them around like this? - - for (uint32 EmuBuffSlot = 0; EmuBuffSlot < PET_BUFF_COUNT; ++EmuBuffSlot) - { - if (emu->spellid[EmuBuffSlot]) - { - eq->spellid[EQBuffSlot] = emu->spellid[EmuBuffSlot]; - eq->ticsremaining[EQBuffSlot++] = emu->ticsremaining[EmuBuffSlot]; - } - } - - FINISH_ENCODE(); - } - ENCODE(OP_PlayerProfile) { SETUP_DIRECT_ENCODE(PlayerProfile_Struct, structs::PlayerProfile_Struct); @@ -3896,19 +3873,6 @@ namespace Titanium } } - static inline int ServerToTitaniumBuffSlot(int index) - { - // we're a disc - if (index >= EQ::spells::LONG_BUFFS + EQ::spells::SHORT_BUFFS) - return index - EQ::spells::LONG_BUFFS - EQ::spells::SHORT_BUFFS + - spells::LONG_BUFFS + spells::SHORT_BUFFS; - // we're a song - if (index >= EQ::spells::LONG_BUFFS) - return index - EQ::spells::LONG_BUFFS + spells::LONG_BUFFS; - // we're a normal buff - return index; // as long as we guard against bad slots server side, we should be fine - } - static inline int TitaniumToServerBuffSlot(int index) { // we're a disc @@ -4014,11 +3978,11 @@ void MessageComponent::ResolveArguments(uint32_t id, std::array& } } -std::unique_ptr BuffComponent::BuffDefinition(Mob* mob, const Buffs_Struct& buff, int slot, +std::unique_ptr BuffComponent::BuffDefinition(Mob* mob, const Buffs_Struct& buff, uint32_t slot, bool fade) const { auto outapp = std::make_unique(OP_BuffDefinition, sizeof(SpellBuffPacket_Struct)); - SpellBuffPacket_Struct* sbf = (SpellBuffPacket_Struct*) outapp->pBuffer; + auto sbf = reinterpret_cast(outapp->pBuffer); sbf->entityid = mob->GetID(); @@ -4042,7 +4006,7 @@ std::unique_ptr BuffComponent::BuffDefinition(Mob* mob, con sbf->buff.x = buff.caston_x; sbf->buff.z = buff.caston_z; - sbf->slotid = slot; + sbf->slotid = ServerToPatchBuffSlot(slot); sbf->bufffade = fade; return outapp; @@ -4051,6 +4015,44 @@ std::unique_ptr BuffComponent::BuffDefinition(Mob* mob, con std::unique_ptr BuffComponent::RefreshBuffs(EmuOpcode opcode, Mob* mob, bool remove, bool buff_timers_suspended, const std::vector& slots) const { + // titanium only sends refresh for pet buffs + if (opcode == OP_RefreshPetBuffs) { + Buffs_Struct* buffs = mob->GetBuffs(); + + std::vector send_slots; + if (slots.empty()) { + for (uint32_t slot = 0; slot < mob->GetMaxTotalSlots(); ++slot) + if (buffs[slot].spellid > 1) + send_slots.push_back(slot); + } else { + for (uint32_t slot : slots) + if (slot < mob->GetMaxTotalSlots() && buffs[slot].spellid > 1) + send_slots.push_back(slot); + } + + auto outapp = std::make_unique(OP_RefreshPetBuffs, sizeof(PetBuff_Struct)); + auto pbs = reinterpret_cast(outapp->pBuffer); + memset(outapp->pBuffer, 0, outapp->size); + + pbs->petid = mob->GetID(); + int MaxSlots = mob->GetMaxTotalSlots(); + if (MaxSlots > PET_BUFF_COUNT) + MaxSlots = PET_BUFF_COUNT; + + int count = 0; + for (uint32_t slot : send_slots) { + if (slot < MaxSlots) { + pbs->spellid[slot] = buffs[slot].spellid; + pbs->ticsremaining[slot] = buffs[slot].ticsremaining; + ++count; + } + } + + pbs->buffcount = count; + + return outapp; + } + return nullptr; } diff --git a/common/patches/titanium.h b/common/patches/titanium.h index bfe5ae269..ae049b172 100644 --- a/common/patches/titanium.h +++ b/common/patches/titanium.h @@ -17,9 +17,9 @@ */ #pragma once -#include "IBuff.h" -#include "IMessage.h" #include "common/struct_strategy.h" +#include "common/patches/IBuff.h" +#include "common/patches/IMessage.h" class EQStreamIdentifier; @@ -66,10 +66,11 @@ protected: class BuffComponent : public ClientPatch::IBuff { public: - BuffComponent() = default; + BuffComponent(uint32_t maxLongBuffs, uint32_t maxShortBuffs) : IBuff(maxLongBuffs, maxShortBuffs) {} + BuffComponent() = delete; ~BuffComponent() override = default; - std::unique_ptr BuffDefinition(Mob* mob, const Buffs_Struct& buff, int slot, + std::unique_ptr BuffDefinition(Mob* mob, const Buffs_Struct& buff, uint32_t slot, bool fade) const override; std::unique_ptr RefreshBuffs(EmuOpcode opcode, Mob* mob, bool remove, bool buff_timers_suspended, const std::vector& slots) const override; diff --git a/common/patches/titanium_ops.h b/common/patches/titanium_ops.h index b212e1df4..96c323402 100644 --- a/common/patches/titanium_ops.h +++ b/common/patches/titanium_ops.h @@ -61,7 +61,6 @@ E(OP_ManaChange) E(OP_MemorizeSpell) E(OP_MoveItem) E(OP_OnLevelMessage) -E(OP_RefreshPetBuffs) E(OP_PlayerProfile) E(OP_NewSpawn) E(OP_MarkRaidNPC) diff --git a/common/patches/tob.cpp b/common/patches/tob.cpp index c64feaa85..b344a8ac9 100644 --- a/common/patches/tob.cpp +++ b/common/patches/tob.cpp @@ -1,22 +1,41 @@ -#include "../global_define.h" -#include "../eqemu_config.h" -#include "../eqemu_logsys.h" +/* EQEmu: EQEmulator + +Copyright (C) 2001-2026 EQEmu Development Team + + 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 3 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 . +*/ + #include "tob.h" -#include "../opcodemgr.h" - -#include "../eq_stream_ident.h" -#include "../crc32.h" - -#include "../eq_packet_structs.h" -#include "../misc_functions.h" -#include "../strings.h" -#include "../inventory_profile.h" #include "tob_structs.h" -#include "../rulesys.h" -#include "../path_manager.h" -#include "../classes.h" -#include "../races.h" -#include "../raid.h" + +#include "common/global_define.h" +#include "common/eqemu_config.h" +#include "common/eqemu_logsys.h" +#include "common/opcodemgr.h" + +#include "common/eq_stream_ident.h" +#include "common/crc32.h" + +#include "common/eq_packet_structs.h" +#include "common/misc_functions.h" +#include "common/strings.h" +#include "common/inventory_profile.h" +#include "common/rulesys.h" +#include "common/path_manager.h" +#include "common/classes.h" +#include "common/races.h" +#include "common/raid.h" #include #include @@ -69,7 +88,6 @@ namespace TOB static inline EQ::spells::CastingSlot TOBToServerCastingSlot(spells::CastingSlot slot); // buff slots - static inline int ServerToTOBBuffSlot(int index); static inline int TOBToServerBuffSlot(int index); void Register(EQStreamIdentifier& into) @@ -5452,20 +5470,6 @@ namespace TOB } } - //TOB has the same # of long buffs as rof2, but 10 more short buffs - static inline int ServerToTOBBuffSlot(int index) - { - // we're a disc - if (index >= EQ::spells::LONG_BUFFS + EQ::spells::SHORT_BUFFS) - return index - EQ::spells::LONG_BUFFS - EQ::spells::SHORT_BUFFS + - spells::LONG_BUFFS + spells::SHORT_BUFFS; - // we're a song - if (index >= EQ::spells::LONG_BUFFS) - return index - EQ::spells::LONG_BUFFS + spells::LONG_BUFFS; - // we're a normal buff - return index; // as long as we guard against bad slots server side, we should be fine - } - static inline int TOBToServerBuffSlot(int index) { // we're a disc @@ -5607,7 +5611,7 @@ std::unique_ptr MessageComponent::InterruptSpellOther(Mob* return outapp; } -std::unique_ptr BuffComponent::BuffDefinition(Mob* mob, const Buffs_Struct& buff, int slot, bool fade) const +std::unique_ptr BuffComponent::BuffDefinition(Mob* mob, const Buffs_Struct& buff, uint32_t slot, bool fade) const { auto packet = std::make_unique(OP_BuffDefinition, sizeof(structs::EQAffectPacket_Struct)); auto affect = reinterpret_cast(packet->pBuffer); @@ -5615,7 +5619,7 @@ std::unique_ptr BuffComponent::BuffDefinition(Mob* mob, con // base packet affect->entity_id = mob->GetID(); affect->unknown004 = 0; - affect->slot_id = ServerToTOBBuffSlot(slot); + affect->slot_id = ServerToPatchBuffSlot(slot); affect->buff_fade = fade ? 1 : 2; // 1 is remove, 2 is modify, 3 is add (only seen 1 and 2 sent) memset(&affect->affect, 0, sizeof(affect->affect)); @@ -5693,7 +5697,7 @@ std::unique_ptr BuffComponent::RefreshBuffs(EmuOpcode opcod buffer.WriteUInt16(send_slots.size()); for (uint32_t slot : send_slots) { - buffer.WriteUInt32(::TOB::ServerToTOBBuffSlot(slot)); // the server stores fewer buffs + buffer.WriteUInt32(ServerToPatchBuffSlot(slot)); // the server stores fewer buffs buffer.WriteInt32(remove ? -1 : buffs[slot].spellid); buffer.WriteUInt32(buffs[slot].ticsremaining); buffer.WriteUInt32(buffs[slot].hit_number); diff --git a/common/patches/tob.h b/common/patches/tob.h index 08c32d276..8f9c511c1 100644 --- a/common/patches/tob.h +++ b/common/patches/tob.h @@ -1,7 +1,25 @@ +/* EQEmu: EQEmulator + +Copyright (C) 2001-2026 EQEmu Development Team + + 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 3 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 . +*/ + #pragma once -#include "rof2.h" -#include "../struct_strategy.h" +#include "common/struct_strategy.h" +#include "common/patches/rof2.h" class EQStreamIdentifier; @@ -46,11 +64,12 @@ protected: class BuffComponent : public UF::BuffComponent { public: - BuffComponent() = default; + BuffComponent(uint32_t maxLongBuffs, uint32_t maxShortBuffs) : UF::BuffComponent(maxLongBuffs, maxShortBuffs) {} + BuffComponent() = delete; ~BuffComponent() override = default; std::unique_ptr - BuffDefinition(Mob* mob, const Buffs_Struct& buff, int slot, bool fade) const override; + BuffDefinition(Mob* mob, const Buffs_Struct& buff, uint32_t slot, bool fade) const override; std::unique_ptr RefreshBuffs(EmuOpcode opcode, Mob* mob, bool remove, bool buff_timers_suspended, const std::vector& slots) const override; void SetRefreshType(std::unique_ptr& packet, Mob* source, Client* target) const override; diff --git a/common/patches/tob_limits.h b/common/patches/tob_limits.h index beb5e7b90..7988dd9b0 100644 --- a/common/patches/tob_limits.h +++ b/common/patches/tob_limits.h @@ -1,9 +1,9 @@ #ifndef COMMON_LAURION_LIMITS_H #define COMMON_LAURION_LIMITS_H -#include "../types.h" -#include "../emu_versions.h" -#include "../skills.h" +#include "common/types.h" +#include "common/emu_versions.h" +#include "common/skills.h" namespace TOB { diff --git a/common/patches/tob_structs.h b/common/patches/tob_structs.h index ec2842967..04a066ad5 100644 --- a/common/patches/tob_structs.h +++ b/common/patches/tob_structs.h @@ -1,5 +1,25 @@ -#ifndef STEAM_LATEST_STRUCTS_H_ -#define STEAM_LATEST_STRUCTS_H_ +/* EQEmu: EQEmulator + +Copyright (C) 2001-2026 EQEmu Development Team + + 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 3 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 . +*/ + +#pragma once + +#include "common/eq_packet_structs.h" +#include "common/skills.h" namespace TOB { namespace structs { @@ -1086,5 +1106,3 @@ namespace TOB { }; //end namespace structs }; //end namespace tob - -#endif /*LAURION_STRUCTS_H_*/ \ No newline at end of file diff --git a/common/patches/uf.cpp b/common/patches/uf.cpp index 4c4c99462..b98b691b7 100644 --- a/common/patches/uf.cpp +++ b/common/patches/uf.cpp @@ -66,7 +66,6 @@ namespace UF static inline spells::CastingSlot ServerToUFCastingSlot(EQ::spells::CastingSlot slot); static inline EQ::spells::CastingSlot UFToServerCastingSlot(spells::CastingSlot slot); - static inline int ServerToUFBuffSlot(int index); static inline int UFToServerBuffSlot(int index); void Register(EQStreamIdentifier &into) @@ -444,72 +443,17 @@ namespace UF OUT(entityid); OUT(buff.effect_type); OUT(buff.level); - // just so we're 100% sure we get a 1.0f ... - eq->buff.bard_modifier = emu->buff.bard_modifier == 10 ? 1.0f : emu->buff.bard_modifier / 10.0f; + OUT(buff.bard_modifier); OUT(buff.spellid); OUT(buff.duration); OUT(buff.num_hits); // TODO: implement slot_data stuff - eq->slotid = ServerToUFBuffSlot(emu->slotid); + OUT(slotid); OUT(bufffade); // Live (October 2011) sends a 2 rather than 0 when a buff is created, but it doesn't seem to matter. FINISH_ENCODE(); } - ENCODE(OP_RefreshBuffs) - { - SETUP_VAR_ENCODE(BuffIcon_Struct); - - uint32 sz = 12 + (17 * emu->count) + emu->name_lengths; // 17 includes nullterm - __packet->size = sz; - __packet->pBuffer = new unsigned char[sz]; - memset(__packet->pBuffer, 0, sz); - - __packet->WriteUInt32(emu->entity_id); - __packet->WriteUInt32(emu->tic_timer); - __packet->WriteUInt8(emu->all_buffs); // 1 = all buffs, 0 = 1 buff - __packet->WriteUInt16(emu->count); - - for (int i = 0; i < emu->count; ++i) - { - __packet->WriteUInt32(emu->type == 0 ? ServerToUFBuffSlot(emu->entries[i].buff_slot) : emu->entries[i].buff_slot); - __packet->WriteSInt32 (emu->entries[i].spell_id); - __packet->WriteUInt32(emu->entries[i].tics_remaining); - __packet->WriteUInt32(emu->entries[i].num_hits); - __packet->WriteString(emu->entries[i].caster); - } - __packet->WriteUInt8(emu->type); - - FINISH_ENCODE(); - /* - uint32 write_var32 = 60; - uint8 write_var8 = 1; - ss.write((const char*)&emu->entity_id, sizeof(uint32)); - ss.write((const char*)&write_var32, sizeof(uint32)); - ss.write((const char*)&write_var8, sizeof(uint8)); - ss.write((const char*)&emu->count, sizeof(uint16)); - write_var32 = 0; - write_var8 = 0; - for(uint16 i = 0; i < emu->count; ++i) - { - if(emu->entries[i].buff_slot >= 25 && emu->entries[i].buff_slot < 37) - { - emu->entries[i].buff_slot += 5; - } - else if(emu->entries[i].buff_slot >= 37) - { - emu->entries[i].buff_slot += 14; - } - ss.write((const char*)&emu->entries[i].buff_slot, sizeof(uint32)); - ss.write((const char*)&emu->entries[i].spell_id, sizeof(uint32)); - ss.write((const char*)&emu->entries[i].tics_remaining, sizeof(uint32)); - ss.write((const char*)&write_var32, sizeof(uint32)); - ss.write((const char*)&write_var8, sizeof(uint8)); - } - ss.write((const char*)&write_var8, sizeof(uint8)); - */ - } - ENCODE(OP_CancelTrade) { ENCODE_LENGTH_EXACT(CancelTrade_Struct); @@ -1802,44 +1746,6 @@ namespace UF FINISH_ENCODE(); } - ENCODE(OP_RefreshPetBuffs) - { - EQApplicationPacket *in = *p; - *p = nullptr; - - unsigned char *__emu_buffer = in->pBuffer; - - PetBuff_Struct *emu = (PetBuff_Struct *)__emu_buffer; - - int PacketSize = 12 + (emu->buffcount * 17); - - in->size = PacketSize; - in->pBuffer = new unsigned char[in->size]; - - char *Buffer = (char *)in->pBuffer; - - VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->petid); - VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); - VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 1); - VARSTRUCT_ENCODE_TYPE(uint16, Buffer, emu->buffcount); - - for (unsigned int i = 0; i < PET_BUFF_COUNT; ++i) - { - if (emu->spellid[i]) - { - VARSTRUCT_ENCODE_TYPE(uint32, Buffer, i); - VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->spellid[i]); - VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->ticsremaining[i]); - VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // numhits - VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // This is a string. Name of the caster of the buff. - } - } - VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->buffcount); /// I think this is actually some sort of type - - delete[] __emu_buffer; - dest->FastQueuePacket(&in, ack_req); - } - ENCODE(OP_PlayerProfile) { SETUP_DIRECT_ENCODE(PlayerProfile_Struct, structs::PlayerProfile_Struct); @@ -2731,8 +2637,6 @@ namespace UF FINISH_ENCODE(); } - ENCODE(OP_RefreshTargetBuffs) { ENCODE_FORWARD(OP_RefreshBuffs); } - ENCODE(OP_TaskDescription) { EQApplicationPacket *in = *p; @@ -5210,19 +5114,6 @@ namespace UF } } - static inline int ServerToUFBuffSlot(int index) - { - // we're a disc - if (index >= EQ::spells::LONG_BUFFS + EQ::spells::SHORT_BUFFS) - return index - EQ::spells::LONG_BUFFS - EQ::spells::SHORT_BUFFS + - spells::LONG_BUFFS + spells::SHORT_BUFFS; - // we're a song - if (index >= EQ::spells::LONG_BUFFS) - return index - EQ::spells::LONG_BUFFS + spells::LONG_BUFFS; - // we're a normal buff - return index; // as long as we guard against bad slots server side, we should be fine - } - static inline int UFToServerBuffSlot(int index) { // we're a disc @@ -5240,48 +5131,43 @@ namespace UF bool remove, bool buff_timers_suspended, const std::vector& slots) const { - // UF introduced the self update buff packet - - uint32 count = 0; - uint32 buff_count; - - buff_count = mob->GetMaxTotalSlots(); - + // UF introduced the self refresh buff packet Buffs_Struct* buffs = mob->GetBuffs(); - for(int i = 0; i < buff_count; ++i) { - if (buffs[i].spellid > 1) { - ++count; - } + size_t buffer_size = 12; // 12 bytes outside the list + std::vector send_slots; + if (slots.empty()) { + for (uint32_t slot = 0; slot < mob->GetMaxTotalSlots(); ++slot) + if (buffs[slot].spellid > 1) { + buffer_size += 17 + strlen(buffs[slot].caster_name); // 17 includes the null terminator + send_slots.push_back(slot); + } + } else { + for (uint32_t slot : slots) + if (slot < mob->GetMaxTotalSlots() && buffs[slot].spellid > 1) { + buffer_size += 17 + strlen(buffs[slot].caster_name); + send_slots.push_back(slot); + } } - //Create it for a targeting window, else create it for a create buff packet. - auto outapp = std::make_unique(opcode, - sizeof(BuffIcon_Struct) + sizeof(BuffIconEntry_Struct) * count); + SerializeBuffer buffer(buffer_size); - BuffIcon_Struct *buff = (BuffIcon_Struct*)outapp->pBuffer; - buff->entity_id = mob->GetID(); - buff->count = count; - buff->all_buffs = 1; - buff->tic_timer = mob->GetRemainingTicTime(); - // (see comment in common/eq_packet_structs.h) - buff->type = mob->IsClient() ? 5 : 7; + buffer.WriteUInt32(mob->GetID()); + buffer.WriteUInt32(mob->GetRemainingTicTime()); + buffer.WriteUInt8(slots.empty() ? 1 : 0); + buffer.WriteUInt16(send_slots.size()); - buff->name_lengths = 0; // hacky shit - uint32 index = 0; - for(int i = 0; i < buff_count; ++i) { - if (buffs[i].spellid > 1) { - buff->entries[index].buff_slot = i; - buff->entries[index].spell_id = buffs[i].spellid; - buff->entries[index].tics_remaining = buffs[i].ticsremaining; - buff->entries[index].num_hits = buffs[i].hit_number; - strn0cpy(buff->entries[index].caster, buffs[i].caster_name, 64); - buff->name_lengths += strlen(buff->entries[index].caster); - ++index; - } + for (uint32_t slot : send_slots) { + buffer.WriteUInt32(ServerToPatchBuffSlot(slot)); + buffer.WriteInt32(remove ? -1 : buffs[slot].spellid); + buffer.WriteInt32(buffs[slot].ticsremaining); + buffer.WriteUInt32(buffs[slot].hit_number); + buffer.WriteString(buffs[slot].caster_name); } - return outapp; + buffer.WriteUInt8(opcode == OP_RefreshPetBuffs ? 2 : 0); + + return std::make_unique(opcode, std::move(buffer)); } } /*UF*/ diff --git a/common/patches/uf.h b/common/patches/uf.h index 607d589ba..9191226ee 100644 --- a/common/patches/uf.h +++ b/common/patches/uf.h @@ -17,8 +17,8 @@ */ #pragma once -#include "sod.h" #include "common/struct_strategy.h" +#include "common/patches/sod.h" class EQStreamIdentifier; @@ -44,7 +44,8 @@ protected: class BuffComponent : public SoD::BuffComponent { public: - BuffComponent() = default; + BuffComponent(uint32_t maxLongBuffs, uint32_t maxShortBuffs) : SoD::BuffComponent(maxLongBuffs, maxShortBuffs) {} + BuffComponent() = delete; ~BuffComponent() override = default; std::unique_ptr RefreshBuffs(EmuOpcode opcode, Mob* mob, bool remove, diff --git a/common/patches/uf_ops.h b/common/patches/uf_ops.h index a92746534..94ef66b25 100644 --- a/common/patches/uf_ops.h +++ b/common/patches/uf_ops.h @@ -26,7 +26,6 @@ E(OP_Barter) E(OP_BazaarSearch) E(OP_BecomeTrader) E(OP_BuffDefinition) -E(OP_RefreshBuffs) E(OP_CancelTrade) E(OP_ChannelMessage) E(OP_CharInventory) @@ -75,7 +74,6 @@ E(OP_MoveItem) E(OP_NewSpawn) E(OP_NewZone) E(OP_OnLevelMessage) -E(OP_RefreshPetBuffs) E(OP_PlayerProfile) E(OP_RaidJoin) E(OP_RaidUpdate) @@ -93,7 +91,6 @@ E(OP_SpawnAppearance) E(OP_SpawnDoor) E(OP_SpecialMesg) E(OP_Stun) -E(OP_RefreshTargetBuffs) E(OP_TaskDescription) E(OP_Track) E(OP_Trader) diff --git a/zone/client_version.cpp b/zone/client_version.cpp index 1efc15835..d8c8db6d7 100644 --- a/zone/client_version.cpp +++ b/zone/client_version.cpp @@ -27,3 +27,112 @@ void Client::SetClientVersion(Version client_version) } Version Client::GetClientVersion() const { return m_ClientVersion; } + +void ClientPatch::InterruptSpell(Client* c, uint32_t message, uint32_t spawn_id, const char* spell_link) { + QueuePacket(c, &IMessage::InterruptSpell, GetClientComponent(c), message, spawn_id, spell_link); +} + +void ClientPatch::InterruptSpellOther(Mob* sender, uint32_t message, uint32_t spawn_id, const char* name, + const char* spell_link) { + QueueCloseClients(sender, true, RuleI(Range, SongMessages), nullptr, true, + sender->IsClient() ? FilterPCSpells : FilterNPCSpells)( + &IMessage::InterruptSpellOther, GetClientComponent, sender, message, spawn_id, name, spell_link); +} + +static bool ShouldSendTargetBuffs(Client* c) { + // this function checks for server rules against LAA and GM status to determine if a buffs packet should be sent + // to a client (c) for targeted mobs + if (c->GetGM() || RuleB(Spells, AlwaysSendTargetsBuffs)) { // this rule bypasses LAA abilities, always return true + if (c->GetGM()) { + if (!c->EntityVariableExists(SEE_BUFFS_FLAG)) { // This flag just ensures that the following message is only sent once + c->Message(Chat::White, + "Your GM flag allows you to always see your targets' buffs."); + c->SetEntityVariable(SEE_BUFFS_FLAG, "1"); + } + } + return true; + } + + if (c->IsRaidGrouped()) { + Raid* raid = c->GetRaid(); + if (raid) { + uint32 gid = raid->GetGroup(c); + if (gid < MAX_RAID_GROUPS && raid->GroupCount(gid) >= 3) { + if (raid->GetLeadershipAA(groupAAInspectBuffs, gid)) + return true; + } + } + } else { + Group* group = c->GetGroup(); + if (group && group->GroupCount() >= 3) { + if (group->GetLeadershipAA(groupAAInspectBuffs)) { + return true; + } + } + } + + return false; +} + +void ClientPatch::SendFullBuffRefresh(Mob* sender, bool remove, bool ackreq) { + bool suspended = zone->BuffTimersSuspended(); + std::vector slots; + + // first, send to self if self is client + if (sender->IsClient()) { + Client* c = sender->CastToClient(); + FastQueuePacket(c, &IBuff::RefreshBuffs, GetClientComponent(c), OP_RefreshBuffs, sender, false, suspended, slots); + } + + // next, send to owner if self is a pet to a client + if (sender->IsPet() && sender->GetOwner()->IsClient()) { + if (Mob* owner = sender->GetOwner(); owner != nullptr && owner->IsClient()) { + Client* c = owner->CastToClient(); + FastQueuePacket(c, &IBuff::RefreshBuffs, GetClientComponent(c), OP_RefreshPetBuffs, sender, false, suspended, slots); + } + } + + // finally send to all clients targeting the mob, will need to mutate the packet to set the type + auto mutate = [sender](std::unique_ptr& packet, Client* c) { + GetClientComponent(c)->SetRefreshType(packet, sender, c); + }; + + QueueClientsByTarget(sender, ackreq, ShouldSendTargetBuffs, mutate)( + &IBuff::RefreshBuffs, GetClientComponent, OP_RefreshTargetBuffs, sender, false, suspended, slots); + + // if we have remove set, this will clear any target windows that shouldn't see the buffs + if (remove) + QueueClientsByTarget(sender, ackreq, [](Client* c) { return !ShouldSendTargetBuffs(c); }, mutate)( + &IBuff::RefreshBuffs, GetClientComponent, OP_RefreshTargetBuffs, sender, true, suspended, slots); +} + +void ClientPatch::SendSingleBuffChange(Mob* sender, const Buffs_Struct& buff, int slot, bool remove, bool ackreq) { + bool suspended = zone->BuffTimersSuspended(); + std::vector slots = { static_cast(slot) }; + + // first, send to self if self is client, which takes the definition and the refresh + if (sender->IsClient()) { + Client* c = sender->CastToClient(); + FastQueuePacket(c, &IBuff::BuffDefinition, GetClientComponent(c), sender, buff, slot, remove); + FastQueuePacket(c, &IBuff::RefreshBuffs, GetClientComponent(c), OP_RefreshBuffs, sender, remove, suspended, slots); + } + + // the rest of the buff packets do not take the definition, only the refresh + if (sender->IsPet() && sender->GetOwner()->IsClient()) { + if (Mob* owner = sender->GetOwner(); owner != nullptr && owner->IsClient()) { + Client* c = owner->CastToClient(); + FastQueuePacket(c, &IBuff::RefreshBuffs, GetClientComponent(c), OP_RefreshPetBuffs, sender, remove, suspended, slots); + } + } + + auto mutate = [sender](std::unique_ptr& packet, Client* c) { + GetClientComponent(c)->SetRefreshType(packet, sender, c); + }; + + QueueClientsByTarget(sender, ackreq, ShouldSendTargetBuffs, mutate)( + &IBuff::RefreshBuffs, GetClientComponent, OP_RefreshTargetBuffs, sender, remove, suspended, slots); + + // the client doesn't automatically do this for some reason, only send it to the sender (TOB doesn't actually need this, but it doesn't double show the message) + if (remove && sender->IsClient()) + sender->CastToClient()->SendColoredText(Chat::Spells, spells[buff.spellid].spell_fades); +} diff --git a/zone/client_version.h b/zone/client_version.h index f7ae8698f..709fc6c2b 100644 --- a/zone/client_version.h +++ b/zone/client_version.h @@ -1,9 +1,24 @@ -// -// Created by dannu on 4/21/2026. -// +/* EQEmu: EQEmulator + + Copyright (C) 2001-2026 EQEmu Development Team + + 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 3 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 . +*/ #pragma once +#include #include "common/emu_versions.h" #include "common/patches/client_version.h" @@ -17,13 +32,19 @@ namespace ClientPatch { using ClientList = std::unordered_map; -template using ComponentGetter = Obj*(*)(const Client*); //std::function; +template using ComponentGetter = Obj*(*)(const Client*); using SendPredicate = std::function; using MutatePacket = std::function&, Client*)>; +template +static Component* GetClientComponent(const Client* client) +{ + return GetComponent(client->GetClientVersion()).get(); +} + template requires std::is_member_function_pointer_v -static void QueuePacket(Client* c, Fun fun, Obj* obj, Args&&... args) +void QueuePacket(Client* c, Fun fun, Obj* obj, Args&&... args) { if (obj != nullptr) { std::unique_ptr app = std::invoke(fun, obj, std::forward(args)...); @@ -33,14 +54,14 @@ static void QueuePacket(Client* c, Fun fun, Obj* obj, Args&&... args) } // packet generator queue functions -static auto QueueClients(Mob* sender, bool ignore_sender = false, bool ackreq = true) +inline auto QueueClients(Mob* sender, bool ignore_sender = false, bool ackreq = true) { return [=](Fun fun, ComponentGetter component, Args&&... args) requires std::is_member_function_pointer_v { std::array, EQ::versions::ClientVersionCount> build_packets; - for (auto [_, ent] : entity_list.GetClientList()) { + for (auto ent : entity_list.GetClientList() | std::views::values) { if (!ignore_sender || ent != sender) { auto& packet = build_packets.at(static_cast(ent->ClientVersion())); if (!packet) @@ -54,7 +75,7 @@ static auto QueueClients(Mob* sender, bool ignore_sender = false, bool ackreq = }; } -static auto QueueCloseClients( +inline auto QueueCloseClients( Mob* sender, bool ignore_sender = false, float distance = 200, Mob* skipped_mob = nullptr, bool is_ack_required = true, eqFilterType filter = FilterNone) @@ -70,14 +91,14 @@ static auto QueueCloseClients( float distance_squared = distance * distance; std::array, EQ::versions::ClientVersionCount> build_packets; - for (auto& [_, mob] : sender->GetCloseMobList(distance)) { + for (auto mob : sender->GetCloseMobList(distance) | std::views::values) { if (mob && mob->IsClient()) { Client* client = mob->CastToClient(); if ((!ignore_sender || client != sender) && client != skipped_mob - && DistanceSquared(client->GetPosition(), sender->GetPosition()) < distance_squared && client->Connected() - && client->ShouldGetPacket(sender, filter)) + && client->ShouldGetPacket(sender, filter) + && DistanceSquared(client->GetPosition(), sender->GetPosition()) < distance_squared) { auto& packet = build_packets.at(static_cast(client->ClientVersion())); if (!packet) @@ -94,7 +115,7 @@ static auto QueueCloseClients( } template -static void FastQueuePacket(Client* c, Fun fun, Obj* obj, Args&&... args) +void FastQueuePacket(Client* c, Fun fun, Obj* obj, Args&&... args) requires std::is_member_function_pointer_v { if (obj != nullptr) { @@ -107,7 +128,7 @@ static void FastQueuePacket(Client* c, Fun fun, Obj* obj, Args&&... args) } } -static auto QueueClientsByTarget(Mob* sender, bool ackreq, bool HoTT, const SendPredicate& should_send, const MutatePacket& mutate) +inline auto QueueClientsByTarget(Mob* sender, bool ackreq, const SendPredicate& should_send, const MutatePacket& mutate) { return [=](Fun fun, ComponentGetter component, Args&&... args) requires std::is_member_function_pointer_v @@ -115,10 +136,9 @@ static auto QueueClientsByTarget(Mob* sender, bool ackreq, bool HoTT, const Send if (sender != nullptr) { std::array, EQ::versions::ClientVersionCount> build_packets; - for (auto [_, c] : entity_list.GetClientList()) { + for (auto c : entity_list.GetClientList() | std::views::values) { Mob* Target = c->GetTarget(); - if ((Target == sender || (HoTT && Target != nullptr && Target->GetTarget() == sender)) && - (Target == c || should_send(c))) { + if (Target == sender && should_send(c)) { auto& packet = build_packets.at(static_cast(c->ClientVersion())); if (!packet) if (auto comp = component(c); comp != nullptr) @@ -128,7 +148,7 @@ static auto QueueClientsByTarget(Mob* sender, bool ackreq, bool HoTT, const Send if (packet) c->QueuePacket(packet.get(), ackreq, Client::CLIENT_CONNECTED); - } + } } } }; @@ -147,7 +167,7 @@ void MessageString(Client* c, uint32_t type, uint32_t id, Args&&... args) } } -static auto CloseMessageString( +inline auto CloseMessageString( Mob* sender, bool ignore_sender = false, float distance = 200.f, Mob* skipped_mob = nullptr, bool is_ack_required = true, eqFilterType filter = FilterNone) @@ -167,122 +187,11 @@ static auto CloseMessageString( }; } -inline void InterruptSpell(Client* c, uint32_t message, uint32_t spawn_id, const char* spell_link) -{ - QueuePacket(c, &IMessage::InterruptSpell, GetClientComponent(c), message, spawn_id, spell_link); -} +void InterruptSpell(Client* c, uint32_t message, uint32_t spawn_id, const char* spell_link); +void InterruptSpellOther(Mob* sender, uint32_t message, uint32_t spawn_id, const char* name, + const char* spell_link); -inline void InterruptSpellOther(Mob* sender, uint32_t message, uint32_t spawn_id, const char* name, - const char* spell_link) -{ - QueueCloseClients(sender, true, RuleI(Range, SongMessages), nullptr, true, - sender->IsClient() ? FilterPCSpells : FilterNPCSpells)( - &IMessage::InterruptSpellOther, GetClientComponent, sender, message, spawn_id, name, spell_link); -} - -static bool ShouldSendTargetBuffs(Client* c) -{ - // this function checks for server rules against LAA and GM status to determine if a buffs packet should be sent - // to a client (c) for targeted mobs - if (c->GetGM() || RuleB(Spells, AlwaysSendTargetsBuffs)) { // this rule bypasses LAA abilities, always return true - if (c->GetGM()) { - if (!c->EntityVariableExists(SEE_BUFFS_FLAG)) { // This flag just ensures that the following message is only sent once - c->Message(Chat::White, - "Your GM flag allows you to always see your targets' buffs."); - c->SetEntityVariable(SEE_BUFFS_FLAG, "1"); - } - } - return true; - } - - if (c->IsRaidGrouped()) { - Raid* raid = c->GetRaid(); - if (raid) { - uint32 gid = raid->GetGroup(c); - if (gid < MAX_RAID_GROUPS && raid->GroupCount(gid) >= 3) { - if (raid->GetLeadershipAA(groupAAInspectBuffs, gid)) - return true; - } - } - } else { - Group* group = c->GetGroup(); - if (group && group->GroupCount() >= 3) { - if (group->GetLeadershipAA(groupAAInspectBuffs)) { - return true; - } - } - } - - return false; -} - -inline void SendFullBuffRefresh(Mob* sender, bool remove = false, bool ackreq = true) -{ - bool suspended = zone->BuffTimersSuspended(); - std::vector slots; - - // first, send to self if self is client - if (sender->IsClient()) { - Client* c = sender->CastToClient(); - FastQueuePacket(c, &IBuff::RefreshBuffs, GetClientComponent(c), OP_RefreshBuffs, sender, false, suspended, slots); - } - - // next, send to owner if self is a pet to a client - if (sender->IsPet() && sender->GetOwner()->IsClient()) { - if (Mob* owner = sender->GetOwner(); owner != nullptr && owner->IsClient()) { - Client* c = owner->CastToClient(); - FastQueuePacket(c, &IBuff::RefreshBuffs, GetClientComponent(c), OP_RefreshPetBuffs, sender, false, suspended, slots); - } - } - - // finally send to all clients targeting the mob, will need to mutate the packet to set the type - auto mutate = [sender](std::unique_ptr& packet, Client* c) { - GetClientComponent(c)->SetRefreshType(packet, sender, c); - }; - - QueueClientsByTarget(sender, ackreq, false, ShouldSendTargetBuffs, mutate)( - &IBuff::RefreshBuffs, GetClientComponent, OP_RefreshTargetBuffs, sender, false, suspended, slots); - - // if we have remove set, this will clear any target windows that shouldn't see the buffs - if (remove) - QueueClientsByTarget(sender, ackreq, true, - [](Client* c) { return !ShouldSendTargetBuffs(c); }, mutate)( - &IBuff::RefreshBuffs, GetClientComponent, OP_RefreshTargetBuffs, sender, true, suspended, slots); -} - -inline void SendSingleBuffChange(Mob* sender, const Buffs_Struct& buff, int slot, bool remove = false, bool ackreq = true) -{ - bool suspended = zone->BuffTimersSuspended(); - std::vector slots = { static_cast(slot) }; - - // first, send to self if self is client, which takes the definition and the refresh - if (sender->IsClient()) { - Client* c = sender->CastToClient(); - // FastQueuePacket(c, &IBuff::BuffDefinition, GetClientComponent(c), sender, buff, slot, false); - // FastQueuePacket(c, &IBuff::RefreshBuffs, GetClientComponent(c), OP_RefreshBuffs, sender, remove, suspended, slots); - // FastQueuePacket(c, &IBuff::BuffDefinition, GetClientComponent(c), sender, buff, slot, remove); - // FastQueuePacket(c, &IBuff::RefreshBuffs, GetClientComponent(c), OP_RefreshBuffs, sender, remove, suspended, slots); - FastQueuePacket(c, &IBuff::RefreshBuffs, GetClientComponent(c), OP_RefreshBuffs, sender, remove, suspended, slots); - } - - // the rest of the buff packets do not take the definition, only the refresh - if (sender->IsPet() && sender->GetOwner()->IsClient()) { - if (Mob* owner = sender->GetOwner(); owner != nullptr && owner->IsClient()) { - Client* c = owner->CastToClient(); - FastQueuePacket(c, &IBuff::RefreshBuffs, GetClientComponent(c), OP_RefreshPetBuffs, sender, remove, suspended, slots); - } - } - - auto mutate = [sender](std::unique_ptr& packet, Client* c) { - GetClientComponent(c)->SetRefreshType(packet, sender, c); - }; - - QueueClientsByTarget(sender, ackreq, false, ShouldSendTargetBuffs, mutate)( - &IBuff::RefreshBuffs, GetClientComponent, OP_RefreshTargetBuffs, sender, remove, suspended, slots); - - // the client doesn't automatically do this for some reason, only send it to the sender - // if (remove && sender->IsClient()) - // sender->CastToClient()->SendColoredText(Chat::Spells, spells[buff.spellid].spell_fades); -} +void SendFullBuffRefresh(Mob* sender, bool remove = false, bool ackreq = true); +void SendSingleBuffChange(Mob* sender, const Buffs_Struct& buff, int slot, bool remove = false, bool ackreq = true); } // namespace ClientPatch