/* EQ2Emulator: Everquest II Server Emulator Copyright (C) 2005 - 2026 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net) This file is part of EQ2Emulator. EQ2Emulator is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. EQ2Emulator is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with EQ2Emulator. If not, see . */ #include "BrokerManager.h" #include "../Items/Items.h" #include "../../common/Log.h" #include "../WorldDatabase.h" #include "../World.h" #include "../Web/PeerManager.h" #include extern WorldDatabase database; extern ZoneList zone_list; extern PeerManager peer_manager; BrokerManager::BrokerManager() { } void BrokerManager::AddSeller(int32 cid, const std::string& name, int32 hid, bool enabled, bool invFlag) { int64 prevSession = 0, prevTotal = 0; { std::shared_lock lock(mtx_); auto sit = players_.find(cid); if (sit != players_.end()) { prevSession = sit->second.coin_session; prevTotal = sit->second.coin_total; } } SellerInfo info{cid, name, hid, enabled, invFlag, prevSession, prevTotal}; { std::unique_lock lock(mtx_); players_[cid] = info; } SavePlayerToDB(info); peer_manager.sendPeersAddSeller(cid, hid, name, enabled, invFlag); } void BrokerManager::LoadSeller(int32 cid, const std::string& name, int32 hid, bool enabled, bool invFlag, int64 coin_session, int64 coin_total) { SellerInfo info{cid, name, hid, enabled, invFlag, coin_session, coin_total}; { std::unique_lock lock(mtx_); players_[cid] = info; } } int64 BrokerManager::ResetSellerSessionCoins(int32 cid) { database.ClearSellerSession(cid); int64 session_coin = 0; { std::unique_lock lock(mtx_); auto it = players_.find(cid); if (it == players_.end()) return 0; session_coin = it->second.coin_session; it->second.coin_total += it->second.coin_session; it->second.coin_session = 0; } return session_coin; } void BrokerManager::AddSellerSessionCoins(int32 cid, uint64 session) { database.AddToSellerSession(cid, session); { std::unique_lock lock(mtx_); auto it = players_.find(cid); if (it == players_.end()) return; it->second.coin_session += session; } } void BrokerManager::RemoveSeller(int32 cid, bool peerCacheOnly) { { std::unique_lock lock(mtx_); players_.erase(cid); active_items_by_char_.erase(cid); inactive_items_by_char_.erase(cid); } if(!peerCacheOnly) peer_manager.sendPeersRemoveSeller(cid); } void BrokerManager::AddItem(const SaleItem& item, bool peerCacheOnly) { { std::unique_lock lock(mtx_); auto& a = active_items_by_char_[item.character_id]; auto& i = inactive_items_by_char_[item.character_id]; a.erase(item.unique_id); i.erase(item.unique_id); if (item.for_sale) a[item.unique_id] = item; else i[item.unique_id] = item; } SaveItemToDB(item); if(!peerCacheOnly) peer_manager.sendPeersAddItemSale(item.character_id, item.house_id, item.item_id, item.unique_id, item.cost_copper, item.inv_slot_id, item.slot_id, item.count, item.from_inventory, item.for_sale, item.creator); } void BrokerManager::LoadItem(const SaleItem& item) { std::unique_lock lock(mtx_); if (item.for_sale) active_items_by_char_[item.character_id][item.unique_id] = item; else inactive_items_by_char_[item.character_id][item.unique_id] = item; } void BrokerManager::SetSaleStatus(int32 cid, int64 uid, bool for_sale) { std::optional toUpdate; { std::unique_lock lock(mtx_); auto& a = active_items_by_char_[cid]; auto& i = inactive_items_by_char_[cid]; if (for_sale) { LogWrite(PLAYER__ERROR, 5, "Broker", "--SetSaleStatus: %u (%u), for_sale=%u", cid, uid, for_sale ); if (auto it = i.find(uid); it != i.end()) { LogWrite(PLAYER__ERROR, 5, "Broker", "--SetSaleStatusFuckOff: %u (%u), for_sale=%u", cid, uid, for_sale ); SaleItem copy = it->second; copy.for_sale = true; i.erase(it); a[uid] = copy; toUpdate = copy; } } else { LogWrite(PLAYER__ERROR, 5, "Broker", "--SetSaleStatusInactive: %u (%u), for_sale=%u", cid, uid, for_sale ); if (auto it = a.find(uid); it != a.end()) { LogWrite(PLAYER__ERROR, 5, "Broker", "--SetSaleStatusFuckyou: %u (%u), for_sale=%u", cid, uid, for_sale ); SaleItem copy = it->second; copy.for_sale = false; a.erase(it); i[uid] = copy; toUpdate = copy; } } } if (toUpdate) { SaveItemToDB(*toUpdate); peer_manager.sendPeersAddItemSale(toUpdate->character_id, toUpdate->house_id, toUpdate->item_id, toUpdate->unique_id, toUpdate->cost_copper, toUpdate->inv_slot_id, toUpdate->slot_id, toUpdate->count, toUpdate->from_inventory, toUpdate->for_sale, toUpdate->creator); } } bool BrokerManager::IsItemListed(int32 cid, int64 uid) { std::shared_lock lock(mtx_); auto& active_map = active_items_by_char_[cid]; auto& inactive_map = inactive_items_by_char_[cid]; auto it = inactive_map.find(uid); if (it != inactive_map.end()) { return true; } auto it2 = active_map.find(uid); if (it2 != active_map.end()) { return true; } return false; } void BrokerManager::SetSalePrice(int32 cid, int64 uid, int64 price) { std::optional toUpdate; { std::unique_lock lock(mtx_); if (auto ait = active_items_by_char_.find(cid); ait != active_items_by_char_.end()) { if (auto it = ait->second.find(uid); it != ait->second.end()) { LogWrite(PLAYER__ERROR, 5, "Broker", "--SetSalePriceActive: %u (%u), cost=%u", cid, uid, price ); it->second.cost_copper = price; toUpdate = it->second; } } if (auto iit = inactive_items_by_char_.find(cid); iit != inactive_items_by_char_.end()) { if (auto it = iit->second.find(uid); it != iit->second.end()) { LogWrite(PLAYER__ERROR, 5, "Broker", "--SetSalePriceInactive: %u (%u), cost=%u", cid, uid, price ); it->second.cost_copper = price; toUpdate = it->second; } } } if (toUpdate) { SaveItemToDB(*toUpdate); peer_manager.sendPeersAddItemSale(toUpdate->character_id, toUpdate->house_id, toUpdate->item_id, toUpdate->unique_id, toUpdate->cost_copper, toUpdate->inv_slot_id, toUpdate->slot_id, toUpdate->count, toUpdate->from_inventory, toUpdate->for_sale, toUpdate->creator); } } void BrokerManager::RemoveItem(int32 cid, int64 uid, int16 qty, bool shouldDelete) { bool didDelete = false; std::optional snapshot; { std::unique_lock lock(mtx_); if (auto ait = active_items_by_char_.find(cid); ait != active_items_by_char_.end()) { auto& m = ait->second; if (auto it = m.find(uid); it != m.end()) { if(shouldDelete) qty = it->second.count; it->second.count -= qty; if (it->second.count <= 0) { didDelete = true; snapshot = it->second; m.erase(it); } else { snapshot = it->second; } if (m.empty()) active_items_by_char_.erase(ait); } } } if (didDelete || shouldDelete) { DeleteItemFromDB(cid, uid); peer_manager.sendPeersRemoveItemSale(cid, uid); } else if (snapshot) { SaveItemToDB(*snapshot); peer_manager.sendPeersAddItemSale(snapshot->character_id, snapshot->house_id, snapshot->item_id, snapshot->unique_id, snapshot->cost_copper, snapshot->inv_slot_id, snapshot->slot_id, snapshot->count, snapshot->from_inventory, snapshot->for_sale, snapshot->creator); } } bool BrokerManager::BuyItem(Client* buyer, int32 seller_cid, int64 uid, int32 quantity) { Client* seller = zone_list.GetClientByCharID(seller_cid); // establish if seller is online if(buyer && buyer->GetCharacterID() == seller_cid) { buyer->Message(CHANNEL_COLOR_RED, "You cannot buy from yourself!"); return false; } if (quantity <= 0 || !IsSaleEnabled(seller_cid) || !IsItemForSale(seller_cid, uid)) { if(buyer) buyer->Message(CHANNEL_COLOR_RED, "Quantity not provided (%u), sale is not enabled (sale enabled? %u) or item is not for sale (itemforsale? %u).", quantity, IsSaleEnabled(seller_cid), IsItemForSale(seller_cid, uid)); return false; } int64 price = GetSalePrice(seller_cid, uid) * quantity; if(!buyer || !buyer->GetPlayer() || !buyer->GetPlayer()->RemoveCoins(price)) { if(buyer) buyer->Message(CHANNEL_COLOR_RED, "You do not have enough coin to purchase."); return false; } if (!database.RemoveBrokerItem(seller_cid, uid, quantity)) { buyer->GetPlayer()->AddCoins(price); buyer->Message(CHANNEL_COLOR_RED, "Failed to remove broker item from database."); return false; } Item* giveItem = nullptr; int16 result_count = 0; std::string creator; bool deleteItem = false; int16 quantity_left = 0; // 2) Mirror in-memory std::optional toUpdate; { std::unique_lock lock(mtx_); if (auto sit = active_items_by_char_.find(seller_cid); sit != active_items_by_char_.end()) { auto& m = sit->second; if (auto it = m.find(uid); it != m.end()) { creator = it->second.creator; giveItem = master_item_list.GetItem(it->second.item_id); SaleItem copy = it->second; toUpdate = copy; if (it->second.count > quantity) { it->second.count -= quantity; quantity_left = it->second.count; result_count = quantity; if(seller && seller->GetPlayer()) { seller->GetPlayer()->item_list.SetVaultItemUniqueIDCount(seller, uid, it->second.count); } } else { result_count = it->second.count; if(seller && seller->GetPlayer()) { seller->GetPlayer()->item_list.RemoveVaultItemFromUniqueID(seller, uid); } m.erase(it); deleteItem = true; } if (m.empty()) active_items_by_char_.erase(sit); } } } if(!giveItem) { buyer->GetPlayer()->AddCoins(price); buyer->Message(CHANNEL_COLOR_RED, "Failed to find item on broker memory."); if(toUpdate) AddItem(*toUpdate); return false; } Item* resultItem = new Item(giveItem); resultItem->details.count = result_count; resultItem->creator = creator; if (buyer->GetPlayer()->item_list.HasFreeSlot() || buyer->GetPlayer()->item_list.CanStack(resultItem, false)) { if(!buyer->AddItem(resultItem, nullptr, AddItemType::BUY_FROM_BROKER)) { buyer->GetPlayer()->AddCoins(price); safe_delete(resultItem); if(toUpdate) AddItem(*toUpdate); return false; } } else { buyer->Message(CHANNEL_COLOR_RED, "You have no free slot available."); buyer->GetPlayer()->AddCoins(price); safe_delete(resultItem); if(toUpdate) AddItem(*toUpdate); return false; } if(deleteItem) { DeleteItemFromDB(seller_cid, uid); DeleteCharacterItemFromDB(seller_cid, uid); } else if(quantity_left) { UpdateCharacterItemDB(seller_cid, uid, quantity_left); } AddSellerSessionCoins(seller_cid, price); LogSale(seller_cid, std::string(buyer->GetPlayer()->GetName()), std::string(resultItem->name), result_count, price); if(seller) { std::string logMsg = GetShopPurchaseMessage(buyer->GetPlayer()->GetName(), std::string(resultItem->name), result_count, price); auto seller_info = GetSellerInfo(seller_cid); seller->SendHouseSaleLog(logMsg, 0, seller_info ? seller_info->coin_total : 0, 0); seller->OpenShopWindow(nullptr); } return true; } void BrokerManager::OnPeerRemoveItem(int32 cid, int64 uid) { std::unique_lock lock(mtx_); if (auto ait = active_items_by_char_.find(cid); ait != active_items_by_char_.end()) ait->second.erase(uid); if (auto iit = inactive_items_by_char_.find(cid); iit != inactive_items_by_char_.end()) iit->second.erase(uid); } bool BrokerManager::IsItemForSale(int32 cid, int64 uid) const { std::shared_lock lock(mtx_); if (auto pit = active_items_by_char_.find(cid); pit != active_items_by_char_.end()) return pit->second.find(uid) != pit->second.end(); return false; } int64 BrokerManager::GetSalePrice(int32 cid, int64 uid) { std::shared_lock lock(mtx_); if (auto pit = active_items_by_char_.find(cid); pit != active_items_by_char_.end()) { if (auto it = pit->second.find(uid); it != pit->second.end()) return it->second.cost_copper; } if (auto pit2 = inactive_items_by_char_.find(cid); pit2 != inactive_items_by_char_.end()) { if (auto it = pit2->second.find(uid); it != pit2->second.end()) return it->second.cost_copper; } return 0; } std::vector BrokerManager::GetActiveForSaleItems(int32 cid) const { std::shared_lock lock(mtx_); std::vector out; if (auto pit = active_items_by_char_.find(cid); pit != active_items_by_char_.end()) { for (auto const& kv : pit->second) out.push_back(kv.second); } return out; } std::optional BrokerManager::GetActiveItem(int32 cid, int64 uid) const { std::shared_lock lock(mtx_); auto pit = active_items_by_char_.find(cid); if (pit == active_items_by_char_.end()) return std::nullopt; auto it = pit->second.find(uid); if (it == pit->second.end()) return std::nullopt; return it->second; // copy out the SaleItem } bool BrokerManager::IsSellingItems(int32 cid, bool vaultOnly) const { // Grab shared lock for thread‐safe read std::shared_lock lock(mtx_); // Find this character’s active sales auto pit = active_items_by_char_.find(cid); if (pit == active_items_by_char_.end()) return false; // no items => not selling from vault // Scan through each SaleItem; if any has sell_from_inventory == false, // that means it’s coming from the vault for (auto const& kv : pit->second) { const SaleItem& item = kv.second; if ((item.for_sale && (!item.from_inventory || !vaultOnly))) return true; } return false; } std::vector BrokerManager::GetInactiveItems(int32 cid) const { std::shared_lock lock(mtx_); std::vector out; if (auto pit = inactive_items_by_char_.find(cid); pit != inactive_items_by_char_.end()) { for (auto const& kv : pit->second) out.push_back(kv.second); } return out; } std::vector> BrokerManager::GetUniqueIDsAndCost(int32 cid) const { std::shared_lock lock(mtx_); std::vector> out; if (auto pit = active_items_by_char_.find(cid); pit != active_items_by_char_.end()) { for (auto const& kv : pit->second) out.emplace_back(kv.second.unique_id, kv.second.cost_copper); } return out; } vector* BrokerManager::GetItems( const std::string& name, int64 itype, int64 ltype, int64 btype, int64 minprice, int64 maxprice, int8 minskill, int8 maxskill, const std::string& seller, const std::string& adornment, int8 mintier, int8 maxtier, int16 minlevel, int16 maxlevel, int8 itemclass ) const { vector* ret = new vector; string lower_name = ::ToLower(string(name.c_str())); std::shared_lock lock(mtx_); std::vector out; for (auto const& char_pair : active_items_by_char_) { int32 cid = char_pair.first; auto pit_player = players_.find(cid); if (pit_player == players_.end()) continue; bool allowInv = pit_player->second.sell_from_inventory; for (auto const& kv : char_pair.second) { auto const& itm = kv.second; LogWrite(PLAYER__DEBUG, 5, "Broker", "--GetItems: %u (selling: %u), allowinv: %u", itm.unique_id, itm.for_sale, allowInv ); if (!itm.for_sale) continue; if (!allowInv && itm.from_inventory) continue; Item* def = master_item_list.GetItem(itm.item_id); if (!def) continue; LogWrite(PLAYER__DEBUG, 5, "Broker", "--GetItems#1: %u (selling: %u), allowinv: %u", itm.unique_id, itm.for_sale, allowInv ); if (!name.empty() && def->lowername.find(lower_name) == std::string::npos) continue; if (itype!=ITEM_BROKER_TYPE_ANY && itype !=ITEM_BROKER_TYPE_ANY64BIT && !master_item_list.ShouldAddItemBrokerType(def, itype)) continue; if (ltype!=ITEM_BROKER_SLOT_ANY && !master_item_list.ShouldAddItemBrokerSlot(def, ltype)) continue; if (btype!=0xFFFFFFFF && !master_item_list.ShouldAddItemBrokerStat(def, btype)) continue; LogWrite(PLAYER__DEBUG, 5, "Broker", "--GetItems#2: %u (cost_copper: %u), seller: %s", itm.unique_id, itm.cost_copper, seller.c_str() ); //if (itm.cost_copper < minprice || itm.cost_copper > maxprice) continue; //if (!seller.empty() && pit_player->second.seller_name.find(seller)==std::string::npos) continue; /*if(mintier > 1 && def->details.tier < mintier) continue; if(maxtier > 0 && def->details.tier > maxtier) continue;*/ LogWrite(PLAYER__DEBUG, 5, "Broker", "--GetItems#3: %u (selling: %u), allowinv: %u", itm.unique_id, itm.for_sale, allowInv ); if(def->generic_info.adventure_default_level == 0 && def->generic_info.tradeskill_default_level == 0 && minlevel > 0 && maxlevel > 0){ if(def->details.recommended_level < minlevel) continue; if(def->details.recommended_level > maxlevel) continue; } else{ if(minlevel > 0 && ((def->generic_info.adventure_default_level == 0 && def->generic_info.tradeskill_default_level == 0) || (def->generic_info.adventure_default_level > 0 && def->generic_info.adventure_default_level < minlevel) || (def->generic_info.tradeskill_default_level > 0 && def->generic_info.tradeskill_default_level < minlevel))) continue; if(maxlevel > 0 && ((def->generic_info.adventure_default_level > 0 && def->generic_info.adventure_default_level > maxlevel) || (def->generic_info.tradeskill_default_level > 0 && def->generic_info.tradeskill_default_level > maxlevel))) continue; } if (itemclass>0) { int64 bit = ((int64)2 << (itemclass-1)); if (!(def->generic_info.adventure_classes & bit) && !(def->generic_info.tradeskill_classes & bit)) continue; } LogWrite(PLAYER__DEBUG, 5, "Broker", "--GetItemsPass: %u (selling: %u), allowinv: %u", itm.unique_id, itm.for_sale, allowInv ); ret->push_back(new Item(def, itm.unique_id, itm.creator, pit_player->second.seller_name, cid, itm.cost_copper, itm.count, pit_player->second.house_id, itm.from_inventory)); } } return ret; } std::string BrokerManager::GetSellerName(int32 cid) const { std::shared_lock lock(mtx_); auto it = players_.find(cid); return (it != players_.end()) ? it->second.seller_name : std::string(); } bool BrokerManager::IsSaleEnabled(int32 cid) const { std::shared_lock lock(mtx_); auto it = players_.find(cid); return (it != players_.end()) ? it->second.sale_enabled : false; } bool BrokerManager::CanSellFromInventory(int32 cid) const { std::shared_lock lock(mtx_); auto it = players_.find(cid); return (it != players_.end()) ? it->second.sell_from_inventory : false; } int32 BrokerManager::GetHouseID(int32 cid) const { std::shared_lock lock(mtx_); auto it = players_.find(cid); return (it != players_.end()) ? it->second.house_id : -1; } std::optional BrokerManager::GetSellerInfo(int32 cid) const { std::shared_lock lock(mtx_); auto it = players_.find(cid); if (it == players_.end()) return std::nullopt; return it->second; } void BrokerManager::SavePlayerToDB(const SellerInfo& p) { Query q; std::ostringstream oss; oss << "INSERT INTO broker_sellers " "(character_id,seller_name,house_id,sale_enabled,sell_from_inventory,coin_session,coin_total) " "VALUES(" << p.character_id << ",'" << EscapeSQLString(p.seller_name) << "'," << p.house_id << "," << (p.sale_enabled ? 1 : 0) << "," << (p.sell_from_inventory ? 1 : 0) << "," << p.coin_session << "," << p.coin_total << ") ON DUPLICATE KEY UPDATE " "seller_name=VALUES(seller_name)," "house_id=VALUES(house_id)," "sale_enabled=VALUES(sale_enabled)," "sell_from_inventory=VALUES(sell_from_inventory)," "coin_session=VALUES(coin_session), " "coin_total=VALUES(coin_total)"; std::string sql = oss.str(); q.AddQueryAsync(p.character_id, &database, Q_INSERT, sql.c_str() ); } #include void BrokerManager::SaveItemToDB(const SaleItem& i) { // 2) Build full SQL std::ostringstream oss; oss << "INSERT INTO broker_items " "(unique_id,character_id,house_id,item_id,cost_copper,for_sale," "inv_slot_id,slot_id,`count`,from_inventory,creator) " "VALUES(" << i.unique_id << "," << i.character_id << "," << i.house_id << "," << i.item_id << "," << i.cost_copper << "," << (i.for_sale ? 1 : 0) << "," << i.inv_slot_id << "," << i.slot_id << "," << i.count << "," << (i.from_inventory ? 1 : 0) << ",'" << EscapeSQLString(i.creator) << "') " "ON DUPLICATE KEY UPDATE " "house_id=VALUES(house_id)," "item_id=VALUES(item_id)," "cost_copper=VALUES(cost_copper)," "for_sale=VALUES(for_sale)," "inv_slot_id=VALUES(inv_slot_id)," "slot_id=VALUES(slot_id)," "`count`=VALUES(`count`)," "from_inventory=VALUES(from_inventory)," "creator=VALUES(creator)"; std::string sql = oss.str(); Query q; q.AddQueryAsync(i.character_id, &database, Q_INSERT, sql.c_str()); } void BrokerManager::UpdateItemInDB(const SaleItem& i) { Query q; std::ostringstream oss; oss << "UPDATE broker_items SET " << "house_id=" << i.house_id << ",item_id=" << i.item_id << ",cost_copper=" << i.cost_copper << ",for_sale=" << (i.for_sale ? 1 : 0) << ",inv_slot_id=" << i.inv_slot_id << ",slot_id=" << i.slot_id << ",count=" << i.count << ",from_inventory=" << (i.from_inventory ? 1 : 0) << ",creator='" << EscapeSQLString(i.creator) << "' " << "WHERE unique_id=" << i.unique_id << " AND character_id=" << i.character_id; std::string sql = oss.str(); q.AddQueryAsync(i.character_id, &database, Q_UPDATE, sql.c_str() ); } void BrokerManager::DeleteItemFromDB(int32 cid, int64 uid) { Query q; q.AddQueryAsync(cid, &database, Q_DELETE, "DELETE FROM broker_items WHERE unique_id=%llu AND character_id=%u", uid, cid ); } void BrokerManager::DeleteCharacterItemFromDB(int32 cid, int64 uid) { Query q; q.AddQueryAsync(cid, &database, Q_DELETE, "DELETE FROM character_items WHERE id=%llu AND char_id=%u", uid, cid ); } void BrokerManager::UpdateCharacterItemDB(int32 cid, int64 uid, int16 count) { Query q; q.AddQueryAsync(cid, &database, Q_UPDATE, "UPDATE character_items set count=%u WHERE id=%llu AND char_id=%u", count, uid, cid ); } void BrokerManager::DeletePlayerFromDB(int32 cid) { Query q; // delete from broker_sellers q.AddQueryAsync(cid, &database, Q_DELETE, "DELETE FROM broker_sellers WHERE character_id=%u", cid); // delete all their items q.AddQueryAsync(cid, &database, Q_DELETE, "DELETE FROM broker_items WHERE character_id=%u", cid); } bool BrokerManager::IsItemFromInventory(int32 cid, int64 uid) const { std::shared_lock lock(mtx_); // 1) Check active items for that character auto pit = active_items_by_char_.find(cid); if (pit != active_items_by_char_.end()) { auto it = pit->second.find(uid); if (it != pit->second.end()) return it->second.from_inventory; } // 2) Otherwise check inactive items auto iit = inactive_items_by_char_.find(cid); if (iit != inactive_items_by_char_.end()) { auto it = iit->second.find(uid); if (it != iit->second.end()) return it->second.from_inventory; } // 3) Not found → false return false; } void BrokerManager::LockActiveItemsForClient(Client* client) const { int32 cid = client->GetCharacterID(); auto infoOpt = GetSellerInfo(cid); if (!infoOpt) return; const SellerInfo& info = *infoOpt; { auto items = GetActiveForSaleItems(cid); for (auto const& itm : items) { if (!itm.for_sale) { client->GetPlayer()->item_list.SetVaultItemLockUniqueID(client, itm.unique_id, false, true); continue; } LogWrite(PLAYER__ERROR, 5, "Broker", "--LockActiveItemsForClient: %u (selling: %u), allowinv: %u, sellfrominv: %u", itm.unique_id, itm.for_sale, itm.from_inventory, info.sell_from_inventory ); if (!info.sell_from_inventory && itm.from_inventory) { client->GetPlayer()->item_list.SetVaultItemLockUniqueID(client, itm.unique_id, false, true); continue; } client->GetPlayer()->item_list.SetVaultItemLockUniqueID(client, itm.unique_id, true, true); } } } void WorldDatabase::ClearSellerSession(int32 character_id) { Query q; std::ostringstream sql; sql << "UPDATE broker_sellers SET coin_session = 0" << " WHERE character_id = " << character_id; std::string full = sql.str(); q.AddQueryAsync(character_id, &database, Q_INSERT, full.c_str()); } void WorldDatabase::AddToSellerSession(int32 character_id, int64 amount) { Query q; std::ostringstream sql; sql << "UPDATE broker_sellers SET coin_session = coin_session + " << amount << " WHERE character_id = " << character_id; std::string full = sql.str(); q.AddQueryAsync(character_id, &database, Q_INSERT, full.c_str()); } int64 WorldDatabase::GetSellerSession(int32 character_id) { Query query; MYSQL_RES* result = query.RunQuery2( Q_SELECT, "SELECT " "coin_session " "FROM broker_sellers " "WHERE character_id = %d " "LIMIT 1", character_id ); if (result) { MYSQL_ROW row; if ((row = mysql_fetch_row(result))) { return strtoull(row[0], NULL, 0); } } return 0; } std::string BrokerManager::GetShopPurchaseMessage(const std::string& buyer_name, const std::string& item_desc, int16 quantity, int64 coin) { int64 platinum = static_cast(coin / 1000000); // 1) Break totalCopper into denominations int64 rem = coin % 1000000; int64 gold = static_cast(rem / 10000); rem %= 10000; int64 silver = static_cast(rem / 100); int64 copper = static_cast(rem % 100); // 1) Timestamp auto now = std::time(nullptr); std::tm tm; localtime_r(&now, &tm); char timebuf[64]; std::strftime(timebuf, sizeof(timebuf), "%B %d, %Y, %I:%M:%S %p", &tm); // 2) Build the sale line std::ostringstream msg; msg << timebuf << " " << buyer_name << " buys "; if (quantity > 1) msg << quantity << " "; msg << item_desc << " for "; // 3) Denominations std::vector parts; if (platinum > 0) parts.push_back(std::to_string(platinum) + " Platinum"); if (gold > 0) parts.push_back(std::to_string(gold) + " Gold"); if (silver > 0) parts.push_back(std::to_string(silver) + " Silver"); if (copper > 0) parts.push_back(std::to_string(copper) + " Copper"); // If all are zero (unlikely), still print "0 Copper" if (parts.empty()) parts.push_back("0 Copper"); // Join with ", " for (size_t i = 0; i < parts.size(); ++i) { if (i) msg << ", "; msg << parts[i]; } return msg.str(); } void BrokerManager::LogSale(int32 cid, const std::string& buyer_name, const std::string& item_desc, int16 quantity, int64 coin) { std::string msg = GetShopPurchaseMessage(buyer_name, item_desc, quantity, coin); std::string esc = EscapeSQLString(msg.c_str()); std::ostringstream sql; sql << "INSERT INTO broker_seller_log " "(character_id,log_time,message) VALUES (" << cid << ",NOW(),'" << esc << "')"; std::string full = sql.str(); Query q; q.AddQueryAsync(cid, &database, Q_INSERT, full.c_str()); } void BrokerManager::LogSaleMessage(int32 cid, const std::string& log_message) { auto now = std::time(nullptr); std::tm tm; localtime_r(&now, &tm); char timebuf[64]; std::strftime(timebuf, sizeof(timebuf), "%B %d, %Y, %I:%M:%S %p", &tm); std::ostringstream msg; msg << timebuf << " " << log_message; std::string esc = EscapeSQLString(msg.str()); std::ostringstream sql; sql << "INSERT INTO broker_seller_log " "(character_id,log_time,message) VALUES (" << cid << ",NOW(),'" << esc << "')"; std::string full = sql.str(); Query q; q.AddQueryAsync(cid, &database, Q_INSERT, full.c_str()); } std::vector BrokerManager::GetSellerLog(int32 cid) const { std::vector out; Query query; MYSQL_RES* result = query.RunQuery2( Q_SELECT, "SELECT " "log_id, " "DATE_FORMAT(log_time, '%%M %%d, %%Y, %%r') as ts, " "message " "FROM broker_seller_log " "WHERE character_id=%u " "ORDER BY log_time ASC", cid ); if (result) { MYSQL_ROW row; while ((row = mysql_fetch_row(result))) { SellerLog entry; entry.log_id = row[0] ? atoll(row[0]) : 0; entry.timestamp = row[1] ? row[1] : ""; entry.message = row[2] ? row[2] : ""; out.push_back(std::move(entry)); } } return out; }