From ab4e1191ef38a110c464966e16852a79de7ebe4c Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Sat, 15 Feb 2025 20:27:09 -0400 Subject: [PATCH] [Fix] Parcel Delivery Updates (#4688) * Fix two parcel bugs Fix two Parcel Bugs - If a player was at their parcel limit and perform a bazaar purchase via parcel delivery, their money would be lost - If a container with items was delivered via parcel, the parcel under certain inventory conditions could be delivered into an incorrect slot resulting in the container being lost. * Incorrect field used for BagSize vs ItemSize. Silly mistake. * Remove duplicate check and reorder stacking check * Fix edge case when Parcel Window remains open and Bazaar purchases are made. * Repair - bazaar purchase of items with charges reverting to 1 charge in error - bazaar visual error with selling price. Was caused by the parcel fee not being properly reflected in the client - corrected a type mismatch with parcel fee uint32 vs uin64 - corrected a few TraderPurchase and TraderSell event data points by splitting quantity and charges * Formatting * Use pre-existing AddMoney and TakeMoney and remove unnecessary routines * Updates after rebase --- common/events/player_events.h | 14 ++- common/inventory_profile.cpp | 54 +++++++++ common/inventory_profile.h | 1 + zone/client.cpp | 101 ----------------- zone/client.h | 2 - zone/parcels.cpp | 204 ++++++++++++++-------------------- zone/trading.cpp | 79 +++++++------ zone/worldserver.cpp | 32 +++--- 8 files changed, 210 insertions(+), 277 deletions(-) diff --git a/common/events/player_events.h b/common/events/player_events.h index 615a475a8..77ea744a5 100644 --- a/common/events/player_events.h +++ b/common/events/player_events.h @@ -882,8 +882,9 @@ namespace PlayerEvent { uint32 trader_id; std::string trader_name; uint32 price; - uint32 charges; - uint32 total_cost; + uint32 quantity; + int32 charges; + uint64 total_cost; uint64 player_money_balance; @@ -903,6 +904,7 @@ namespace PlayerEvent { CEREAL_NVP(trader_id), CEREAL_NVP(trader_name), CEREAL_NVP(price), + CEREAL_NVP(quantity), CEREAL_NVP(charges), CEREAL_NVP(total_cost), CEREAL_NVP(player_money_balance) @@ -922,8 +924,9 @@ namespace PlayerEvent { uint32 buyer_id; std::string buyer_name; uint32 price; - uint32 charges; - uint32 total_cost; + uint32 quantity; + int32 charges; + uint64 total_cost; uint64 player_money_balance; @@ -943,6 +946,7 @@ namespace PlayerEvent { CEREAL_NVP(buyer_id), CEREAL_NVP(buyer_name), CEREAL_NVP(price), + CEREAL_NVP(quantity), CEREAL_NVP(charges), CEREAL_NVP(total_cost), CEREAL_NVP(player_money_balance) @@ -1152,6 +1156,7 @@ namespace PlayerEvent { uint32 augment_5_id; uint32 augment_6_id; uint32 quantity; + int32 charges; std::string from_player_name; std::string to_player_name; uint32 sent_date; @@ -1169,6 +1174,7 @@ namespace PlayerEvent { CEREAL_NVP(augment_5_id), CEREAL_NVP(augment_6_id), CEREAL_NVP(quantity), + CEREAL_NVP(charges), CEREAL_NVP(from_player_name), CEREAL_NVP(to_player_name), CEREAL_NVP(sent_date) diff --git a/common/inventory_profile.cpp b/common/inventory_profile.cpp index 4022a08ce..7017184d9 100644 --- a/common/inventory_profile.cpp +++ b/common/inventory_profile.cpp @@ -2028,3 +2028,57 @@ int16 EQ::InventoryProfile::_HasEvolvingItem(ItemInstQueue &iqueue, uint64 evolv return INVALID_INDEX; } + + +int16 EQ::InventoryProfile::FindFirstFreeSlotThatFitsItemWithStacking(ItemInstance *item_inst) const +{ + auto item_data = item_inst->GetItem(); + if (!item_data) { + return INVALID_INDEX; + } + + for (int16 i = invslot::GENERAL_BEGIN; i <= invslot::GENERAL_END; i++) { + auto const inv_item = GetItem(i); + if (!inv_item) { + // Found available slot in personal inventory + // Anything will fit here + return i; + } + + if (item_data->IsClassBag() && item_inst->IsNoneEmptyContainer()) { + // If the inbound item is a bag with items, it cannot be stored within a bag + // Move to next potential slot + continue; + } + + if (inv_item->GetID() == item_data->ID && item_data->Stackable) { + if (item_inst->GetCharges() + inv_item->GetCharges() <= item_data->StackSize) { + // Found a personal inventory slot that has room for a stackable addition + return i; + } + } + + if (inv_item->IsClassBag() && CanItemFitInContainer(item_data, inv_item->GetItem())) { + int16 const base_slot_id = CalcSlotId(i, invbag::SLOT_BEGIN); + uint8 const bag_size = inv_item->GetItem()->BagSlots; + uint8 const item_size = item_data->Size; + + for (uint8 bag_slot = invbag::SLOT_BEGIN; bag_slot < bag_size; bag_slot++) { + auto bag_item = GetItem(base_slot_id + bag_slot); + if (!bag_item) { + // Found available slot within bag that will hold inbound item + return base_slot_id + bag_slot; + } + + if (bag_item && item_data->Stackable && bag_item->GetID() == item_data->ID) { + if (item_inst->GetCharges() + bag_item->GetCharges() <= item_data->StackSize) { + // Found a bag slot has room for a stackable addition + return base_slot_id + bag_slot; + } + } + } + } + } + + return INVALID_INDEX; +} diff --git a/common/inventory_profile.h b/common/inventory_profile.h index 887afd512..577342785 100644 --- a/common/inventory_profile.h +++ b/common/inventory_profile.h @@ -179,6 +179,7 @@ namespace EQ int16 FindFreeSlot(bool for_bag, bool try_cursor, uint8 min_size = 0, bool is_arrow = false); int16 FindFreeSlotForTradeItem(const ItemInstance* inst, int16 general_start = invslot::GENERAL_BEGIN, uint8 bag_start = invbag::SLOT_BEGIN); int16 FindFirstFreeSlotThatFitsItem(const EQ::ItemData *inst); + int16 FindFirstFreeSlotThatFitsItemWithStacking(ItemInstance *inst) const; // Calculate slot_id for an item within a bag static int16 CalcSlotId(int16 slot_id); // Calc parent bag's slot_id diff --git a/zone/client.cpp b/zone/client.cpp index 90d3b115a..8c61c04e8 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -12969,107 +12969,6 @@ void Client::RemoveItemBySerialNumber(uint32 serial_number, uint32 quantity) } } -void Client::AddMoneyToPPWithOverflow(uint64 copper, bool update_client) -{ - //I noticed in the ROF2 client that the client auto updates the currency values using overflow - //Therefore, I created this method to ensure that the db matches and clients don't see 10 pp 5 gp - //becoming 9pp 15 gold with the current AddMoneyToPP method. - - auto add_pp = copper / 1000; - auto add_gp = (copper - add_pp * 1000) / 100; - auto add_sp = (copper - add_pp * 1000 - add_gp * 100) / 10; - auto add_cp = copper - add_pp * 1000 - add_gp * 100 - add_sp * 10; - - m_pp.copper += add_cp; - if (m_pp.copper >= 10) { - m_pp.silver += m_pp.copper / 10; - m_pp.copper = m_pp.copper % 10; - } - - m_pp.silver += add_sp; - if (m_pp.silver >= 10) { - m_pp.gold += m_pp.silver / 10; - m_pp.silver = m_pp.silver % 10; - } - - m_pp.gold += add_gp; - if (m_pp.gold >= 10) { - m_pp.platinum += m_pp.gold / 10; - m_pp.gold = m_pp.gold % 10; - } - - m_pp.platinum += add_pp; - - if (update_client) { - SendMoneyUpdate(); - } - - RecalcWeight(); - SaveCurrency(); - - LogDebug("Client::AddMoneyToPPWithOverflow() [{}] should have: plat:[{}] gold:[{}] silver:[{}] copper:[{}]", - GetName(), - m_pp.platinum, - m_pp.gold, - m_pp.silver, - m_pp.copper - ); -} - -bool Client::TakeMoneyFromPPWithOverFlow(uint64 copper, bool update_client) -{ - int32 remove_pp = copper / 1000; - int32 remove_gp = (copper - remove_pp * 1000) / 100; - int32 remove_sp = (copper - remove_pp * 1000 - remove_gp * 100) / 10; - int32 remove_cp = copper - remove_pp * 1000 - remove_gp * 100 - remove_sp * 10; - - uint64 current_money = GetCarriedMoney(); - - if (copper > current_money) { - return false; //client does not have enough money on them - } - - m_pp.copper -= remove_cp; - if (m_pp.copper < 0) { - m_pp.silver -= 1; - m_pp.copper = m_pp.copper + 10; - if (m_pp.copper >= 10) { - m_pp.silver += m_pp.copper / 10; - m_pp.copper = m_pp.copper % 10; - } - } - - m_pp.silver -= remove_sp; - if (m_pp.silver < 0) { - m_pp.gold -= 1; - m_pp.silver = m_pp.silver + 10; - if (m_pp.silver >= 10) { - m_pp.gold += m_pp.silver / 10; - m_pp.silver = m_pp.silver % 10; - } - } - - m_pp.gold -= remove_gp; - if (m_pp.gold < 0) { - m_pp.platinum -= 1; - m_pp.gold = m_pp.gold + 10; - if (m_pp.gold >= 10) { - m_pp.platinum += m_pp.gold / 10; - m_pp.gold = m_pp.gold % 10; - } - } - - m_pp.platinum -= remove_pp; - - if (update_client) { - SendMoneyUpdate(); - } - - SaveCurrency(); - RecalcWeight(); - return true; -} - void Client::SendTopLevelInventory() { EQ::ItemInstance* inst = nullptr; diff --git a/zone/client.h b/zone/client.h index db1c3e9b4..3501a2326 100644 --- a/zone/client.h +++ b/zone/client.h @@ -879,11 +879,9 @@ public: void QuestReadBook(const char* text, uint8 type); void SendMoneyUpdate(); bool TakeMoneyFromPP(uint64 copper, bool update_client = false); - bool TakeMoneyFromPPWithOverFlow(uint64 copper, bool update_client); bool TakePlatinum(uint32 platinum, bool update_client = false); void AddMoneyToPP(uint64 copper, bool update_client = false); void AddMoneyToPP(uint32 copper, uint32 silver, uint32 gold, uint32 platinum, bool update_client = false); - void AddMoneyToPPWithOverflow(uint64 copper, bool update_client); void AddPlatinum(uint32 platinu, bool update_client = false); bool HasMoney(uint64 copper); uint64 GetCarriedMoney(); diff --git a/zone/parcels.cpp b/zone/parcels.cpp index 925deb7e0..a6187bc81 100644 --- a/zone/parcels.cpp +++ b/zone/parcels.cpp @@ -632,7 +632,13 @@ void Client::DoParcelRetrieve(const ParcelRetrieve_Struct &parcel_in) return; } - auto p = m_parcels.find(parcel_in.parcel_slot_id); + auto p = std::find_if( + m_parcels.begin(), + m_parcels.end(), + [&](const std::pair &x) { + return x.first == parcel_in.parcel_slot_id && x.second.item_id == parcel_in.parcel_item_id; + } + ); if (p != m_parcels.end()) { uint32 item_id = parcel_in.parcel_item_id; uint32 item_quantity = p->second.quantity; @@ -656,25 +662,70 @@ void Client::DoParcelRetrieve(const ParcelRetrieve_Struct &parcel_in) p->second.aug_slot_6 ) ); + if (!inst) { SendParcelRetrieveAck(); return; } + if (inst->IsStackable()) { + inst->SetCharges(item_quantity > 0 ? item_quantity : 1); + } + switch (parcel_in.parcel_item_id) { case PARCEL_MONEY_ITEM_ID: { AddMoneyToPP(p->second.quantity, true); MessageString( - Chat::Yellow, - PARCEL_DELIVERED, - merchant->GetCleanName(), - "Money", - p->second.from_name.c_str() + Chat::Yellow, PARCEL_DELIVERED, merchant->GetCleanName(), "Money", p->second.from_name.c_str() ); break; } default: { - auto free_id = GetInv().FindFreeSlot(false, false); + std::vector results{}; + if (inst->IsClassBag() && inst->GetItem()->BagSlots > 0) { + auto contents = inst->GetContents(); + results = CharacterParcelsContainersRepository::GetWhere( + database, fmt::format("`parcels_id` = {}", p->second.id) + ); + for (auto i: results) { + auto item = database.CreateItem( + i.item_id, + i.quantity, + i.aug_slot_1, + i.aug_slot_2, + i.aug_slot_3, + i.aug_slot_4, + i.aug_slot_5, + i.aug_slot_6 + ); + + if (!item) { + SendParcelRetrieveAck(); + return; + } + + if (CheckLoreConflict(item->GetItem())) { + if (RuleB(Parcel, DeleteOnDuplicate)) { + MessageString(Chat::Yellow, PARCEL_DUPLICATE_DELETE, inst->GetItem()->Name); + continue; + } + + MessageString(Chat::Yellow, DUP_LORE); + SendParcelRetrieveAck(); + return; + } + + contents->emplace(i.slot_id, item); + } + } + + auto const free_id = GetInv().FindFirstFreeSlotThatFitsItemWithStacking(inst.get()); + if (free_id == INVALID_INDEX) { + SendParcelRetrieveAck(); + MessageString(Chat::White, PARCEL_INV_FULL, merchant->GetCleanName()); + return; + } + if (CheckLoreConflict(inst->GetItem())) { if (RuleB(Parcel, DeleteOnDuplicate)) { MessageString(Chat::Yellow, PARCEL_DUPLICATE_DELETE, inst->GetItem()->Name); @@ -685,108 +736,33 @@ void Client::DoParcelRetrieve(const ParcelRetrieve_Struct &parcel_in) return; } } - else if (inst->IsStackable()) { - inst->SetCharges(item_quantity); - if (TryStacking(inst.get(), ItemPacketTrade, true, false)) { - MessageString( - Chat::Yellow, - PARCEL_DELIVERED_2, - merchant->GetCleanName(), - std::to_string(item_quantity).c_str(), - inst->GetItem()->Name, - p->second.from_name.c_str() - ); - } - else if (free_id != INVALID_INDEX) { - inst->SetCharges(item_quantity); - if (PutItemInInventory(free_id, *inst, true)) { - MessageString( - Chat::Yellow, - PARCEL_DELIVERED_2, - merchant->GetCleanName(), - std::to_string(item_quantity).c_str(), - inst->GetItem()->Name, - p->second.from_name.c_str() - ); - } - } - else { - MessageString(Chat::Yellow, PARCEL_INV_FULL, merchant->GetCleanName()); - SendParcelRetrieveAck(); - return; - } + + if (AutoPutLootInInventory(*inst.get(), false, true)) { + MessageString( + Chat::Yellow, + PARCEL_DELIVERED_2, + merchant->GetCleanName(), + std::to_string(item_quantity).c_str(), + inst->GetItem()->Name, + p->second.from_name.c_str() + ); } - else if (free_id != INVALID_INDEX) { - std::vector results{}; - if (inst->IsClassBag() && inst->GetItem()->BagSlots > 0) { - results = CharacterParcelsContainersRepository::GetWhere(database, fmt::format("`parcels_id` = {}", p->second.id)); - for (auto const &i : results) { - std::unique_ptr item( - database.CreateItem( - i.item_id, - i.quantity, - i.aug_slot_1, - i.aug_slot_2, - i.aug_slot_3, - i.aug_slot_4, - i.aug_slot_5, - i.aug_slot_6 - ) - ); - if (CheckLoreConflict(item->GetItem())) { - Message( - Chat::Yellow, - fmt::format("Lore Item Found in Inventory: {}", item->GetItem()->Name).c_str()); - MessageString(Chat::Yellow, DUP_LORE); - Message(Chat::Red, "Unable to retrieve parcel."); - SendParcelRetrieveAck(); - return; - } - } - } - inst->SetCharges(item_quantity > 0 ? item_quantity : 1); - if (PutItemInInventory(free_id, *inst.get(), true)) { - if (inst->IsClassBag() && inst->GetItem()->BagSlots > 0) { - for (auto const &i: results) { - std::unique_ptr item( - database.CreateItem( - i.item_id, - i.quantity, - i.aug_slot_1, - i.aug_slot_2, - i.aug_slot_3, - i.aug_slot_4, - i.aug_slot_5, - i.aug_slot_6 - ) - ); - auto bag_slot = EQ::InventoryProfile::CalcSlotId(free_id, i.slot_id); - PutItemInInventory(bag_slot, *item.get(), true); - } - } - MessageString( - Chat::Yellow, - PARCEL_DELIVERED, - merchant->GetCleanName(), - inst->GetItem()->Name, - p->second.from_name.c_str() - ); - if (player_event_logs.IsEventEnabled(PlayerEvent::PARCEL_RETRIEVE)) { - PlayerEvent::ParcelRetrieve e{}; - e.from_player_name = p->second.from_name; - e.item_id = p->second.item_id; - e.augment_1_id = p->second.aug_slot_1; - e.augment_2_id = p->second.aug_slot_2; - e.augment_3_id = p->second.aug_slot_3; - e.augment_4_id = p->second.aug_slot_4; - e.augment_5_id = p->second.aug_slot_5; - e.augment_6_id = p->second.aug_slot_6; - e.quantity = p->second.quantity; - e.sent_date = p->second.sent_date; - RecordPlayerEventLog(PlayerEvent::PARCEL_RETRIEVE, e); + if (player_event_logs.IsEventEnabled(PlayerEvent::PARCEL_RETRIEVE)) { + PlayerEvent::ParcelRetrieve e{}; + e.from_player_name = p->second.from_name; + e.item_id = p->second.item_id; + e.augment_1_id = p->second.aug_slot_1; + e.augment_2_id = p->second.aug_slot_2; + e.augment_3_id = p->second.aug_slot_3; + e.augment_4_id = p->second.aug_slot_4; + e.augment_5_id = p->second.aug_slot_5; + e.augment_6_id = p->second.aug_slot_6; + e.quantity = p->second.quantity; + e.sent_date = p->second.sent_date; + RecordPlayerEventLog(PlayerEvent::PARCEL_RETRIEVE, e); - for (auto const &i:results) { + for (auto const &i:results) { e.from_player_name = p->second.from_name; e.item_id = i.item_id; e.augment_1_id = i.aug_slot_1; @@ -798,19 +774,9 @@ void Client::DoParcelRetrieve(const ParcelRetrieve_Struct &parcel_in) e.quantity = i.quantity; e.sent_date = p->second.sent_date; RecordPlayerEventLog(PlayerEvent::PARCEL_RETRIEVE, e); - } - } + + } - else { - MessageString(Chat::Yellow, PARCEL_INV_FULL, merchant->GetCleanName()); - SendParcelRetrieveAck(); - return; - } - } - else { - MessageString(Chat::Yellow, PARCEL_INV_FULL, merchant->GetCleanName()); - SendParcelRetrieveAck(); - return; } } } @@ -819,6 +785,7 @@ void Client::DoParcelRetrieve(const ParcelRetrieve_Struct &parcel_in) SendParcelDelete(parcel_in); m_parcels.erase(p); } + SendParcelRetrieveAck(); SendParcelIconStatus(); } @@ -831,7 +798,6 @@ bool Client::DeleteParcel(uint32 parcel_id) return false; } - auto it = std::find_if(m_parcels.cbegin(), m_parcels.cend(), [&](const auto &x) { return x.second.id == parcel_id; }); SetParcelCount(GetParcelCount() - 1); return true; diff --git a/zone/trading.cpp b/zone/trading.cpp index 94273db7f..f3dc839d3 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -1458,12 +1458,13 @@ void Client::BuyTraderItem(TraderBuy_Struct *tbs, Client *Trader, const EQApplic .trader_id = Trader->CharacterID(), .trader_name = Trader->GetCleanName(), .price = tbs->price, - .charges = outtbs->quantity, + .quantity = outtbs->quantity, + .charges = buy_item->GetCharges(), .total_cost = (tbs->price * outtbs->quantity), .player_money_balance = GetCarriedMoney(), }; - RecordPlayerEventLog(PlayerEvent::TRADER_PURCHASE, e); + RecordPlayerEventLog(PlayerEvent::TRADER_PURCHASE, e); } if (player_event_logs.IsEventEnabled(PlayerEvent::TRADER_SELL)) { @@ -1479,7 +1480,8 @@ void Client::BuyTraderItem(TraderBuy_Struct *tbs, Client *Trader, const EQApplic .buyer_id = CharacterID(), .buyer_name = GetCleanName(), .price = tbs->price, - .charges = outtbs->quantity, + .quantity = outtbs->quantity, + .charges = buy_item->GetCharges(), .total_cost = (tbs->price * outtbs->quantity), .player_money_balance = Trader->GetCarriedMoney(), }; @@ -1960,8 +1962,8 @@ void Client::SellToBuyer(const EQApplicationPacket *app) } uint64 total_cost = (uint64) sell_line.item_cost * (uint64) sell_line.seller_quantity; - AddMoneyToPPWithOverflow(total_cost, false); - buyer->TakeMoneyFromPPWithOverFlow(total_cost, false); + AddMoneyToPP(total_cost, false); + buyer->TakeMoneyFromPP(total_cost, false); if (player_event_logs.IsEventEnabled(PlayerEvent::BARTER_TRANSACTION)) { PlayerEvent::BarterTransaction e{}; @@ -2879,6 +2881,21 @@ void Client::BuyTraderItemOutsideBazaar(TraderBuy_Struct *tbs, const EQApplicati 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->method = BazaarByParcel; + in->sub_action = TooManyParcels; + TraderRepository::UpdateActiveTransaction(database, trader_item.id, false); + TradeRequestFailed(app); + return; + } + LogTrading( "Name: [{}] IsStackable: [{}] Requested Quantity: [{}] Charges on Item [{}]", buy_item->GetItem()->Name, @@ -2888,23 +2905,24 @@ void Client::BuyTraderItemOutsideBazaar(TraderBuy_Struct *tbs, const EQApplicati ); // Determine the actual quantity for the purchase + int32 charges = static_cast(tbs->quantity); if (!buy_item->IsStackable()) { - tbs->quantity = 1; - } - else { - int32 item_charges = buy_item->GetCharges(); - if (item_charges <= 0) { - tbs->quantity = 1; + if (buy_item->GetCharges() <= 0) { + charges = 1; } - else if (static_cast(item_charges) < tbs->quantity) { - tbs->quantity = item_charges; + else { + charges = buy_item->GetCharges(); } } - LogTrading("Actual quantity that will be traded is [{}]", tbs->quantity); + LogTrading( + "Actual quantity that will be traded is [{}] {}", + tbs->quantity, + buy_item->GetCharges() ? fmt::format("with {} charges", buy_item->GetCharges()) : "" + ); - uint64 total_transaction_value = static_cast(tbs->price) * static_cast(tbs->quantity); - if (total_transaction_value > MAX_TRANSACTION_VALUE) { + uint64 total_cost = static_cast(tbs->price) * static_cast(tbs->quantity); + if (total_cost > MAX_TRANSACTION_VALUE) { Message( Chat::Red, "That would exceed the single transaction limit of %u platinum.", @@ -2915,9 +2933,8 @@ void Client::BuyTraderItemOutsideBazaar(TraderBuy_Struct *tbs, const EQApplicati return; } - uint32 total_cost = tbs->price * tbs->quantity; - uint32 fee = static_cast(std::round((uint32) total_cost * RuleR(Bazaar, ParcelDeliveryCostMod))); - if (!TakeMoneyFromPP(total_cost + fee)) { + uint64 fee = std::round(total_cost * RuleR(Bazaar, ParcelDeliveryCostMod)); + if (!TakeMoneyFromPP(total_cost + fee, false)) { RecordPlayerEventLog( PlayerEvent::POSSIBLE_HACK, PlayerEvent::PossibleHackEvent{ @@ -2951,7 +2968,8 @@ void Client::BuyTraderItemOutsideBazaar(TraderBuy_Struct *tbs, const EQApplicati .trader_id = tbs->trader_id, .trader_name = tbs->seller_name, .price = tbs->price, - .charges = tbs->quantity, + .quantity = tbs->quantity, + .charges = buy_item->IsStackable() ? 1 : charges, .total_cost = total_cost, .player_money_balance = GetCarriedMoney(), }; @@ -2960,24 +2978,10 @@ void Client::BuyTraderItemOutsideBazaar(TraderBuy_Struct *tbs, const EQApplicati } CharacterParcelsRepository::CharacterParcels parcel_out{}; - 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->method = BazaarByParcel; - in->sub_action = TooManyParcels; - TraderRepository::UpdateActiveTransaction(database, trader_item.id, false); - TradeRequestFailed(app); - return; - } parcel_out.from_name = tbs->seller_name; parcel_out.note = "Delivered from a Bazaar Purchase"; parcel_out.sent_date = time(nullptr); - parcel_out.quantity = buy_item->IsStackable() ? tbs->quantity : buy_item->GetCharges(); + 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); @@ -3017,7 +3021,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 = parcel_out.quantity; + e.quantity = tbs->quantity; + e.charges = buy_item->IsStackable() ? 1 : charges; e.sent_date = parcel_out.sent_date; RecordPlayerEventLog(PlayerEvent::PARCEL_SEND, e); @@ -3060,6 +3065,8 @@ void Client::BuyTraderItemOutsideBazaar(TraderBuy_Struct *tbs, const EQApplicati strn0cpy(out_data->trader_buy_struct.buyer_name, GetCleanName(), sizeof(out_data->trader_buy_struct.buyer_name)); worldserver.SendPacket(out_server.get()); + + SendMoneyUpdate(); } void Client::SetBuyerWelcomeMessage(const char *welcome_message) diff --git a/zone/worldserver.cpp b/zone/worldserver.cpp index accfd5a65..4dd61f588 100644 --- a/zone/worldserver.cpp +++ b/zone/worldserver.cpp @@ -4002,21 +4002,23 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) TraderRepository::UpdateActiveTransaction(database, in->id, false); + auto item = trader_pc->FindTraderItemBySerialNumber(item_sn); + if (player_event_logs.IsEventEnabled(PlayerEvent::TRADER_SELL)) { - auto buy_item = trader_pc->FindTraderItemBySerialNumber(item_sn); - auto e = PlayerEvent::TraderSellEvent{ - .item_id = in->trader_buy_struct.item_id, - .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), + auto e = PlayerEvent::TraderSellEvent{ + .item_id = item ? item->GetID() : 0, + .augment_1_id = item->GetAugmentItemID(0), + .augment_2_id = item->GetAugmentItemID(1), + .augment_3_id = item->GetAugmentItemID(2), + .augment_4_id = item->GetAugmentItemID(3), + .augment_5_id = item->GetAugmentItemID(4), + .augment_6_id = item->GetAugmentItemID(5), .item_name = in->trader_buy_struct.item_name, .buyer_id = in->buyer_id, .buyer_name = in->trader_buy_struct.buyer_name, .price = in->trader_buy_struct.price, - .charges = in->trader_buy_struct.quantity, + .quantity = in->trader_buy_struct.quantity, + .charges = item ? item->IsStackable() ? 1 : item->GetCharges() : 0, .total_cost = (in->trader_buy_struct.price * in->trader_buy_struct.quantity), .player_money_balance = trader_pc->GetCarriedMoney(), }; @@ -4121,7 +4123,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) sell_line.seller_quantity, sell_line.item_name, buyer->GetCleanName()); - buyer->AddMoneyToPPWithOverflow(total_cost, true); + buyer->AddMoneyToPP(total_cost, true); buyer->RemoveItem(sell_line.item_id, sell_line.seller_quantity); buyer->Message( @@ -4220,7 +4222,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) if (inst->IsStackable()) { if (!buyer->PutItemInInventoryWithStacking(inst.get())) { buyer->Message(Chat::Red, "Error putting item in your inventory."); - buyer->AddMoneyToPPWithOverflow(total_cost, true); + buyer->AddMoneyToPP(total_cost, true); in->action = Barter_FailedTransaction; in->sub_action = Barter_FailedBuyerChecks; worldserver.SendPacket(pack); @@ -4232,7 +4234,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) inst->SetCharges(1); if (!buyer->PutItemInInventoryWithStacking(inst.get())) { buyer->Message(Chat::Red, "Error putting item in your inventory."); - buyer->AddMoneyToPPWithOverflow(total_cost, true); + buyer->AddMoneyToPP(total_cost, true); in->action = Barter_FailedTransaction; in->sub_action = Barter_FailedBuyerChecks; worldserver.SendPacket(pack); @@ -4241,7 +4243,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) } } - if (!buyer->TakeMoneyFromPPWithOverFlow(total_cost, false)) { + if (!buyer->TakeMoneyFromPP(total_cost, false)) { in->action = Barter_FailedTransaction; in->sub_action = Barter_FailedBuyerChecks; worldserver.SendPacket(pack); @@ -4306,7 +4308,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) uint64 total_cost = (uint64) sell_line.item_cost * (uint64) sell_line.seller_quantity; seller->RemoveItem(in->buy_item_id, in->seller_quantity); - seller->AddMoneyToPPWithOverflow(total_cost, false); + seller->AddMoneyToPP(total_cost, false); seller->SendBarterBuyerClientMessage( sell_line, Barter_SellerTransactionComplete,