diff --git a/server/WorldStructs.xml b/server/WorldStructs.xml
index 5f7bfad..27a8e94 100644
--- a/server/WorldStructs.xml
+++ b/server/WorldStructs.xml
@@ -10469,15 +10469,21 @@ to zero and treated like placeholders." />
-
+
-
-
-
+
+
+
-
+
+
+
+
+
+
+
diff --git a/source/WorldServer/Broker/BrokerManager.cpp b/source/WorldServer/Broker/BrokerManager.cpp
new file mode 100644
index 0000000..830ae44
--- /dev/null
+++ b/source/WorldServer/Broker/BrokerManager.cpp
@@ -0,0 +1,954 @@
+/*
+ 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 didDelete = false;
+ SaleItem 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()) {
+ 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) {
+ DeleteItemFromDB(cid, uid);
+ peer_manager.sendPeersRemoveItemSale(cid, uid);
+ }
+ else if (snapshot.count > 0) {
+ 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->GetCharacterID()) {
+ 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__ERROR, 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__ERROR, 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__ERROR, 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__ERROR, 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__ERROR, 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));
+ }
+ }
+ 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;
+}
\ No newline at end of file
diff --git a/source/WorldServer/Broker/BrokerManager.h b/source/WorldServer/Broker/BrokerManager.h
new file mode 100644
index 0000000..d291a1c
--- /dev/null
+++ b/source/WorldServer/Broker/BrokerManager.h
@@ -0,0 +1,182 @@
+/*
+ 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 .
+*/
+
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "../../common/types.h"
+
+// Key = (character_id, unique_id)
+using Key = std::pair;
+
+class Item;
+class Client;
+
+struct SaleItem {
+ int64 unique_id;
+ int32 character_id;
+ int32 house_id;
+ int64 item_id;
+ int64 cost_copper;
+ bool for_sale;
+ sint32 inv_slot_id;
+ int16 slot_id;
+ int16 count;
+ bool from_inventory;
+ string creator;
+};
+
+struct SellerInfo {
+ int32 character_id;
+ std::string seller_name;
+ int64 house_id;
+ bool sale_enabled;
+ bool sell_from_inventory;
+ int64 coin_session;
+ int64 coin_total;
+};
+
+struct SellerLog {
+ int64 log_id; // auto_increment PK
+ std::string timestamp; // e.g. "October 20, 2005, 05:29:33 AM"
+ std::string message; // the human-readable text
+};
+
+class BrokerManager {
+public:
+ BrokerManager();
+
+ //–– Player management
+ void AddSeller(int32 cid,
+ const std::string& name,
+ int32 house_id,
+ bool sale_enabled,
+ bool sell_from_inventory);
+
+ void LoadSeller(int32 cid,
+ const std::string& name,
+ int32 house_id,
+ bool sale_enabled,
+ bool sell_from_inventory,
+ int64 coin_session,
+ int64 coin_total);
+
+ int64 ResetSellerSessionCoins(int32 cid);
+ void AddSellerSessionCoins(int32 cid, uint64 session);
+ void RemoveSeller(int32 character_id, bool peerCacheOnly = false);
+
+ //–– Item management
+ void AddItem(const SaleItem& item, bool peerCacheOnly = false);
+ void LoadItem(const SaleItem& item);
+
+ //–– Activate / deactivate sale flag
+ void SetSaleStatus(int32 cid, int64 uid, bool for_sale);
+ bool IsItemListed(int32 cid, int64 uid);
+ void SetSalePrice(int32 cid, int64 uid, int64 price);
+ int64 GetSalePrice(int32 cid, int64 uid);
+
+ //–– Remove quantity
+ void RemoveItem(int32 cid, int64 uid, int16 quantity = 1);
+
+ //–– Attempt to buy (atomic DB + in-memory + broadcast)
+ bool BuyItem(Client* buyer, int32 seller_cid, int64 uid, int32 quantity);
+ bool IsItemForSale(int32 seller_cid, int64 uid) const;
+
+ //–– Called when a peer notifies that an item was sold/removed (in-memory only)
+ void OnPeerRemoveItem(int32 character_id, int64 unique_id);
+
+ //–– Queries
+ std::vector GetActiveForSaleItems(int32 cid) const;
+ std::optional GetActiveItem(int32 cid, int64 uid) const;
+ bool IsSellingItems(int32 cid, bool vaultOnly = false) const;
+ std::vector GetInactiveItems(int32 cid) const;
+ //–– Global search API (only active_items_)
+ vector
- * 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;
+
+ //–– UI helper: get (unique_id, cost) for active items
+ std::vector> GetUniqueIDsAndCost(int32 cid) const;
+
+ std::optional GetSellerInfo(int32 character_id) const;
+ //–– Lookup seller name
+ std::string GetSellerName(int32 cid) const;
+ bool IsSaleEnabled(int32 cid) const;
+ bool CanSellFromInventory(int32 cid) const;
+ int32 GetHouseID(int32 cid) const;
+
+ bool IsItemFromInventory(int32 cid, int64 uid) const;
+ void LockActiveItemsForClient(Client* client) const;
+
+ std::string GetShopPurchaseMessage(const std::string& buyer_name, const std::string& item_desc, int16 quantity, int64 coin);
+ void LogSale(int32 character_id, const std::string& buyer_name,const std::string& item_desc, int16 quantity, int64 coin);
+ void LogSaleMessage(int32 character_id,const std::string& log_message);
+ std::vector GetSellerLog(int32 character_id) const;
+ static std::string EscapeSQLString(const std::string& s) {
+ std::string out;
+ out.reserve(s.size() * 2);
+ for (char c : s) {
+ if (c == '\'') out += "''";
+ else out += c;
+ }
+ return out;
+ }
+private:
+ mutable std::shared_mutex mtx_;
+ std::unordered_map players_;
+ std::unordered_map<
+ int32,
+ std::unordered_map
+ > active_items_by_char_;
+ std::unordered_map<
+ int32,
+ std::unordered_map
+ > inactive_items_by_char_;
+
+ //–– DB sync (async writes)
+ void SavePlayerToDB(const SellerInfo& p);
+ void SaveItemToDB(const SaleItem& i);
+ void UpdateItemInDB(const SaleItem& i);
+ void DeleteItemFromDB(int32 character_id, int64 unique_id);
+ void DeleteCharacterItemFromDB(int32 cid, int64 uid);
+ void UpdateCharacterItemDB(int32 cid, int64 uid, int16 count);
+ void DeletePlayerFromDB(int32 cid);
+};
\ No newline at end of file
diff --git a/source/WorldServer/Commands/Commands.cpp b/source/WorldServer/Commands/Commands.cpp
index 3cb9d9c..f969dc5 100644
--- a/source/WorldServer/Commands/Commands.cpp
+++ b/source/WorldServer/Commands/Commands.cpp
@@ -1,6 +1,6 @@
/*
EQ2Emulator: Everquest II Server Emulator
- Copyright (C) 2005 - 2025 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net)
+ Copyright (C) 2005 - 2026 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net)
This file is part of EQ2Emulator.
@@ -52,6 +52,7 @@
#include "../Bots/Bot.h"
#include "../Web/PeerManager.h"
#include "../../common/GlobalHeaders.h"
+#include "../Broker/BrokerManager.h"
extern WorldDatabase database;
extern MasterSpellList master_spell_list;
@@ -76,6 +77,7 @@ extern MasterAAList master_aa_list;
extern MasterRaceTypeList race_types_list;
extern Classes classes;
extern PeerManager peer_manager;
+extern BrokerManager broker;
//devn00b: Fix for linux builds since we dont use stricmp we use strcasecmp
#if defined(__GNUC__)
@@ -2193,14 +2195,35 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
LogWrite(COMMAND__ERROR, 0, "Command", "/info appearance: Unknown Index: %u", item_index);
}
else if(strcmp(sep->arg[0], "item") == 0 || strcmp(sep->arg[0], "merchant") == 0 || strcmp(sep->arg[0], "store") == 0 || strcmp(sep->arg[0], "buyback") == 0 || strcmp(sep->arg[0], "consignment") == 0){
- int32 item_id = atoul(sep->arg[1]);
- Item* item = master_item_list.GetItem(item_id);
- if(item){
+ int64 item_id = strtoull(sep->arg[1], NULL, 0);
+ Item* item = nullptr;
+
+ if (strcmp(sep->arg[0], "store") == 0)
+ item = client->GetPlayer()->item_list.GetVaultItemFromUniqueID(item_id, true);
+ else
+ item = master_item_list.GetItem(item_id);
+
+ if(!item && client->GetMerchantTransactionID() && strcmp(sep->arg[0], "merchant") == 0) {
+ Spawn* merchant = client->GetPlayer()->GetZone()->GetSpawnByID(client->GetMerchantTransactionID());
+ if(merchant && merchant->GetHouseCharacterID() && merchant->GetPickupUniqueItemID()) {
+ if(auto itemInfo = broker.GetActiveItem(merchant->GetHouseCharacterID(), item_id)) {
+ item = master_item_list.GetItem(itemInfo->item_id);
+ if(item) {
+ EQ2Packet* app = item->serialize(client->GetVersion(), true, client->GetPlayer());
+ client->QueuePacket(app);
+ }
+ }
+ }
+ }
+ else if(!item && strcmp(sep->arg[0], "consignment") == 0) {
+ client->SendSellerItemByItemUniqueId(item_id);
+ }
+ else if(item){
EQ2Packet* app = item->serialize(client->GetVersion(), true, client->GetPlayer());
client->QueuePacket(app);
}
else
- LogWrite(COMMAND__ERROR, 0, "Command", "/info item|merchant|store|buyback|consignment: Unknown Item ID: %u", item_id);
+ LogWrite(COMMAND__ERROR, 0, "Command", "/info item|merchant|store|buyback|consignment: Unknown Item ID: %u (full arguments %s)", item_id, sep->argplus[0]);
}
else if (strcmp(sep->arg[0], "spell") == 0) {
sint32 spell_id = atol(sep->arg[1]);
@@ -3900,8 +3923,12 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
if (!spawn || !client->HasOwnerOrEditAccess() || !spawn->GetPickupItemID())
break;
-
- if(client->AddItem(spawn->GetPickupItemID(), 1)) {
+ Item* tmpItem = client->GetPlayer()->item_list.GetVaultItemFromUniqueID(spawn->GetPickupUniqueItemID());
+ if((tmpItem && tmpItem->generic_info.item_type == ITEM_TYPE_HOUSE_CONTAINER) || client->AddItem(spawn->GetPickupItemID(), 1)) {
+ if ( tmpItem && tmpItem->generic_info.item_type == ITEM_TYPE_HOUSE_CONTAINER ) {
+ tmpItem->TryUnlockItem(LockReason::LockReason_House);
+ client->QueuePacket(client->GetPlayer()->SendInventoryUpdate(client->GetVersion()));
+ }
Query query;
query.RunQuery2(Q_INSERT, "delete from spawn_instance_data where spawn_id = %u and spawn_location_id = %u and pickup_item_id = %u", spawn->GetDatabaseID(), spawn->GetSpawnLocationID(), spawn->GetPickupItemID());
@@ -4030,12 +4057,16 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
case COMMAND_PLACE_HOUSE_ITEM: {
if (sep && sep->IsNumber(0))
{
- int32 uniqueid = atoi(sep->arg[0]);
+ int64 uniqueid = strtoull(sep->arg[0], NULL, 0);
- Item* item = client->GetPlayer()->item_list.GetItemFromUniqueID(uniqueid);
- //Item* item = player->GetEquipmentList()->GetItem(slot);
+ Item* item = client->GetPlayer()->item_list.GetVaultItemFromUniqueID(uniqueid);
+
if (item && (item->IsHouseItem() || item->IsHouseContainer()))
{
+ if(item->IsHouseContainer() && item->details.inv_slot_id != InventorySlotType::HOUSE_VAULT) { // must be in base slot of vault in house for house containers
+ client->SimpleMessage(CHANNEL_COLOR_RED, "Must be in vault to place this item.");
+ break;
+ }
if (!client->HasOwnerOrEditAccess())
{
client->SimpleMessage(CHANNEL_COLOR_RED, "This is not your home!");
@@ -4062,7 +4093,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
Object* obj = new Object();
Spawn* spawn = (Spawn*)obj;
memset(&spawn->appearance, 0, sizeof(spawn->appearance));
- strcpy(spawn->appearance.name, "temp");
+ strcpy(spawn->appearance.name, item->name.c_str());
spawn->SetX(client->GetPlayer()->GetX());
spawn->SetY(client->GetPlayer()->GetY());
spawn->SetZ(client->GetPlayer()->GetZ());
@@ -4403,20 +4434,49 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
}
case COMMAND_BUY_FROM_BROKER:{
if(sep && sep->arg[1][0] && sep->IsNumber(0) && sep->IsNumber(1)){
- int32 item_id = atoul(sep->arg[0]);
+ int64 item_id = strtoull(sep->arg[0], NULL, 0);
int16 quantity = atoul(sep->arg[1]);
- Item* item = master_item_list.GetItem(item_id);
- if(item && item->generic_info.max_charges > 1)
- quantity = item->generic_info.max_charges;
- client->AddItem(item_id, quantity, AddItemType::BUY_FROM_BROKER);
+ if(client->IsGMStoreSearch()) {
+ Item* item = master_item_list.GetItem(item_id);
+ if(item && item->generic_info.max_charges > 1)
+ quantity = item->generic_info.max_charges;
+ client->AddItem(item_id, quantity, AddItemType::BUY_FROM_BROKER);
+ }
+ else {
+ client->BuySellerItemByItemUniqueId(item_id, quantity);
+ LogWrite(COMMAND__ERROR, 0, "Command", "BUY_FROM_BROKER. Item ID %u, Quantity %u, full args %s.", item_id, quantity, sep->argplus[0]);
+ }
}
break;
}
case COMMAND_SEARCH_STORES_PAGE:{
+ LogWrite(COMMAND__ERROR, 0, "Command", "SearchStores: %s", sep && sep->arg[0] ? sep->argplus[0] : "");
if(sep && sep->arg[0][0] && sep->IsNumber(0)){
int32 page = atoul(sep->arg[0]);
client->SearchStore(page);
}
+ else {
+ client->SetGMStoreSearch(false);
+ PacketStruct* packet = configReader.getStruct("WS_StartBroker", client->GetVersion());
+ if (packet) {
+ packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(client->GetPlayer()));
+ //packet->setDataByName("unknown", 1);
+ packet->setDataByName("unknown2", 5, 0);
+ packet->setDataByName("unknown2", 20, 1);
+ packet->setDataByName("unknown2", 58, 3);
+ packet->setDataByName("unknown2", 40, 4);
+ client->QueuePacket(packet->serialize());
+ if(client->GetVersion() > 561) {
+ PacketStruct* packet2 = configReader.getStruct("WS_BrokerBags", client->GetVersion());
+ if (packet2) {
+ packet2->setDataByName("char_id", client->GetCharacterID());
+ client->QueuePacket(packet2->serialize()); //send this for now, needed to properly clear data
+ safe_delete(packet2);
+ }
+ safe_delete(packet);
+ }
+ }
+ }
break;
}
case COMMAND_SEARCH_STORES:{
@@ -4424,11 +4484,11 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
const char* values = sep->argplus[0];
if(values){
LogWrite(ITEM__WARNING, 0, "Item", "SearchStores: %s", values);
-
map str_values = TranslateBrokerRequest(values);
vector
- * items = master_item_list.GetItems(str_values, client);
if(items){
- client->SetItemSearch(items);
+ client->SetItemSearch(items, str_values);
+ client->SetSearchPage(0);
client->SearchStore(0);
}
}
@@ -5424,6 +5484,13 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
}
case COMMAND_ITEMSEARCH:
case COMMAND_FROMBROKER:{
+
+ if(command->handler == COMMAND_ITEMSEARCH) {
+ client->SetGMStoreSearch(true);
+ }
+ else {
+ client->SetGMStoreSearch(false);
+ }
PacketStruct* packet = configReader.getStruct("WS_StartBroker", client->GetVersion());
if (packet) {
packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(client->GetPlayer()));
@@ -5433,13 +5500,15 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
packet->setDataByName("unknown2", 58, 3);
packet->setDataByName("unknown2", 40, 4);
client->QueuePacket(packet->serialize());
- PacketStruct* packet2 = configReader.getStruct("WS_BrokerBags", client->GetVersion());
- if (packet2) {
- packet2->setDataByName("char_id", client->GetCharacterID());
- client->QueuePacket(packet2->serialize()); //send this for now, needed to properly clear data
- safe_delete(packet2);
+ if(client->GetVersion() > 561) {
+ PacketStruct* packet2 = configReader.getStruct("WS_BrokerBags", client->GetVersion());
+ if (packet2) {
+ packet2->setDataByName("char_id", client->GetCharacterID());
+ client->QueuePacket(packet2->serialize()); //send this for now, needed to properly clear data
+ safe_delete(packet2);
+ }
+ safe_delete(packet);
}
- safe_delete(packet);
}
break;
}
@@ -5838,6 +5907,15 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
case COMMAND_SPLIT: { Command_Split(client, sep); break; }
case COMMAND_RAIDSAY: { Command_RaidSay(client, sep); break; }
case COMMAND_RELOAD_ZONEINFO: { Command_ReloadZoneInfo(client, sep); break; }
+ case COMMAND_SLE: { Command_SetLocationEntry(client, sep); break; }
+ case COMMAND_STORE_LIST_ITEM: { Command_StoreListItem(client, sep); break; }
+ case COMMAND_STORE_SET_PRICE: { Command_StoreSetPrice(client, sep); break; }
+ case COMMAND_STORE_SET_PRICE_LOCAL: { Command_StoreSetPriceLocal(client, sep); break; }
+ case COMMAND_STORE_START_SELLING: { Command_StoreStartSelling(client, sep); break; }
+ case COMMAND_STORE_STOP_SELLING: { Command_StoreStopSelling(client, sep); break; }
+ case COMMAND_STORE_UNLIST_ITEM: { Command_StoreUnlistItem(client, sep); break; }
+ case COMMAND_CLOSE_STORE_KEEP_SELLING: { Command_CloseStoreKeepSelling(client, sep); break; }
+ case COMMAND_CANCEL_STORE: { Command_CancelStore(client, sep); break; }
default:
{
LogWrite(COMMAND__WARNING, 0, "Command", "Unhandled command: %s", command->command.data.c_str());
@@ -7024,7 +7102,7 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
if(item)
{
- if(item->details.item_locked) {
+ if(item->IsItemLocked()) {
client->SimpleMessage(CHANNEL_COLOR_RED, "You cannot destroy the item in use.");
return;
}
@@ -7036,11 +7114,15 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
client->SimpleMessage(CHANNEL_COLOR_RED, "You can't destroy this item.");
return;
}
+ if(client->GetPlayer()->item_list.IsItemInSlotType(item, InventorySlotType::HOUSE_VAULT) || client->GetPlayer()->item_list.IsItemInSlotType(item, InventorySlotType::BASE_INVENTORY)) {
+ broker.RemoveItem(client->GetPlayer()->GetCharacterID(), item->details.unique_id, item->details.count);
+ }
if(item->GetItemScript() && lua_interface)
lua_interface->RunItemScript(item->GetItemScript(), "destroyed", item, client->GetPlayer());
//reobtain item make sure it wasn't removed
item = player->item_list.GetItemFromIndex(index);
+
int32 bag_id = 0;
if(item){
bag_id = item->details.inv_slot_id;
@@ -7049,6 +7131,8 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
client->GetPlayer()->item_list.DestroyItem(index);
client->GetPlayer()->UpdateInventory(bag_id);
client->GetPlayer()->CalculateApplyWeight();
+
+ client->OpenShopWindow(nullptr); // update the window if it is open
}
}
else if(sep->arg[4][0] && strncasecmp("move", sep->arg[0], 4) == 0 && sep->IsNumber(1) && sep->IsNumber(2) && sep->IsNumber(3) && sep->IsNumber(4))
@@ -7058,33 +7142,34 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
sint32 bag_id = atol(sep->arg[3]);
int8 charges = atoi(sep->arg[4]);
Item* item = client->GetPlayer()->item_list.GetItemFromIndex(from_index);
-
+ int64 unique_id = 0;
+ int16 count = 0;
if(!item) {
client->SimpleMessage(CHANNEL_COLOR_RED, "You have no item.");
return;
}
-
- if(to_slot == item->details.slot_id && (bag_id < 0 || bag_id == item->details.inv_slot_id)) {
+ unique_id = item->details.unique_id;
+ count = item->details.count;
+ if(to_slot == item->details.slot_id && (bag_id == item->details.inv_slot_id)) {
return;
}
- if(item->details.item_locked)
+ if(item->IsItemLocked())
{
client->SimpleMessage(CHANNEL_COLOR_RED, "You cannot move the item in use.");
return;
}
- if(bag_id == -4 && !client->GetPlayer()->item_list.SharedBankAddAllowed(item))
+ if(bag_id == InventorySlotType::SHARED_BANK && !client->GetPlayer()->item_list.SharedBankAddAllowed(item))
{
client->SimpleMessage(CHANNEL_COLOR_RED, "That item (or an item inside) cannot be shared.");
return;
}
-
sint32 old_inventory_id = 0;
if(item)
old_inventory_id = item->details.inv_slot_id;
//autobank
- if (bag_id == -3 && to_slot == -1)
+ if (bag_id == InventorySlotType::BANK && to_slot == -1)
{
if (player->HasFreeBankSlot())
to_slot = player->FindFreeBankSlot();
@@ -7129,6 +7214,16 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
}
client->GetPlayer()->CalculateApplyWeight();
+ if(item) {
+ if(!client->GetPlayer()->item_list.IsItemInSlotType(item, InventorySlotType::HOUSE_VAULT) &&
+ !client->GetPlayer()->item_list.IsItemInSlotType(item, InventorySlotType::BASE_INVENTORY)) {
+ broker.RemoveItem(client->GetPlayer()->GetCharacterID(), unique_id, charges);
+ }
+ }
+ else {
+ broker.RemoveItem(client->GetPlayer()->GetCharacterID(), unique_id, count);
+ }
+ client->OpenShopWindow(nullptr); // update the window if it is open
}
else if(sep->arg[1][0] && strncasecmp("equip", sep->arg[0], 5) == 0 && sep->IsNumber(1))
{
@@ -7159,6 +7254,7 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
client->QueuePacket(characterSheetPackets);
client->GetPlayer()->CalculateBonuses();
+ client->OpenShopWindow(nullptr); // update the window if it is open
}
else if (sep->arg[1][0] && strncasecmp("unpack", sep->arg[0], 6) == 0 && sep->IsNumber(1))
{
@@ -7168,7 +7264,7 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
int16 index = atoi(sep->arg[1]);
Item* item = client->GetPlayer()->item_list.GetItemFromIndex(index);
if (item) {
- if(item->details.item_locked)
+ if(item->IsItemLocked())
{
client->SimpleMessage(CHANNEL_COLOR_RED, "You cannot unpack the item in use.");
return;
@@ -7186,6 +7282,7 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
}
client->RemoveItem(item, 1);
+ client->OpenShopWindow(nullptr); // update the window if it is open
}
}
@@ -7224,6 +7321,7 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
client->UnequipItem(index, bag_id, to_slot, appearance_equip);
client->GetPlayer()->CalculateBonuses();
+ client->OpenShopWindow(nullptr); // update the window if it is open
}
else if(sep->arg[2][0] && strncasecmp("swap_equip", sep->arg[0], 10) == 0 && sep->IsNumber(1) && sep->IsNumber(2))
{
@@ -7276,9 +7374,10 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
// Send the inventory update packet
client->QueuePacket(player->item_list.serialize(player, client->GetVersion()));
+ client->OpenShopWindow(nullptr); // update the window if it is open
return;
}
- else if (bag_id == -3 && to_slot == -1) {
+ else if (bag_id == InventorySlotType::BANK && to_slot == -1) {
// Auto Bank
if (!player->item_list.GetFirstFreeBankSlot(&bag_id, &to_slot)) {
client->SimpleMessage(CHANNEL_STATUS, "You do not have any free bank slots.");
@@ -7291,14 +7390,15 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
player->item_list.RemoveOverflowItem(item);
}
client->QueuePacket(player->item_list.serialize(player, client->GetVersion()));
+ client->OpenShopWindow(nullptr); // update the window if it is open
}
- else if (bag_id == -4) {
+ else if (bag_id == InventorySlotType::SHARED_BANK) {
// Shared Bank
if (!player->item_list.SharedBankAddAllowed(item)) {
client->SimpleMessage(CHANNEL_STATUS, "That item (or an item inside) cannot be shared.");
return;
}
- Item* tmp_item = player->item_list.GetItem(-4, to_slot);
+ Item* tmp_item = player->item_list.GetItem(bag_id, to_slot);
if (tmp_item) {
client->SimpleMessage(CHANNEL_STATUS, "You can not place an overflow item into an occupied slot");
return;
@@ -7311,6 +7411,7 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
player->item_list.RemoveOverflowItem(item);
}
client->QueuePacket(player->item_list.serialize(player, client->GetVersion()));
+ client->OpenShopWindow(nullptr); // update the window if it is open
return;
}
}
@@ -7330,6 +7431,7 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
player->item_list.RemoveOverflowItem(item);
}
client->QueuePacket(player->item_list.serialize(player, client->GetVersion()));
+ client->OpenShopWindow(nullptr); // update the window if it is open
return;
}
}
@@ -9976,14 +10078,18 @@ void Commands::Command_TradeAddItem(Client* client, Seperator* sep)
int32 index = atoi(sep->arg[0]);
item = client->GetPlayer()->GetPlayerItemList()->GetItemFromIndex(index);
if (item) {
- if(item->details.item_locked || item->details.equip_slot_id) {
+ if(item->IsItemLocked() || item->details.equip_slot_id) {
client->SimpleMessage(CHANNEL_COLOR_RED, "You cannot trade an item currently in use.");
return;
}
- else if(item->details.inv_slot_id == -3 || item->details.inv_slot_id == -4) {
+ else if(item->details.inv_slot_id == InventorySlotType::BANK || item->details.inv_slot_id == InventorySlotType::SHARED_BANK) {
client->SimpleMessage(CHANNEL_COLOR_RED, "You cannot trade an item in the bank.");
return;
}
+ else if(client->GetPlayer()->item_list.IsItemInSlotType(item, InventorySlotType::HOUSE_VAULT)) {
+ client->SimpleMessage(CHANNEL_COLOR_RED, "You cannot trade an item in the house vault.");
+ return;
+ }
int8 result = client->GetPlayer()->trade->AddItemToTrade(client->GetPlayer(), item, atoi(sep->arg[2]), atoi(sep->arg[1]));
if (result == 1)
@@ -11041,6 +11147,23 @@ void Commands::Command_Test(Client* client, EQ2_16BitString* command_parms) {
else if(atoi(sep->arg[0]) == 38) {
client->GetPlayer()->GetZone()->SendFlightPathsPackets(client);
}
+ else if(atoi(sep->arg[0]) == 39) {
+ client->OpenShopWindow(nullptr, true);
+ }
+ else if(atoi(sep->arg[0]) == 40) {
+
+ PacketStruct* packet2 = configReader.getStruct("WS_HouseStoreLog", client->GetVersion());
+ if (packet2) {
+ packet2->setDataByName("data", sep->arg[1]);
+ packet2->setDataByName("coin_gain_session", atoul(sep->arg[2]));
+ packet2->setDataByName("coin_gain_alltime", atoul(sep->arg[3]));
+ packet2->setDataByName("sales_log_open", atoi(sep->arg[4]));
+ EQ2Packet* outapp = packet2->serialize();
+ DumpPacket(outapp);
+ client->QueuePacket(outapp);
+ safe_delete(packet2);
+ }
+ }
}
else {
PacketStruct* packet2 = configReader.getStruct("WS_ExaminePartialSpellInfo", client->GetVersion());
@@ -11503,37 +11626,8 @@ void Commands::Command_Wind(Client* client, Seperator* sep) {
void Commands::Command_SendMerchantWindow(Client* client, Seperator* sep, bool sell) {
Spawn* spawn = client->GetPlayer()->GetTarget();
- if(client->GetVersion() < 561) {
- sell = false; // doesn't support in the same way as AoM just open the normal buy/sell window
- }
- if(spawn) {
- client->SetMerchantTransaction(spawn);
- if (spawn->GetMerchantID() > 0 && spawn->IsClientInMerchantLevelRange(client)){
- client->SendHailCommand(spawn);
- //MerchantFactionMultiplier* multiplier = world.GetMerchantMultiplier(spawn->GetMerchantID());
- //if(!multiplier || (multiplier && client->GetPlayer()->GetFactions()->GetFactionValue(multiplier->faction_id) >= multiplier->faction_min)){
- client->SendBuyMerchantList(sell);
- if(!(spawn->GetMerchantType() & MERCHANT_TYPE_NO_BUY))
- client->SendSellMerchantList(sell);
- if(!(spawn->GetMerchantType() & MERCHANT_TYPE_NO_BUY_BACK))
- client->SendBuyBackList(sell);
-
- if(client->GetVersion() > 561) {
- PacketStruct* packet = configReader.getStruct("WS_UpdateMerchant", client->GetVersion());
- if (packet) {
- packet->setDataByName("spawn_id", 0xFFFFFFFF);
- packet->setDataByName("type", 16);
- EQ2Packet* outapp = packet->serialize();
- if (outapp)
- client->QueuePacket(outapp);
- safe_delete(packet);
- }
- }
- }
- if (spawn->GetMerchantType() & MERCHANT_TYPE_REPAIR)
- client->SendRepairList();
- }
- // client->SimpleMessage(CHANNEL_COLOR_RED, "Your faction is too low to use this merchant.");
+ if(spawn)
+ client->SendMerchantWindow(spawn, sell);
}
@@ -12946,4 +13040,301 @@ void Commands::Command_RaidSay(Client* client, Seperator* sep) {
*/
void Commands::Command_ReloadZoneInfo(Client* client, Seperator* sep) {
world.ClearZoneInfoCache();
+}
+
+// somewhere in your command‐handler:
+void Commands::Command_SetLocationEntry(Client* client, Seperator* sep) {
+ if(client->GetCurrentZone()->GetInstanceType() == PERSONAL_HOUSE_INSTANCE) {
+ client->Message(CHANNEL_COLOR_YELLOW, "Use in a non house zone.");
+ return;
+ }
+
+ if (!sep->IsSet(1) || (sep->IsNumber(0))) {
+ client->Message(CHANNEL_COLOR_YELLOW, "Usage: /sle ");
+ return;
+ }
+
+ Spawn* target = client->GetPlayer()->GetTarget();
+
+ if(!target) {
+ client->Message(CHANNEL_COLOR_RED, "Target missing for set location entry, /sle ");
+ return;
+ }
+
+ // GetSpawnEntryID for spawn_location_entry to set the spawnpercentage
+ int32 spawnEntryID = target->GetSpawnEntryID();
+ int32 spawnPlacementID = target->GetSpawnLocationPlacementID();
+ int32 spawnLocationID = target->GetSpawnLocationID();
+ int32 dbID = target->GetDatabaseID();
+ if (spawnPlacementID == 0 || spawnLocationID == 0 || spawnEntryID == 0 || dbID == 0) {
+ client->Message(CHANNEL_COLOR_RED, "Error: no valid spawn entry selected.");
+ return;
+ }
+
+ // 2) Whitelist the allowed columns
+ static const std::unordered_set allowed = {
+ "x",
+ "y",
+ "z",
+ "x_offset",
+ "y_offset",
+ "z_offset",
+ "heading",
+ "pitch",
+ "roll",
+ "respawn",
+ "respawn_offset_low",
+ "respawn_offset_high",
+ "duplicated_spawn",
+ "expire_timer",
+ "expire_offset",
+ "grid_id",
+ "processed",
+ "instance_id",
+ "lvl_override",
+ "hp_override",
+ "mp_override",
+ "str_override",
+ "sta_override",
+ "wis_override",
+ "int_override",
+ "agi_override",
+ "heat_override",
+ "cold_override",
+ "magic_override",
+ "mental_override",
+ "divine_override",
+ "disease_override",
+ "poison_override",
+ "difficulty_override",
+ "spawnpercentage",
+ "condition"
+ };
+
+
+ const std::string& field = std::string(sep->arg[0]);
+ if (!allowed.count(field)) {
+ client->Message(CHANNEL_COLOR_RED, "Error: column '%s' is not modifiable.", field.c_str());
+ return;
+ }
+
+ const std::string& val = std::string(sep->arg[1]);
+
+ Query query;
+ if(field == "spawnpercentage" || field == "condition") {
+ query.AddQueryAsync(0,
+ &database,
+ Q_UPDATE,
+ // we embed the whitelisted field name directly in the format
+ "UPDATE spawn_location_entry "
+ "SET %s=%s "
+ "WHERE id=%u and spawn_location_id=%u and spawn_id=%u ",
+ field.c_str(),
+ val.c_str(),
+ spawnEntryID,
+ spawnLocationID,
+ dbID
+ );
+ }
+ else {
+ query.AddQueryAsync(0,
+ &database,
+ Q_UPDATE,
+ // we embed the whitelisted field name directly in the format
+ "UPDATE spawn_location_placement "
+ "SET %s=%s "
+ "WHERE id=%u and spawn_location_id=%u ",
+ field.c_str(),
+ val.c_str(),
+ spawnPlacementID,
+ spawnLocationID
+ );
+ }
+
+ client->Message(CHANNEL_COLOR_YELLOW, "Modified %s to %s for row entry id %u, spawn placement id %u, related to location id %u and spawn database id %u.",
+ field.c_str(), val.c_str(), spawnEntryID, spawnPlacementID, spawnLocationID, dbID);
+}
+
+
+
+void Commands::Command_StoreListItem(Client* client, Seperator* sep) {
+ if(!client->GetShopWindowStatus()) {
+ client->Message(CHANNEL_COLOR_RED, "Shop not available.");
+ return;
+ }
+
+ if(sep && sep->arg[0]) {
+ auto info = broker.GetSellerInfo(client->GetPlayer()->GetCharacterID());
+ if(!info) {
+ client->Message(CHANNEL_COLOR_RED, "Player %u is not in the broker database.", client->GetPlayer()->GetCharacterID());
+ }
+ else if(sep->IsNumber(0)) {
+ int64 unique_id = atoll(sep->arg[0]);
+ if(client->GetPlayer()->item_list.CanStoreSellItem(unique_id, true)) {
+ Item* item = client->GetPlayer()->item_list.GetVaultItemFromUniqueID(unique_id, true);
+ if(item) {
+ bool isInv = !client->GetPlayer()->item_list.IsItemInSlotType(item, InventorySlotType::HOUSE_VAULT);
+ int64 cost = broker.GetSalePrice(client->GetPlayer()->GetCharacterID(), item->details.unique_id);
+ client->AddItemSale(item->details.unique_id, item->details.item_id, cost, item->details.inv_slot_id, item->details.slot_id, item->details.count, isInv, true, item->creator);
+ }
+ else
+ client->Message(CHANNEL_COLOR_RED, "Broker issue, cannot find item %u.", unique_id);
+
+ client->SetItemSaleStatus(unique_id, true);
+ client->GetPlayer()->item_list.SetVaultItemLockUniqueID(client, unique_id, true, false);
+ client->SetSellerStatus();
+ }
+ }
+ else {
+ client->Message(CHANNEL_COLOR_RED, "Invalid arguments for /store_list_item unique_id.");
+
+ }
+ }
+}
+void Commands::Command_StoreSetPrice(Client* client, Seperator* sep) {
+ if(!client->GetShopWindowStatus()) {
+ client->Message(CHANNEL_COLOR_RED, "Shop not available.");
+ return;
+ }
+
+ if(sep && sep->arg[0]) {
+ auto info = broker.GetSellerInfo(client->GetPlayer()->GetCharacterID());
+ int64 unique_id = atoll(sep->arg[0]);
+ if(!info) {
+ client->Message(CHANNEL_COLOR_RED, "Player %u is not in the broker database.", client->GetPlayer()->GetCharacterID());
+ }
+ else if(info->sell_from_inventory && broker.IsItemFromInventory(client->GetPlayer()->GetCharacterID(), unique_id)) {
+ client->Message(CHANNEL_COLOR_RED, "You cannot change the price while selling.");
+ }
+ else if(info->sell_from_inventory && !broker.IsItemFromInventory(client->GetPlayer()->GetCharacterID(), unique_id) && broker.IsItemForSale(client->GetPlayer()->GetCharacterID(), unique_id)) {
+ client->Message(CHANNEL_COLOR_RED, "You cannot change the price while selling.");
+ }
+ else if(sep->IsNumber(1) && sep->IsNumber(2) && sep->IsNumber(3) && sep->IsNumber(4)) {
+ int32 plat = atoul(sep->arg[1]);
+ int32 gold = atoul(sep->arg[2]);
+ int32 silver = atoul(sep->arg[3]);
+ int32 copper = atoul(sep->arg[4]);
+ int64 price = plat * 1000000 + gold * 10000 + silver * 100 + copper;
+
+ LogWrite(PLAYER__INFO, 5, "Broker",
+ "--StoreSetPrice: %u (%u), cost=%u",
+ client->GetPlayer()->GetCharacterID(), unique_id, price
+ );
+ client->SetItemSaleCost(unique_id, plat, gold, silver, copper);
+ }
+ else {
+ client->Message(CHANNEL_COLOR_RED, "Invalid arguments for /store_set_price unique_id platinum gold silver copper.");
+ }
+ }
+}
+void Commands::Command_StoreSetPriceLocal(Client* client, Seperator* sep) {
+ if(!client->GetShopWindowStatus()) {
+ client->Message(CHANNEL_COLOR_RED, "Shop not available.");
+ return;
+ }
+
+ if(sep && sep->arg[0]) {
+ auto info = broker.GetSellerInfo(client->GetPlayer()->GetCharacterID());
+ if(!info) {
+ client->Message(CHANNEL_COLOR_RED, "Player %u is not in the broker database.", client->GetPlayer()->GetCharacterID());
+ }
+ else if(info->sell_from_inventory) {
+ client->Message(CHANNEL_COLOR_RED, "You cannot change the price while selling.");
+ }
+ else if(sep->IsNumber(0) && sep->IsNumber(1) && sep->IsNumber(2) && sep->IsNumber(3) && sep->IsNumber(4)) {
+ int64 unique_id = atoll(sep->arg[0]);
+ int32 plat = atoul(sep->arg[1]);
+ int32 gold = atoul(sep->arg[2]);
+ int32 silver = atoul(sep->arg[3]);
+ int32 copper = atoul(sep->arg[4]);
+ int64 price = plat * 1000000 + gold * 10000 + silver * 100 + copper;
+ LogWrite(PLAYER__INFO, 5, "Broker",
+ "--StoreSetLocalPrice: %u (%u), cost=%u",
+ client->GetPlayer()->GetCharacterID(), unique_id, price
+ );
+ client->SetItemSaleCost(unique_id, plat, gold, silver, copper);
+ }
+ else {
+ client->Message(CHANNEL_COLOR_RED, "Invalid arguments for /store_set_price_local unique_id platinum gold silver copper.");
+ }
+ }
+
+}
+void Commands::Command_StoreStartSelling(Client* client, Seperator* sep) {
+ if(!client->GetShopWindowStatus()) {
+ client->Message(CHANNEL_COLOR_RED, "Shop not available.");
+ return;
+ }
+
+ broker.AddSeller(client->GetPlayer()->GetCharacterID(), std::string(client->GetPlayer()->GetName()), client->GetPlayer()->GetPlayerInfo()->GetHouseZoneID(), true, true);
+ client->OpenShopWindow(nullptr);
+ broker.LockActiveItemsForClient(client);
+}
+
+void Commands::Command_StoreStopSelling(Client* client, Seperator* sep) {
+ if(!client->GetShopWindowStatus()) {
+ client->Message(CHANNEL_COLOR_RED, "Shop not available.");
+ return;
+ }
+
+ broker.AddSeller(client->GetPlayer()->GetCharacterID(), std::string(client->GetPlayer()->GetName()), client->GetPlayer()->GetPlayerInfo()->GetHouseZoneID(), true, false);
+ client->OpenShopWindow(nullptr);
+ broker.LockActiveItemsForClient(client);
+}
+
+void Commands::Command_StoreUnlistItem(Client* client, Seperator* sep) {
+ if(!client->GetShopWindowStatus()) {
+ client->Message(CHANNEL_COLOR_RED, "Shop not available.");
+ return;
+ }
+ if(sep && sep->arg[0]) {
+ auto info = broker.GetSellerInfo(client->GetPlayer()->GetCharacterID());
+ if(!info) {
+ client->Message(CHANNEL_COLOR_RED, "Player %u is not in the broker database.", client->GetPlayer()->GetCharacterID());
+ }
+ else if(sep->IsNumber(0)) {
+ int64 unique_id = atoll(sep->arg[0]);
+ client->SetItemSaleStatus(unique_id, false);
+ client->SetSellerStatus();
+ client->GetPlayer()->item_list.SetVaultItemLockUniqueID(client, unique_id, false, false);
+ }
+ else {
+ client->Message(CHANNEL_COLOR_RED, "Invalid arguments for /store_unlist_item unique_id.");
+
+ }
+ }
+}
+
+void Commands::Command_CloseStoreKeepSelling(Client* client, Seperator* sep) {
+ if(!client->GetShopWindowStatus()) {
+ client->Message(CHANNEL_COLOR_RED, "Shop not available.");
+ return;
+ }
+ auto info = broker.GetSellerInfo(client->GetPlayer()->GetCharacterID());
+ client->SetShopWindowStatus(false);
+ if(!info) {
+ client->Message(CHANNEL_COLOR_RED, "Player %u is not in the broker database.", client->GetPlayer()->GetCharacterID());
+ }
+ else {
+ bool itemsSelling = broker.IsSellingItems(client->GetPlayer()->GetCharacterID());
+ broker.AddSeller(client->GetPlayer()->GetCharacterID(), std::string(client->GetPlayer()->GetName()), client->GetPlayer()->GetPlayerInfo()->GetHouseZoneID(), itemsSelling, true);
+ }
+ broker.LockActiveItemsForClient(client);
+}
+
+void Commands::Command_CancelStore(Client* client, Seperator* sep) {
+ if(!client->GetShopWindowStatus()) {
+ client->Message(CHANNEL_COLOR_RED, "Shop not available.");
+ return;
+ }
+ auto info = broker.GetSellerInfo(client->GetPlayer()->GetCharacterID());
+ client->SetShopWindowStatus(false);
+ if(!info) {
+ client->Message(CHANNEL_COLOR_RED, "Player %u is not in the broker database.", client->GetPlayer()->GetCharacterID());
+ }
+ else {
+ bool itemsSelling = broker.IsSellingItems(client->GetPlayer()->GetCharacterID(), true);
+ broker.AddSeller(client->GetPlayer()->GetCharacterID(), std::string(client->GetPlayer()->GetName()), client->GetPlayer()->GetPlayerInfo()->GetHouseZoneID(), itemsSelling, false);
+ }
+ broker.LockActiveItemsForClient(client);
}
\ No newline at end of file
diff --git a/source/WorldServer/Commands/Commands.h b/source/WorldServer/Commands/Commands.h
index 3630616..05bcec0 100644
--- a/source/WorldServer/Commands/Commands.h
+++ b/source/WorldServer/Commands/Commands.h
@@ -1,6 +1,6 @@
/*
EQ2Emulator: Everquest II Server Emulator
- Copyright (C) 2005 - 2025 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net)
+ Copyright (C) 2005 - 2026 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net)
This file is part of EQ2Emulator.
@@ -467,7 +467,16 @@ public:
void Command_RaidSay(Client* client, Seperator* sep);
void Command_ReloadZoneInfo(Client* client, Seperator* sep);
-
+ void Command_SetLocationEntry(Client* client, Seperator* sep);
+ void Command_StoreListItem(Client* client, Seperator* sep);
+ void Command_StoreSetPrice(Client* client, Seperator* sep);
+ void Command_StoreSetPriceLocal(Client* client, Seperator* sep);
+ void Command_StoreStartSelling(Client* client, Seperator* sep);
+ void Command_StoreStopSelling(Client* client, Seperator* sep);
+ void Command_StoreUnlistItem(Client* client, Seperator* sep);
+ void Command_CloseStoreKeepSelling(Client* client, Seperator* sep);
+ void Command_CancelStore(Client* client, Seperator* sep);
+
// AA Commands
void Get_AA_Xml(Client* client, Seperator* sep);
void Add_AA(Client* client, Seperator* sep);
@@ -984,7 +993,16 @@ private:
#define COMMAND_MOOD 800
#define COMMAND_RELOAD_PLAYERSCRIPTS 801
-
+#define COMMAND_SLE 802
+#define COMMAND_STORE_LIST_ITEM 803
+#define COMMAND_STORE_SET_PRICE 804
+#define COMMAND_STORE_SET_PRICE_LOCAL 805
+#define COMMAND_STORE_START_SELLING 806
+#define COMMAND_STORE_STOP_SELLING 807
+#define COMMAND_STORE_UNLIST_ITEM 808
+#define COMMAND_CLOSE_STORE_KEEP_SELLING 809
+#define COMMAND_CANCEL_STORE 810
+
#define COMMAND_MODIFY 1000 // INSERT INTO `commands`(`id`,`type`,`command`,`subcommand`,`handler`,`required_status`) VALUES ( NULL,'1','modify','','1000','200');
#define COMMAND_MODIFY_CHARACTER 1001
#define COMMAND_MODIFY_FACTION 1002
diff --git a/source/WorldServer/Commands/CommandsDB.cpp b/source/WorldServer/Commands/CommandsDB.cpp
index a5c8d01..f5ca257 100644
--- a/source/WorldServer/Commands/CommandsDB.cpp
+++ b/source/WorldServer/Commands/CommandsDB.cpp
@@ -1,6 +1,6 @@
-/*
+/*
EQ2Emulator: Everquest II Server Emulator
- Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
+ Copyright (C) 2005 - 2026 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net)
This file is part of EQ2Emulator.
@@ -17,6 +17,7 @@
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see .
*/
+
#ifdef WIN32
#include
#include
@@ -118,6 +119,9 @@ bool WorldDatabase::RemoveSpawnTemplate(int32 template_id)
int32 WorldDatabase::CreateSpawnFromTemplateByID(Client* client, int32 template_id)
{
+ if(client && client->GetCurrentZone() && client->GetCurrentZone()->GetInstanceType() == Instance_Type::PERSONAL_HOUSE_INSTANCE) {
+ return 0;
+ }
Query query, query2, query3, query4, query5, query6;
MYSQL_ROW row;
int32 spawn_location_id = 0;
diff --git a/source/WorldServer/Entity.cpp b/source/WorldServer/Entity.cpp
index 536d946..1d247db 100644
--- a/source/WorldServer/Entity.cpp
+++ b/source/WorldServer/Entity.cpp
@@ -1,6 +1,6 @@
/*
EQ2Emulator: Everquest II Server Emulator
- Copyright (C) 2005 - 2025 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net)
+ Copyright (C) 2005 - 2026 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net)
This file is part of EQ2Emulator.
@@ -17,6 +17,7 @@
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see .
*/
+
#include "Entity.h"
#include
#include "Items/Items.h"
@@ -149,8 +150,8 @@ void Entity::DeleteSpellEffects(bool removeClient)
GetInfoStruct()->spell_effects[i].spell = nullptr;
}
}
- MMaintainedSpells.releasewritelock(__FUNCTION__, __LINE__);
MSpellEffects.releasewritelock(__FUNCTION__, __LINE__);
+ MMaintainedSpells.releasewritelock(__FUNCTION__, __LINE__);
map::iterator deletedPtrItrs;
for(deletedPtrItrs = deletedPtrs.begin(); deletedPtrItrs != deletedPtrs.end(); deletedPtrItrs++) {
@@ -1091,6 +1092,11 @@ void Entity::DoRegenUpdate(){
void Entity::AddMaintainedSpell(LuaSpell* luaspell){
if (!luaspell)
return;
+
+ if(luaspell->spell->GetSpellData()->not_maintained || luaspell->spell->GetSpellData()->duration1 == 0) {
+ LogWrite(NPC__SPELLS, 5, "NPC", "AddMaintainedSpell Spell ID: %u, Concentration: %u disallowed, not_maintained true (%u) or duration is 0 (%u).", luaspell->spell->GetSpellData()->id, luaspell->spell->GetSpellData()->req_concentration, luaspell->spell->GetSpellData()->not_maintained, luaspell->spell->GetSpellData()->duration1);
+ return;
+ }
Spell* spell = luaspell->spell;
MaintainedEffects* effect = GetFreeMaintainedSpellSlot();
@@ -1290,7 +1296,7 @@ SpellEffects* Entity::GetSpellEffectBySpellType(int8 spell_type) {
return ret;
}
-SpellEffects* Entity::GetSpellEffectWithLinkedTimer(int32 id, int32 linked_timer, sint32 type_group_spell_id, Entity* caster) {
+SpellEffects* Entity::GetSpellEffectWithLinkedTimer(int32 id, int32 linked_timer, sint32 type_group_spell_id, Entity* caster, bool notCaster) {
SpellEffects* ret = 0;
InfoStruct* info = GetInfoStruct();
MSpellEffects.readlock(__FUNCTION__, __LINE__);
@@ -1301,7 +1307,7 @@ SpellEffects* Entity::GetSpellEffectWithLinkedTimer(int32 id, int32 linked_timer
(linked_timer > 0 && info->spell_effects[i].spell->spell->GetSpellData()->linked_timer == linked_timer) ||
(type_group_spell_id > 0 && info->spell_effects[i].spell->spell->GetSpellData()->type_group_spell_id == type_group_spell_id))
{
- if (type_group_spell_id >= -1 && (!caster || info->spell_effects[i].caster == caster)){
+ if (type_group_spell_id >= -1 && (!caster || (!notCaster && info->spell_effects[i].caster == caster) || (notCaster && info->spell_effects[i].caster != caster))){
ret = &info->spell_effects[i];
break;
}
@@ -1312,12 +1318,14 @@ SpellEffects* Entity::GetSpellEffectWithLinkedTimer(int32 id, int32 linked_timer
return ret;
}
-LuaSpell* Entity::HasLinkedTimerID(LuaSpell* spell, Spawn* target, bool stackWithOtherPlayers) {
+LuaSpell* Entity::HasLinkedTimerID(LuaSpell* spell, Spawn* target, bool stackWithOtherPlayers, bool checkNotCaster) {
if(!spell->spell->GetSpellData()->linked_timer && !spell->spell->GetSpellData()->type_group_spell_id)
return nullptr;
LuaSpell* ret = nullptr;
InfoStruct* info = GetInfoStruct();
- MSpellEffects.readlock(__FUNCTION__, __LINE__);
+ std::vector targets = spell->GetTargets();
+
+ MMaintainedSpells.readlock(__FUNCTION__, __LINE__);
//this for loop primarily handles self checks and 'friendly' checks
for(int i = 0; i < NUM_MAINTAINED_EFFECTS; i++) {
if(info->maintained_effects[i].spell_id != 0xFFFFFFFF)
@@ -1327,21 +1335,34 @@ LuaSpell* Entity::HasLinkedTimerID(LuaSpell* spell, Spawn* target, bool stackWit
(spell->spell->GetSpellData()->type_group_spell_id > 0 && spell->spell->GetSpellData()->type_group_spell_id == info->maintained_effects[i].spell->spell->GetSpellData()->type_group_spell_id)) &&
((spell->spell->GetSpellData()->friendly_spell) ||
(!spell->spell->GetSpellData()->friendly_spell && spell->spell->GetSpellData()->type_group_spell_id >= -1 && spell->caster == info->maintained_effects[i].spell->caster) ) &&
- (target == nullptr || info->maintained_effects[i].spell->initial_target == target->GetID())) {
+ (target == nullptr || info->maintained_effects[i].spell->HasTarget(target->GetID()) || info->maintained_effects[i].spell->HasAnyTarget(targets))) {
ret = info->maintained_effects[i].spell;
break;
}
}
}
- MSpellEffects.releasereadlock(__FUNCTION__, __LINE__);
-
- if(!ret && !stackWithOtherPlayers && target && target->IsEntity())
- {
- SpellEffects* effect = ((Entity*)target)->GetSpellEffectWithLinkedTimer(spell->spell->GetSpellID(), spell->spell->GetSpellData()->linked_timer, spell->spell->GetSpellData()->type_group_spell_id, nullptr);
- if(effect)
- ret = effect->spell;
+ MMaintainedSpells.releasereadlock(__FUNCTION__, __LINE__);
+
+ if(checkNotCaster && ret && ret->caster != spell->caster)
+ return ret;
+ else if(checkNotCaster && ret)
+ ret = nullptr;
+
+ for (int32 id : spell->GetTargets()) {
+ Spawn* tmpTarget = spell->zone->GetSpawnByID(id);
+ if(!ret && !stackWithOtherPlayers && tmpTarget && tmpTarget->IsEntity())
+ {
+ SpellEffects* effect = ((Entity*)tmpTarget)->GetSpellEffectWithLinkedTimer(spell->spell->GetSpellID(), spell->spell->GetSpellData()->linked_timer, spell->spell->GetSpellData()->type_group_spell_id, checkNotCaster ? spell->caster : nullptr, checkNotCaster);
+ if(effect) {
+ ret = effect->spell;
+ break;
+ }
+ }
}
+ if(checkNotCaster && ret && ret->caster == spell->caster)
+ ret = nullptr;
+
return ret;
}
diff --git a/source/WorldServer/Entity.h b/source/WorldServer/Entity.h
index 9d1a434..40348a0 100644
--- a/source/WorldServer/Entity.h
+++ b/source/WorldServer/Entity.h
@@ -1,6 +1,6 @@
/*
EQ2Emulator: Everquest II Server Emulator
- Copyright (C) 2005 - 2025 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net)
+ Copyright (C) 2005 - 2026 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net)
This file is part of EQ2Emulator.
@@ -1496,8 +1496,8 @@ public:
SpellEffects* GetFreeSpellEffectSlot();
SpellEffects* GetSpellEffect(int32 id, Entity* caster = 0, bool on_char_load = false);
SpellEffects* GetSpellEffectBySpellType(int8 spell_type);
- SpellEffects* GetSpellEffectWithLinkedTimer(int32 id, int32 linked_timer = 0, sint32 type_group_spell_id = 0, Entity* caster = 0);
- LuaSpell* HasLinkedTimerID(LuaSpell* spell, Spawn* target = nullptr, bool stackWithOtherPlayers = true);
+ SpellEffects* GetSpellEffectWithLinkedTimer(int32 id, int32 linked_timer = 0, sint32 type_group_spell_id = 0, Entity* caster = 0, bool notCaster = false);
+ LuaSpell* HasLinkedTimerID(LuaSpell* spell, Spawn* target = nullptr, bool stackWithOtherPlayers = true, bool checkNotCaster = false);
//flags
int32 GetFlags() { return info_struct.get_flags(); }
diff --git a/source/WorldServer/Items/Items.cpp b/source/WorldServer/Items/Items.cpp
index 54ad29d..6b6f5bc 100644
--- a/source/WorldServer/Items/Items.cpp
+++ b/source/WorldServer/Items/Items.cpp
@@ -1,6 +1,6 @@
/*
EQ2Emulator: Everquest II Server Emulator
- Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
+ Copyright (C) 2005 - 2026 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net)
This file is part of EQ2Emulator.
@@ -17,6 +17,7 @@
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see .
*/
+
#include "Items.h"
#include "../Spells.h"
#include "../Quests.h"
@@ -32,6 +33,8 @@
#include
#include
#include "../Rules/Rules.h"
+#include "../WorldDatabase.h"
+#include "../Broker/BrokerManager.h"
extern World world;
extern MasterSpellList master_spell_list;
@@ -41,6 +44,9 @@ extern ConfigReader configReader;
extern LuaInterface* lua_interface;
extern RuleManager rule_manager;
extern Classes classes;
+extern MasterItemList master_item_list;
+extern WorldDatabase database;
+extern BrokerManager broker;
MasterItemList::MasterItemList(){
AddMappedItemStat(ITEM_STAT_ADORNING, std::string("adorning"));
@@ -151,10 +157,519 @@ map>::iterator MasterItemList::FindBrokerItemMap
return enditr;
}
+bool MasterItemList::ShouldAddItemBrokerType(Item* item, int64 itype) {
+ bool should_add = false;
+ switch(itype){
+ case ITEM_BROKER_TYPE_ADORNMENT:{
+ if(item->IsAdornment())
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_TYPE_AMMO:{
+ if(item->IsAmmo())
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_TYPE_ATTUNEABLE:{
+ if(item->CheckFlag(ATTUNEABLE))
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_TYPE_BAG:{
+ if(item->IsBag())
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_TYPE_BAUBLE:{
+ if(item->IsBauble())
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_TYPE_BOOK:{
+ if(item->IsBook())
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_TYPE_CHAINARMOR:{
+ if(item->IsChainArmor())
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_TYPE_CLOAK:{
+ if(item->IsCloak())
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_TYPE_CLOTHARMOR:{
+ if(item->IsClothArmor())
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_TYPE_COLLECTABLE:{
+ if(item->IsCollectable())
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_TYPE_CRUSHWEAPON:{
+ if(item->IsCrushWeapon() && (item->weapon_info->wield_type == ITEM_WIELD_TYPE_DUAL || item->weapon_info->wield_type == ITEM_WIELD_TYPE_SINGLE))
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_TYPE_DRINK:{
+ if(item->IsFoodDrink())
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_TYPE_FOOD:{
+ if(item->IsFoodFood())
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_TYPE_HOUSEITEM:{
+ if(item->IsHouseItem() || item->IsHouseContainer())
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_TYPE_JEWELRY:{
+ if(item->IsJewelry())
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_TYPE_LEATHERARMOR:{
+ if(item->IsLeatherArmor())
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_TYPE_LORE:{
+ if(item->CheckFlag(LORE))
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_TYPE_MISC:{
+ if(item->IsMisc())
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_TYPE_PIERCEWEAPON:{
+ if(item->IsPierceWeapon() && (item->weapon_info->wield_type == ITEM_WIELD_TYPE_DUAL || item->weapon_info->wield_type == ITEM_WIELD_TYPE_SINGLE))
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_TYPE_PLATEARMOR:{
+ if(item->IsPlateArmor())
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_TYPE_POISON:{
+ if(item->IsPoison())
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_TYPE_POTION:{
+ if(item->IsPotion())
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_TYPE_RECIPEBOOK:{
+ if(item->IsRecipeBook())
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_TYPE_SALESDISPLAY:{
+ if(item->IsSalesDisplay())
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_TYPE_SHIELD:{
+ if(item->IsShield())
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_TYPE_SLASHWEAPON:{
+ if(item->IsSlashWeapon() && (item->weapon_info->wield_type == ITEM_WIELD_TYPE_DUAL || item->weapon_info->wield_type == ITEM_WIELD_TYPE_SINGLE))
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_TYPE_SPELLSCROLL:{
+ if(item->IsSpellScroll())
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_TYPE_TINKERED:{
+ if(item->tinkered == 1)
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_TYPE_TRADESKILL:{
+ if(item->crafted == 1)
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_TYPE_2H_CRUSH:{
+ should_add = item->IsWeapon() && item->weapon_info->wield_type == ITEM_WIELD_TYPE_TWO_HAND && item->generic_info.skill_req1 == SKILL_ID_STAFF;
+ break;
+ }
+ case ITEM_BROKER_TYPE_2H_PIERCE:{
+ should_add = item->IsWeapon() && item->weapon_info->wield_type == ITEM_WIELD_TYPE_TWO_HAND && item->generic_info.skill_req1 == SKILL_ID_GREATSPEAR;
+ break;
+ }
+ case ITEM_BROKER_TYPE_2H_SLASH:{
+ should_add = item->IsWeapon() && item->weapon_info->wield_type == ITEM_WIELD_TYPE_TWO_HAND && item->generic_info.skill_req1 == SKILL_ID_GREATSWORD;
+ break;
+ }
+ }
+ return should_add;
+}
+bool MasterItemList::ShouldAddItemBrokerSlot(Item* item, int64 ltype) {
+ bool should_add = false;
+
+ switch(ltype){
+ case ITEM_BROKER_SLOT_AMMO:{
+ should_add = item->HasSlot(EQ2_AMMO_SLOT);
+ break;
+ }
+ case ITEM_BROKER_SLOT_CHARM:{
+ should_add = item->HasSlot(EQ2_CHARM_SLOT_1, EQ2_CHARM_SLOT_2);
+ break;
+ }
+ case ITEM_BROKER_SLOT_CHEST:{
+ should_add = item->HasSlot(EQ2_CHEST_SLOT);
+ break;
+ }
+ case ITEM_BROKER_SLOT_CLOAK:{
+ should_add = item->HasSlot(EQ2_CLOAK_SLOT);
+ break;
+ }
+ case ITEM_BROKER_SLOT_DRINK:{
+ should_add = item->HasSlot(EQ2_DRINK_SLOT);
+ break;
+ }
+ case ITEM_BROKER_SLOT_EARS:{
+ should_add = item->HasSlot(EQ2_EARS_SLOT_1, EQ2_EARS_SLOT_2);
+ break;
+ }
+ case ITEM_BROKER_SLOT_FEET:{
+ should_add = item->HasSlot(EQ2_FEET_SLOT);
+ break;
+ }
+ case ITEM_BROKER_SLOT_FOOD:{
+ should_add = item->HasSlot(EQ2_FOOD_SLOT);
+ break;
+ }
+ case ITEM_BROKER_SLOT_FOREARMS:{
+ should_add = item->HasSlot(EQ2_FOREARMS_SLOT);
+ break;
+ }
+ case ITEM_BROKER_SLOT_HANDS:{
+ should_add = item->HasSlot(EQ2_HANDS_SLOT);
+ break;
+ }
+ case ITEM_BROKER_SLOT_HEAD:{
+ should_add = item->HasSlot(EQ2_HEAD_SLOT);
+ break;
+ }
+ case ITEM_BROKER_SLOT_LEGS:{
+ should_add = item->HasSlot(EQ2_LEGS_SLOT);
+ break;
+ }
+ case ITEM_BROKER_SLOT_NECK:{
+ should_add = item->HasSlot(EQ2_NECK_SLOT);
+ break;
+ }
+ case ITEM_BROKER_SLOT_PRIMARY:{
+ should_add = item->HasSlot(EQ2_PRIMARY_SLOT);
+ break;
+ }
+ case ITEM_BROKER_SLOT_PRIMARY_2H:{
+ should_add = item->HasSlot(EQ2_PRIMARY_SLOT) && item->IsWeapon() && item->weapon_info->wield_type == ITEM_WIELD_TYPE_TWO_HAND;
+ break;
+ }
+ case ITEM_BROKER_SLOT_RANGE_WEAPON:{
+ should_add = item->HasSlot(EQ2_RANGE_SLOT);
+ break;
+ }
+ case ITEM_BROKER_SLOT_RING:{
+ should_add = item->HasSlot(EQ2_LRING_SLOT, EQ2_RRING_SLOT);
+ break;
+ }
+ case ITEM_BROKER_SLOT_SECONDARY:{
+ should_add = item->HasSlot(EQ2_SECONDARY_SLOT);
+ break;
+ }
+ case ITEM_BROKER_SLOT_SHOULDERS:{
+ should_add = item->HasSlot(EQ2_SHOULDERS_SLOT);
+ break;
+ }
+ case ITEM_BROKER_SLOT_WAIST:{
+ should_add = item->HasSlot(EQ2_WAIST_SLOT);
+ break;
+ }
+ case ITEM_BROKER_SLOT_WRIST:{
+ should_add = item->HasSlot(EQ2_LWRIST_SLOT, EQ2_RWRIST_SLOT);
+ break;
+ }
+ }
+
+ return should_add;
+}
+
+bool MasterItemList::ShouldAddItemBrokerStat(Item* item, int64 btype) {
+ bool should_add = false;
+ bool stat_found = false;
+ switch(btype){
+ case ITEM_BROKER_STAT_TYPE_NONE:{
+ if (item->item_stats.size() == 0)
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_STAT_TYPE_DEF:{
+ stat_found = item->HasStat(ITEM_STAT_DEFENSE, GetItemStatNameByID(ITEM_STAT_DEFENSE));
+ if (stat_found)
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_STAT_TYPE_STR:{
+ stat_found = item->HasStat(ITEM_STAT_STR);
+ if (stat_found)
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_STAT_TYPE_STA:{
+ stat_found = item->HasStat(ITEM_STAT_STA);
+ if (stat_found)
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_STAT_TYPE_AGI:{
+ stat_found = item->HasStat(ITEM_STAT_AGI);
+ if (stat_found)
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_STAT_TYPE_WIS:{
+ stat_found = item->HasStat(ITEM_STAT_WIS);
+ if (stat_found)
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_STAT_TYPE_INT:{
+ stat_found = item->HasStat(ITEM_STAT_INT);
+ if (stat_found)
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_STAT_TYPE_HEALTH:{
+ stat_found = item->HasStat(ITEM_STAT_HEALTH);
+ if (stat_found)
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_STAT_TYPE_POWER:{
+ stat_found = item->HasStat(ITEM_STAT_POWER);
+ if (stat_found)
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_STAT_TYPE_HEAT:{
+ stat_found = item->HasStat(ITEM_STAT_VS_HEAT);
+ if (stat_found)
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_STAT_TYPE_COLD:{
+ stat_found = item->HasStat(ITEM_STAT_VS_COLD);
+ if (stat_found)
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_STAT_TYPE_MAGIC:{
+ stat_found = item->HasStat(ITEM_STAT_VS_MAGIC);
+ if (stat_found)
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_STAT_TYPE_MENTAL:{
+ stat_found = item->HasStat(ITEM_STAT_VS_MENTAL);
+ if (stat_found)
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_STAT_TYPE_DIVINE:{
+ stat_found = item->HasStat(ITEM_STAT_VS_DIVINE);
+ if (stat_found)
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_STAT_TYPE_POISON:{
+ stat_found = item->HasStat(ITEM_STAT_VS_POISON);
+ if (stat_found)
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_STAT_TYPE_DISEASE:{
+ stat_found = item->HasStat(ITEM_STAT_VS_DISEASE);
+ if (stat_found)
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_STAT_TYPE_CRUSH:{
+ stat_found = item->HasStat(ITEM_STAT_DMG_CRUSH);
+ if (stat_found)
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_STAT_TYPE_SLASH:{
+ stat_found = item->HasStat(ITEM_STAT_DMG_SLASH);
+ if (stat_found)
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_STAT_TYPE_PIERCE:{
+ stat_found = item->HasStat(ITEM_STAT_DMG_PIERCE);
+ if (stat_found)
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_STAT_TYPE_CRITICAL: {
+ stat_found = item->HasStat(ITEM_STAT_CRITICALMITIGATION);
+ if (stat_found)
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_STAT_TYPE_DBL_ATTACK:{
+ stat_found = item->HasStat(ITEM_STAT_MULTIATTACKCHANCE);
+ if (stat_found)
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_STAT_TYPE_ABILITY_MOD:{
+ stat_found = item->HasStat(ITEM_STAT_ABILITY_MODIFIER);
+ if (stat_found)
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_STAT_TYPE_POTENCY:{
+ stat_found = item->HasStat(ITEM_STAT_POTENCY);
+ if (stat_found)
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_STAT_TYPE_AEAUTOATTACK:{
+ stat_found = item->HasStat(ITEM_STAT_AEAUTOATTACKCHANCE);
+ if (stat_found)
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_STAT_TYPE_ATTACKSPEED:{
+ stat_found = item->HasStat(ITEM_STAT_ATTACKSPEED);
+ if (stat_found)
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_STAT_TYPE_BLOCKCHANCE:{
+ stat_found = item->HasStat(ITEM_STAT_EXTRASHIELDBLOCKCHANCE);
+ if (stat_found)
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_STAT_TYPE_CASTINGSPEED:{
+ stat_found = item->HasStat(ITEM_STAT_ABILITYCASTINGSPEED);
+ if (stat_found)
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_STAT_TYPE_CRITBONUS:{
+ stat_found = item->HasStat(ITEM_STAT_CRITBONUS);
+ if (stat_found)
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_STAT_TYPE_CRITCHANCE:{
+ stat_found = item->HasStat(ITEM_STAT_MELEECRITCHANCE);
+ if (stat_found)
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_STAT_TYPE_DPS:{
+ stat_found = item->HasStat(ITEM_STAT_DPS);
+ if (stat_found)
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_STAT_TYPE_FLURRYCHANCE:{
+ stat_found = item->HasStat(ITEM_STAT_FLURRY);
+ if (stat_found)
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_STAT_TYPE_HATEGAIN:{
+ stat_found = item->HasStat(ITEM_STAT_HATEGAINMOD);
+ if (stat_found)
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_STAT_TYPE_MITIGATION:{
+ stat_found = item->HasStat(ITEM_STAT_ARMORMITIGATIONINCREASE);
+ if (stat_found)
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_STAT_TYPE_MULTI_ATTACK:{
+ stat_found = item->HasStat(ITEM_STAT_MULTIATTACKCHANCE);
+ if (stat_found)
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_STAT_TYPE_RECOVERY:{
+ stat_found = item->HasStat(ITEM_STAT_ABILITYRECOVERYSPEED);
+ if (stat_found)
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_STAT_TYPE_REUSE_SPEED:{
+ stat_found = item->HasStat(ITEM_STAT_ABILITYREUSESPEED);
+ if (stat_found)
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_STAT_TYPE_SPELL_WPNDMG:{
+ stat_found = item->HasStat(ITEM_STAT_SPELLWEAPONDAMAGEBONUS);
+ if (stat_found)
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_STAT_TYPE_STRIKETHROUGH:{
+ stat_found = item->HasStat(ITEM_STAT_STRIKETHROUGH);
+ if (stat_found)
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_STAT_TYPE_TOUGHNESS:{
+ stat_found = item->HasStat(ITEM_STAT_PVPTOUGHNESS);
+ if (stat_found)
+ should_add = true;
+ break;
+ }
+ case ITEM_BROKER_STAT_TYPE_WEAPONDMG:{
+ stat_found = item->HasStat(ITEM_STAT_WEAPONDAMAGEBONUS);
+ if (stat_found)
+ should_add = true;
+ break;
+ }
+ default: {
+ LogWrite(ITEM__DEBUG, 0, "Item", "Unknown item broker stat type %u", btype);
+ LogWrite(ITEM__DEBUG, 0, "Item", "If you have a client before the new expansion this may be the reason. Please be patient while we update items to support the new client.", btype);
+ break;
+ }
+ }
+
+ return should_add;
+}
+
vector
- * MasterItemList::GetItems(string name, int64 itype, int64 ltype, int64 btype, int64 minprice, int64 maxprice, int8 minskill, int8 maxskill, string seller, string adornment, int8 mintier, int8 maxtier, int16 minlevel, int16 maxlevel, sint8 itemclass){
vector
- * ret = new vector
- ;
map::iterator iter;
- Item* item = 0;
+ Item* item = 0;
const char* chkname = 0;
//const char* chkseller = 0;
//const char* chkadornment = 0;
@@ -170,511 +685,19 @@ vector
- * MasterItemList::GetItems(string name, int64 itype, int64 ltype, i
item = iter->second;
if(item){
if(itype != ITEM_BROKER_TYPE_ANY && itype != ITEM_BROKER_TYPE_ANY64BIT){
- should_add = false;
- switch(itype){
- case ITEM_BROKER_TYPE_ADORNMENT:{
- if(item->IsAdornment())
- should_add = true;
- break;
- }
- case ITEM_BROKER_TYPE_AMMO:{
- if(item->IsAmmo())
- should_add = true;
- break;
- }
- case ITEM_BROKER_TYPE_ATTUNEABLE:{
- if(item->CheckFlag(ATTUNEABLE))
- should_add = true;
- break;
- }
- case ITEM_BROKER_TYPE_BAG:{
- if(item->IsBag())
- should_add = true;
- break;
- }
- case ITEM_BROKER_TYPE_BAUBLE:{
- if(item->IsBauble())
- should_add = true;
- break;
- }
- case ITEM_BROKER_TYPE_BOOK:{
- if(item->IsBook())
- should_add = true;
- break;
- }
- case ITEM_BROKER_TYPE_CHAINARMOR:{
- if(item->IsChainArmor())
- should_add = true;
- break;
- }
- case ITEM_BROKER_TYPE_CLOAK:{
- if(item->IsCloak())
- should_add = true;
- break;
- }
- case ITEM_BROKER_TYPE_CLOTHARMOR:{
- if(item->IsClothArmor())
- should_add = true;
- break;
- }
- case ITEM_BROKER_TYPE_COLLECTABLE:{
- if(item->IsCollectable())
- should_add = true;
- break;
- }
- case ITEM_BROKER_TYPE_CRUSHWEAPON:{
- if(item->IsCrushWeapon() && (item->weapon_info->wield_type == ITEM_WIELD_TYPE_DUAL || item->weapon_info->wield_type == ITEM_WIELD_TYPE_SINGLE))
- should_add = true;
- break;
- }
- case ITEM_BROKER_TYPE_DRINK:{
- if(item->IsFoodDrink())
- should_add = true;
- break;
- }
- case ITEM_BROKER_TYPE_FOOD:{
- if(item->IsFoodFood())
- should_add = true;
- break;
- }
- case ITEM_BROKER_TYPE_HOUSEITEM:{
- if(item->IsHouseItem() || item->IsHouseContainer())
- should_add = true;
- break;
- }
- case ITEM_BROKER_TYPE_JEWELRY:{
- if(item->IsJewelry())
- should_add = true;
- break;
- }
- case ITEM_BROKER_TYPE_LEATHERARMOR:{
- if(item->IsLeatherArmor())
- should_add = true;
- break;
- }
- case ITEM_BROKER_TYPE_LORE:{
- if(item->CheckFlag(LORE))
- should_add = true;
- break;
- }
- case ITEM_BROKER_TYPE_MISC:{
- if(item->IsMisc())
- should_add = true;
- break;
- }
- case ITEM_BROKER_TYPE_PIERCEWEAPON:{
- if(item->IsPierceWeapon() && (item->weapon_info->wield_type == ITEM_WIELD_TYPE_DUAL || item->weapon_info->wield_type == ITEM_WIELD_TYPE_SINGLE))
- should_add = true;
- break;
- }
- case ITEM_BROKER_TYPE_PLATEARMOR:{
- if(item->IsPlateArmor())
- should_add = true;
- break;
- }
- case ITEM_BROKER_TYPE_POISON:{
- if(item->IsPoison())
- should_add = true;
- break;
- }
- case ITEM_BROKER_TYPE_POTION:{
- if(item->IsPotion())
- should_add = true;
- break;
- }
- case ITEM_BROKER_TYPE_RECIPEBOOK:{
- if(item->IsRecipeBook())
- should_add = true;
- break;
- }
- case ITEM_BROKER_TYPE_SALESDISPLAY:{
- if(item->IsSalesDisplay())
- should_add = true;
- break;
- }
- case ITEM_BROKER_TYPE_SHIELD:{
- if(item->IsShield())
- should_add = true;
- break;
- }
- case ITEM_BROKER_TYPE_SLASHWEAPON:{
- if(item->IsSlashWeapon() && (item->weapon_info->wield_type == ITEM_WIELD_TYPE_DUAL || item->weapon_info->wield_type == ITEM_WIELD_TYPE_SINGLE))
- should_add = true;
- break;
- }
- case ITEM_BROKER_TYPE_SPELLSCROLL:{
- if(item->IsSpellScroll())
- should_add = true;
- break;
- }
- case ITEM_BROKER_TYPE_TINKERED:{
- if(item->tinkered == 1)
- should_add = true;
- break;
- }
- case ITEM_BROKER_TYPE_TRADESKILL:{
- if(item->crafted == 1)
- should_add = true;
- break;
- }
- case ITEM_BROKER_TYPE_2H_CRUSH:{
- should_add = item->IsWeapon() && item->weapon_info->wield_type == ITEM_WIELD_TYPE_TWO_HAND && item->generic_info.skill_req1 == SKILL_ID_STAFF;
- break;
- }
- case ITEM_BROKER_TYPE_2H_PIERCE:{
- should_add = item->IsWeapon() && item->weapon_info->wield_type == ITEM_WIELD_TYPE_TWO_HAND && item->generic_info.skill_req1 == SKILL_ID_GREATSPEAR;
- break;
- }
- case ITEM_BROKER_TYPE_2H_SLASH:{
- should_add = item->IsWeapon() && item->weapon_info->wield_type == ITEM_WIELD_TYPE_TWO_HAND && item->generic_info.skill_req1 == SKILL_ID_GREATSWORD;
- break;
- }
- }
+ should_add = ShouldAddItemBrokerType(item, itype);
if(!should_add)
continue;
}
if(ltype != ITEM_BROKER_SLOT_ANY){
- should_add = false;
- switch(ltype){
- case ITEM_BROKER_SLOT_AMMO:{
- should_add = item->HasSlot(EQ2_AMMO_SLOT);
- break;
- }
- case ITEM_BROKER_SLOT_CHARM:{
- should_add = item->HasSlot(EQ2_CHARM_SLOT_1, EQ2_CHARM_SLOT_2);
- break;
- }
- case ITEM_BROKER_SLOT_CHEST:{
- should_add = item->HasSlot(EQ2_CHEST_SLOT);
- break;
- }
- case ITEM_BROKER_SLOT_CLOAK:{
- should_add = item->HasSlot(EQ2_CLOAK_SLOT);
- break;
- }
- case ITEM_BROKER_SLOT_DRINK:{
- should_add = item->HasSlot(EQ2_DRINK_SLOT);
- break;
- }
- case ITEM_BROKER_SLOT_EARS:{
- should_add = item->HasSlot(EQ2_EARS_SLOT_1, EQ2_EARS_SLOT_2);
- break;
- }
- case ITEM_BROKER_SLOT_FEET:{
- should_add = item->HasSlot(EQ2_FEET_SLOT);
- break;
- }
- case ITEM_BROKER_SLOT_FOOD:{
- should_add = item->HasSlot(EQ2_FOOD_SLOT);
- break;
- }
- case ITEM_BROKER_SLOT_FOREARMS:{
- should_add = item->HasSlot(EQ2_FOREARMS_SLOT);
- break;
- }
- case ITEM_BROKER_SLOT_HANDS:{
- should_add = item->HasSlot(EQ2_HANDS_SLOT);
- break;
- }
- case ITEM_BROKER_SLOT_HEAD:{
- should_add = item->HasSlot(EQ2_HEAD_SLOT);
- break;
- }
- case ITEM_BROKER_SLOT_LEGS:{
- should_add = item->HasSlot(EQ2_LEGS_SLOT);
- break;
- }
- case ITEM_BROKER_SLOT_NECK:{
- should_add = item->HasSlot(EQ2_NECK_SLOT);
- break;
- }
- case ITEM_BROKER_SLOT_PRIMARY:{
- should_add = item->HasSlot(EQ2_PRIMARY_SLOT);
- break;
- }
- case ITEM_BROKER_SLOT_PRIMARY_2H:{
- should_add = item->HasSlot(EQ2_PRIMARY_SLOT) && item->IsWeapon() && item->weapon_info->wield_type == ITEM_WIELD_TYPE_TWO_HAND;
- break;
- }
- case ITEM_BROKER_SLOT_RANGE_WEAPON:{
- should_add = item->HasSlot(EQ2_RANGE_SLOT);
- break;
- }
- case ITEM_BROKER_SLOT_RING:{
- should_add = item->HasSlot(EQ2_LRING_SLOT, EQ2_RRING_SLOT);
- break;
- }
- case ITEM_BROKER_SLOT_SECONDARY:{
- should_add = item->HasSlot(EQ2_SECONDARY_SLOT);
- break;
- }
- case ITEM_BROKER_SLOT_SHOULDERS:{
- should_add = item->HasSlot(EQ2_SHOULDERS_SLOT);
- break;
- }
- case ITEM_BROKER_SLOT_WAIST:{
- should_add = item->HasSlot(EQ2_WAIST_SLOT);
- break;
- }
- case ITEM_BROKER_SLOT_WRIST:{
- should_add = item->HasSlot(EQ2_LWRIST_SLOT, EQ2_RWRIST_SLOT);
- break;
- }
- }
+ should_add = ShouldAddItemBrokerSlot(item, ltype);
if(!should_add)
continue;
}
if(btype != 0xFFFFFFFF){
vector::iterator itr;
- bool stat_found = false;
- should_add = false;
- switch(btype){
- case ITEM_BROKER_STAT_TYPE_NONE:{
- if (item->item_stats.size() == 0)
- should_add = true;
- break;
- }
- case ITEM_BROKER_STAT_TYPE_DEF:{
- stat_found = item->HasStat(ITEM_STAT_DEFENSE, GetItemStatNameByID(ITEM_STAT_DEFENSE));
- if (stat_found)
- should_add = true;
- break;
- }
- case ITEM_BROKER_STAT_TYPE_STR:{
- stat_found = item->HasStat(ITEM_STAT_STR);
- if (stat_found)
- should_add = true;
- break;
- }
- case ITEM_BROKER_STAT_TYPE_STA:{
- stat_found = item->HasStat(ITEM_STAT_STA);
- if (stat_found)
- should_add = true;
- break;
- }
- case ITEM_BROKER_STAT_TYPE_AGI:{
- stat_found = item->HasStat(ITEM_STAT_AGI);
- if (stat_found)
- should_add = true;
- break;
- }
- case ITEM_BROKER_STAT_TYPE_WIS:{
- stat_found = item->HasStat(ITEM_STAT_WIS);
- if (stat_found)
- should_add = true;
- break;
- }
- case ITEM_BROKER_STAT_TYPE_INT:{
- stat_found = item->HasStat(ITEM_STAT_INT);
- if (stat_found)
- should_add = true;
- break;
- }
- case ITEM_BROKER_STAT_TYPE_HEALTH:{
- stat_found = item->HasStat(ITEM_STAT_HEALTH);
- if (stat_found)
- should_add = true;
- break;
- }
- case ITEM_BROKER_STAT_TYPE_POWER:{
- stat_found = item->HasStat(ITEM_STAT_POWER);
- if (stat_found)
- should_add = true;
- break;
- }
- case ITEM_BROKER_STAT_TYPE_HEAT:{
- stat_found = item->HasStat(ITEM_STAT_VS_HEAT);
- if (stat_found)
- should_add = true;
- break;
- }
- case ITEM_BROKER_STAT_TYPE_COLD:{
- stat_found = item->HasStat(ITEM_STAT_VS_COLD);
- if (stat_found)
- should_add = true;
- break;
- }
- case ITEM_BROKER_STAT_TYPE_MAGIC:{
- stat_found = item->HasStat(ITEM_STAT_VS_MAGIC);
- if (stat_found)
- should_add = true;
- break;
- }
- case ITEM_BROKER_STAT_TYPE_MENTAL:{
- stat_found = item->HasStat(ITEM_STAT_VS_MENTAL);
- if (stat_found)
- should_add = true;
- break;
- }
- case ITEM_BROKER_STAT_TYPE_DIVINE:{
- stat_found = item->HasStat(ITEM_STAT_VS_DIVINE);
- if (stat_found)
- should_add = true;
- break;
- }
- case ITEM_BROKER_STAT_TYPE_POISON:{
- stat_found = item->HasStat(ITEM_STAT_VS_POISON);
- if (stat_found)
- should_add = true;
- break;
- }
- case ITEM_BROKER_STAT_TYPE_DISEASE:{
- stat_found = item->HasStat(ITEM_STAT_VS_DISEASE);
- if (stat_found)
- should_add = true;
- break;
- }
- case ITEM_BROKER_STAT_TYPE_CRUSH:{
- stat_found = item->HasStat(ITEM_STAT_DMG_CRUSH);
- if (stat_found)
- should_add = true;
- break;
- }
- case ITEM_BROKER_STAT_TYPE_SLASH:{
- stat_found = item->HasStat(ITEM_STAT_DMG_SLASH);
- if (stat_found)
- should_add = true;
- break;
- }
- case ITEM_BROKER_STAT_TYPE_PIERCE:{
- stat_found = item->HasStat(ITEM_STAT_DMG_PIERCE);
- if (stat_found)
- should_add = true;
- break;
- }
- case ITEM_BROKER_STAT_TYPE_CRITICAL: {
- stat_found = item->HasStat(ITEM_STAT_CRITICALMITIGATION);
- if (stat_found)
- should_add = true;
- break;
- }
- case ITEM_BROKER_STAT_TYPE_DBL_ATTACK:{
- stat_found = item->HasStat(ITEM_STAT_MULTIATTACKCHANCE);
- if (stat_found)
- should_add = true;
- break;
- }
- case ITEM_BROKER_STAT_TYPE_ABILITY_MOD:{
- stat_found = item->HasStat(ITEM_STAT_ABILITY_MODIFIER);
- if (stat_found)
- should_add = true;
- break;
- }
- case ITEM_BROKER_STAT_TYPE_POTENCY:{
- stat_found = item->HasStat(ITEM_STAT_POTENCY);
- if (stat_found)
- should_add = true;
- break;
- }
- case ITEM_BROKER_STAT_TYPE_AEAUTOATTACK:{
- stat_found = item->HasStat(ITEM_STAT_AEAUTOATTACKCHANCE);
- if (stat_found)
- should_add = true;
- break;
- }
- case ITEM_BROKER_STAT_TYPE_ATTACKSPEED:{
- stat_found = item->HasStat(ITEM_STAT_ATTACKSPEED);
- if (stat_found)
- should_add = true;
- break;
- }
- case ITEM_BROKER_STAT_TYPE_BLOCKCHANCE:{
- stat_found = item->HasStat(ITEM_STAT_EXTRASHIELDBLOCKCHANCE);
- if (stat_found)
- should_add = true;
- break;
- }
- case ITEM_BROKER_STAT_TYPE_CASTINGSPEED:{
- stat_found = item->HasStat(ITEM_STAT_ABILITYCASTINGSPEED);
- if (stat_found)
- should_add = true;
- break;
- }
- case ITEM_BROKER_STAT_TYPE_CRITBONUS:{
- stat_found = item->HasStat(ITEM_STAT_CRITBONUS);
- if (stat_found)
- should_add = true;
- break;
- }
- case ITEM_BROKER_STAT_TYPE_CRITCHANCE:{
- stat_found = item->HasStat(ITEM_STAT_MELEECRITCHANCE);
- if (stat_found)
- should_add = true;
- break;
- }
- case ITEM_BROKER_STAT_TYPE_DPS:{
- stat_found = item->HasStat(ITEM_STAT_DPS);
- if (stat_found)
- should_add = true;
- break;
- }
- case ITEM_BROKER_STAT_TYPE_FLURRYCHANCE:{
- stat_found = item->HasStat(ITEM_STAT_FLURRY);
- if (stat_found)
- should_add = true;
- break;
- }
- case ITEM_BROKER_STAT_TYPE_HATEGAIN:{
- stat_found = item->HasStat(ITEM_STAT_HATEGAINMOD);
- if (stat_found)
- should_add = true;
- break;
- }
- case ITEM_BROKER_STAT_TYPE_MITIGATION:{
- stat_found = item->HasStat(ITEM_STAT_ARMORMITIGATIONINCREASE);
- if (stat_found)
- should_add = true;
- break;
- }
- case ITEM_BROKER_STAT_TYPE_MULTI_ATTACK:{
- stat_found = item->HasStat(ITEM_STAT_MULTIATTACKCHANCE);
- if (stat_found)
- should_add = true;
- break;
- }
- case ITEM_BROKER_STAT_TYPE_RECOVERY:{
- stat_found = item->HasStat(ITEM_STAT_ABILITYRECOVERYSPEED);
- if (stat_found)
- should_add = true;
- break;
- }
- case ITEM_BROKER_STAT_TYPE_REUSE_SPEED:{
- stat_found = item->HasStat(ITEM_STAT_ABILITYREUSESPEED);
- if (stat_found)
- should_add = true;
- break;
- }
- case ITEM_BROKER_STAT_TYPE_SPELL_WPNDMG:{
- stat_found = item->HasStat(ITEM_STAT_SPELLWEAPONDAMAGEBONUS);
- if (stat_found)
- should_add = true;
- break;
- }
- case ITEM_BROKER_STAT_TYPE_STRIKETHROUGH:{
- stat_found = item->HasStat(ITEM_STAT_STRIKETHROUGH);
- if (stat_found)
- should_add = true;
- break;
- }
- case ITEM_BROKER_STAT_TYPE_TOUGHNESS:{
- stat_found = item->HasStat(ITEM_STAT_PVPTOUGHNESS);
- if (stat_found)
- should_add = true;
- break;
- }
- case ITEM_BROKER_STAT_TYPE_WEAPONDMG:{
- stat_found = item->HasStat(ITEM_STAT_WEAPONDAMAGEBONUS);
- if (stat_found)
- should_add = true;
- break;
- }
- default: {
- LogWrite(ITEM__DEBUG, 0, "Item", "Unknown item broker stat type %u", btype);
- LogWrite(ITEM__DEBUG, 0, "Item", "If you have a client before the new expansion this may be the reason. Please be patient while we update items to support the new client.", btype);
- break;
- }
- }
+ should_add = ShouldAddItemBrokerStat(item, btype);
if (!should_add)
continue;
}
@@ -775,18 +798,16 @@ vector
- * MasterItemList::GetItems(map criteria, Client* cl
btype = itr->second[btype];
}
}
- return GetItems(name, itype, ltype, btype, minprice, maxprice, minskill, maxskill, seller, adornment, mintier, maxtier, minlevel, maxlevel, itemclass);
+ if(client_to_map && client_to_map->IsGMStoreSearch()) {
+ return GetItems(name, itype, ltype, btype, minprice, maxprice, minskill, maxskill, seller, adornment, mintier, maxtier, minlevel, maxlevel, itemclass);
+ }
+ else {
+ return broker.GetItems(name, itype, ltype, btype, minprice, maxprice, minskill, maxskill, seller, adornment, mintier, maxtier, minlevel, maxlevel, itemclass);
+ }
}
-void MasterItemList::ResetUniqueID(int32 new_id){
- next_unique_id = new_id;
-}
-
-int32 MasterItemList::NextUniqueID(){
- next_unique_id++;
- if(next_unique_id >= 0xFFFFFFF0)
- next_unique_id = 1;
- return next_unique_id;
+int64 MasterItemList::NextUniqueID(){
+ return database.LoadNextUniqueItemID();
}
bool MasterItemList::IsBag(int32 item_id){
@@ -888,7 +909,11 @@ void MasterItemList::AddItem(Item* item){
}
Item::Item(){
+ seller_char_id = 0;
+ seller_house_id = 0;
+ is_search_store_item = false;
item_script = "";
+ broker_price = 0;
sell_price = 0;
sell_status = 0;
max_sell_value = 0;
@@ -920,7 +945,11 @@ Item::Item(){
}
Item::Item(Item* in_item){
+ seller_char_id = 0;
+ seller_house_id = 0;
+ is_search_store_item = false;
needs_deletion = false;
+ broker_price = 0;
sell_price = in_item->sell_price;
sell_status = in_item->sell_status;
max_sell_value = in_item->max_sell_value;
@@ -938,6 +967,38 @@ Item::Item(Item* in_item){
grouped_char_ids.insert(in_item->grouped_char_ids.begin(), in_item->grouped_char_ids.end());
effect_type = in_item->effect_type;
book_language = in_item->book_language;
+ details.lock_flags = 0;
+ details.item_locked = false;
+}
+
+Item::Item(Item* in_item, int64 unique_id, std::string in_creator, std::string in_seller_name, int32 in_seller_char_id, int64 in_broker_price, int16 count, int64 in_seller_house_id){
+ is_search_store_item = true;
+ broker_price = in_broker_price;
+ needs_deletion = false;
+ sell_price = in_item->sell_price;
+ sell_status = in_item->sell_status;
+ max_sell_value = in_item->max_sell_value;
+ save_needed = false;
+ SetItem(in_item);
+ details.unique_id = unique_id;
+ if (IsBag())
+ details.bag_id = details.unique_id;
+ generic_info.condition = 100;
+ spell_id = in_item->spell_id;
+ spell_tier = in_item->spell_tier;
+ no_buy_back = in_item->no_buy_back;
+ no_sale = in_item->no_sale;
+ created = in_item->created;
+ grouped_char_ids.insert(in_item->grouped_char_ids.begin(), in_item->grouped_char_ids.end());
+ effect_type = in_item->effect_type;
+ book_language = in_item->book_language;
+ creator = in_creator;
+ seller_name = in_seller_name;
+ seller_char_id = in_seller_char_id;
+ details.count = count;
+ seller_house_id = in_seller_house_id;
+ details.lock_flags = 0;
+ details.item_locked = false;
}
Item::~Item(){
@@ -1029,7 +1090,7 @@ void Item::SetItem(Item* old_item){
memcpy(bauble_info, old_item->bauble_info, sizeof(Bauble_Info));
break;
}
- case ITEM_TYPE_SKILL:{
+ case ITEM_TYPE_SKILL:{
skill_info = new Skill_Info;
memcpy(skill_info, old_item->skill_info, sizeof(Skill_Info));
break;
@@ -1074,6 +1135,12 @@ void Item::SetItem(Item* old_item){
case ITEM_TYPE_HOUSE_CONTAINER:{
houseitem_info = new HouseItem_Info;
memset(houseitem_info, 0, sizeof(HouseItem_Info));
+ bag_info = new Bag_Info;
+ memset(bag_info, 0, sizeof(Bag_Info));
+
+ if(old_item->bag_info)
+ memcpy(bag_info, old_item->bag_info, sizeof(Bag_Info));
+
if(old_item->houseitem_info) {
memcpy(houseitem_info, old_item->houseitem_info, sizeof(HouseItem_Info));
}
@@ -1346,7 +1413,7 @@ bool Item::IsRanged(){
}
bool Item::IsBag(){
- return generic_info.item_type == ITEM_TYPE_BAG;
+ return generic_info.item_type == ITEM_TYPE_BAG || generic_info.item_type == ITEM_TYPE_HOUSE_CONTAINER;
}
bool Item::IsFood(){
@@ -1547,7 +1614,7 @@ void Item::SetItemType(int8 in_type){
ranged_info = new Ranged_Info;
memset(ranged_info, 0, sizeof(Ranged_Info));
}
- else if(IsBag() && !bag_info){
+ else if(IsBag() && !IsHouseContainer() && !bag_info){
bag_info = new Bag_Info;
memset(bag_info, 0, sizeof(Bag_Info));
}
@@ -1578,11 +1645,14 @@ void Item::SetItemType(int8 in_type){
book_info->author.size = 0;
book_info->title.size = 0;
}
- else if(IsHouseItem() && !houseitem_info){
+ else if(IsHouseItem() && !IsHouseContainer() && !houseitem_info){
houseitem_info = new HouseItem_Info;
memset(houseitem_info, 0, sizeof(HouseItem_Info));
}
else if(IsHouseContainer() && !housecontainer_info){
+ bag_info = new Bag_Info;
+ memset(bag_info, 0, sizeof(Bag_Info));
+
if(!houseitem_info) {
houseitem_info = new HouseItem_Info;
memset(houseitem_info, 0, sizeof(HouseItem_Info));
@@ -2515,7 +2585,58 @@ void Item::serialize(PacketStruct* packet, bool show_name, Player* player, int16
packet->setDataByName("broker_commission", housecontainer_info->broker_commission);
packet->setDataByName("fence_commission", housecontainer_info->fence_commission);
}
- }
+ if(bag_info){
+ int8 max_slots = player->GetMaxBagSlots(client->GetVersion());
+ if (bag_info->num_slots > max_slots)
+ bag_info->num_slots = max_slots;
+
+ int16 free_slots = bag_info->num_slots;
+ if (player) {
+ Item* bag = player->GetPlayerItemList()->GetItemFromUniqueID(details.unique_id, true);
+ if (bag && bag->IsBag()) {
+ vector
- * bag_items = player->GetPlayerItemList()->GetItemsInBag(bag);
+ if (bag_items->size() > bag->bag_info->num_slots) {
+ free_slots = 0;
+ packet->setArrayLengthByName("num_names", bag->bag_info->num_slots);
+ }
+ else {
+ free_slots = bag->bag_info->num_slots - bag_items->size();
+ packet->setArrayLengthByName("num_names", bag_items->size());
+ }
+ vector
- ::iterator itr;
+ int16 i = 0;
+ Item* tmp_bag_item = 0;
+ for (itr = bag_items->begin(); itr != bag_items->end(); itr++) {
+ tmp_bag_item = *itr;
+ if (tmp_bag_item && tmp_bag_item->details.slot_id < bag->bag_info->num_slots) {
+ packet->setArrayDataByName("item_name", tmp_bag_item->name.c_str(), i);
+ i++;
+ }
+ }
+ safe_delete(bag_items);
+ }
+ }
+ packet->setDataByName("num_slots", bag_info->num_slots);
+ packet->setDataByName("num_empty", free_slots);
+ packet->setDataByName("weight_reduction", bag_info->weight_reduction);
+ packet->setDataByName("item_score", 2);
+ //packet->setDataByName("unknown5", 0x1e50a86f);
+ //packet->setDataByName("unknown6", 0x2c17f61d);
+ //1 armorer
+ //2 weaponsmith
+ //4 tailor
+ //16 jeweler
+ //32 sage
+ //64 alchemist
+ //120 all scholars
+ //250 all craftsman
+ //int8 blah[] = {0x00,0x00,0x01,0x01,0xb6,0x01,0x01};
+ //int8 blah[] = {0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
+ int8 blah[] = { 0xd8,0x66,0x9b,0x6d,0xb6,0xfb,0x7f };
+ for (int8 i = 0; i < sizeof(blah); i++)
+ packet->setSubstructDataByName("footer", "footer_unknown_0", blah[i], 0, i);
+ }
+ }
}
}
@@ -3040,8 +3161,8 @@ Item* PlayerItemList::GetBankBag(int8 inventory_slot, bool lock){
Item* bag = 0;
if(lock)
MPlayerItems.readlock(__FUNCTION__, __LINE__);
- if(items.count(-3) > 0 && items[-3][BASE_EQUIPMENT].count(inventory_slot) > 0 && items[-3][BASE_EQUIPMENT][inventory_slot]->IsBag())
- bag = items[-3][BASE_EQUIPMENT][inventory_slot];
+ if(items.count(InventorySlotType::BANK) > 0 && items[InventorySlotType::BANK][BASE_EQUIPMENT].count(inventory_slot) > 0 && items[InventorySlotType::BANK][BASE_EQUIPMENT][inventory_slot]->IsBag())
+ bag = items[InventorySlotType::BANK][BASE_EQUIPMENT][inventory_slot];
if(lock)
MPlayerItems.releasereadlock(__FUNCTION__, __LINE__);
return bag;
@@ -3129,10 +3250,10 @@ bool PlayerItemList::HasFreeSlot(){
bool PlayerItemList::GetFirstFreeBankSlot(sint32* bag_id, sint16* slot) {
bool ret = false;
MPlayerItems.readlock(__FUNCTION__, __LINE__);
- if (items.count(-3) > 0) {
+ if (items.count(InventorySlotType::BANK) > 0) {
for (int8 i = 0; i < NUM_BANK_SLOTS; i++) {
- if (items[-3][BASE_EQUIPMENT].count(i) == 0) {
- *bag_id = -3;
+ if (items[InventorySlotType::BANK][BASE_EQUIPMENT].count(i) == 0) {
+ *bag_id = InventorySlotType::BANK;
*slot = i;
ret = true;
break;
@@ -3140,7 +3261,7 @@ bool PlayerItemList::GetFirstFreeBankSlot(sint32* bag_id, sint16* slot) {
}
}
else {
- *bag_id = -3;
+ *bag_id = InventorySlotType::BANK;
*slot = 0;
ret = true;
}
@@ -3299,9 +3420,13 @@ void PlayerItemList::Stack(Item* orig_item, Item* item){
orig_item->save_needed = true;
}
-bool PlayerItemList::AssignItemToFreeSlot(Item* item){
+bool PlayerItemList::AssignItemToFreeSlot(Item* item, bool inventory_only){
if(item){
- Item* orig_item = CanStack(item);
+ Item* orig_item = CanStack(item, !inventory_only);
+
+ if(inventory_only && !IsItemInSlotType(orig_item, InventorySlotType::BASE_INVENTORY)){
+ orig_item = nullptr;
+ }
if(orig_item){
Stack(orig_item, item);
return true;
@@ -3347,8 +3472,9 @@ bool PlayerItemList::AssignItemToFreeSlot(Item* item){
}
-void PlayerItemList::RemoveItem(Item* item, bool delete_item){
- MPlayerItems.writelock(__FUNCTION__, __LINE__);
+void PlayerItemList::RemoveItem(Item* item, bool delete_item, bool lock){
+ if(lock)
+ MPlayerItems.writelock(__FUNCTION__, __LINE__);
if(items.count(item->details.inv_slot_id) > 0 && items[item->details.inv_slot_id][item->details.appearance_type].count(item->details.slot_id) > 0){
items[item->details.inv_slot_id][item->details.appearance_type].erase(item->details.slot_id);
indexed_items[item->details.index] = 0;
@@ -3375,7 +3501,8 @@ void PlayerItemList::RemoveItem(Item* item, bool delete_item){
lua_interface->SetLuaUserDataStale(item);
safe_delete(item);
}
- MPlayerItems.releasewritelock(__FUNCTION__, __LINE__);
+ if(lock)
+ MPlayerItems.releasewritelock(__FUNCTION__, __LINE__);
}
void PlayerItemList::DestroyItem(int16 index){
@@ -3446,7 +3573,9 @@ int32 PlayerItemList::GetWeight(){
for(int16 i = 0; i < indexed_items.size(); i++){
Item* item = indexed_items[i];
if (item) {
- if(item->details.inv_slot_id != -3 && item->details.inv_slot_id != -4)
+ if(!IsItemInSlotType(item, InventorySlotType::BANK, false) &&
+ !IsItemInSlotType(item, InventorySlotType::SHARED_BANK, false) &&
+ !IsItemInSlotType(item, InventorySlotType::HOUSE_VAULT, false))
ret += item->generic_info.weight;
}
}
@@ -3454,15 +3583,27 @@ int32 PlayerItemList::GetWeight(){
return ret;
}
+bool PlayerItemList::IsItemInSlotType(Item* item, InventorySlotType type, bool lockItems) {
+ if(!item)
+ return false;
+
+ bool matchType = (item->details.inv_slot_id == type);
+ if(item->details.inv_slot_id > 0) {
+ Item* bagItem = GetItemFromUniqueID(item->details.inv_slot_id, true, lockItems);
+ if(bagItem && bagItem->details.inv_slot_id == type)
+ matchType = true;
+ }
+ return matchType;
+}
bool PlayerItemList::MoveItem(sint32 to_bag_id, int16 from_index, sint8 to, int8 appearance_type, int8 charges){
MPlayerItems.writelock(__FUNCTION__, __LINE__);
Item* item_from = indexed_items[from_index];
Item* item_to = 0;
- if(item_from){
+ if(item_from && !item_from->IsItemLocked()){
if(to_bag_id > 0){ //bag item
Item* bag = GetItemFromUniqueID(to_bag_id, true, false);
- if(bag && bag->details.num_slots > to && (!item_from || !item_from->IsBag()))
+ if(bag && !bag->IsItemLocked() && bag->details.num_slots > to && (!item_from || !item_from->IsBag()))
item_to = items[to_bag_id][BASE_EQUIPMENT][to];
else{
MPlayerItems.releasewritelock(__FUNCTION__, __LINE__);
@@ -3476,6 +3617,14 @@ bool PlayerItemList::MoveItem(sint32 to_bag_id, int16 from_index, sint8 to, int8
return false;
}
}
+
+ LogWrite(PLAYER__ERROR, 0, "MoveItem",
+ "--Item: %u is locked %u", item_to ? item_to->details.unique_id : 0, item_to ? item_to->IsItemLocked() : 0
+ );
+ if(item_to && item_to->IsItemLocked()) {
+ MPlayerItems.releasewritelock(__FUNCTION__, __LINE__);
+ return false;
+ }
if(charges > 0) {
if (item_to && item_from->details.item_id == item_to->details.item_id){
if(item_to->details.count > 0 && item_to->details.count < item_to->stack_count){
@@ -3554,14 +3703,23 @@ bool PlayerItemList::MoveItem(sint32 to_bag_id, int16 from_index, sint8 to, int8
return true;
}
}
+
+ bool canMove = true;
+ if(item_to && item_to->IsItemLocked())
+ canMove = false;
+
MPlayerItems.releasewritelock(__FUNCTION__, __LINE__);
- if (item_to)
+ LogWrite(PLAYER__ERROR, 0, "MoveItem",
+ "--Item#2: %u is locked %u", item_to ? item_to->details.unique_id : 0, item_to ? item_to->IsItemLocked() : 0
+ );
+ if (item_to && canMove)
MoveItem(item_to, item_from->details.inv_slot_id, item_from->details.slot_id, BASE_EQUIPMENT, true);
- MoveItem(item_from, to_bag_id, to, BASE_EQUIPMENT, item_to ? false:true);
+ if(canMove)
+ MoveItem(item_from, to_bag_id, to, BASE_EQUIPMENT, item_to ? false:true);
- return true;
+ return canMove;
}
MPlayerItems.releasewritelock(__FUNCTION__, __LINE__);
return false;
@@ -3751,7 +3909,7 @@ void PlayerItemList::AddItemToPacket(PacketStruct* packet, Player* player, Item*
//TODO: Add check to allow scribe
menu_data += ITEM_MENU_TYPE_SCRIBE;
}
- if (item->generic_info.item_type == ITEM_TYPE_HOUSE || item->generic_info.item_type == ITEM_TYPE_HOUSE_CONTAINER){
+ if (item->generic_info.item_type == ITEM_TYPE_HOUSE || (item->generic_info.item_type == ITEM_TYPE_HOUSE_CONTAINER && item->details.inv_slot_id == InventorySlotType::HOUSE_VAULT)){ // containers must be in base house slot for placement
menu_data += ITEM_MENU_TYPE_TEST1;
menu_data += ITEM_MENU_TYPE_HOUSE;
}
@@ -3800,7 +3958,7 @@ void PlayerItemList::AddItemToPacket(PacketStruct* packet, Player* player, Item*
menu_data += ORIG_ITEM_MENU_TYPE_FOOD;
}
}
- if(item->details.item_locked) {
+ if(item->IsItemLocked()) {
menu_data += ITEM_MENU_TYPE_BROKEN; // broken is also used to lock item during crafting
}
// Added the if (overflow) so mouseover examines work properly
@@ -3862,7 +4020,7 @@ bool PlayerItemList::AddOverflowItem(Item* item) {
MPlayerItems.writelock(__FUNCTION__, __LINE__);
if (item && item->details.item_id > 0 && overflowItems.size() < 255) {
item->details.slot_id = 6;
- item->details.inv_slot_id = -2;
+ item->details.inv_slot_id = InventorySlotType::OVERFLOW;
overflowItems.push_back(item);
ret = true;
}
@@ -3897,11 +4055,18 @@ vector
- * PlayerItemList::GetOverflowItemList() {
}
bool PlayerItemList::HasItem(int32 id, bool include_bank){
+ if(include_bank) {
+ Item* item = GetItemFromID(id, 1, true, true);
+ if(item)
+ return true;
+ else
+ return false;
+ }
map> >::iterator itr;
map::iterator slot_itr;
MPlayerItems.readlock(__FUNCTION__, __LINE__);
for(itr = items.begin(); itr != items.end(); itr++){
- if(include_bank || (!include_bank && itr->first >= 0)){
+ if(itr->first >= 0){
for(slot_itr=itr->second[BASE_EQUIPMENT].begin();slot_itr!=itr->second[BASE_EQUIPMENT].end(); slot_itr++){
if(slot_itr->second && slot_itr->second->details.item_id == id){
MPlayerItems.releasereadlock(__FUNCTION__, __LINE__);
@@ -4082,10 +4247,245 @@ Item* PlayerItemList::GetItemFromUniqueID(int32 id, bool include_bank, bool lock
return 0;
}
+void PlayerItemList::SetVaultItemLockUniqueID(Client* client, int64 id, bool state, bool lock) {
+ if (lock) {
+ MPlayerItems.readlock(__FUNCTION__, __LINE__);
+ }
+ sint32 inv_slot_id = 0;
+ Item* item = client->GetPlayer()->item_list.GetVaultItemFromUniqueID(id, false);
+ if(item) {
+ bool bag_remains_locked = true;
+ if(state && !item->TryLockItem(LockReason::LockReason_Shop)) {
+ // this shouldn't happen, but if we have a conflict it might
+ client->Message(CHANNEL_COLOR_RED, "Failed to lock item %u for vault.", id);
+ return;
+ }
+ else if(!state && !item->TryUnlockItem(LockReason::LockReason_Shop)) {
+ // still in use for another reason, we don't need to report to the user it will spam them
+ }
+ if(item->details.inv_slot_id) {
+ Item* bagItem = client->GetPlayer()->item_list.GetVaultItemFromUniqueID(item->details.inv_slot_id, false);
+ if(bagItem) {
+ inv_slot_id = item->details.inv_slot_id;
+ if(!state) {
+ bag_remains_locked = false;
+ if (auto bagIt = items.find(item->details.inv_slot_id);
+ bagIt != items.end())
+ {
+ const auto& bagSlots = bagIt->second.at(BASE_EQUIPMENT);
+ for (auto& [bagSlot, bagItem] : bagSlots) {
+ if (bagItem && bagItem->IsItemLocked()) {
+ bag_remains_locked = true;
+ break;
+ }
+ }
+ }
+ }
+ if(!bag_remains_locked) {
+ bagItem->TryUnlockItem(LockReason::LockReason_Shop);
+ }
+ else {
+ bagItem->TryLockItem(LockReason::LockReason_Shop);
+ }
+ }
+ }
+ }
+ if (lock) {
+ MPlayerItems.releasereadlock(__FUNCTION__, __LINE__);
+ }
+ if(inv_slot_id) {
+ client->GetPlayer()->UpdateInventory(item->details.inv_slot_id);
+ }
+ else {
+ EQ2Packet* outapp = client->GetPlayer()->SendInventoryUpdate(client->GetVersion());
+ client->QueuePacket(outapp);
+ }
+}
+
+bool PlayerItemList::CanStoreSellItem(int64 unique_id, bool lock) {
+ if (lock) {
+ MPlayerItems.readlock(__FUNCTION__, __LINE__);
+ }
+ Item* item = GetVaultItemFromUniqueID(unique_id, false);
+ if(!item || (item->CheckFlag(NO_TRADE) && (item->CheckFlag2(HEIRLOOM) == 0))) {
+ if(lock)
+ MPlayerItems.releasereadlock(__FUNCTION__, __LINE__);
+ return false;
+ }
+ if(item->CheckFlag(ATTUNED)) {
+ if(lock)
+ MPlayerItems.releasereadlock(__FUNCTION__, __LINE__);
+ return false;
+ }
+
+ if(item->IsBag() && items.count(item->details.bag_id) > 0){
+ map::iterator itr;
+ for(itr = items[item->details.bag_id][BASE_EQUIPMENT].begin(); itr != items[item->details.bag_id][BASE_EQUIPMENT].end(); itr++){
+ if(itr->second->CheckFlag(NO_TRADE) && itr->second->CheckFlag2(HEIRLOOM) == 0){
+ if(lock)
+ MPlayerItems.releasereadlock(__FUNCTION__, __LINE__);
+ return false;
+ }
+ if(item->CheckFlag(ATTUNED)) {
+ if(lock)
+ MPlayerItems.releasereadlock(__FUNCTION__, __LINE__);
+ return false;
+ }
+ }
+ }
+ if(lock) {
+ MPlayerItems.releasereadlock(__FUNCTION__, __LINE__);
+ }
+ return true;
+}
+
+void PlayerItemList::SetVaultItemUniqueIDCount(Client* client, int64 unique_id, int16 count, bool lock) {
+ if (lock) {
+ MPlayerItems.readlock(__FUNCTION__, __LINE__);
+ }
+
+ sint32 inv_slot_id = 0;
+ bool countUpdated = false;
+ Item* item = GetVaultItemFromUniqueID(unique_id, false);
+ if(item) {
+ item->details.count = count;
+ item->save_needed = true;
+ inv_slot_id = item->details.inv_slot_id;
+ countUpdated = true;
+ }
+ if (lock) {
+ MPlayerItems.releasereadlock(__FUNCTION__, __LINE__);
+ }
+
+ if(client && client->GetPlayer() && countUpdated) {
+ if(inv_slot_id) {
+ client->GetPlayer()->UpdateInventory(inv_slot_id);
+ }
+ else {
+ EQ2Packet* outapp = client->GetPlayer()->SendInventoryUpdate(client->GetVersion());
+ client->QueuePacket(outapp);
+ }
+ }
+}
+
+
+void PlayerItemList::RemoveVaultItemFromUniqueID(Client* client, int64 unique_id, bool lock) {
+ if (lock) {
+ MPlayerItems.writelock(__FUNCTION__, __LINE__);
+ }
+
+ sint32 inv_slot_id = 0;
+ bool foundItem = false;
+ Item* item = GetVaultItemFromUniqueID(unique_id, false);
+ if(item) {
+ inv_slot_id = item->details.inv_slot_id;
+ RemoveItem(item, true, false);
+ foundItem = true;
+ }
+ if (lock) {
+ MPlayerItems.releasewritelock(__FUNCTION__, __LINE__);
+ }
+
+ if(client && client->GetPlayer() && foundItem) {
+ if(inv_slot_id) {
+ client->GetPlayer()->UpdateInventory(inv_slot_id);
+ }
+ else {
+ EQ2Packet* outapp = client->GetPlayer()->SendInventoryUpdate(client->GetVersion());
+ client->QueuePacket(outapp);
+ }
+ }
+}
+
+Item* PlayerItemList::GetVaultItemFromUniqueID(int64 id, bool lock) {
+ if (lock) {
+ MPlayerItems.readlock(__FUNCTION__, __LINE__);
+ }
+
+ // 1) Check the house vault
+ if (auto vaultIt = items.find(InventorySlotType::HOUSE_VAULT);
+ vaultIt != items.end())
+ {
+ for (auto& [containerIdx, slotMap] : vaultIt->second) {
+ for (auto& [slotID, itemPtr] : slotMap) {
+ if(itemPtr) {
+ LogWrite(PLAYER__ERROR, 0, "Vault",
+ "--GetVaultItem: %u (%s - %u) needs to match %u", slotID, itemPtr->name.c_str(), itemPtr->details.unique_id, id
+ );
+ }
+ else {
+ LogWrite(PLAYER__ERROR, 0, "Vault",
+ "--GetVaultItem: %u (??) needs to match %u", slotID, id
+ );
+ }
+ if (itemPtr && itemPtr->details.unique_id == id) {
+ if (lock) MPlayerItems.releasereadlock(__FUNCTION__, __LINE__);
+ return itemPtr;
+ }
+
+ // If it's a bag, search its contents
+ if (!itemPtr->IsBag())
+ continue;
+
+ if (auto bagIt = items.find(itemPtr->details.bag_id);
+ bagIt != items.end())
+ {
+ const auto& bagSlots = bagIt->second.at(BASE_EQUIPMENT);
+ for (auto& [bagSlot, bagItem] : bagSlots) {
+ if (bagItem && bagItem->details.unique_id == id) {
+ if (lock) MPlayerItems.releasereadlock(__FUNCTION__, __LINE__);
+ return bagItem;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // 2) Check base inventory slots and any bags inside them
+ const auto& baseEquip = items
+ .at(InventorySlotType::BASE_INVENTORY)
+ .at(BASE_EQUIPMENT);
+
+ for (int8 slotIdx = 0; slotIdx < NUM_INV_SLOTS; ++slotIdx) {
+ auto it = baseEquip.find(slotIdx);
+ if (it == baseEquip.end() || it->second == nullptr)
+ continue;
+
+ Item* curr = it->second;
+ // Direct match in base slot
+ if (curr->details.unique_id == id) {
+ if (lock) MPlayerItems.releasereadlock(__FUNCTION__, __LINE__);
+ return curr;
+ }
+
+ // If it's a bag, search its contents
+ if (!curr->IsBag())
+ continue;
+
+ if (auto bagIt = items.find(curr->details.bag_id);
+ bagIt != items.end())
+ {
+ const auto& bagSlots = bagIt->second.at(BASE_EQUIPMENT);
+ for (auto& [bagSlot, bagItem] : bagSlots) {
+ if (bagItem && bagItem->details.unique_id == id) {
+ if (lock) MPlayerItems.releasereadlock(__FUNCTION__, __LINE__);
+ return bagItem;
+ }
+ }
+ }
+ }
+
+ if (lock) {
+ MPlayerItems.releasereadlock(__FUNCTION__, __LINE__);
+ }
+ return nullptr;
+}
+
bool PlayerItemList::HasFreeBankSlot() {
bool ret = false;
MPlayerItems.readlock(__FUNCTION__, __LINE__);
- if (items[-3][BASE_EQUIPMENT].size() < 12) //12 slots in the bank
+ if (items[InventorySlotType::BANK][BASE_EQUIPMENT].size() < 12) //12 slots in the bank
ret = true;
MPlayerItems.releasereadlock(__FUNCTION__, __LINE__);
return ret;
@@ -4095,7 +4495,7 @@ int8 PlayerItemList::FindFreeBankSlot() {
int8 ret = 0;
MPlayerItems.readlock(__FUNCTION__, __LINE__);
for (int8 i = 0; i < 12; i++) { //12 slots in the bank
- if (items[-3][BASE_EQUIPMENT].count(i) == 0) {
+ if (items[InventorySlotType::BANK][BASE_EQUIPMENT].count(i) == 0) {
ret = i;
break;
}
@@ -4104,6 +4504,154 @@ int8 PlayerItemList::FindFreeBankSlot() {
return ret;
}
+void PlayerItemList::PopulateHouseStoragePacket(Client* client, PacketStruct* packet, Item* item, int16 itemIdx, int8 storage_flags) {
+ int64 cost = broker.GetSalePrice(client->GetPlayer()->GetCharacterID(), item->details.unique_id);
+ bool sale = broker.IsItemForSale(client->GetPlayer()->GetCharacterID(), item->details.unique_id);
+ bool isInv = !IsItemInSlotType(item, InventorySlotType::HOUSE_VAULT, false);
+ client->AddItemSale(item->details.unique_id, item->details.item_id, cost, item->details.inv_slot_id, item->details.slot_id, item->details.count, isInv, sale, item->creator);
+
+ if(broker.IsItemForSale(client->GetPlayer()->GetCharacterID(), item->details.unique_id))
+ storage_flags += HouseStoreItemFlags::HOUSE_STORE_FOR_SALE;
+
+ LogWrite(PLAYER__ERROR, 5, "Broker",
+ "--Sell broker item: %u (%s - %u), cost=%u",
+ client->GetPlayer()->GetCharacterID(), item->name.c_str(), item->details.unique_id, cost
+ );
+
+ packet->setArrayDataByName("your_item_name", item->name.c_str(), itemIdx);
+ packet->setArrayDataByName("unique_id", item->details.item_id, itemIdx);
+ packet->setArrayDataByName("unique_id2", item->details.unique_id, itemIdx);
+ packet->setArrayDataByName("cost", cost, itemIdx);
+ packet->setArrayDataByName("your_item_quantity", item->details.count, itemIdx);
+ packet->setArrayDataByName("your_item_icon", item->GetIcon(packet->GetVersion()), itemIdx);
+ packet->setArrayDataByName("storage_flags", storage_flags, itemIdx);
+}
+
+void PlayerItemList::GetVaultItems(Client* client, int32 spawn_id, int8 maxSlots, bool isSelling) {
+ int8 ret = 0;
+ int8 numItems = 0;
+ MPlayerItems.readlock(__FUNCTION__, __LINE__);
+ for (int8 i = 0; i < maxSlots; i++) {
+ if (items[InventorySlotType::HOUSE_VAULT][BASE_EQUIPMENT].count(i) != 0) {
+ Item* item = items[InventorySlotType::HOUSE_VAULT][BASE_EQUIPMENT][i];
+ if(item) {
+ if(!item->IsBag()) {
+ numItems++;
+ }
+ else {
+ bool bagHasItem = false;
+ if(items.count(item->details.bag_id) > 0){
+ map::iterator itr;
+ for(itr = items[item->details.bag_id][BASE_EQUIPMENT].begin(); itr != items[item->details.bag_id][BASE_EQUIPMENT].end(); itr++){
+ if(itr->second) {
+ numItems++;
+ bagHasItem = true;
+ }
+ }
+ }
+ if(!bagHasItem) {
+ numItems++;
+ }
+ }
+ }
+ }
+ }
+ for (int8 i = 0; i < NUM_INV_SLOTS; i++) {
+ if (items[InventorySlotType::BASE_INVENTORY][BASE_EQUIPMENT].count(i) != 0) {
+ Item* item = items[InventorySlotType::BASE_INVENTORY][BASE_EQUIPMENT][i];
+ if(item) {
+ if(!item->IsBag()) {
+ numItems++;
+ }
+ else {
+ bool bagHasItem = false;
+ if(items.count(item->details.bag_id) > 0){
+ map::iterator itr;
+ for(itr = items[item->details.bag_id][BASE_EQUIPMENT].begin(); itr != items[item->details.bag_id][BASE_EQUIPMENT].end(); itr++){
+ if(itr->second) {
+ numItems++;
+ bagHasItem = true;
+ }
+ }
+ }
+ if(!bagHasItem) {
+ numItems++;
+ }
+ }
+ }
+ }
+ }
+
+ PacketStruct* packet = configReader.getStruct("WS_HouseStorage", client->GetVersion());
+ if (packet) {
+ packet->setDataByName("spawn_id", spawn_id);
+ packet->setDataByName("type", isSelling ? 6 : 4);
+ packet->setArrayLengthByName("your_item_count", numItems);
+ int16 itemIdx = 0;
+ for (int8 i = 0; i < maxSlots; i++) {
+ if (items[InventorySlotType::HOUSE_VAULT][BASE_EQUIPMENT].count(i) != 0) {
+ Item* item = items[InventorySlotType::HOUSE_VAULT][BASE_EQUIPMENT][i];
+ if(item) {
+ if(!item->IsBag()) {
+ PopulateHouseStoragePacket(client, packet, item, itemIdx, HouseStoreItemFlags::HOUSE_STORE_VAULT_TAB);
+ itemIdx++;
+ }
+ else {
+ bool bagHasItem = false;
+ if(items.count(item->details.bag_id) > 0){
+ map::iterator itr;
+ for(itr = items[item->details.bag_id][BASE_EQUIPMENT].begin(); itr != items[item->details.bag_id][BASE_EQUIPMENT].end(); itr++){
+ if(itr->second) {
+ PopulateHouseStoragePacket(client, packet, itr->second, itemIdx, HouseStoreItemFlags::HOUSE_STORE_VAULT_TAB);
+ bagHasItem = true;
+ itemIdx++;
+ }
+ }
+ }
+ if(!bagHasItem) {
+ PopulateHouseStoragePacket(client, packet, item, itemIdx, HouseStoreItemFlags::HOUSE_STORE_VAULT_TAB);
+ itemIdx++;
+ }
+ }
+ }
+ }
+ }
+
+ for (int8 i = 0; i < NUM_INV_SLOTS; i++) {
+ if (items[InventorySlotType::BASE_INVENTORY][BASE_EQUIPMENT].count(i) != 0) {
+ Item* item = items[InventorySlotType::BASE_INVENTORY][BASE_EQUIPMENT][i];
+ if(item) {
+ if(!item->IsBag()) {
+ PopulateHouseStoragePacket(client, packet, item, itemIdx, 0);
+ itemIdx++;
+ }
+ else {
+ bool bagHasItem = false;
+ if(items.count(item->details.bag_id) > 0){
+ map::iterator itr;
+ for(itr = items[item->details.bag_id][BASE_EQUIPMENT].begin(); itr != items[item->details.bag_id][BASE_EQUIPMENT].end(); itr++){
+ if(itr->second) {
+ PopulateHouseStoragePacket(client, packet, itr->second, itemIdx, 0);
+ bagHasItem = true;
+ itemIdx++;
+ }
+ }
+ }
+ if(!bagHasItem) {
+ PopulateHouseStoragePacket(client, packet, item, itemIdx, 0);
+ itemIdx++;
+ }
+ }
+ }
+ }
+ }
+ EQ2Packet* outapp = packet->serialize();
+ client->QueuePacket(outapp);
+ safe_delete(packet);
+ }
+ MPlayerItems.releasereadlock(__FUNCTION__, __LINE__);
+}
+
void PlayerItemList::ResetPackets() {
MPlayerItems.writelock(__FUNCTION__, __LINE__);
safe_delete_array(orig_packet);
@@ -4237,7 +4785,7 @@ bool EquipmentItemList::AddItem(int8 slot, Item* item){
SetItem(slot, item, true);
if (item->details.unique_id == 0) {
- GetItem(slot)->details.unique_id = MasterItemList::NextUniqueID();
+ GetItem(slot)->details.unique_id = master_item_list.NextUniqueID();
if (item->IsBag())
item->details.bag_id = item->details.unique_id;
}
@@ -4639,6 +5187,57 @@ int16 Item::GetIcon(int16 version) {
return details.icon;
}
+bool Item::TryLockItem(LockReason reason) {
+ std::unique_lock lock(item_lock_mtx_);
+ // current flags
+ auto cur = static_cast(details.lock_flags);
+
+ // 0) If this reason is already applied, succeed immediately
+ if ((cur & reason) == reason) {
+ return true;
+ }
+
+ // 1) No lock held? allow any first‐lock
+ if (cur == LockReason::LockReason_None) {
+ details.lock_flags = static_cast(reason);
+ details.item_locked = true;
+ return true;
+ }
+
+ // 2) Only House‐lock held, and we're adding Shop‐lock? allow
+ if ((cur == LockReason::LockReason_House && reason == LockReason::LockReason_Shop) ||
+ (cur == LockReason::LockReason_Shop && reason == LockReason::LockReason_House)) {
+ details.lock_flags = static_cast(cur | reason);
+ // item_locked already true
+ return true;
+ }
+
+ // 3) Anything else: reject
+ return false;
+}
+
+bool Item::TryUnlockItem(LockReason reason) {
+ std::unique_lock lock(item_lock_mtx_);
+ LockReason cur = static_cast(details.lock_flags);
+ if ((cur & reason) == reason) {
+ details.lock_flags = int32(cur & ~reason);
+ if (details.lock_flags == 0)
+ details.item_locked = false;
+ return true;
+ }
+ return false;
+}
+
+bool Item::IsItemLocked() {
+ std::shared_lock lock(item_lock_mtx_);
+ return details.lock_flags != 0;
+}
+
+bool Item::IsItemLockedFor(LockReason reason) {
+ std::shared_lock lock(item_lock_mtx_);
+ return (static_cast(details.lock_flags) & reason) == reason;
+}
+
int32 MasterItemList::GetItemStatIDByName(std::string name)
{
boost::to_lower(name);
diff --git a/source/WorldServer/Items/Items.h b/source/WorldServer/Items/Items.h
index 25c021c..6c200fd 100644
--- a/source/WorldServer/Items/Items.h
+++ b/source/WorldServer/Items/Items.h
@@ -1,6 +1,6 @@
/*
EQ2Emulator: Everquest II Server Emulator
- Copyright (C) 2005 - 2025 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net)
+ Copyright (C) 2005 - 2026 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net)
This file is part of EQ2Emulator.
@@ -17,11 +17,13 @@
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see .
*/
+
#ifndef __EQ2_ITEMS__
#define __EQ2_ITEMS__
#include