Merge pull request #231 from EQEmu/trade_stacking

'Smart' Trade Transfers
This commit is contained in:
Uleat 2014-08-26 19:04:01 -04:00
commit efbcaf6f64
12 changed files with 658 additions and 237 deletions

View File

@ -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...)
@ -49,6 +55,16 @@ Akkadius: Spawn related logging cleanup
Akkadius: General code cleanup
Akkadius: More to come for QueryServ
== 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

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

View File

@ -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'",

View File

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

View File

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

View File

@ -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<void*>* event_details = nullptr);
void SendZonePoints();
void SendBuyerResults(char *SearchQuery, uint32 SearchID);

View File

@ -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,38 @@ 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<void*> 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);
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));
int offset = 0;
for (std::list<void*>::iterator iter = event_details.begin(); iter != event_details.end(); ++iter, ++offset) {
QSTradeItems_Struct* detail = reinterpret_cast<QSTradeItems_Struct*>(*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 +4945,43 @@ 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<void*> 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);
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));
int offset = 0;
for (std::list<void*>::iterator iter = event_details.begin(); iter != event_details.end(); ++iter, ++offset) {
QSHandinItems_Struct* detail = reinterpret_cast<QSHandinItems_Struct*>(*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

View File

@ -527,7 +527,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

@ -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
@ -71,8 +74,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 +124,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 +139,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,200 +319,464 @@ 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) {
void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, std::list<void*>* 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());
int16 slot_id;
const Item_Struct* item = nullptr;
QSPlayerLogTrade_Struct* qsaudit = nullptr;
bool QSPLT = false;
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)) {
qsaudit = (QSPlayerLogTrade_Struct*) qspack->pBuffer;
QSPLT = true;
if (RuleB(QueryServ, PlayerLogTrades) && event_entry && event_details) {
qs_audit = (QSPlayerLogTrade_Struct*)event_entry;
qs_log = true;
if (finalizer) {
qs_audit->char2_id = this->character_id;
if(finalizer) { qsaudit->char2_id = this->character_id; }
else { qsaudit->char1_id = this->character_id; }
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 {
qs_audit->char1_id = this->character_id;
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;
}
}
// 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;
// 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 == nullptr) { continue; }
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());
mlog(TRADING__CLIENT, "Giving %s (%d) in slot %d to %s", inst->GetItem()->Name, inst->GetItem()->ID, i, 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);
/// Log Player Trades through QueryServ if Rule Enabled
if(QSPLT) {
uint16 item_count = qsaudit->char1_count + qsaudit->char2_count;
parent_offset = item_count;
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;
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);
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);
if(finalizer) { qsaudit->char2_count++; }
else { qsaudit->char1_count++; }
event_details->push_back(detail);
if (finalizer)
qs_audit->char2_count += detail->charges;
else
qs_audit->char1_count += detail->charges;
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);
//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(baginst == nullptr) { continue; }
if (bag_inst) {
detail = new QSTradeItems_Struct;
int16 k=Inventory::CalcSlotId(i, j);
item_count = qsaudit->char1_count + qsaudit->char2_count;
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);
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);
event_details->push_back(detail);
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;
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);
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);
DeleteItemInInventory(trade_slot);
}
else {
PushItemOnCursor(*inst, true);
DeleteItemInInventory(i);
}
if(QSPLT) {
qsaudit->items[parent_offset].to_id = this->character_id;
qsaudit->items[parent_offset].to_slot = MainCursor;
// 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->IsType(ItemClassContainer)) {
for(uint8 bagslot_idx = SUB_BEGIN; bagslot_idx < inst->GetItem()->BagSlots; bagslot_idx++) {
const ItemInst* bag_inst = inst->GetItem(bagslot_idx);
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(bag_inst == nullptr) { continue; }
int16 to_bagslot_id = Inventory::CalcSlotId(MainCursor, bagslot_idx);
if ((partial_slot == MainCursor) || (partial_slot == INVALID_INDEX))
break;
qsaudit->items[++parent_offset].to_id = this->character_id;
qsaudit->items[parent_offset].to_slot = to_bagslot_id;
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()));
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.",
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;
}
}
}
}
// Money - look into how NPC's receive cash
this->AddMoneyToPP(other->trade->cp, other->trade->sp, other->trade->gp, other->trade->pp, true);
// 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);
// This is currently setup to show character offers, not receipts
if(QSPLT) {
if(finalizer) {
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;
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;
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;
bias_inst->SetCharges(bias_inst->GetItem()->StackSize);
inst->SetCharges(new_charges);
}
else {
bias_inst->SetCharges(bias_inst->GetCharges() + inst->GetCharges());
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;
}
}
}
else {
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;
}
// 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);
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);
}
DeleteItemInInventory(trade_slot);
}
}
@ -517,62 +784,72 @@ void Client::FinishTrade(Mob* tradingWith, ServerPacket* qspack, bool finalizer)
}
}
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;
}
}
}