From 28be3b87b7798479d59cc66bb7ca6328e265aba0 Mon Sep 17 00:00:00 2001 From: Trevius Date: Mon, 16 Feb 2015 11:56:23 -0600 Subject: [PATCH] (RoF2) Bazaar Trading (Buying/Selling) is now fully functional. Bazaar (/bazaar) search is not yet functional. --- changelog.txt | 3 + common/patches/rof2.cpp | 85 ++++++++++-- common/patches/rof2_ops.h | 1 + common/patches/rof2_structs.h | 37 ++++-- utils/patches/patch_RoF2.conf | 4 +- zone/client.cpp | 1 + zone/client.h | 6 +- zone/client_packet.cpp | 156 ++++++++++++++-------- zone/trading.cpp | 239 ++++++++++++++++++++++++---------- 9 files changed, 375 insertions(+), 157 deletions(-) diff --git a/changelog.txt b/changelog.txt index 99da395d8..6fcdf69a8 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,8 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 02/16/2015 == +Trevius: (RoF2) Bazaar Trading (Buying/Selling) is now fully functional. Bazaar (/bazaar) search is not yet functional. + == 02/14/2015 == Trevius: (RoF2) Bazaar is now partially functional. RoF2 clients can start/end trader mode and other clients can purchase from them. No other functionality yet. diff --git a/common/patches/rof2.cpp b/common/patches/rof2.cpp index 6ba1adbc7..28e845a0a 100644 --- a/common/patches/rof2.cpp +++ b/common/patches/rof2.cpp @@ -3578,14 +3578,13 @@ namespace RoF2 // Live actually has 200 items now, but 80 is the most our internal struct supports for (uint32 i = 0; i < 200; i++) { - //strncpy(eq->items[i].SerialNumber, "0000000000000000", sizeof(eq->items[i].SerialNumber)); - //snprintf(eq->items[i].SerialNumber, sizeof(eq->items[i].SerialNumber), "%016d", emu->SerialNumber[i]); - snprintf(eq->items[i].SerialNumber, sizeof(eq->items[i].SerialNumber), "%016d", 0); eq->items[i].Unknown18 = 0; if (i < 80) { + snprintf(eq->items[i].SerialNumber, sizeof(eq->items[i].SerialNumber), "%016d", emu->SerialNumber[i]); eq->ItemCost[i] = emu->ItemCost[i]; } else { + snprintf(eq->items[i].SerialNumber, sizeof(eq->items[i].SerialNumber), "%016d", 0); eq->ItemCost[i] = 0; } } @@ -3637,18 +3636,61 @@ namespace RoF2 FINISH_ENCODE(); } - ENCODE(OP_TraderShop) + ENCODE(OP_TraderDelItem) { - ENCODE_LENGTH_EXACT(TraderClick_Struct); - SETUP_DIRECT_ENCODE(TraderClick_Struct, structs::TraderClick_Struct); + ENCODE_LENGTH_EXACT(TraderDelItem_Struct); + SETUP_DIRECT_ENCODE(TraderDelItem_Struct, structs::TraderDelItem_Struct); - //eq->Code = emu->Unknown004; OUT(TraderID); - OUT(Approval); + snprintf(eq->SerialNumber, sizeof(eq->SerialNumber), "%016d", emu->ItemID); + Log.Out(Logs::Detail, Logs::Trading, "ENCODE(OP_TraderDelItem): TraderID %d, SerialNumber: %d", emu->TraderID, emu->ItemID); FINISH_ENCODE(); } + ENCODE(OP_TraderShop) + { + uint32 psize = (*p)->size; + if (psize == sizeof(TraderClick_Struct)) + { + ENCODE_LENGTH_EXACT(TraderClick_Struct); + SETUP_DIRECT_ENCODE(TraderClick_Struct, structs::TraderClick_Struct); + + eq->Code = 28; // Seen on Live + OUT(TraderID); + OUT(Approval); + + FINISH_ENCODE(); + } + else if (psize == sizeof(TraderBuy_Struct)) + { + ENCODE_LENGTH_EXACT(TraderBuy_Struct); + SETUP_DIRECT_ENCODE(TraderBuy_Struct, structs::TraderBuy_Struct); + + OUT(Action); + OUT(TraderID); + + //memcpy(eq->BuyerName, emu->BuyerName, sizeof(eq->BuyerName)); + //memcpy(eq->SellerName, emu->SellerName, sizeof(eq->SellerName)); + + memcpy(eq->ItemName, emu->ItemName, sizeof(eq->ItemName)); + OUT(ItemID); + OUT(AlreadySold); + OUT(Price); + OUT(Quantity); + snprintf(eq->SerialNumber, sizeof(eq->SerialNumber), "%016d", emu->ItemID); + + Log.Out(Logs::Detail, Logs::Trading, "ENCODE(OP_TraderShop): Buy Action %d, Price %d, Trader %d, ItemID %d, Quantity %d, ItemName, %s", + eq->Action, eq->Price, eq->TraderID, eq->ItemID, eq->Quantity, emu->ItemName); + + FINISH_ENCODE(); + } + else + { + Log.Out(Logs::Detail, Logs::Trading, "ENCODE(OP_TraderShop): Encode Size Unknown (%d)", psize); + } + } + ENCODE(OP_TributeInfo) { ENCODE_LENGTH_ATLEAST(TributeAbility_Struct); @@ -4961,19 +5003,38 @@ namespace RoF2 { DECODE_LENGTH_EXACT(structs::TraderClick_Struct); SETUP_DIRECT_DECODE(TraderClick_Struct, structs::TraderClick_Struct); - //MEMSET_IN(TraderClick_Struct); - //emu->Unknown004 = eq->Code; IN(TraderID); IN(Approval); - Log.Out(Logs::Detail, Logs::Trading, "DECODE(OP_TraderShop): Decoded packet size (%d) to size (%d)", sizeof(structs::TraderClick_Struct), sizeof(TraderClick_Struct)); + FINISH_DIRECT_DECODE(); + } + else if (psize == sizeof(structs::TraderBuy_Struct)) + { + + DECODE_LENGTH_EXACT(structs::TraderBuy_Struct); + SETUP_DIRECT_DECODE(TraderBuy_Struct, structs::TraderBuy_Struct); + + IN(Action); + IN(Price); + IN(TraderID); + memcpy(emu->ItemName, eq->ItemName, sizeof(emu->ItemName)); + IN(ItemID); + IN(Quantity); + Log.Out(Logs::Detail, Logs::Trading, "DECODE(OP_TraderShop): (Unknowns) Unknown004 %d, Unknown008 %d, Unknown012 %d, Unknown076 %d, Unknown276 %d", + eq->Unknown004, eq->Unknown008, eq->Unknown012, eq->Unknown076, eq->Unknown276); + Log.Out(Logs::Detail, Logs::Trading, "DECODE(OP_TraderShop): Buy Action %d, Price %d, Trader %d, ItemID %d, Quantity %d, ItemName, %s", + eq->Action, eq->Price, eq->TraderID, eq->ItemID, eq->Quantity, eq->ItemName); FINISH_DIRECT_DECODE(); } + else if (psize == 4) + { + Log.Out(Logs::Detail, Logs::Trading, "DECODE(OP_TraderShop): Forwarding packet as-is with size 4"); + } else { - Log.Out(Logs::Detail, Logs::Trading, "DECODE(OP_TraderShop): Decode Size Mismatch (%d), expected (%d)", psize, sizeof(structs::TraderClick_Struct)); + Log.Out(Logs::Detail, Logs::Trading, "DECODE(OP_TraderShop): Decode Size Unknown (%d)", psize); } } diff --git a/common/patches/rof2_ops.h b/common/patches/rof2_ops.h index 0a85855f1..3edcac459 100644 --- a/common/patches/rof2_ops.h +++ b/common/patches/rof2_ops.h @@ -3,6 +3,7 @@ E(OP_SendMembershipDetails) E(OP_TraderShop) +E(OP_TraderDelItem) // incoming packets that require a DECODE translation: // Begin RoF2 Decodes diff --git a/common/patches/rof2_structs.h b/common/patches/rof2_structs.h index 8bab425a4..e4e11ae8b 100644 --- a/common/patches/rof2_structs.h +++ b/common/patches/rof2_structs.h @@ -3224,6 +3224,26 @@ struct TraderStatus_Struct { }; struct TraderBuy_Struct { + /*000*/ uint32 Action; + /*004*/ uint32 Unknown004; + /*008*/ uint32 Unknown008; + /*012*/ uint32 Unknown012; + /*016*/ uint32 TraderID; + /*020*/ char BuyerName[64]; + /*084*/ char SellerName[64]; + /*148*/ char Unknown148[32]; + /*180*/ char ItemName[64]; + /*244*/ char SerialNumber[16]; + /*260*/ uint32 Unknown076; + /*264*/ uint32 ItemID; + /*268*/ uint32 Price; + /*272*/ uint32 AlreadySold; + /*276*/ uint32 Unknown276; + /*280*/ uint32 Quantity; + /*284*/ +}; + +struct TraderBuy_Struct_OLD { /*000*/ uint32 Action; /*004*/ uint32 Unknown004; /*008*/ uint32 Price; @@ -3253,19 +3273,12 @@ struct MoneyUpdate_Struct{ int32 copper; }; -//struct MoneyUpdate_Struct -//{ -//*0000*/ uint32 spawn_id; // ***Placeholder -//*0004*/ uint32 cointype; // Coin Type -//*0008*/ uint32 amount; // Amount -//*0012*/ -//}; - - struct TraderDelItem_Struct{ - uint32 slotid; - uint32 quantity; - uint32 unknown; + /*000*/ uint32 Unknown000; + /*004*/ uint32 TraderID; + /*008*/ char SerialNumber[16]; + /*024*/ uint32 Unknown012; + /*028*/ }; struct TraderClick_Struct{ diff --git a/utils/patches/patch_RoF2.conf b/utils/patches/patch_RoF2.conf index 189edc8b3..0cc0c69d0 100644 --- a/utils/patches/patch_RoF2.conf +++ b/utils/patches/patch_RoF2.conf @@ -402,12 +402,12 @@ OP_LootComplete=0x55c4 # bazaar trader stuff: OP_BazaarSearch=0x39d6 -OP_TraderDelItem=0x0000 +OP_TraderDelItem=0x5829 OP_BecomeTrader=0x61b3 OP_TraderShop=0x31df OP_Trader=0x4ef5 -OP_TraderBuy=0x0000 OP_Barter=0x243a +OP_TraderBuy=0x0000 OP_ShopItem=0x0000 OP_BazaarInspect=0x0000 OP_Bazaar=0x0000 diff --git a/zone/client.cpp b/zone/client.cpp index 9af91988c..63a9b5073 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -165,6 +165,7 @@ Client::Client(EQStreamInterface* ieqs) Trader=false; Buyer = false; CustomerID = 0; + TraderID = 0; TrackingID = 0; WID = 0; account_id = 0; diff --git a/zone/client.h b/zone/client.h index 53d3c0428..f2325ccca 100644 --- a/zone/client.h +++ b/zone/client.h @@ -266,10 +266,11 @@ public: void SendBazaarResults(uint32 trader_id,uint32 class_,uint32 race,uint32 stat,uint32 slot,uint32 type,char name[64],uint32 minprice,uint32 maxprice); void SendTraderItem(uint32 item_id,uint16 quantity); uint16 FindTraderItem(int32 SerialNumber,uint16 Quantity); + uint32 FindTraderItemSerialNumber(int32 ItemID); ItemInst* FindTraderItemBySerialNumber(int32 SerialNumber); void FindAndNukeTraderItem(int32 item_id,uint16 quantity,Client* customer,uint16 traderslot); - void NukeTraderItem(uint16 slot,int16 charges,uint16 quantity,Client* customer,uint16 traderslot, int uniqueid); - void ReturnTraderReq(const EQApplicationPacket* app,int16 traderitemcharges); + void NukeTraderItem(uint16 slot, int16 charges, uint16 quantity, Client* customer, uint16 traderslot, int32 uniqueid, int32 itemid = 0); + void ReturnTraderReq(const EQApplicationPacket* app,int16 traderitemcharges, uint32 itemid = 0); void TradeRequestFailed(const EQApplicationPacket* app); void BuyTraderItem(TraderBuy_Struct* tbs,Client* trader,const EQApplicationPacket* app); void TraderUpdate(uint16 slot_id,uint32 trader_id); @@ -1388,6 +1389,7 @@ private: uint16 BoatID; uint16 TrackingID; uint16 CustomerID; + uint16 TraderID; uint32 account_creation; uint8 firstlogon; uint32 mercid; // current merc diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index f602bf75d..2d60b444e 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -11952,12 +11952,6 @@ void Client::Handle_OP_ShopEnd(const EQApplicationPacket *app) { EQApplicationPacket empty(OP_ShopEndConfirm); QueuePacket(&empty); - //EQApplicationPacket* outapp = new EQApplicationPacket(OP_ShopEndConfirm, 2); - //outapp->pBuffer[0] = 0x0a; - //outapp->pBuffer[1] = 0x66; - //QueuePacket(outapp); - //safe_delete(outapp); - //Save(); return; } @@ -13443,23 +13437,22 @@ void Client::Handle_OP_TraderBuy(const EQApplicationPacket *app) // // Client has elected to buy an item from a Trader // + if (app->size != sizeof(TraderBuy_Struct)) { + Log.Out(Logs::General, Logs::Error, "Wrong size: OP_TraderBuy, size=%i, expected %i", app->size, sizeof(TraderBuy_Struct)); + return; + } - if (app->size == sizeof(TraderBuy_Struct)){ + TraderBuy_Struct* tbs = (TraderBuy_Struct*)app->pBuffer; - TraderBuy_Struct* tbs = (TraderBuy_Struct*)app->pBuffer; - - if (Client* Trader = entity_list.GetClientByID(tbs->TraderID)){ - BuyTraderItem(tbs, Trader, app); - Log.Out(Logs::Detail, Logs::Trading, "Client::Handle_OP_TraderBuy: Buy Trader Item "); - } - else { - Log.Out(Logs::Detail, Logs::Trading, "Client::Handle_OP_TraderBuy: Null Client Pointer"); - } + if (Client* Trader = entity_list.GetClientByID(tbs->TraderID)){ + BuyTraderItem(tbs, Trader, app); + Log.Out(Logs::Detail, Logs::Trading, "Client::Handle_OP_TraderBuy: Buy Trader Item "); } else { - Log.Out(Logs::Detail, Logs::Trading, "Client::Handle_OP_TraderBuy: Struct size mismatch"); - + Log.Out(Logs::Detail, Logs::Trading, "Client::Handle_OP_TraderBuy: Null Client Pointer"); } + + return; } @@ -13521,56 +13514,105 @@ void Client::Handle_OP_TradeRequestAck(const EQApplicationPacket *app) void Client::Handle_OP_TraderShop(const EQApplicationPacket *app) { // Bazaar Trader: - // - // This is when a potential purchaser right clicks on this client who is in Trader mode to - // browse their goods. - // - if (app->size != sizeof(TraderClick_Struct)) { - Log.Out(Logs::General, Logs::Error, "Wrong size: OP_TraderShop, size=%i, expected %i", app->size, sizeof(TraderClick_Struct)); - return; - } - - TraderClick_Struct* tcs = (TraderClick_Struct*)app->pBuffer; - - EQApplicationPacket* outapp = new EQApplicationPacket(OP_TraderShop, sizeof(TraderClick_Struct)); - - TraderClick_Struct* outtcs = (TraderClick_Struct*)outapp->pBuffer; - - Client* Trader = entity_list.GetClientByID(tcs->TraderID); - - if (Trader) + if (app->size == sizeof(TraderClick_Struct)) { - outtcs->Approval = Trader->WithCustomer(GetID()); - Log.Out(Logs::Detail, Logs::Trading, "Client::Handle_OP_TraderShop: Shop Request (%s) to (%s) with Approval: %d", GetCleanName(), Trader->GetCleanName(), outtcs->Approval); - } - else { - Log.Out(Logs::Detail, Logs::Trading, "Client::Handle_OP_TraderShop: entity_list.GetClientByID(tcs->traderid)" - " returned a nullptr pointer"); + // This is when a potential purchaser right clicks on this client who is in Trader mode to + // browse their goods. + TraderClick_Struct* tcs = (TraderClick_Struct*)app->pBuffer; + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_TraderShop, sizeof(TraderClick_Struct)); + + TraderClick_Struct* outtcs = (TraderClick_Struct*)outapp->pBuffer; + + Client* Trader = entity_list.GetClientByID(tcs->TraderID); + + if (Trader) + { + outtcs->Approval = Trader->WithCustomer(GetID()); + Log.Out(Logs::Detail, Logs::Trading, "Client::Handle_OP_TraderShop: Shop Request (%s) to (%s) with Approval: %d", GetCleanName(), Trader->GetCleanName(), outtcs->Approval); + } + else { + Log.Out(Logs::Detail, Logs::Trading, "Client::Handle_OP_TraderShop: entity_list.GetClientByID(tcs->traderid)" + " returned a nullptr pointer"); + return; + } + + outtcs->TraderID = tcs->TraderID; + + outtcs->Unknown008 = 0x3f800000; + + QueuePacket(outapp); + + + if (outtcs->Approval) { + this->BulkSendTraderInventory(Trader->CharacterID()); + Trader->Trader_CustomerBrowsing(this); + TraderID = tcs->TraderID; + Log.Out(Logs::Detail, Logs::Trading, "Client::Handle_OP_TraderShop: Trader Inventory Sent"); + } + else + { + Message_StringID(clientMessageYellow, TRADER_BUSY); + Log.Out(Logs::Detail, Logs::Trading, "Client::Handle_OP_TraderShop: Trader Busy"); + } + + safe_delete(outapp); + return; } + else if (app->size == sizeof(TraderBuy_Struct)) + { + // RoF+ + // Customer has purchased an item from the Trader - outtcs->TraderID = tcs->TraderID; + TraderBuy_Struct* tbs = (TraderBuy_Struct*)app->pBuffer; - outtcs->Unknown008 = 0x3f800000; + if (Client* Trader = entity_list.GetClientByID(tbs->TraderID)) + { + BuyTraderItem(tbs, Trader, app); + Log.Out(Logs::Detail, Logs::Trading, "Handle_OP_TraderShop: Buy Action %d, Price %d, Trader %d, ItemID %d, Quantity %d, ItemName, %s", + tbs->Action, tbs->Price, tbs->TraderID, tbs->ItemID, tbs->Quantity, tbs->ItemName); + } + else + { + Log.Out(Logs::Detail, Logs::Trading, "OP_TraderShop: Null Client Pointer"); + } + } + else if (app->size == 4) + { + // RoF+ + // Customer has closed the trade window + uint32 Command = *((uint32 *)app->pBuffer); - QueuePacket(outapp); - - - if (outtcs->Approval) { - this->BulkSendTraderInventory(Trader->CharacterID()); - Trader->Trader_CustomerBrowsing(this); - Log.Out(Logs::Detail, Logs::Trading, "Client::Handle_OP_TraderShop: Trader Inventory Sent"); + if (Command == 4) + { + Client* c = entity_list.GetClientByID(TraderID); + TraderID = 0; + if (c) + { + c->WithCustomer(0); + Log.Out(Logs::Detail, Logs::Trading, "Client::Handle_OP_Trader: End Transaction - Code %d", Command); + } + else + { + Log.Out(Logs::Detail, Logs::Trading, "Client::Handle_OP_Trader: Null Client Pointer for Trader - Code %d", Command); + } + EQApplicationPacket empty(OP_ShopEndConfirm); + QueuePacket(&empty); + } + else + { + Log.Out(Logs::Detail, Logs::Trading, "Client::Handle_OP_Trader: Unhandled Code %d", Command); + } } else { - Message_StringID(clientMessageYellow, TRADER_BUSY); - Log.Out(Logs::Detail, Logs::Trading, "Client::Handle_OP_TraderShop: Trader Busy"); + Log.Out(Logs::Detail, Logs::Trading, "Unknown size for OP_TraderShop: %i\n", app->size); + Log.Out(Logs::General, Logs::Error, "Unknown size for OP_TraderShop: %i\n", app->size); + DumpPacket(app); + return; } - - safe_delete(outapp); - - return; } void Client::Handle_OP_TradeSkillCombine(const EQApplicationPacket *app) diff --git a/zone/trading.cpp b/zone/trading.cpp index 292f31713..047aa3a6a 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -1098,7 +1098,15 @@ void Client::Trader_EndTrader() { for(int i = 0; i < 80; i++) { if(gis->Items[i] != 0) { - tdis->ItemID = gis->SerialNumber[i]; + if (Customer->GetClientVersion() >= ClientVersion::RoF) + { + // RoF+ use Item IDs for now + tdis->ItemID = gis->Items[i]; + } + else + { + tdis->ItemID = gis->SerialNumber[i]; + } Customer->QueuePacket(outapp); } @@ -1220,6 +1228,29 @@ void Client::BulkSendTraderInventory(uint32 char_id) { safe_delete(TraderItems); } +uint32 Client::FindTraderItemSerialNumber(int32 ItemID) { + + ItemInst* item = nullptr; + uint16 SlotID = 0; + for (int i = EmuConstants::GENERAL_BEGIN; i <= EmuConstants::GENERAL_END; i++){ + item = this->GetInv().GetItem(i); + if (item && item->GetItem()->ID == 17899){ //Traders Satchel + for (int x = SUB_BEGIN; x < EmuConstants::ITEM_CONTAINER_SIZE; x++) { + // we already have the parent bag and a contents iterator..why not just iterate the bag!?? + SlotID = Inventory::CalcSlotId(i, x); + item = this->GetInv().GetItem(SlotID); + if (item) { + if (item->GetID() == ItemID) + return item->GetSerialNumber(); + } + } + } + } + Log.Out(Logs::Detail, Logs::Trading, "Client::FindTraderItemSerialNumber Couldn't find item! Item ID %i", ItemID); + + return 0; +} + ItemInst* Client::FindTraderItemBySerialNumber(int32 SerialNumber){ ItemInst* item = nullptr; @@ -1287,9 +1318,9 @@ uint16 Client::FindTraderItem(int32 SerialNumber, uint16 Quantity){ item = this->GetInv().GetItem(SlotID); - if(item && item->GetSerialNumber() == SerialNumber && - (item->GetCharges() >= Quantity || (item->GetCharges() <= 0 && Quantity == 1))){ - + if (item && item->GetSerialNumber() == SerialNumber && + (item->GetCharges() >= Quantity || (item->GetCharges() <= 0 && Quantity == 1))) + { return SlotID; } } @@ -1301,21 +1332,34 @@ uint16 Client::FindTraderItem(int32 SerialNumber, uint16 Quantity){ return 0; } -void Client::NukeTraderItem(uint16 Slot,int16 Charges,uint16 Quantity,Client* Customer,uint16 TraderSlot, int SerialNumber) { +void Client::NukeTraderItem(uint16 Slot,int16 Charges,uint16 Quantity,Client* Customer,uint16 TraderSlot, int32 SerialNumber, int32 itemid) { + + if(!Customer) + return; - if(!Customer) return; Log.Out(Logs::Detail, Logs::Trading, "NukeTraderItem(Slot %i, Charges %i, Quantity %i", Slot, Charges, Quantity); - if(Quantity < Charges) { + + if(Quantity < Charges) + { Customer->SendSingleTraderItem(this->CharacterID(), SerialNumber); m_inv.DeleteItem(Slot, Quantity); } - else { + else + { EQApplicationPacket* outapp = new EQApplicationPacket(OP_TraderDelItem,sizeof(TraderDelItem_Struct)); TraderDelItem_Struct* tdis = (TraderDelItem_Struct*)outapp->pBuffer; tdis->Unknown000 = 0; tdis->TraderID = Customer->GetID(); - tdis->ItemID = SerialNumber; + if (Customer->GetClientVersion() >= ClientVersion::RoF) + { + // RoF+ use Item IDs for now + tdis->ItemID = itemid; + } + else + { + tdis->ItemID = SerialNumber; + } tdis->Unknown012 = 0; @@ -1323,7 +1367,6 @@ void Client::NukeTraderItem(uint16 Slot,int16 Charges,uint16 Quantity,Client* Cu safe_delete(outapp); m_inv.DeleteItem(Slot); - } // This updates the trader. Removes it from his trading bags. // @@ -1374,49 +1417,61 @@ void Client::FindAndNukeTraderItem(int32 SerialNumber, uint16 Quantity, Client* int16 Charges=0; uint16 SlotID = FindTraderItem(SerialNumber, Quantity); - if(SlotID > 0){ + + if(SlotID > 0) { item = this->GetInv().GetItem(SlotID); - if(item) { - Charges = this->GetInv().GetItem(SlotID)->GetCharges(); - - Stackable = item->IsStackable(); - - if(!Stackable) - Quantity = (Charges > 0) ? Charges : 1; - - Log.Out(Logs::Detail, Logs::Trading, "FindAndNuke %s, Charges %i, Quantity %i", item->GetItem()->Name, Charges, Quantity); + if (!item) + { + Log.Out(Logs::Detail, Logs::Trading, "Could not find Item: %i on Trader: %s", SerialNumber, Quantity, this->GetName()); + return; } - if(item && (Charges <= Quantity || (Charges <= 0 && Quantity==1) || !Stackable)){ + + Charges = this->GetInv().GetItem(SlotID)->GetCharges(); + + Stackable = item->IsStackable(); + + if (!Stackable) + Quantity = (Charges > 0) ? Charges : 1; + + Log.Out(Logs::Detail, Logs::Trading, "FindAndNuke %s, Charges %i, Quantity %i", item->GetItem()->Name, Charges, Quantity); + + if (Charges <= Quantity || (Charges <= 0 && Quantity==1) || !Stackable) + { this->DeleteItemInInventory(SlotID, Quantity); - TraderCharges_Struct* GetSlot = database.LoadTraderItemWithCharges(this->CharacterID()); + TraderCharges_Struct* TraderItems = database.LoadTraderItemWithCharges(this->CharacterID()); uint8 Count = 0; bool TestSlot = true; - for(int y = 0;y < 80;y++){ + for(int i = 0;i < 80;i++){ - if(TestSlot && GetSlot->SerialNumber[y] == SerialNumber){ - - database.DeleteTraderItem(this->CharacterID(),y); - NukeTraderItem(SlotID, Charges, Quantity, Customer, TraderSlot, GetSlot->SerialNumber[y]); + if(TestSlot && TraderItems->SerialNumber[i] == SerialNumber) + { + database.DeleteTraderItem(this->CharacterID(),i); + NukeTraderItem(SlotID, Charges, Quantity, Customer, TraderSlot, TraderItems->SerialNumber[i], TraderItems->ItemID[i]); TestSlot=false; } - else if(GetSlot->ItemID[y] > 0) + else if (TraderItems->ItemID[i] > 0) + { Count++; + } } - if(Count == 0) + if (Count == 0) + { Trader_EndTrader(); + } return; } - else if(item) { + else + { database.UpdateTraderItemCharges(this->CharacterID(), item->GetSerialNumber(), Charges-Quantity); - NukeTraderItem(SlotID, Charges, Quantity, Customer, TraderSlot, item->GetSerialNumber()); + NukeTraderItem(SlotID, Charges, Quantity, Customer, TraderSlot, item->GetSerialNumber(), item->GetID()); return; @@ -1426,22 +1481,38 @@ void Client::FindAndNukeTraderItem(int32 SerialNumber, uint16 Quantity, Client* Quantity,this->GetName()); } -void Client::ReturnTraderReq(const EQApplicationPacket* app, int16 TraderItemCharges){ +void Client::ReturnTraderReq(const EQApplicationPacket* app, int16 TraderItemCharges, uint32 itemid){ TraderBuy_Struct* tbs = (TraderBuy_Struct*)app->pBuffer; - EQApplicationPacket* outapp = new EQApplicationPacket(OP_TraderBuy, sizeof(TraderBuy_Struct)); + EQApplicationPacket* outapp = nullptr; + + if (GetClientVersion() >= ClientVersion::RoF) + { + outapp = new EQApplicationPacket(OP_TraderShop, sizeof(TraderBuy_Struct)); + } + else + { + outapp = new EQApplicationPacket(OP_TraderBuy, sizeof(TraderBuy_Struct)); + } TraderBuy_Struct* outtbs = (TraderBuy_Struct*)outapp->pBuffer; - memcpy(outtbs, tbs, app->size); - outtbs->Price = (tbs->Price * static_cast(TraderItemCharges)); + if (GetClientVersion() >= ClientVersion::RoF) + { + // Convert Serial Number back to Item ID for RoF+ + outtbs->ItemID = itemid; + } + else + { + // RoF+ requires individual price, but older clients require total price + outtbs->Price = (tbs->Price * static_cast(TraderItemCharges)); + } outtbs->Quantity = TraderItemCharges; - + // This should probably be trader ID, not customer ID as it is below. outtbs->TraderID = this->GetID(); - outtbs->AlreadySold = 0; QueuePacket(outapp); @@ -1478,7 +1549,7 @@ static void BazaarAuditTrail(const char *seller, const char *buyer, const char * database.QueryDatabase(query); } -void Client::BuyTraderItem(TraderBuy_Struct* tbs,Client* Trader,const EQApplicationPacket* app){ +void Client::BuyTraderItem(TraderBuy_Struct* tbs, Client* Trader, const EQApplicationPacket* app){ if(!Trader) return; @@ -1487,13 +1558,34 @@ void Client::BuyTraderItem(TraderBuy_Struct* tbs,Client* Trader,const EQApplicat return; } - EQApplicationPacket* outapp = new EQApplicationPacket(OP_Trader,sizeof(TraderBuy_Struct)); + EQApplicationPacket* outapp = nullptr; + + if (Trader->GetClientVersion() >= ClientVersion::RoF) + { + //outapp = new EQApplicationPacket(OP_TraderShop, sizeof(TraderBuy_Struct)); + } + else + { + //outapp = new EQApplicationPacket(OP_Trader, sizeof(TraderBuy_Struct)); + } + + outapp = new EQApplicationPacket(OP_Trader, sizeof(TraderBuy_Struct)); TraderBuy_Struct* outtbs = (TraderBuy_Struct*)outapp->pBuffer; outtbs->ItemID = tbs->ItemID; - const ItemInst* BuyItem = Trader->FindTraderItemBySerialNumber(tbs->ItemID); + const ItemInst* BuyItem = nullptr; + uint32 ItemID = 0; + + if (GetClientVersion() >= ClientVersion::RoF) + { + // Convert Item ID to Serial Number for RoF+ + ItemID = tbs->ItemID; + tbs->ItemID = Trader->FindTraderItemSerialNumber(tbs->ItemID); + } + + BuyItem = Trader->FindTraderItemBySerialNumber(tbs->ItemID); if(!BuyItem) { Log.Out(Logs::Detail, Logs::Trading, "Unable to find item on trader."); @@ -1543,12 +1635,10 @@ void Client::BuyTraderItem(TraderBuy_Struct* tbs,Client* Trader,const EQApplicat return; } - ReturnTraderReq(app, outtbs->Quantity); + ReturnTraderReq(app, outtbs->Quantity, ItemID); outtbs->TraderID = this->GetID(); - outtbs->Action = BazaarBuyItem; - strn0cpy(outtbs->ItemName, BuyItem->GetItem()->Name, 64); int TraderSlot = 0; @@ -1558,54 +1648,50 @@ void Client::BuyTraderItem(TraderBuy_Struct* tbs,Client* Trader,const EQApplicat else SendTraderItem(BuyItem->GetItem()->ID, BuyItem->GetCharges()); - - EQApplicationPacket* outapp2 = new EQApplicationPacket(OP_MoneyUpdate,sizeof(MoneyUpdate_Struct)); - - MoneyUpdate_Struct* mus= (MoneyUpdate_Struct*)outapp2->pBuffer; - // This cannot overflow assuming MAX_TRANSACTION_VALUE, checked above, is the default of 2000000000 uint32 TotalCost = tbs->Price * outtbs->Quantity; - outtbs->Price = TotalCost; + if (Trader->GetClientVersion() >= ClientVersion::RoF) + { + // RoF+ uses individual item price where older clients use total price + outtbs->Price = tbs->Price; + } + else + { + outtbs->Price = TotalCost; + } this->TakeMoneyFromPP(TotalCost); - mus->platinum = TotalCost / 1000; + Log.Out(Logs::Detail, Logs::Trading, "Customer Paid: %d in Copper", TotalCost); - TotalCost -= (mus->platinum * 1000); + uint32 platinum = TotalCost / 1000; + TotalCost -= (platinum * 1000); + uint32 gold = TotalCost / 100; + TotalCost -= (gold * 100); + uint32 silver = TotalCost / 10; + TotalCost -= (silver * 10); + uint32 copper = TotalCost; - mus->gold = TotalCost / 100; + Trader->AddMoneyToPP(copper, silver, gold, platinum, true); - TotalCost -= (mus->gold * 100); - - mus->silver = TotalCost / 10; - - TotalCost -= (mus->silver * 10); - - mus->copper = TotalCost; - - Trader->AddMoneyToPP(mus->copper,mus->silver,mus->gold,mus->platinum,false); - - mus->platinum = Trader->GetPlatinum(); - mus->gold = Trader->GetGold(); - mus->silver = Trader->GetSilver(); - mus->copper = Trader->GetCopper(); + Log.Out(Logs::Detail, Logs::Trading, "Trader Received: %d Platinum, %d Gold, %d Silver, %d Copper", platinum, gold, silver, copper); TraderSlot = Trader->FindTraderItem(tbs->ItemID, outtbs->Quantity); - Trader->QueuePacket(outapp2); - - if(RuleB(Bazaar, AuditTrail)) BazaarAuditTrail(Trader->GetName(), GetName(), BuyItem->GetItem()->Name, outtbs->Quantity, outtbs->Price, 0); Trader->FindAndNukeTraderItem(tbs->ItemID, outtbs->Quantity, this, 0); + if (ItemID > 0 && Trader->GetClientVersion() >= ClientVersion::RoF) + { + // Convert Serial Number back to ItemID for RoF+ + outtbs->ItemID = ItemID; + } + Trader->QueuePacket(outapp); - - safe_delete(outapp); - safe_delete(outapp2); } void Client::SendBazaarWelcome() @@ -1996,6 +2082,15 @@ static void UpdateTraderCustomerPriceChanged(uint32 CustomerID, TraderCharges_St for(int i = 0; i < 80; i++) { if(gis->ItemID[i] == ItemID) { + if (Customer->GetClientVersion() >= ClientVersion::RoF) + { + // RoF+ use Item IDs for now + tdis->ItemID = gis->ItemID[i]; + } + else + { + tdis->ItemID = gis->SerialNumber[i]; + } tdis->ItemID = gis->SerialNumber[i]; Log.Out(Logs::Detail, Logs::Trading, "Telling customer to remove item %i with %i charges and S/N %i", ItemID, Charges, gis->SerialNumber[i]);