[Feature] Implement Move Multiple Items (#4259)

* Implement Move Multiple Items

* Send LinkDead on invalid packet

* structure this more like MoveItem

* implement all modes

* remove un-needed debug message

* handle mode 3 swaps in bank\shared bank correctly.

* Revert "handle mode 3 swaps in bank\shared bank correctly."

This reverts commit ce01fbfde70d52e88381772a6c7a77b4b650c7c5.

* Revert "remove un-needed debug message"

This reverts commit f4b662459e11a60c3a46a97e5320757c4b2b9a84.

* handle mode 3 swaps without extra unintended code

* correct variable type

* remove magic numbers

* forgot a semicolon in emu_constants.h

* fix bad rebase artifact

* Remove unused struct

* apply changes discussed in PR

* last rebase conflict

* last rebase conflict

fix more inventory type enum refs

* fix windows build error

* fix other windows build error.

* fix duplication bug
This commit is contained in:
catapultam-habeo 2024-07-30 12:40:48 -05:00 committed by GitHub
parent d465a3deba
commit fc3c691588
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 141 additions and 1 deletions

View File

@ -1620,6 +1620,32 @@ struct MoveItem_Struct
/*0012*/
};
// New for RoF2 - Size: 12
struct InventorySlot_Struct
{
/*000*/ int16 Type; // Worn and Normal inventory = 0, Bank = 1, Shared Bank = 2, Delete Item = -1
/*002*/ int16 Unknown02;
/*004*/ int16 Slot;
/*006*/ int16 SubIndex;
/*008*/ int16 AugIndex; // Guessing - Seen 0xffff
/*010*/ int16 Unknown01; // Normally 0 - Seen 13262 when deleting an item, but didn't match item ID
/*012*/
};
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*/ uint32 count;
/*0004*/ MultiMoveItemSub_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)

View File

