diff --git a/common/emu_oplist.h b/common/emu_oplist.h index 6e30333aa..ffd8792ee 100644 --- a/common/emu_oplist.h +++ b/common/emu_oplist.h @@ -62,6 +62,7 @@ N(OP_BeginCast), N(OP_Bind_Wound), N(OP_BlockedBuffs), N(OP_BoardBoat), +N(OP_BookButton), N(OP_Buff), N(OP_BuffCreate), N(OP_BuffRemoveRequest), diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index d503f56bc..2831f09f6 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -2559,7 +2559,10 @@ struct GMEmoteZone_Struct { struct BookText_Struct { uint8 window; // where to display the text (0xFF means new window) uint8 type; //type: 0=scroll, 1=book, 2=item info.. prolly others. - uint32 invslot; // Only used in SoF and later clients. + int16 invslot; // Only used in SoF and later clients. + int32 target_id; + int8 can_cast; + int8 can_scribe; char booktext[1]; // Variable Length }; // This is the request to read a book. @@ -2568,11 +2571,18 @@ struct BookText_Struct { struct BookRequest_Struct { uint8 window; // where to display the text (0xFF means new window) uint8 type; //type: 0=scroll, 1=book, 2=item info.. prolly others. - uint32 invslot; // Only used in Sof and later clients; - int16 subslot; // The subslot inside of a bag if it is inside one. + int16 invslot; // Only used in Sof and later clients; + int32 target_id; char txtfile[20]; }; +// used by Scribe and CastSpell book buttons +struct BookButton_Struct +{ + int16 invslot; // server slot + int32 target_id; +}; + /* ** Object/Ground Spawn struct ** Used for Forges, Ovens, ground spawns, items dropped to ground, etc diff --git a/common/patches/rof2.cpp b/common/patches/rof2.cpp index a9ee64c94..6aba46261 100644 --- a/common/patches/rof2.cpp +++ b/common/patches/rof2.cpp @@ -2785,7 +2785,10 @@ namespace RoF2 else eq->window = emu->window; OUT(type); - OUT(invslot); + eq->invslot = ServerToRoF2TypelessSlot(emu->invslot, invtype::typePossessions); + OUT(target_id); + OUT(can_cast); + OUT(can_scribe); strn0cpy(eq->txtfile, emu->booktext, sizeof(eq->txtfile)); FINISH_ENCODE(); @@ -4411,6 +4414,17 @@ namespace RoF2 FINISH_DIRECT_DECODE(); } + DECODE(OP_BookButton) + { + DECODE_LENGTH_EXACT(structs::BookButton_Struct); + SETUP_DIRECT_DECODE(BookButton_Struct, structs::BookButton_Struct); + + emu->invslot = static_cast(RoF2ToServerTypelessSlot(eq->slot, invtype::typePossessions)); + IN(target_id); + + FINISH_DIRECT_DECODE(); + } + DECODE(OP_Buff) { DECODE_LENGTH_EXACT(structs::SpellBuffPacket_Struct); @@ -5117,8 +5131,8 @@ namespace RoF2 SETUP_DIRECT_DECODE(BookRequest_Struct, structs::BookRequest_Struct); IN(type); - IN(invslot); - IN(subslot); + emu->invslot = static_cast(RoF2ToServerTypelessSlot(eq->invslot, invtype::typePossessions)); + IN(target_id); emu->window = (uint8)eq->window; strn0cpy(emu->txtfile, eq->txtfile, sizeof(emu->txtfile)); diff --git a/common/patches/rof2_ops.h b/common/patches/rof2_ops.h index 4fa2e546a..3e8b2cecc 100644 --- a/common/patches/rof2_ops.h +++ b/common/patches/rof2_ops.h @@ -150,6 +150,7 @@ D(OP_AugmentInfo) D(OP_AugmentItem) D(OP_BazaarSearch) D(OP_BlockedBuffs) +D(OP_BookButton) D(OP_Buff) D(OP_BuffRemoveRequest) D(OP_CastSpell) diff --git a/common/patches/rof2_structs.h b/common/patches/rof2_structs.h index 05cb4fb57..af9889f55 100644 --- a/common/patches/rof2_structs.h +++ b/common/patches/rof2_structs.h @@ -2868,15 +2868,23 @@ struct BookText_Struct { // This is just a "text file" on the server // or in our case, the 'name' column in our books table. struct BookRequest_Struct { -/*0000*/ uint32 window; // where to display the text (0xFFFFFFFF means new window). -/*0004*/ uint16 invslot; // Is the slot, but the RoF2 conversion causes it to fail. Turned to 0 since it isnt required anyway. -/*0006*/ int16 subslot; // Inventory sub-slot (0-x) -/*0008*/ uint16 unknown006; // Seen FFFF -/*0010*/ uint16 unknown008; // seen 0000 -/*0012*/ uint32 type; // 0 = Scroll, 1 = Book, 2 = Item Info. Possibly others -/*0016*/ uint32 unknown0012; -/*0020*/ uint16 unknown0016; -/*0022*/ char txtfile[8194]; +/*0000*/ uint32 window; // where to display the text (0xFFFFFFFF means new window). +/*0004*/ TypelessInventorySlot_Struct invslot; // book ItemIndex (with int16_t alignment padding) +/*0012*/ uint32 type; // 0 = Scroll, 1 = Book, 2 = Item Info. Possibly others +/*0016*/ uint32 target_id; // client's target when using the book +/*0020*/ uint8 can_cast; // show Cast Spell button in book window +/*0021*/ uint8 can_scribe; // show Scribe button in book window +/*0022*/ char txtfile[8194]; +/*8216*/ +}; + +// used by Scribe and CastSpell book buttons +struct BookButton_Struct +{ +/*0000*/ TypelessInventorySlot_Struct slot; // book ItemIndex (with int16_t alignment padding) +/*0008*/ int32 target_id; // client's target when using the book button +/*0012*/ int32 unused; // always 0 from button packets +/*0016*/ }; /* diff --git a/common/patches/sod.cpp b/common/patches/sod.cpp index 4214e2730..336cdff9b 100644 --- a/common/patches/sod.cpp +++ b/common/patches/sod.cpp @@ -1782,6 +1782,9 @@ namespace SoD eq->window = emu->window; OUT(type); eq->invslot = ServerToSoDSlot(emu->invslot); + OUT(target_id); + OUT(can_cast); + OUT(can_scribe); strn0cpy(eq->txtfile, emu->booktext, sizeof(eq->txtfile)); FINISH_ENCODE(); @@ -2817,6 +2820,17 @@ namespace SoD FINISH_DIRECT_DECODE(); } + DECODE(OP_BookButton) + { + DECODE_LENGTH_EXACT(structs::BookButton_Struct); + SETUP_DIRECT_DECODE(BookButton_Struct, structs::BookButton_Struct); + + emu->invslot = static_cast(SoDToServerSlot(eq->invslot)); + IN(target_id); + + FINISH_DIRECT_DECODE(); + } + DECODE(OP_Buff) { DECODE_LENGTH_EXACT(structs::SpellBuffPacket_Struct); @@ -3364,7 +3378,8 @@ namespace SoD SETUP_DIRECT_DECODE(BookRequest_Struct, structs::BookRequest_Struct); IN(type); - emu->invslot = SoDToServerSlot(eq->invslot); + emu->invslot = static_cast(SoDToServerSlot(eq->invslot)); + IN(target_id); emu->window = (uint8)eq->window; strn0cpy(emu->txtfile, eq->txtfile, sizeof(emu->txtfile)); diff --git a/common/patches/sod_ops.h b/common/patches/sod_ops.h index d70807cce..f314bb33f 100644 --- a/common/patches/sod_ops.h +++ b/common/patches/sod_ops.h @@ -103,6 +103,7 @@ D(OP_ApplyPoison) D(OP_AugmentInfo) D(OP_AugmentItem) D(OP_BazaarSearch) +D(OP_BookButton) D(OP_Buff) D(OP_CastSpell) D(OP_ChannelMessage) diff --git a/common/patches/sod_structs.h b/common/patches/sod_structs.h index 18c3c63d6..3c4d13f67 100644 --- a/common/patches/sod_structs.h +++ b/common/patches/sod_structs.h @@ -2351,12 +2351,21 @@ struct BookText_Struct { // This is just a "text file" on the server // or in our case, the 'name' column in our books table. struct BookRequest_Struct { -/*0000*/ uint32 window; // where to display the text (0xFFFFFFFF means new window). -/*0004*/ uint32 invslot; // The inventory slot the book is in. Not used, but echoed in the response packet. -/*0008*/ uint32 type; // 0 = Scroll, 1 = Book, 2 = Item Info. Possibly others -/*0012*/ uint32 unknown0012; -/*0016*/ uint16 unknown0016; -/*0018*/ char txtfile[8194]; +/*0000*/ uint32 window; // where to display the text (0xFFFFFFFF means new window). +/*0004*/ uint32 invslot; // The inventory slot the book is in +/*0008*/ uint32 type; // 0 = Scroll, 1 = Book, 2 = Item Info. Possibly others +/*0012*/ uint32 target_id; +/*0016*/ uint8 can_cast; +/*0017*/ uint8 can_scribe; +/*0018*/ char txtfile[8194]; +}; + +// used by Scribe and CastSpell book buttons +struct BookButton_Struct +{ +/*0000*/ int32 invslot; +/*0004*/ int32 target_id; // client's target when using the book +/*0008*/ int32 unused; // always 0 from button packets }; /* diff --git a/common/patches/sof.cpp b/common/patches/sof.cpp index 693b6b46b..17af33fc4 100644 --- a/common/patches/sof.cpp +++ b/common/patches/sof.cpp @@ -1452,6 +1452,9 @@ namespace SoF eq->window = emu->window; OUT(type); eq->invslot = ServerToSoFSlot(emu->invslot); + OUT(target_id); + OUT(can_cast); + OUT(can_scribe); strn0cpy(eq->txtfile, emu->booktext, sizeof(eq->txtfile)); FINISH_ENCODE(); @@ -2261,6 +2264,17 @@ namespace SoF FINISH_DIRECT_DECODE(); } + DECODE(OP_BookButton) + { + DECODE_LENGTH_EXACT(structs::BookButton_Struct); + SETUP_DIRECT_DECODE(BookButton_Struct, structs::BookButton_Struct); + + emu->invslot = static_cast(SoFToServerSlot(eq->invslot)); + IN(target_id); + + FINISH_DIRECT_DECODE(); + } + DECODE(OP_Buff) { DECODE_LENGTH_EXACT(structs::SpellBuffPacket_Struct); @@ -2769,7 +2783,8 @@ namespace SoF SETUP_DIRECT_DECODE(BookRequest_Struct, structs::BookRequest_Struct); IN(type); - emu->invslot = SoFToServerSlot(eq->invslot); + emu->invslot = static_cast(SoFToServerSlot(eq->invslot)); + IN(target_id); emu->window = (uint8)eq->window; strn0cpy(emu->txtfile, eq->txtfile, sizeof(emu->txtfile)); diff --git a/common/patches/sof_ops.h b/common/patches/sof_ops.h index b68378996..5100fcc26 100644 --- a/common/patches/sof_ops.h +++ b/common/patches/sof_ops.h @@ -94,6 +94,7 @@ D(OP_AltCurrencySellSelection) D(OP_ApplyPoison) D(OP_AugmentInfo) D(OP_AugmentItem) +D(OP_BookButton) D(OP_Buff) D(OP_Bug) D(OP_CastSpell) diff --git a/common/patches/sof_structs.h b/common/patches/sof_structs.h index bda416d73..70163ffc2 100644 --- a/common/patches/sof_structs.h +++ b/common/patches/sof_structs.h @@ -2321,12 +2321,21 @@ struct BookText_Struct { // This is just a "text file" on the server // or in our case, the 'name' column in our books table. struct BookRequest_Struct { -/*0000*/ uint32 window; // where to display the text (0xFFFFFFFF means new window). -/*0004*/ uint32 invslot; // The inventory slot the book is in. Not used, but echoed in the response packet. -/*0008*/ uint32 type; // 0 = Scroll, 1 = Book, 2 = Item Info. Possibly others -/*0012*/ uint32 unknown0012; -/*0016*/ uint16 unknown0016; -/*0018*/ char txtfile[8194]; +/*0000*/ uint32 window; // where to display the text (0xFFFFFFFF means new window). +/*0004*/ uint32 invslot; // The inventory slot the book is in +/*0008*/ uint32 type; // 0 = Scroll, 1 = Book, 2 = Item Info. Possibly others +/*0012*/ uint32 target_id; +/*0016*/ uint8 can_cast; +/*0017*/ uint8 can_scribe; +/*0018*/ char txtfile[8194]; +}; + +// used by Scribe and CastSpell book buttons +struct BookButton_Struct +{ +/*0000*/ int32 invslot; +/*0004*/ int32 target_id; // client's target when using the book +/*0008*/ int32 unused; // always 0 from button packets }; /* diff --git a/common/patches/uf.cpp b/common/patches/uf.cpp index b9c1d888a..b9197353e 100644 --- a/common/patches/uf.cpp +++ b/common/patches/uf.cpp @@ -2027,6 +2027,9 @@ namespace UF eq->window = emu->window; OUT(type); eq->invslot = ServerToUFSlot(emu->invslot); + OUT(target_id); + OUT(can_cast); + OUT(can_scribe); strn0cpy(eq->txtfile, emu->booktext, sizeof(eq->txtfile)); FINISH_ENCODE(); @@ -3105,6 +3108,17 @@ namespace UF FINISH_DIRECT_DECODE(); } + DECODE(OP_BookButton) + { + DECODE_LENGTH_EXACT(structs::BookButton_Struct); + SETUP_DIRECT_DECODE(BookButton_Struct, structs::BookButton_Struct); + + emu->invslot = static_cast(UFToServerSlot(eq->invslot)); + IN(target_id); + + FINISH_DIRECT_DECODE(); + } + DECODE(OP_Buff) { DECODE_LENGTH_EXACT(structs::SpellBuffPacket_Struct); @@ -3664,7 +3678,8 @@ namespace UF SETUP_DIRECT_DECODE(BookRequest_Struct, structs::BookRequest_Struct); IN(type); - emu->invslot = UFToServerSlot(eq->invslot); + emu->invslot = static_cast(UFToServerSlot(eq->invslot)); + IN(target_id); emu->window = (uint8)eq->window; strn0cpy(emu->txtfile, eq->txtfile, sizeof(emu->txtfile)); diff --git a/common/patches/uf_ops.h b/common/patches/uf_ops.h index 498b4f33d..615aa787a 100644 --- a/common/patches/uf_ops.h +++ b/common/patches/uf_ops.h @@ -110,6 +110,7 @@ D(OP_ApplyPoison) D(OP_AugmentInfo) D(OP_AugmentItem) D(OP_BazaarSearch) +D(OP_BookButton) D(OP_Buff) D(OP_BuffRemoveRequest) D(OP_CastSpell) diff --git a/common/patches/uf_structs.h b/common/patches/uf_structs.h index c629b19db..e13f34d77 100644 --- a/common/patches/uf_structs.h +++ b/common/patches/uf_structs.h @@ -2400,12 +2400,21 @@ struct BookText_Struct { // This is just a "text file" on the server // or in our case, the 'name' column in our books table. struct BookRequest_Struct { -/*0000*/ uint32 window; // where to display the text (0xFFFFFFFF means new window). -/*0004*/ uint32 invslot; // The inventory slot the book is in. Not used, but echoed in the response packet. -/*0008*/ uint32 type; // 0 = Scroll, 1 = Book, 2 = Item Info. Possibly others -/*0012*/ uint32 unknown0012; -/*0016*/ uint16 unknown0016; -/*0018*/ char txtfile[8194]; +/*0000*/ uint32 window; // where to display the text (0xFFFFFFFF means new window). +/*0004*/ uint32 invslot; // The inventory slot the book is in +/*0008*/ uint32 type; // 0 = Scroll, 1 = Book, 2 = Item Info. Possibly others +/*0012*/ uint32 target_id; +/*0016*/ uint8 can_cast; +/*0017*/ uint8 can_scribe; +/*0018*/ char txtfile[8194]; +}; + +// used by Scribe and CastSpell book buttons +struct BookButton_Struct +{ +/*0000*/ int32 invslot; +/*0004*/ int32 target_id; // client's target when using the book +/*0008*/ int32 unused; // always 0 from button packets }; /* diff --git a/utils/patches/patch_RoF2.conf b/utils/patches/patch_RoF2.conf index ec63f7913..c1c2c3a15 100644 --- a/utils/patches/patch_RoF2.conf +++ b/utils/patches/patch_RoF2.conf @@ -370,6 +370,7 @@ OP_AggroMeterTargetInfo=0x16bc OP_AggroMeterUpdate=0x1781 OP_UnderWorld=0x2eb3 # clients sends up when they detect an underworld issue, might be useful for cheat detection OP_KickPlayers=0x6770 +OP_BookButton=0x6146 # Expeditions OP_DzQuit=0xb2e3 diff --git a/utils/patches/patch_SoD.conf b/utils/patches/patch_SoD.conf index e0c4752d0..b79f1441c 100644 --- a/utils/patches/patch_SoD.conf +++ b/utils/patches/patch_SoD.conf @@ -360,6 +360,7 @@ OP_Marquee=0x7dc9 OP_Fling=0x2b88 OP_CancelSneakHide=0x7705 OP_UnderWorld=0x51ae # clients sends up when they detect an underworld issue, might be useful for cheat detection +OP_BookButton=0x4348 # Expedition OP_DzQuit=0x054e diff --git a/utils/patches/patch_SoF.conf b/utils/patches/patch_SoF.conf index a7640ec08..91d5c7660 100644 --- a/utils/patches/patch_SoF.conf +++ b/utils/patches/patch_SoF.conf @@ -341,6 +341,7 @@ OP_Marquee=0x2f75 OP_Untargetable=0x3e36 OP_CancelSneakHide=0x5335 OP_UnderWorld=0x7580 # clients sends up when they detect an underworld issue, might be useful for cheat detection +OP_BookButton=0x4eee #expedition OP_DzQuit=0x20d6 diff --git a/utils/patches/patch_UF.conf b/utils/patches/patch_UF.conf index 9e78cf140..75c4a1c5f 100644 --- a/utils/patches/patch_UF.conf +++ b/utils/patches/patch_UF.conf @@ -371,6 +371,7 @@ OP_Marquee=0x3675 OP_Fling=0x51b1 OP_CancelSneakHide=0x7686 OP_UnderWorld=0x2d9d # clients sends up when they detect an underworld issue, might be useful for cheat detection +OP_BookButton=0x018e OP_DzQuit=0x1539 OP_DzListTimers=0x21e9 diff --git a/zone/client.cpp b/zone/client.cpp index c3e6a4840..e582aa695 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -2211,25 +2211,13 @@ void Client::ReadBook(BookRequest_Struct *book) { if (ClientVersion() >= EQ::versions::ClientVersion::SoF) { - // Find out what slot the book was read from. // SoF+ need to look up book type for the output message. - int16 read_from_slot; - - if (book->subslot >= 0) { - uint16 offset; - offset = (book->invslot-23) * 10; // How many packs to skip. - read_from_slot = 251 + offset + book->subslot; - } - else { - read_from_slot = book->invslot -1; - } - const EQ::ItemInstance *inst = nullptr; - if (read_from_slot <= EQ::invbag::GENERAL_BAGS_END) - { - inst = m_inv[read_from_slot]; - } + if (book->invslot <= EQ::invbag::GENERAL_BAGS_END) + { + inst = m_inv[book->invslot]; + } if(inst) out->type = inst->GetItem()->Book; @@ -2240,6 +2228,9 @@ void Client::ReadBook(BookRequest_Struct *book) { out->type = book->type; } out->invslot = book->invslot; + out->target_id = book->target_id; + out->can_cast = 0; // todo: implement + out->can_scribe = 0; // todo: implement memcpy(out->booktext, booktxt2.c_str(), length); diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 7ad94d2b4..37636b4f7 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -145,6 +145,7 @@ void MapOpcodes() ConnectedOpcodes[OP_Bind_Wound] = &Client::Handle_OP_Bind_Wound; ConnectedOpcodes[OP_BlockedBuffs] = &Client::Handle_OP_BlockedBuffs; ConnectedOpcodes[OP_BoardBoat] = &Client::Handle_OP_BoardBoat; + ConnectedOpcodes[OP_BookButton] = &Client::Handle_OP_BookButton; ConnectedOpcodes[OP_Buff] = &Client::Handle_OP_Buff; ConnectedOpcodes[OP_BuffRemoveRequest] = &Client::Handle_OP_BuffRemoveRequest; ConnectedOpcodes[OP_Bug] = &Client::Handle_OP_Bug; @@ -4130,6 +4131,28 @@ void Client::Handle_OP_BoardBoat(const EQApplicationPacket *app) return; } +void Client::Handle_OP_BookButton(const EQApplicationPacket* app) +{ + if (app->size != sizeof(BookButton_Struct)) + { + LogError("Size mismatch in OP_BookButton. expected [{}] got [{}]", sizeof(BookButton_Struct), app->size); + DumpPacket(app); + return; + } + + BookButton_Struct* book = reinterpret_cast(app->pBuffer); + + const EQ::ItemInstance* const inst = GetInv().GetItem(book->invslot); + if (inst && inst->GetItem()->Book) + { + // todo: if scribe book learn recipes and delete book from inventory + // todo: if cast book use its spell on target and delete book from inventory (unless reusable?) + } + + EQApplicationPacket outapp(OP_FinishWindow, 0); + QueuePacket(&outapp); +} + void Client::Handle_OP_Buff(const EQApplicationPacket *app) { /* diff --git a/zone/client_packet.h b/zone/client_packet.h index f779974ba..68d1a3f94 100644 --- a/zone/client_packet.h +++ b/zone/client_packet.h @@ -56,6 +56,7 @@ void Handle_OP_Bind_Wound(const EQApplicationPacket *app); void Handle_OP_BlockedBuffs(const EQApplicationPacket *app); void Handle_OP_BoardBoat(const EQApplicationPacket *app); + void Handle_OP_BookButton(const EQApplicationPacket *app); void Handle_OP_Buff(const EQApplicationPacket *app); void Handle_OP_BuffRemoveRequest(const EQApplicationPacket *app); void Handle_OP_Bug(const EQApplicationPacket *app);