From 9a5d2d2bc575b34f4a8464e4db2501d7a6a365d0 Mon Sep 17 00:00:00 2001 From: Uleat Date: Fri, 22 Aug 2014 20:48:11 -0400 Subject: [PATCH 1/3] Trade Stacking: BETA --- changelog.txt | 10 + common/item.cpp | 93 ++++++++ common/item.h | 1 + zone/common.h | 2 +- zone/inventory.cpp | 21 +- zone/trading.cpp | 533 ++++++++++++++++++++++++++++++--------------- 6 files changed, 475 insertions(+), 185 deletions(-) diff --git a/changelog.txt b/changelog.txt index bf2ea97f0..9d593e3e2 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,15 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 08/22/2014 == +Uleat: Rework of Trade::FinishedTrade() and Trade::ResetTrade() to parse items a little more intelligently. + +Trade window items are now sent to client inventory in this order: + - Bags + - Partial stack movements + - All remaining items + +If any of these procedures cause any problems, please post them immediately. + == 08/20/2014 == Uleat: Rework of Trade::AddEntity() - function used to move items into the trade window. Now accepts argument for 'stack_size' and updates client properly. Note: I tested trade with Titanium:{SoF,SoD,UF,RoF} in both directions and no client generated an OP_MoveItem event for attempting to place a stackable diff --git a/common/item.cpp b/common/item.cpp index 7d6618335..bbb6f14f8 100644 --- a/common/item.cpp +++ b/common/item.cpp @@ -654,6 +654,99 @@ int16 Inventory::FindFreeSlot(bool for_bag, bool try_cursor, uint8 min_size, boo return INVALID_INDEX; } +// This is a mix of HasSpaceForItem and FindFreeSlot..due to existing coding behavior, it was better to add a new helper function... +int16 Inventory::FindFreeSlotForTradeItem(const ItemInst* inst) { + // Do not arbitrarily use this function..it is designed for use with Client::ResetTrade() and Client::FinishTrade(). + // If you have a need, use it..but, understand it is not a compatible replacement for Inventory::FindFreeSlot(). + // + // I'll probably implement a bitmask in the new inventory system to avoid having to adjust stack bias -U + + if (!inst || !inst->GetID()) + return INVALID_INDEX; + + // step 1: find room for bags (caller should really ask for slots for bags first to avoid sending them to cursor..and bag item loss) + if (inst->IsType(ItemClassContainer)) { + for (int16 free_slot = EmuConstants::GENERAL_BEGIN; free_slot <= EmuConstants::GENERAL_END; ++free_slot) + if (!m_inv[free_slot]) + return free_slot; + + return MainCursor; // return cursor since bags do not stack and will not fit inside other bags..yet...) + } + + // step 2: find partial room for stackables + if (inst->IsStackable()) { + for (int16 free_slot = EmuConstants::GENERAL_BEGIN; free_slot <= EmuConstants::GENERAL_END; ++free_slot) { + const ItemInst* main_inst = m_inv[free_slot]; + + if (!main_inst) + continue; + + if ((main_inst->GetID() == inst->GetID()) && (main_inst->GetCharges() < main_inst->GetItem()->StackSize)) + return free_slot; + + if (main_inst->IsType(ItemClassContainer)) { // if item-specific containers already have bad items, we won't fix it here... + for (uint8 free_bag_slot = SUB_BEGIN; (free_bag_slot < main_inst->GetItem()->BagSlots) && (free_bag_slot < EmuConstants::ITEM_CONTAINER_SIZE); ++free_bag_slot) { + const ItemInst* sub_inst = main_inst->GetItem(free_bag_slot); + + if (!sub_inst) + continue; + + if ((sub_inst->GetID() == inst->GetID()) && (sub_inst->GetCharges() < sub_inst->GetItem()->StackSize)) + return Inventory::CalcSlotId(free_slot, free_bag_slot); + } + } + } + } + + // step 3a: find room for container-specific items (ItemClassArrow) + if (inst->GetItem()->ItemType == ItemTypeArrow) { + for (int16 free_slot = EmuConstants::GENERAL_BEGIN; free_slot <= EmuConstants::GENERAL_END; ++free_slot) { + const ItemInst* main_inst = m_inv[free_slot]; + + if (!main_inst || (main_inst->GetItem()->BagType != BagTypeQuiver) || !main_inst->IsType(ItemClassContainer)) + continue; + + for (uint8 free_bag_slot = SUB_BEGIN; (free_bag_slot < main_inst->GetItem()->BagSlots) && (free_bag_slot < EmuConstants::ITEM_CONTAINER_SIZE); ++free_bag_slot) + if (!main_inst->GetItem(free_bag_slot)) + return Inventory::CalcSlotId(free_slot, free_bag_slot); + } + } + + // step 3b: find room for container-specific items (ItemClassSmallThrowing) + if (inst->GetItem()->ItemType == ItemTypeSmallThrowing) { + for (int16 free_slot = EmuConstants::GENERAL_BEGIN; free_slot <= EmuConstants::GENERAL_END; ++free_slot) { + const ItemInst* main_inst = m_inv[free_slot]; + + if (!main_inst || (main_inst->GetItem()->BagType != BagTypeBandolier) || !main_inst->IsType(ItemClassContainer)) + continue; + + for (uint8 free_bag_slot = SUB_BEGIN; (free_bag_slot < main_inst->GetItem()->BagSlots) && (free_bag_slot < EmuConstants::ITEM_CONTAINER_SIZE); ++free_bag_slot) + if (!main_inst->GetItem(free_bag_slot)) + return Inventory::CalcSlotId(free_slot, free_bag_slot); + } + } + + // step 4: just find an empty slot + for (int16 free_slot = EmuConstants::GENERAL_BEGIN; free_slot <= EmuConstants::GENERAL_END; ++free_slot) { + const ItemInst* main_inst = m_inv[free_slot]; + + if (!main_inst) + return free_slot; + + if (main_inst->IsType(ItemClassContainer)) { + if ((main_inst->GetItem()->BagSize < inst->GetItem()->Size) || (main_inst->GetItem()->BagType == BagTypeBandolier) || (main_inst->GetItem()->BagType == BagTypeQuiver)) + continue; + + for (uint8 free_bag_slot = SUB_BEGIN; (free_bag_slot < main_inst->GetItem()->BagSlots) && (free_bag_slot < EmuConstants::ITEM_CONTAINER_SIZE); ++free_bag_slot) + if (!main_inst->GetItem(free_bag_slot)) + return Inventory::CalcSlotId(free_slot, free_bag_slot); + } + } + + //return INVALID_INDEX; // everything else pushes to the cursor + return MainCursor; +} + // Opposite of below: Get parent bag slot_id from a slot inside of bag int16 Inventory::CalcSlotId(int16 slot_id) { int16 parent_slot_id = INVALID_INDEX; diff --git a/common/item.h b/common/item.h index ef59ddd6f..f279bd2aa 100644 --- a/common/item.h +++ b/common/item.h @@ -172,6 +172,7 @@ public: // Locate an available inventory slot int16 FindFreeSlot(bool for_bag, bool try_cursor, uint8 min_size = 0, bool is_arrow = false); + int16 FindFreeSlotForTradeItem(const ItemInst* inst); // Calculate slot_id for an item within a bag static int16 CalcSlotId(int16 slot_id); // Calc parent bag's slot_id diff --git a/zone/common.h b/zone/common.h index 3cc1af1cf..eccffec62 100644 --- a/zone/common.h +++ b/zone/common.h @@ -526,7 +526,7 @@ public: Mob* With(); // Add item from cursor slot to trade bucket (automatically does bag data too) - void AddEntity(uint16 from_slot_id, uint16 trade_slot_id, uint32 stack_size); + void AddEntity(uint16 trade_slot_id, uint32 stack_size); // Audit trade void LogTrade(); diff --git a/zone/inventory.cpp b/zone/inventory.cpp index 5c5f80122..197841c76 100644 --- a/zone/inventory.cpp +++ b/zone/inventory.cpp @@ -822,25 +822,24 @@ bool Client::PushItemOnCursor(const ItemInst& inst, bool client_update) return database.SaveCursor(CharacterID(), s, e); } -bool Client::PutItemInInventory(int16 slot_id, const ItemInst& inst, bool client_update) -{ +bool Client::PutItemInInventory(int16 slot_id, const ItemInst& inst, bool client_update) { mlog(INVENTORY__SLOTS, "Putting item %s (%d) into slot %d", inst.GetItem()->Name, inst.GetItem()->ID, slot_id); + if (slot_id == MainCursor) - { - return PushItemOnCursor(inst,client_update); - } + return PushItemOnCursor(inst, client_update); else m_inv.PutItem(slot_id, inst); - if (client_update) { - SendItemPacket(slot_id, &inst, (slot_id == MainCursor) ? ItemPacketSummonItem : ItemPacketTrade); - } + if (client_update) + SendItemPacket(slot_id, &inst, ((slot_id == MainCursor) ? ItemPacketSummonItem : ItemPacketTrade)); if (slot_id == MainCursor) { - std::list::const_iterator s=m_inv.cursor_begin(),e=m_inv.cursor_end(); + std::list::const_iterator s = m_inv.cursor_begin(), e = m_inv.cursor_end(); return database.SaveCursor(this->CharacterID(), s, e); - } else + } + else { return database.SaveInventory(this->CharacterID(), &inst, slot_id); + } CalcBonuses(); } @@ -1539,7 +1538,7 @@ bool Client::SwapItem(MoveItem_Struct* move_in) { // Also sends trade information to other client of trade session if(RuleB(QueryServ, PlayerLogMoves)) { QSSwapItemAuditor(move_in); } // QS Audit - trade->AddEntity(src_slot_id, dst_slot_id, move_in->number_in_stack); + trade->AddEntity(dst_slot_id, move_in->number_in_stack); return true; } else { diff --git a/zone/trading.cpp b/zone/trading.cpp index 1efcbbb33..1ae9596b9 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -71,8 +71,8 @@ void Trade::Start(uint32 mob_id, bool initiate_with) } // Add item from a given slot to trade bucket (automatically does bag data too) -void Trade::AddEntity(uint16 from_slot_id, uint16 trade_slot_id, uint32 stack_size) { - // TODO: review for inventory saves +void Trade::AddEntity(uint16 trade_slot_id, uint32 stack_size) { + // TODO: review for inventory saves / consider changing return type to bool so failure can be passed to desync handler if (!owner || !owner->IsClient()) { // This should never happen @@ -121,7 +121,7 @@ void Trade::AddEntity(uint16 from_slot_id, uint16 trade_slot_id, uint32 stack_si if (_stack_size > 0) inst->SetCharges(_stack_size); else - client->DeleteItemInInventory(from_slot_id); + client->DeleteItemInInventory(MainCursor); SendItemData(inst2, trade_slot_id); } @@ -136,7 +136,7 @@ void Trade::AddEntity(uint16 from_slot_id, uint16 trade_slot_id, uint32 stack_si _log(TRADING__HOLDER, "%s added item '%s' to trade slot %i", owner->GetName(), inst->GetItem()->Name, trade_slot_id); client->PutItemInInventory(trade_slot_id, *inst); - client->DeleteItemInInventory(from_slot_id); + client->DeleteItemInInventory(MainCursor); } } @@ -316,206 +316,393 @@ void Trade::DumpTrade() #endif void Client::ResetTrade() { - const Item_Struct* TempItem = 0; - ItemInst* ins; - int x; AddMoneyToPP(trade->cp, trade->sp, trade->gp, trade->pp, true); - for(x = EmuConstants::TRADE_BEGIN; x <= EmuConstants::TRADE_END; x++) - { - TempItem = 0; - ins = GetInv().GetItem(x); - if (ins) - TempItem = ins->GetItem(); - if (TempItem) - { - bool is_arrow = (TempItem->ItemType == ItemTypeArrow) ? true : false; - int freeslotid = GetInv().FindFreeSlot(ins->IsType(ItemClassContainer), true, TempItem->Size, is_arrow); - if (freeslotid == INVALID_INDEX) - { - DropInst(ins); + + // step 1: process bags + for (int16 trade_slot = EmuConstants::TRADE_BEGIN; trade_slot <= EmuConstants::TRADE_END; ++trade_slot) { + const ItemInst* inst = m_inv[trade_slot]; + + if (inst && inst->IsType(ItemClassContainer)) { + int16 free_slot = m_inv.FindFreeSlotForTradeItem(inst); + + if (free_slot != INVALID_INDEX) { + PutItemInInventory(free_slot, *inst); + SendItemPacket(free_slot, inst, ItemPacketTrade); } - else - { - PutItemInInventory(freeslotid, *ins); - SendItemPacket(freeslotid, ins, ItemPacketTrade); + else { + DropInst(inst); } - DeleteItemInInventory(x); + + DeleteItemInInventory(trade_slot); + } + } + + // step 2a: process stackables + for (int16 trade_slot = EmuConstants::TRADE_BEGIN; trade_slot <= EmuConstants::TRADE_END; ++trade_slot) { + ItemInst* inst = GetInv().GetItem(trade_slot); + + if (inst && inst->IsStackable()) { + while (true) { + // there's no built-in safety check against an infinite loop..but, it should break on one of the conditional checks + int16 free_slot = m_inv.FindFreeSlotForTradeItem(inst); + + if ((free_slot == MainCursor) || (free_slot == INVALID_INDEX)) + break; + + ItemInst* partial_inst = GetInv().GetItem(free_slot); + + if (!partial_inst) + break; + + if (partial_inst->GetID() != inst->GetID()) { + _log(TRADING__ERROR, "Client::ResetTrade() - an incompatible location reference was returned by Inventory::FindFreeSlotForTradeItem()"); + + break; + } + + if ((partial_inst->GetCharges() + inst->GetCharges()) > partial_inst->GetItem()->StackSize) { + int16 new_charges = (partial_inst->GetCharges() + inst->GetCharges()) - partial_inst->GetItem()->StackSize; + + partial_inst->SetCharges(partial_inst->GetItem()->StackSize); + inst->SetCharges(new_charges); + } + else { + partial_inst->SetCharges(partial_inst->GetCharges() + inst->GetCharges()); + inst->SetCharges(0); + } + + PutItemInInventory(free_slot, *partial_inst); + SendItemPacket(free_slot, partial_inst, ItemPacketTrade); + + if (inst->GetCharges() == 0) { + DeleteItemInInventory(trade_slot); + + break; + } + } + } + } + + // step 2b: adjust trade stack bias + // (if any partial stacks exist before the final stack, FindFreeSlotForTradeItem() will return that slot in step 3 and an overwrite will occur) + for (int16 trade_slot = EmuConstants::TRADE_END; trade_slot >= EmuConstants::TRADE_BEGIN; --trade_slot) { + ItemInst* inst = GetInv().GetItem(trade_slot); + + if (inst && inst->IsStackable()) { + for (int16 bias_slot = EmuConstants::TRADE_BEGIN; bias_slot <= EmuConstants::TRADE_END; ++bias_slot) { + if (bias_slot >= trade_slot) + break; + + ItemInst* bias_inst = GetInv().GetItem(bias_slot); + + if (!bias_inst || (bias_inst->GetID() != inst->GetID()) || (bias_inst->GetCharges() >= bias_inst->GetItem()->StackSize)) + continue; + + if ((bias_inst->GetCharges() + inst->GetCharges()) > bias_inst->GetItem()->StackSize) { + int16 new_charges = (bias_inst->GetCharges() + inst->GetCharges()) - bias_inst->GetItem()->StackSize; + + bias_inst->SetCharges(bias_inst->GetItem()->StackSize); + inst->SetCharges(new_charges); + } + else { + bias_inst->SetCharges(bias_inst->GetCharges() + inst->GetCharges()); + inst->SetCharges(0); + } + + if (inst->GetCharges() == 0) { + DeleteItemInInventory(trade_slot); + + break; + } + } + } + } + + // step 3: process everything else + for (int16 trade_slot = EmuConstants::TRADE_BEGIN; trade_slot <= EmuConstants::TRADE_END; ++trade_slot) { + const ItemInst* inst = m_inv[trade_slot]; + + if (inst) { + int16 free_slot = m_inv.FindFreeSlotForTradeItem(inst); + + if (free_slot != INVALID_INDEX) { + PutItemInInventory(free_slot, *inst); + SendItemPacket(free_slot, inst, ItemPacketTrade); + } + else { + DropInst(inst); + } + + DeleteItemInInventory(trade_slot); } } } void Client::FinishTrade(Mob* tradingWith, ServerPacket* qspack, bool finalizer) { - if(tradingWith && tradingWith->IsClient()) { Client* other = tradingWith->CastToClient(); if(other) { mlog(TRADING__CLIENT, "Finishing trade with client %s", other->GetName()); - int16 slot_id; - const Item_Struct* item = nullptr; - QSPlayerLogTrade_Struct* qsaudit = nullptr; - bool QSPLT = false; - - // QS code - if(qspack && RuleB(QueryServ, PlayerLogTrades)) { - qsaudit = (QSPlayerLogTrade_Struct*) qspack->pBuffer; - QSPLT = true; - - if(finalizer) { qsaudit->char2_id = this->character_id; } - else { qsaudit->char1_id = this->character_id; } - } - - // Move each trade slot into free inventory slot - for(int16 i = EmuConstants::TRADE_BEGIN; i <= EmuConstants::TRADE_END; i++){ - const ItemInst* inst = m_inv[i]; - uint16 parent_offset = 0; - - if(inst == nullptr) { continue; } - - mlog(TRADING__CLIENT, "Giving %s (%d) in slot %d to %s", inst->GetItem()->Name, inst->GetItem()->ID, i, other->GetName()); - - /// Log Player Trades through QueryServ if Rule Enabled - if(QSPLT) { - uint16 item_count = qsaudit->char1_count + qsaudit->char2_count; - parent_offset = item_count; - - qsaudit->items[item_count].from_id = this->character_id; - qsaudit->items[item_count].from_slot = i; - qsaudit->items[item_count].to_id = other->CharacterID(); - qsaudit->items[item_count].to_slot = 0; - qsaudit->items[item_count].item_id = inst->GetID(); - qsaudit->items[item_count].charges = inst->GetCharges(); - qsaudit->items[item_count].aug_1 = inst->GetAugmentItemID(1); - qsaudit->items[item_count].aug_2 = inst->GetAugmentItemID(2); - qsaudit->items[item_count].aug_3 = inst->GetAugmentItemID(3); - qsaudit->items[item_count].aug_4 = inst->GetAugmentItemID(4); - qsaudit->items[item_count].aug_5 = inst->GetAugmentItemID(5); - - if(finalizer) { qsaudit->char2_count++; } - else { qsaudit->char1_count++; } - - if(inst->IsType(ItemClassContainer)) { - // Pseudo-Slot ID's are generated based on how the db saves bag items... - for(uint8 j = SUB_BEGIN; j < inst->GetItem()->BagSlots; j++) { - const ItemInst* baginst = inst->GetItem(j); - - if(baginst == nullptr) { continue; } - - int16 k=Inventory::CalcSlotId(i, j); - item_count = qsaudit->char1_count + qsaudit->char2_count; - - qsaudit->items[item_count].from_id = this->character_id; - qsaudit->items[item_count].from_slot = k; - qsaudit->items[item_count].to_id = other->CharacterID(); - qsaudit->items[item_count].to_slot = 0; - qsaudit->items[item_count].item_id = baginst->GetID(); - qsaudit->items[item_count].charges = baginst->GetCharges(); - qsaudit->items[item_count].aug_1 = baginst->GetAugmentItemID(1); - qsaudit->items[item_count].aug_2 = baginst->GetAugmentItemID(2); - qsaudit->items[item_count].aug_3 = baginst->GetAugmentItemID(3); - qsaudit->items[item_count].aug_4 = baginst->GetAugmentItemID(4); - qsaudit->items[item_count].aug_5 = baginst->GetAugmentItemID(5); - - if(finalizer) { qsaudit->char2_count++; } - else { qsaudit->char1_count++; } - } - } - } - - if (inst->GetItem()->NoDrop != 0 || Admin() >= RuleI(Character, MinStatusForNoDropExemptions) || RuleI(World, FVNoDropFlag) == 1 || other == this) { - bool is_arrow = (inst->GetItem()->ItemType == ItemTypeArrow) ? true : false; - slot_id = other->GetInv().FindFreeSlot(inst->IsType(ItemClassContainer), true, inst->GetItem()->Size, is_arrow); - - mlog(TRADING__CLIENT, "Trying to put %s (%d) into slot %d", inst->GetItem()->Name, inst->GetItem()->ID, slot_id); - - if(other->PutItemInInventory(slot_id, *inst, true)) { - mlog(TRADING__CLIENT, "Item %s (%d) successfully transfered, deleting from trade slot.", inst->GetItem()->Name, inst->GetItem()->ID); - - if(QSPLT) { - qsaudit->items[parent_offset].to_slot = slot_id; - - if(inst->IsType(ItemClassContainer)) { - for(uint8 bagslot_idx = SUB_BEGIN; bagslot_idx < inst->GetItem()->BagSlots; bagslot_idx++) { - const ItemInst* bag_inst = inst->GetItem(bagslot_idx); - - if(bag_inst == nullptr) { continue; } - int16 to_bagslot_id = Inventory::CalcSlotId(slot_id, bagslot_idx); - - qsaudit->items[++parent_offset].to_slot = to_bagslot_id; - } - } - } - } - else { - PushItemOnCursor(*inst, true); - mlog(TRADING__ERROR, "Unable to give item %d (%d) to %s, returning to giver.", inst->GetItem()->Name, inst->GetItem()->ID, other->GetName()); - - if(QSPLT) { - qsaudit->items[parent_offset].to_id = this->character_id; - qsaudit->items[parent_offset].to_slot = MainCursor; - - if(inst->IsType(ItemClassContainer)) { - for(uint8 bagslot_idx = SUB_BEGIN; bagslot_idx < inst->GetItem()->BagSlots; bagslot_idx++) { - const ItemInst* bag_inst = inst->GetItem(bagslot_idx); - - if(bag_inst == nullptr) { continue; } - int16 to_bagslot_id = Inventory::CalcSlotId(MainCursor, bagslot_idx); - - qsaudit->items[++parent_offset].to_id = this->character_id; - qsaudit->items[parent_offset].to_slot = to_bagslot_id; - } - } - } - } - - DeleteItemInInventory(i); - } - else { - PushItemOnCursor(*inst, true); - DeleteItemInInventory(i); - - if(QSPLT) { - qsaudit->items[parent_offset].to_id = this->character_id; - qsaudit->items[parent_offset].to_slot = MainCursor; - - if(inst->IsType(ItemClassContainer)) { - for(uint8 bagslot_idx = SUB_BEGIN; bagslot_idx < inst->GetItem()->BagSlots; bagslot_idx++) { - const ItemInst* bag_inst = inst->GetItem(bagslot_idx); - - if(bag_inst == nullptr) { continue; } - int16 to_bagslot_id = Inventory::CalcSlotId(MainCursor, bagslot_idx); - - qsaudit->items[++parent_offset].to_id = this->character_id; - qsaudit->items[parent_offset].to_slot = to_bagslot_id; - } - } - } - } - } - - // Money - look into how NPC's receive cash this->AddMoneyToPP(other->trade->cp, other->trade->sp, other->trade->gp, other->trade->pp, true); - // This is currently setup to show character offers, not receipts - if(QSPLT) { - if(finalizer) { + // step 0: pre-processing + // QS code + if (qspack && RuleB(QueryServ, PlayerLogTrades)) { + QSPlayerLogTrade_Struct* qsaudit = (QSPlayerLogTrade_Struct*)qspack->pBuffer; + + if (finalizer) { + qsaudit->char2_id = this->character_id; + qsaudit->char2_money.platinum = this->trade->pp; qsaudit->char2_money.gold = this->trade->gp; qsaudit->char2_money.silver = this->trade->sp; qsaudit->char2_money.copper = this->trade->cp; } else { - qsaudit->char1_money.platinum = this->trade->pp; + qsaudit->char1_id = this->character_id; + + qsaudit->char1_money.platinum = this->trade->pp; qsaudit->char1_money.gold = this->trade->gp; qsaudit->char1_money.silver = this->trade->sp; qsaudit->char1_money.copper = this->trade->cp; } + + // qsaudit->items[x].to_slot is disabled until QueryServ:PlayerLogTrades code is updated + for (int16 trade_slot = EmuConstants::TRADE_BEGIN; trade_slot <= EmuConstants::TRADE_END; ++trade_slot) { + const ItemInst* inst = m_inv[trade_slot]; + + if (!inst) + continue; + + uint16 item_offset = qsaudit->char1_count + qsaudit->char2_count; + + qsaudit->items[item_offset].from_id = this->character_id; + qsaudit->items[item_offset].from_slot = trade_slot; + qsaudit->items[item_offset].to_id = other->CharacterID(); + qsaudit->items[item_offset].to_slot = 0; // disabled + qsaudit->items[item_offset].item_id = inst->GetID(); + qsaudit->items[item_offset].charges = inst->GetCharges(); + qsaudit->items[item_offset].aug_1 = inst->GetAugmentItemID(1); + qsaudit->items[item_offset].aug_2 = inst->GetAugmentItemID(2); + qsaudit->items[item_offset].aug_3 = inst->GetAugmentItemID(3); + qsaudit->items[item_offset].aug_4 = inst->GetAugmentItemID(4); + qsaudit->items[item_offset].aug_5 = inst->GetAugmentItemID(5); + + if (finalizer) + ++qsaudit->char2_count; + else + ++qsaudit->char1_count; + + if (inst->IsType(ItemClassContainer)) { + // Pseudo-Slot ID's are generated based on how the db saves bag items... + for (uint8 sub_slot = SUB_BEGIN; sub_slot < inst->GetItem()->BagSlots; ++sub_slot) { + const ItemInst* sub_inst = inst->GetItem(sub_slot); + + if (!sub_inst) + continue; + + int16 from_slot = Inventory::CalcSlotId(trade_slot, sub_slot); + item_offset = qsaudit->char1_count + qsaudit->char2_count; + + qsaudit->items[item_offset].from_id = this->character_id; + qsaudit->items[item_offset].from_slot = from_slot; + qsaudit->items[item_offset].to_id = other->CharacterID(); + qsaudit->items[item_offset].to_slot = 0; // disabled + qsaudit->items[item_offset].item_id = sub_inst->GetID(); + qsaudit->items[item_offset].charges = sub_inst->GetCharges(); + qsaudit->items[item_offset].aug_1 = sub_inst->GetAugmentItemID(1); + qsaudit->items[item_offset].aug_2 = sub_inst->GetAugmentItemID(2); + qsaudit->items[item_offset].aug_3 = sub_inst->GetAugmentItemID(3); + qsaudit->items[item_offset].aug_4 = sub_inst->GetAugmentItemID(4); + qsaudit->items[item_offset].aug_5 = sub_inst->GetAugmentItemID(5); + + if (finalizer) + ++qsaudit->char2_count; + else + ++qsaudit->char1_count; + } + } + } + } + + // step 1: process bags + for (int16 trade_slot = EmuConstants::TRADE_BEGIN; trade_slot <= EmuConstants::TRADE_END; ++trade_slot) { + const ItemInst* inst = m_inv[trade_slot]; + + if (inst && inst->IsType(ItemClassContainer)) { + mlog(TRADING__CLIENT, "Giving container %s (%d) in slot %d to %s", inst->GetItem()->Name, inst->GetItem()->ID, trade_slot, other->GetName()); + + // TODO: need to check bag items/augments for no drop..everything for attuned... + if (inst->GetItem()->NoDrop != 0 || Admin() >= RuleI(Character, MinStatusForNoDropExemptions) || RuleI(World, FVNoDropFlag) == 1 || other == this) { + int16 free_slot = other->GetInv().FindFreeSlotForTradeItem(inst); + + if (free_slot != INVALID_INDEX) { + if (other->PutItemInInventory(free_slot, *inst, true)) { + mlog(TRADING__CLIENT, "Container %s (%d) successfully transferred, deleting from trade slot.", inst->GetItem()->Name, inst->GetItem()->ID); + } + else { + mlog(TRADING__ERROR, "Transfer of container %s (%d) to %s failed, returning to giver.", inst->GetItem()->Name, inst->GetItem()->ID, other->GetName()); + + PushItemOnCursor(*inst, true); + } + } + else { + mlog(TRADING__ERROR, "%s's inventory is full, returning container %s (%d) to giver.", other->GetName(), inst->GetItem()->Name, inst->GetItem()->ID); + + PushItemOnCursor(*inst, true); + } + } + else { + mlog(TRADING__ERROR, "Container %s (%d) is NoDrop, returning to giver.", inst->GetItem()->Name, inst->GetItem()->ID); + + PushItemOnCursor(*inst, true); + } + + DeleteItemInInventory(trade_slot); + } + } + + // step 2a: process stackables + for (int16 trade_slot = EmuConstants::TRADE_BEGIN; trade_slot <= EmuConstants::TRADE_END; ++trade_slot) { + ItemInst* inst = GetInv().GetItem(trade_slot); + + if (inst && inst->IsStackable()) { + while (true) { + // there's no built-in safety check against an infinite loop..but, it should break on one of the conditional checks + int16 partial_slot = other->GetInv().FindFreeSlotForTradeItem(inst); + + if ((partial_slot == MainCursor) || (partial_slot == INVALID_INDEX)) + break; + + ItemInst* partial_inst = other->GetInv().GetItem(partial_slot); + + if (!partial_inst) + break; + + if (partial_inst->GetID() != inst->GetID()) { + _log(TRADING__ERROR, "Client::ResetTrade() - an incompatible location reference was returned by Inventory::FindFreeSlotForTradeItem()"); + + break; + } + + int16 old_charges = inst->GetCharges(); + int16 partial_charges = partial_inst->GetCharges(); + + if ((partial_inst->GetCharges() + inst->GetCharges()) > partial_inst->GetItem()->StackSize) { + int16 new_charges = (partial_inst->GetCharges() + inst->GetCharges()) - partial_inst->GetItem()->StackSize; + + partial_inst->SetCharges(partial_inst->GetItem()->StackSize); + inst->SetCharges(new_charges); + } + else { + partial_inst->SetCharges(partial_inst->GetCharges() + inst->GetCharges()); + inst->SetCharges(0); + } + + mlog(TRADING__CLIENT, "Transferring partial stack %s (%d) in slot %d to %s", inst->GetItem()->Name, inst->GetItem()->ID, trade_slot, other->GetName()); + + if (other->PutItemInInventory(partial_slot, *partial_inst, true)) { + mlog(TRADING__CLIENT, "Partial stack %s (%d) successfully transferred, deleting %i charges from trade slot.", + inst->GetItem()->Name, inst->GetItem()->ID, (old_charges - inst->GetCharges())); + } + else { + mlog(TRADING__ERROR, "Transfer of partial stack %s (%d) to %s failed, returning %i charges to trade slot.", + inst->GetItem()->Name, inst->GetItem()->ID, other->GetName(), (old_charges - inst->GetCharges())); + + inst->SetCharges(old_charges); + partial_inst->SetCharges(partial_charges); + + break; + } + + if (inst->GetCharges() == 0) { + DeleteItemInInventory(trade_slot); + + break; + } + } + } + } + + // step 2b: adjust trade stack bias + // (if any partial stacks exist before the final stack, FindFreeSlotForTradeItem() will return that slot in step 3 and an overwrite will occur) + for (int16 trade_slot = EmuConstants::TRADE_END; trade_slot >= EmuConstants::TRADE_BEGIN; --trade_slot) { + ItemInst* inst = GetInv().GetItem(trade_slot); + + if (inst && inst->IsStackable()) { + for (int16 bias_slot = EmuConstants::TRADE_BEGIN; bias_slot <= EmuConstants::TRADE_END; ++bias_slot) { + if (bias_slot >= trade_slot) + break; + + ItemInst* bias_inst = GetInv().GetItem(bias_slot); + + if (!bias_inst || (bias_inst->GetID() != inst->GetID()) || (bias_inst->GetCharges() >= bias_inst->GetItem()->StackSize)) + continue; + + if ((bias_inst->GetCharges() + inst->GetCharges()) > bias_inst->GetItem()->StackSize) { + int16 new_charges = (bias_inst->GetCharges() + inst->GetCharges()) - bias_inst->GetItem()->StackSize; + + bias_inst->SetCharges(bias_inst->GetItem()->StackSize); + inst->SetCharges(new_charges); + } + else { + bias_inst->SetCharges(bias_inst->GetCharges() + inst->GetCharges()); + inst->SetCharges(0); + } + + if (inst->GetCharges() == 0) { + DeleteItemInInventory(trade_slot); + + break; + } + } + } + } + + // step 3: process everything else + for (int16 trade_slot = EmuConstants::TRADE_BEGIN; trade_slot <= EmuConstants::TRADE_END; ++trade_slot) { + const ItemInst* inst = m_inv[trade_slot]; + + if (inst) { + mlog(TRADING__CLIENT, "Giving item %s (%d) in slot %d to %s", inst->GetItem()->Name, inst->GetItem()->ID, trade_slot, other->GetName()); + + // TODO: need to check bag items/augments for no drop..everything for attuned... + if (inst->GetItem()->NoDrop != 0 || Admin() >= RuleI(Character, MinStatusForNoDropExemptions) || RuleI(World, FVNoDropFlag) == 1 || other == this) { + int16 free_slot = other->GetInv().FindFreeSlotForTradeItem(inst); + + if (free_slot != INVALID_INDEX) { + if (other->PutItemInInventory(free_slot, *inst, true)) { + mlog(TRADING__CLIENT, "Item %s (%d) successfully transferred, deleting from trade slot.", inst->GetItem()->Name, inst->GetItem()->ID); + } + else { + mlog(TRADING__ERROR, "Transfer of Item %s (%d) to %s failed, returning to giver.", inst->GetItem()->Name, inst->GetItem()->ID, other->GetName()); + + PushItemOnCursor(*inst, true); + } + } + else { + mlog(TRADING__ERROR, "%s's inventory is full, returning item %s (%d) to giver.", other->GetName(), inst->GetItem()->Name, inst->GetItem()->ID); + + PushItemOnCursor(*inst, true); + } + } + else { + mlog(TRADING__ERROR, "Item %s (%d) is NoDrop, returning to giver.", inst->GetItem()->Name, inst->GetItem()->ID); + + PushItemOnCursor(*inst, true); + } + + DeleteItemInInventory(trade_slot); + } } //Do not reset the trade here, done by the caller. } } + + // trading with npc doesn't require Inventory::FindFreeSlotForTradeItem() rework else if(tradingWith && tradingWith->IsNPC()) { QSPlayerLogHandin_Struct* qsaudit = nullptr; bool QSPLH = false; From d4a9fed45eeae224f64dfaa93bb3920af19a6cff Mon Sep 17 00:00:00 2001 From: Uleat Date: Mon, 25 Aug 2014 22:29:00 -0400 Subject: [PATCH 2/3] Added QS code to Client::FinishTrade() --- zone/client.h | 2 +- zone/client_packet.cpp | 75 +++++++--- zone/trading.cpp | 320 +++++++++++++++++++++++++---------------- 3 files changed, 250 insertions(+), 147 deletions(-) diff --git a/zone/client.h b/zone/client.h index f2d16ea0f..d3aed5540 100644 --- a/zone/client.h +++ b/zone/client.h @@ -268,7 +268,7 @@ public: void TradeRequestFailed(const EQApplicationPacket* app); void BuyTraderItem(TraderBuy_Struct* tbs,Client* trader,const EQApplicationPacket* app); void TraderUpdate(uint16 slot_id,uint32 trader_id); - void FinishTrade(Mob* with, ServerPacket* qspack = nullptr, bool finalizer = false); + void FinishTrade(Mob* with, bool finalizer = false, void* event_entry = nullptr, std::list* event_details = nullptr); void SendZonePoints(); void SendBuyerResults(char *SearchQuery, uint32 SearchID); diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index f35ec0565..8d39f815a 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -4862,6 +4862,7 @@ void Client::Handle_OP_TradeAcceptClick(const EQApplicationPacket *app) { Mob* with = trade->With(); trade->state = TradeAccepted; + if (with && with->IsClient()) { //finish trade... // Have both accepted? @@ -4872,6 +4873,7 @@ void Client::Handle_OP_TradeAcceptClick(const EQApplicationPacket *app) other->trade->state = TradeCompleting; trade->state = TradeCompleting; + // should we do this for NoDrop items as well? if (CheckTradeLoreConflict(other) || other->CheckTradeLoreConflict(this)) { Message_StringID(13, TRADE_CANCEL_LORE); other->Message_StringID(13, TRADE_CANCEL_LORE); @@ -4887,23 +4889,37 @@ void Client::Handle_OP_TradeAcceptClick(const EQApplicationPacket *app) // start QS code if(RuleB(QueryServ, PlayerLogTrades)) { - uint16 trade_count = 0; + QSPlayerLogTrade_Struct event_entry; + std::list event_details; - // Item trade count for packet sizing - for(int16 slot_id = EmuConstants::TRADE_BEGIN; slot_id <= EmuConstants::TRADE_END; slot_id++) { - if(other->GetInv().GetItem(slot_id)) { trade_count += other->GetInv().GetItem(slot_id)->GetTotalItemCount(); } - if(m_inv[slot_id]) { trade_count += m_inv[slot_id]->GetTotalItemCount(); } - } - - ServerPacket* qspack = new ServerPacket(ServerOP_QSPlayerLogTrades, sizeof(QSPlayerLogTrade_Struct) + (sizeof(QSTradeItems_Struct) * trade_count)); + memset(&event_entry, 0, sizeof(QSPlayerLogTrade_Struct)); // Perform actual trade - this->FinishTrade(other, qspack, true); - other->FinishTrade(this, qspack, false); + this->FinishTrade(other, true, &event_entry, &event_details); + other->FinishTrade(this, false, &event_entry, &event_details); - qspack->Deflate(); - if(worldserver.Connected()) { worldserver.SendPacket(qspack); } - safe_delete(qspack); + ServerPacket* qs_pack = new ServerPacket(ServerOP_QSPlayerLogTrades, sizeof(QSPlayerLogTrade_Struct)+(sizeof(QSTradeItems_Struct)* event_details.size())); + + QSPlayerLogTrade_Struct* qs_buf = (QSPlayerLogTrade_Struct*)qs_pack->pBuffer; + + memcpy(qs_buf, &event_entry, sizeof(QSPlayerLogTrade_Struct)); + + int offset = 0; + + for (std::list::iterator iter = event_details.begin(); iter != event_details.end(); ++iter, ++offset) { + QSTradeItems_Struct* detail = reinterpret_cast(*iter); + qs_buf->items[offset] = *detail; + safe_delete(detail); + } + + event_details.clear(); + + qs_pack->Deflate(); + + if(worldserver.Connected()) + worldserver.SendPacket(qs_pack); + + safe_delete(qs_pack); // end QS code } else { @@ -4928,25 +4944,42 @@ void Client::Handle_OP_TradeAcceptClick(const EQApplicationPacket *app) if(with->IsNPC()) { // Audit trade to database for player trade stream if(RuleB(QueryServ, PlayerLogHandins)) { - uint16 handin_count = 0; + QSPlayerLogHandin_Struct event_entry; + std::list event_details; - for(int16 slot_id = EmuConstants::TRADE_BEGIN; slot_id <= EmuConstants::TRADE_NPC_END; slot_id++) { - if(m_inv[slot_id]) { handin_count += m_inv[slot_id]->GetTotalItemCount(); } + memset(&event_entry, 0, sizeof(QSPlayerLogHandin_Struct)); + + FinishTrade(with->CastToNPC(), false, &event_entry, &event_details); + + ServerPacket* qs_pack = new ServerPacket(ServerOP_QSPlayerLogHandins, sizeof(QSPlayerLogHandin_Struct)+(sizeof(QSHandinItems_Struct)* event_details.size())); + + QSPlayerLogHandin_Struct* qs_buf = (QSPlayerLogHandin_Struct*)qs_pack->pBuffer; + + memcpy(qs_buf, &event_entry, sizeof(QSPlayerLogHandin_Struct)); + + int offset = 0; + + for (std::list::iterator iter = event_details.begin(); iter != event_details.end(); ++iter, ++offset) { + QSHandinItems_Struct* detail = reinterpret_cast(*iter); + qs_buf->items[offset] = *detail; + safe_delete(detail); } - ServerPacket* qspack = new ServerPacket(ServerOP_QSPlayerLogHandins, sizeof(QSPlayerLogHandin_Struct) + (sizeof(QSHandinItems_Struct) * handin_count)); + event_details.clear(); - FinishTrade(with->CastToNPC(), qspack); + qs_pack->Deflate(); - qspack->Deflate(); - if(worldserver.Connected()) { worldserver.SendPacket(qspack); } - safe_delete(qspack); + if(worldserver.Connected()) + worldserver.SendPacket(qs_pack); + + safe_delete(qs_pack); } else { FinishTrade(with->CastToNPC()); } } #ifdef BOTS + // TODO: Log Bot trades else if(with->IsBot()) with->CastToBot()->FinishTrade(this, Bot::BotTradeClientNormal); #endif diff --git a/zone/trading.cpp b/zone/trading.cpp index 1ae9596b9..88b277651 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -22,7 +22,10 @@ #include "../common/rulesys.h" #include "quest_parser_collection.h" #include "worldserver.h" +#include "queryserv.h" + extern WorldServer worldserver; +extern QueryServ* QServ; // The maximum amount of a single bazaar/barter transaction expressed in copper. // Equivalent to 2 Million plat @@ -438,92 +441,38 @@ void Client::ResetTrade() { } } -void Client::FinishTrade(Mob* tradingWith, ServerPacket* qspack, bool finalizer) { +void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, std::list* event_details) { if(tradingWith && tradingWith->IsClient()) { Client* other = tradingWith->CastToClient(); + QSPlayerLogTrade_Struct* qs_audit = nullptr; + bool qs_log = false; if(other) { mlog(TRADING__CLIENT, "Finishing trade with client %s", other->GetName()); this->AddMoneyToPP(other->trade->cp, other->trade->sp, other->trade->gp, other->trade->pp, true); - + // step 0: pre-processing // QS code - if (qspack && RuleB(QueryServ, PlayerLogTrades)) { - QSPlayerLogTrade_Struct* qsaudit = (QSPlayerLogTrade_Struct*)qspack->pBuffer; - + if (RuleB(QueryServ, PlayerLogTrades) && event_entry && event_details) { + qs_audit = (QSPlayerLogTrade_Struct*)event_entry; + qs_log = true; + if (finalizer) { - qsaudit->char2_id = this->character_id; + qs_audit->char2_id = this->character_id; - qsaudit->char2_money.platinum = this->trade->pp; - qsaudit->char2_money.gold = this->trade->gp; - qsaudit->char2_money.silver = this->trade->sp; - qsaudit->char2_money.copper = this->trade->cp; + qs_audit->char2_money.platinum = this->trade->pp; + qs_audit->char2_money.gold = this->trade->gp; + qs_audit->char2_money.silver = this->trade->sp; + qs_audit->char2_money.copper = this->trade->cp; } else { - qsaudit->char1_id = this->character_id; + qs_audit->char1_id = this->character_id; - qsaudit->char1_money.platinum = this->trade->pp; - qsaudit->char1_money.gold = this->trade->gp; - qsaudit->char1_money.silver = this->trade->sp; - qsaudit->char1_money.copper = this->trade->cp; - } - - // qsaudit->items[x].to_slot is disabled until QueryServ:PlayerLogTrades code is updated - for (int16 trade_slot = EmuConstants::TRADE_BEGIN; trade_slot <= EmuConstants::TRADE_END; ++trade_slot) { - const ItemInst* inst = m_inv[trade_slot]; - - if (!inst) - continue; - - uint16 item_offset = qsaudit->char1_count + qsaudit->char2_count; - - qsaudit->items[item_offset].from_id = this->character_id; - qsaudit->items[item_offset].from_slot = trade_slot; - qsaudit->items[item_offset].to_id = other->CharacterID(); - qsaudit->items[item_offset].to_slot = 0; // disabled - qsaudit->items[item_offset].item_id = inst->GetID(); - qsaudit->items[item_offset].charges = inst->GetCharges(); - qsaudit->items[item_offset].aug_1 = inst->GetAugmentItemID(1); - qsaudit->items[item_offset].aug_2 = inst->GetAugmentItemID(2); - qsaudit->items[item_offset].aug_3 = inst->GetAugmentItemID(3); - qsaudit->items[item_offset].aug_4 = inst->GetAugmentItemID(4); - qsaudit->items[item_offset].aug_5 = inst->GetAugmentItemID(5); - - if (finalizer) - ++qsaudit->char2_count; - else - ++qsaudit->char1_count; - - if (inst->IsType(ItemClassContainer)) { - // Pseudo-Slot ID's are generated based on how the db saves bag items... - for (uint8 sub_slot = SUB_BEGIN; sub_slot < inst->GetItem()->BagSlots; ++sub_slot) { - const ItemInst* sub_inst = inst->GetItem(sub_slot); - - if (!sub_inst) - continue; - - int16 from_slot = Inventory::CalcSlotId(trade_slot, sub_slot); - item_offset = qsaudit->char1_count + qsaudit->char2_count; - - qsaudit->items[item_offset].from_id = this->character_id; - qsaudit->items[item_offset].from_slot = from_slot; - qsaudit->items[item_offset].to_id = other->CharacterID(); - qsaudit->items[item_offset].to_slot = 0; // disabled - qsaudit->items[item_offset].item_id = sub_inst->GetID(); - qsaudit->items[item_offset].charges = sub_inst->GetCharges(); - qsaudit->items[item_offset].aug_1 = sub_inst->GetAugmentItemID(1); - qsaudit->items[item_offset].aug_2 = sub_inst->GetAugmentItemID(2); - qsaudit->items[item_offset].aug_3 = sub_inst->GetAugmentItemID(3); - qsaudit->items[item_offset].aug_4 = sub_inst->GetAugmentItemID(4); - qsaudit->items[item_offset].aug_5 = sub_inst->GetAugmentItemID(5); - - if (finalizer) - ++qsaudit->char2_count; - else - ++qsaudit->char1_count; - } - } + qs_audit->char1_money.platinum = this->trade->pp; + qs_audit->char1_money.gold = this->trade->gp; + qs_audit->char1_money.silver = this->trade->sp; + qs_audit->char1_money.copper = this->trade->cp; } } @@ -541,22 +490,69 @@ void Client::FinishTrade(Mob* tradingWith, ServerPacket* qspack, bool finalizer) if (free_slot != INVALID_INDEX) { if (other->PutItemInInventory(free_slot, *inst, true)) { mlog(TRADING__CLIENT, "Container %s (%d) successfully transferred, deleting from trade slot.", inst->GetItem()->Name, inst->GetItem()->ID); + if (qs_log) { + QSTradeItems_Struct* detail = new QSTradeItems_Struct; + + detail->from_id = this->character_id; + detail->from_slot = trade_slot; + detail->to_id = other->CharacterID(); + detail->to_slot = free_slot; + detail->item_id = inst->GetID(); + detail->charges = 1; + detail->aug_1 = inst->GetAugmentItemID(1); + detail->aug_2 = inst->GetAugmentItemID(2); + detail->aug_3 = inst->GetAugmentItemID(3); + detail->aug_4 = inst->GetAugmentItemID(4); + detail->aug_5 = inst->GetAugmentItemID(5); + + event_details->push_back(detail); + + if (finalizer) + qs_audit->char2_count += detail->charges; + else + qs_audit->char1_count += detail->charges; + + //for (uint8 sub_slot = SUB_BEGIN; ((sub_slot < inst->GetItem()->BagSlots) && (sub_slot < EmuConstants::ITEM_CONTAINER_SIZE)); ++sub_slot) { + for (uint8 sub_slot = SUB_BEGIN; (sub_slot < EmuConstants::ITEM_CONTAINER_SIZE); ++sub_slot) { // this is to catch ALL items + const ItemInst* bag_inst = inst->GetItem(sub_slot); + + if (bag_inst) { + detail = new QSTradeItems_Struct; + + detail->from_id = this->character_id; + detail->from_slot = Inventory::CalcSlotId(trade_slot, sub_slot); + detail->to_id = other->CharacterID(); + detail->to_slot = Inventory::CalcSlotId(free_slot, sub_slot); + detail->item_id = bag_inst->GetID(); + detail->charges = (!bag_inst->IsStackable() ? 1 : bag_inst->GetCharges()); + detail->aug_1 = bag_inst->GetAugmentItemID(1); + detail->aug_2 = bag_inst->GetAugmentItemID(2); + detail->aug_3 = bag_inst->GetAugmentItemID(3); + detail->aug_4 = bag_inst->GetAugmentItemID(4); + detail->aug_5 = bag_inst->GetAugmentItemID(5); + + event_details->push_back(detail); + + if (finalizer) + qs_audit->char2_count += detail->charges; + else + qs_audit->char1_count += detail->charges; + } + } + } } else { mlog(TRADING__ERROR, "Transfer of container %s (%d) to %s failed, returning to giver.", inst->GetItem()->Name, inst->GetItem()->ID, other->GetName()); - PushItemOnCursor(*inst, true); } } else { mlog(TRADING__ERROR, "%s's inventory is full, returning container %s (%d) to giver.", other->GetName(), inst->GetItem()->Name, inst->GetItem()->ID); - PushItemOnCursor(*inst, true); } } else { mlog(TRADING__ERROR, "Container %s (%d) is NoDrop, returning to giver.", inst->GetItem()->Name, inst->GetItem()->ID); - PushItemOnCursor(*inst, true); } @@ -583,7 +579,6 @@ void Client::FinishTrade(Mob* tradingWith, ServerPacket* qspack, bool finalizer) if (partial_inst->GetID() != inst->GetID()) { _log(TRADING__ERROR, "Client::ResetTrade() - an incompatible location reference was returned by Inventory::FindFreeSlotForTradeItem()"); - break; } @@ -606,6 +601,28 @@ void Client::FinishTrade(Mob* tradingWith, ServerPacket* qspack, bool finalizer) if (other->PutItemInInventory(partial_slot, *partial_inst, true)) { mlog(TRADING__CLIENT, "Partial stack %s (%d) successfully transferred, deleting %i charges from trade slot.", inst->GetItem()->Name, inst->GetItem()->ID, (old_charges - inst->GetCharges())); + if (qs_log) { + QSTradeItems_Struct* detail = new QSTradeItems_Struct; + + detail->from_id = this->character_id; + detail->from_slot = trade_slot; + detail->to_id = other->CharacterID(); + detail->to_slot = partial_slot; + detail->item_id = inst->GetID(); + detail->charges = (old_charges - inst->GetCharges()); + detail->aug_1 = 0; + detail->aug_2 = 0; + detail->aug_3 = 0; + detail->aug_4 = 0; + detail->aug_5 = 0; + + event_details->push_back(detail); + + if (finalizer) + qs_audit->char2_count += detail->charges; + else + qs_audit->char1_count += detail->charges; + } } else { mlog(TRADING__ERROR, "Transfer of partial stack %s (%d) to %s failed, returning %i charges to trade slot.", @@ -613,13 +630,11 @@ void Client::FinishTrade(Mob* tradingWith, ServerPacket* qspack, bool finalizer) inst->SetCharges(old_charges); partial_inst->SetCharges(partial_charges); - break; } if (inst->GetCharges() == 0) { DeleteItemInInventory(trade_slot); - break; } } @@ -654,7 +669,6 @@ void Client::FinishTrade(Mob* tradingWith, ServerPacket* qspack, bool finalizer) if (inst->GetCharges() == 0) { DeleteItemInInventory(trade_slot); - break; } } @@ -675,22 +689,70 @@ void Client::FinishTrade(Mob* tradingWith, ServerPacket* qspack, bool finalizer) if (free_slot != INVALID_INDEX) { if (other->PutItemInInventory(free_slot, *inst, true)) { mlog(TRADING__CLIENT, "Item %s (%d) successfully transferred, deleting from trade slot.", inst->GetItem()->Name, inst->GetItem()->ID); + if (qs_log) { + QSTradeItems_Struct* detail = new QSTradeItems_Struct; + + detail->from_id = this->character_id; + detail->from_slot = trade_slot; + detail->to_id = other->CharacterID(); + detail->to_slot = free_slot; + detail->item_id = inst->GetID(); + detail->charges = (!inst->IsStackable() ? 1 : inst->GetCharges()); + detail->aug_1 = inst->GetAugmentItemID(1); + detail->aug_2 = inst->GetAugmentItemID(2); + detail->aug_3 = inst->GetAugmentItemID(3); + detail->aug_4 = inst->GetAugmentItemID(4); + detail->aug_5 = inst->GetAugmentItemID(5); + + event_details->push_back(detail); + + if (finalizer) + qs_audit->char2_count += detail->charges; + else + qs_audit->char1_count += detail->charges; + + // 'step 3' should never really see containers..but, just in case... + //for (uint8 sub_slot = SUB_BEGIN; ((sub_slot < inst->GetItem()->BagSlots) && (sub_slot < EmuConstants::ITEM_CONTAINER_SIZE)); ++sub_slot) { + for (uint8 sub_slot = SUB_BEGIN; (sub_slot < EmuConstants::ITEM_CONTAINER_SIZE); ++sub_slot) { // this is to catch ALL items + const ItemInst* bag_inst = inst->GetItem(sub_slot); + + if (bag_inst) { + detail = new QSTradeItems_Struct; + + detail->from_id = this->character_id; + detail->from_slot = trade_slot; + detail->to_id = other->CharacterID(); + detail->to_slot = free_slot; + detail->item_id = bag_inst->GetID(); + detail->charges = (!bag_inst->IsStackable() ? 1 : bag_inst->GetCharges()); + detail->aug_1 = bag_inst->GetAugmentItemID(1); + detail->aug_2 = bag_inst->GetAugmentItemID(2); + detail->aug_3 = bag_inst->GetAugmentItemID(3); + detail->aug_4 = bag_inst->GetAugmentItemID(4); + detail->aug_5 = bag_inst->GetAugmentItemID(5); + + event_details->push_back(detail); + + if (finalizer) + qs_audit->char2_count += detail->charges; + else + qs_audit->char1_count += detail->charges; + } + } + } } else { mlog(TRADING__ERROR, "Transfer of Item %s (%d) to %s failed, returning to giver.", inst->GetItem()->Name, inst->GetItem()->ID, other->GetName()); - PushItemOnCursor(*inst, true); } } else { mlog(TRADING__ERROR, "%s's inventory is full, returning item %s (%d) to giver.", other->GetName(), inst->GetItem()->Name, inst->GetItem()->ID); - PushItemOnCursor(*inst, true); } } else { mlog(TRADING__ERROR, "Item %s (%d) is NoDrop, returning to giver.", inst->GetItem()->Name, inst->GetItem()->ID); - PushItemOnCursor(*inst, true); } @@ -701,65 +763,73 @@ void Client::FinishTrade(Mob* tradingWith, ServerPacket* qspack, bool finalizer) //Do not reset the trade here, done by the caller. } } - - // trading with npc doesn't require Inventory::FindFreeSlotForTradeItem() rework else if(tradingWith && tradingWith->IsNPC()) { - QSPlayerLogHandin_Struct* qsaudit = nullptr; - bool QSPLH = false; + QSPlayerLogHandin_Struct* qs_audit = nullptr; + bool qs_log = false; // QS code - if(qspack && RuleB(QueryServ, PlayerLogTrades)) { + if(RuleB(QueryServ, PlayerLogTrades) && event_entry && event_details) { // Currently provides only basic functionality. Calling method will also // need to be modified before item returns and rewards can be logged. -U - qsaudit = (QSPlayerLogHandin_Struct*) qspack->pBuffer; - QSPLH = true; + qs_audit = (QSPlayerLogHandin_Struct*)event_entry; + qs_log = true; - qsaudit->quest_id = 0; - qsaudit->char_id = character_id; - qsaudit->char_money.platinum = trade->pp; - qsaudit->char_money.gold = trade->gp; - qsaudit->char_money.silver = trade->sp; - qsaudit->char_money.copper = trade->cp; - qsaudit->char_count = 0; - qsaudit->npc_id = tradingWith->GetNPCTypeID(); - qsaudit->npc_money.platinum = 0; - qsaudit->npc_money.gold = 0; - qsaudit->npc_money.silver = 0; - qsaudit->npc_money.copper = 0; - qsaudit->npc_count = 0; + qs_audit->quest_id = 0; + qs_audit->char_id = character_id; + qs_audit->char_money.platinum = trade->pp; + qs_audit->char_money.gold = trade->gp; + qs_audit->char_money.silver = trade->sp; + qs_audit->char_money.copper = trade->cp; + qs_audit->char_count = 0; + qs_audit->npc_id = tradingWith->GetNPCTypeID(); + qs_audit->npc_money.platinum = 0; + qs_audit->npc_money.gold = 0; + qs_audit->npc_money.silver = 0; + qs_audit->npc_money.copper = 0; + qs_audit->npc_count = 0; } - if(QSPLH) { // This can be incoporated below when revisions are made -U - for(int16 slot_id = EmuConstants::TRADE_BEGIN; slot_id <= EmuConstants::TRADE_NPC_END; slot_id++) { - const ItemInst* trade_inst = m_inv[slot_id]; + if(qs_log) { // This can be incorporated below when revisions are made -U + for (int16 trade_slot = EmuConstants::TRADE_BEGIN; trade_slot <= EmuConstants::TRADE_NPC_END; ++trade_slot) { + const ItemInst* trade_inst = m_inv[trade_slot]; if(trade_inst) { - strcpy(qsaudit->items[qsaudit->char_count].action_type, "HANDIN"); + QSHandinItems_Struct* detail = new QSHandinItems_Struct; - qsaudit->items[qsaudit->char_count].char_slot = slot_id; - qsaudit->items[qsaudit->char_count].item_id = trade_inst->GetID(); - qsaudit->items[qsaudit->char_count].charges = trade_inst->GetCharges(); - qsaudit->items[qsaudit->char_count].aug_1 = trade_inst->GetAugmentItemID(1); - qsaudit->items[qsaudit->char_count].aug_2 = trade_inst->GetAugmentItemID(2); - qsaudit->items[qsaudit->char_count].aug_3 = trade_inst->GetAugmentItemID(3); - qsaudit->items[qsaudit->char_count].aug_4 = trade_inst->GetAugmentItemID(4); - qsaudit->items[qsaudit->char_count++].aug_5 = trade_inst->GetAugmentItemID(5); + strcpy(detail->action_type, "HANDIN"); + + detail->char_slot = trade_slot; + detail->item_id = trade_inst->GetID(); + detail->charges = (!trade_inst->IsStackable() ? 1 : trade_inst->GetCharges()); + detail->aug_1 = trade_inst->GetAugmentItemID(1); + detail->aug_2 = trade_inst->GetAugmentItemID(2); + detail->aug_3 = trade_inst->GetAugmentItemID(3); + detail->aug_4 = trade_inst->GetAugmentItemID(4); + detail->aug_5 = trade_inst->GetAugmentItemID(5); + + event_details->push_back(detail); + qs_audit->char_count += detail->charges; if(trade_inst->IsType(ItemClassContainer)) { - for(uint8 bag_idx = SUB_BEGIN; bag_idx < trade_inst->GetItem()->BagSlots; bag_idx++) { - const ItemInst* trade_baginst = trade_inst->GetItem(bag_idx); + for (uint8 sub_slot = SUB_BEGIN; sub_slot < trade_inst->GetItem()->BagSlots; ++sub_slot) { + const ItemInst* trade_baginst = trade_inst->GetItem(sub_slot); if(trade_baginst) { - strcpy(qsaudit->items[qsaudit->char_count].action_type, "HANDIN"); + detail = new QSHandinItems_Struct; - qsaudit->items[qsaudit->char_count].char_slot = Inventory::CalcSlotId(slot_id, bag_idx); - qsaudit->items[qsaudit->char_count].item_id = trade_baginst->GetID(); - qsaudit->items[qsaudit->char_count].charges = trade_baginst->GetCharges(); - qsaudit->items[qsaudit->char_count].aug_1 = trade_baginst->GetAugmentItemID(1); - qsaudit->items[qsaudit->char_count].aug_2 = trade_baginst->GetAugmentItemID(2); - qsaudit->items[qsaudit->char_count].aug_3 = trade_baginst->GetAugmentItemID(3); - qsaudit->items[qsaudit->char_count].aug_4 = trade_baginst->GetAugmentItemID(4); - qsaudit->items[qsaudit->char_count++].aug_5 = trade_baginst->GetAugmentItemID(5); + strcpy(detail->action_type, "HANDIN"); + + detail->char_slot = Inventory::CalcSlotId(trade_slot, sub_slot); + detail->item_id = trade_baginst->GetID(); + detail->charges = (!trade_inst->IsStackable() ? 1 : trade_inst->GetCharges()); + detail->aug_1 = trade_baginst->GetAugmentItemID(1); + detail->aug_2 = trade_baginst->GetAugmentItemID(2); + detail->aug_3 = trade_baginst->GetAugmentItemID(3); + detail->aug_4 = trade_baginst->GetAugmentItemID(4); + detail->aug_5 = trade_baginst->GetAugmentItemID(5); + + event_details->push_back(detail); + qs_audit->char_count += detail->charges; } } } From 18a4f831be630adca360763dfbc302f5d3d07767 Mon Sep 17 00:00:00 2001 From: Uleat Date: Tue, 26 Aug 2014 06:37:40 -0400 Subject: [PATCH 3/3] Tweaked QS code for Client::FinishTrade() and QueryServ handlers. --- changelog.txt | 6 ++++++ common/servertalk.h | 2 ++ queryserv/database.cpp | 12 ++++++------ queryserv/database.h | 4 ++-- queryserv/worldserver.cpp | 6 ++---- zone/client_packet.cpp | 6 ++++-- zone/trading.cpp | 20 ++++++++++++++++++++ 7 files changed, 42 insertions(+), 14 deletions(-) diff --git a/changelog.txt b/changelog.txt index 9172332d9..98e1f0606 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,11 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 08/26/2014 == +Uleat: Implemented 'Smart' Player Trade transfers. Trades are processed by containers, stackables and then all remaining. QueryServ logs have been updated to match these transactions. +Note: QueryServ logs previously listed 'Items' on the main entry table. This indicated the number of slots affected and not the actual number of items. +This field now indicates the actual number of items transferred. For non-stackable, the value is '1' and stackable is the number of charges. A _detail_count +property has been added to both 'Trade' and 'Handin' structs to indicate the number of details recorded..though, not tracked..it could be added. + == 08/24/2014 == Uleat: Fix (attempted) for zone crashes related to zone shut-down. This change disables all Mob AI and disables/deletes all Mob timers once Zone::ShutDown() is called. More areas will be addressed as reports come in. Note: Perl and Lua quests tested to work..please post any aberrant behavior. (I finally set my spell-check to US English...) diff --git a/common/servertalk.h b/common/servertalk.h index 6d1a83a84..5337b6b1d 100644 --- a/common/servertalk.h +++ b/common/servertalk.h @@ -1118,6 +1118,7 @@ struct QSPlayerLogTrade_Struct { uint32 char2_id; MoneyUpdate_Struct char2_money; uint16 char2_count; + uint16 _detail_count; QSTradeItems_Struct items[0]; }; @@ -1141,6 +1142,7 @@ struct QSPlayerLogHandin_Struct { uint32 npc_id; MoneyUpdate_Struct npc_money; uint16 npc_count; + uint16 _detail_count; QSHandinItems_Struct items[0]; }; diff --git a/queryserv/database.cpp b/queryserv/database.cpp index 4b94f215b..cfc4a8892 100644 --- a/queryserv/database.cpp +++ b/queryserv/database.cpp @@ -119,7 +119,7 @@ void Database::AddSpeech(const char* from, const char* to, const char* message, safe_delete_array(S3); } -void Database::LogPlayerTrade(QSPlayerLogTrade_Struct* QS, uint32 Items) { +void Database::LogPlayerTrade(QSPlayerLogTrade_Struct* QS, uint32 DetailCount) { char errbuf[MYSQL_ERRMSG_SIZE]; char* query = 0; @@ -134,8 +134,8 @@ void Database::LogPlayerTrade(QSPlayerLogTrade_Struct* QS, uint32 Items) { _log(QUERYSERV__ERROR, "%s", query); } - if(Items > 0) { - for(int i = 0; i < Items; i++) { + if(DetailCount > 0) { + for(int i = 0; i < DetailCount; i++) { if(!RunQuery(query, MakeAnyLenString(&query, "INSERT INTO `qs_player_trade_record_entries` SET `event_id`='%i', " "`from_id`='%i', `from_slot`='%i', `to_id`='%i', `to_slot`='%i', `item_id`='%i', " "`charges`='%i', `aug_1`='%i', `aug_2`='%i', `aug_3`='%i', `aug_4`='%i', `aug_5`='%i'", @@ -149,7 +149,7 @@ void Database::LogPlayerTrade(QSPlayerLogTrade_Struct* QS, uint32 Items) { } } -void Database::LogPlayerHandin(QSPlayerLogHandin_Struct* QS, uint32 Items) { +void Database::LogPlayerHandin(QSPlayerLogHandin_Struct* QS, uint32 DetailCount) { char errbuf[MYSQL_ERRMSG_SIZE]; char* query = 0; uint32 lastid = 0; @@ -163,8 +163,8 @@ void Database::LogPlayerHandin(QSPlayerLogHandin_Struct* QS, uint32 Items) { _log(QUERYSERV__ERROR, "%s", query); } - if(Items > 0) { - for(int i = 0; i < Items; i++) { + if(DetailCount > 0) { + for(int i = 0; i < DetailCount; i++) { if(!RunQuery(query, MakeAnyLenString(&query, "INSERT INTO `qs_player_handin_record_entries` SET `event_id`='%i', " "`action_type`='%s', `char_slot`='%i', `item_id`='%i', `charges`='%i', " "`aug_1`='%i', `aug_2`='%i', `aug_3`='%i', `aug_4`='%i', `aug_5`='%i'", diff --git a/queryserv/database.h b/queryserv/database.h index ac002a0b5..a25d91611 100644 --- a/queryserv/database.h +++ b/queryserv/database.h @@ -43,8 +43,8 @@ public: ~Database(); void AddSpeech(const char* from, const char* to, const char* message, uint16 minstatus, uint32 guilddbid, uint8 type); - void LogPlayerTrade(QSPlayerLogTrade_Struct* QS, uint32 Items); - void LogPlayerHandin(QSPlayerLogHandin_Struct* QS, uint32 Items); + void LogPlayerTrade(QSPlayerLogTrade_Struct* QS, uint32 DetailCount); + void LogPlayerHandin(QSPlayerLogHandin_Struct* QS, uint32 DetailCount); void LogPlayerNPCKill(QSPlayerLogNPCKill_Struct* QS, uint32 Members); void LogPlayerDelete(QSPlayerLogDelete_Struct* QS, uint32 Items); void LogPlayerMove(QSPlayerLogMove_Struct* QS, uint32 Items); diff --git a/queryserv/worldserver.cpp b/queryserv/worldserver.cpp index 154db3a07..fc87929ca 100644 --- a/queryserv/worldserver.cpp +++ b/queryserv/worldserver.cpp @@ -80,14 +80,12 @@ void WorldServer::Process() } case ServerOP_QSPlayerLogTrades: { QSPlayerLogTrade_Struct *QS = (QSPlayerLogTrade_Struct*)pack->pBuffer; - uint32 Items = QS->char1_count + QS->char2_count; - database.LogPlayerTrade(QS, Items); + database.LogPlayerTrade(QS, QS->_detail_count); break; } case ServerOP_QSPlayerLogHandins: { QSPlayerLogHandin_Struct *QS = (QSPlayerLogHandin_Struct*)pack->pBuffer; - uint32 Items = QS->char_count + QS->npc_count; - database.LogPlayerHandin(QS, Items); + database.LogPlayerHandin(QS, QS->_detail_count); break; } case ServerOP_QSPlayerLogNPCKills: { diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 8d39f815a..76c873711 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -4898,8 +4898,9 @@ void Client::Handle_OP_TradeAcceptClick(const EQApplicationPacket *app) this->FinishTrade(other, true, &event_entry, &event_details); other->FinishTrade(this, false, &event_entry, &event_details); - ServerPacket* qs_pack = new ServerPacket(ServerOP_QSPlayerLogTrades, sizeof(QSPlayerLogTrade_Struct)+(sizeof(QSTradeItems_Struct)* event_details.size())); + event_entry._detail_count = event_details.size(); + ServerPacket* qs_pack = new ServerPacket(ServerOP_QSPlayerLogTrades, sizeof(QSPlayerLogTrade_Struct)+(sizeof(QSTradeItems_Struct)* event_entry._detail_count)); QSPlayerLogTrade_Struct* qs_buf = (QSPlayerLogTrade_Struct*)qs_pack->pBuffer; memcpy(qs_buf, &event_entry, sizeof(QSPlayerLogTrade_Struct)); @@ -4951,8 +4952,9 @@ void Client::Handle_OP_TradeAcceptClick(const EQApplicationPacket *app) FinishTrade(with->CastToNPC(), false, &event_entry, &event_details); - ServerPacket* qs_pack = new ServerPacket(ServerOP_QSPlayerLogHandins, sizeof(QSPlayerLogHandin_Struct)+(sizeof(QSHandinItems_Struct)* event_details.size())); + event_entry._detail_count = event_details.size(); + ServerPacket* qs_pack = new ServerPacket(ServerOP_QSPlayerLogHandins, sizeof(QSPlayerLogHandin_Struct)+(sizeof(QSHandinItems_Struct)* event_entry._detail_count)); QSPlayerLogHandin_Struct* qs_buf = (QSPlayerLogHandin_Struct*)qs_pack->pBuffer; memcpy(qs_buf, &event_entry, sizeof(QSPlayerLogHandin_Struct)); diff --git a/zone/trading.cpp b/zone/trading.cpp index 88b277651..23d8d77af 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -656,6 +656,8 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st if (!bias_inst || (bias_inst->GetID() != inst->GetID()) || (bias_inst->GetCharges() >= bias_inst->GetItem()->StackSize)) continue; + int16 old_charges = inst->GetCharges(); + if ((bias_inst->GetCharges() + inst->GetCharges()) > bias_inst->GetItem()->StackSize) { int16 new_charges = (bias_inst->GetCharges() + inst->GetCharges()) - bias_inst->GetItem()->StackSize; @@ -667,6 +669,24 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st inst->SetCharges(0); } + if (qs_log) { + QSTradeItems_Struct* detail = new QSTradeItems_Struct; + + detail->from_id = this->character_id; + detail->from_slot = trade_slot; + detail->to_id = this->character_id; + detail->to_slot = bias_slot; + detail->item_id = inst->GetID(); + detail->charges = (old_charges - inst->GetCharges()); + detail->aug_1 = 0; + detail->aug_2 = 0; + detail->aug_3 = 0; + detail->aug_4 = 0; + detail->aug_5 = 0; + + event_details->push_back(detail); + } + if (inst->GetCharges() == 0) { DeleteItemInInventory(trade_slot); break;