@ -10909,7 +10909,121 @@ void Client::Handle_OP_MoveItem(const EQApplicationPacket *app)
void Client::Handle_OP_MoveMultipleItems(const EQApplicationPacket *app)
{
Kick("Unimplemented move multiple items"); // TODO: lets not desync though
// This packet is only sent from the client if we ctrl click items in inventory
if (m_ClientVersionBit & EQ::versions::maskRoF2AndLater) {
if (!CharacterID()) {
LinkDead();
return;
}
if (app->size < sizeof(MultiMoveItem_Struct)) {
LinkDead();
return; // Not enough data to be a valid packet
}
const MultiMoveItem_Struct* multi_move = reinterpret_cast<const MultiMoveItem_Struct*>(app->pBuffer);
if (app->size != sizeof(MultiMoveItem_Struct) + sizeof(MultiMoveItemSub_Struct) * multi_move->count) {
LinkDead();
return; // Packet size does not match expected size
}
const int16 from_parent = multi_move->moves[0].from_slot.Slot;
const int16 to_parent = multi_move->moves[0].to_slot.Slot;
// CTRL + left click drops an item into a bag without opening it.
// This can be a bag, in which case it tries to fill the target bag with the contents of the bag on the cursor
// CTRL + right click swaps the contents of two bags if a bag is on your cursor and you ctrl-right click on another bag.
// We need to check if this is a swap or just an addition (left click or right click)
// Check if any component of this transaction is coming from anywhere other than the cursor
bool left_click = true;
for (int i = 0; i < multi_move->count; i++) {
if (multi_move->moves[i].from_slot.Slot != EQ::invslot::slotCursor) {
left_click = false;
}
}
// This is a left click which is purely additive. This should always be cursor object or cursor bag contents into general\bank\whatever bag
if (left_click) {
for (int i = 0; i < multi_move->count; i++) {
MoveItem_Struct* mi = new MoveItem_Struct();
mi->from_slot = multi_move->moves[i].from_slot.SubIndex == -1 ? multi_move->moves[i].from_slot.Slot : m_inv.CalcSlotId(multi_move->moves[i].from_slot.Slot, multi_move->moves[i].from_slot.SubIndex);
mi->to_slot = m_inv.CalcSlotId(multi_move->moves[i].to_slot.Slot, multi_move->moves[i].to_slot.SubIndex);
if (multi_move->moves[i].to_slot.Type == EQ::invtype::typeBank) { // Target is bank inventory
mi->to_slot = m_inv.CalcSlotId(multi_move->moves[i].to_slot.Slot + EQ::invslot::BANK_BEGIN, multi_move->moves[i].to_slot.SubIndex);
} else if (multi_move->moves[i].to_slot.Type == EQ::invtype::typeSharedBank) { // Target is shared bank inventory
mi->to_slot = m_inv.CalcSlotId(multi_move->moves[i].to_slot.Slot + EQ::invslot::SHARED_BANK_BEGIN, multi_move->moves[i].to_slot.SubIndex);
}
// This sends '1' as the stack count for unstackable items, which our titanium-era SwapItem blows up
if (m_inv.GetItem(mi->from_slot)->IsStackable()) {
mi->number_in_stack = multi_move->moves[i].number_in_stack;
} else {
mi->number_in_stack = 0;
}
if (!SwapItem(mi) && IsValidSlot(mi->from_slot) && IsValidSlot(mi->to_slot)) {
bool error = false;
SwapItemResync(mi);
InterrogateInventory(this, false, true, false, error, false);
if (error) {
InterrogateInventory(this, true, false, true, error);
}
}
}
// This is the swap.
// Client behavior is just to move stacks without combining them
// Items get rearranged to fill the 'top' of the bag first
} else {
struct MoveInfo {
EQ::ItemInstance* item;
uint16 to_slot;
};
std::vector<MoveInfo> items;
items.reserve(multi_move->count);
for (int i = 0; i < multi_move->count; i++) {
// These are always bags, so we don't need to worry about raw items in slotCursor
uint16 from_slot = m_inv.CalcSlotId(multi_move->moves[i].from_slot.Slot, multi_move->moves[i].from_slot.SubIndex);
if (multi_move->moves[i].from_slot.Type == EQ::invtype::typeBank) { // Target is bank inventory
from_slot = m_inv.CalcSlotId(multi_move->moves[i].from_slot.Slot + EQ::invslot::BANK_BEGIN, multi_move->moves[i].from_slot.SubIndex);
} else if (multi_move->moves[i].from_slot.Type == EQ::invtype::typeSharedBank) { // Target is shared bank inventory
from_slot = m_inv.CalcSlotId(multi_move->moves[i].from_slot.Slot + EQ::invslot::SHARED_BANK_BEGIN, multi_move->moves[i].from_slot.SubIndex);
}
uint16 to_slot = m_inv.CalcSlotId(multi_move->moves[i].to_slot.Slot, multi_move->moves[i].to_slot.SubIndex);
if (multi_move->moves[i].to_slot.Type == EQ::invtype::typeBank) { // Target is bank inventory
to_slot = m_inv.CalcSlotId(multi_move->moves[i].to_slot.Slot + EQ::invslot::BANK_BEGIN, multi_move->moves[i].to_slot.SubIndex);
} else if (multi_move->moves[i].to_slot.Type == EQ::invtype::typeSharedBank) { // Target is shared bank inventory
to_slot = m_inv.CalcSlotId(multi_move->moves[i].to_slot.Slot + EQ::invslot::SHARED_BANK_BEGIN, multi_move->moves[i].to_slot.SubIndex);
}
// I wasn't able to produce any error states on purpose.
MoveInfo move{
.item = m_inv.PopItem(from_slot), // Don't delete the instance here
.to_slot = to_slot
};
if (move.item) {
items.push_back(move);
database.SaveInventory(CharacterID(), NULL, from_slot); // We have to manually save inventory here.
} else {
LinkDead();
return; // Prevent inventory desync here. Forcing a resync would be better, but we don't have a MoveItem struct to work with.
}
}
for (const MoveInfo& move : items) {
PutItemInInventory(move.to_slot, *move.item); // This saves inventory too
}
}
} else {
LinkDead(); // This packet should not be sent by an older client
return;
}
}
void Client::Handle_OP_OpenContainer(const EQApplicationPacket *app)