From 5de05763adcb8a0e5ffdc32c91e27816eb785653 Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Sat, 12 Apr 2025 15:49:51 -0300 Subject: [PATCH] Added checks for same item with price change. Client treats them all the same so all need to be updated. Also repaired max stacksize of 32767 on RoF2 --- common/patches/rof2.cpp | 18 +- common/patches/rof2_limits.h | 1 + common/repositories/trader_repository.h | 34 +- common/shareddb.cpp | 2 +- zone/client.h | 14 +- zone/client_packet.cpp | 2 +- zone/trading.cpp | 417 ++++++++++++++---------- 7 files changed, 279 insertions(+), 209 deletions(-) diff --git a/common/patches/rof2.cpp b/common/patches/rof2.cpp index 5191fa692..407c021df 100644 --- a/common/patches/rof2.cpp +++ b/common/patches/rof2.cpp @@ -439,7 +439,7 @@ namespace RoF2 break; } default: { - LogTradingDetail("Unhandled action [{}]", sub_action); + //LogTradingDetail("Unhandled action [{}]", sub_action); dest->FastQueuePacket(&in); } } @@ -533,7 +533,7 @@ namespace RoF2 break; } default: { - LogTrading("(RoF2) Unhandled action [{}]", action); + //LogTradingDetail("(RoF2) Unhandled action [{}]", action); dest->FastQueuePacket(&in, ack_req); } } @@ -622,10 +622,10 @@ namespace RoF2 break; } default: { - LogTrading( - "(RoF2) Unhandled action [{}]", - in->action - ); + // LogTradingDetail( + // "(RoF2) Unhandled action [{}]", + // in->action + // ); dest->QueuePacket(inapp); } } @@ -4339,7 +4339,7 @@ namespace RoF2 break; } default: { - LogTrading("(RoF2) Unhandled action [{}]", action); + // LogTradingDetail("(RoF2) Unhandled action [{}]", action); EQApplicationPacket *in = *p; *p = nullptr; @@ -6224,7 +6224,7 @@ namespace RoF2 break; } default: { - LogTrading("(RoF2) Unhandled action [{}]", action); + //LogTradingDetail("(RoF2) Unhandled action [{}]", action); } } } @@ -6357,7 +6357,7 @@ namespace RoF2 break; } default: { - LogTrading("(RoF2) Unhandled action [{}]", action); + //LogTradingDetail("(RoF2) Unhandled action [{}]", action); } return; } diff --git a/common/patches/rof2_limits.h b/common/patches/rof2_limits.h index 668b0df74..3dc23f0ed 100644 --- a/common/patches/rof2_limits.h +++ b/common/patches/rof2_limits.h @@ -307,6 +307,7 @@ namespace RoF2 const size_t SAY_LINK_BODY_SIZE = 56; const uint32 MAX_GUILD_ID = 50000; const uint32 MAX_BAZAAR_TRADERS = 600; + const uint64 MAX_BAZAAR_TRANSACTION = 3276700000000; //3276700000000 } /*constants*/ diff --git a/common/repositories/trader_repository.h b/common/repositories/trader_repository.h index 88cc7910a..5d33a2f88 100644 --- a/common/repositories/trader_repository.h +++ b/common/repositories/trader_repository.h @@ -198,30 +198,40 @@ public: return UpdateOne(db, m); } - static int UpdatePrice(Database &db, const std::string &item_unique_id, uint32 price) + static std::vector UpdatePrice(Database &db, const std::string &item_unique_id, uint32 price) { - const auto trader_item = GetWhere( - db, - fmt::format("`item_unique_id` = '{}' ", item_unique_id) + std::vector all_entries{}; + + const auto query = fmt::format( + "UPDATE trader t1 SET t1.`item_cost` = '{}', t1.`listing_date` = FROM_UNIXTIME({}) WHERE t1.`item_id` = " + "(SELECT t2.`item_id` FROM trader t2 WHERE t2.`item_unique_id` = '{}')", + price, + time(nullptr), + item_unique_id ); - if (trader_item.empty() || trader_item.size() > 1) { - return 0; + auto results = db.QueryDatabase(query); + if (results.RowsAffected() == 0) { + return all_entries; } - auto m = trader_item[0]; - m.item_cost = price; - m.listing_date = time(nullptr); + all_entries = GetWhere( + db, + fmt::format( + "`item_id` = (SELECT t1.`item_id` FROM trader t1 WHERE t1.`item_unique_id` = '{}');", + item_unique_id + ) + ); - return ReplaceOne(db, m); + return all_entries; } - static Trader GetItemBySerialNumber(Database &db, std::string &item_unique_id, uint32 trader_id) + static Trader GetItemByItemUniqueNumber(Database &db, std::string &item_unique_id) { Trader e{}; const auto trader_item = GetWhere( db, - fmt::format("`character_id` = '{}' AND `item_unique_id` = '{}' LIMIT 1", trader_id, item_unique_id) + fmt::format("`item_unique_id` = '{}' LIMIT 1", item_unique_id) ); if (trader_item.empty()) { diff --git a/common/shareddb.cpp b/common/shareddb.cpp index 0f1622000..95ff53cdc 100644 --- a/common/shareddb.cpp +++ b/common/shareddb.cpp @@ -740,7 +740,7 @@ bool SharedDatabase::GetInventory(Client *c) inst->SetColor(color); } - if (charges == std::numeric_limits::max()) { + if (charges > std::numeric_limits::max()) { inst->SetCharges(-1); } else if (charges == 0 && inst->IsStackable()) { // Stackable items need a minimum charge of 1 remain moveable. diff --git a/zone/client.h b/zone/client.h index 5cc62aab4..b17922b06 100644 --- a/zone/client.h +++ b/zone/client.h @@ -377,12 +377,13 @@ public: EQ::ItemInstance* FindTraderItemBySerialNumber(std::string &serial_number); EQ::ItemInstance* FindTraderItemByUniqueID(std::string &unique_id); EQ::ItemInstance* FindTraderItemByUniqueID(const char* unique_id); + std::vector FindTraderItemsByUniqueID(const char* unique_id); void FindAndNukeTraderItem(std::string &item_unique_id, int16 quantity, Client* customer, uint16 trader_slot); void NukeTraderItem(uint16 slot, int16 charges, int16 quantity, Client* customer, uint16 trader_slot, const std::string &serial_number, int32 item_id = 0); void ReturnTraderReq(const EQApplicationPacket* app,int16 traderitemcharges, uint32 itemid = 0); void TradeRequestFailed(const EQApplicationPacket* app); void BuyTraderItem(const EQApplicationPacket* app); - void BuyTraderItemOutsideBazaar(TraderBuy_Struct* tbs, const EQApplicationPacket* app); + void BuyTraderItemFromBazaarWindow(const EQApplicationPacket* app); void FinishTrade( Mob *with, bool finalizer = false, @@ -422,11 +423,12 @@ public: uint32 GetCustomerID() { return customer_id; } void SetCustomerID(uint32 id) { customer_id = id; } void ClearTraderMerchantList() { m_trader_merchant_list.clear(); } - void AddDataToMerchantList(int16 slot_id, int32 quantity, const std::string &item_unique_id); - std::tuple GetDataFromMerchantListByMerchantSlotId(int16 slot_id); + void AddDataToMerchantList(int16 slot_id, uint32 item_id, int32 quantity, const std::string &item_unique_id); + int16 GetNextFreeSlotFromMerchantList(); + std::tuple GetDataFromMerchantListByMerchantSlotId(int16 slot_id); int16 GetSlotFromMerchantListByItemUniqueId(const std::string &unique_id); - std::pair> GetDataFromMerchantListByItemUniqueId(const std::string &unique_id); - std::map>* GetTraderMerchantList() { return &m_trader_merchant_list; } + std::pair> GetDataFromMerchantListByItemUniqueId(const std::string &unique_id); + std::map>* GetTraderMerchantList() { return &m_trader_merchant_list; } void SetBuyerID(uint32 id) { m_buyer_id = id; } uint32 GetBuyerID() { return m_buyer_id; } @@ -2112,7 +2114,7 @@ private: uint8 mercSlot; // selected merc slot time_t m_trader_transaction_date; uint32 m_trader_count{}; - std::map> m_trader_merchant_list{}; + std::map> m_trader_merchant_list{}; // itemid, qty, item_unique_id uint32 m_buyer_id; uint32 m_barter_time; int32 m_parcel_platinum; diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 00082ce29..ab3126c70 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -15510,7 +15510,7 @@ void Client::Handle_OP_TraderBuy(const EQApplicationPacket *app) in->quantity, in->item_unique_id ); - BuyTraderItemOutsideBazaar(in, app); + BuyTraderItemFromBazaarWindow(app); break; } case BazaarByDirectToInventory: { diff --git a/zone/trading.cpp b/zone/trading.cpp index 88fbfbd42..3297a5bf8 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -1011,7 +1011,7 @@ void Client::BulkSendTraderInventory(uint32 character_id) inst->SetMerchantCount(inst->IsStackable() ? inst->GetCharges() : 1); inst->SetMerchantSlot(i + 1); inst->SetPrice(trader_items.at(i).item_cost); - AddDataToMerchantList(i + 1, inst->GetMerchantCount(), inst->GetUniqueID()); + AddDataToMerchantList(i + 1, inst->GetID(), inst->GetMerchantCount(), inst->GetUniqueID()); SendItemPacket(i + 1, inst.get(), ItemPacketMerchant); } else @@ -1119,6 +1119,32 @@ EQ::ItemInstance *Client::FindTraderItemByUniqueID(const char* unique_id) return nullptr; } +std::vector Client::FindTraderItemsByUniqueID(const char* unique_id) +{ + std::vector items{}; + EQ::ItemInstance *item = nullptr; + int16 slot_id = 0; + + for (int16 i = EQ::invslot::GENERAL_BEGIN; i <= EQ::invslot::GENERAL_END; i++) { + item = GetInv().GetItem(i); + if (item && item->GetItem()->BagType == EQ::item::BagTypeTradersSatchel) { + for (int16 x = EQ::invbag::SLOT_BEGIN; x <= EQ::invbag::SLOT_END; x++) { + // we already have the parent bag and a contents iterator..why not just iterate the bag!?? + slot_id = EQ::InventoryProfile::CalcSlotId(i, x); + item = GetInv().GetItem(slot_id); + if (item) { + if (item->GetUniqueID().compare(unique_id) == 0) { + items.push_back(item); + } + } + } + } + } + + LogTrading("Couldn't find item! item_unique_id was [{}]", unique_id); + return items; +} + GetItems2_Struct *Client::GetTraderItems() { const EQ::ItemInstance *item = nullptr; @@ -1469,8 +1495,8 @@ void Client::BuyTraderItem(const EQApplicationPacket *app) return; } - auto [slot_id, merchant_data] = GetDataFromMerchantListByItemUniqueId(buy_inst->GetUniqueID()); - auto [merchant_quantity, item_unique_id] = merchant_data; + auto [slot_id, merchant_data] = GetDataFromMerchantListByItemUniqueId(buy_inst->GetUniqueID()); + auto [item_id, merchant_quantity, item_unique_id] = merchant_data; data->action = BazaarBuyItem; data->price = in->price; @@ -1494,7 +1520,7 @@ void Client::BuyTraderItem(const EQApplicationPacket *app) vendor_inst->SetPrice(in->price); auto list = GetTraderMerchantList(); TraderRepository::UpdateQuantity(database, item_unique_id, merchant_quantity - quantity); - std::get<0>(list->at(slot_id)) -= quantity; + std::get<1>(list->at(slot_id)) -= quantity; SendItemPacket(slot_id, vendor_inst.get(), ItemPacketMerchant); } else { @@ -2459,8 +2485,8 @@ void Client::TraderUpdateItem(const EQApplicationPacket *app) auto in = reinterpret_cast(app->pBuffer); uint32 new_price = in->new_price; auto inst = FindTraderItemByUniqueID(in->item_unique_id); - std::unique_ptr inst_copy(inst ? inst->Clone() : nullptr); + auto customer = entity_list.GetClientByID(GetCustomerID()); if (new_price == 0) { auto result = TraderRepository::DeleteWhere(database, fmt::format("`item_unique_id` = '{}'", in->item_unique_id)); if (!result) { @@ -2471,30 +2497,31 @@ void Client::TraderUpdateItem(const EQApplicationPacket *app) in->sub_action = BazaarPriceChange_RemoveItem; QueuePacket(app); - auto customer = entity_list.GetClientByID(GetCustomerID()); - if (customer && inst_copy) { - auto list = customer->GetTraderMerchantList(); - auto [slot_id, merchant_data] = customer->GetDataFromMerchantListByItemUniqueId(in->item_unique_id); - + if (customer && inst) { + auto list = customer->GetTraderMerchantList(); auto client_packet = new EQApplicationPacket(OP_ShopDelItem, static_cast(sizeof(Merchant_DelItem_Struct))); - auto client_data = reinterpret_cast(client_packet->pBuffer); + auto client_data = reinterpret_cast(client_packet->pBuffer); client_data->npcid = GetID(); client_data->playerid = customer->GetID(); - client_data->itemslot = slot_id; - customer->QueuePacket(client_packet); + for (auto const [slot_id, merchant_data]: *list) { + auto const [item_id, merchant_quantity, item_unique_id] = merchant_data; + if (item_id == inst->GetID()) { + client_data->itemslot = slot_id; + customer->QueuePacket(client_packet); + AddDataToMerchantList(slot_id, 0, 0, "0000000000000000"); + } + } safe_delete(client_packet); - list->erase(slot_id); - customer->Message( Chat::Red, fmt::format( "Trader {} removed item {} from the bazaar. Item no longer available.", GetCleanName(), - inst_copy->GetItem()->Name + inst->GetItem()->Name ).c_str() ); LogTrading("Trader removed item from trader list with item_unique_id {}", in->item_unique_id); @@ -2503,53 +2530,73 @@ void Client::TraderUpdateItem(const EQApplicationPacket *app) return; } - auto result = TraderRepository::UpdatePrice(database, in->item_unique_id, new_price); - if (!result && inst_copy) { - // Item does not exist so it must be re-created, - TraderRepository::Trader trader_item{}; + auto result = TraderRepository::UpdatePrice(database, in->item_unique_id, new_price); + if (result.empty()) { + auto trader_items = FindTraderItemsByUniqueID(in->item_unique_id); + std::vector queue{}; + for (auto const& i : trader_items) { + TraderRepository::Trader e{}; - trader_item.id = 0; - trader_item.char_entity_id = GetID(); - trader_item.character_id = CharacterID(); - trader_item.char_zone_id = GetZoneID(); - trader_item.char_zone_instance_id = GetInstanceID(); - trader_item.item_charges = inst_copy->GetCharges(); - trader_item.item_cost = new_price; - trader_item.item_id = inst_copy->GetID(); - trader_item.item_unique_id = in->item_unique_id; - trader_item.slot_id = 0; - trader_item.listing_date = time(nullptr); - if (inst_copy->IsAugmented()) { - auto augs = inst_copy->GetAugmentIDs(); - trader_item.augment_one = augs.at(0); - trader_item.augment_two = augs.at(1); - trader_item.augment_three = augs.at(2); - trader_item.augment_four = augs.at(3); - trader_item.augment_five = augs.at(4); - trader_item.augment_six = augs.at(5); + e.id = 0; + e.char_entity_id = GetID(); + e.character_id = CharacterID(); + e.char_zone_id = GetZoneID(); + e.char_zone_instance_id = GetInstanceID(); + e.item_charges = i->GetCharges(); + e.item_cost = new_price; + e.item_id = i->GetID(); + e.item_unique_id = i->GetUniqueID(); + e.slot_id = 0; + e.listing_date = time(nullptr); + if (i->IsAugmented()) { + auto augs = i->GetAugmentIDs(); + e.augment_one = augs.at(0); + e.augment_two = augs.at(1); + e.augment_three = augs.at(2); + e.augment_four = augs.at(3); + e.augment_five = augs.at(4); + e.augment_six = augs.at(5); + } + + queue.push_back(e); + if (customer) { + int16 next_slot_id = GetNextFreeSlotFromMerchantList(); + if (next_slot_id != INVALID_INDEX) { + std::unique_ptr vendor_inst_copy(i ? i->Clone() : nullptr); + vendor_inst_copy->SetUniqueID(i->GetUniqueID()); + vendor_inst_copy->SetMerchantCount(i->IsStackable() ? i->GetCharges() : 1); + vendor_inst_copy->SetMerchantSlot(next_slot_id ); + vendor_inst_copy->SetPrice(new_price); + AddDataToMerchantList(next_slot_id, i->GetID(), i->GetMerchantCount(), i->GetUniqueID()); + customer->SendItemPacket(next_slot_id, vendor_inst_copy.get(), ItemPacketMerchant); + } + } } - TraderRepository::ReplaceOne(database, trader_item); + if (!queue.empty()) { + TraderRepository::ReplaceMany(database, queue); + } + } + else { + for (auto const i : result) { + auto [slot_id, merchant_data] = customer->GetDataFromMerchantListByItemUniqueId(i.item_unique_id); + auto [item_id, merchant_quantity, item_unique_id] = merchant_data; + std::unique_ptr vendor_inst_copy(inst ? inst->Clone() : nullptr); + vendor_inst_copy->SetUniqueID(i.item_unique_id); + vendor_inst_copy->SetMerchantCount(i.item_charges); + vendor_inst_copy->SetMerchantSlot(slot_id); + vendor_inst_copy->SetPrice(new_price); + customer->SendItemPacket(slot_id, vendor_inst_copy.get(), ItemPacketMerchant); + } + + customer->Message( + Chat::Red, + fmt::format("Trader {} updated the price of item {}", GetCleanName(), inst->GetItem()->Name).c_str() + ); } in->sub_action = BazaarPriceChange_UpdatePrice; QueuePacket(app); - - auto customer = entity_list.GetClientByID(GetCustomerID()); - if (customer && inst_copy) { - auto [slot_id, merchant_data] = customer->GetDataFromMerchantListByItemUniqueId(inst_copy->GetUniqueID()); - auto [merchant_quantity, item_unique_id] = merchant_data; - - inst_copy->SetMerchantCount(merchant_quantity); - inst_copy->SetMerchantSlot(slot_id); - inst_copy->SetPrice(new_price); - customer->SendItemPacket(slot_id, inst_copy.get(), ItemPacketMerchant); - - customer->Message( - Chat::Red, - fmt::format("Trader {} updated the price of item {}", GetCleanName(), inst_copy->GetItem()->Name).c_str() - ); - } } void Client::SendBazaarDone(uint32 trader_id) @@ -2705,16 +2752,53 @@ std::string Client::DetermineMoneyString(uint64 cp) return fmt::format("{}", money); } -void Client::BuyTraderItemOutsideBazaar(TraderBuy_Struct *tbs, const EQApplicationPacket *app) +void Client::BuyTraderItemFromBazaarWindow(const EQApplicationPacket *app) { - auto in = (TraderBuy_Struct *) app->pBuffer; - auto sn = std::string(tbs->item_unique_id); - auto trader_item = TraderRepository::GetItemBySerialNumber(database, sn, tbs->trader_id); + auto in = reinterpret_cast(app->pBuffer); + auto item_unique_id = std::string(in->item_unique_id); + auto trader_item = TraderRepository::GetItemByItemUniqueNumber(database, item_unique_id); + + LogTradingDetail( + "Packet details: \n" + "Action :{}\n" + "Method :{}\n" + "SubAction :{}\n" + "Unknown_012 :{}\n" + "Trader ID :{}\n" + "Buyer Name :{}\n" + "Seller Name :{}\n" + "Unknown_148 :{}\n" + "Item Name :{}\n" + "Item Unique ID :{}\n" + "Unknown_261 :{}\n" + "Item ID :{}\n" + "Price :{}\n" + "Already Sold :{}\n" + "Unknown_276 :{}\n" + "Quantity :{}\n", + in->action, + in->method, + in->sub_action, + in->unknown_012, + in->trader_id, + in->buyer_name, + in->seller_name, + in->unknown_148, + in->item_name, + in->item_unique_id, + in->unknown_261, + in->item_id, + in->price, + in->already_sold, + in->unknown_276, + in->quantity + ); + if (!trader_item.id || GetTraderTransactionDate() < trader_item.listing_date) { - LogTrading("Attempt to purchase an item outside of the Bazaar trader_id [{}] item serial_number " - "[{}] The Traders data was outdated.", - tbs->trader_id, - tbs->item_unique_id + LogTrading("Attempt to purchase an item outside of the Bazaar trader_id [{}] item unique_id " + "[{}] The Traders data was outdated.", + in->trader_id, + in->item_unique_id ); in->method = BazaarByParcel; in->sub_action = DataOutDated; @@ -2723,10 +2807,10 @@ void Client::BuyTraderItemOutsideBazaar(TraderBuy_Struct *tbs, const EQApplicati } if (trader_item.active_transaction) { - LogTrading("Attempt to purchase an item outside of the Bazaar trader_id [{}] item serial_number " - "[{}] The item is already within an active transaction.", - tbs->trader_id, - tbs->item_unique_id + LogTrading("Attempt to purchase an item outside of the Bazaar trader_id [{}] item serial_number " + "[{}] The item is already within an active transaction.", + in->trader_id, + in->item_unique_id ); in->method = BazaarByParcel; in->sub_action = DataOutDated; @@ -2736,38 +2820,13 @@ void Client::BuyTraderItemOutsideBazaar(TraderBuy_Struct *tbs, const EQApplicati TraderRepository::UpdateActiveTransaction(database, trader_item.id, true); - std::unique_ptr buy_item( - database.CreateItem( - trader_item.item_id, - trader_item.item_charges, - trader_item.augment_one, - trader_item.augment_two, - trader_item.augment_three, - trader_item.augment_four, - trader_item.augment_five, - trader_item.augment_six - ) - ); - - if (!buy_item) { - LogTrading("Unable to find item id [{}] item_sn [{}] on trader", - trader_item.item_id, - trader_item.item_unique_id - ); - in->method = BazaarByParcel; - in->sub_action = Failed; - TraderRepository::UpdateActiveTransaction(database, trader_item.id, false); - TradeRequestFailed(app); - return; - } - auto next_slot = FindNextFreeParcelSlot(CharacterID()); if (next_slot == INVALID_INDEX) { LogTrading( "{} attempted to purchase {} from the bazaar with parcel delivery. Unfortunately their parcel limit was reached. " "Purchase unsuccessful.", GetCleanName(), - buy_item->GetItem()->Name + in->item_name ); in->method = BazaarByParcel; in->sub_action = TooManyParcels; @@ -2777,36 +2836,18 @@ void Client::BuyTraderItemOutsideBazaar(TraderBuy_Struct *tbs, const EQApplicati } LogTrading( - "Name: [{}] IsStackable: [{}] Requested Quantity: [{}] Charges on Item [{}]", - buy_item->GetItem()->Name, - buy_item->IsStackable(), - tbs->quantity, - buy_item->GetCharges() + "Name: [{}] Requested Quantity: [{}] Charges on Item [{}]", + in->item_name, + in->quantity, + trader_item.item_charges ); - // Determine the actual quantity for the purchase - int32 charges = static_cast(tbs->quantity); - if (!buy_item->IsStackable()) { - if (buy_item->GetCharges() <= 0) { - charges = 1; - } - else { - charges = buy_item->GetCharges(); - } - } - - LogTrading( - "Actual quantity that will be traded is [{}] {}", - tbs->quantity, - buy_item->GetCharges() ? fmt::format("with {} charges", buy_item->GetCharges()) : "" - ); - - uint64 total_cost = static_cast(tbs->price) * static_cast(tbs->quantity); - if (total_cost > MAX_TRANSACTION_VALUE) { + uint64 total_cost = static_cast(in->price) * static_cast(in->quantity); + if (total_cost > RoF2::constants::MAX_BAZAAR_TRANSACTION) { Message( Chat::Red, "That would exceed the single transaction limit of %u platinum.", - MAX_TRANSACTION_VALUE / 1000 + RoF2::constants::MAX_BAZAAR_TRANSACTION / 1000 ); TraderRepository::UpdateActiveTransaction(database, trader_item.id, false); TradeRequestFailed(app); @@ -2815,16 +2856,6 @@ void Client::BuyTraderItemOutsideBazaar(TraderBuy_Struct *tbs, const EQApplicati uint64 fee = std::round(total_cost * RuleR(Bazaar, ParcelDeliveryCostMod)); if (!TakeMoneyFromPP(total_cost + fee, false)) { - RecordPlayerEventLog( - PlayerEvent::POSSIBLE_HACK, - PlayerEvent::PossibleHackEvent{ - .message = fmt::format( - "{} attempted to buy {} in bazaar but did not have enough money.", - GetCleanName(), - buy_item->GetItem()->Name - ) - } - ); in->method = BazaarByParcel; in->sub_action = InsufficientFunds; TraderRepository::UpdateActiveTransaction(database, trader_item.id, false); @@ -2837,19 +2868,19 @@ void Client::BuyTraderItemOutsideBazaar(TraderBuy_Struct *tbs, const EQApplicati if (buy_item && PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::TRADER_PURCHASE)) { auto e = PlayerEvent::TraderPurchaseEvent{ - .item_id = buy_item->GetID(), - .augment_1_id = buy_item->GetAugmentItemID(0), - .augment_2_id = buy_item->GetAugmentItemID(1), - .augment_3_id = buy_item->GetAugmentItemID(2), - .augment_4_id = buy_item->GetAugmentItemID(3), - .augment_5_id = buy_item->GetAugmentItemID(4), - .augment_6_id = buy_item->GetAugmentItemID(5), - .item_name = buy_item->GetItem()->Name, - .trader_id = tbs->trader_id, - .trader_name = tbs->seller_name, - .price = tbs->price, - .quantity = tbs->quantity, - .charges = buy_item->IsStackable() ? 1 : charges, + .item_id = trader_item.item_id, + .augment_1_id = trader_item.augment_one, + .augment_2_id = trader_item.augment_two, + .augment_3_id = trader_item.augment_three, + .augment_4_id = trader_item.augment_four, + .augment_5_id = trader_item.augment_five, + .augment_6_id = trader_item.augment_six, + .item_name = in->item_name, + .trader_id = in->trader_id, + .trader_name = in->seller_name, + .price = in->price, + .quantity = in->quantity, + .charges = trader_item.item_charges, .total_cost = total_cost, .player_money_balance = GetCarriedMoney(), }; @@ -2858,17 +2889,17 @@ void Client::BuyTraderItemOutsideBazaar(TraderBuy_Struct *tbs, const EQApplicati } CharacterParcelsRepository::CharacterParcels parcel_out{}; - parcel_out.from_name = tbs->seller_name; + parcel_out.from_name = in->seller_name; parcel_out.note = "Delivered from a Bazaar Purchase"; parcel_out.sent_date = time(nullptr); - parcel_out.quantity = charges; - parcel_out.item_id = buy_item->GetItem()->ID; - parcel_out.aug_slot_1 = buy_item->GetAugmentItemID(0); - parcel_out.aug_slot_2 = buy_item->GetAugmentItemID(1); - parcel_out.aug_slot_3 = buy_item->GetAugmentItemID(2); - parcel_out.aug_slot_4 = buy_item->GetAugmentItemID(3); - parcel_out.aug_slot_5 = buy_item->GetAugmentItemID(4); - parcel_out.aug_slot_6 = buy_item->GetAugmentItemID(5); + parcel_out.quantity = in->quantity; + parcel_out.item_id = trader_item.item_id; + parcel_out.aug_slot_1 = trader_item.augment_one; + parcel_out.aug_slot_2 = trader_item.augment_two; + parcel_out.aug_slot_3 = trader_item.augment_three; + parcel_out.aug_slot_4 = trader_item.augment_four; + parcel_out.aug_slot_5 = trader_item.augment_five; + parcel_out.aug_slot_6 = trader_item.augment_six; parcel_out.char_id = CharacterID(); parcel_out.slot_id = next_slot; parcel_out.id = 0; @@ -2889,7 +2920,7 @@ void Client::BuyTraderItemOutsideBazaar(TraderBuy_Struct *tbs, const EQApplicati return; } - ReturnTraderReq(app, tbs->quantity, buy_item->GetID()); + ReturnTraderReq(app, in->quantity, trader_item.item_id); if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::PARCEL_SEND)) { PlayerEvent::ParcelSend e{}; e.from_player_name = parcel_out.from_name; @@ -2901,8 +2932,8 @@ void Client::BuyTraderItemOutsideBazaar(TraderBuy_Struct *tbs, const EQApplicati e.augment_4_id = parcel_out.aug_slot_4; e.augment_5_id = parcel_out.aug_slot_5; e.augment_6_id = parcel_out.aug_slot_6; - e.quantity = tbs->quantity; - e.charges = buy_item->IsStackable() ? 1 : charges; + e.quantity = in->quantity; + e.charges = trader_item.item_charges; e.sent_date = parcel_out.sent_date; RecordPlayerEventLog(PlayerEvent::PARCEL_SEND, e); @@ -2912,32 +2943,41 @@ void Client::BuyTraderItemOutsideBazaar(TraderBuy_Struct *tbs, const EQApplicati ps.item_slot = parcel_out.slot_id; strn0cpy(ps.send_to, GetCleanName(), sizeof(ps.send_to)); - if (trader_item.item_charges <= static_cast(tbs->quantity) || !buy_item->IsStackable()) { + if (trader_item.item_charges <= static_cast(in->quantity) || !trader_item.item_charges <= 0) { TraderRepository::DeleteOne(database, trader_item.id); } SendParcelDeliveryToWorld(ps); - if (RuleB(Bazaar, AuditTrail)) { - BazaarAuditTrail(tbs->seller_name, GetName(), buy_item->GetItem()->Name, tbs->quantity, tbs->price, 0); - } - - auto out_server = std::make_unique( - ServerOP_BazaarPurchase, static_cast(sizeof(BazaarPurchaseMessaging_Struct)) - ); + auto out_server = std::make_unique(ServerOP_BazaarPurchase, static_cast(sizeof(BazaarPurchaseMessaging_Struct))); auto out_data = (BazaarPurchaseMessaging_Struct *) out_server->pBuffer; - out_data->trader_buy_struct = *tbs; - out_data->buyer_id = CharacterID(); - out_data->item_aug_1 = buy_item->GetAugmentItemID(0); - out_data->item_aug_2 = buy_item->GetAugmentItemID(1); - out_data->item_aug_3 = buy_item->GetAugmentItemID(2); - out_data->item_aug_4 = buy_item->GetAugmentItemID(3); - out_data->item_aug_5 = buy_item->GetAugmentItemID(4); - out_data->item_aug_6 = buy_item->GetAugmentItemID(5); - out_data->item_quantity_available = trader_item.item_charges; - out_data->id = trader_item.id; + out_data->trader_buy_struct.action = in->action; + out_data->trader_buy_struct.method = in->method; + out_data->trader_buy_struct.already_sold = in->already_sold; + out_data->trader_buy_struct.item_id = in->item_id; + out_data->trader_buy_struct.price = in->price; + out_data->trader_buy_struct.quantity = in->quantity; + out_data->trader_buy_struct.sub_action = in->sub_action; + out_data->trader_buy_struct.trader_id = in->trader_id; + out_data->buyer_id = CharacterID(); + out_data->item_aug_1 = trader_item.augment_one; + out_data->item_aug_2 = trader_item.augment_two; + out_data->item_aug_3 = trader_item.augment_three; + out_data->item_aug_4 = trader_item.augment_four; + out_data->item_aug_5 = trader_item.augment_five; + out_data->item_aug_6 = trader_item.augment_six; + out_data->item_quantity_available = trader_item.item_charges; + out_data->id = trader_item.id; strn0cpy(out_data->trader_buy_struct.buyer_name, GetCleanName(), sizeof(out_data->trader_buy_struct.buyer_name)); + strn0cpy(out_data->trader_buy_struct.buyer_name, in->buyer_name, sizeof(out_data->trader_buy_struct.buyer_name)); + strn0cpy(out_data->trader_buy_struct.item_name, in->item_name, sizeof(out_data->trader_buy_struct.item_name)); + strn0cpy(out_data->trader_buy_struct.seller_name, in->seller_name, sizeof(out_data->trader_buy_struct.seller_name)); + strn0cpy( + out_data->trader_buy_struct.item_unique_id, + in->item_unique_id, + sizeof(out_data->trader_buy_struct.item_unique_id) + ); worldserver.SendPacket(out_server.get()); @@ -3699,16 +3739,33 @@ void Client::CancelTraderTradeWindow() FastQueuePacket(&end_session); } -void Client::AddDataToMerchantList(int16 slot_id, int32 quantity, const std::string &item_unique_id) +void Client::AddDataToMerchantList(int16 slot_id, uint32 item_id, int32 quantity, const std::string &item_unique_id) { auto list = GetTraderMerchantList(); - list->emplace(std::pair(slot_id, std::make_tuple(quantity, item_unique_id))); + list->emplace(std::pair(slot_id, std::make_tuple(item_id, quantity, item_unique_id))); } -std::tuple Client::GetDataFromMerchantListByMerchantSlotId(int16 slot_id) +int16 Client::GetNextFreeSlotFromMerchantList() { auto list = GetTraderMerchantList(); - return list->contains(slot_id) ? list->at(slot_id) : std::make_tuple(INVALID_INDEX, "0000000000000000"); + for (auto const &[slot_id, merchant_data] : *list) { + auto [item_id, quantity, item_unique_id] = merchant_data; + if (item_id == 0) { + return slot_id; + } + } + + if (list->size() == GetInv().GetLookup()->InventoryTypeSize.Bazaar) { + return INVALID_INDEX; + } + + return list->size() + 1; +} + +std::tuple Client::GetDataFromMerchantListByMerchantSlotId(int16 slot_id) +{ + auto list = GetTraderMerchantList(); + return list->contains(slot_id) ? list->at(slot_id) : std::make_tuple(0, 0, "0000000000000000"); } int16 Client::GetSlotFromMerchantListByItemUniqueId(const std::string &unique_id) @@ -3716,7 +3773,7 @@ int16 Client::GetSlotFromMerchantListByItemUniqueId(const std::string &unique_id auto list = GetTraderMerchantList(); for (auto [slot_id, merchant_data] : *list) { - auto [quantity, item_unique_id] = merchant_data; + auto [item_id, quantity, item_unique_id] = merchant_data; if (item_unique_id == unique_id) { return slot_id; } @@ -3725,16 +3782,16 @@ int16 Client::GetSlotFromMerchantListByItemUniqueId(const std::string &unique_id return INVALID_INDEX; } -std::pair> Client::GetDataFromMerchantListByItemUniqueId(const std::string &unique_id) +std::pair> Client::GetDataFromMerchantListByItemUniqueId(const std::string &unique_id) { auto list = GetTraderMerchantList(); for (auto [slot_id, merchant_data] : *list) { - auto [quantity, item_unique_id] = merchant_data; + auto [item_id, quantity, item_unique_id] = merchant_data; if (item_unique_id == unique_id) { return { slot_id, merchant_data }; } } - return std::make_pair(INVALID_INDEX, std::make_tuple(0, "0000000000000000")); + return std::make_pair(INVALID_INDEX, std::make_tuple(0, 0, "0000000000000000")); }