From 604c7ad4ab813fa8a39306e7822187ef280251b0 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sun, 17 Sep 2023 14:14:38 -0400 Subject: [PATCH] [Feature] Add opcodes for Cast and Scribe book buttons (#3578) This is just the packet framework for the Scribe button on recipe books and the Cast Spell button on books that allow casting spells on targets. It will need to be hooked up to a content implementation --- common/emu_oplist.h | 1 + common/eq_packet_structs.h | 16 +++++++++++++--- common/patches/rof2.cpp | 20 +++++++++++++++++--- common/patches/rof2_ops.h | 1 + common/patches/rof2_structs.h | 26 +++++++++++++++++--------- common/patches/sod.cpp | 17 ++++++++++++++++- common/patches/sod_ops.h | 1 + common/patches/sod_structs.h | 21 +++++++++++++++------ common/patches/sof.cpp | 17 ++++++++++++++++- common/patches/sof_ops.h | 1 + common/patches/sof_structs.h | 21 +++++++++++++++------ common/patches/uf.cpp | 17 ++++++++++++++++- common/patches/uf_ops.h | 1 + common/patches/uf_structs.h | 21 +++++++++++++++------ utils/patches/patch_RoF2.conf | 1 + utils/patches/patch_SoD.conf | 1 + utils/patches/patch_SoF.conf | 1 + utils/patches/patch_UF.conf | 1 + zone/client.cpp | 23 +++++++---------------- zone/client_packet.cpp | 23 +++++++++++++++++++++++ zone/client_packet.h | 1 + 21 files changed, 180 insertions(+), 52 deletions(-) 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);