diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index 8452813f5..9bc0b7fbd 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -1548,6 +1548,12 @@ struct MoveItem_Struct /*0012*/ }; +struct MultiMoveItem_Struct +{ +/*0000*/ int32 count; +/*0004*/ MoveItem_Struct moves[0]; +}; + // both MoveItem_Struct/DeleteItem_Struct server structures will be changing to a structure-based slot format..this will // be used for handling SoF/SoD/etc... time stamps sent using the MoveItem_Struct format. (nothing will be done with this // info at the moment..but, it is forwarded on to the server for handling/future use) diff --git a/common/patches/rof.cpp b/common/patches/rof.cpp index a57e798db..7449a84df 100644 --- a/common/patches/rof.cpp +++ b/common/patches/rof.cpp @@ -1762,6 +1762,36 @@ namespace RoF FINISH_ENCODE(); } + ENCODE(OP_MoveMultipleItems) + { + // can't use our macros :( + if ((*p)->size < sizeof(MultiMoveItem_Struct) + sizeof(MoveItem_Struct)) { + Log(Logs::Detail, Logs::Netcode, + "Wrong size on outbound %s (MultiMoveItem_Struct): Got %d, expected at least %d", + opcodes->EmuToName((*p)->GetOpcode()), (*p)->size, + sizeof(MultiMoveItem_Struct) + sizeof(MoveItem_Struct)); + delete *p; + *p = nullptr; + return; + } + + SETUP_VAR_ENCODE(MultiMoveItem_Struct); + + int32 count = std::abs(emu->count); + ALLOC_VAR_ENCODE(structs::MultiMoveItem_Struct, sizeof(structs::MultiMoveItem_Struct) + + sizeof(structs::MultiMoveItemSub_Struct) * count); + + OUT(count); + + for (int i = 0; i < count; ++i) { + eq->moves[i].from_slot = ServerToRoFSlot(emu->moves[i].from_slot); + eq->moves[i].to_slot = ServerToRoFSlot(emu->moves[i].to_slot); + eq->moves[i].number_in_stack = emu->moves[i].number_in_stack; + } + + FINISH_ENCODE(); + } + ENCODE(OP_NewSpawn) { ENCODE_FORWARD(OP_ZoneSpawns); } ENCODE(OP_NewZone) @@ -4873,6 +4903,39 @@ namespace RoF FINISH_DIRECT_DECODE(); } + DECODE(OP_MoveMultipleItems) + { + // can't use our macros :( + if (__packet->size != + sizeof(structs::MultiMoveItem_Struct) + sizeof(structs::MultiMoveItemSub_Struct)) { + Log(Logs::Detail, Logs::Netcode, + "Wrong size on incoming %s (structs::MultiMoveItem_Struct): Got %d, expected %d", + opcodes->EmuToName(__packet->GetOpcode()), __packet->size, + sizeof(structs::MultiMoveItem_Struct) + sizeof(structs::MultiMoveItemSub_Struct)); + __packet->SetOpcode(OP_Unknown); /* invalidate the packet */ + return; + } + + // can be negative + int32 count = std::abs(*(int32 *)__packet->pBuffer); + + unsigned char *__eq_buffer = __packet->pBuffer; + __packet->size = sizeof(MultiMoveItem_Struct) + count * sizeof(MoveItem_Struct); + __packet->pBuffer = new unsigned char[__packet->size]; + MultiMoveItem_Struct *emu = (MultiMoveItem_Struct *) __packet->pBuffer; + structs::MultiMoveItem_Struct *eq = (structs::MultiMoveItem_Struct *) __eq_buffer; + + IN(count); + + for (int i = 0; i < count; ++i) { + emu->moves[i].from_slot = RoFToServerSlot(eq->moves[i].from_slot); + emu->moves[i].to_slot = RoFToServerSlot(eq->moves[i].to_slot); + emu->moves[i].number_in_stack = eq->moves[i].number_in_stack; + } + + FINISH_DIRECT_DECODE(); + } + DECODE(OP_PetCommands) { DECODE_LENGTH_EXACT(structs::PetCommand_Struct); diff --git a/common/patches/rof2.cpp b/common/patches/rof2.cpp index 64b991ddc..43f1c17a0 100644 --- a/common/patches/rof2.cpp +++ b/common/patches/rof2.cpp @@ -1831,6 +1831,36 @@ namespace RoF2 FINISH_ENCODE(); } + ENCODE(OP_MoveMultipleItems) + { + // can't use our macros :( + if ((*p)->size < sizeof(MultiMoveItem_Struct) + sizeof(MoveItem_Struct)) { + Log(Logs::Detail, Logs::Netcode, + "Wrong size on outbound %s (MultiMoveItem_Struct): Got %d, expected at least %d", + opcodes->EmuToName((*p)->GetOpcode()), (*p)->size, + sizeof(MultiMoveItem_Struct) + sizeof(MoveItem_Struct)); + delete *p; + *p = nullptr; + return; + } + + SETUP_VAR_ENCODE(MultiMoveItem_Struct); + + int32 count = std::abs(emu->count); + ALLOC_VAR_ENCODE(structs::MultiMoveItem_Struct, sizeof(structs::MultiMoveItem_Struct) + + sizeof(structs::MultiMoveItemSub_Struct) * count); + + OUT(count); + + for (int i = 0; i < count; ++i) { + eq->moves[i].from_slot = ServerToRoF2Slot(emu->moves[i].from_slot); + eq->moves[i].to_slot = ServerToRoF2Slot(emu->moves[i].to_slot); + eq->moves[i].number_in_stack = emu->moves[i].number_in_stack; + } + + FINISH_ENCODE(); + } + ENCODE(OP_NewSpawn) { ENCODE_FORWARD(OP_ZoneSpawns); } ENCODE(OP_NewZone) @@ -5112,6 +5142,39 @@ namespace RoF2 FINISH_DIRECT_DECODE(); } + DECODE(OP_MoveMultipleItems) + { + // can't use our macros :( + if (__packet->size != + sizeof(structs::MultiMoveItem_Struct) + sizeof(structs::MultiMoveItemSub_Struct)) { + Log(Logs::Detail, Logs::Netcode, + "Wrong size on incoming %s (structs::MultiMoveItem_Struct): Got %d, expected %d", + opcodes->EmuToName(__packet->GetOpcode()), __packet->size, + sizeof(structs::MultiMoveItem_Struct) + sizeof(structs::MultiMoveItemSub_Struct)); + __packet->SetOpcode(OP_Unknown); /* invalidate the packet */ + return; + } + + // can be negative + int32 count = std::abs(*(int32 *)__packet->pBuffer); + + unsigned char *__eq_buffer = __packet->pBuffer; + __packet->size = sizeof(MultiMoveItem_Struct) + count * sizeof(MoveItem_Struct); + __packet->pBuffer = new unsigned char[__packet->size]; + MultiMoveItem_Struct *emu = (MultiMoveItem_Struct *) __packet->pBuffer; + structs::MultiMoveItem_Struct *eq = (structs::MultiMoveItem_Struct *) __eq_buffer; + + IN(count); + + for (int i = 0; i < count; ++i) { + emu->moves[i].from_slot = RoF2ToServerSlot(eq->moves[i].from_slot); + emu->moves[i].to_slot = RoF2ToServerSlot(eq->moves[i].to_slot); + emu->moves[i].number_in_stack = eq->moves[i].number_in_stack; + } + + FINISH_DIRECT_DECODE(); + } + DECODE(OP_PetCommands) { DECODE_LENGTH_EXACT(structs::PetCommand_Struct); diff --git a/common/patches/rof2_ops.h b/common/patches/rof2_ops.h index 2cd81aaee..c082952f6 100644 --- a/common/patches/rof2_ops.h +++ b/common/patches/rof2_ops.h @@ -95,6 +95,7 @@ E(OP_ManaChange) E(OP_MercenaryDataResponse) E(OP_MercenaryDataUpdate) E(OP_MoveItem) +E(OP_MoveMultipleItems) E(OP_NewSpawn) E(OP_NewZone) E(OP_OnLevelMessage) @@ -183,6 +184,7 @@ D(OP_ItemVerifyRequest) D(OP_LoadSpellSet) D(OP_LootItem) D(OP_MoveItem) +D(OP_MoveMultipleItems) D(OP_PetCommands) D(OP_RaidInvite) D(OP_ReadBook) diff --git a/common/patches/rof2_structs.h b/common/patches/rof2_structs.h index 5aa11977f..7264ed8b2 100644 --- a/common/patches/rof2_structs.h +++ b/common/patches/rof2_structs.h @@ -1837,7 +1837,7 @@ struct MultiMoveItemSub_Struct struct MultiMoveItem_Struct { -/*0000*/ uint32 count; +/*0000*/ int32 count; /*0004*/ MultiMoveItemSub_Struct moves[0]; }; diff --git a/common/patches/rof_ops.h b/common/patches/rof_ops.h index 9030a480b..3090911e8 100644 --- a/common/patches/rof_ops.h +++ b/common/patches/rof_ops.h @@ -80,6 +80,7 @@ E(OP_ManaChange) E(OP_MercenaryDataResponse) E(OP_MercenaryDataUpdate) E(OP_MoveItem) +E(OP_MoveMultipleItems) E(OP_NewSpawn) E(OP_NewZone) E(OP_OnLevelMessage) @@ -167,6 +168,7 @@ D(OP_ItemVerifyRequest) D(OP_LoadSpellSet) D(OP_LootItem) D(OP_MoveItem) +D(OP_MoveMultipleItems) D(OP_PetCommands) D(OP_RaidInvite) D(OP_ReadBook) diff --git a/common/patches/rof_structs.h b/common/patches/rof_structs.h index 8de4fb9ab..fbb70a4e5 100644 --- a/common/patches/rof_structs.h +++ b/common/patches/rof_structs.h @@ -1815,6 +1815,20 @@ struct MoveItem_Struct /*0028*/ }; +struct MultiMoveItemSub_Struct +{ +/*0000*/ InventorySlot_Struct from_slot; +/*0012*/ InventorySlot_Struct to_slot; +/*0024*/ uint32 number_in_stack; +/*0028*/ uint8 unknown[8]; +}; + +struct MultiMoveItem_Struct +{ +/*0000*/ int32 count; +/*0004*/ MultiMoveItemSub_Struct moves[0]; +}; + // // from_slot/to_slot // -1 - destroy diff --git a/common/patches/titanium.cpp b/common/patches/titanium.cpp index 45f267223..9246b4129 100644 --- a/common/patches/titanium.cpp +++ b/common/patches/titanium.cpp @@ -47,10 +47,12 @@ namespace Titanium // server to client inventory location converters static inline int16 ServerToTitaniumSlot(uint32 serverSlot); static inline int16 ServerToTitaniumCorpseSlot(uint32 serverCorpseSlot); + //static inline structs::InventorySlot_Struct ServerToNewStyleTitaniumSlot(uint32 serverSlot, ItemPacketType PacketType = ItemPacketInvalid); // client to server inventory location converters static inline uint32 TitaniumToServerSlot(int16 titaniumSlot); static inline uint32 TitaniumToServerCorpseSlot(int16 titaniumCorpseSlot); + //static inline uint32 NewStyleTitaniumToServerSlot(structs::InventorySlot_Struct titaniumSlot, ItemPacketType PacketType = ItemPacketInvalid); // server to client text link converter static inline void ServerToTitaniumTextLink(std::string& titaniumTextLink, const std::string& serverTextLink); @@ -2474,6 +2476,20 @@ namespace Titanium return titaniumCorpseSlot; } + /*static inline uint32 NewStyleTitaniumToServerSlot(structs::InventorySlot_Struct titaniumSlot, ItemPacketType PacketType) + { + uint32 ServerSlot = INVALID_INDEX; + uint32 TempSlot = 0; + + if (titaniumSlot.Type == invtype::InvTypePossessions) { // Worn/Personal Inventory and Cursor + TempSlot = titaniumSlot.Slot; + if (titaniumSlot.SubIndex >= EQEmu::inventory::containerBegin) // Bag Slots + TempSlot = (TempSlot + 3) * EQEmu::inventory::ContainerCount + titaniumSlot.SubIndex + 1; + + ServerSlot = TempSlot; + } + }*/ + static inline void ServerToTitaniumTextLink(std::string& titaniumTextLink, const std::string& serverTextLink) { if ((constants::SayLinkBodySize == EQEmu::legacy::TEXT_LINK_BODY_LENGTH) || (serverTextLink.find('\x12') == std::string::npos)) { diff --git a/common/patches/titanium_limits.h b/common/patches/titanium_limits.h index 3266ade6f..25170ed17 100644 --- a/common/patches/titanium_limits.h +++ b/common/patches/titanium_limits.h @@ -48,7 +48,7 @@ namespace Titanium InvTypeSharedBank, InvTypeTrade, InvTypeWorld, - InvTypeLimbo, + InvTypeLimbo, // this is where titanium actually ends InvTypeTribute, InvTypeGuildTribute, InvTypeMerchant, diff --git a/zone/client.h b/zone/client.h index e0732fd92..d9099726e 100644 --- a/zone/client.h +++ b/zone/client.h @@ -229,6 +229,13 @@ struct ClientReward uint32 amount; }; +// used for OP_MoveMultipleItems +struct InternalMultiMoveItem { + MoveItem_Struct move; + EQEmu::ItemInstance *item; + bool move_finished; +}; + class ClientFactory { public: Client *MakeClient(std::shared_ptr ieqs); @@ -869,6 +876,13 @@ public: bool SwapItem(MoveItem_Struct* move_in); void SwapItemResync(MoveItem_Struct* move_slots); void QSSwapItemAuditor(MoveItem_Struct* move_in, bool postaction_call = false); + bool MoveMultipleItems(MultiMoveItem_Struct *move_in); + bool MoveMultipleItems(std::deque &moves, bool send_packet = false); + bool MultiMovesGetItems(std::deque &moves); + bool MultiMovesFinish(std::deque &moves); + void MultiMovesFailReturnItems(std::deque &moves); + void MultiMovesFailRevertMoves(std::deque &moves); + void SendMoveMultipleItems(std::deque &moves); void PutLootInInventory(int16 slot_id, const EQEmu::ItemInstance &inst, ServerLootItem_Struct** bag_item_data = 0); bool AutoPutLootInInventory(EQEmu::ItemInstance& inst, bool try_worn = false, bool try_cursor = true, ServerLootItem_Struct** bag_item_data = 0); bool SummonItem(uint32 item_id, int16 charges = -1, uint32 aug1 = 0, uint32 aug2 = 0, uint32 aug3 = 0, uint32 aug4 = 0, uint32 aug5 = 0, uint32 aug6 = 0, bool attuned = false, uint16 to_slot = EQEmu::inventory::slotCursor, uint32 ornament_icon = 0, uint32 ornament_idfile = 0, uint32 ornament_hero_model = 0); diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 53fa8a27d..2a47b752b 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -9858,7 +9858,9 @@ void Client::Handle_OP_MoveItem(const EQApplicationPacket *app) void Client::Handle_OP_MoveMultipleItems(const EQApplicationPacket *app) { - Kick(); // TODO: lets not desync though + // verify size? Translators already fail if too small + // any state checking? + MoveMultipleItems((MultiMoveItem_Struct *)app->pBuffer); } void Client::Handle_OP_OpenContainer(const EQApplicationPacket *app) diff --git a/zone/inventory.cpp b/zone/inventory.cpp index 49e8e83b5..442659eb1 100644 --- a/zone/inventory.cpp +++ b/zone/inventory.cpp @@ -2088,6 +2088,218 @@ void Client::QSSwapItemAuditor(MoveItem_Struct* move_in, bool postaction_call) { safe_delete(qspack); } +/* The idea behind this is that we will move all the items out of their source slots + * into a temporary holding area. Then we will move them from the holding area to their + * destination slots. These steps can fail, and we will have to revert. + * + * The reason for this is because the client will say move item from slot x to slot y + * then move item from slot y to slot x. Which means we have to some sort of temporary + * area because otherwise we would just undo the move. + */ + +bool Client::MoveMultipleItems(MultiMoveItem_Struct *move_in) +{ + // We need somewhere to hold the moves and the items need some place to live temporarily + std::deque moves; + + for (int i = 0; i < move_in->count; ++i) { + InternalMultiMoveItem m; + m.move.from_slot = move_in->moves[i].from_slot; + m.move.to_slot = move_in->moves[i].to_slot; + m.move.number_in_stack = move_in->moves[i].number_in_stack; + m.item = nullptr; + m.move_finished = false; + + if (!IsValidSlot(m.move.from_slot)) { + Log(Logs::Detail, Logs::Inventory, "Invalid slot move from slot %u to slot %u with %u charges!", m.move.from_slot, m.move.to_slot, m.move.number_in_stack); + return false; + } + + if (!IsValidSlot(m.move.to_slot)) { + Log(Logs::Detail, Logs::Inventory, "Invalid slot move from slot %u to slot %u with %u charges!", m.move.from_slot, m.move.to_slot, m.move.number_in_stack); + return false; + } + + moves.push_back(m); + } + + return MoveMultipleItems(moves); +} + +bool Client::MoveMultipleItems(std::deque &moves, bool send_packet) +{ + if (!MultiMovesGetItems(moves)) { + Log(Logs::Detail, Logs::Inventory, "MultiMovesGetItems failed!"); + MultiMovesFailReturnItems(moves); + return false; + } + + // The client also calls a function called UpdateToSlots here, I think this will make sure items can fit in destination + // and find where to put items that need a home (ex. when bandolier needs to remove an item) + + if (!MultiMovesFinish(moves)) { + Log(Logs::Detail, Logs::Inventory, "MutliMovesFinish failed!"); + MultiMovesFailRevertMoves(moves); + MultiMovesFailReturnItems(moves); + return false; + } + + if (send_packet) + SendMoveMultipleItems(moves); + + // Now we need to save, we couldn't do it in processing because of failure + // could be better ... + // This may save slots multiple times, can rethink later ... + for (auto &m : moves) { + database.SaveInventory(character_id, m_inv.GetItem(m.move.from_slot), m.move.from_slot); + database.SaveInventory(character_id, m_inv.GetItem(m.move.to_slot), m.move.to_slot); + } + + return true; +} + +bool Client::MultiMovesFinish(std::deque &moves) +{ + for (auto &m : moves) { + if (!m.item) { // this shouldn't happen + Log(Logs::Detail, Logs::Inventory, "MultiMovesFinish encountered a null item!"); + return false; + } + + // check lore, we could be moving from shared to non-shared bank + if (CheckLoreConflict(m.item->GetItem())) { + Log(Logs::Detail, Logs::Inventory, "MultiMovesFinish encountered a lore conflict!"); + return false; + } + + // check we are allowed to equip it + + auto dst_inst = m_inv.GetItem(m.move.to_slot); + // so we need to verify each of these moves are good + auto parent_slot = m_inv.CalcSlotId(m.move.to_slot); + if (parent_slot != INVALID_INDEX) { // okay, we're moving into a bag, lets verify that + auto bag_inst = m_inv.GetItem(parent_slot); + + if (!bag_inst) { + Log(Logs::Detail, Logs::Inventory, "MultiMovesFinish tried moving into a null bag!"); + return false; + } + + if (!bag_inst->IsClassBag()) { + Log(Logs::Detail, Logs::Inventory, "MultiMovesFinish tried moving into a non-bag!"); + return false; + } + + if (!m_inv.CanItemFitInContainer(m.item->GetItem(), bag_inst->GetItem())) { + Log(Logs::Detail, Logs::Inventory, "MultiMovesFinish tried moving into a bag it can't fit in!"); + return false; + } + } + // so at this point, we know we're moving into a valid bag or not moving into a bag at all, time to check others + if (dst_inst) { // okay, there is still an item here, we must be stacking + if (m.item->GetCharges() + dst_inst->GetCharges() > m.item->GetItem()->StackSize) { + Log(Logs::Detail, Logs::Inventory, "MultiMoveFinish tried to over flow a stack!"); + return false; + } + + if (m.item->GetItem()->ID != dst_inst->GetItem()->ID) { + Log(Logs::Detail, Logs::Inventory, "MutliMoveFinish tried to stack different items!"); + return false; + } + + dst_inst->SetCharges(m.item->GetCharges() + dst_inst->GetCharges()); + } else { // okay, now we're not stacking so we just need to move + m_inv.PutItem(m.move.to_slot, *m.item); + } + m.move_finished = true; + safe_delete(m.item); + } + return true; +} + +/* This function will move item instances out of the inventory into a temporary holding area + * If this returns false, we need to move them back into their source slots + */ +bool Client::MultiMovesGetItems(std::deque &moves) +{ + for (auto &m : moves) { + auto inst = m_inv.GetItem(m.move.from_slot); + if (!inst) { // trying to move an item that doesn't exist ... + Log(Logs::Detail, Logs::Inventory, "MultiMovesGetItems tried to move from an empty slot!"); + return false; + } + + // if the item is stackable and we are not moving all the items out, we're splitting + if (m.move.number_in_stack > 0 && inst->IsStackable() && inst->GetCharges() != m.move.number_in_stack) { + if (inst->GetCharges() > m.move.number_in_stack) { + Log(Logs::Detail, Logs::Inventory, "MultiMovesGetItems tried to move more items in a stack than we have!"); + return false; + } + // new instance + auto new_inst = database.CreateItem(inst->GetItem(), m.move.number_in_stack); + // modify old + inst->SetCharges(inst->GetCharges() - m.move.number_in_stack); + m.item = new_inst; + } else { + m.item = m_inv.PopItem(m.move.from_slot); // we need to steal the item! + } + } + + return true; +} + +/* This function will move the items back from the temporary holding area after a fail + */ +void Client::MultiMovesFailReturnItems(std::deque &moves) +{ + for (auto &m : moves) { + if (!m.item) // if the item is null, we haven't stolen it yet, so nothing to fix + continue; + + // if we moved a partial stack, we need to put them all back + if (m.move.number_in_stack > 0 && m.item->IsStackable() && m.item->GetCharges() != m.move.number_in_stack) { + auto inst = m_inv.GetItem(m.move.from_slot); + if (!inst) { // well shit + safe_delete(m.item); + continue; + } + + inst->SetCharges(inst->GetCharges() + m.item->GetCharges()); + safe_delete(m.item); + } else { + m_inv.PutItem(m.move.from_slot, *m.item); + safe_delete(m.item); + } + } +} + +/* This function will move items back into the temporary holding area after a fail + */ +void Client::MultiMovesFailRevertMoves(std::deque &moves) +{ + for (auto &m : moves) { + if (!m.move_finished) // if we haven't set this to true, we have not put the item in it's destination + continue; + m.item = m_inv.PopItem(m.move.to_slot); + } +} + +void Client::SendMoveMultipleItems(std::deque &moves) +{ + auto app = new EQApplicationPacket(OP_MoveMultipleItems, sizeof(MultiMoveItem_Struct) + sizeof(MoveItem_Struct) * moves.size()); + auto mmi = (MultiMoveItem_Struct *)app->pBuffer; + mmi->count = moves.size(); + int i = 0; + + for (auto &e : moves) { + mmi->moves[i].from_slot = e.move.from_slot; + mmi->moves[i].to_slot = e.move.to_slot; + mmi->moves[i].number_in_stack = e.move.number_in_stack; + ++i; + } + FastQueuePacket(&app); +} + void Client::DyeArmor(EQEmu::TintProfile* dye){ int16 slot=0; for (int i = EQEmu::textures::textureBegin; i <= EQEmu::textures::LastTintableTexture; i++) {