Adding buff infrastructure to handle legacy and modern buff semantics

This commit is contained in:
dannuic
2026-04-25 00:24:23 -06:00
parent c5d048ad5b
commit bcf65c141d
66 changed files with 641 additions and 291 deletions
+34
View File
@@ -0,0 +1,34 @@
//
// Created by dannu on 4/24/2026.
//
#pragma once
#include "common/emu_opcodes.h"
#include <cstdint>
#include <vector>
#include "common/types.h"
class Client;
class Mob;
class EQApplicationPacket;
class Buffs_Struct;
namespace Buff {
class IBuff
{
public:
IBuff() = default;
virtual ~IBuff() = default;
virtual EQApplicationPacket* MakeLegacyBuffsPacket(Mob* mob, int32_t timer, bool for_target, bool clear_buffs) const = 0;
virtual EQApplicationPacket* BuffDefinition(Mob* mob, const Buffs_Struct& buff, int slot, bool fade) const = 0;
virtual EQApplicationPacket* RefreshBuffs(EmuOpcode opcode, Mob* mob, int32_t timer, bool remove, bool buff_timers_suspended, const std::vector<uint32_t>& slots) const = 0;
virtual void SetRefreshType(EQApplicationPacket* packet, Mob* source, Client* target) const = 0;
};
} // namespace Buff
+13
View File
@@ -34,30 +34,38 @@ struct ClientComponents
{
switch (version) {
case Version::TOB:
buffComponent = std::make_shared<Buff::TOB>();
messageComponent = std::make_shared<Message::TOB>();
break;
case Version::RoF2:
buffComponent = std::make_shared<Buff::RoF2>();
messageComponent = std::make_shared<Message::RoF2>();
break;
case Version::RoF:
buffComponent = std::make_shared<Buff::RoF>();
messageComponent = std::make_shared<Message::RoF>();
break;
case Version::UF:
buffComponent = std::make_shared<Buff::UF>();
messageComponent = std::make_shared<Message::UF>();
break;
case Version::SoD:
buffComponent = std::make_shared<Buff::SoD>();
messageComponent = std::make_shared<Message::SoD>();
break;
case Version::SoF:
buffComponent = std::make_shared<Buff::SoF>();
messageComponent = std::make_shared<Message::SoF>();
break;
default:
buffComponent = std::make_shared<Buff::Titanium>();
messageComponent = std::make_shared<Message::Titanium>();
break;
}
}
const Version version;
std::shared_ptr<Buff::IBuff> buffComponent;
std::shared_ptr<Message::IMessage> messageComponent;
};
@@ -78,6 +86,11 @@ static const ClientComponents& GetComponents(Version version)
return patches.at(version);
}
const std::shared_ptr<Buff::IBuff>& GetBuffComponent(Version version)
{
return GetComponents(version).buffComponent;
}
const std::shared_ptr<Message::IMessage>& GetMessageComponent(Version version)
{
return GetComponents(version).messageComponent;
+2
View File
@@ -7,7 +7,9 @@
#include "common/emu_versions.h"
#include <memory>
namespace Buff { class IBuff; }
namespace Message { class IMessage; }
// store all static functions for the different patches here
const std::shared_ptr<Buff::IBuff>& GetBuffComponent(EQ::versions::ClientVersion version);
const std::shared_ptr<Message::IMessage>& GetMessageComponent(EQ::versions::ClientVersion version);
+1 -1
View File
@@ -353,7 +353,7 @@
0x05ea,
0x1b6f,
0x198e,
0x7bd6, OP_Buff
0x7bd6, OP_BuffDefinition
0x3501,
0x47ab,
0x7a9e, OP_World_Client_CRC1
+10 -10
View File
@@ -402,7 +402,7 @@ namespace RoF
FINISH_ENCODE();
}
ENCODE(OP_Buff)
ENCODE(OP_BuffDefinition)
{
ENCODE_LENGTH_EXACT(SpellBuffPacket_Struct);
SETUP_DIRECT_ENCODE(SpellBuffPacket_Struct, structs::SpellBuffPacket_Struct);
@@ -427,14 +427,14 @@ namespace RoF
else
eq->bufffade = 2;
// Bit of a hack. OP_Buff appears to add/remove the buff while OP_BuffCreate adds/removes the actual buff icon
// 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_BuffCreate, 29);
outapp = new EQApplicationPacket(OP_RefreshBuffs, 29);
outapp->WriteUInt32(emu->entityid);
outapp->WriteUInt32(0); // tic timer
outapp->WriteUInt8(0); // Type of OP_BuffCreate packet ?
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)
@@ -446,10 +446,10 @@ namespace RoF
FINISH_ENCODE();
if (outapp)
dest->FastQueuePacket(&outapp); // Send the OP_BuffCreate to remove the buff
dest->FastQueuePacket(&outapp); // Send the OP_RefreshBuffs to remove the buff
}
ENCODE(OP_BuffCreate)
ENCODE(OP_RefreshBuffs)
{
SETUP_VAR_ENCODE(BuffIcon_Struct);
@@ -1858,9 +1858,9 @@ namespace RoF
FINISH_ENCODE();
}
ENCODE(OP_PetBuffWindow)
ENCODE(OP_RefreshPetBuffs)
{
// The format of the RoF packet is identical to the OP_BuffCreate packet.
// The format of the RoF packet is identical to the OP_RefreshBuffs packet.
SETUP_VAR_ENCODE(PetBuff_Struct);
@@ -3239,7 +3239,7 @@ namespace RoF
FINISH_ENCODE();
}
ENCODE(OP_TargetBuffs) { ENCODE_FORWARD(OP_BuffCreate); }
ENCODE(OP_RefreshTargetBuffs) { ENCODE_FORWARD(OP_RefreshBuffs); }
ENCODE(OP_TaskDescription)
{
@@ -4196,7 +4196,7 @@ namespace RoF
FINISH_DIRECT_DECODE();
}
DECODE(OP_Buff)
DECODE(OP_BuffDefinition)
{
DECODE_LENGTH_EXACT(structs::SpellBuffPacket_Struct);
SETUP_DIRECT_DECODE(SpellBuffPacket_Struct, structs::SpellBuffPacket_Struct);
+11
View File
@@ -60,3 +60,14 @@ public:
};
} // namespace Message
namespace Buff {
class RoF : public UF
{
public:
RoF() = default;
~RoF() override = default;
};
} // namespace Buff
+10 -10
View File
@@ -661,7 +661,7 @@ namespace RoF2
FINISH_ENCODE();
}
ENCODE(OP_Buff)
ENCODE(OP_BuffDefinition)
{
ENCODE_LENGTH_EXACT(SpellBuffPacket_Struct);
SETUP_DIRECT_ENCODE(SpellBuffPacket_Struct, structs::SpellBuffPacket_Struct);
@@ -685,14 +685,14 @@ namespace RoF2
else
eq->bufffade = 2;
// Bit of a hack. OP_Buff appears to add/remove the buff while OP_BuffCreate adds/removes the actual buff icon
// 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_BuffCreate, 29u);
outapp = new EQApplicationPacket(OP_RefreshBuffs, 29u);
outapp->WriteUInt32(emu->entityid);
outapp->WriteUInt32(0); // tic timer
outapp->WriteUInt8(0); // Type of OP_BuffCreate packet ?
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)
@@ -704,10 +704,10 @@ namespace RoF2
FINISH_ENCODE();
if (outapp)
dest->FastQueuePacket(&outapp); // Send the OP_BuffCreate to remove the buff
dest->FastQueuePacket(&outapp); // Send the OP_RefreshBuffs to remove the buff
}
ENCODE(OP_BuffCreate)
ENCODE(OP_RefreshBuffs)
{
SETUP_VAR_ENCODE(BuffIcon_Struct);
@@ -2464,9 +2464,9 @@ namespace RoF2
FINISH_ENCODE();
}
ENCODE(OP_PetBuffWindow)
ENCODE(OP_RefreshPetBuffs)
{
// The format of the RoF2 packet is identical to the OP_BuffCreate packet.
// The format of the RoF2 packet is identical to the OP_RefreshBuffs packet.
SETUP_VAR_ENCODE(PetBuff_Struct);
@@ -3841,7 +3841,7 @@ namespace RoF2
FINISH_ENCODE();
}
ENCODE(OP_TargetBuffs) { ENCODE_FORWARD(OP_BuffCreate); }
ENCODE(OP_RefreshTargetBuffs) { ENCODE_FORWARD(OP_RefreshBuffs); }
ENCODE(OP_TaskDescription)
{
@@ -5145,7 +5145,7 @@ namespace RoF2
FINISH_DIRECT_DECODE();
}
DECODE(OP_Buff)
DECODE(OP_BuffDefinition)
{
DECODE_LENGTH_EXACT(structs::SpellBuffPacket_Struct);
SETUP_DIRECT_DECODE(SpellBuffPacket_Struct, structs::SpellBuffPacket_Struct);
+11
View File
@@ -60,3 +60,14 @@ public:
};
} // namespace Message
namespace Buff {
class RoF2 : public RoF
{
public:
RoF2() = default;
~RoF2() override = default;
};
} // namespace Buff
+5 -5
View File
@@ -43,8 +43,8 @@ E(OP_BazaarSearch)
E(OP_BecomeTrader)
E(OP_BeginCast)
E(OP_BlockedBuffs)
E(OP_Buff)
E(OP_BuffCreate)
E(OP_BuffDefinition)
E(OP_RefreshBuffs)
E(OP_BuyerItems)
E(OP_CancelTrade)
E(OP_CastSpell)
@@ -100,7 +100,7 @@ E(OP_MoveItem)
E(OP_NewSpawn)
E(OP_NewZone)
E(OP_OnLevelMessage)
E(OP_PetBuffWindow)
E(OP_RefreshPetBuffs)
E(OP_PlayerProfile)
E(OP_RaidJoin)
E(OP_RaidUpdate)
@@ -123,7 +123,7 @@ E(OP_SpawnAppearance)
E(OP_SpawnDoor)
E(OP_SpecialMesg)
E(OP_Stun)
E(OP_TargetBuffs)
E(OP_RefreshTargetBuffs)
E(OP_TaskDescription)
E(OP_TaskHistoryReply)
E(OP_Track)
@@ -153,7 +153,7 @@ D(OP_Barter)
D(OP_BazaarSearch)
D(OP_BlockedBuffs)
D(OP_BookButton)
D(OP_Buff)
D(OP_BuffDefinition)
D(OP_BuffRemoveRequest)
D(OP_BuyerItems)
D(OP_CastSpell)
+5 -5
View File
@@ -27,8 +27,8 @@ E(OP_Barter)
E(OP_BazaarSearch)
E(OP_BeginCast)
E(OP_BlockedBuffs)
E(OP_Buff)
E(OP_BuffCreate)
E(OP_BuffDefinition)
E(OP_RefreshBuffs)
E(OP_CancelTrade)
E(OP_CastSpell)
E(OP_ChannelMessage)
@@ -81,7 +81,7 @@ E(OP_MoveItem)
E(OP_NewSpawn)
E(OP_NewZone)
E(OP_OnLevelMessage)
E(OP_PetBuffWindow)
E(OP_RefreshPetBuffs)
E(OP_PlayerProfile)
E(OP_RaidJoin)
E(OP_RaidUpdate)
@@ -104,7 +104,7 @@ E(OP_SpawnAppearance)
E(OP_SpawnDoor)
E(OP_SpecialMesg)
E(OP_Stun)
E(OP_TargetBuffs)
E(OP_RefreshTargetBuffs)
E(OP_TaskDescription)
E(OP_TaskHistoryReply)
E(OP_Track)
@@ -131,7 +131,7 @@ D(OP_AugmentInfo)
D(OP_AugmentItem)
D(OP_BazaarSearch)
D(OP_BlockedBuffs)
D(OP_Buff)
D(OP_BuffDefinition)
D(OP_BuffRemoveRequest)
D(OP_CastSpell)
D(OP_ChannelMessage)
+4 -4
View File
@@ -293,7 +293,7 @@ namespace SoD
dest->FastQueuePacket(&in, ack_req);
}
ENCODE(OP_Buff)
ENCODE(OP_BuffDefinition)
{
ENCODE_LENGTH_EXACT(SpellBuffPacket_Struct);
SETUP_DIRECT_ENCODE(SpellBuffPacket_Struct, structs::SpellBuffPacket_Struct);
@@ -1375,7 +1375,7 @@ namespace SoD
FINISH_ENCODE();
}
ENCODE(OP_PetBuffWindow)
ENCODE(OP_RefreshPetBuffs)
{
EQApplicationPacket *in = *p;
*p = nullptr;
@@ -2145,7 +2145,7 @@ namespace SoD
FINISH_ENCODE();
}
ENCODE(OP_TargetBuffs)
ENCODE(OP_RefreshTargetBuffs)
{
SETUP_VAR_ENCODE(BuffIcon_Struct);
@@ -2877,7 +2877,7 @@ namespace SoD
FINISH_DIRECT_DECODE();
}
DECODE(OP_Buff)
DECODE(OP_BuffDefinition)
{
DECODE_LENGTH_EXACT(structs::SpellBuffPacket_Struct);
SETUP_DIRECT_DECODE(SpellBuffPacket_Struct, structs::SpellBuffPacket_Struct);
+11
View File
@@ -60,3 +60,14 @@ public:
};
} // namespace Message
namespace Buff {
class SoD : public SoF
{
public:
SoD() = default;
~SoD() override = default;
};
} // namespace Buff
+4 -4
View File
@@ -23,7 +23,7 @@ E(OP_ApplyPoison)
E(OP_AugmentInfo)
E(OP_Barter)
E(OP_BazaarSearch)
E(OP_Buff)
E(OP_BuffDefinition)
E(OP_CancelTrade)
E(OP_ChannelMessage)
E(OP_CharInventory)
@@ -65,7 +65,7 @@ E(OP_MoveItem)
E(OP_NewSpawn)
E(OP_NewZone)
E(OP_OnLevelMessage)
E(OP_PetBuffWindow)
E(OP_RefreshPetBuffs)
E(OP_PlayerProfile)
E(OP_RaidJoin)
E(OP_RaidUpdate)
@@ -80,7 +80,7 @@ E(OP_SomeItemPacketMaybe)
E(OP_SpawnDoor)
E(OP_SpecialMesg)
E(OP_Stun)
E(OP_TargetBuffs)
E(OP_RefreshTargetBuffs)
E(OP_TaskDescription)
E(OP_Track)
E(OP_Trader)
@@ -102,7 +102,7 @@ D(OP_AugmentInfo)
D(OP_AugmentItem)
D(OP_BazaarSearch)
D(OP_BookButton)
D(OP_Buff)
D(OP_BuffDefinition)
D(OP_CastSpell)
D(OP_ChannelMessage)
D(OP_CharacterCreate)
+3 -3
View File
@@ -272,7 +272,7 @@ namespace SoF
FINISH_ENCODE();
}
ENCODE(OP_Buff)
ENCODE(OP_BuffDefinition)
{
ENCODE_LENGTH_EXACT(SpellBuffPacket_Struct);
SETUP_DIRECT_ENCODE(SpellBuffPacket_Struct, structs::SpellBuffPacket_Struct);
@@ -1049,7 +1049,7 @@ namespace SoF
FINISH_ENCODE();
}
ENCODE(OP_PetBuffWindow)
ENCODE(OP_RefreshPetBuffs)
{
ENCODE_LENGTH_EXACT(PetBuff_Struct);
SETUP_DIRECT_ENCODE(PetBuff_Struct, PetBuff_Struct);
@@ -2321,7 +2321,7 @@ namespace SoF
FINISH_DIRECT_DECODE();
}
DECODE(OP_Buff)
DECODE(OP_BuffDefinition)
{
DECODE_LENGTH_EXACT(structs::SpellBuffPacket_Struct);
SETUP_DIRECT_DECODE(SpellBuffPacket_Struct, structs::SpellBuffPacket_Struct);
+11
View File
@@ -60,3 +60,14 @@ public:
};
} // namespace Message
namespace Buff {
class SoF : public Titanium
{
public:
SoF() = default;
~SoF() override = default;
};
} // namespace Buff
+3 -3
View File
@@ -23,7 +23,7 @@ E(OP_ApplyPoison)
E(OP_AugmentInfo)
E(OP_BazaarSearch)
E(OP_BecomeTrader)
E(OP_Buff)
E(OP_BuffDefinition)
E(OP_CancelTrade)
E(OP_ChannelMessage)
E(OP_CharInventory)
@@ -60,7 +60,7 @@ E(OP_MoveItem)
E(OP_NewSpawn)
E(OP_NewZone)
E(OP_OnLevelMessage)
E(OP_PetBuffWindow)
E(OP_RefreshPetBuffs)
E(OP_PlayerProfile)
E(OP_RaidJoin)
E(OP_RaidUpdate)
@@ -93,7 +93,7 @@ D(OP_ApplyPoison)
D(OP_AugmentInfo)
D(OP_AugmentItem)
D(OP_BookButton)
D(OP_Buff)
D(OP_BuffDefinition)
D(OP_Bug)
D(OP_CastSpell)
D(OP_ChannelMessage)
+81 -3
View File
@@ -36,6 +36,8 @@
#include <sstream>
#include "zone/mob.h"
namespace Titanium
{
@@ -326,7 +328,7 @@ namespace Titanium
}
}
ENCODE(OP_Buff)
ENCODE(OP_BuffDefinition)
{
ENCODE_LENGTH_EXACT(SpellBuffPacket_Struct);
SETUP_DIRECT_ENCODE(SpellBuffPacket_Struct, structs::SpellBuffPacket_Struct);
@@ -1307,7 +1309,7 @@ namespace Titanium
FINISH_ENCODE();
}
ENCODE(OP_PetBuffWindow)
ENCODE(OP_RefreshPetBuffs)
{
ENCODE_LENGTH_EXACT(PetBuff_Struct);
SETUP_DIRECT_ENCODE(PetBuff_Struct, PetBuff_Struct);
@@ -2540,7 +2542,7 @@ namespace Titanium
}
}
DECODE(OP_Buff)
DECODE(OP_BuffDefinition)
{
DECODE_LENGTH_EXACT(structs::SpellBuffPacket_Struct);
SETUP_DIRECT_DECODE(SpellBuffPacket_Struct, structs::SpellBuffPacket_Struct);
@@ -4014,3 +4016,79 @@ void Titanium::ResolveArguments(uint32_t id, std::array<const char*, 9>& args) c
}
} // namespace Message
namespace Buff {
EQApplicationPacket* Titanium::MakeLegacyBuffsPacket(Mob* mob, int32_t timer, bool for_target, bool clear_buffs) const
{
uint32 count = 0;
uint32 buff_count;
// for self we want all buffs, for target, we want to skip song window buffs
// since NPCs and pets don't have a song window, we still see it for them :P
if (for_target) {
buff_count = (clear_buffs) ? 0 : mob->GetMaxBuffSlots();
}
else {
buff_count = mob->GetMaxTotalSlots();
}
Buffs_Struct* buffs = mob->GetBuffs();
for(int i = 0; i < buff_count; ++i) {
if (buffs[i].spellid > 1) {
++count;
}
}
EQApplicationPacket* outapp = nullptr;
//Create it for a targeting window, else create it for a create buff packet.
if(for_target) {
outapp = new EQApplicationPacket(OP_RefreshTargetBuffs, sizeof(BuffIcon_Struct) + sizeof(BuffIconEntry_Struct) * count);
}
else {
outapp = new EQApplicationPacket(OP_RefreshBuffs, 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 = timer;
// there are more types, the client doesn't seem to really care though. The others are also currently hard to fill in here ...
// (see comment in common/eq_packet_structs.h)
if (for_target)
buff->type = mob->IsClient() ? 5 : 7;
else
buff->type = 0;
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;
}
EQApplicationPacket* Titanium::BuffDefinition(Mob* mob, const Buffs_Struct& buff, int slot, bool fade) const
{
return nullptr;
}
EQApplicationPacket* Titanium::RefreshBuffs(EmuOpcode opcode, Mob* mob, int32_t timer, bool remove,
bool buff_timers_suspended, const std::vector<uint32_t>& slots) const
{
return nullptr;
}
void Titanium::SetRefreshType(EQApplicationPacket* packet, Mob* source, Client* target) const {}
} // namespace Buff
+18
View File
@@ -17,6 +17,7 @@
*/
#pragma once
#include "IBuff.h"
#include "IMessage.h"
#include "common/struct_strategy.h"
@@ -73,3 +74,20 @@ protected:
} // namespace Message
namespace Buff {
class Titanium : public IBuff
{
public:
Titanium() = default;
~Titanium() override = default;
EQApplicationPacket* MakeLegacyBuffsPacket(Mob* mob, int32_t timer, bool for_target, bool clear_buffs) const override;
EQApplicationPacket* BuffDefinition(Mob* mob, const Buffs_Struct& buff, int slot, bool fade) const override;
EQApplicationPacket* RefreshBuffs(EmuOpcode opcode, Mob* mob, int32_t timer, bool remove, bool buff_timers_suspended, const std::vector<uint32_t>& slots) const override;
void SetRefreshType(EQApplicationPacket* packet, Mob* source, Client* target) const override;
};
} // namespace Buff
+3 -3
View File
@@ -21,7 +21,7 @@ E(OP_AdventureMerchantSell)
E(OP_ApplyPoison)
E(OP_BazaarSearch)
E(OP_BecomeTrader)
E(OP_Buff)
E(OP_BuffDefinition)
E(OP_ChannelMessage)
E(OP_CharInventory)
E(OP_ClientUpdate)
@@ -61,7 +61,7 @@ E(OP_ManaChange)
E(OP_MemorizeSpell)
E(OP_MoveItem)
E(OP_OnLevelMessage)
E(OP_PetBuffWindow)
E(OP_RefreshPetBuffs)
E(OP_PlayerProfile)
E(OP_NewSpawn)
E(OP_MarkRaidNPC)
@@ -91,7 +91,7 @@ D(OP_AdventureMerchantSell)
D(OP_ApplyPoison)
D(OP_AugmentItem)
D(OP_BazaarSearch)
D(OP_Buff)
D(OP_BuffDefinition)
D(OP_Bug)
D(OP_CastSpell)
D(OP_ChannelMessage)
+118 -86
View File
@@ -27,6 +27,8 @@
#include "common/packet_dump.h"
#include "world/sof_char_create_data.h"
#include "zone/client.h"
#include "zone/mob.h"
#include "zone/string_ids.h"
namespace TOB
@@ -254,92 +256,6 @@ namespace TOB
FINISH_ENCODE();
}
ENCODE(OP_Buff)
{
ENCODE_LENGTH_EXACT(SpellBuffPacket_Struct);
SETUP_DIRECT_ENCODE(SpellBuffPacket_Struct, structs::EQAffectPacket_Struct);
eq->entity_id = emu->entityid;
eq->unknown004 = 0;
//fill in affect info
eq->affect.caster_id.Id = emu->buff.player_id;
eq->affect.flags = 0;
eq->affect.spell_id = emu->buff.spellid;
eq->affect.duration = emu->buff.duration;
eq->affect.initial_duration = emu->buff.duration;
eq->affect.hit_count = emu->buff.num_hits;
eq->affect.viral_timer = 0;
eq->affect.modifier = emu->buff.bard_modifier == 10 ? 1.0f : emu->buff.bard_modifier / 10.0f;
eq->affect.y = emu->buff.y;
eq->affect.x = emu->buff.x;
eq->affect.z = emu->buff.z;
eq->affect.level = emu->buff.level;
eq->slot_id = ServerToTOBBuffSlot(emu->slotid);
if (emu->bufffade == 1)
{
eq->buff_fade = 1;
}
else
{
eq->buff_fade = 2;
}
EQApplicationPacket* outapp = nullptr;
if (emu->bufffade == 1)
{
// Bit of a hack. OP_Buff appears to add/remove the buff while OP_BuffCreate adds/removes the actual buff icon
outapp = new EQApplicationPacket(OP_BuffCreate, 30);
outapp->WriteUInt32(emu->entityid);
outapp->WriteUInt32(0); // tic timer
outapp->WriteUInt8(0); // Type of OP_BuffCreate packet ?
outapp->WriteUInt16(1); // 1 buff in this packet
outapp->WriteUInt32(ServerToTOBBuffSlot(emu->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
outapp->WriteUInt8(0); // Type
}
FINISH_ENCODE();
if (outapp) {
dest->FastQueuePacket(&outapp);
}
}
ENCODE(OP_BuffCreate)
{
SETUP_VAR_ENCODE(BuffIcon_Struct);
//TOB has one extra 0x00 byte before the end byte
uint32 sz = 13 + (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 ? ServerToTOBBuffSlot(emu->entries[i].buff_slot) : emu->entries[i].buff_slot);
__packet->WriteUInt32(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(0); // Unknown1
__packet->WriteUInt8(emu->type); // Unknown2
FINISH_ENCODE();
}
ENCODE(OP_CancelTrade)
{
ENCODE_LENGTH_EXACT(CancelTrade_Struct);
@@ -5693,3 +5609,119 @@ EQApplicationPacket* TOB::InterruptSpellOther(Mob* sender, uint32_t message, uin
} // namespace Message
namespace Buff {
EQApplicationPacket* TOB::MakeLegacyBuffsPacket(Mob* mob, int32_t timer, bool for_target, bool clear_buffs) const { return nullptr; }
EQApplicationPacket* TOB::BuffDefinition(Mob* mob, const Buffs_Struct& buff, int slot, bool fade) const
{
auto packet = new EQApplicationPacket(OP_BuffDefinition, sizeof(::TOB::structs::EQAffectPacket_Struct));
auto affect = reinterpret_cast<::TOB::structs::EQAffectPacket_Struct*>(packet->pBuffer);
// base packet
affect->entity_id = mob->GetID();
affect->unknown004 = 0;
affect->slot_id = ::TOB::ServerToTOBBuffSlot(slot);
affect->buff_fade = fade ? 1 : 2; // 1 is remove, 2 is modify, 3 is add (only seen 1 and 2 sent)
// affect slots
for (int affect_slot = 0; affect_slot < 6; ++affect_slot) {
// all of this is unknown, just what we've seen
affect->affect.slots[affect_slot].slot = -1; // this is always -1
affect->affect.slots[affect_slot].padding = 0; // this is never 0, but the values aren't clear
affect->affect.slots[affect_slot].value = 0; // this is always 0
}
// affect info
affect->affect.caster_id.Id = buff.casterid;
affect->affect.caster_id.WorldId = RuleI(World, Id);
affect->affect.caster_id.Reserved = 0;
affect->affect.flags = 0;
affect->affect.spell_id = buff.spellid;
affect->affect.duration = buff.ticsremaining;
affect->affect.initial_duration = buff.ticsremaining; // this isn't correct, it's the total duration
affect->affect.hit_count = buff.hit_number;
affect->affect.viral_timer = 0;
affect->affect.modifier = static_cast<float>(buff.instrument_mod) / 10.f;
affect->affect.y = static_cast<float>(buff.caston_y);
affect->affect.x = static_cast<float>(buff.caston_x);
affect->affect.z = static_cast<float>(buff.caston_z);
affect->affect.type = 2;
affect->affect.level = buff.casterlevel > 0 ? buff.casterlevel : mob->GetLevel();
//no idea if these are right; eqlib doesn't seem to know either
if (buff.dot_rune > 0)
affect->affect.charges = buff.dot_rune;
else if (buff.magic_rune > 0)
affect->affect.charges = buff.magic_rune;
else if (buff.melee_rune > 0)
affect->affect.charges = buff.melee_rune;
else if (buff.counters > 0)
affect->affect.charges = buff.counters;
affect->affect.activatable = 0;
affect->affect.unknown1 = 0; //might be some timer, not sure though
return packet;
}
EQApplicationPacket* TOB::RefreshBuffs(EmuOpcode opcode, Mob* mob, int32_t timer, bool remove, bool buff_timers_suspended, const std::vector<uint32_t>& slots) const
{
Buffs_Struct* buffs = mob->GetBuffs();
// pre-calculate the buffer size to avoid too many grow calls
size_t buffer_size = 13; // 13 bytes outside the list
std::vector<uint32_t> 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);
}
}
SerializeBuffer buffer(buffer_size);
buffer.WriteUInt32(mob->GetID());
buffer.WriteInt32(timer);
buffer.WriteUInt8(slots.empty() ? 1 : 0); // 1 indicates all buffs on the player (0 to add or remove a single buff)
buffer.WriteUInt16(send_slots.size());
for (uint32_t slot : send_slots) {
buffer.WriteUInt32(::TOB::ServerToTOBBuffSlot(slot)); // the server stores fewer buffs
buffer.WriteInt32(remove ? -1 : buffs[slot].spellid);
buffer.WriteUInt32(buffs[slot].ticsremaining);
buffer.WriteUInt32(buffs[slot].hit_number);
buffer.WriteString(buffs[slot].caster_name);
}
buffer.WriteUInt8(opcode == OP_RefreshPetBuffs ? 2 : 0);
buffer.WriteUInt8(buff_timers_suspended ? 1 : 0); // bBuffTimersOnHold
return new EQApplicationPacket(opcode, std::move(buffer));
}
// 0 = self buff window, 1 = self target window, 2 = pet buff or target window, 4 = group, 5 = PC, 7 = NPC
void TOB::SetRefreshType(EQApplicationPacket* packet, Mob* source, Client* target) const
{
unsigned char* type = &packet->pBuffer[packet->size - 2];
if (target->GetID() == source->GetID())
*type = 1;
else if (target->IsPet())
*type = 2;
else if (target->HasGroup() && source->GetGroup() == target->GetGroup())
*type = 4;
else if (target->IsClient())
*type = 5;
else
*type = 7;
}
} // namespace Buff
+19 -2
View File
@@ -39,8 +39,8 @@ namespace Message {
class TOB : public RoF2
{
public:
TOB() {}
~TOB() override {}
TOB() = default;
~TOB() override = default;
[[nodiscard]] EQApplicationPacket* Formatted(uint32_t color, uint32_t id, const std::array<const char*, 9>& args) const override;
@@ -53,3 +53,20 @@ protected:
};
} // namespace Message
namespace Buff {
class TOB : public RoF2
{
public:
TOB() = default;
~TOB() override = default;
EQApplicationPacket* MakeLegacyBuffsPacket(Mob* mob, int32_t timer, bool for_target, bool clear_buffs) const override;
EQApplicationPacket* BuffDefinition(Mob* mob, const Buffs_Struct& buff, int slot, bool fade) const override;
EQApplicationPacket* RefreshBuffs(EmuOpcode opcode, Mob* mob, int32_t timer, bool remove, bool buff_timers_suspended, const std::vector<uint32_t>& slots) const override;
void SetRefreshType(EQApplicationPacket* packet, Mob* source, Client* target) const override;
};
} // namespace Buff
-2
View File
@@ -6,8 +6,6 @@ E(OP_ApplyPoison)
E(OP_AugmentInfo)
E(OP_BeginCast)
E(OP_BlockedBuffs)
E(OP_Buff)
E(OP_BuffCreate)
E(OP_CancelTrade)
E(OP_CastSpell)
E(OP_ChannelMessage)
+5 -5
View File
@@ -434,7 +434,7 @@ namespace UF
}
}
ENCODE(OP_Buff)
ENCODE(OP_BuffDefinition)
{
ENCODE_LENGTH_EXACT(SpellBuffPacket_Struct);
SETUP_DIRECT_ENCODE(SpellBuffPacket_Struct, structs::SpellBuffPacket_Struct);
@@ -454,7 +454,7 @@ namespace UF
FINISH_ENCODE();
}
ENCODE(OP_BuffCreate)
ENCODE(OP_RefreshBuffs)
{
SETUP_VAR_ENCODE(BuffIcon_Struct);
@@ -1800,7 +1800,7 @@ namespace UF
FINISH_ENCODE();
}
ENCODE(OP_PetBuffWindow)
ENCODE(OP_RefreshPetBuffs)
{
EQApplicationPacket *in = *p;
*p = nullptr;
@@ -2729,7 +2729,7 @@ namespace UF
FINISH_ENCODE();
}
ENCODE(OP_TargetBuffs) { ENCODE_FORWARD(OP_BuffCreate); }
ENCODE(OP_RefreshTargetBuffs) { ENCODE_FORWARD(OP_RefreshBuffs); }
ENCODE(OP_TaskDescription)
{
@@ -3638,7 +3638,7 @@ namespace UF
FINISH_DIRECT_DECODE();
}
DECODE(OP_Buff)
DECODE(OP_BuffDefinition)
{
DECODE_LENGTH_EXACT(structs::SpellBuffPacket_Struct);
SETUP_DIRECT_DECODE(SpellBuffPacket_Struct, structs::SpellBuffPacket_Struct);
+11
View File
@@ -60,3 +60,14 @@ public:
};
} // namespace Message
namespace Buff {
class UF : public SoD
{
public:
UF() = default;
~UF() override = default;
};
} // namespace Buff
+5 -5
View File
@@ -25,8 +25,8 @@ E(OP_AugmentInfo)
E(OP_Barter)
E(OP_BazaarSearch)
E(OP_BecomeTrader)
E(OP_Buff)
E(OP_BuffCreate)
E(OP_BuffDefinition)
E(OP_RefreshBuffs)
E(OP_CancelTrade)
E(OP_ChannelMessage)
E(OP_CharInventory)
@@ -75,7 +75,7 @@ E(OP_MoveItem)
E(OP_NewSpawn)
E(OP_NewZone)
E(OP_OnLevelMessage)
E(OP_PetBuffWindow)
E(OP_RefreshPetBuffs)
E(OP_PlayerProfile)
E(OP_RaidJoin)
E(OP_RaidUpdate)
@@ -93,7 +93,7 @@ E(OP_SpawnAppearance)
E(OP_SpawnDoor)
E(OP_SpecialMesg)
E(OP_Stun)
E(OP_TargetBuffs)
E(OP_RefreshTargetBuffs)
E(OP_TaskDescription)
E(OP_Track)
E(OP_Trader)
@@ -116,7 +116,7 @@ D(OP_AugmentInfo)
D(OP_AugmentItem)
D(OP_BazaarSearch)
D(OP_BookButton)
D(OP_Buff)
D(OP_BuffDefinition)
D(OP_BuffRemoveRequest)
D(OP_CastSpell)
D(OP_ChannelMessage)