Trade Stacking: BETA

This commit is contained in:
Uleat 2014-08-22 20:48:11 -04:00
parent 3b16c86007
commit 9a5d2d2bc5
6 changed files with 475 additions and 185 deletions

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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();

View File

@ -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<ItemInst*>::const_iterator s=m_inv.cursor_begin(),e=m_inv.cursor_end();
std::list<ItemInst*>::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 {

View File

@ -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;