mirror of
https://github.com/EQEmu/Server.git
synced 2026-06-10 10:50:24 +00:00
Full Packet Review for Known Conversion (#5100)
This commit is contained in:
@@ -630,8 +630,8 @@ struct GMTrainEnd_Struct
|
||||
struct GMSkillChange_Struct {
|
||||
/*000*/ uint16 npcid;
|
||||
/*002*/ uint8 unknown1[2]; // something like PC_ID, but not really. stays the same thru the session though
|
||||
/*002*/ uint16 skillbank; // 0 if normal skills, 1 if languages
|
||||
/*002*/ uint8 unknown2[2];
|
||||
/*004*/ uint16 skillbank; // 0 if normal skills, 1 if languages
|
||||
/*006*/ uint8 unknown2[2];
|
||||
/*008*/ uint16 skill_id;
|
||||
/*010*/ uint8 unknown3[2];
|
||||
};
|
||||
@@ -5334,7 +5334,7 @@ typedef struct {
|
||||
struct ControlBoat_Struct {
|
||||
/*000*/ uint32 boatId; // entitylist id of the boat
|
||||
/*004*/ bool TakeControl; // 01 if taking control, 00 if releasing it
|
||||
/*007*/ char unknown[3]; // no idea what these last three bytes represent
|
||||
/*005*/ char unknown[3]; // no idea what these last three bytes represent
|
||||
};
|
||||
|
||||
struct AugmentInfo_Struct
|
||||
|
||||
+530
-28
@@ -220,7 +220,7 @@ namespace TOB
|
||||
ENCODE_LENGTH_EXACT(ApplyPoison_Struct);
|
||||
SETUP_DIRECT_ENCODE(ApplyPoison_Struct, structs::ApplyPoison_Struct);
|
||||
|
||||
eq->inventorySlot = ServerToTOBTypelessSlot(emu->inventorySlot, EQ::invtype::typePossessions);
|
||||
eq->inventorySlot = ServerToTOBSlot(emu->inventorySlot);
|
||||
OUT(success);
|
||||
|
||||
FINISH_ENCODE();
|
||||
@@ -234,6 +234,10 @@ namespace TOB
|
||||
OUT(itemid);
|
||||
OUT(window);
|
||||
strn0cpy(eq->augment_info, emu->augment_info, 64);
|
||||
// TODO: TOB wire format has 8 extra bytes beyond server struct (total 80 bytes):
|
||||
// +0x48 uint32 unknown072 -- 0x37 (55) triggers hardcoded "perfected distiller" message in client;
|
||||
// 0 causes jnz path (loc_1401EFB6D) which likely uses augment_info text
|
||||
// +0x4C uint32 unknown076 -- "always matches what client sends"; not echoed by current decoder
|
||||
|
||||
FINISH_ENCODE();
|
||||
}
|
||||
@@ -248,8 +252,8 @@ namespace TOB
|
||||
OUT(Unknown08);
|
||||
eq->Result = static_cast<uint8>(emu->Result);
|
||||
OUT(Amount);
|
||||
eq->StringSize = 0; // set this to 0, but it's a string size
|
||||
eq->Lucky = 0; // set to 1 to message a lucky beg
|
||||
eq->StringSize = 0; // TODO: set this to 0, but it's a string size
|
||||
eq->Lucky = 0; // TODO: set to 1 to message a lucky beg
|
||||
|
||||
FINISH_ENCODE();
|
||||
}
|
||||
@@ -317,6 +321,16 @@ namespace TOB
|
||||
FINISH_ENCODE();
|
||||
}
|
||||
|
||||
ENCODE(OP_Charm)
|
||||
{
|
||||
SETUP_DIRECT_ENCODE(Charm_Struct, structs::Charm_Struct);
|
||||
eq->owner_id = emu->owner_id;
|
||||
eq->pet_id = emu->pet_id;
|
||||
eq->charmer_id = 0; // TOB wire format has extra spawn ID at +0x08; server struct doesn't provide it; passed to sub_1402FA570 when non-null
|
||||
eq->command = static_cast<uint8>(emu->command);
|
||||
FINISH_ENCODE();
|
||||
}
|
||||
|
||||
ENCODE(OP_ChannelMessage)
|
||||
{
|
||||
EQApplicationPacket* in = *p;
|
||||
@@ -459,6 +473,12 @@ namespace TOB
|
||||
OUT(type);
|
||||
OUT(icon);
|
||||
eq->unknown16 = 0;
|
||||
// TODO: unknown24 is used by the client as ItemDefinition->ItemNumber (item DB ID for the
|
||||
// world container). Server struct field is labeled unknown24 and may not be populated.
|
||||
// TOB wire: +0x00 drop_id, +0x04 unknown04(-1), +0x08 unknown08(-1), +0x0C type(1B),
|
||||
// +0x10 unknown16(1B, client overwrites to 10), +0x14 icon, +0x18 unknown24/ItemNumber,
|
||||
// +0x1C object_name[64]
|
||||
OUT(unknown24);
|
||||
OUT_str(object_name);
|
||||
|
||||
FINISH_ENCODE();
|
||||
@@ -520,6 +540,7 @@ namespace TOB
|
||||
|
||||
OUT(spawn_id);
|
||||
OUT(killer_id);
|
||||
OUT(corpseid);
|
||||
OUT(spell_id);
|
||||
OUT(attack_skill);
|
||||
OUT(damage);
|
||||
@@ -566,7 +587,20 @@ namespace TOB
|
||||
SETUP_DIRECT_ENCODE(DeleteSpawn_Struct, structs::DeleteSpawn_Struct);
|
||||
|
||||
OUT(spawn_id);
|
||||
eq->unknown04 = 1; // Observed
|
||||
OUT(Decay);
|
||||
|
||||
FINISH_ENCODE();
|
||||
}
|
||||
|
||||
ENCODE(OP_DisciplineTimer)
|
||||
{
|
||||
ENCODE_LENGTH_EXACT(DisciplineTimer_Struct);
|
||||
SETUP_DIRECT_ENCODE(DisciplineTimer_Struct, structs::DisciplineTimer_Struct);
|
||||
|
||||
OUT(TimerID);
|
||||
OUT(Duration);
|
||||
OUT(Unknown08);
|
||||
eq->ServerTime = Timer::GetCurrentTime();
|
||||
|
||||
FINISH_ENCODE();
|
||||
}
|
||||
@@ -602,6 +636,28 @@ namespace TOB
|
||||
FINISH_ENCODE();
|
||||
}
|
||||
|
||||
ENCODE(OP_Fling)
|
||||
{
|
||||
SETUP_DIRECT_ENCODE(fling_struct, structs::fling_struct);
|
||||
|
||||
OUT(speed_z);
|
||||
OUT(new_y);
|
||||
OUT(new_x);
|
||||
OUT(new_z);
|
||||
eq->radius = 0.0f;
|
||||
eq->unknown = 0;
|
||||
OUT(travel_time);
|
||||
eq->collision = static_cast<uint8>(emu->collision != 0 ? 1 : 0);
|
||||
// TODO: verify fall damage semantics — client stores player.408 = (TOB[29] == 0), so
|
||||
// direct copy of disable_fall_damage (1=no damage) yields player.408=0; if player.408
|
||||
// is a "damage disabled" flag this is correct, but if it is "damage enabled" flip to
|
||||
// eq->fall_damage = !emu->disable_fall_damage (needs gameplay test)
|
||||
eq->fall_damage = emu->disable_fall_damage;
|
||||
eq->z_override = emu->unk3;
|
||||
|
||||
FINISH_ENCODE();
|
||||
}
|
||||
|
||||
ENCODE(OP_GMTraining) {
|
||||
ENCODE_LENGTH_EXACT(GMTrainee_Struct);
|
||||
SETUP_DIRECT_ENCODE(GMTrainee_Struct, structs::GMTrainee_Struct);
|
||||
@@ -695,6 +751,23 @@ namespace TOB
|
||||
delete in;
|
||||
}
|
||||
|
||||
ENCODE(OP_GroupInvite)
|
||||
{
|
||||
ENCODE_LENGTH_EXACT(GroupInvite_Struct);
|
||||
SETUP_VAR_ENCODE(GroupInvite_Struct);
|
||||
// Allocate 4 bytes beyond the struct so the client's read of group_request_id
|
||||
// at offset 168 (past the 168-byte struct) lands in valid, zeroed memory.
|
||||
// The server has no equivalent field; the client receives 0.
|
||||
ALLOC_VAR_ENCODE(structs::GroupGeneric_Struct, sizeof(structs::GroupGeneric_Struct) + sizeof(uint32));
|
||||
|
||||
memcpy(eq->name1, emu->invitee_name, sizeof(eq->name1));
|
||||
memcpy(eq->name2, emu->inviter_name, sizeof(eq->name2));
|
||||
// TODO: determine what the client expects for group_request_id at offset 168 —
|
||||
// it is stored in EverQuest_GroupRequestId and may be used in the accept/decline flow.
|
||||
|
||||
FINISH_ENCODE();
|
||||
}
|
||||
|
||||
ENCODE(OP_HPUpdate)
|
||||
{
|
||||
SETUP_DIRECT_ENCODE(SpawnHPUpdate_Struct, structs::SpawnHPUpdate_Struct);
|
||||
@@ -730,6 +803,28 @@ namespace TOB
|
||||
FINISH_ENCODE();
|
||||
}
|
||||
|
||||
ENCODE(OP_IncreaseStats)
|
||||
{
|
||||
ENCODE_LENGTH_EXACT(IncreaseStat_Struct);
|
||||
SETUP_VAR_ENCODE(IncreaseStat_Struct);
|
||||
ALLOC_VAR_ENCODE(structs::IncreaseStat_Struct, sizeof(structs::IncreaseStat_Struct));
|
||||
|
||||
// entity_id is stashed in unknown13[0..1] by Client::IncStats/SetStats (zone/client.cpp)
|
||||
// because IncreaseStat_Struct has no spawn_id field. The client validates this against
|
||||
// g_pLocalPlayer->SpawnID before applying the stat.
|
||||
eq->spawn_id = *reinterpret_cast<const uint16 *>(emu->unknown13);
|
||||
|
||||
if (emu->str) { eq->stat_type = STAT_STR; eq->value = emu->str; }
|
||||
else if (emu->sta) { eq->stat_type = STAT_STA; eq->value = emu->sta; }
|
||||
else if (emu->agi) { eq->stat_type = STAT_AGI; eq->value = emu->agi; }
|
||||
else if (emu->dex) { eq->stat_type = STAT_DEX; eq->value = emu->dex; }
|
||||
else if (emu->int_) { eq->stat_type = STAT_INT; eq->value = emu->int_; }
|
||||
else if (emu->wis) { eq->stat_type = STAT_WIS; eq->value = emu->wis; }
|
||||
else if (emu->cha) { eq->stat_type = STAT_CHA; eq->value = emu->cha; }
|
||||
|
||||
FINISH_ENCODE();
|
||||
}
|
||||
|
||||
ENCODE(OP_ItemPacket)
|
||||
{
|
||||
EQApplicationPacket* in = *p;
|
||||
@@ -751,14 +846,11 @@ namespace TOB
|
||||
cereal::BinaryInputArchive ar(ss);
|
||||
ar(pms);
|
||||
|
||||
uint32 player_name_length = pms.player_name.length();
|
||||
uint32 note_length = pms.note.length();
|
||||
|
||||
auto* int_struct = (EQ::InternalSerializedItem_Struct*)pms.serialized_item.data();
|
||||
|
||||
SerializeBuffer buffer;
|
||||
buffer.WriteInt32((int32_t)type);
|
||||
SerializeItem(buffer, (const EQ::ItemInstance*)int_struct->inst, int_struct->slot_id, 0, old_item_pkt->PacketType);
|
||||
SerializeItem(buffer, (const EQ::ItemInstance*)int_struct->inst, pms.slot_id, 0, old_item_pkt->PacketType);
|
||||
|
||||
buffer.WriteUInt32(pms.sent_time);
|
||||
buffer.WriteLengthString(pms.player_name);
|
||||
@@ -784,6 +876,64 @@ namespace TOB
|
||||
delete in;
|
||||
}
|
||||
|
||||
ENCODE(OP_ItemRecastDelay)
|
||||
{
|
||||
SETUP_DIRECT_ENCODE(ItemRecastDelay_Struct, structs::ItemRecastDelay_Struct);
|
||||
|
||||
// TODO: server struct ItemRecastDelay_Struct needs an ItemGlobalIndex (12 bytes) so the
|
||||
// client can locate the item and update its per-item recast timestamp (item+0x14).
|
||||
// Until that field is added server-side, item_global_index is zeroed and the item
|
||||
// lookup in GetItemByGlobalIndex will fail silently — SetCoreItemRecastTimer (the
|
||||
// core recast-by-type timer) will still fire correctly for valid recast_type values.
|
||||
eq->item_slot = {};
|
||||
eq->recast_delay = emu->recast_delay;
|
||||
eq->recast_type = emu->recast_type;
|
||||
// ignore_casting_requirement has no client equivalent (not read by TOB client)
|
||||
|
||||
FINISH_ENCODE();
|
||||
}
|
||||
|
||||
DECODE(OP_ItemVerifyRequest)
|
||||
{
|
||||
DECODE_LENGTH_EXACT(structs::ItemVerifyRequest_Struct);
|
||||
SETUP_DIRECT_DECODE(ItemVerifyRequest_Struct, structs::ItemVerifyRequest_Struct);
|
||||
|
||||
emu->slot = TOBToServerSlot(eq->inventory_slot);
|
||||
IN(target);
|
||||
|
||||
FINISH_DIRECT_DECODE();
|
||||
}
|
||||
|
||||
ENCODE(OP_ItemVerifyReply)
|
||||
{
|
||||
ENCODE_LENGTH_EXACT(ItemVerifyReply_Struct);
|
||||
SETUP_DIRECT_ENCODE(ItemVerifyReply_Struct, structs::ItemVerifyReply_Struct);
|
||||
|
||||
OUT(slot);
|
||||
OUT(spell);
|
||||
OUT(target);
|
||||
// TODO: expand server struct ItemVerifyReply_Struct to support autobook-scribe — TOB wire format (20 bytes):
|
||||
// +0x00 int32 slot (passed as ItemGlobalIndex* to GetItemByGlobalIndex / IsHeldSlot)
|
||||
// +0x04 uint32 spell (client reads lower 16 bits; 0x407 = autobook-scribe path)
|
||||
// +0x08 uint32 target
|
||||
// +0x0C int32 unknown0 (exit gate: handler skips without processing if < 0; send 0)
|
||||
// +0x10 int32 recast_time (fasttime() timestamp; must be non-zero to enter autobook-scribe
|
||||
// path when spell==0x407; zeroed here until server provides it)
|
||||
// unknown0 and recast_time are zeroed by ALLOC_VAR_ENCODE (memset)
|
||||
|
||||
FINISH_ENCODE();
|
||||
}
|
||||
|
||||
ENCODE(OP_LinkedReuse)
|
||||
{
|
||||
SETUP_DIRECT_ENCODE(LinkedSpellReuseTimer_Struct, structs::LinkedSpellReuseTimer_Struct);
|
||||
OUT(timer_id);
|
||||
eq->unknown = 0;
|
||||
OUT(end_time);
|
||||
OUT(start_time);
|
||||
FINISH_ENCODE();
|
||||
}
|
||||
|
||||
ENCODE(OP_LogServer) {
|
||||
SETUP_VAR_ENCODE(LogServer_Struct);
|
||||
ALLOC_LEN_ENCODE(1932);
|
||||
@@ -858,6 +1008,22 @@ namespace TOB
|
||||
FINISH_ENCODE();
|
||||
}
|
||||
|
||||
ENCODE(OP_LootItem)
|
||||
{
|
||||
ENCODE_LENGTH_EXACT(LootingItem_Struct);
|
||||
SETUP_DIRECT_ENCODE(LootingItem_Struct, structs::LootingItem_Struct);
|
||||
|
||||
OUT(lootee);
|
||||
OUT(looter);
|
||||
eq->slot_id = ServerToTOBCorpseMainSlot(emu->slot_id);
|
||||
OUT(auto_loot);
|
||||
// TODO: unknown16 appears to be quantity (items looted from a partial stack);
|
||||
// server struct has no quantity field so we cannot populate it here
|
||||
eq->unknown16 = 0;
|
||||
|
||||
FINISH_ENCODE();
|
||||
}
|
||||
|
||||
ENCODE(OP_ManaChange) {
|
||||
ENCODE_LENGTH_EXACT(ManaChange_Struct);
|
||||
SETUP_DIRECT_ENCODE(ManaChange_Struct, structs::ManaChange_Struct);
|
||||
@@ -1084,7 +1250,7 @@ namespace TOB
|
||||
/*
|
||||
s32 NPCAgroMaxDist;
|
||||
*/
|
||||
buffer.WriteInt32(600);
|
||||
buffer.WriteInt32(emu->npc_aggro_max_dist);
|
||||
|
||||
/*
|
||||
s32 ForageLow;
|
||||
@@ -1207,6 +1373,30 @@ namespace TOB
|
||||
delete in;
|
||||
}
|
||||
|
||||
ENCODE(OP_PickPocket)
|
||||
{
|
||||
ENCODE_LENGTH_EXACT(sPickPocket_Struct);
|
||||
SETUP_VAR_ENCODE(sPickPocket_Struct);
|
||||
|
||||
uint32 nameLen = strnlen(emu->itemname, sizeof(emu->itemname));
|
||||
uint32 pktLen = sizeof(structs::PickPocket_Struct) + nameLen + 1;
|
||||
|
||||
ALLOC_LEN_ENCODE(pktLen);
|
||||
|
||||
auto *eq = reinterpret_cast<structs::PickPocket_Struct *>(__packet->pBuffer);
|
||||
eq->to = emu->to;
|
||||
eq->from = emu->from;
|
||||
eq->myskill = emu->myskill;
|
||||
eq->type = static_cast<uint8>(emu->type);
|
||||
eq->coin = emu->coin;
|
||||
eq->nameLen = nameLen;
|
||||
if (nameLen > 0)
|
||||
memcpy(__packet->pBuffer + sizeof(structs::PickPocket_Struct), emu->itemname, nameLen);
|
||||
__packet->pBuffer[sizeof(structs::PickPocket_Struct) + nameLen] = 0; // luckily
|
||||
|
||||
FINISH_ENCODE();
|
||||
}
|
||||
|
||||
ENCODE(OP_PlayerProfile) {
|
||||
EQApplicationPacket* in = *p;
|
||||
*p = nullptr;
|
||||
@@ -2250,6 +2440,22 @@ namespace TOB
|
||||
delete in;
|
||||
}
|
||||
|
||||
ENCODE(OP_ReadBook)
|
||||
{
|
||||
ENCODE_LENGTH_ATLEAST(BookText_Struct);
|
||||
SETUP_DIRECT_ENCODE(BookText_Struct, structs::BookRequest_Struct);
|
||||
|
||||
eq->window = (emu->window == 0xFF) ? 0xFFFFFFFF : emu->window;
|
||||
OUT(type);
|
||||
OUT(target_id);
|
||||
eq->invslot = ServerToTOBTypelessSlot(emu->invslot, invtype::typePossessions);
|
||||
OUT(can_cast); // wire 0x13 = note-path cast button
|
||||
OUT(can_scribe); // wire 0x14 = book cast / note scribe button
|
||||
strn0cpy(eq->txtfile, emu->booktext, sizeof(eq->txtfile));
|
||||
|
||||
FINISH_ENCODE();
|
||||
}
|
||||
|
||||
ENCODE(OP_RecipeAutoCombine)
|
||||
{
|
||||
ENCODE_LENGTH_EXACT(RecipeAutoCombine_Struct);
|
||||
@@ -2257,12 +2463,19 @@ namespace TOB
|
||||
|
||||
OUT(object_type);
|
||||
OUT(some_id);
|
||||
eq->container_slot = ServerToTOBSlot(emu->unknown1);
|
||||
{
|
||||
structs::InventorySlot_Struct cslot = ServerToTOBSlot(emu->unknown1);
|
||||
eq->container_type = cslot.Type;
|
||||
eq->container_slot_index = cslot.Slot;
|
||||
eq->container_subindex = cslot.SubIndex;
|
||||
eq->container_augindex = cslot.AugIndex;
|
||||
// Padding2 intentionally omitted — client deserializer skips it
|
||||
}
|
||||
structs::InventorySlot_Struct TOBSlot;
|
||||
TOBSlot.Type = 8; // Observed
|
||||
TOBSlot.Slot = 0xffff;
|
||||
TOBSlot.SubIndex = 0xffff;
|
||||
TOBSlot.AugIndex = 0xffff;
|
||||
TOBSlot.Type = 8; // Observed
|
||||
TOBSlot.Slot = -1;
|
||||
TOBSlot.SubIndex = -1;
|
||||
TOBSlot.AugIndex = -1;
|
||||
TOBSlot.Padding2 = 0;
|
||||
eq->unknown_slot = TOBSlot;
|
||||
OUT(recipe_id);
|
||||
@@ -2286,7 +2499,7 @@ namespace TOB
|
||||
eq->aapoints_assigned[4] = 0;
|
||||
eq->aapoints_assigned[5] = 0;
|
||||
|
||||
for (uint32 i = 0; i < structs::MAX_PP_AA_ARRAY; ++i)
|
||||
for (uint32 i = 0; i < MAX_PP_AA_ARRAY; ++i) // server struct has 240 entries; TOB expects 300, entries 240-299 remain zero
|
||||
{
|
||||
eq->aa_list[i].AA = emu->aa_list[i].AA;
|
||||
eq->aa_list[i].value = emu->aa_list[i].value;
|
||||
@@ -2786,7 +2999,6 @@ namespace TOB
|
||||
ENCODE_LENGTH_EXACT(Merchant_Purchase_Struct);
|
||||
SETUP_DIRECT_ENCODE(Merchant_Purchase_Struct, structs::Merchant_Purchase_Response_Struct);
|
||||
|
||||
OUT(npcid);
|
||||
eq->inventory_slot = ServerToTOBTypelessSlot(emu->itemslot, EQ::invtype::typePossessions);
|
||||
OUT(quantity);
|
||||
OUT(price);
|
||||
@@ -2799,17 +3011,16 @@ namespace TOB
|
||||
ENCODE_LENGTH_EXACT(MerchantClick_Struct);
|
||||
SETUP_DIRECT_ENCODE(MerchantClick_Struct, structs::MerchantClickResponse_Struct);
|
||||
|
||||
if (emu->command == 0) {
|
||||
OUT(player_id);
|
||||
eq->npc_id = 0;
|
||||
}
|
||||
else {
|
||||
OUT(npc_id);
|
||||
OUT(npc_id);
|
||||
if (emu->command != 0) {
|
||||
OUT(player_id);
|
||||
OUT(rate);
|
||||
OUT(tab_display);
|
||||
eq->unknown028 = 256;
|
||||
// TODO: ldon_category (+16), alt_currency1 (+20), alt_currency2 (+24) not in
|
||||
// MerchantClick_Struct -- always 0; alt-currency/LDON merchants may not open correctly
|
||||
}
|
||||
// close (command==0): player_id stays 0 (SETUP zeroed it) -- byte at offset 4 = 0 -> close path
|
||||
|
||||
FINISH_ENCODE();
|
||||
}
|
||||
@@ -2881,7 +3092,12 @@ namespace TOB
|
||||
eq->spawn_id = sas->spawn_id;
|
||||
eq->type = ServerToTOBSpawnAppearanceType(sas->type);
|
||||
eq->parameter = sas->parameter;
|
||||
|
||||
// msg_stat_change reads the primary value from lock_id (offset +16) for half the
|
||||
// TOBAppearance types (MaxHealth, Health, PVP, Sneak, Linkdead, Invisibility visibility).
|
||||
// The other half read from parameter (offset +8). Both are set to the same server value
|
||||
// so either read path works; each case ignores the field it doesn't use.
|
||||
eq->lock_id = sas->parameter;
|
||||
|
||||
dest->FastQueuePacket(&outapp, ack_req);
|
||||
delete in;
|
||||
return;
|
||||
@@ -2948,6 +3164,50 @@ namespace TOB
|
||||
FINISH_ENCODE();
|
||||
}
|
||||
|
||||
ENCODE(OP_Track)
|
||||
{
|
||||
SETUP_VAR_ENCODE(Track_Struct);
|
||||
int EntryCount = __packet->size / sizeof(Track_Struct);
|
||||
|
||||
if (EntryCount == 0 || (__packet->size % sizeof(Track_Struct)) != 0) {
|
||||
LogNetcode("[STRUCTS] Wrong size on outbound [{}]: Got [{}], expected multiple of [{}]", opcodes->EmuToName(__packet->GetOpcode()), __packet->size, sizeof(Track_Struct));
|
||||
delete __packet;
|
||||
return;
|
||||
}
|
||||
|
||||
int PacketSize = 2;
|
||||
for (int i = 0; i < EntryCount; ++i)
|
||||
PacketSize += 13 + strlen(emu[i].name);
|
||||
|
||||
ALLOC_LEN_ENCODE(PacketSize);
|
||||
|
||||
char *Buffer = (char *)__packet->pBuffer;
|
||||
VARSTRUCT_ENCODE_TYPE(uint16, Buffer, EntryCount);
|
||||
for (int i = 0; i < EntryCount; ++i) {
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu[i].entityid);
|
||||
VARSTRUCT_ENCODE_TYPE(float, Buffer, emu[i].distance);
|
||||
VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu[i].level);
|
||||
VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu[i].is_npc);
|
||||
VARSTRUCT_ENCODE_STRING(Buffer, emu[i].name);
|
||||
VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu[i].is_pet);
|
||||
VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu[i].is_merc);
|
||||
}
|
||||
|
||||
FINISH_ENCODE();
|
||||
}
|
||||
|
||||
ENCODE(OP_Weather)
|
||||
{
|
||||
SETUP_DIRECT_ENCODE(Weather_Struct, structs::Weather_Struct);
|
||||
|
||||
OUT(val1);
|
||||
OUT(type);
|
||||
eq->unknown = 0;
|
||||
OUT(mode);
|
||||
|
||||
FINISH_ENCODE();
|
||||
}
|
||||
|
||||
ENCODE(OP_WearChange)
|
||||
{
|
||||
ENCODE_LENGTH_EXACT(WearChange_Struct);
|
||||
@@ -2965,6 +3225,60 @@ namespace TOB
|
||||
FINISH_ENCODE();
|
||||
}
|
||||
|
||||
ENCODE(OP_WhoAllResponse)
|
||||
{
|
||||
SETUP_VAR_ENCODE(WhoAllReturnStruct);
|
||||
|
||||
int Count = emu->playercount;
|
||||
|
||||
// TOB client expects playercount in unknown44[0] and unknown52 = 0
|
||||
emu->unknown44[0] = Count;
|
||||
emu->unknown52 = 0;
|
||||
|
||||
ALLOC_LEN_ENCODE(__packet->size + (Count * 4));
|
||||
|
||||
char *InBuffer = (char *)__emu_buffer + sizeof(WhoAllReturnStruct);
|
||||
char *OutBuffer = (char *)__packet->pBuffer;
|
||||
|
||||
memcpy(OutBuffer, __emu_buffer, sizeof(WhoAllReturnStruct));
|
||||
OutBuffer += sizeof(WhoAllReturnStruct);
|
||||
|
||||
for (int i = 0; i < Count; ++i) {
|
||||
uint32 x;
|
||||
|
||||
x = VARSTRUCT_DECODE_TYPE(uint32, InBuffer);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, x); // FormatMSGID
|
||||
|
||||
InBuffer += 4; // skip server PIDMSGID
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, 0); // PIDMSGID = 0 (no surname)
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, 0xffffffff); // extra uint32 before Name
|
||||
|
||||
char Name[64];
|
||||
|
||||
VARSTRUCT_DECODE_STRING(Name, InBuffer); // Char Name
|
||||
VARSTRUCT_ENCODE_STRING(OutBuffer, Name);
|
||||
|
||||
x = VARSTRUCT_DECODE_TYPE(uint32, InBuffer);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, x); // RankMSGID
|
||||
|
||||
VARSTRUCT_DECODE_STRING(Name, InBuffer); // Guild Name
|
||||
VARSTRUCT_ENCODE_STRING(OutBuffer, Name);
|
||||
|
||||
for (int j = 0; j < 7; ++j) {
|
||||
x = VARSTRUCT_DECODE_TYPE(uint32, InBuffer);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, x);
|
||||
}
|
||||
|
||||
VARSTRUCT_DECODE_STRING(Name, InBuffer); // Account
|
||||
VARSTRUCT_ENCODE_STRING(OutBuffer, Name);
|
||||
|
||||
x = VARSTRUCT_DECODE_TYPE(uint32, InBuffer);
|
||||
VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, x); // Unknown100
|
||||
}
|
||||
|
||||
FINISH_ENCODE();
|
||||
}
|
||||
|
||||
ENCODE(OP_ZoneChange)
|
||||
{
|
||||
ENCODE_LENGTH_EXACT(ZoneChange_Struct);
|
||||
@@ -3479,7 +3793,7 @@ namespace TOB
|
||||
DECODE_LENGTH_EXACT(structs::ApplyPoison_Struct);
|
||||
SETUP_DIRECT_DECODE(ApplyPoison_Struct, structs::ApplyPoison_Struct);
|
||||
|
||||
emu->inventorySlot = TOBToServerTypelessSlot(eq->inventorySlot, invtype::typePossessions);
|
||||
emu->inventorySlot = TOBToServerSlot(eq->inventorySlot);
|
||||
IN(success);
|
||||
|
||||
FINISH_DIRECT_DECODE();
|
||||
@@ -3494,7 +3808,13 @@ namespace TOB
|
||||
IN(race_id);
|
||||
IN(class_id);
|
||||
|
||||
// TODO: expand the approval logic to include the rest of the TOB struct values (and remove the direct translation here)
|
||||
// TODO: expand approval logic — TOB wire format (84 bytes total):
|
||||
// +0x00 char[64] name
|
||||
// +0x40 uint32 race_id
|
||||
// +0x44 uint32 class_id
|
||||
// +0x48 uint32 deity_id (not in server NameApproval_Struct)
|
||||
// +0x4c uint32 heroic_type (0–4; not in server struct)
|
||||
// +0x50 uint32 unknown (always 0)
|
||||
|
||||
FINISH_DIRECT_DECODE();
|
||||
}
|
||||
@@ -3551,6 +3871,39 @@ namespace TOB
|
||||
emu->Initialise = init;
|
||||
}
|
||||
|
||||
DECODE(OP_BookButton)
|
||||
{
|
||||
DECODE_LENGTH_EXACT(structs::BookButton_Struct);
|
||||
SETUP_DIRECT_DECODE(BookButton_Struct, structs::BookButton_Struct);
|
||||
|
||||
emu->invslot = static_cast<int16_t>(TOBToServerTypelessSlot(eq->slot, invtype::typePossessions));
|
||||
IN(target_id);
|
||||
|
||||
FINISH_DIRECT_DECODE();
|
||||
}
|
||||
|
||||
DECODE(OP_BuffDefinition)
|
||||
{
|
||||
DECODE_LENGTH_EXACT(structs::EQAffectPacket_Struct);
|
||||
SETUP_DIRECT_DECODE(SpellBuffPacket_Struct, structs::EQAffectPacket_Struct);
|
||||
|
||||
emu->entityid = eq->entity_id;
|
||||
emu->buff.level = eq->affect.level;
|
||||
emu->buff.bard_modifier = 0;
|
||||
emu->buff.spellid = eq->affect.spell_id;
|
||||
emu->buff.duration = eq->affect.duration;
|
||||
emu->buff.counters = eq->affect.hit_count;
|
||||
emu->buff.player_id = eq->affect.caster_id.Id;
|
||||
emu->buff.num_hits = eq->affect.hit_count;
|
||||
emu->buff.y = eq->affect.y;
|
||||
emu->buff.x = eq->affect.x;
|
||||
emu->buff.z = eq->affect.z;
|
||||
emu->slotid = TOBToServerBuffSlot(static_cast<int>(eq->slot_id));
|
||||
emu->bufffade = eq->buff_fade;
|
||||
|
||||
FINISH_DIRECT_DECODE();
|
||||
}
|
||||
|
||||
DECODE(OP_BuffRemoveRequest)
|
||||
{
|
||||
// This is to cater for the fact that short buff box buffs start at 30 as opposed to 25 in prior clients.
|
||||
@@ -3573,7 +3926,7 @@ namespace TOB
|
||||
emu->slot = static_cast<uint32>(TOBToServerCastingSlot(static_cast<spells::CastingSlot>(eq->slot)));
|
||||
|
||||
IN(spell_id);
|
||||
emu->inventoryslot = -1;
|
||||
emu->inventoryslot = TOBToServerSlot(TOBCastingInventorySlotToInventorySlot(eq->inventory_slot));
|
||||
IN(target_id);
|
||||
IN(y_pos);
|
||||
IN(x_pos);
|
||||
@@ -3615,7 +3968,7 @@ namespace TOB
|
||||
ChannelMessage_Struct* emu = (ChannelMessage_Struct*)__packet->pBuffer;
|
||||
|
||||
strn0cpy(emu->targetname, Target, sizeof(emu->targetname));
|
||||
strn0cpy(emu->sender, Target, sizeof(emu->sender));
|
||||
strn0cpy(emu->sender, Sender, sizeof(emu->sender));
|
||||
emu->language = Language;
|
||||
emu->chan_num = Channel;
|
||||
emu->skill_in_language = Skill;
|
||||
@@ -3652,7 +4005,32 @@ namespace TOB
|
||||
IN(CHA);
|
||||
IN(tutorial);
|
||||
|
||||
// TODO: can handle the heroic type here as well (new member)
|
||||
// TODO: expand heroic character handling — TOB wire format (168 bytes total):
|
||||
// +0x00 uint8[72] padding (zeroed)
|
||||
// +0x48 uint32 gender
|
||||
// +0x4c uint32 race
|
||||
// +0x50 uint32 class_
|
||||
// +0x54 uint32 deity
|
||||
// +0x58 uint32 start_zone
|
||||
// +0x5c uint32 haircolor
|
||||
// +0x60 uint32 beard
|
||||
// +0x64 uint32 beardcolor
|
||||
// +0x68 uint32 hairstyle
|
||||
// +0x6c uint32 face
|
||||
// +0x70 uint32 eyecolor1
|
||||
// +0x74 uint32 eyecolor2
|
||||
// +0x78 uint32 drakkin_heritage
|
||||
// +0x7c uint32 drakkin_tattoo
|
||||
// +0x80 uint32 drakkin_details
|
||||
// +0x84 uint32 STR
|
||||
// +0x88 uint32 STA
|
||||
// +0x8c uint32 AGI
|
||||
// +0x90 uint32 DEX
|
||||
// +0x94 uint32 WIS
|
||||
// +0x98 uint32 INT
|
||||
// +0x9c uint32 CHA
|
||||
// +0xa0 uint32 tutorial
|
||||
// +0xa4 uint32 heroic_type (0=none, 1=lvl 85, 2=lvl 50, 3=lvl 100, 4=lvl 115; not in server CharCreate_Struct)
|
||||
|
||||
FINISH_DIRECT_DECODE();
|
||||
}
|
||||
@@ -3664,6 +4042,8 @@ namespace TOB
|
||||
|
||||
IN(doorid);
|
||||
IN(player_id);
|
||||
IN(item_id);
|
||||
emu->picklockskill = static_cast<uint8>(eq->picklockskill);
|
||||
|
||||
FINISH_DIRECT_DECODE();
|
||||
}
|
||||
@@ -3708,6 +4088,18 @@ namespace TOB
|
||||
|
||||
DECODE(OP_ConsiderCorpse) { DECODE_FORWARD(OP_Consider); }
|
||||
|
||||
DECODE(OP_Consume)
|
||||
{
|
||||
DECODE_LENGTH_EXACT(structs::Consume_Struct);
|
||||
SETUP_DIRECT_DECODE(Consume_Struct, structs::Consume_Struct);
|
||||
|
||||
emu->slot = TOBToServerSlot(eq->slot);
|
||||
emu->auto_consumed = (eq->mode == 1) ? 0x000003E7u : 0xFFFFFFFFu;
|
||||
emu->type = static_cast<uint8>(eq->type + 1); // TOB 0/1 → server 1/2
|
||||
|
||||
FINISH_DIRECT_DECODE();
|
||||
}
|
||||
|
||||
DECODE(OP_CorpseDrag)
|
||||
{
|
||||
std::string CorpseName;
|
||||
@@ -3725,6 +4117,24 @@ namespace TOB
|
||||
strncpy(emu->DraggerName, DraggerName.c_str(), 64);
|
||||
}
|
||||
|
||||
DECODE(OP_Damage)
|
||||
{
|
||||
DECODE_LENGTH_EXACT(structs::CombatDamage_Struct);
|
||||
SETUP_DIRECT_DECODE(CombatDamage_Struct, structs::CombatDamage_Struct);
|
||||
|
||||
IN(target);
|
||||
IN(source);
|
||||
IN(type);
|
||||
IN(spellid);
|
||||
IN(damage);
|
||||
IN(force);
|
||||
IN(hit_heading);
|
||||
IN(hit_pitch);
|
||||
IN(special);
|
||||
|
||||
FINISH_DIRECT_DECODE();
|
||||
}
|
||||
|
||||
DECODE(OP_DeleteItem)
|
||||
{
|
||||
DECODE_LENGTH_EXACT(structs::DeleteItem_Struct);
|
||||
@@ -3743,12 +4153,34 @@ namespace TOB
|
||||
SETUP_DIRECT_DECODE(EnterWorld_Struct, structs::EnterWorld_Struct);
|
||||
|
||||
memcpy(emu->name, eq->name, sizeof(emu->name));
|
||||
|
||||
// TODO: map TOB wire fields to server flags.
|
||||
// TOB wire format (72 bytes total):
|
||||
// +0x00 char[64] name
|
||||
// +0x40 int32 unknown1 — 0 normally; 0x00010000 when re-entering after ZoneNotReady
|
||||
// +0x44 int32 zoneID — EverQuest_EnterZoneReason: -1 = enter last zone (normal);
|
||||
// other values = specific zone ID (tutorial zone? home city?)
|
||||
// Server struct uses separate `tutorial` and `return_home` uint32 flags.
|
||||
// Correct mapping requires knowing which zoneID values correspond to tutorial vs return_home.
|
||||
emu->return_home = 0;
|
||||
emu->tutorial = 0;
|
||||
|
||||
FINISH_DIRECT_DECODE();
|
||||
}
|
||||
|
||||
DECODE(OP_EnvDamage)
|
||||
{
|
||||
DECODE_LENGTH_EXACT(structs::EnvDamage2_Struct);
|
||||
SETUP_DIRECT_DECODE(EnvDamage2_Struct, structs::EnvDamage2_Struct);
|
||||
|
||||
emu->id = eq->entity_id;
|
||||
emu->damage = static_cast<uint32>(eq->damage);
|
||||
IN(dmgtype);
|
||||
emu->constant = 0xFFFF;
|
||||
|
||||
FINISH_DIRECT_DECODE();
|
||||
}
|
||||
|
||||
DECODE(OP_GMTraining)
|
||||
{
|
||||
DECODE_LENGTH_EXACT(structs::GMTrainee_Struct);
|
||||
@@ -3849,6 +4281,49 @@ namespace TOB
|
||||
FINISH_DIRECT_DECODE();
|
||||
}
|
||||
|
||||
DECODE(OP_PickPocket)
|
||||
{
|
||||
DECODE_LENGTH_ATLEAST(structs::PickPocket_Struct);
|
||||
SETUP_DIRECT_DECODE(PickPocket_Struct, structs::PickPocket_Struct);
|
||||
|
||||
emu->to = eq->to;
|
||||
emu->from = eq->from;
|
||||
emu->myskill = static_cast<uint16>(eq->myskill);
|
||||
emu->type = eq->type;
|
||||
emu->coin = eq->coin;
|
||||
|
||||
FINISH_DIRECT_DECODE();
|
||||
}
|
||||
|
||||
DECODE(OP_ReadBook)
|
||||
{
|
||||
// Client always sends 8216 bytes (struct is 8215); ATLEAST accepts the extra trailing byte.
|
||||
DECODE_LENGTH_ATLEAST(structs::BookRequest_Struct);
|
||||
SETUP_DIRECT_DECODE(BookRequest_Struct, structs::BookRequest_Struct);
|
||||
|
||||
IN(type);
|
||||
emu->invslot = static_cast<int16_t>(TOBToServerTypelessSlot(eq->invslot, invtype::typePossessions));
|
||||
IN(target_id);
|
||||
emu->window = (uint8)eq->window;
|
||||
strn0cpy(emu->txtfile, eq->txtfile, sizeof(emu->txtfile));
|
||||
|
||||
FINISH_DIRECT_DECODE();
|
||||
}
|
||||
|
||||
DECODE(OP_RecipeAutoCombine)
|
||||
{
|
||||
DECODE_LENGTH_EXACT(structs::RecipeAutoCombine_CS_Struct);
|
||||
SETUP_DIRECT_DECODE(RecipeAutoCombine_Struct, structs::RecipeAutoCombine_CS_Struct);
|
||||
|
||||
IN(object_type); // eq[+32] → emu[+0]
|
||||
IN(some_id); // eq[+36] → emu[+4]
|
||||
emu->unknown1 = TOBToServerSlot(eq->container_slot); // eq[+20] → emu[+8]
|
||||
IN(recipe_id); // eq[+4] → emu[+12]
|
||||
emu->reply_code = 0; // junk in client request; server overwrites in reply
|
||||
|
||||
FINISH_DIRECT_DECODE();
|
||||
}
|
||||
|
||||
DECODE(OP_RemoveBlockedBuffs) { DECODE_FORWARD(OP_BlockedBuffs); }
|
||||
|
||||
DECODE(OP_SetServerFilter)
|
||||
@@ -3939,6 +4414,26 @@ namespace TOB
|
||||
FINISH_DIRECT_DECODE();
|
||||
}
|
||||
|
||||
DECODE(OP_WhoAllRequest)
|
||||
{
|
||||
DECODE_LENGTH_EXACT(structs::Who_All_Struct);
|
||||
SETUP_DIRECT_DECODE(Who_All_Struct, structs::Who_All_Struct);
|
||||
|
||||
memcpy(emu->whom, eq->whom, sizeof(emu->whom));
|
||||
IN(wrace);
|
||||
IN(wclass);
|
||||
IN(lvllow);
|
||||
IN(lvlhigh);
|
||||
IN(gmlookup);
|
||||
// TOB splits RoF2's combined guildid field into a flag (0x94) and actual ID (0x98).
|
||||
// When guildid is non-zero it is a real guild ID; otherwise fall back to guildid_flag
|
||||
// which carries 0xFFFFFFFF (no filter) or trader/buyer sentinel values.
|
||||
emu->guildid = eq->guildid ? eq->guildid : eq->guildid_flag;
|
||||
IN(type);
|
||||
|
||||
FINISH_DIRECT_DECODE();
|
||||
}
|
||||
|
||||
DECODE(OP_ZoneEntry)
|
||||
{
|
||||
DECODE_LENGTH_EXACT(structs::ClientZoneEntry_Struct);
|
||||
@@ -5448,6 +5943,8 @@ namespace TOB
|
||||
return item::ItemPacketType::ItemPacketGuildTribute;
|
||||
case ItemPacketType::ItemPacketCharmUpdate:
|
||||
return item::ItemPacketType::ItemPacketCharmUpdate;
|
||||
case ItemPacketType::ItemPacketParcel:
|
||||
return item::ItemPacketType::ItemPacketParcel;
|
||||
default:
|
||||
return item::ItemPacketType::ItemPacketInvalid;
|
||||
}
|
||||
@@ -5611,6 +6108,11 @@ void MessageComponent::ResolveArguments(uint32_t id, std::array<const char*, 9>&
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: verify that zone/client.cpp's raw FormattedMessage_Struct path is unreachable for TOB
|
||||
// clients. That path builds the packet using the server struct layout (3x uint32 header +
|
||||
// null-terminated strings), which is incompatible with the TOB wire format serialized below.
|
||||
// There is no ENCODE(OP_FormattedMessage) in tob.cpp to fix it up, so if that path is reachable
|
||||
// it would deliver a malformed packet.
|
||||
std::unique_ptr<EQApplicationPacket> MessageComponent::Formatted(uint32_t color, uint32_t id,
|
||||
const FormattedArgs& args) const
|
||||
{
|
||||
|
||||
@@ -12,6 +12,7 @@ E(OP_CastSpell)
|
||||
E(OP_ChannelMessage)
|
||||
E(OP_CharacterCreateRequest)
|
||||
E(OP_CharInventory)
|
||||
E(OP_Charm)
|
||||
E(OP_ClickObjectAction)
|
||||
E(OP_ClientUpdate)
|
||||
E(OP_Consider)
|
||||
@@ -20,16 +21,24 @@ E(OP_Death)
|
||||
E(OP_DeleteCharge)
|
||||
E(OP_DeleteItem)
|
||||
E(OP_DeleteSpawn)
|
||||
E(OP_DisciplineTimer)
|
||||
E(OP_DisciplineUpdate)
|
||||
E(OP_ExpansionInfo)
|
||||
E(OP_ExpUpdate)
|
||||
E(OP_Fling)
|
||||
E(OP_GMTraining)
|
||||
E(OP_GMTrainSkillConfirm)
|
||||
E(OP_GroundSpawn)
|
||||
E(OP_GroupInvite)
|
||||
E(OP_HPUpdate)
|
||||
E(OP_Illusion)
|
||||
E(OP_IncreaseStats)
|
||||
E(OP_ItemPacket)
|
||||
E(OP_ItemRecastDelay)
|
||||
E(OP_ItemVerifyReply)
|
||||
E(OP_LinkedReuse)
|
||||
E(OP_LogServer)
|
||||
E(OP_LootItem)
|
||||
E(OP_ManaChange)
|
||||
E(OP_MemorizeSpell)
|
||||
E(OP_MobHealth)
|
||||
@@ -38,7 +47,9 @@ E(OP_MoveItem)
|
||||
E(OP_NewSpawn)
|
||||
E(OP_NewZone)
|
||||
E(OP_OnLevelMessage)
|
||||
E(OP_PickPocket)
|
||||
E(OP_PlayerProfile)
|
||||
E(OP_ReadBook)
|
||||
E(OP_RemoveBlockedBuffs)
|
||||
E(OP_RespondAA)
|
||||
E(OP_RequestClientZoneChange)
|
||||
@@ -57,7 +68,10 @@ E(OP_SpecialMesg)
|
||||
E(OP_SpawnAppearance)
|
||||
E(OP_SpawnDoor)
|
||||
E(OP_Stun)
|
||||
E(OP_Track)
|
||||
E(OP_WearChange)
|
||||
E(OP_Weather)
|
||||
E(OP_WhoAllResponse)
|
||||
E(OP_ZoneChange)
|
||||
E(OP_ZoneEntry)
|
||||
E(OP_ZonePlayerToBind)
|
||||
@@ -70,6 +84,8 @@ D(OP_ApproveName)
|
||||
D(OP_AugmentInfo)
|
||||
D(OP_AugmentItem)
|
||||
D(OP_BlockedBuffs)
|
||||
D(OP_BookButton)
|
||||
D(OP_BuffDefinition)
|
||||
D(OP_BuffRemoveRequest)
|
||||
D(OP_CastSpell)
|
||||
D(OP_ChannelMessage)
|
||||
@@ -78,16 +94,23 @@ D(OP_ClientUpdate)
|
||||
D(OP_ClickDoor)
|
||||
D(OP_Consider)
|
||||
D(OP_ConsiderCorpse)
|
||||
D(OP_Consume)
|
||||
D(OP_CorpseDrag)
|
||||
D(OP_Damage)
|
||||
D(OP_DeleteItem)
|
||||
D(OP_EnterWorld)
|
||||
D(OP_EnvDamage)
|
||||
D(OP_GMTraining)
|
||||
D(OP_GroupDisband)
|
||||
D(OP_GroupInvite)
|
||||
D(OP_GroupInvite2)
|
||||
D(OP_ItemVerifyRequest)
|
||||
D(OP_LootItem)
|
||||
D(OP_MemorizeSpell)
|
||||
D(OP_MoveItem)
|
||||
D(OP_PickPocket)
|
||||
D(OP_ReadBook)
|
||||
D(OP_RecipeAutoCombine)
|
||||
D(OP_RemoveBlockedBuffs)
|
||||
D(OP_SetServerFilter)
|
||||
D(OP_ShopPlayerBuy)
|
||||
@@ -96,6 +119,7 @@ D(OP_ShopRequest)
|
||||
D(OP_SpawnAppearance)
|
||||
D(OP_TradeSkillCombine)
|
||||
D(OP_WearChange)
|
||||
D(OP_WhoAllRequest)
|
||||
D(OP_ZoneEntry)
|
||||
D(OP_ZoneChange)
|
||||
#undef E
|
||||
|
||||
+219
-35
@@ -509,6 +509,14 @@ namespace TOB {
|
||||
/*176*/
|
||||
};
|
||||
|
||||
struct Weather_Struct {
|
||||
/*000*/ uint32 val1; // 0xFF = clear weather
|
||||
/*004*/ uint32 type; // 0x31=rain, 0x02=snow, 0=normal
|
||||
/*008*/ uint32 unknown; // TOB wire padding; client skips this offset
|
||||
/*012*/ uint32 mode; // server struct's mode field, shifted to +0x0C on TOB
|
||||
/*016*/
|
||||
};
|
||||
|
||||
struct WearChange_Struct {
|
||||
/*000*/ uint32 spawn_id;
|
||||
/*004*/ uint32 wear_slot_id;
|
||||
@@ -521,6 +529,22 @@ namespace TOB {
|
||||
/*032*/
|
||||
};
|
||||
|
||||
struct Who_All_Struct {
|
||||
/*000*/ char whom[64];
|
||||
/*064*/ uint8 unknown064[64]; // always zero
|
||||
/*128*/ uint32 wrace; // 0xFFFFFFFF = any race
|
||||
/*132*/ uint32 wclass; // 0xFFFFFFFF = any class
|
||||
/*136*/ uint32 lvllow; // 0xFFFFFFFF = any level
|
||||
/*140*/ uint32 lvlhigh; // 0xFFFFFFFF = any level
|
||||
/*144*/ uint32 gmlookup; // 0xFFFFFFFF = not filtering by GM
|
||||
/*148*/ uint32 guildid_flag; // 0xFFFFFFFF = no special filter; 0x7FC00000 = guild/trader/buyer active
|
||||
/*152*/ uint32 guildid; // actual guild ID for /who guild, else 0
|
||||
/*156*/ uint32 unknown09C; // always 0
|
||||
/*160*/ uint32 type; // 0 = /who, 3 = /who all
|
||||
/*164*/ uint8 unknown0A4[12]; // padding
|
||||
/*176*/
|
||||
};
|
||||
|
||||
struct ExpUpdate_Struct
|
||||
{
|
||||
/*000*/ uint64 exp; // This is exp % / 1000 now; eg 69250 = 69.25%
|
||||
@@ -530,7 +554,7 @@ namespace TOB {
|
||||
struct DeleteSpawn_Struct
|
||||
{
|
||||
/*00*/ uint32 spawn_id; // Spawn ID to delete
|
||||
/*04*/ uint8 unknown04; // Seen 1
|
||||
/*04*/ uint8 Decay; // Seen 1
|
||||
/*05*/
|
||||
};
|
||||
|
||||
@@ -553,14 +577,13 @@ namespace TOB {
|
||||
|
||||
// Was new for RoF2 - Used for Merchant_Purchase_Struct, doesn't look changed
|
||||
// Can't sellfrom other than main inventory so Slot Type is not needed.
|
||||
// The padding is because these structs are padded to the default 4 bytes
|
||||
// There is in general no padding for this, but sometimes a pad will be added
|
||||
struct TypelessInventorySlot_Struct
|
||||
{
|
||||
/*000*/ int16 Slot;
|
||||
/*002*/ int16 SubIndex;
|
||||
/*004*/ int16 AugIndex;
|
||||
/*006*/ int16 Padding;
|
||||
/*008*/
|
||||
/*006*/
|
||||
};
|
||||
|
||||
struct Consider_Struct {
|
||||
@@ -576,6 +599,16 @@ namespace TOB {
|
||||
/*024*/
|
||||
};
|
||||
|
||||
struct Consume_Struct
|
||||
{
|
||||
/*000*/ InventorySlot_Struct slot; // ItemGlobalIndex: Type(4)+Slot(2)+SubIndex(2)+AugIndex(2)+Pad(2)
|
||||
/*012*/ uint32 unknown; // always 0xFFFFFFFF on wire
|
||||
/*016*/ uint8 type; // 0=Food, 1=Water (server expects 1=Food, 2=Water)
|
||||
/*017*/ uint8 mode; // 0=auto-consume, 1=right-click
|
||||
/*018*/ uint8 pad[2];
|
||||
/*020*/
|
||||
};
|
||||
|
||||
struct SpawnHPUpdate_Struct
|
||||
{
|
||||
/*00*/ int16 spawn_id;
|
||||
@@ -587,8 +620,8 @@ namespace TOB {
|
||||
struct ClickDoor_Struct {
|
||||
/*00*/ uint16 player_id;
|
||||
/*02*/ uint8 padding1[2];
|
||||
/*04*/ int32 unknown1;
|
||||
/*08*/ int32 unknown2;
|
||||
/*04*/ uint32 item_id;
|
||||
/*08*/ uint32 picklockskill;
|
||||
/*12*/ uint8 doorid;
|
||||
/*13*/ uint8 padding2[3];
|
||||
};
|
||||
@@ -599,7 +632,7 @@ namespace TOB {
|
||||
Rampage: 0x2
|
||||
NoCastOnText: 0x4
|
||||
DoubleBowShot: 0x8
|
||||
UnknownSpellFlag: 0x10
|
||||
UnknownSpellFlag: 0x10 // display flag of some sort, setting to 1 calls DisplayChatText
|
||||
Flurry: 0x20
|
||||
Riposte: 0x40
|
||||
Critical: 0x80
|
||||
@@ -613,6 +646,9 @@ namespace TOB {
|
||||
Strikethrough: 0x8000
|
||||
LuckyRiposte: 0x10000
|
||||
Twincast: 0x20000
|
||||
ShieldBlock: 0x40000
|
||||
StaffBlock: 0x80000
|
||||
Locked: 0x100000
|
||||
Might be more flags beyond this but I'm not sure
|
||||
*/
|
||||
|
||||
@@ -620,20 +656,37 @@ namespace TOB {
|
||||
{
|
||||
/*000*/ uint16 target;
|
||||
/*002*/ uint16 source;
|
||||
/*004*/ uint32 unknown1; //not read by the client
|
||||
/*004*/ uint32 unknown1; // not read by the client
|
||||
/*008*/ int64 damage;
|
||||
/*016*/ uint32 special; //flags; will document above
|
||||
/*016*/ uint32 special; // flags; will document above
|
||||
/*020*/ int32 spellid;
|
||||
/*024*/ uint32 spell_level; //spell caster level (unconfirmed; it is used for the spell link)
|
||||
/*028*/ float force; //I haven't actually been able to confirm these three yet
|
||||
/*024*/ uint32 spell_level; // spell caster level (unconfirmed; it is used for the spell link)
|
||||
/*028*/ float force;
|
||||
/*032*/ float hit_heading;
|
||||
/*036*/ int32 hit_pitch;
|
||||
/*040*/ uint8 type;
|
||||
/*041*/ uint8 padding[3];
|
||||
/*044*/ uint32 unknown2; //not read by the client
|
||||
/*036*/ float hit_pitch;
|
||||
/*040*/ uint8 type; // skill
|
||||
/*041*/ uint8 isoffhand; // used for determining skill used for message
|
||||
/*042*/ uint8 padding[2];
|
||||
/*044*/ uint32 unknown2; // not read by the client
|
||||
/*048*/
|
||||
};
|
||||
|
||||
struct EnvDamage2_Struct {
|
||||
/*0000*/ uint16 entity_id; // spawn ID of entity taking damage (self)
|
||||
/*0002*/ uint16 unknown2; // source/attacker spawn_id; 0 for environmental
|
||||
/*0004*/ int32 spell_id; // spell causing damage; -1 or 0 = none
|
||||
/*0008*/ uint64 unknown8;
|
||||
/*0010*/ int64 damage; // damage amount
|
||||
/*0018*/ uint64 unknown18;
|
||||
/*0020*/ uint64 unknown20;
|
||||
/*0028*/ float unknown28;
|
||||
/*002C*/ uint32 unknown2C;
|
||||
/*0030*/ uint32 unknown30;
|
||||
/*0034*/ uint32 unknown34;
|
||||
/*0038*/ uint8 dmgtype; // 0xFA=Lava, 0xFB=Drowning, 0xFC=Falling, 0xFD=Trap
|
||||
/*0039*/ uint8 unknown39;
|
||||
};
|
||||
|
||||
struct Animation_Struct {
|
||||
/*00*/ uint16 spawnid;
|
||||
/*02*/ uint8 action;
|
||||
@@ -671,6 +724,31 @@ namespace TOB {
|
||||
/*0028*/
|
||||
};
|
||||
|
||||
struct ItemRecastDelay_Struct
|
||||
{
|
||||
/*000*/ InventorySlot_Struct item_slot; // zeroed until server struct gains item slot fields
|
||||
/*012*/ uint32 recast_delay; // seconds until item can be used again
|
||||
/*016*/ uint32 recast_type; // recast group (1..99); SetCoreItemRecastTimer
|
||||
/*020*/
|
||||
};
|
||||
|
||||
struct ItemVerifyRequest_Struct
|
||||
{
|
||||
/*000*/ InventorySlot_Struct inventory_slot; // ItemGlobalIndex: Type(+0 int32) Slot(+4) SubIndex(+6) AugIndex(+8) Pad(+10)
|
||||
/*012*/ uint32 target; // Target entity ID (g_pTargetPlayer+0x168), or 0
|
||||
/*016*/
|
||||
};
|
||||
|
||||
struct ItemVerifyReply_Struct
|
||||
{
|
||||
/*000*/ int32 slot; // Right-clicked slot (passed as ItemGlobalIndex* to GetItemByGlobalIndex)
|
||||
/*004*/ uint32 spell; // Spell ID; client reads lower 16 bits; 0x407 triggers autobook-scribe
|
||||
/*008*/ uint32 target; // Target Entity ID
|
||||
/*012*/ int32 unknown0; // Exit gate: handler skips if < 0; send 0
|
||||
/*016*/ int32 recast_time; // fasttime() timestamp for autobook-scribe path (spell==0x407); send 0
|
||||
/*020*/
|
||||
};
|
||||
|
||||
struct MerchantClickRequest_Struct
|
||||
{
|
||||
/*000*/ uint32 npc_id; // Merchant NPC's entity id
|
||||
@@ -700,6 +778,30 @@ namespace TOB {
|
||||
/*015*/
|
||||
};
|
||||
|
||||
// Used for OP_ReadBook in both directions (S→C text display, C→S content request).
|
||||
// IDA-confirmed layout: type+target_id precede invslot (differs from RoF2 ordering).
|
||||
struct BookRequest_Struct
|
||||
{
|
||||
/*0000*/ uint32 window; // 0xFFFFFFFF = new window; maps from emu->window (0xFF)
|
||||
/*0004*/ uint32 type; // 0=note/scroll, 1=book, 2=item info
|
||||
/*0008*/ uint32 target_id;
|
||||
/*0012*/ TypelessInventorySlot_Struct invslot;
|
||||
/*0018*/ uint8 padding;
|
||||
/*0019*/ uint8 can_cast;
|
||||
/*0020*/ uint8 can_scribe; // book-path cast button; note-path scribe button
|
||||
/*0021*/ char txtfile[8194]; // null-terminated text / book file name
|
||||
/*8215*/
|
||||
};
|
||||
|
||||
struct BookButton_Struct
|
||||
{
|
||||
/*000*/ TypelessInventorySlot_Struct slot; // book ItemIndex
|
||||
/*006*/ int16 unknown2; // zero padding
|
||||
/*008*/ int32 target_id; // spawn_id of target player or 0
|
||||
/*012*/ int32 unknown3; // zero padding
|
||||
/*016*/
|
||||
};
|
||||
|
||||
struct MemorizeSpell_Struct {
|
||||
uint32 slot; // Spot in the spell book/memorized slot
|
||||
int32 spell_id; // Spell id (200 or c8 is minor healing, etc)
|
||||
@@ -707,6 +809,13 @@ namespace TOB {
|
||||
uint32 reduction; // lower reuse (only used if scribing is 4)
|
||||
};
|
||||
|
||||
struct LinkedSpellReuseTimer_Struct {
|
||||
uint32 timer_id; // +0x00 linked spell group index (0–24)
|
||||
uint32 unknown; // +0x04 extra DWORD present in TOB/TDS+ clients
|
||||
uint32 end_time; // +0x08 absolute time when spell group is ready
|
||||
uint32 start_time; // +0x0C server send timestamp (for client latency correction)
|
||||
};
|
||||
|
||||
//I've observed 5 s16 that are all -1.
|
||||
//Clicky items don't even trigger this as far as i can tell so not sure what this is for now.
|
||||
//One of these could have changed to a s32 but im not sure.
|
||||
@@ -733,6 +842,15 @@ namespace TOB {
|
||||
/*39*/
|
||||
};
|
||||
|
||||
struct Charm_Struct
|
||||
{
|
||||
/*00*/ uint32 owner_id;
|
||||
/*04*/ uint32 pet_id;
|
||||
/*08*/ uint32 charmer_id; // TOB-only field not present in server Charm_Struct; purpose unknown (passed to sub_1402FA570 when non-null); set to 0
|
||||
/*0C*/ uint8 command; // 1=make pet, 0=release pet; server sends this as uint32 at +0x08
|
||||
/*0D*/
|
||||
};
|
||||
|
||||
struct InterruptCast_Struct
|
||||
{
|
||||
uint32 spawnid;
|
||||
@@ -920,6 +1038,13 @@ namespace TOB {
|
||||
/*332*/
|
||||
};
|
||||
|
||||
struct IncreaseStat_Struct {
|
||||
/*000*/ uint32 spawn_id; // must equal g_pLocalPlayer->SpawnID; not in server struct (see encoder TODO)
|
||||
/*004*/ uint32 stat_type; // 0=STR 1=STA 2=AGI 3=DEX 4=INT 5=WIS 6=CHA; client ignores > 6
|
||||
/*008*/ uint32 value; // must be > 0 to be applied
|
||||
/*012*/
|
||||
};
|
||||
|
||||
struct moneyOnCorpseStruct {
|
||||
/*000*/ uint8 type; // 0 = someone is already looting, 1 = OK, 2 = cannot access at this time, 3 = OK, 4 = cannot loot while hostile nearby, 5 = too far away to loot, 6 = loot all, 7 = cancel loot, 8 = add access, 9 = using advloot (when right clicking), 10 = show advloot
|
||||
/*001*/ uint8 padding1[3];
|
||||
@@ -950,7 +1075,21 @@ namespace TOB {
|
||||
/*0144*/ uint32 unknown0144;
|
||||
/*0148*/ uint32 unknown0148;
|
||||
/*0152*/ uint16 unknown0152;
|
||||
/*0154*/
|
||||
/*0154*/ uint8 unknown0154[14];
|
||||
/*0168*/
|
||||
};
|
||||
|
||||
// TOB pick pocket wire format (S→C and C→S identical layout).
|
||||
// coin sits at unaligned offset 0x0D; valid under pack(1).
|
||||
struct PickPocket_Struct {
|
||||
/*0x00*/ uint32 to;
|
||||
/*0x04*/ uint32 from;
|
||||
/*0x08*/ uint32 myskill;
|
||||
/*0x0C*/ uint8 type;
|
||||
/*0x0D*/ uint32 coin;
|
||||
/*0x11*/ uint32 nameLen;
|
||||
// char name[nameLen] follows, then uint8 luckily
|
||||
/*0x15*/
|
||||
};
|
||||
|
||||
struct AugmentInfo_Struct
|
||||
@@ -978,7 +1117,7 @@ namespace TOB {
|
||||
|
||||
struct ApplyPoison_Struct
|
||||
{
|
||||
TypelessInventorySlot_Struct inventorySlot;
|
||||
InventorySlot_Struct inventorySlot;
|
||||
uint32 success;
|
||||
};
|
||||
|
||||
@@ -1001,21 +1140,42 @@ namespace TOB {
|
||||
/*92*/
|
||||
};
|
||||
|
||||
//received and sent back as an ACK with different reply_code
|
||||
// Server→Client (38 bytes): container_slot serialized without Padding2 to match client deserializer
|
||||
struct RecipeAutoCombine_Struct {
|
||||
/*00*/ uint32 object_type;
|
||||
/*04*/ uint32 some_id;
|
||||
/*08*/ InventorySlot_Struct container_slot; //echoed in reply - Was uint32 unknown1
|
||||
/*20*/ InventorySlot_Struct unknown_slot; //echoed in reply
|
||||
/*32*/ uint32 recipe_id;
|
||||
/*36*/ uint32 reply_code;
|
||||
/*40*/
|
||||
/*08*/ int32 container_type; // InventorySlot.Type
|
||||
/*12*/ int16 container_slot_index; // InventorySlot.Slot
|
||||
/*14*/ int16 container_subindex; // InventorySlot.SubIndex
|
||||
/*16*/ int16 container_augindex; // InventorySlot.AugIndex
|
||||
/*18*/ InventorySlot_Struct unknown_slot; // 12 bytes; echoed in reply
|
||||
/*30*/ uint32 recipe_id;
|
||||
/*34*/ uint32 reply_code;
|
||||
/*38*/
|
||||
};
|
||||
|
||||
// Client→Server (56 bytes): layout from CTradeskillWnd::HandleCombine
|
||||
struct RecipeAutoCombine_CS_Struct {
|
||||
/*00*/ uint32 con_type; // GetConType of container
|
||||
/*04*/ uint32 recipe_id; // GetItemRecordNum of recipe item
|
||||
/*08*/ uint32 unknown1; // constant 4
|
||||
/*12*/ uint8 unknown2[8]; // zeros (uninitialized)
|
||||
/*20*/ InventorySlot_Struct container_slot; // 12 bytes
|
||||
/*32*/ uint32 object_type;
|
||||
/*36*/ uint32 some_id;
|
||||
/*40*/ uint8 flag1;
|
||||
/*41*/ uint8 flag2;
|
||||
/*42*/ uint8 unknown3;
|
||||
/*43*/ uint8 flag3;
|
||||
/*44*/ uint32 unknown4; // zeros (uninitialized)
|
||||
/*48*/ int64 start_tick;
|
||||
/*56*/
|
||||
};
|
||||
|
||||
/*
|
||||
** New Combine Struct
|
||||
** Client requesting to perform a tradeskill combine
|
||||
** Size: 24 bytes
|
||||
** Size: 28 bytes
|
||||
** Used In: OP_TradeSkillCombine
|
||||
** Last Updated: 01-05-2013
|
||||
*/
|
||||
@@ -1023,7 +1183,15 @@ namespace TOB {
|
||||
{
|
||||
/*00*/ InventorySlot_Struct container_slot;
|
||||
/*12*/ InventorySlot_Struct guildtribute_slot; // Slot type is 8? (MapGuildTribute = 8)
|
||||
/*24*/
|
||||
/*24*/ uint32 unknown0x18; // TOB wire format; not used by server
|
||||
/*28*/
|
||||
};
|
||||
|
||||
struct DisciplineTimer_Struct {
|
||||
/*00*/ uint32 TimerID;
|
||||
/*04*/ uint32 Duration;
|
||||
/*08*/ uint32 Unknown08; // server-side absolute expiry time (fasttime units)
|
||||
/*0C*/ uint32 ServerTime; // server's current time when packet sent (fasttime units)
|
||||
};
|
||||
|
||||
struct Disciplines_Struct {
|
||||
@@ -1032,11 +1200,12 @@ namespace TOB {
|
||||
|
||||
struct Merchant_Sell_Request_Struct {
|
||||
/*000*/ uint32 npcid; // Merchant NPC's entity id
|
||||
/*004*/ uint32 playerid; // Player's entity id
|
||||
/*008*/ uint32 itemslot; // Merchant Slot / Item Instance ID
|
||||
/*012*/ uint32 unknown12;
|
||||
/*004*/ uint32 playerid; // unset by client (stack garbage at this offset)
|
||||
/*008*/ uint32 itemslot; // lower 4 bytes of 8-byte item instance ID
|
||||
/*012*/ uint32 unknown12; // upper 4 bytes of 8-byte item instance ID
|
||||
/*016*/ uint32 quantity; // Already sold
|
||||
/*020*/
|
||||
/*020*/ uint32 unknown20; // unset by client — trailing pad; client sends 24 bytes total
|
||||
/*024*/
|
||||
};
|
||||
|
||||
struct Merchant_Sell_Response_Struct {
|
||||
@@ -1054,17 +1223,18 @@ namespace TOB {
|
||||
struct Merchant_Purchase_Request_Struct {
|
||||
/*000*/ uint32 npcid; // Merchant NPC's entity id
|
||||
/*004*/ TypelessInventorySlot_Struct inventory_slot;
|
||||
/*010*/ int16 padding;
|
||||
/*012*/ uint32 quantity;
|
||||
/*016*/
|
||||
};
|
||||
|
||||
struct Merchant_Purchase_Response_Struct {
|
||||
/*000*/ uint32 npcid; // Merchant NPC's entity id
|
||||
/*004*/ TypelessInventorySlot_Struct inventory_slot;
|
||||
/*012*/ uint32 quantity;
|
||||
/*016*/ uint32 price;
|
||||
/*020*/ uint32 unknown020;
|
||||
/*024*/
|
||||
/*000*/ TypelessInventorySlot_Struct inventory_slot;
|
||||
/*006*/ int16 padding;
|
||||
/*008*/ uint32 quantity;
|
||||
/*012*/ uint32 price;
|
||||
/*016*/ uint32 unknown016;
|
||||
/*020*/
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -1107,6 +1277,20 @@ namespace TOB {
|
||||
uint32 Zone;
|
||||
};
|
||||
|
||||
struct fling_struct {
|
||||
/* 00 */ float speed_z; // must be > 0 for handler to proceed
|
||||
/* 04 */ float new_y;
|
||||
/* 08 */ float new_x;
|
||||
/* 12 */ float new_z;
|
||||
/* 16 */ float radius; // landing zone radius; 0.0f = land exactly at target
|
||||
/* 20 */ uint32 unknown; // not accessed; padding
|
||||
/* 24 */ int32 travel_time; // -1 = auto-calc; 0 = default 1000ms; >0 = explicit ms
|
||||
/* 28 */ uint8 collision; // 0 = disable collision; non-zero = keep collision
|
||||
/* 29 */ uint8 fall_damage; // 0 = no fall damage (player.408=1); non-zero = take damage
|
||||
/* 30 */ uint8 z_override; // 1 = override z-target comparison
|
||||
/* 31 */
|
||||
};
|
||||
|
||||
struct BeggingResponse_Struct
|
||||
{
|
||||
/*00*/ uint32 Unknown00;
|
||||
|
||||
+519
-514
File diff suppressed because it is too large
Load Diff
@@ -577,7 +577,7 @@ OP_DisarmTraps=0x7362
|
||||
OP_Disarm=0x5a91
|
||||
OP_Sneak=0x7f05
|
||||
OP_Fishing=0x3cdb
|
||||
OP_InstillDoubt=0x3cdb
|
||||
OP_InstillDoubt=0x0000
|
||||
OP_FeignDeath=0x3d9f
|
||||
OP_Mend=0x3bac
|
||||
OP_Bind_Wound=0x580f
|
||||
|
||||
@@ -2305,6 +2305,7 @@ void Client::SetStats(uint8 type,int16 set_val){
|
||||
m_pp.CHA=set_val;
|
||||
break;
|
||||
}
|
||||
*reinterpret_cast<uint16*>(iss->unknown13) = GetID();
|
||||
QueuePacket(outapp);
|
||||
safe_delete(outapp);
|
||||
}
|
||||
@@ -2395,6 +2396,7 @@ void Client::IncStats(uint8 type, int16 increase_val)
|
||||
}
|
||||
break;
|
||||
}
|
||||
*reinterpret_cast<uint16*>(iss->unknown13) = GetID();
|
||||
QueuePacket(outapp);
|
||||
safe_delete(outapp);
|
||||
}
|
||||
|
||||
@@ -8868,9 +8868,9 @@ void Client::Handle_OP_Hide(const EQApplicationPacket *app)
|
||||
// newer client respond to OP_CancelSneakHide with OP_Hide with a size of 4 and 0 data
|
||||
if (app->size == 4) {
|
||||
auto data = app->ReadUInt32(0);
|
||||
if (data)
|
||||
LogDebug("Got OP_Hide with unexpected data [{}]", data);
|
||||
return;
|
||||
// only return if data is 0. data == 1 (or potentially non-zero) is the client requesting a hide roll
|
||||
if (data == 0)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!HasSkill(EQ::skills::SkillHide) && GetSkill(EQ::skills::SkillHide) == 0)
|
||||
|
||||
@@ -973,6 +973,22 @@ void Client::SendTradeskillDetails(uint32 recipe_id) {
|
||||
|
||||
uint32 total = sizeof(uint32) + dist + datalen;
|
||||
|
||||
// The TOB client reads 4 bytes of trivial after the 10 component slots.
|
||||
// Fetch the trivial from the recipe table and append it so the client doesn't read garbage.
|
||||
uint32 trivial = 0;
|
||||
std::string trivial_query = StringFormat(
|
||||
"SELECT trivial FROM tradeskill_recipe WHERE id = %u LIMIT 1", recipe_id);
|
||||
auto trivial_results = content_db.QueryDatabase(trivial_query);
|
||||
if (trivial_results.Success() && trivial_results.RowCount() > 0) {
|
||||
auto trow = trivial_results.begin();
|
||||
if (trow[0]) {
|
||||
trivial = (uint32)Strings::ToInt(trow[0]);
|
||||
}
|
||||
}
|
||||
uint32 trivial_net = htonl(trivial);
|
||||
memcpy(buf + total, &trivial_net, sizeof(uint32));
|
||||
total += sizeof(uint32);
|
||||
|
||||
auto outapp = new EQApplicationPacket(OP_RecipeDetails);
|
||||
outapp->size = total;
|
||||
outapp->pBuffer = (uchar*) buf;
|
||||
|
||||
Reference in New Issue
Block a user