Vault, Broker support for DoF, KoS, AoM. Shop support for DoF, KoS.

Broker/Vault (Fix #10):
- Traditional broker integrated (/frombroker), GM itemsearch is still available via /itemsearch.  AoM client cannot sell/use shop, can only buy from broker.
- DoF and KoS client now support House Shop (sell inventory, vault to broker).
- House containers now function like actual containers and can be stored in the vault which will allow placement in the home (to serve as a merchant).
- Sales crate merchant windows in player houses implemented for broker.
- Peering URI's added for broker/shop support: /addseller, /removeseller, /additemsale, /removeitemsale
- House vault and vault slots implemented for DoF, KoS and AoM clients.

Stability and Functionality:
- World Database now has seq_character_items (Sequence) to sync self and peers instantiating new unique IDs for character items.  This is to avoid conflict/overriding another unique id.
- spawn and spawn_houses tables have a lua_script that can now be defined outside the existing spawn_scripts table for a singular spawn.
- Fixed a watchdog/hangup when clearing hate lists inside a spawn list lock.
- Item unique id is being transitioned to int64 (although older clients still only support int32, clients later on are int64).
- Fixed so that spells that have no duration will no longer be added to maintained effects.
- Fixed spell cleanup, maintained effect does not remain on the Player causing a crash.
- Fixed spell conflicts to that check all targets are applicable for the spell.
- Fixed issues with maintained effects or spell effects stacking repeatedly.
- Fixed assigning items to non inventory slots when 'adding' an item to the Player.
- Fixed locking orders between maintained effects and spell effects to avoid deadlocks.
- Fixed entering house and visiting houses, targetting of the house door is now enforced server side.
- Item locking is now enforced by the type of locking (eg. house placement, crafting, shop list for sale).  Locks no longer override/interfere with each other.
- Additional logging around spell casting and targets.
- Spawns/Objects/Widgets so on related to houses now have their own sub tables _houses, eg. spawn_houses, spawn_npc_houses, spawn_object_houses, so on to avoid conflicting with existing tables non-house.

- new LUA Functions:
	ShowShopWindow(Player, FromSpawn) - opens shop window for player (if in their house) for listing, pricing items, retrieving sales log and coin, etc.
	SetSpawnHouseScript(Target, LuaScript) - Utilized in the item script 'placed' function to set the spawned house item's lua script.
	SendBook(Target, ItemID) - Sends the book to the target player based on the item id.
	GetPickupItemID(Spawn) - Gets the item id that the house spawn would represent
	SetHouseCharacterID(Spawn, CharID) - Sets the house spawn character id (should be used on Spawn).  Set CharID to 0 to set to the current houses character id.

- Updated LUA Functions:
	StartHeroicOpportunity(Caster, ClassID, OverrideTarget)  - OverrideTarget now available to change the heroic opportunity target
	HasItem(Player, ItemID, IncludeBank) - No parameter change, include bank, which serves as 'all' should now work correctly (previously did not check bank/other negative slots),  default is false.

- Slash Commands Added:
	/sle - Fix #41, Set Location Entry (DB command for setting spawn location entry values such as offsets and overrides)
	/store_list_item - used to list an item for broker shop
	/store_set_price - used for setting items price for shop
	/store_set_price_local - used for inventory items price for shop
	/store_start_selling - Begin selling from inventory for shop
	/store_stop_selling - Stop selling from inventory
	/store_unlist_item - used to unlist an item from broker shop
	/close_store_keep_selling - Closes store shop window, but keep selling from inventory while in house
	/cancel_store - cancel selling from inventory
This commit is contained in:
Emagi 2025-07-26 07:10:07 -04:00
parent 21052e3289
commit f60d261f00
46 changed files with 4796 additions and 1131 deletions

View File

@ -10469,15 +10469,21 @@ to zero and treated like placeholders." />
<Data ElementName="your_item_count" Type="int16" />
<Data ElementName="your_item_array" Type="Array" ArraySizeVariable="your_item_count">
<Data ElementName="your_item_name" Type="EQ2_8Bit_String" />
<Data ElementName="unique_id" Type="int64" />
<Data ElementName="cost" Type="int64" />
<Data ElementName="unique_id2" Type="int64" />
<Data ElementName="your_item_quantity" Type="int16" />
<Data ElementName="your_item_icon" Type="int16" />
<Data ElementName="your_item_unknown4" Type ="int8" Size="4" />
<Data ElementName="your_item_background" Type="int8" />
<Data ElementName="your_item_unknown4" Type ="int8" Size="9" />
<Data ElementName="your_item_unknown5" Type ="int8" Size="1" />
<Data ElementName="storage_flags" Type ="int8" />
<Data ElementName="your_item_unknown6" Type ="int8" Size="12" />
</Data>
<Data ElementName="type" Type="int8"/> <!-- 4 opens window -->
<Data ElementName="type" Type="int8"/> <!-- 4 opens window & start selling button, 6 opens window & stop selling button -->
</Struct>
<Struct Name="WS_HouseStoreLog" ClientVersion="546" OpcodeName="OP_ClientCmdMsg" OpcodeType="OP_EqStoreLogCmd">
<Data ElementName="data" Type="EQ2_16Bit_String" />
<Data ElementName="coin_gain_session" Type="int64" /> <!-- sending twice will reset coin session -->
<Data ElementName="coin_gain_alltime" Type="int64" />
<Data ElementName="sales_log_open" Type="int8" />
</Struct>
<Struct Name="WS_UpdateMerchant" ClientVersion="1" OpcodeName="OP_ClientCmdMsg" OpcodeType="OP_EqUpdateMerchantCmd">
<Data ElementName="spawn_id" Type="int32" />

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "BrokerManager.h"
#include "../Items/Items.h"
#include "../../common/Log.h"
#include "../WorldDatabase.h"
#include "../World.h"
#include "../Web/PeerManager.h"
#include <cstdlib>
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<SaleItem> 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<SaleItem> 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<SaleItem> 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<SaleItem> BrokerManager::GetActiveForSaleItems(int32 cid) const
{
std::shared_lock lock(mtx_);
std::vector<SaleItem> 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<SaleItem> 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 threadsafe read
std::shared_lock lock(mtx_);
// Find this characters 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 its 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<SaleItem> BrokerManager::GetInactiveItems(int32 cid) const
{
std::shared_lock lock(mtx_);
std::vector<SaleItem> 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<std::pair<int64,int32>>
BrokerManager::GetUniqueIDsAndCost(int32 cid) const
{
std::shared_lock lock(mtx_);
std::vector<std::pair<int64,int32>> 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<Item*>* 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<Item*>* ret = new vector<Item*>;
string lower_name = ::ToLower(string(name.c_str()));
std::shared_lock lock(mtx_);
std::vector<SaleItem> 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<SellerInfo> 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 <sstream>
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<int64>(coin / 1000000);
// 1) Break totalCopper into denominations
int64 rem = coin % 1000000;
int64 gold = static_cast<int64>(rem / 10000);
rem %= 10000;
int64 silver = static_cast<int64>(rem / 100);
int64 copper = static_cast<int64>(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<std::string> 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<SellerLog> BrokerManager::GetSellerLog(int32 cid) const {
std::vector<SellerLog> 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;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <cstdint>
#include <functional>
#include <unordered_map>
#include <vector>
#include <mutex>
#include <string>
#include <optional>
#include <shared_mutex>
#include "../../common/types.h"
// Key = (character_id, unique_id)
using Key = std::pair<int32, int64>;
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<SaleItem> GetActiveForSaleItems(int32 cid) const;
std::optional<SaleItem> GetActiveItem(int32 cid, int64 uid) const;
bool IsSellingItems(int32 cid, bool vaultOnly = false) const;
std::vector<SaleItem> GetInactiveItems(int32 cid) const;
// Global search API (only active_items_)
vector<Item*>* 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<std::pair<int64,int32>> GetUniqueIDsAndCost(int32 cid) const;
std::optional<SellerInfo> 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<SellerLog> 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<int32,SellerInfo> players_;
std::unordered_map<
int32,
std::unordered_map<int64_t, SaleItem>
> active_items_by_char_;
std::unordered_map<
int32,
std::unordered_map<int64_t, SaleItem>
> 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);
};

View File

@ -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<string, string> str_values = TranslateBrokerRequest(values);
vector<Item*>* 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 commandhandler:
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 <column_name> <new_value>");
return;
}
Spawn* target = client->GetPlayer()->GetTarget();
if(!target) {
client->Message(CHANNEL_COLOR_RED, "Target missing for set location entry, /sle <column_name> <new_value>");
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<std::string> 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);
}

View File

@ -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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifdef WIN32
#include <WinSock2.h>
#include <windows.h>
@ -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;

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "Entity.h"
#include <math.h>
#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<LuaSpell*,bool>::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<int32> 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;
}

View File

@ -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(); }

File diff suppressed because it is too large Load Diff

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef __EQ2_ITEMS__
#define __EQ2_ITEMS__
#include <map>
#include <vector>
#include <ctime>
#include <shared_mutex>
#include "../../common/types.h"
#include "../../common/DataBuffer.h"
#include "../Commands/Commands.h"
@ -647,6 +649,47 @@ enum ItemEffectType {
EFFECT_CURE_TYPE_MAGIC=6,
EFFECT_CURE_TYPE_ALL=7
};
enum InventorySlotType {
HOUSE_VAULT=-5,
SHARED_BANK=-4,
BANK=-3,
OVERFLOW=-2,
UNKNOWN_INV_SLOT_TYPE=-1,
BASE_INVENTORY=0
};
enum class LockReason : int32 {
LockReason_None = 0,
LockReason_House = 1u << 0,
LockReason_Crafting = 1u << 1,
LockReason_Shop = 1u << 2,
};
inline LockReason operator|(LockReason a, LockReason b) {
return static_cast<LockReason>(
static_cast<uint32_t>(a) | static_cast<uint32_t>(b)
);
}
inline LockReason operator&(LockReason a, LockReason b) {
return static_cast<LockReason>(
static_cast<uint32_t>(a) & static_cast<uint32_t>(b)
);
}
inline LockReason operator~(LockReason a) {
return static_cast<LockReason>(~static_cast<uint32_t>(a));
}
enum HouseStoreItemFlags {
HOUSE_STORE_ITEM_TEXT_RED=1,
HOUSE_STORE_UNKNOWN_BIT2=2,
HOUSE_STORE_UNKNOWN_BIT4=4,
HOUSE_STORE_FOR_SALE=8,
HOUSE_STORE_UNKNOWN_BIT16=16,
HOUSE_STORE_VAULT_TAB=32
// rest are also unknown
};
#pragma pack(1)
struct ItemStatsValues{
sint16 str;
@ -710,10 +753,11 @@ struct ItemCore{
int16 count;
int8 tier;
int8 num_slots;
int32 unique_id;
int64 unique_id;
int8 num_free_slots;
int16 recommended_level;
bool item_locked;
int32 lock_flags;
bool new_item;
int16 new_index;
};
@ -934,6 +978,8 @@ public:
#pragma pack()
Item();
Item(Item* in_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);
~Item();
string lowername;
string name;
@ -942,10 +988,15 @@ public:
int32 sell_price;
int32 sell_status;
int32 max_sell_value;
int64 broker_price;
bool is_search_store_item;
bool save_needed;
int8 weapon_type;
string adornment;
string creator;
string seller_name;
int32 seller_char_id;
int64 seller_house_id;
int32 adorn0;
int32 adorn1;
int32 adorn2;
@ -986,6 +1037,7 @@ public:
bool crafted;
bool tinkered;
int8 book_language;
mutable std::shared_mutex item_lock_mtx_;
void AddEffect(string effect, int8 percentage, int8 subbulletflag);
void AddBookPage(int8 page, string page_text,int8 valign, int8 halign);
@ -1067,6 +1119,10 @@ public:
void AddSlot(int8 slot_id);
void SetSlots(int32 slots);
int16 GetIcon(int16 version);
bool TryLockItem(LockReason reason);
bool TryUnlockItem(LockReason reason);
bool IsItemLocked();
bool IsItemLockedFor(LockReason reason);
};
class MasterItemList{
public:
@ -1079,14 +1135,16 @@ public:
Item* GetAllItemsByClassification(const char* name);
ItemStatsValues* CalculateItemBonuses(int32 item_id, Entity* entity = 0);
ItemStatsValues* CalculateItemBonuses(Item* desc, Entity* entity = 0, ItemStatsValues* values = 0);
bool ShouldAddItemBrokerType(Item* item, int64 itype);
bool ShouldAddItemBrokerSlot(Item* item, int64 ltype);
bool ShouldAddItemBrokerStat(Item* item, int64 btype);
vector<Item*>* 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<Item*>* GetItems(map<string, string> criteria, Client* client_to_map);
void AddItem(Item* item);
bool IsBag(int32 item_id);
void RemoveAll();
static int32 NextUniqueID();
static void ResetUniqueID(int32 new_id);
static int32 next_unique_id;
static int64 NextUniqueID();
int32 GetItemStatIDByName(std::string name);
std::string GetItemStatNameByID(int32 id);
void AddMappedItemStat(int32 id, std::string lower_case_name);
@ -1120,10 +1178,18 @@ public:
void MoveItem(Item* item, sint32 inv_slot, int16 slot, int8 appearance_type, bool erase_old); // erase old was true
bool MoveItem(sint32 to_bag_id, int16 from_index, sint8 to, int8 appearance_type, int8 charges);
void EraseItem(Item* item);
Item* GetItemFromUniqueID(int32 item_id, bool include_bank = false, bool lock = true);
void SetVaultItemLockUniqueID(Client* client, int64 id, bool state, bool lock);
bool CanStoreSellItem(int64 unique_id, bool lock);
bool IsItemInSlotType(Item* item, InventorySlotType type, bool lockItems=true);
void SetVaultItemUniqueIDCount(Client* client, int64 unique_id, int16 count, bool lock = true);
void RemoveVaultItemFromUniqueID(Client* client, int64 item_id, bool lock = true);
Item* GetVaultItemFromUniqueID(int64 item_id, bool lock = true);
Item* GetItemFromID(int32 item_id, int8 count = 0, bool include_bank = false, bool lock = true);
sint32 GetAllStackCountItemFromID(int32 item_id, int8 count = 0, bool include_bank = false, bool lock = true);
bool AssignItemToFreeSlot(Item* item);
bool AssignItemToFreeSlot(Item* item, bool inventory_only = true);
int16 GetNumberOfFreeSlots();
int16 GetNumberOfItems();
int32 GetWeight();
@ -1132,7 +1198,7 @@ public:
void DestroyItem(int16 index);
Item* CanStack(Item* item, bool include_bank = false);
vector<Item*> GetAllItemsFromID(int32 item, bool include_bank = false, bool lock = false);
void RemoveItem(Item* item, bool delete_item = false);
void RemoveItem(Item* item, bool delete_item = false, bool lock = true);
bool AddItem(Item* item);
Item* GetItem(sint32 bag_slot, int16 slot, int8 appearance_type = 0);
@ -1143,7 +1209,10 @@ public:
map<int32, Item*>* GetAllItems();
bool HasFreeBankSlot();
int8 FindFreeBankSlot();
void GetVaultItems(Client* client, int32 spawn_id, int8 maxSlots, bool isSelling = false);
void PopulateHouseStoragePacket(Client* client, PacketStruct* packet, Item* item, int16 itemIdx, int8 storage_flags);
///<summary>Get the first free slot and store them in the provided variables</summary>
///<param name='bag_id'>Will contain the bag id of the first free spot</param>
///<param name='slot'>Will contain the slot id of the first free slot</param>

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifdef WIN32
#include <WinSock2.h>
#include <windows.h>
@ -172,6 +173,7 @@ void WorldDatabase::LoadDataFromRow(DatabaseResult* result, Item* item)
item->generic_info.part_of_quest_id = result->GetInt32Str("part_of_quest_id");
item->details.recommended_level = result->GetInt16Str("recommended_level");
item->details.item_locked = false;
item->details.lock_flags = 0;
item->generic_info.adventure_default_level = result->GetInt16Str("adventure_default_level");
item->generic_info.max_charges = result->GetInt16Str("max_charges");
item->generic_info.display_charges = result->GetInt8Str("display_charges");
@ -541,7 +543,7 @@ int32 WorldDatabase::LoadHouseContainers(int32 item_id){
if (item)
{
LogWrite(ITEM__DEBUG, 5, "Items", "\tHouse Container for item_id %u", id);
LogWrite(ITEM__DEBUG, 5, "Items", "\tType: %i, '%i', '%u', '%i', '%i'", ITEM_TYPE_RECIPE, result.GetInt8Str("num_slots"), result.GetInt64Str("allowed_types"), result.GetInt8Str("broker_commission"), result.GetInt8Str("fence_commission"));
LogWrite(ITEM__DEBUG, 5, "Items", "\tType: %i, '%i', '%u', '%i', '%i'", ITEM_TYPE_HOUSE_CONTAINER, result.GetInt8Str("num_slots"), result.GetInt64Str("allowed_types"), result.GetInt8Str("broker_commission"), result.GetInt8Str("fence_commission"));
item->SetItemType(ITEM_TYPE_HOUSE_CONTAINER);
item->housecontainer_info->num_slots = result.GetInt8Str("num_slots");
@ -549,6 +551,11 @@ int32 WorldDatabase::LoadHouseContainers(int32 item_id){
item->housecontainer_info->broker_commission = result.GetInt8Str("broker_commission");
item->housecontainer_info->fence_commission = result.GetInt8Str("fence_commission");
item->details.num_slots = item->housecontainer_info->num_slots;
item->details.num_free_slots = item->housecontainer_info->num_slots;
item->bag_info->num_slots = item->housecontainer_info->num_slots;
item->bag_info->weight_reduction = 0;
total++;
}
else
@ -1097,18 +1104,19 @@ void WorldDatabase::LoadItemList(int32 item_id)
LogWrite(ITEM__INFO, 0, "Items", "Loaded %u Total Item%s (took %u seconds)", total, ( total == 1 ) ? "" : "s", Timer::GetUnixTimeStamp() - t_now);
}
int32 WorldDatabase::LoadNextUniqueItemID()
int64 WorldDatabase::LoadNextUniqueItemID()
{
Query query;
MYSQL_ROW row;
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT max(id) FROM character_items");
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT NEXT VALUE FOR seq_character_items AS next_id");
if(result && (row = mysql_fetch_row(result)))
{
if(row[0])
{
LogWrite(ITEM__DEBUG, 0, "Items", "%s: max(id): %u", __FUNCTION__, atoul(row[0]));
return strtoul(row[0], NULL, 0);
int64 max_ = strtoull(row[0], NULL, 0);
LogWrite(ITEM__DEBUG, 0, "Items", "%s: max(id): %u", __FUNCTION__, max_);
return max_;
}
else
return 0;
@ -1119,6 +1127,31 @@ int32 WorldDatabase::LoadNextUniqueItemID()
return 0;
}
void WorldDatabase::ResetNextUniqueItemID()
{
Query query;
Query query2;
MYSQL_ROW row;
MYSQL_ROW row2;
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT next_not_cached_value FROM seq_character_items");
MYSQL_RES* result2 = query2.RunQuery2(Q_SELECT, "SELECT COALESCE(MAX(id),0) + 1 FROM character_items");
if(result && (row = mysql_fetch_row(result)) && result2 && (row2 = mysql_fetch_row(result2)))
{
if(row[0] && row2[0])
{
int64 max_cur = strtoull(row[0], NULL, 0);
int64 max_expected = strtoull(row2[0], NULL, 0);
string update_item = string("ALTER SEQUENCE seq_character_items RESTART WITH %llu");
if(max_cur < max_expected)
query.AddQueryAsync(0, this, Q_UPDATE, update_item.c_str(), max_expected);
LogWrite(ITEM__DEBUG, 0, "Items", "%s: max(current): %u max(expected): %u", __FUNCTION__, max_cur, max_expected);
}
}
else if(!result)
LogWrite(ITEM__ERROR, 0, "Items", "%s: Unable to reset next unique item ID.", __FUNCTION__);
}
void WorldDatabase::SaveItems(Client* client)
{
LogWrite(ITEM__DEBUG, 3, "Items", "Save Items for Player %i", client->GetCharacterID());
@ -1382,7 +1415,7 @@ void WorldDatabase::LoadCharacterItemList(int32 account_id, int32 char_id, Playe
int8 remainder = item->details.count % 255;
item->details.count = remainder;
if (item->details.inv_slot_id == -2)
if (item->details.inv_slot_id == InventorySlotType::OVERFLOW)
player->item_list.AddOverflowItem(item);
else {
if(!player->item_list.AddItem(item))
@ -1398,7 +1431,7 @@ void WorldDatabase::LoadCharacterItemList(int32 account_id, int32 char_id, Playe
}
}
else {
if (item->details.inv_slot_id == -2)
if (item->details.inv_slot_id == InventorySlotType::OVERFLOW)
player->item_list.AddOverflowItem(item);
else
player->item_list.AddItem(item);

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef __EQ2_ITEMS__
#define __EQ2_ITEMS__
#include <map>
@ -730,9 +731,7 @@ public:
void AddItem(Item* item);
bool IsBag(int32 item_id);
void RemoveAll();
static int32 NextUniqueID();
static void ResetUniqueID(int32 new_id);
static int32 next_unique_id;
static int64 NextUniqueID();
};
class PlayerItemList {
public:

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef __EQ2_ITEMS__
#define __EQ2_ITEMS__
#include <map>
@ -824,9 +825,7 @@ public:
void AddItem(Item* item);
bool IsBag(int32 item_id);
void RemoveAll();
static int32 NextUniqueID();
static void ResetUniqueID(int32 new_id);
static int32 next_unique_id;
static int64 NextUniqueID();
};
class PlayerItemList {
public:

View File

@ -1,22 +1,23 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
/*
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.
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 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.
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 <http://www.gnu.org/licenses/>.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#include "LuaFunctions.h"
#include "Spawn.h"
#include "WorldDatabase.h"
@ -37,6 +38,7 @@
#include "ClientPacketFunctions.h"
#include "Transmute.h"
#include "Titles.h"
#include "./Broker/BrokerManager.h"
#include <boost/algorithm/string/predicate.hpp>
#include <sstream>
#include <boost/algorithm/string.hpp>
@ -60,6 +62,7 @@ extern MasterRaceTypeList race_types_list;
extern MasterLanguagesList master_languages_list;
extern MasterTitlesList master_titles_list;
extern RuleManager rule_manager;
extern BrokerManager broker;
vector<string> ParseString(string strVal, char delim) {
stringstream ss(strVal);
@ -9646,7 +9649,6 @@ int EQ2Emu_lua_CureByType(lua_State* state) {
}
else {
ZoneServer* zone = spell->zone;
vector<int32> targets = spell->targets;
vector<Entity*> targets_to_cure;
for (int32 id : spell->GetTargets()) {
target = zone->GetSpawnByID(id);
@ -9797,6 +9799,7 @@ int EQ2Emu_lua_StartHeroicOpportunity(lua_State* state) {
Spawn* caster = lua_interface->GetSpawn(state);
int8 class_id = lua_interface->GetInt8Value(state, 2);
Spawn* targetOverride = lua_interface->GetSpawn(state, 3);
lua_interface->ResetFunctionStack(state);
if (!caster) {
@ -9810,10 +9813,13 @@ int EQ2Emu_lua_StartHeroicOpportunity(lua_State* state) {
}
Spawn* target = caster->GetTarget();
if (!target) {
if (!target && !targetOverride) {
lua_interface->LogError("%s: LUA StartHeroicOpportunity command error: target is not valid", lua_interface->GetScriptName(state));
return 0;
}
if(!target)
target = targetOverride;
Client* client = ((Player*)caster)->GetClient();
if (!client) {
@ -14656,3 +14662,103 @@ int EQ2Emu_lua_GetExpRequiredByLevel(lua_State* state) {
lua_interface->SetInt32Value(state, exp_required);
return 1;
}
int EQ2Emu_lua_ShowShopWindow(lua_State* state) {
Spawn* player = lua_interface->GetSpawn(state);
Spawn* from_spawn = lua_interface->GetSpawn(state, 2);
lua_interface->ResetFunctionStack(state);
if (player) {
Client* client = 0;
if (player->IsPlayer())
client = ((Player*)player)->GetClient();
if (client) {
client->OpenShopWindow(from_spawn, true);
}
}
return 0;
}
int EQ2Emu_lua_SetSpawnHouseScript(lua_State* state) {
Spawn* target = lua_interface->GetSpawn(state);
string lua_script = lua_interface->GetStringValue(state, 2);
lua_interface->ResetFunctionStack(state);
if (target && target->GetDatabaseID() && !target->IsPlayer() && !target->IsBot()) {
database.UpdateHouseSpawnScript(target->GetDatabaseID(), lua_script);
bool scriptActive = false;
if (lua_interface && lua_interface->GetSpawnScript(lua_script.c_str()) != 0) {
scriptActive = true;
target->SetSpawnScript(lua_script);
}
if (scriptActive) {
target->GetZone()->CallSpawnScript(target, SPAWN_SCRIPT_SPAWN);
}
}
return 0;
}
int EQ2Emu_lua_SendBook(lua_State* state) {
Spawn* target = lua_interface->GetSpawn(state);
int32 item_id = lua_interface->GetInt32Value(state, 2);
lua_interface->ResetFunctionStack(state);
if (target && target->IsPlayer() && ((Player*)target)->GetClient()) {
Item* item = master_item_list.GetItem(item_id);
if (item) {
((Player*)target)->GetClient()->SendShowBook(target, item->name, item->book_language, item->book_pages);
}
}
return 0;
}
int EQ2Emu_lua_GetPickupItemID(lua_State* state) {
Spawn* spawn = lua_interface->GetSpawn(state);
lua_interface->ResetFunctionStack(state);
if (spawn) {
lua_interface->SetInt32Value(state, spawn->GetPickupItemID());
return 1;
}
return 0;
}
int EQ2Emu_lua_SetHouseCharacterID(lua_State* state) {
Spawn* spawn = lua_interface->GetSpawn(state);
int32 character_id = lua_interface->GetInt32Value(state, 2);
lua_interface->ResetFunctionStack(state);
if (spawn) {
if(!character_id) {
PlayerHouse* ph = world.GetPlayerHouseByInstanceID(spawn->GetZone()->GetInstanceID());
if(ph)
spawn->SetHouseCharacterID(ph->character_id);
}
else {
spawn->SetHouseCharacterID(character_id);
}
}
return 0;
}
int EQ2Emu_lua_GetHouseCharacterID(lua_State* state) {
Spawn* spawn = lua_interface->GetSpawn(state);
lua_interface->ResetFunctionStack(state);
if (spawn) {
lua_interface->SetInt32Value(state, spawn->GetHouseCharacterID());
}
else {
lua_interface->SetInt32Value(state, 0);
}
return 1;
}
int EQ2Emu_lua_ShowHouseShopMerchant(lua_State* state) {
Spawn* spawn = lua_interface->GetSpawn(state);
Spawn* player = lua_interface->GetSpawn(state, 2);
lua_interface->ResetFunctionStack(state);
if (spawn && player && player->IsPlayer()) {
if(player->GetClient())
player->GetClient()->SendMerchantWindow(spawn, false);
}
return 0;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef LUA_FUNCTIONS_H
#define LUA_FUNCTIONS_H
@ -684,4 +685,12 @@ int EQ2Emu_lua_GetZonePlayerFirstLevel(lua_State* state);
int EQ2Emu_lua_GetSpellRequiredLevel(lua_State* state);
int EQ2Emu_lua_GetExpRequiredByLevel(lua_State* state);
int EQ2Emu_lua_ShowShopWindow(lua_State* state);
int EQ2Emu_lua_SetSpawnHouseScript(lua_State* state);
int EQ2Emu_lua_SendBook(lua_State* state);
int EQ2Emu_lua_GetPickupItemID(lua_State* state);
int EQ2Emu_lua_SetHouseCharacterID(lua_State* state);
int EQ2Emu_lua_GetHouseCharacterID(lua_State* state);
int EQ2Emu_lua_ShowHouseShopMerchant(lua_State* state);
#endif

View File

@ -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.
@ -1824,6 +1824,14 @@ void LuaInterface::RegisterFunctions(lua_State* state) {
lua_register(state,"GetSpellRequiredLevel", EQ2Emu_lua_GetSpellRequiredLevel);
lua_register(state,"GetExpRequiredByLevel", EQ2Emu_lua_GetExpRequiredByLevel);
lua_register(state,"ShowShopWindow", EQ2Emu_lua_ShowShopWindow);
lua_register(state,"SetSpawnHouseScript", EQ2Emu_lua_SetSpawnHouseScript);
lua_register(state,"SendBook", EQ2Emu_lua_SendBook);
lua_register(state,"GetPickupItemID", EQ2Emu_lua_GetPickupItemID);
lua_register(state,"SetHouseCharacterID", EQ2Emu_lua_SetHouseCharacterID);
lua_register(state,"GetHouseCharacterID", EQ2Emu_lua_GetHouseCharacterID);
lua_register(state,"ShowHouseShopMerchant", EQ2Emu_lua_ShowHouseShopMerchant);
}
void LuaInterface::LogError(const char* error, ...) {

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef LUA_INTERFACE_H
#define LUA_INTERFACE_H
@ -161,7 +162,16 @@ struct LuaSpell{
std::shared_lock lock(targets_mutex);
return std::find(targets.begin(), targets.end(), id) != targets.end();
}
bool HasAnyTarget(const std::vector<int32>& ids) const {
std::shared_lock lock(targets_mutex);
return std::any_of(
ids.begin(), ids.end(),
[this](int32 id){
return std::find(targets.begin(), targets.end(), id) != targets.end();
}
);
}
std::vector<int32> GetRemovedTargets() const {
std::shared_lock lock(targets_mutex);

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "NPC.h"
#include "WorldDatabase.h"
#include <math.h>
@ -108,6 +109,8 @@ NPC::NPC(NPC* old_npc){
SetLootDropType(old_npc->GetLootDropType());
has_spells = old_npc->HasSpells();
SetScaredByStrongPlayers(old_npc->IsScaredByStrongPlayers());
if(old_npc->GetSpawnScriptSetDB() && old_npc->GetSpawnScript())
SetSpawnScript(std::string(old_npc->GetSpawnScript()));
}
}

View File

@ -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.
@ -271,13 +271,13 @@ void Brain::AddHate(Entity* entity, sint32 hate) {
}
}
void Brain::ClearHate() {
void Brain::ClearHate(bool lockSpawnList) {
// Lock the hate list, we are altering the list so use a write lock
MHateList.writelock(__FUNCTION__, __LINE__);
map<int32, sint32>::iterator itr;
for (itr = m_hatelist.begin(); itr != m_hatelist.end(); itr++) {
Spawn* spawn = m_body->GetZone()->GetSpawnByID(itr->first);
Spawn* spawn = m_body->GetZone()->GetSpawnByID(itr->first, lockSpawnList);
if (spawn && spawn->IsEntity())
{
((Entity*)spawn)->MHatedBy.lock();

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef __NPC_AI_H__
#define __NPC_AI_H__
#include "NPC.h"
@ -59,7 +60,7 @@ public:
/// <param name="hate">The amount of hate to add</param>
virtual void AddHate(Entity* entity, sint32 hate);
/// <summary>Completely clears the hate list for this npc</summary>
void ClearHate();
void ClearHate(bool lockSpawnList = true);
/// <summary>Removes the given entity from this NPC's hate list</summary>
/// <param name="entity">Entity to remove from this NPC's hate list</param>
void ClearHate(Entity* entity);

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "World.h"
#include "Object.h"
#include "Spells.h"
@ -95,5 +96,7 @@ Object* Object::Copy(){
new_spawn->SetOmittedByDBFlag(IsOmittedByDBFlag());
new_spawn->SetLootTier(GetLootTier());
new_spawn->SetLootDropType(GetLootDropType());
if(GetSpawnScriptSetDB() && GetSpawnScript())
new_spawn->SetSpawnScript(std::string(GetSpawnScript()));
return new_spawn;
}

View File

@ -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.
@ -137,6 +137,7 @@ Player::Player(){
active_drink_unique_id = 0;
raidsheet_changed = false;
hassent_raid = false;
house_vault_slots = 0;
}
Player::~Player(){
SetSaveSpellEffects(true);
@ -953,7 +954,7 @@ EQ2Packet* PlayerInfo::serialize(int16 version, int16 modifyPos, int32 modifyVal
packet->setDataByName("rain2", info_struct->get_wind()); //-102.24);
packet->setDataByName("status_points", info_struct->get_status_points());
packet->setDataByName("guild_status", 888888);
packet->setDataByName("vault_slots", player->GetHouseVaultSlots());
if (house_zone_id > 0){
string house_name = database.GetZoneName(house_zone_id);
if(house_name.length() > 0)
@ -1484,7 +1485,7 @@ vector<EQ2Packet*> Player::UnequipItem(int16 index, sint32 bag_id, int8 slot, in
SetCharSheetChanged(true);
SetEquipment(0, equip_slot_id ? equip_slot_id : old_slot);
}
else if (item_list.AssignItemToFreeSlot(item)) {
else if (item_list.AssignItemToFreeSlot(item, true)) {
if(appearance_type)
database.DeleteItem(GetCharacterID(), item, "APPEARANCE");
else
@ -1645,8 +1646,8 @@ vector<EQ2Packet*> Player::UnequipItem(int16 index, sint32 bag_id, int8 slot, in
}
}
else {
if ((bag_id == 0 && slot < NUM_INV_SLOTS) || (bag_id == -3 && slot < NUM_BANK_SLOTS) || (bag_id == -4 && slot < NUM_SHARED_BANK_SLOTS)) {
if (bag_id == -4 && item->CheckFlag(NO_TRADE)) {
if ((bag_id == 0 && slot < NUM_INV_SLOTS) || (bag_id == InventorySlotType::BANK && slot < NUM_BANK_SLOTS) || (bag_id == InventorySlotType::SHARED_BANK && slot < NUM_SHARED_BANK_SLOTS) || (bag_id == InventorySlotType::HOUSE_VAULT && slot < GetHouseVaultSlots())) {
if ((bag_id == InventorySlotType::SHARED_BANK || bag_id == InventorySlotType::HOUSE_VAULT) && !item_list.SharedBankAddAllowed(item)) {
PacketStruct* packet = configReader.getStruct("WS_DisplayText", version);
if (packet) {
packet->setDataByName("color", CHANNEL_COLOR_YELLOW);
@ -2018,7 +2019,7 @@ vector<EQ2Packet*> Player::EquipItem(int16 index, int16 version, int8 appearance
const char* zone_script = world.GetZoneScript(GetZone()->GetZoneID());
if (zone_script && lua_interface)
lua_interface->RunZoneScript(zone_script, "item_equipped", GetZone(), this, item->details.item_id, item->name.c_str(), 0, item->details.unique_id);
int32 bag_id = item->details.inv_slot_id;
sint32 bag_id = item->details.inv_slot_id;
if (item->generic_info.condition == 0) {
Client* client = GetClient();
if (client) {
@ -2108,7 +2109,7 @@ bool Player::AddItem(Item* item, AddItemType type) {
safe_delete(item);
return false;
}
else if (item_list.AssignItemToFreeSlot(item)) {
else if (item_list.AssignItemToFreeSlot(item, true)) {
item->save_needed = true;
CalculateApplyWeight();
return true;
@ -2160,7 +2161,7 @@ void Player::UpdateInventory(int32 bag_id) {
EQ2Packet* Player::MoveInventoryItem(sint32 to_bag_id, int16 from_index, int8 new_slot, int8 charges, int8 appearance_type, bool* item_deleted, int16 version) {
Item* item = item_list.GetItemFromIndex(from_index);
bool isOverflow = ((item != nullptr) && (item->details.inv_slot_id == -2));
bool isOverflow = ((item != nullptr) && (item->details.inv_slot_id == InventorySlotType::OVERFLOW));
int8 result = item_list.MoveItem(to_bag_id, from_index, new_slot, appearance_type, charges);
if (result == 1) {
if(isOverflow && item->details.inv_slot_id != -2) {
@ -3434,6 +3435,11 @@ void Player::AddMaintainedSpell(LuaSpell* luaspell){
if(!luaspell)
return;
if(luaspell->spell->GetSpellData()->not_maintained || luaspell->spell->GetSpellData()->duration1 == 0) {
LogWrite(PLAYER__INFO, 0, "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();
int32 target_type = 0;
@ -7458,8 +7464,8 @@ void Player::SaveSpellEffects()
savedEffects.AddQueryAsync(GetCharacterID(), &database, Q_DELETE, "delete from character_custom_spell_display where charid=%u", GetCharacterID());
savedEffects.AddQueryAsync(GetCharacterID(), &database, Q_DELETE, "delete from character_custom_spell_data where charid=%u", GetCharacterID());
InfoStruct* info = GetInfoStruct();
MSpellEffects.readlock(__FUNCTION__, __LINE__);
MMaintainedSpells.readlock(__FUNCTION__, __LINE__);
MSpellEffects.readlock(__FUNCTION__, __LINE__);
for(int i = 0; i < 45; i++) {
if(info->spell_effects[i].spell_id != 0xFFFFFFFF)
{
@ -7492,7 +7498,8 @@ void Player::SaveSpellEffects()
SaveCustomSpellDataIndex(info->spell_effects[i].spell);
SaveCustomSpellEffectsDisplay(info->spell_effects[i].spell);
}
if (i < NUM_MAINTAINED_EFFECTS && info->maintained_effects[i].spell_id != 0xFFFFFFFF){
if (i < NUM_MAINTAINED_EFFECTS && info->maintained_effects[i].spell && info->maintained_effects[i].spell_id != 0xFFFFFFFF){
LogWrite(PLAYER__INFO, 0, "Player", "Saving slot %u maintained effect %u", i, info->maintained_effects[i].spell_id);
Spawn* spawn = GetZone()->GetSpawnByID(info->maintained_effects[i].spell->initial_target);
int32 target_char_id = 0;
@ -7582,8 +7589,8 @@ void Player::SaveSpellEffects()
}
}
}
MMaintainedSpells.releasereadlock(__FUNCTION__, __LINE__);
MSpellEffects.releasereadlock(__FUNCTION__, __LINE__);
MMaintainedSpells.releasereadlock(__FUNCTION__, __LINE__);
}
void Player::MentorTarget()

View File

@ -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.
@ -225,7 +225,7 @@ struct QuickBarItem{
int16 icon_type;
int32 id;
int8 tier;
int32 unique_id;
int64 unique_id;
EQ2_16BitString text;
};
@ -1103,8 +1103,11 @@ public:
void SetActiveFoodUniqueID(int32 unique_id, bool update_db = true);
void SetActiveDrinkUniqueID(int32 unique_id, bool update_db = true);
int32 GetActiveFoodUniqueID() { return active_food_unique_id; }
int32 GetActiveDrinkUniqueID() { return active_drink_unique_id; }
int64 GetActiveFoodUniqueID() { return active_food_unique_id; }
int64 GetActiveDrinkUniqueID() { return active_drink_unique_id; }
void SetHouseVaultSlots(int8 allowed_slots) { house_vault_slots = allowed_slots; }
int8 GetHouseVaultSlots() { return house_vault_slots; }
Mutex MPlayerQuests;
float pos_packet_speed;
@ -1264,8 +1267,10 @@ private:
Quest* GetAnyQuest(int32 quest_id);
Quest* GetCompletedQuest(int32 quest_id);
std::atomic<int32> active_food_unique_id;
std::atomic<int32> active_drink_unique_id;
std::atomic<int64> active_food_unique_id;
std::atomic<int64> active_drink_unique_id;
int8 house_vault_slots;
};
#pragma pack()
#endif

View File

@ -1,21 +1,21 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
/*
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.
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 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.
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 <http://www.gnu.org/licenses/>.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#include "PlayerGroups.h"
@ -34,6 +34,7 @@ extern ZoneList zone_list;
extern RuleManager rule_manager;
extern PeerManager peer_manager;
extern WorldDatabase database;
extern LuaInterface* lua_interface;
/******************************************************** PlayerGroup ********************************************************/
PlayerGroup::PlayerGroup(int32 id) {
@ -1094,6 +1095,7 @@ void PlayerGroupManager::UpdateGroupBuffs() {
caster = *vitr;
caster->GetMaintainedMutex()->readlock(__FUNCTION__, __LINE__);
// go through the player's maintained spells
bool skipSpell = false;
me = caster->GetMaintainedSpells();
for (i = 0; i < NUM_MAINTAINED_EFFECTS; i++) {
if (me[i].spell_id == 0xFFFFFFFF)
@ -1113,9 +1115,7 @@ void PlayerGroupManager::UpdateGroupBuffs() {
if (spell && spell->GetSpellData()->group_spell && spell->GetSpellData()->friendly_spell &&
(spell->GetSpellData()->target_type == SPELL_TARGET_GROUP_AE || spell->GetSpellData()->target_type == SPELL_TARGET_RAID_AE)) {
luaspell->ClearCharTargets();
for (target_itr = group->GetMembers()->begin(); target_itr != group->GetMembers()->end(); target_itr++) {
group_member = (*target_itr)->member;
@ -1127,8 +1127,39 @@ void PlayerGroupManager::UpdateGroupBuffs() {
client = (*target_itr)->client;
LuaSpell* conflictSpell = caster->HasLinkedTimerID(luaspell, group_member, false, true);
if(conflictSpell && group_member && group_member->IsEntity())
{
if(conflictSpell->spell->GetSpellData()->min_class_skill_req > 0 && spell->GetSpellData()->min_class_skill_req > 0)
{
if(conflictSpell->spell->GetSpellData()->min_class_skill_req <= spell->GetSpellData()->min_class_skill_req)
{
if(spell->GetSpellData()->duration_until_cancel && !luaspell->num_triggers)
{
for (int32 id : conflictSpell->GetTargets()) {
Spawn* tmpTarget = caster->GetZone()->GetSpawnByID(id);
if(tmpTarget && tmpTarget->IsEntity())
{
((Entity*)tmpTarget)->RemoveEffectsFromLuaSpell(conflictSpell);
caster->GetZone()->RemoveTargetFromSpell(conflictSpell, tmpTarget, false);
caster->GetZone()->GetSpellProcess()->CheckRemoveTargetFromSpell(conflictSpell);
lua_interface->RemoveSpawnFromSpell(conflictSpell, tmpTarget);
}
}
}
}
else
{
// this is a spell that is no good, have to abort!
caster->GetMaintainedMutex()->releasereadlock(__FUNCTION__, __LINE__);
caster->GetZone()->GetSpellProcess()->SpellCannotStack(caster->GetZone(), client, caster, luaspell, conflictSpell);
skipSpell = true;
break;
}
}
}
has_effect = false;
if (group_member->GetSpellEffect(spell->GetSpellID(), caster)) {
has_effect = true;
}
@ -1226,13 +1257,18 @@ void PlayerGroupManager::UpdateGroupBuffs() {
client->QueuePacket(packet);
}
}
luaspell->SwapTargets(new_target_list);
SpellProcess::AddSelfAndPet(luaspell, caster);
new_target_list.clear();
if(!skipSpell) {
luaspell->SwapTargets(new_target_list);
SpellProcess::AddSelfAndPet(luaspell, caster);
new_target_list.clear();
}
else
break;
}
}
caster->GetMaintainedMutex()->releasereadlock(__FUNCTION__, __LINE__);
if(!skipSpell)
caster->GetMaintainedMutex()->releasereadlock(__FUNCTION__, __LINE__);
}
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <assert.h>
#include "../../common/debug.h"
#include "../../common/Log.h"
@ -734,7 +735,12 @@ bool Recipe::PlayerHasComponentByItemID(Client* client, vector<Item*>* player_co
cur_qty = track_req_qty;
track_req_qty -= cur_qty;
itemss[i]->details.item_locked = true;
if(!itemss[i]->TryLockItem(LockReason::LockReason_Crafting)) {
for (int8 s = 0; s < i; s++) {
itemss[i]->TryUnlockItem(LockReason::LockReason_Crafting);
}
return false;
}
player_component_pair_qty->push_back({itemss[i]->details.unique_id, cur_qty});
player_components->push_back(itemss[i]);
if(have_qty >= required_qty)

View File

@ -1,22 +1,23 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
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.
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 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.
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 <http://www.gnu.org/licenses/>.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Sign.h"
#include "../common/ConfigReader.h"
@ -152,6 +153,8 @@ Sign* Sign::Copy(){
new_spawn->SetLootTier(GetLootTier());
new_spawn->SetLootDropType(GetLootDropType());
new_spawn->SetLanguage(GetLanguage());
if(GetSpawnScriptSetDB() && GetSpawnScript())
new_spawn->SetSpawnScript(std::string(GetSpawnScript()));
return new_spawn;
}

View File

@ -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.
@ -120,6 +120,7 @@ Spawn::Spawn(){
has_spawn_proximities = false;
pickup_item_id = 0;
pickup_unique_item_id = 0;
house_character_id = 0;
disable_sounds = false;
has_quests_required = false;
has_history_required = false;
@ -153,6 +154,7 @@ Spawn::Spawn(){
respawn_offset_high = 0;
duplicated_spawn = true;
ResetKnockedBack();
spawn_script_setdb = false;
}
Spawn::~Spawn(){
@ -1925,8 +1927,9 @@ const char* Spawn::GetSpawnScript(){
return 0;
}
void Spawn::SetSpawnScript(string name){
void Spawn::SetSpawnScript(string name, bool db_set){
spawn_script = name;
spawn_script_setdb = db_set;
}
void Spawn::SetPrimaryCommand(const char* name, const char* command, float distance){

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef __EQ2_SPAWN__
#define __EQ2_SPAWN__
@ -1089,8 +1090,9 @@ public:
bool HasProvidedQuests(){
return (quest_ids.size() > 0);
}
void SetSpawnScript(string name);
void SetSpawnScript(string name, bool db_set = false);
const char* GetSpawnScript();
bool GetSpawnScriptSetDB() { return spawn_script_setdb; }
vector<Spawn*>* GetSpawnGroup();
bool HasSpawnGroup();
@ -1321,9 +1323,15 @@ public:
{
pickup_unique_item_id = uniqueid;
}
void SetHouseCharacterID(int32 charid)
{
house_character_id = charid;
}
int32 GetPickupItemID() { return pickup_item_id; }
int32 GetPickupUniqueItemID() { return pickup_unique_item_id; }
int32 GetHouseCharacterID() { return house_character_id; }
bool IsSoundsDisabled() { return disable_sounds; }
void SetSoundsDisabled(bool val) { disable_sounds = val; }
@ -1468,6 +1476,7 @@ protected:
int32 transporter_id;
int32 pickup_item_id;
int32 pickup_unique_item_id;
int32 house_character_id;
map<int32, vector<int16>* > required_quests;
map<int32, LUAHistory*> required_history;
@ -1506,6 +1515,7 @@ private:
int tmp_action_state;
int32 running_to;
string spawn_script;
bool spawn_script_setdb;
static int32 next_id;
ZoneServer* zone;
int32 spawn_location_id;

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "SpellProcess.h"
#include "../common/Log.h"
#include "Tradeskills/Tradeskills.h"
@ -433,10 +434,15 @@ bool SpellProcess::DeleteCasterSpell(LuaSpell* spell, string reason, bool removi
bool ret = false;
Spawn* target = 0;
bool target_valid = false;
if(spell) {
LogWrite(SPELL__INFO, 0, "Spell", "SpellProcess::DeleteCasterSpell Spell: %s, Reason: %s, RemoveTarget: %s.",
spell->spell->GetName(), reason.c_str(), remove_target ? "Yes" : "All");
if(remove_target) {
for (int32 id : spell->GetTargets()) {
if(remove_target->GetID() == id) {
LogWrite(SPELL__INFO, 0, "Spell", "SpellProcess::DeleteCasterSpell RemoveTarget Spell: %s, Reason: %s, Target: %s.",
spell->spell->GetName(), reason.c_str(), remove_target->GetName());
if(remove_target->IsEntity()){
spell->RemoveTarget(remove_target->GetID());
lua_interface->RemoveSpawnFromSpell(spell, remove_target);
@ -485,13 +491,19 @@ bool SpellProcess::DeleteCasterSpell(LuaSpell* spell, string reason, bool removi
CheckRemoveTargetFromSpell(spell, removing_all_spells, removing_all_spells);
ZoneServer* zone = spell->zone;
if(zone) {
LogWrite(SPELL__DEBUG, 0, "Spell", "SpellProcess::DeleteCasterSpell RemoveTargets Spell: %s.",
spell->spell->GetName());
for (int32 id : spell->GetTargets()) {
target = zone->GetSpawnByID(id);
if(target && target->IsEntity()){
LogWrite(SPELL__INFO, 0, "Spell", "SpellProcess::DeleteCasterSpell RemoveTargets Spell: %s, Reason: %s, CurrentTarget: %s (%u).",
spell->spell->GetName(), reason.c_str(), target->GetName(), id);
spell->RemoveTarget(target->GetID());
lua_interface->RemoveSpawnFromSpell(spell, target);
}
else{
LogWrite(SPELL__INFO, 0, "Spell", "SpellProcess::DeleteCasterSpell RemoveTarget Spell: %s, Reason: %s, CurrentTarget: ??Unknown??.",
spell->spell->GetName(), reason.c_str());
spell->RemoveTarget(spell->caster->GetID());
lua_interface->RemoveSpawnFromSpell(spell, spell->caster);
}
@ -637,6 +649,7 @@ bool SpellProcess::CastInstant(Spell* spell, Entity* caster, Entity* target, boo
lua_spell->initial_target = target->GetID();
lua_spell->initial_target_char_id = (target && target->IsPlayer()) ? ((Player*)target)->GetCharacterID() : 0;
GetSpellTargets(lua_spell);
PrintTargets(lua_spell, "instant");
if (!lua_spell->spell->IsCopiedSpell())
{
@ -1019,6 +1032,23 @@ Spell* SpellProcess::GetSpell(Entity* caster){
return spell;
}
void SpellProcess::PrintTargets(LuaSpell* spell, std::string stage) {
std::vector<int32> targets = spell->GetTargets();
if(spell->zone) {
for (int32_t id : targets) {
Spawn* tmp = spell->zone->GetSpawnByID(id);
if(tmp) {
LogWrite(SPELL__INFO, 0, "Spell", "SpellProcess::PrintTarget(%s) Spell: %s, Spawn: %s (%u) (DBID: %u).",
stage.c_str(), spell->spell->GetName(), tmp->GetName(), tmp->GetID(), tmp->GetDatabaseID());
}
else {
LogWrite(SPELL__ERROR, 0, "Spell", "SpellProcess::PrintTarget(%s) Spell: %s SpawnID: %u - ??Unknown??.",
stage.c_str(), spell->spell->GetName(), id);
}
}
}
}
void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster, Spawn* target, bool lock, bool harvest_spell, LuaSpell* customSpell, int16 custom_cast_time, bool in_heroic_opp)
{
if((customSpell != 0 || spell != 0) && caster)
@ -1173,6 +1203,8 @@ void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster,
return;
}
PrintTargets(lua_spell, "cast");
LuaSpell* conflictSpell = caster->HasLinkedTimerID(lua_spell, target, (spell_type == SPELL_TYPE_DD || spell_type == SPELL_TYPE_DOT));
if(conflictSpell)
{
@ -1185,18 +1217,17 @@ void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster,
if(spell->GetSpellData()->friendly_spell)
{
ZoneServer* zone = caster->GetZone();
Spawn* tmpTarget = zone->GetSpawnByID(conflictSpell->initial_target);
if(tmpTarget && tmpTarget->IsEntity())
{
((Entity*)tmpTarget)->RemoveEffectsFromLuaSpell(conflictSpell);
zone->RemoveTargetFromSpell(conflictSpell, tmpTarget, false);
CheckRemoveTargetFromSpell(conflictSpell);
lua_interface->RemoveSpawnFromSpell(conflictSpell, tmpTarget);
if(client && IsReady(conflictSpell->spell, client->GetPlayer()))
UnlockSpell(client, conflictSpell->spell);
for (int32 id : conflictSpell->GetTargets()) {
Spawn* tmpTarget = zone->GetSpawnByID(id);
if(tmpTarget && tmpTarget->IsEntity())
{
((Entity*)tmpTarget)->RemoveEffectsFromLuaSpell(conflictSpell);
zone->RemoveTargetFromSpell(conflictSpell, tmpTarget, false);
CheckRemoveTargetFromSpell(conflictSpell);
lua_interface->RemoveSpawnFromSpell(conflictSpell, tmpTarget);
}
}
DeleteSpell(lua_spell);
return;
}
else if(lua_spell->spell->GetSpellData()->spell_type == SPELL_TYPE_DEBUFF)
{
@ -1205,11 +1236,11 @@ void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster,
}
}
}
else
{
SpellCannotStack(zone, client, lua_spell->caster, lua_spell, conflictSpell);
return;
}
else
{
SpellCannotStack(zone, client, lua_spell->caster, lua_spell, conflictSpell);
return;
}
}
}
@ -1717,8 +1748,8 @@ bool SpellProcess::CastProcessedSpell(LuaSpell* spell, bool passive, bool in_her
}
}
if(client && client->GetCurrentZone() &&
!spell->spell->GetSpellData()->friendly_spell)
PrintTargets(spell, "castprocess");
if(client && client->GetCurrentZone())
{
ZoneServer* zone = client->GetCurrentZone();
Spawn* tmpTarget = zone->GetSpawnByID(spell->initial_target);
@ -1726,8 +1757,30 @@ bool SpellProcess::CastProcessedSpell(LuaSpell* spell, bool passive, bool in_her
LuaSpell* conflictSpell = spell->caster->HasLinkedTimerID(spell, tmpTarget, (spell_type == SPELL_TYPE_DD || spell_type == SPELL_TYPE_DOT));
if(conflictSpell && tmpTarget && tmpTarget->IsEntity())
{
((Entity*)tmpTarget)->RemoveSpellEffect(conflictSpell);
zone->RemoveTargetFromSpell(conflictSpell, tmpTarget);
if(conflictSpell->spell->GetSpellData()->min_class_skill_req > 0 && spell->spell->GetSpellData()->min_class_skill_req > 0)
{
if(conflictSpell->spell->GetSpellData()->min_class_skill_req <= spell->spell->GetSpellData()->min_class_skill_req)
{
if(spell->spell->GetSpellData()->duration_until_cancel && !spell->num_triggers)
{
for (int32 id : conflictSpell->GetTargets()) {
Spawn* tmpTarget = zone->GetSpawnByID(id);
if(tmpTarget && tmpTarget->IsEntity())
{
((Entity*)tmpTarget)->RemoveEffectsFromLuaSpell(conflictSpell);
zone->RemoveTargetFromSpell(conflictSpell, tmpTarget, false);
CheckRemoveTargetFromSpell(conflictSpell);
lua_interface->RemoveSpawnFromSpell(conflictSpell, tmpTarget);
}
}
}
}
else
{
SpellCannotStack(zone, client, spell->caster, spell, conflictSpell);
return false;
}
}
}
}
@ -1813,19 +1866,37 @@ bool SpellProcess::CastProcessedSpell(LuaSpell* spell, bool passive, bool in_her
}
continue;
}
if (firstTarget && !spell->spell->GetSpellData()->not_maintained) {
spell->caster->AddMaintainedSpell(spell);
}
firstTarget = false;
SpellEffects* effect = ((Entity*)target)->GetSpellEffect(spell->spell->GetSpellID());
if (effect && effect->tier > spell->spell->GetSpellTier()) {
if(!effect)
effect = ((Entity*)target)->GetSpellEffectWithLinkedTimer(spell->spell->GetSpellID(), spell->spell->GetSpellData()->linked_timer, spell->spell->GetSpellData()->type_group_spell_id, spell->caster, true);
if (effect && effect->spell != spell && effect->tier > spell->spell->GetSpellTier()) {
if(client) {
spell->caster->GetZone()->SendSpellFailedPacket(client, SPELL_ERROR_TAKE_EFFECT_MOREPOWERFUL);
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "The spell did not take hold as the target already has a better spell of this type.");
if (!passive)
SendFinishedCast(spell, client);
if(spell->zone)
spell->zone->GetSpellProcess()->RemoveSpellScriptTimerBySpell(spell);
DeleteActiveSpell(spell, true);
return false;
}
}
else{
if(effect && effect->spell != spell) {
if(effect->spell->zone)
effect->spell->zone->GetSpellProcess()->RemoveSpellScriptTimerBySpell(effect->spell);
DeleteActiveSpell(effect->spell, true);
}
if (firstTarget) {
spell->caster->AddMaintainedSpell(spell);
}
firstTarget = false;
((Entity*)target)->AddSpellEffect(spell);
if(spell->spell->GetSpellData()->det_type > 0)
((Entity*)target)->AddDetrimentalSpell(spell);
@ -2251,6 +2322,8 @@ void SpellProcess::GetSpellTargets(LuaSpell* luaspell)
}
else // default self cast for group/raid AE
{
if(caster->IsPlayer())
GetPlayerGroupTargets((Player*)caster, caster, luaspell, true);
target = caster;
luaspell->initial_target = caster->GetID();
luaspell->initial_target_char_id = (caster && caster->IsPlayer()) ? ((Player*)caster)->GetCharacterID() : 0;
@ -2818,8 +2891,9 @@ void SpellProcess::CheckRemoveTargetFromSpell(LuaSpell* spell, bool allow_delete
for (int32 id : spell->GetTargets()) {
if (remove_spawn->GetID() == id) {
found_target = true;
spell->RemoveTarget(id);
LogWrite(SPELL__DEBUG, 0, "Spell", "%s CheckRemoveTargetFromSpell %s (%u).", spell->spell->GetName(), remove_spawn->GetName(), remove_spawn->GetID());
LogWrite(SPELL__DEBUG, 0, "Spell", "SpellProcess::CheckRemoveTargetFromSpell Spell: %s, Spawn: %s (%u).", spell->spell->GetName(), remove_spawn->GetName(), remove_spawn->GetID());
if(remove_spawn && std::find(spawnsToRemove.begin(), spawnsToRemove.end(), remove_spawn) == spawnsToRemove.end())
spawnsToRemove.push_back(remove_spawn);
break;
@ -3035,6 +3109,16 @@ void SpellProcess::AddSelfAndPetToCharTargets(LuaSpell* spell, Spawn* caster, bo
void SpellProcess::DeleteActiveSpell(LuaSpell* spell, bool skipRemoveCurrent) {
lua_interface->SetLuaUserDataStale(spell);
if(spell->zone) {
for (int32 id : spell->GetTargets()) {
Spawn* target = spell->zone->GetSpawnByID(id);
if(target)
lua_interface->RemoveSpawnFromSpell(spell, target);
}
}
if (spell->caster) {
((Entity*)spell->caster)->RemoveMaintainedSpell(spell);
}
if(!skipRemoveCurrent) {
lua_interface->RemoveCurrentSpell(spell->state, spell, true);
lua_interface->DeletePendingSpell(spell);
@ -3052,7 +3136,21 @@ bool SpellProcess::AddLuaSpellTarget(LuaSpell* lua_spell, int32 id, bool lock_sp
lua_spell->AddTarget(id);
ret = true;
}
if(lua_spell->zone) {
Spawn* target = lua_spell->zone->GetSpawnByID(id);
if(target && target->IsPlayer()){
lua_spell->AddCharIDTarget(((Player*)target)->GetCharacterID(), 0);
if (((Entity*)target)->HasPet()){
Entity* pet = ((Entity*)target)->GetPet();
if (pet)
lua_spell->AddCharIDTarget(((Player*)target)->GetCharacterID(), PET_TYPE_COMBAT);
pet = ((Entity*)target)->GetCharmedPet();
if (pet)
lua_spell->AddCharIDTarget(((Player*)target)->GetCharacterID(), PET_TYPE_CHARMED);
}
}
}
return ret;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef __EQ2_SPELL_PROCESS__
#define __EQ2_SPELL_PROCESS__
#include <mutex>
@ -182,7 +183,8 @@ public:
/// <param name='lock'>??? not currently used</param>
/// <param name='harvest_spell'>Is this a harvest spell?</param>
void ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster, Spawn* target = 0, bool lock = true, bool harvest_spell = false, LuaSpell* customSpell = 0, int16 custom_cast_time = 0, bool in_heroic_opp = false);
void PrintTargets(LuaSpell* spell, std::string stage);
/// <summary>Cast an EntityCommand (right click menu)</summary>
/// <param name='zone'>The current ZoneServer</param>
/// <param name='entity_command'>the EntityCommand to cast</param>

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string.hpp>
@ -253,6 +254,7 @@ void TradeskillMgr::BeginCrafting(Client* client, vector<pair<int32,int16>> comp
// TODO: use the vecotr to lock inventory slots
vector<pair<int32, int16>>::iterator itr;
vector<pair<int32, int16>>::iterator itr2;
bool missingItem = false;
int32 itemid = 0;
vector<Item*> tmpItems;
@ -266,7 +268,16 @@ void TradeskillMgr::BeginCrafting(Client* client, vector<pair<int32,int16>> comp
break;
}
item->details.item_locked = true;
if(!item->TryLockItem(LockReason::LockReason_Crafting)) {
for (itr = components.begin(); itr != components.end(); itr++) {
itemid = itr->first;
Item* item = client->GetPlayer()->item_list.GetItemFromUniqueID(itemid);
if(item)
item->TryUnlockItem(LockReason::LockReason_Crafting);
}
missingItem = true;
break;
}
tmpItems.push_back(item);
}
@ -280,7 +291,8 @@ void TradeskillMgr::BeginCrafting(Client* client, vector<pair<int32,int16>> comp
vector<Item*>::iterator itemitr;
for (itemitr = tmpItems.begin(); itemitr != tmpItems.end(); itemitr++) {
Item* tmpItem = *itemitr;
tmpItem->details.item_locked = false;
if(tmpItem)
tmpItem->TryUnlockItem(LockReason::LockReason_Crafting);
}
ClientPacketFunctions::StopCrafting(client);
return;
@ -358,13 +370,13 @@ void TradeskillMgr::StopCrafting(Client* client, bool lock) {
item = client->GetPlayer()->item_list.GetItemFromUniqueID(itmid);
if (item && item->details.count <= qty)
{
item->details.item_locked = false;
item->TryUnlockItem(LockReason::LockReason_Crafting);
client->GetPlayer()->item_list.RemoveItem(item);
updateInvReq = true;
}
else if(item) {
item->details.count -= qty;
item->details.item_locked = false;
item->TryUnlockItem(LockReason::LockReason_Crafting);
item->save_needed = true;
updateInvReq = true;
}

View File

@ -1,21 +1,21 @@
/*
EQ2Emu: Everquest II Server Emulator
Copyright (C) 2007-2025 EQ2Emu Development Team (https://www.eq2emu.com)
/*
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 EQ2Emu.
This file is part of EQ2Emulator.
EQ2Emu 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 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.
EQ2Emu 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.
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 EQ2Emu. If not, see <http://www.gnu.org/licenses/>.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#include "PeerManager.h"
@ -855,4 +855,84 @@ void PeerManager::sendPeersActiveQuery(int32 character_id, bool remove) {
peer_https_pool.sendPostRequestToPeerAsync(peer->id, peer->webAddr, std::to_string(peer->webPort), "/activequery", jsonPayload);
}
}
void PeerManager::sendPeersAddSeller(int32 character_id, int32 house_id, std::string name, bool saleEnabled, bool invEnabled) {
boost::property_tree::ptree root;
root.put("character_id", character_id);
root.put("house_id", house_id);
root.put("name", name);
root.put("sale_enabled", saleEnabled);
root.put("inventory_enabled", invEnabled);
std::ostringstream jsonStream;
boost::property_tree::write_json(jsonStream, root);
std::string jsonPayload = jsonStream.str();
LogWrite(PEERING__DEBUG, 0, "Peering", "%s: Add Seller, CharID: %u", __FUNCTION__, character_id);
for (const auto& [id, peer] : peers) {
if (peer->healthCheck.status != HealthStatus::OK)
continue;
peer_https_pool.sendPostRequestToPeerAsync(peer->id, peer->webAddr, std::to_string(peer->webPort), "/addseller", jsonPayload);
}
}
void PeerManager::sendPeersRemoveSeller(int32 character_id) {
boost::property_tree::ptree root;
root.put("character_id", character_id);
std::ostringstream jsonStream;
boost::property_tree::write_json(jsonStream, root);
std::string jsonPayload = jsonStream.str();
LogWrite(PEERING__DEBUG, 0, "Peering", "%s: Remove Seller, CharID: %u", __FUNCTION__, character_id);
for (const auto& [id, peer] : peers) {
if (peer->healthCheck.status != HealthStatus::OK)
continue;
peer_https_pool.sendPostRequestToPeerAsync(peer->id, peer->webAddr, std::to_string(peer->webPort), "/removeseller", jsonPayload);
}
}
void PeerManager::sendPeersAddItemSale(int32 character_id, int32 house_id, int32 itemID, int64 uniqueID, int64 price, sint32 invSlotID, int16 slotID, int16 count, bool inInventory, bool forSale, std::string itemCreator) {
boost::property_tree::ptree root;
root.put("character_id", character_id);
root.put("house_id", house_id);
root.put("item_creator", itemCreator);
root.put("item_id", itemID);
root.put("unique_id", uniqueID);
root.put("price", price);
root.put("inv_slot_id", invSlotID);
root.put("slot_id", slotID);
root.put("count", count);
root.put("in_inventory", inInventory);
root.put("for_sale", forSale);
std::ostringstream jsonStream;
boost::property_tree::write_json(jsonStream, root);
std::string jsonPayload = jsonStream.str();
LogWrite(PEERING__DEBUG, 0, "Peering", "%s: Add Item Sale, CharID: %u, UniqueID: %u, ItemID: %u", __FUNCTION__, character_id, uniqueID, itemID);
for (const auto& [id, peer] : peers) {
if (peer->healthCheck.status != HealthStatus::OK)
continue;
peer_https_pool.sendPostRequestToPeerAsync(peer->id, peer->webAddr, std::to_string(peer->webPort), "/additemsale", jsonPayload);
}
}
void PeerManager::sendPeersRemoveItemSale(int32 character_id, int64 uniqueID) {
boost::property_tree::ptree root;
root.put("character_id", character_id);
root.put("unique_id", uniqueID);
std::ostringstream jsonStream;
boost::property_tree::write_json(jsonStream, root);
std::string jsonPayload = jsonStream.str();
LogWrite(PEERING__DEBUG, 0, "Peering", "%s: Remove Item Sale, CharID: %u, UniqueID: %u", __FUNCTION__, character_id, uniqueID);
for (const auto& [id, peer] : peers) {
if (peer->healthCheck.status != HealthStatus::OK)
continue;
peer_https_pool.sendPostRequestToPeerAsync(peer->id, peer->webAddr, std::to_string(peer->webPort), "/removeitemsale", jsonPayload);
}
}

View File

@ -1,21 +1,21 @@
/*
EQ2Emu: Everquest II Server Emulator
Copyright (C) 2007-2025 EQ2Emu Development Team (https://www.eq2emu.com)
/*
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 EQ2Emu.
This file is part of EQ2Emulator.
EQ2Emu 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 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.
EQ2Emu 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.
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 EQ2Emu. If not, see <http://www.gnu.org/licenses/>.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef PEERMANAGER_H
@ -225,6 +225,12 @@ public:
void sendZonePlayerList(std::vector<string>* queries, std::vector<WhoAllPeerPlayer>* peer_list, bool isGM);
bool GetClientGuildDetails(int32 matchCharID, GuildMember* member_details);
void sendPeersAddSeller(int32 character_id, int32 house_id, std::string name, bool saleEnabled, bool invEnabled);
void sendPeersRemoveSeller(int32 character_id);
void sendPeersAddItemSale(int32 character_id, int32 house_id, int32 itemID, int64 uniqueID, int64 price, sint32 invSlotID, int16 slotID, int16 count, bool inInventory, bool forSale, std::string itemCreator);
void sendPeersRemoveItemSale(int32 character_id, int64 uniqueID);
};

View File

@ -1,21 +1,21 @@
/*
EQ2Emu: Everquest II Server Emulator
Copyright (C) 2007-2025 EQ2Emu Development Team (https://www.eq2emu.com)
/*
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 EQ2Emu.
This file is part of EQ2Emulator.
EQ2Emu 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 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.
EQ2Emu 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.
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 EQ2Emu. If not, see <http://www.gnu.org/licenses/>.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#include "HTTPSClientPool.h"
@ -24,6 +24,7 @@ along with EQ2Emu. If not, see <http://www.gnu.org/licenses/>.
#include "../LoginServer.h"
#include "../LuaInterface.h"
#include "../Guilds/Guild.h"
#include "../Broker/BrokerManager.h"
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
@ -44,6 +45,7 @@ extern MasterSpellList master_spell_list;
extern MasterFactionList master_faction_list;
extern ClientList client_list;
extern GuildList guild_list;
extern BrokerManager broker;
PeerManager peer_manager;
HTTPSClientPool peer_https_pool;
@ -1409,4 +1411,175 @@ void World::Web_worldhandle_activequery(const http::request<http::string_body>&
std::string json = oss.str();
res.body() = json;
res.prepare_payload();
}
void World::Web_worldhandle_addseller(const http::request<http::string_body>& req, http::response<http::string_body>& res) {
res.set(http::field::content_type, "application/json; charset=utf-8");
boost::property_tree::ptree pt, json_tree;
std::istringstream json_stream(req.body());
boost::property_tree::read_json(json_stream, json_tree);
std::string playerName("");
int32 houseID = 0, charID = 0;
bool saleEnabled = false, invEnabled = false, success = false;
if (auto character_id = json_tree.get_optional<int32>("character_id")) {
charID = character_id.get();
}
if (auto name = json_tree.get_optional<std::string>("name")) {
playerName = name.get();
}
if (auto house_id = json_tree.get_optional<int32>("house_id")) {
houseID = house_id.get();
}
if (auto sale_enabled = json_tree.get_optional<bool>("sale_enabled")) {
saleEnabled = sale_enabled.get();
}
if (auto inventory_enabled = json_tree.get_optional<bool>("inventory_enabled")) {
invEnabled = inventory_enabled.get();
}
if(charID) {
broker.AddSeller(charID, playerName, houseID, saleEnabled, invEnabled);
success = true;
}
pt.put("success", success);
pt.put("character_id", charID);
std::ostringstream oss;
boost::property_tree::write_json(oss, pt);
std::string json = oss.str();
res.body() = json;
res.prepare_payload();
}
void World::Web_worldhandle_removeseller(const http::request<http::string_body>& req, http::response<http::string_body>& res) {
res.set(http::field::content_type, "application/json; charset=utf-8");
boost::property_tree::ptree pt, json_tree;
std::istringstream json_stream(req.body());
boost::property_tree::read_json(json_stream, json_tree);
int32 charID = 0;
bool success = false;
if (auto character_id = json_tree.get_optional<int32>("character_id")) {
charID = character_id.get();
}
if(charID) {
broker.RemoveSeller(charID, true);
success = true;
}
pt.put("success", success);
pt.put("character_id", charID);
std::ostringstream oss;
boost::property_tree::write_json(oss, pt);
std::string json = oss.str();
res.body() = json;
res.prepare_payload();
}
void World::Web_worldhandle_additemsale(const http::request<http::string_body>& req, http::response<http::string_body>& res) {
res.set(http::field::content_type, "application/json; charset=utf-8");
boost::property_tree::ptree pt, json_tree;
std::istringstream json_stream(req.body());
boost::property_tree::read_json(json_stream, json_tree);
int32 houseID = 0, charID = 0;
bool success = false;
int32 itemID = 0;
int64 uniqueID = 0, inPrice = 0;
sint32 invSlotID = 0;
int16 slotID = 0, inCount = 0;
bool inInventory = false;
bool forSale = false;
std::string itemCreator("");
if (auto character_id = json_tree.get_optional<int32>("character_id")) {
charID = character_id.get();
}
if (auto house_id = json_tree.get_optional<int32>("house_id")) {
houseID = house_id.get();
}
if (auto item_id = json_tree.get_optional<int32>("item_id")) {
itemID = item_id.get();
}
if (auto unique_id = json_tree.get_optional<int64>("unique_id")) {
uniqueID = unique_id.get();
}
if (auto price = json_tree.get_optional<int64>("price")) {
inPrice = price.get();
}
if (auto inv_slot_id = json_tree.get_optional<sint32>("inv_slot_id")) {
invSlotID = inv_slot_id.get();
}
if (auto slot_id = json_tree.get_optional<int16>("slot_id")) {
slotID = slot_id.get();
}
if (auto count = json_tree.get_optional<int16>("count")) {
inCount = count.get();
}
if (auto in_inventory = json_tree.get_optional<bool>("in_inventory")) {
inInventory = in_inventory.get();
}
if (auto for_sale = json_tree.get_optional<bool>("for_sale")) {
forSale = for_sale.get();
}
if (auto item_creator = json_tree.get_optional<std::string>("item_creator")) {
itemCreator = item_creator.get();
}
if(charID && uniqueID && itemID) {
SaleItem it{};
it.unique_id = uniqueID;
it.character_id = charID;
it.house_id = houseID;
it.item_id = itemID;
it.cost_copper = inPrice;
it.for_sale = forSale;
it.inv_slot_id = invSlotID;
it.slot_id = slotID;
it.count = inCount;
it.from_inventory = inInventory;
it.creator = itemCreator;
broker.AddItem(it, true);
success = true;
}
pt.put("success", success);
pt.put("character_id", charID);
std::ostringstream oss;
boost::property_tree::write_json(oss, pt);
std::string json = oss.str();
res.body() = json;
res.prepare_payload();
}
void World::Web_worldhandle_removeitemsale(const http::request<http::string_body>& req, http::response<http::string_body>& res) {
res.set(http::field::content_type, "application/json; charset=utf-8");
boost::property_tree::ptree pt, json_tree;
std::istringstream json_stream(req.body());
boost::property_tree::read_json(json_stream, json_tree);
int32 charID = 0;
int64 uniqueID = 0;
bool success = false;
if (auto character_id = json_tree.get_optional<int32>("character_id")) {
charID = character_id.get();
}
if (auto unique_id = json_tree.get_optional<int64>("unique_id")) {
uniqueID = unique_id.get();
}
if(charID && uniqueID) {
broker.OnPeerRemoveItem(charID, uniqueID);
success = true;
}
pt.put("success", success);
pt.put("character_id", charID);
std::ostringstream oss;
boost::property_tree::write_json(oss, pt);
std::string json = oss.str();
res.body() = json;
res.prepare_payload();
}

View File

@ -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.
@ -167,6 +167,8 @@ Widget* Widget::Copy(){
new_spawn->SetOpenZ(GetOpenZ());
new_spawn->SetMultiFloorLift(multi_floor_lift);
new_spawn->SetSoundsDisabled(IsSoundsDisabled());
if(GetSpawnScriptSetDB() && GetSpawnScript())
new_spawn->SetSpawnScript(std::string(GetSpawnScript()));
return new_spawn;
}
@ -417,6 +419,7 @@ void Widget::HandleUse(Client* client, string command, int8 overrideWidgetType){
ClientPacketFunctions::SendHouseVisitWindow(client, world.GetAllPlayerHousesByHouseID(hz->id));
ClientPacketFunctions::SendBaseHouseWindow(client, hz, ph, id);
client->GetPlayer()->SetTarget(this);
}
}
}
@ -445,6 +448,7 @@ void Widget::HandleUse(Client* client, string command, int8 overrideWidgetType){
ClientPacketFunctions::SendBaseHouseWindow(client, hz, ph, id);
client->GetCurrentZone()->SendHouseItems(client);
client->GetPlayer()->SetTarget(this);
}
else {
if (hz)

View File

@ -283,6 +283,11 @@ void World::init(std::string web_ipaddr, int16 web_port, std::string cert_file,
world_webserver->register_route("/peerstatus", World::Web_worldhandle_peerstatus);
world_webserver->register_route("/activequery", World::Web_worldhandle_activequery);
world_webserver->register_route("/addseller", World::Web_worldhandle_addseller);
world_webserver->register_route("/removeseller", World::Web_worldhandle_removeseller);
world_webserver->register_route("/additemsale", World::Web_worldhandle_additemsale);
world_webserver->register_route("/removeitemsale", World::Web_worldhandle_removeitemsale);
world_webserver->run();
LogWrite(INIT__INFO, 0, "Init", "World Web Server is listening on %s:%u..", web_ipaddr.c_str(), web_port);
web_success = true;
@ -2641,6 +2646,7 @@ void World::AddPlayerHouse(int32 char_id, int32 house_id, int64 unique_id, int32
ph->escrow_status = escrow_status;
ph->upkeep_due = upkeep_due;
ph->player_name = player_name;
ph->character_id = char_id;
ReloadHouseData(ph);
m_playerHouses[house_id][char_id] = ph;
}
@ -3709,4 +3715,5 @@ void ZoneInfoMemory::LoadFromDatabaseRow(MYSQL_ROW row) {
canGate = atoul(row[24]);
cityZone = atoul(row[25]);
canEvac = atoul(row[26]);
zoneLuaScript = (row[27] != nullptr) ? row[27] : "";
}

View File

@ -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.
@ -229,6 +229,7 @@ struct PlayerHouse {
int64 escrow_coins;
int32 escrow_status;
string player_name;
int32 character_id;
list<Deposit> deposits;
map<string, Deposit> depositsMap;
list<HouseHistory> history;
@ -425,6 +426,7 @@ public:
std::string zoneDescription;
std::string zoneMotd;
std::string zoneSkyFile;
std::string zoneLuaScript;
float underworld;
float safeX, safeY, safeZ, safeHeading;
int16 minimumLevel, maximumLevel, minimumVersion;
@ -738,6 +740,10 @@ public:
static void Web_worldhandle_setguildeventfilter(const http::request<http::string_body>& req, http::response<http::string_body>& res);
static void Web_worldhandle_peerstatus(const http::request<http::string_body>& req, http::response<http::string_body>& res);
static void Web_worldhandle_activequery(const http::request<http::string_body>& req, http::response<http::string_body>& res);
static void Web_worldhandle_addseller(const http::request<http::string_body>& req, http::response<http::string_body>& res);
static void Web_worldhandle_removeseller(const http::request<http::string_body>& req, http::response<http::string_body>& res);
static void Web_worldhandle_additemsale(const http::request<http::string_body>& req, http::response<http::string_body>& res);
static void Web_worldhandle_removeitemsale(const http::request<http::string_body>& req, http::response<http::string_body>& res);
static void Web_populate_status(boost::property_tree::ptree& pt);

View File

@ -1,21 +1,21 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
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.
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 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.
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 <http://www.gnu.org/licenses/>.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#include <math.h>
@ -53,6 +53,7 @@ along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
#include "SpellProcess.h"
#include "races.h"
#include "Web/PeerManager.h"
#include "Broker/BrokerManager.h"
extern Classes classes;
extern Commands commands;
@ -75,6 +76,7 @@ extern RuleManager rule_manager;
extern MasterLanguagesList master_languages_list;
extern ChestTrapList chest_trap_list;
extern PeerManager peer_manager;
BrokerManager broker;
//devn00b: Fix for linux builds since we dont use stricmp we use strcasecmp
#if defined(__GNUC__)
@ -953,23 +955,29 @@ int32 WorldDatabase::LoadAppearances(ZoneServer* zone, Client* client){
return count;
}
void WorldDatabase::LoadNPCs(ZoneServer* zone){
void WorldDatabase::LoadNPCs(ZoneServer* zone, bool isInstanceType){
Query query;
MYSQL_ROW row;
NPC* npc = 0;
int32 id = 0;
int32 total = 0;
MYSQL_RES* result = query.RunQuery2(Q_SELECT,"SELECT npc.spawn_id, s.name, npc.min_level, npc.max_level, npc.enc_level, s.race, s.model_type, npc.class_, npc.gender, s.command_primary, s.command_secondary, s.show_name, npc.min_group_size, npc.max_group_size, npc.hair_type_id, npc.facial_hair_type_id, npc.wing_type_id, npc.chest_type_id, npc.legs_type_id, npc.soga_hair_type_id, npc.soga_facial_hair_type_id, s.attackable, s.show_level, s.targetable, s.show_command_icon, s.display_hand_icon, s.hp, s.power, s.size, s.collision_radius, npc.action_state, s.visual_state, npc.mood_state, npc.initial_state, npc.activity_status, s.faction_id, s.sub_title, s.merchant_id, s.merchant_type, s.size_offset, npc.attack_type, npc.ai_strategy+0, npc.spell_list_id, npc.secondary_spell_list_id, npc.skill_list_id, npc.secondary_skill_list_id, npc.equipment_list_id, npc.str, npc.sta, npc.wis, npc.intel, npc.agi, npc.heat, npc.cold, npc.magic, npc.mental, npc.divine, npc.disease, npc.poison, npc.aggro_radius, npc.cast_percentage, npc.randomize, npc.soga_model_type, npc.heroic_flag, npc.alignment, npc.elemental, npc.arcane, npc.noxious, s.savagery, s.dissonance, npc.hide_hood, npc.emote_state, s.prefix, s.suffix, s.last_name, s.expansion_flag, s.holiday_flag, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards, npc.water_type, npc.flying_type, s.loot_tier, s.loot_drop_type, npc.scared_by_strong_players, npc.action_state_str\n"
"FROM spawn s\n"
"INNER JOIN spawn_npcs npc\n"
std::string houseTable("");
if(isInstanceType) {
houseTable.append("_houses");
}
MYSQL_RES* result = query.RunQuery2(Q_SELECT,"SELECT npc.spawn_id, s.name, npc.min_level, npc.max_level, npc.enc_level, s.race, s.model_type, npc.class_, npc.gender, s.command_primary, s.command_secondary, s.show_name, npc.min_group_size, npc.max_group_size, npc.hair_type_id, npc.facial_hair_type_id, npc.wing_type_id, npc.chest_type_id, npc.legs_type_id, npc.soga_hair_type_id, npc.soga_facial_hair_type_id, s.attackable, s.show_level, s.targetable, s.show_command_icon, s.display_hand_icon, s.hp, s.power, s.size, s.collision_radius, npc.action_state, s.visual_state, npc.mood_state, npc.initial_state, npc.activity_status, s.faction_id, s.sub_title, s.merchant_id, s.merchant_type, s.size_offset, npc.attack_type, npc.ai_strategy+0, npc.spell_list_id, npc.secondary_spell_list_id, npc.skill_list_id, npc.secondary_skill_list_id, npc.equipment_list_id, npc.str, npc.sta, npc.wis, npc.intel, npc.agi, npc.heat, npc.cold, npc.magic, npc.mental, npc.divine, npc.disease, npc.poison, npc.aggro_radius, npc.cast_percentage, npc.randomize, npc.soga_model_type, npc.heroic_flag, npc.alignment, npc.elemental, npc.arcane, npc.noxious, s.savagery, s.dissonance, npc.hide_hood, npc.emote_state, s.prefix, s.suffix, s.last_name, s.expansion_flag, s.holiday_flag, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards, npc.water_type, npc.flying_type, s.loot_tier, s.loot_drop_type, npc.scared_by_strong_players, npc.action_state_str, s.lua_script\n"
"FROM spawn%s s\n"
"INNER JOIN spawn_npcs%s npc\n"
"ON s.id = npc.spawn_id\n"
"INNER JOIN spawn_location_entry le\n"
"INNER JOIN spawn_location_entry%s le\n"
"ON npc.spawn_id = le.spawn_id\n"
"INNER JOIN spawn_location_placement lp\n"
"INNER JOIN spawn_location_placement%s lp\n"
"ON le.spawn_location_id = lp.spawn_location_id\n"
"WHERE lp.zone_id = %u and (lp.instance_id = 0 or lp.instance_id = %u)\n"
"GROUP BY s.id",
zone->GetZoneID(), zone->GetInstanceID());
houseTable.c_str(), houseTable.c_str(), houseTable.c_str(), houseTable.c_str(), zone->GetZoneID(), zone->GetInstanceID());
while(result && (row = mysql_fetch_row(result))){
/*npc->SetAppearanceID(atoi(row[12]));
AppearanceData* appearance = world.GetNPCAppearance(npc->GetAppearanceID());
@ -1128,6 +1136,11 @@ void WorldDatabase::LoadNPCs(ZoneServer* zone){
npc->GetInfoStruct()->set_action_state(action_state_str);
}
if(row[87]){
std::string lua_script = std::string(row[87]);
npc->SetSpawnScript(lua_script, true);
}
zone->AddNPC(id, npc);
total++;
LogWrite(NPC__DEBUG, 5, "NPC", "---Loading NPC: '%s' (%u)", npc->appearance.name, id);
@ -1243,23 +1256,27 @@ void WorldDatabase::LoadSpiritShards(ZoneServer* zone){
LogWrite(NPC__INFO, 0, "NPC", "--Loaded %i Spirit Shard(s).", total);
}
void WorldDatabase::LoadSigns(ZoneServer* zone){
void WorldDatabase::LoadSigns(ZoneServer* zone, bool isInstanceType){
Query query;
MYSQL_ROW row;
Sign* sign = 0;
int32 id = 0;
int32 total = 0;
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT ss.spawn_id, s.name, s.model_type, s.size, s.show_command_icon, ss.widget_id, ss.widget_x, ss.widget_y, ss.widget_z, s.command_primary, s.command_secondary, s.collision_radius, ss.icon, ss.type, ss.title, ss.description, ss.sign_distance, ss.zone_id, ss.zone_x, ss.zone_y, ss.zone_z, ss.zone_heading, ss.include_heading, ss.include_location, s.transport_id, s.size_offset, s.display_hand_icon, s.visual_state, s.expansion_flag, s.holiday_flag, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards, s.loot_tier, s.loot_drop_type, ss.language\n"
"FROM spawn s\n"
"INNER JOIN spawn_signs ss\n"
std::string houseTable("");
if(isInstanceType) {
houseTable.append("_houses");
}
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT ss.spawn_id, s.name, s.model_type, s.size, s.show_command_icon, ss.widget_id, ss.widget_x, ss.widget_y, ss.widget_z, s.command_primary, s.command_secondary, s.collision_radius, ss.icon, ss.type, ss.title, ss.description, ss.sign_distance, ss.zone_id, ss.zone_x, ss.zone_y, ss.zone_z, ss.zone_heading, ss.include_heading, ss.include_location, s.transport_id, s.size_offset, s.display_hand_icon, s.visual_state, s.expansion_flag, s.holiday_flag, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards, s.loot_tier, s.loot_drop_type, ss.language, s.lua_script\n"
"FROM spawn%s s\n"
"INNER JOIN spawn_signs%s ss\n"
"ON s.id = ss.spawn_id\n"
"INNER JOIN spawn_location_entry le\n"
"INNER JOIN spawn_location_entry%s le\n"
"ON ss.spawn_id = le.spawn_id\n"
"INNER JOIN spawn_location_placement lp\n"
"INNER JOIN spawn_location_placement%s lp\n"
"ON le.spawn_location_id = lp.spawn_location_id\n"
"WHERE lp.zone_id = %u and (lp.instance_id = 0 or lp.instance_id = %u)\n"
"GROUP BY s.id",
zone->GetZoneID(), zone->GetInstanceID());
houseTable.c_str(), houseTable.c_str(), houseTable.c_str(), houseTable.c_str(), zone->GetZoneID(), zone->GetInstanceID());
while(result && (row = mysql_fetch_row(result))){
int32 signXpackFlag = atoul(row[28]);
@ -1330,6 +1347,11 @@ void WorldDatabase::LoadSigns(ZoneServer* zone){
sign->SetLanguage(atoul(row[36]));
if(row[37]){
std::string lua_script = std::string(row[37]);
sign->SetSpawnScript(lua_script, true);
}
zone->AddSign(id, sign);
total++;
@ -1339,23 +1361,28 @@ void WorldDatabase::LoadSigns(ZoneServer* zone){
LogWrite(SIGN__DEBUG, 0, "Sign", "--Loaded %i Sign(s)", total);
}
void WorldDatabase::LoadWidgets(ZoneServer* zone){
void WorldDatabase::LoadWidgets(ZoneServer* zone, bool isInstanceType){
Query query;
MYSQL_ROW row;
Widget* widget = 0;
int32 id = 0;
int32 total = 0;
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT sw.spawn_id, s.name, s.model_type, s.size, s.show_command_icon, sw.widget_id, sw.widget_x, sw.widget_y, sw.widget_z, s.command_primary, s.command_secondary, s.collision_radius, sw.include_heading, sw.include_location, sw.icon, sw.type, sw.open_heading, sw.open_y, sw.action_spawn_id, sw.open_sound_file, sw.close_sound_file, sw.open_duration, sw.closed_heading, sw.linked_spawn_id, sw.close_y, s.transport_id, s.size_offset, sw.house_id, sw.open_x, sw.open_z, sw.close_x, sw.close_z, s.display_hand_icon, s.expansion_flag, s.holiday_flag, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards, s.loot_tier, s.loot_drop_type\n"
"FROM spawn s\n"
"INNER JOIN spawn_widgets sw\n"
std::string houseTable("");
if(isInstanceType) {
houseTable.append("_houses");
}
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT sw.spawn_id, s.name, s.model_type, s.size, s.show_command_icon, sw.widget_id, sw.widget_x, sw.widget_y, sw.widget_z, s.command_primary, s.command_secondary, s.collision_radius, sw.include_heading, sw.include_location, sw.icon, sw.type, sw.open_heading, sw.open_y, sw.action_spawn_id, sw.open_sound_file, sw.close_sound_file, sw.open_duration, sw.closed_heading, sw.linked_spawn_id, sw.close_y, s.transport_id, s.size_offset, sw.house_id, sw.open_x, sw.open_z, sw.close_x, sw.close_z, s.display_hand_icon, s.expansion_flag, s.holiday_flag, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards, s.loot_tier, s.loot_drop_type, s.lua_script\n"
"FROM spawn%s s\n"
"INNER JOIN spawn_widgets%s sw\n"
"ON s.id = sw.spawn_id\n"
"INNER JOIN spawn_location_entry le\n"
"INNER JOIN spawn_location_entry%s le\n"
"ON sw.spawn_id = le.spawn_id\n"
"INNER JOIN spawn_location_placement lp\n"
"INNER JOIN spawn_location_placement%s lp\n"
"ON le.spawn_location_id = lp.spawn_location_id\n"
"WHERE lp.zone_id = %u and (lp.instance_id = 0 or lp.instance_id = %u)\n"
"GROUP BY s.id",
zone->GetZoneID(), zone->GetInstanceID());
houseTable.c_str(), houseTable.c_str(), houseTable.c_str(), houseTable.c_str(), zone->GetZoneID(), zone->GetInstanceID());
while(result && (row = mysql_fetch_row(result))){
int32 widgetXpackFlag = atoul(row[33]);
int32 widgetHolidayFlag = atoul(row[34]);
@ -1437,7 +1464,12 @@ void WorldDatabase::LoadWidgets(ZoneServer* zone){
widget->SetLootTier(atoul(row[39]));
widget->SetLootDropType(atoul(row[40]));
if(row[41]){
std::string lua_script = std::string(row[41]);
widget->SetSpawnScript(lua_script, true);
}
zone->AddWidget(id, widget);
total++;
@ -1447,23 +1479,28 @@ void WorldDatabase::LoadWidgets(ZoneServer* zone){
LogWrite(WIDGET__DEBUG, 0, "Widget", "--Loaded %i Widget(s)", total);
}
void WorldDatabase::LoadObjects(ZoneServer* zone){
void WorldDatabase::LoadObjects(ZoneServer* zone, bool isInstanceType){
Query query;
MYSQL_ROW row;
Object* object = 0;
int32 id = 0;
int32 total = 0;
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT so.spawn_id, s.name, s.race, s.model_type, s.command_primary, s.command_secondary, s.targetable, s.size, s.show_name, s.visual_state, s.attackable, s.show_level, s.show_command_icon, s.display_hand_icon, s.faction_id, s.collision_radius, s.transport_id, s.size_offset, so.device_id, s.expansion_flag, s.holiday_flag, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards, s.loot_tier, s.loot_drop_type\n"
"FROM spawn s\n"
"INNER JOIN spawn_objects so\n"
std::string houseTable("");
if(isInstanceType) {
houseTable.append("_houses");
}
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT so.spawn_id, s.name, s.race, s.model_type, s.command_primary, s.command_secondary, s.targetable, s.size, s.show_name, s.visual_state, s.attackable, s.show_level, s.show_command_icon, s.display_hand_icon, s.faction_id, s.collision_radius, s.transport_id, s.size_offset, so.device_id, s.expansion_flag, s.holiday_flag, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards, s.loot_tier, s.loot_drop_type, s.lua_script\n"
"FROM spawn%s s\n"
"INNER JOIN spawn_objects%s so\n"
"ON s.id = so.spawn_id\n"
"INNER JOIN spawn_location_entry le\n"
"INNER JOIN spawn_location_entry%s le\n"
"ON so.spawn_id = le.spawn_id\n"
"INNER JOIN spawn_location_placement lp\n"
"INNER JOIN spawn_location_placement%s lp\n"
"ON le.spawn_location_id = lp.spawn_location_id\n"
"WHERE lp.zone_id = %u and (lp.instance_id = 0 or lp.instance_id = %u)\n"
"GROUP BY s.id",
zone->GetZoneID(), zone->GetInstanceID());
"GROUP BY s.id",
houseTable.c_str(), houseTable.c_str(), houseTable.c_str(), houseTable.c_str(), zone->GetZoneID(), zone->GetInstanceID());
while(result && (row = mysql_fetch_row(result))){
@ -1519,6 +1556,11 @@ void WorldDatabase::LoadObjects(ZoneServer* zone){
object->SetLootDropType(atoul(row[26]));
if(row[27]){
std::string lua_script = std::string(row[27]);
object->SetSpawnScript(lua_script, true);
}
zone->AddObject(id, object);
total++;
@ -1528,23 +1570,28 @@ void WorldDatabase::LoadObjects(ZoneServer* zone){
LogWrite(OBJECT__DEBUG, 0, "Object", "--Loaded %i Object(s)", total);
}
void WorldDatabase::LoadGroundSpawns(ZoneServer* zone){
void WorldDatabase::LoadGroundSpawns(ZoneServer* zone, bool isInstanceType){
Query query;
MYSQL_ROW row;
GroundSpawn* spawn = 0;
int32 id = 0;
int32 total = 0;
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT sg.spawn_id, s.name, s.race, s.model_type, s.command_primary, s.command_secondary, s.targetable, s.size, s.show_name, s.visual_state, s.attackable, s.show_level, s.show_command_icon, s.display_hand_icon, s.faction_id, s.collision_radius, sg.number_harvests, sg.num_attempts_per_harvest, sg.groundspawn_id, sg.collection_skill, s.size_offset, s.expansion_flag, s.holiday_flag, s.disable_sounds, s.aaxp_rewards, s.loot_tier, s.loot_drop_type\n"
"FROM spawn s\n"
"INNER JOIN spawn_ground sg\n"
std::string houseTable("");
if(isInstanceType) {
houseTable.append("_houses");
}
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT sg.spawn_id, s.name, s.race, s.model_type, s.command_primary, s.command_secondary, s.targetable, s.size, s.show_name, s.visual_state, s.attackable, s.show_level, s.show_command_icon, s.display_hand_icon, s.faction_id, s.collision_radius, sg.number_harvests, sg.num_attempts_per_harvest, sg.groundspawn_id, sg.collection_skill, s.size_offset, s.expansion_flag, s.holiday_flag, s.disable_sounds, s.aaxp_rewards, s.loot_tier, s.loot_drop_type, s.lua_script\n"
"FROM spawn%s s\n"
"INNER JOIN spawn_ground%s sg\n"
"ON s.id = sg.spawn_id\n"
"INNER JOIN spawn_location_entry le\n"
"INNER JOIN spawn_location_entry%s le\n"
"ON sg.spawn_id = le.spawn_id\n"
"INNER JOIN spawn_location_placement lp\n"
"INNER JOIN spawn_location_placement%s lp\n"
"ON le.spawn_location_id = lp.spawn_location_id\n"
"WHERE lp.zone_id = %u and (lp.instance_id = 0 or lp.instance_id = %u)\n"
"GROUP BY s.id",
zone->GetZoneID(), zone->GetInstanceID());
houseTable.c_str(), houseTable.c_str(), houseTable.c_str(), houseTable.c_str(), zone->GetZoneID(), zone->GetInstanceID());
while(result && (row = mysql_fetch_row(result))){
int32 gsXpackFlag = atoul(row[21]);
@ -1601,6 +1648,11 @@ void WorldDatabase::LoadGroundSpawns(ZoneServer* zone){
spawn->SetLootDropType(atoul(row[26]));
if(row[27]){
std::string lua_script = std::string(row[27]);
spawn->SetSpawnScript(lua_script, true);
}
zone->AddGroundSpawn(id, spawn);
total++;
LogWrite(GROUNDSPAWN__DEBUG, 5, "GSpawn", "---Loading GroundSpawn: '%s' (%u)", spawn->appearance.name, id);
@ -3551,6 +3603,8 @@ void WorldDatabase::LoadSpawns(ZoneServer* zone)
LogWrite(SPAWN__TRACE, 0, "Spawn", "Enter LoadSpawns");
int8 isInstanceType = (zone->GetInstanceType() == Instance_Type::PERSONAL_HOUSE_INSTANCE);
if(zone->GetInstanceID() == 0) {
npcs = ProcessSpawnLocations(zone, "SELECT sln.id, sle.id, slp.x, slp.y, slp.z, slp.x_offset, slp.y_offset, slp.z_offset, slp.heading, sle.spawn_id, sle.spawnpercentage, slp.respawn, slp.respawn_offset_low, slp.respawn_offset_high, slp.duplicated_spawn, slp.grid_id, slp.id, slp.expire_timer, slp.expire_offset, slp.pitch, slp.roll, sle.condition, slp.lvl_override, slp.hp_override, slp.mp_override, slp.str_override, slp.sta_override, slp.wis_override, slp.int_override, slp.agi_override, slp.heat_override, slp.cold_override, slp.magic_override, slp.mental_override, slp.divine_override, slp.disease_override, slp.poison_override, difficulty_override FROM spawn_location_placement slp, spawn_location_name sln, spawn_location_entry sle, spawn_npcs sn where sn.spawn_id = sle.spawn_id and sln.id = sle.spawn_location_id and sln.id = slp.spawn_location_id and slp.zone_id=%u and slp.instance_id=%u ORDER BY sln.id, sle.id", SPAWN_ENTRY_TYPE_NPC);
objects = ProcessSpawnLocations(zone, "SELECT sln.id, sle.id, slp.x, slp.y, slp.z, slp.x_offset, slp.y_offset, slp.z_offset, slp.heading, sle.spawn_id, sle.spawnpercentage, slp.respawn, slp.respawn_offset_low, slp.respawn_offset_high, slp.duplicated_spawn, slp.grid_id, slp.id, slp.expire_timer, slp.expire_offset, slp.pitch, slp.roll, sle.condition, slp.lvl_override, slp.hp_override, slp.mp_override, slp.str_override, slp.sta_override, slp.wis_override, slp.int_override, slp.agi_override, slp.heat_override, slp.cold_override, slp.magic_override, slp.mental_override, slp.divine_override, slp.disease_override, slp.poison_override, difficulty_override FROM spawn_location_placement slp, spawn_location_name sln, spawn_location_entry sle, spawn_objects so where so.spawn_id = sle.spawn_id and sln.id = sle.spawn_location_id and sln.id = slp.spawn_location_id and slp.zone_id=%u and slp.instance_id=%u ORDER BY sln.id, sle.id", SPAWN_ENTRY_TYPE_OBJECT);
@ -3561,6 +3615,19 @@ void WorldDatabase::LoadSpawns(ZoneServer* zone)
spawn_group_associations = LoadSpawnLocationGroupAssociations(zone);
spawn_group_chances = LoadSpawnGroupChances(zone);
}
else if(isInstanceType) {
npcs = ProcessSpawnLocations(zone, "SELECT sln.id, sle.id, slp.x, slp.y, slp.z, slp.x_offset, slp.y_offset, slp.z_offset, slp.heading, sle.spawn_id, sle.spawnpercentage, slp.respawn, slp.respawn_offset_low, slp.respawn_offset_high, slp.duplicated_spawn, slp.grid_id, slp.id, slp.expire_timer, slp.expire_offset, slp.pitch, slp.roll, sle.condition, slp.lvl_override, slp.hp_override, slp.mp_override, slp.str_override, slp.sta_override, slp.wis_override, slp.int_override, slp.agi_override, slp.heat_override, slp.cold_override, slp.magic_override, slp.mental_override, slp.divine_override, slp.disease_override, slp.poison_override, difficulty_override FROM spawn_location_placement_houses slp, spawn_location_name_houses sln, spawn_location_entry_houses sle, spawn_npcs_houses sn where sn.spawn_id = sle.spawn_id and sln.id = sle.spawn_location_id and sln.id = slp.spawn_location_id and slp.zone_id=%i and (slp.instance_id = 0 or slp.instance_id=%u) ORDER BY sln.id, sle.id", SPAWN_ENTRY_TYPE_NPC);
objects = ProcessSpawnLocations(zone, "SELECT sln.id, sle.id, slp.x, slp.y, slp.z, slp.x_offset, slp.y_offset, slp.z_offset, slp.heading, sle.spawn_id, sle.spawnpercentage, slp.respawn, slp.respawn_offset_low, slp.respawn_offset_high, slp.duplicated_spawn, slp.grid_id, slp.id, slp.expire_timer, slp.expire_offset, slp.pitch, slp.roll, sle.condition, slp.lvl_override, slp.hp_override, slp.mp_override, slp.str_override, slp.sta_override, slp.wis_override, slp.int_override, slp.agi_override, slp.heat_override, slp.cold_override, slp.magic_override, slp.mental_override, slp.divine_override, slp.disease_override, slp.poison_override, difficulty_override FROM spawn_location_placement_houses slp, spawn_location_name_houses sln, spawn_location_entry_houses sle, spawn_objects_houses so where so.spawn_id = sle.spawn_id and sln.id = sle.spawn_location_id and sln.id = slp.spawn_location_id and slp.zone_id=%i and (slp.instance_id = 0 or slp.instance_id=%u) ORDER BY sln.id, sle.id", SPAWN_ENTRY_TYPE_OBJECT);
widgets = ProcessSpawnLocations(zone, "SELECT sln.id, sle.id, slp.x, slp.y, slp.z, slp.x_offset, slp.y_offset, slp.z_offset, slp.heading, sle.spawn_id, sle.spawnpercentage, slp.respawn, slp.respawn_offset_low, slp.respawn_offset_high, slp.duplicated_spawn, slp.grid_id, slp.id, slp.expire_timer, slp.expire_offset, slp.pitch, slp.roll, sle.condition, slp.lvl_override, slp.hp_override, slp.mp_override, slp.str_override, slp.sta_override, slp.wis_override, slp.int_override, slp.agi_override, slp.heat_override, slp.cold_override, slp.magic_override, slp.mental_override, slp.divine_override, slp.disease_override, slp.poison_override, difficulty_override FROM spawn_location_placement_houses slp, spawn_location_name_houses sln, spawn_location_entry_houses sle, spawn_widgets_houses sw where sw.spawn_id = sle.spawn_id and sln.id = sle.spawn_location_id and sln.id = slp.spawn_location_id and slp.zone_id=%i and (slp.instance_id = 0 or slp.instance_id=%u) ORDER BY sln.id, sle.id", SPAWN_ENTRY_TYPE_WIDGET);
signs = ProcessSpawnLocations(zone, "SELECT sln.id, sle.id, slp.x, slp.y, slp.z, slp.x_offset, slp.y_offset, slp.z_offset, slp.heading, sle.spawn_id, sle.spawnpercentage, slp.respawn, slp.respawn_offset_low, slp.respawn_offset_high, slp.duplicated_spawn, slp.grid_id, slp.id, slp.expire_timer, slp.expire_offset, slp.pitch, slp.roll, sle.condition, slp.lvl_override, slp.hp_override, slp.mp_override, slp.str_override, slp.sta_override, slp.wis_override, slp.int_override, slp.agi_override, slp.heat_override, slp.cold_override, slp.magic_override, slp.mental_override, slp.divine_override, slp.disease_override, slp.poison_override, difficulty_override FROM spawn_location_placement_houses slp, spawn_location_name_houses sln, spawn_location_entry_houses sle, spawn_signs_houses ss where ss.spawn_id = sle.spawn_id and sln.id = sle.spawn_location_id and sln.id = slp.spawn_location_id and slp.zone_id=%i and (slp.instance_id = 0 or slp.instance_id=%u) ORDER BY sln.id, sle.id", SPAWN_ENTRY_TYPE_SIGN);
ground_spawns = ProcessSpawnLocations(zone, "SELECT sln.id, sle.id, slp.x, slp.y, slp.z, slp.x_offset, slp.y_offset, slp.z_offset, slp.heading, sle.spawn_id, sle.spawnpercentage, slp.respawn, slp.respawn_offset_low, slp.respawn_offset_high, slp.duplicated_spawn, slp.grid_id, slp.id, slp.expire_timer, slp.expire_offset, slp.pitch, slp.roll, sle.condition, slp.lvl_override, slp.hp_override, slp.mp_override, slp.str_override, slp.sta_override, slp.wis_override, slp.int_override, slp.agi_override, slp.heat_override, slp.cold_override, slp.magic_override, slp.mental_override, slp.divine_override, slp.disease_override, slp.poison_override, difficulty_override FROM spawn_location_placement_houses slp, spawn_location_name_houses sln, spawn_location_entry_houses sle, spawn_ground_houses sg where sg.spawn_id = sle.spawn_id and sln.id = sle.spawn_location_id and sln.id = slp.spawn_location_id and slp.zone_id=%i and (slp.instance_id = 0 or slp.instance_id=%u) ORDER BY sln.id, sle.id", SPAWN_ENTRY_TYPE_GROUNDSPAWN);
npcs += ProcessSpawnLocations(zone, "SELECT sln.id, sle.id, slp.x, slp.y, slp.z, slp.x_offset, slp.y_offset, slp.z_offset, slp.heading, sle.spawn_id, sle.spawnpercentage, slp.respawn, slp.respawn_offset_low, slp.respawn_offset_high, slp.duplicated_spawn, slp.grid_id, slp.id, slp.expire_timer, slp.expire_offset, slp.pitch, slp.roll, sle.condition, slp.lvl_override, slp.hp_override, slp.mp_override, slp.str_override, slp.sta_override, slp.wis_override, slp.int_override, slp.agi_override, slp.heat_override, slp.cold_override, slp.magic_override, slp.mental_override, slp.divine_override, slp.disease_override, slp.poison_override, difficulty_override FROM spawn_location_placement slp, spawn_location_name sln, spawn_location_entry sle, spawn_npcs sn where sn.spawn_id = sle.spawn_id and sln.id = sle.spawn_location_id and sln.id = slp.spawn_location_id and slp.zone_id=%i and (slp.instance_id = 0 or slp.instance_id=%u) ORDER BY sln.id, sle.id", SPAWN_ENTRY_TYPE_NPC);
objects += ProcessSpawnLocations(zone, "SELECT sln.id, sle.id, slp.x, slp.y, slp.z, slp.x_offset, slp.y_offset, slp.z_offset, slp.heading, sle.spawn_id, sle.spawnpercentage, slp.respawn, slp.respawn_offset_low, slp.respawn_offset_high, slp.duplicated_spawn, slp.grid_id, slp.id, slp.expire_timer, slp.expire_offset, slp.pitch, slp.roll, sle.condition, slp.lvl_override, slp.hp_override, slp.mp_override, slp.str_override, slp.sta_override, slp.wis_override, slp.int_override, slp.agi_override, slp.heat_override, slp.cold_override, slp.magic_override, slp.mental_override, slp.divine_override, slp.disease_override, slp.poison_override, difficulty_override FROM spawn_location_placement slp, spawn_location_name sln, spawn_location_entry sle, spawn_objects so where so.spawn_id = sle.spawn_id and sln.id = sle.spawn_location_id and sln.id = slp.spawn_location_id and slp.zone_id=%i and (slp.instance_id = 0 or slp.instance_id=%u) ORDER BY sln.id, sle.id", SPAWN_ENTRY_TYPE_OBJECT);
widgets += ProcessSpawnLocations(zone, "SELECT sln.id, sle.id, slp.x, slp.y, slp.z, slp.x_offset, slp.y_offset, slp.z_offset, slp.heading, sle.spawn_id, sle.spawnpercentage, slp.respawn, slp.respawn_offset_low, slp.respawn_offset_high, slp.duplicated_spawn, slp.grid_id, slp.id, slp.expire_timer, slp.expire_offset, slp.pitch, slp.roll, sle.condition, slp.lvl_override, slp.hp_override, slp.mp_override, slp.str_override, slp.sta_override, slp.wis_override, slp.int_override, slp.agi_override, slp.heat_override, slp.cold_override, slp.magic_override, slp.mental_override, slp.divine_override, slp.disease_override, slp.poison_override, difficulty_override FROM spawn_location_placement slp, spawn_location_name sln, spawn_location_entry sle, spawn_widgets sw where sw.spawn_id = sle.spawn_id and sln.id = sle.spawn_location_id and sln.id = slp.spawn_location_id and slp.zone_id=%i and (slp.instance_id = 0 or slp.instance_id=%u) ORDER BY sln.id, sle.id", SPAWN_ENTRY_TYPE_WIDGET);
signs += ProcessSpawnLocations(zone, "SELECT sln.id, sle.id, slp.x, slp.y, slp.z, slp.x_offset, slp.y_offset, slp.z_offset, slp.heading, sle.spawn_id, sle.spawnpercentage, slp.respawn, slp.respawn_offset_low, slp.respawn_offset_high, slp.duplicated_spawn, slp.grid_id, slp.id, slp.expire_timer, slp.expire_offset, slp.pitch, slp.roll, sle.condition, slp.lvl_override, slp.hp_override, slp.mp_override, slp.str_override, slp.sta_override, slp.wis_override, slp.int_override, slp.agi_override, slp.heat_override, slp.cold_override, slp.magic_override, slp.mental_override, slp.divine_override, slp.disease_override, slp.poison_override, difficulty_override FROM spawn_location_placement slp, spawn_location_name sln, spawn_location_entry sle, spawn_signs ss where ss.spawn_id = sle.spawn_id and sln.id = sle.spawn_location_id and sln.id = slp.spawn_location_id and slp.zone_id=%i and (slp.instance_id = 0 or slp.instance_id=%u) ORDER BY sln.id, sle.id", SPAWN_ENTRY_TYPE_SIGN);
ground_spawns += ProcessSpawnLocations(zone, "SELECT sln.id, sle.id, slp.x, slp.y, slp.z, slp.x_offset, slp.y_offset, slp.z_offset, slp.heading, sle.spawn_id, sle.spawnpercentage, slp.respawn, slp.respawn_offset_low, slp.respawn_offset_high, slp.duplicated_spawn, slp.grid_id, slp.id, slp.expire_timer, slp.expire_offset, slp.pitch, slp.roll, sle.condition, slp.lvl_override, slp.hp_override, slp.mp_override, slp.str_override, slp.sta_override, slp.wis_override, slp.int_override, slp.agi_override, slp.heat_override, slp.cold_override, slp.magic_override, slp.mental_override, slp.divine_override, slp.disease_override, slp.poison_override, difficulty_override FROM spawn_location_placement slp, spawn_location_name sln, spawn_location_entry sle, spawn_ground sg where sg.spawn_id = sle.spawn_id and sln.id = sle.spawn_location_id and sln.id = slp.spawn_location_id and slp.zone_id=%i and (slp.instance_id = 0 or slp.instance_id=%u) ORDER BY sln.id, sle.id", SPAWN_ENTRY_TYPE_GROUNDSPAWN);
}
else {
npcs = ProcessSpawnLocations(zone, "SELECT sln.id, sle.id, slp.x, slp.y, slp.z, slp.x_offset, slp.y_offset, slp.z_offset, slp.heading, sle.spawn_id, sle.spawnpercentage, slp.respawn, slp.respawn_offset_low, slp.respawn_offset_high, slp.duplicated_spawn, slp.grid_id, slp.id, slp.expire_timer, slp.expire_offset, slp.pitch, slp.roll, sle.condition, slp.lvl_override, slp.hp_override, slp.mp_override, slp.str_override, slp.sta_override, slp.wis_override, slp.int_override, slp.agi_override, slp.heat_override, slp.cold_override, slp.magic_override, slp.mental_override, slp.divine_override, slp.disease_override, slp.poison_override, difficulty_override FROM spawn_location_placement slp, spawn_location_name sln, spawn_location_entry sle, spawn_npcs sn where sn.spawn_id = sle.spawn_id and sln.id = sle.spawn_location_id and sln.id = slp.spawn_location_id and slp.zone_id=%i and (slp.instance_id = 0 or slp.instance_id=%u) ORDER BY sln.id, sle.id", SPAWN_ENTRY_TYPE_NPC);
objects = ProcessSpawnLocations(zone, "SELECT sln.id, sle.id, slp.x, slp.y, slp.z, slp.x_offset, slp.y_offset, slp.z_offset, slp.heading, sle.spawn_id, sle.spawnpercentage, slp.respawn, slp.respawn_offset_low, slp.respawn_offset_high, slp.duplicated_spawn, slp.grid_id, slp.id, slp.expire_timer, slp.expire_offset, slp.pitch, slp.roll, sle.condition, slp.lvl_override, slp.hp_override, slp.mp_override, slp.str_override, slp.sta_override, slp.wis_override, slp.int_override, slp.agi_override, slp.heat_override, slp.cold_override, slp.magic_override, slp.mental_override, slp.divine_override, slp.disease_override, slp.poison_override, difficulty_override FROM spawn_location_placement slp, spawn_location_name sln, spawn_location_entry sle, spawn_objects so where so.spawn_id = sle.spawn_id and sln.id = sle.spawn_location_id and sln.id = slp.spawn_location_id and slp.zone_id=%i and (slp.instance_id = 0 or slp.instance_id=%u) ORDER BY sln.id, sle.id", SPAWN_ENTRY_TYPE_OBJECT);
@ -3570,17 +3637,22 @@ void WorldDatabase::LoadSpawns(ZoneServer* zone)
spawn_groups = LoadSpawnLocationGroups(zone);
spawn_group_associations = LoadSpawnLocationGroupAssociations(zone);
spawn_group_chances = LoadSpawnGroupChances(zone);
}
LogWrite(SPAWN__INFO, 0, "Spawn", "Loaded for zone '%s' (%u):\n\t%u NPC(s), %u Object(s), %u Widget(s)\n\t%u Sign(s), %u Ground Spawn(s), %u Spawn Group(s)\n\t%u Spawn Group Association(s), %u Spawn Group Chance(s)", zone->GetZoneName(), zone->GetZoneID(), npcs, objects, widgets, signs, ground_spawns, spawn_groups, spawn_group_associations, spawn_group_chances);
LogWrite(SPAWN__INFO, 0, "Spawn", "Loaded for zone '%s' (%u):\n\t%u NPC(s), %u Object(s), %u Widget(s)\n\t%u Sign(s), %u Ground Spawn(s), %u Spawn Group(s)\n\t%u Spawn Group Association(s), %u Spawn Group Chance(s), %u InstanceHouse", zone->GetZoneName(), zone->GetZoneID(), npcs, objects, widgets, signs, ground_spawns, spawn_groups, spawn_group_associations, spawn_group_chances, isInstanceType);
LogWrite(SPAWN__TRACE, 0, "Spawn", "Exit LoadSpawns");
}
bool WorldDatabase::UpdateSpawnLocationSpawns(Spawn* spawn) {
Query query;
query.RunQuery2(Q_UPDATE, "update spawn_location_placement set x=%f, y=%f, z=%f, heading=%f, x_offset=%f, y_offset=%f, z_offset=%f, respawn=%u, expire_timer=%u, expire_offset=%u, grid_id=%u, pitch=%f, roll=%f where id = %u",
spawn->GetX(), spawn->GetY(), spawn->GetZ(), spawn->GetHeading(), spawn->GetXOffset(), spawn->GetYOffset(), spawn->GetZOffset(), spawn->GetRespawnTime(), spawn->GetExpireTime(), spawn->GetExpireOffsetTime(), spawn->GetLocation(), spawn->GetPitch(), spawn->GetRoll(), spawn->GetSpawnLocationPlacementID());
int8 isInstanceType = (spawn && spawn->GetZone() && spawn->GetZone()->GetInstanceType() == Instance_Type::PERSONAL_HOUSE_INSTANCE);
std::string houseTable("");
if(isInstanceType) {
houseTable.append("_houses");
}
query.RunQuery2(Q_UPDATE, "update spawn_location_placement%s set x=%f, y=%f, z=%f, heading=%f, x_offset=%f, y_offset=%f, z_offset=%f, respawn=%u, expire_timer=%u, expire_offset=%u, grid_id=%u, pitch=%f, roll=%f where id = %u",
houseTable.c_str(), spawn->GetX(), spawn->GetY(), spawn->GetZ(), spawn->GetHeading(), spawn->GetXOffset(), spawn->GetYOffset(), spawn->GetZOffset(), spawn->GetRespawnTime(), spawn->GetExpireTime(), spawn->GetExpireOffsetTime(), spawn->GetLocation(), spawn->GetPitch(), spawn->GetRoll(), spawn->GetSpawnLocationPlacementID());
if (query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF) {
LogWrite(WORLD__ERROR, 0, "World", "Error in UpdateSpawnLocationSpawns query '%s': %s", query.GetQuery(), query.GetError());
return false;
@ -3588,10 +3660,10 @@ bool WorldDatabase::UpdateSpawnLocationSpawns(Spawn* spawn) {
return true;
}
bool WorldDatabase::UpdateSpawnWidget(int32 widget_id, char* queryString) {
bool WorldDatabase::UpdateSpawnWidget(int32 widget_id, char* queryString,bool is_house) {
Query query;
query.RunQuery2(Q_UPDATE, "update spawn_widgets set %s where widget_id = %u",
queryString, widget_id);
query.RunQuery2(Q_UPDATE, "update spawn_widgets%s set %s where widget_id = %u",
is_house ? "_houses" : "", queryString, widget_id);
if (query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF) {
LogWrite(WORLD__ERROR, 0, "World", "Error in UpdateSpawnWidget query '%s': %s", query.GetQuery(), query.GetError());
return false;
@ -3666,11 +3738,11 @@ void WorldDatabase::LoadRevivePoints(vector<RevivePoint*>* revive_points, int32
}
}
int32 WorldDatabase::GetNextSpawnIDInZone(int32 zone_id)
int32 WorldDatabase::GetNextSpawnIDInZone(int32 zone_id, bool isInstanceType)
{
Query query;
int32 ret = 0;
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT MAX(id) FROM spawn where id LIKE '%i____'", zone_id);
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT MAX(id) FROM spawn%s where id LIKE '%i____'", isInstanceType ? "_houses" : "", zone_id);
if(result && mysql_num_rows(result) > 0) {
MYSQL_ROW row;
row = mysql_fetch_row(result);
@ -3692,14 +3764,18 @@ bool WorldDatabase::SaveSpawnInfo(Spawn* spawn){
string suffix = getSafeEscapeString(spawn->GetSuffixTitle());
string prefix = getSafeEscapeString(spawn->GetPrefixTitle());
string last_name = getSafeEscapeString(spawn->GetLastName());
int8 isInstanceType = (spawn->GetZone()->GetInstanceType() == Instance_Type::PERSONAL_HOUSE_INSTANCE);
std::string houseTable("");
if(isInstanceType) {
houseTable.append("_houses");
}
if(spawn->GetDatabaseID() == 0){
int32 new_spawn_id = GetNextSpawnIDInZone(spawn->GetZone()->GetZoneID(), isInstanceType);
int8 isInstanceType = (spawn->GetZone()->GetInstanceType() == Instance_Type::PERSONAL_HOUSE_INSTANCE);
int32 new_spawn_id = GetNextSpawnIDInZone(spawn->GetZone()->GetZoneID());
query.RunQuery2(Q_INSERT, "insert into spawn (id, name, race, model_type, size, targetable, show_name, command_primary, command_secondary, visual_state, attackable, show_level, show_command_icon, display_hand_icon, faction_id, collision_radius, hp, power, prefix, suffix, last_name, is_instanced_spawn, merchant_min_level, merchant_max_level) values(%u, '%s', %i, %i, %i, %i, %i, %u, %u, %i, %i, %i, %i, %i, %u, %i, %u, %u, '%s', '%s', '%s', %u, %u, %u)",
new_spawn_id, name.c_str(), spawn->GetRace(), spawn->GetModelType(), spawn->GetSize(), spawn->appearance.targetable, spawn->appearance.display_name, spawn->GetPrimaryCommandListID(), spawn->GetSecondaryCommandListID(), spawn->GetVisualState(), spawn->appearance.attackable, spawn->appearance.show_level, spawn->appearance.show_command_icon, spawn->appearance.display_hand_icon, 0, spawn->appearance.pos.collision_radius, spawn->GetTotalHP(), spawn->GetTotalPower(), prefix.c_str(), suffix.c_str(), last_name.c_str(), isInstanceType, spawn->GetMerchantMinLevel(), spawn->GetMerchantMaxLevel());
query.RunQuery2(Q_INSERT, "insert into spawn%s (id, name, race, model_type, size, targetable, show_name, command_primary, command_secondary, visual_state, attackable, show_level, show_command_icon, display_hand_icon, faction_id, collision_radius, hp, power, prefix, suffix, last_name, is_instanced_spawn, merchant_min_level, merchant_max_level) values(%u, '%s', %i, %i, %i, %i, %i, %u, %u, %i, %i, %i, %i, %i, %u, %i, %u, %u, '%s', '%s', '%s', %u, %u, %u)",
houseTable.c_str(), new_spawn_id, name.c_str(), spawn->GetRace(), spawn->GetModelType(), spawn->GetSize(), spawn->appearance.targetable, spawn->appearance.display_name, spawn->GetPrimaryCommandListID(), spawn->GetSecondaryCommandListID(), spawn->GetVisualState(), spawn->appearance.attackable, spawn->appearance.show_level, spawn->appearance.show_command_icon, spawn->appearance.display_hand_icon, 0, spawn->appearance.pos.collision_radius, spawn->GetTotalHP(), spawn->GetTotalPower(), prefix.c_str(), suffix.c_str(), last_name.c_str(), isInstanceType, spawn->GetMerchantMinLevel(), spawn->GetMerchantMaxLevel());
if( new_spawn_id > 0 )
spawn->SetDatabaseID(new_spawn_id); // use the new zone_id range
@ -3709,40 +3785,40 @@ bool WorldDatabase::SaveSpawnInfo(Spawn* spawn){
return false; // else, hang your head in shame as you are an utter failure
if(spawn->IsNPC()){
query.RunQuery2(Q_INSERT, "insert into spawn_npcs (spawn_id, min_level, max_level, enc_level, class_, gender, min_group_size, max_group_size, hair_type_id, facial_hair_type_id, wing_type_id, chest_type_id, legs_type_id, soga_hair_type_id, soga_facial_hair_type_id, soga_model_type, heroic_flag, action_state, mood_state, initial_state, activity_status, hide_hood, emote_state) values(%u, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i)",
spawn->GetDatabaseID(), spawn->GetLevel(), spawn->GetLevel(), spawn->GetDifficulty(), spawn->GetAdventureClass(), spawn->GetGender(), 0, 0, ((NPC*)spawn)->features.hair_type, ((NPC*)spawn)->features.hair_face_type,
query.RunQuery2(Q_INSERT, "insert into spawn_npcs%s (spawn_id, min_level, max_level, enc_level, class_, gender, min_group_size, max_group_size, hair_type_id, facial_hair_type_id, wing_type_id, chest_type_id, legs_type_id, soga_hair_type_id, soga_facial_hair_type_id, soga_model_type, heroic_flag, action_state, mood_state, initial_state, activity_status, hide_hood, emote_state) values(%u, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i)",
houseTable.c_str(), spawn->GetDatabaseID(), spawn->GetLevel(), spawn->GetLevel(), spawn->GetDifficulty(), spawn->GetAdventureClass(), spawn->GetGender(), 0, 0, ((NPC*)spawn)->features.hair_type, ((NPC*)spawn)->features.hair_face_type,
((NPC*)spawn)->features.wing_type, ((NPC*)spawn)->features.chest_type, ((NPC*)spawn)->features.legs_type, ((NPC*)spawn)->features.soga_hair_type, ((NPC*)spawn)->features.soga_hair_face_type, spawn->appearance.soga_model_type, spawn->appearance.heroic_flag, spawn->GetActionState(), spawn->GetMoodState(), spawn->GetInitialState(), spawn->GetActivityStatus(), spawn->appearance.hide_hood, spawn->appearance.emote_state);
}
else if(spawn->IsObject()){
query.RunQuery2(Q_INSERT, "insert into spawn_objects (spawn_id) values(%u)", spawn->GetDatabaseID());
query.RunQuery2(Q_INSERT, "insert into spawn_objects%s (spawn_id) values(%u)", houseTable.c_str(), spawn->GetDatabaseID());
}
else if(spawn->IsWidget()){
Widget* widget = (Widget*)spawn;
query.RunQuery2(Q_INSERT, "insert into spawn_widgets (spawn_id, widget_id) values(%u, %u)", spawn->GetDatabaseID(), widget->GetWidgetID());
query.RunQuery2(Q_INSERT, "insert into spawn_widgets%s (spawn_id, widget_id) values(%u, %u)", houseTable.c_str(), spawn->GetDatabaseID(), widget->GetWidgetID());
}
else if(spawn->IsSign()){
query.RunQuery2(Q_INSERT, "insert into spawn_signs (spawn_id, description) values(%u, 'change me')", spawn->GetDatabaseID());
query.RunQuery2(Q_INSERT, "insert into spawn_signs%s (spawn_id, description) values(%u, 'change me')", houseTable.c_str(), spawn->GetDatabaseID());
}
else if (spawn->IsGroundSpawn()) {
query.RunQuery2(Q_INSERT, "insert into spawn_ground (spawn_id) values(%u)", spawn->GetDatabaseID());
query.RunQuery2(Q_INSERT, "insert into spawn_ground%s (spawn_id) values(%u)", houseTable.c_str(), spawn->GetDatabaseID());
}
}
else{
if(spawn->IsNPC()){
query.RunQuery2(Q_UPDATE, "update spawn_npcs, spawn set name='%s', min_level=%i, max_level=%i, enc_level=%i, race=%i, model_type=%i, class_=%i, gender=%i, show_name=%i, attackable=%i, show_level=%i, targetable=%i, show_command_icon=%i, display_hand_icon=%i, hair_type_id=%i, facial_hair_type_id=%i, wing_type_id=%i, chest_type_id=%i, legs_type_id=%i, soga_hair_type_id=%i, soga_facial_hair_type_id=%i, soga_model_type=%i, size=%i, hp=%u, heroic_flag=%i, power=%u, collision_radius=%i, command_primary=%u, command_secondary=%u, visual_state=%i, action_state=%i, mood_state=%i, initial_state=%i, activity_status=%i, alignment=%i, faction_id=%u, hide_hood=%i, emote_state=%i, suffix ='%s', prefix='%s', last_name='%s', merchant_min_level = %u, merchant_max_level = %u where spawn_npcs.spawn_id = spawn.id and spawn.id = %u",
name.c_str(), spawn->GetLevel(), spawn->GetLevel(), spawn->GetDifficulty(), spawn->GetRace(), spawn->GetModelType(),
query.RunQuery2(Q_UPDATE, "update spawn_npcs%s, spawn set name='%s', min_level=%i, max_level=%i, enc_level=%i, race=%i, model_type=%i, class_=%i, gender=%i, show_name=%i, attackable=%i, show_level=%i, targetable=%i, show_command_icon=%i, display_hand_icon=%i, hair_type_id=%i, facial_hair_type_id=%i, wing_type_id=%i, chest_type_id=%i, legs_type_id=%i, soga_hair_type_id=%i, soga_facial_hair_type_id=%i, soga_model_type=%i, size=%i, hp=%u, heroic_flag=%i, power=%u, collision_radius=%i, command_primary=%u, command_secondary=%u, visual_state=%i, action_state=%i, mood_state=%i, initial_state=%i, activity_status=%i, alignment=%i, faction_id=%u, hide_hood=%i, emote_state=%i, suffix ='%s', prefix='%s', last_name='%s', merchant_min_level = %u, merchant_max_level = %u where spawn_npcs%s.spawn_id = spawn.id and spawn.id = %u",
houseTable.c_str(), name.c_str(), spawn->GetLevel(), spawn->GetLevel(), spawn->GetDifficulty(), spawn->GetRace(), spawn->GetModelType(),
spawn->GetAdventureClass(), spawn->GetGender(), spawn->appearance.display_name, spawn->appearance.attackable, spawn->appearance.show_level, spawn->appearance.targetable, spawn->appearance.show_command_icon, spawn->appearance.display_hand_icon, ((NPC*)spawn)->features.hair_type,
((NPC*)spawn)->features.hair_face_type, ((NPC*)spawn)->features.wing_type, ((NPC*)spawn)->features.chest_type, ((NPC*)spawn)->features.legs_type, ((NPC*)spawn)->features.soga_hair_type, ((NPC*)spawn)->features.soga_hair_face_type, spawn->appearance.soga_model_type, spawn->GetSize(),
spawn->GetTotalHPBase(), spawn->appearance.heroic_flag, spawn->GetTotalPowerBase(), spawn->GetCollisionRadius(), spawn->GetPrimaryCommandListID(),
spawn->GetSecondaryCommandListID(), spawn->GetVisualState(), spawn->GetActionState(), spawn->GetMoodState(), spawn->GetInitialState(),
spawn->GetActivityStatus(), ((NPC*)spawn)->GetAlignment(), spawn->GetFactionID(), spawn->appearance.hide_hood, spawn->appearance.emote_state,
suffix.c_str(), prefix.c_str(), last_name.c_str(), spawn->GetMerchantMinLevel(), spawn->GetMerchantMaxLevel(), spawn->GetDatabaseID());
suffix.c_str(), prefix.c_str(), last_name.c_str(), spawn->GetMerchantMinLevel(), spawn->GetMerchantMaxLevel(), houseTable.c_str(), spawn->GetDatabaseID());
}
else if(spawn->IsObject()){
query.RunQuery2(Q_UPDATE, "update spawn_objects, spawn set name='%s', model_type=%i, show_name=%i, targetable=%i, size=%i, command_primary=%u, command_secondary=%u, visual_state=%i, attackable=%i, show_level=%i, show_command_icon=%i, display_hand_icon=%i, collision_radius=%i, hp = %u, power = %u, device_id = %i, merchant_min_level = %u, merchant_max_level = %u where spawn_objects.spawn_id = spawn.id and spawn.id = %u",
name.c_str(), spawn->GetModelType(), spawn->appearance.display_name, spawn->appearance.targetable, spawn->GetSize(), spawn->GetPrimaryCommandListID(), spawn->GetSecondaryCommandListID(), spawn->GetVisualState(), spawn->appearance.attackable, spawn->appearance.show_level, spawn->appearance.show_command_icon, spawn->appearance.display_hand_icon,
spawn->GetCollisionRadius(), spawn->GetTotalHP(), spawn->GetTotalPower(), ((Object*)spawn)->GetDeviceID(), spawn->GetMerchantMinLevel(), spawn->GetMerchantMaxLevel(), spawn->GetDatabaseID());
query.RunQuery2(Q_UPDATE, "update spawn_objects%s, spawn set name='%s', model_type=%i, show_name=%i, targetable=%i, size=%i, command_primary=%u, command_secondary=%u, visual_state=%i, attackable=%i, show_level=%i, show_command_icon=%i, display_hand_icon=%i, collision_radius=%i, hp = %u, power = %u, device_id = %i, merchant_min_level = %u, merchant_max_level = %u where spawn_objects%s.spawn_id = spawn.id and spawn.id = %u",
houseTable.c_str(), name.c_str(), spawn->GetModelType(), spawn->appearance.display_name, spawn->appearance.targetable, spawn->GetSize(), spawn->GetPrimaryCommandListID(), spawn->GetSecondaryCommandListID(), spawn->GetVisualState(), spawn->appearance.attackable, spawn->appearance.show_level, spawn->appearance.show_command_icon, spawn->appearance.display_hand_icon,
spawn->GetCollisionRadius(), spawn->GetTotalHP(), spawn->GetTotalPower(), ((Object*)spawn)->GetDeviceID(), spawn->GetMerchantMinLevel(), spawn->GetMerchantMaxLevel(), houseTable.c_str(), spawn->GetDatabaseID());
}
else if(spawn->IsWidget()){
Widget* widget = (Widget*)spawn;
@ -3750,25 +3826,25 @@ bool WorldDatabase::SaveSpawnInfo(Spawn* spawn){
char* closeSound = 0;
if (widget->GetOpenSound() != NULL) openSound = (char*)widget->GetOpenSound(); else openSound = (char*)string("0").c_str();
if (widget->GetCloseSound() != NULL) closeSound = (char*)widget->GetCloseSound(); else closeSound = (char*)string("0").c_str();
query.RunQuery2(Q_UPDATE, "update spawn_widgets, spawn set name='%s', race=%i, model_type=%i, show_name=%i, attackable=%i, show_level=%i, show_command_icon=%i, display_hand_icon=%i, size=%i, hp=%u, power=%u, collision_radius=%i, command_primary=%u, command_secondary=%u, visual_state=%i, faction_id=%u, suffix ='%s', prefix='%s', last_name='%s',widget_id = %u,widget_x = %f,widget_y = %f,widget_z = %f,include_heading = %u,include_location = %u,icon = %u,type='%s',open_heading = %f,closed_heading = %f,open_x = %f,open_y = %f,open_z = %f,action_spawn_id = %u,open_sound_file='%s',close_sound_file='%s',open_duration = %u,close_x = %f,close_y=%f,close_z=%f,linked_spawn_id = %u,house_id = %u, merchant_min_level = %u, merchant_max_level = %u where spawn_widgets.spawn_id = spawn.id and spawn.id = %u",
name.c_str(), spawn->GetRace(), spawn->GetModelType(), spawn->appearance.display_name, spawn->appearance.attackable, spawn->appearance.show_level, spawn->appearance.show_command_icon, spawn->appearance.display_hand_icon, spawn->GetSize(),
query.RunQuery2(Q_UPDATE, "update spawn_widgets%s, spawn set name='%s', race=%i, model_type=%i, show_name=%i, attackable=%i, show_level=%i, show_command_icon=%i, display_hand_icon=%i, size=%i, hp=%u, power=%u, collision_radius=%i, command_primary=%u, command_secondary=%u, visual_state=%i, faction_id=%u, suffix ='%s', prefix='%s', last_name='%s',widget_id = %u,widget_x = %f,widget_y = %f,widget_z = %f,include_heading = %u,include_location = %u,icon = %u,type='%s',open_heading = %f,closed_heading = %f,open_x = %f,open_y = %f,open_z = %f,action_spawn_id = %u,open_sound_file='%s',close_sound_file='%s',open_duration = %u,close_x = %f,close_y=%f,close_z=%f,linked_spawn_id = %u,house_id = %u, merchant_min_level = %u, merchant_max_level = %u where spawn_widgets%s.spawn_id = spawn.id and spawn.id = %u",
houseTable.c_str(), name.c_str(), spawn->GetRace(), spawn->GetModelType(), spawn->appearance.display_name, spawn->appearance.attackable, spawn->appearance.show_level, spawn->appearance.show_command_icon, spawn->appearance.display_hand_icon, spawn->GetSize(),
spawn->GetTotalHP(), spawn->GetTotalPower(), spawn->GetCollisionRadius(), spawn->GetPrimaryCommandListID(), spawn->GetSecondaryCommandListID(), spawn->GetVisualState(), spawn->GetFactionID(),
suffix.c_str(), prefix.c_str(), last_name.c_str(), widget->GetWidgetID(), widget->GetX(), widget->GetY(), widget->GetZ(), widget->GetIncludeHeading(), widget->GetIncludeLocation(), widget->GetIconValue(), Widget::GetWidgetTypeNameByTypeID(widget->GetWidgetType()).c_str(),
widget->GetOpenHeading(), widget->GetClosedHeading(), widget->GetOpenX(), widget->GetOpenY(), widget->GetOpenZ(),
widget->GetActionSpawnID(), openSound, closeSound,widget->GetOpenDuration(),
widget->GetCloseX(),widget->GetCloseY(),widget->GetCloseZ(),widget->GetLinkedSpawnID(),widget->GetHouseID(),
spawn->GetMerchantMinLevel(), spawn->GetMerchantMaxLevel(), spawn->GetDatabaseID());
spawn->GetMerchantMinLevel(), spawn->GetMerchantMaxLevel(), houseTable.c_str(), spawn->GetDatabaseID());
}
else if(spawn->IsSign()){
Sign* sign = (Sign*)spawn;
query.RunQuery2(Q_UPDATE, "update spawn_signs, spawn set name='%s', race=%i, model_type=%i, show_name=%i, attackable=%i, show_level=%i, show_command_icon=%i, display_hand_icon=%i, size=%i, hp=%u, power=%u, collision_radius=%i, command_primary=%u, command_secondary=%u, visual_state=%i, faction_id=%u, suffix ='%s', prefix='%s', last_name='%s', `type`='%s', zone_id = %u, widget_id = %u, title='%s', widget_x = %f, widget_y = %f, widget_z = %f, icon = %u, description='%s', sign_distance = %f, zone_x = %f, zone_y = %f, zone_z = %f, zone_heading = %f, include_heading = %u, include_location = %u, merchant_min_level = %u, merchant_max_level = %u, language = %u where spawn_signs.spawn_id = spawn.id and spawn.id = %u",
name.c_str(), spawn->GetRace(), spawn->GetModelType(), spawn->appearance.display_name, spawn->appearance.attackable, spawn->appearance.show_level, spawn->appearance.show_command_icon, spawn->appearance.display_hand_icon, spawn->GetSize(),
query.RunQuery2(Q_UPDATE, "update spawn_signs%s, spawn set name='%s', race=%i, model_type=%i, show_name=%i, attackable=%i, show_level=%i, show_command_icon=%i, display_hand_icon=%i, size=%i, hp=%u, power=%u, collision_radius=%i, command_primary=%u, command_secondary=%u, visual_state=%i, faction_id=%u, suffix ='%s', prefix='%s', last_name='%s', `type`='%s', zone_id = %u, widget_id = %u, title='%s', widget_x = %f, widget_y = %f, widget_z = %f, icon = %u, description='%s', sign_distance = %f, zone_x = %f, zone_y = %f, zone_z = %f, zone_heading = %f, include_heading = %u, include_location = %u, merchant_min_level = %u, merchant_max_level = %u, language = %u where spawn_signs%s.spawn_id = spawn.id and spawn.id = %u",
houseTable.c_str(), name.c_str(), spawn->GetRace(), spawn->GetModelType(), spawn->appearance.display_name, spawn->appearance.attackable, spawn->appearance.show_level, spawn->appearance.show_command_icon, spawn->appearance.display_hand_icon, spawn->GetSize(),
spawn->GetTotalHP(), spawn->GetTotalPower(), spawn->GetCollisionRadius(), spawn->GetPrimaryCommandListID(), spawn->GetSecondaryCommandListID(), spawn->GetVisualState(), spawn->GetFactionID(),
suffix.c_str(), prefix.c_str(), last_name.c_str(), sign->GetSignType() == SIGN_TYPE_GENERIC ? "Generic" : "Zone", sign->GetSignZoneID(),
sign->GetWidgetID(), sign->GetSignTitle(), sign->GetWidgetX(), sign->GetWidgetY(), sign->GetWidgetZ(),
sign->GetIconValue(), sign->GetSignDescription(), sign->GetSignDistance(), sign->GetSignZoneX(),
sign->GetSignZoneY(), sign->GetSignZoneZ(), sign->GetSignZoneHeading(), sign->GetIncludeHeading(),
sign->GetIncludeLocation(), spawn->GetMerchantMinLevel(), spawn->GetMerchantMaxLevel(), sign->GetLanguage(), spawn->GetDatabaseID());
sign->GetIncludeLocation(), spawn->GetMerchantMinLevel(), spawn->GetMerchantMaxLevel(), sign->GetLanguage(), houseTable.c_str(), spawn->GetDatabaseID());
}
}
if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){
@ -3794,7 +3870,7 @@ int32 WorldDatabase::SaveCombinedSpawnLocation(ZoneServer* zone, Spawn* in_spawn
spawnLocationID = spawn_location_id;
if(!name)
name = "Combine SpawnGroup Generated";
if(!CreateNewSpawnLocation(spawn_location_id, name)){
if(!CreateNewSpawnLocation(spawn_location_id, name, (zone->GetInstanceType() == Instance_Type::PERSONAL_HOUSE_INSTANCE))){
safe_delete(spawns);
return 0;
}
@ -3841,21 +3917,28 @@ bool WorldDatabase::SaveSpawnEntry(Spawn* spawn, const char* spawn_location_name
Query query;
Query query2;
int32 count = 0;
int8 isInstanceType = (spawn->GetZone()->GetInstanceType() == Instance_Type::PERSONAL_HOUSE_INSTANCE);
std::string houseTable("");
if(isInstanceType) {
houseTable.append("_houses");
}
if(create_spawnlocation){
count = GetSpawnLocationCount(spawn->GetSpawnLocationID());
count = GetSpawnLocationCount(spawn->GetSpawnLocationID(), spawn);
if(count == 0){
if(!CreateNewSpawnLocation(spawn->GetSpawnLocationID(), spawn_location_name))
if(!CreateNewSpawnLocation(spawn->GetSpawnLocationID(), spawn_location_name, isInstanceType))
return false;
}
}
query.RunQuery2(Q_INSERT, "insert into spawn_location_entry (spawn_id, spawn_location_id, spawnpercentage) values(%u, %u, %i)", spawn->GetDatabaseID(), spawn->GetSpawnLocationID(), percent);
query.RunQuery2(Q_INSERT, "insert into spawn_location_entry%s (spawn_id, spawn_location_id, spawnpercentage) values(%u, %u, %i)", houseTable.c_str(), spawn->GetDatabaseID(), spawn->GetSpawnLocationID(), percent);
if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){
LogWrite(SPAWN__ERROR, 0, "Spawn", "Error in SaveSpawnEntry query '%s': %s", query.GetQuery(), query.GetError());
return false;
}
if(save_zonespawn){
query2.RunQuery2(Q_INSERT, "insert into spawn_location_placement (zone_id, instance_id, spawn_location_id, x, y, z, x_offset, y_offset, z_offset, heading, grid_id) values(%u, %u, %u, %f, %f, %f, %f, %f, %f, %f, %u)", spawn->GetZone()->GetZoneID(), spawn->GetZone()->GetInstanceID(), spawn->GetSpawnLocationID(), spawn->GetX(), spawn->GetY(), spawn->GetZ(),x_offset, y_offset, z_offset, spawn->GetHeading(), spawn->GetLocation());
query2.RunQuery2(Q_INSERT, "insert into spawn_location_placement%s (zone_id, instance_id, spawn_location_id, x, y, z, x_offset, y_offset, z_offset, heading, grid_id) values(%u, %u, %u, %f, %f, %f, %f, %f, %f, %f, %u)", houseTable.c_str(), spawn->GetZone()->GetZoneID(), spawn->GetZone()->GetInstanceID(), spawn->GetSpawnLocationID(), spawn->GetX(), spawn->GetY(), spawn->GetZ(),x_offset, y_offset, z_offset, spawn->GetHeading(), spawn->GetLocation());
if(query2.GetErrorNumber() && query2.GetError() && query2.GetErrorNumber() < 0xFFFFFFFF){
LogWrite(SPAWN__ERROR, 0, "Spawn", "Error in SaveSpawnEntry query '%s': %s", query2.GetQuery(), query2.GetError());
return false;
@ -3901,12 +3984,12 @@ float WorldDatabase::GetSpawnLocationPlacementOffsetZ(int32 location_id) {
return ret;
}
bool WorldDatabase::CreateNewSpawnLocation(int32 id, const char* name){
bool WorldDatabase::CreateNewSpawnLocation(int32 id, const char* name, bool isHouseType){
Query query;
if(!name)
name = "Unknown Spawn Location Name";
string str_name = getSafeEscapeString(name);
query.RunQuery2(Q_INSERT, "insert into spawn_location_name (id, name) values(%u, '%s')", id, str_name.c_str());
query.RunQuery2(Q_INSERT, "insert into spawn_location_name%s (id, name) values(%u, '%s')", isHouseType ? "_houses" : "", id, str_name.c_str());
if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){
LogWrite(SPAWN__ERROR, 0, "Spawn", "Error in CreateNewSpawnLocation query '%s': %s", query.GetQuery(), query.GetError());
return false;
@ -3918,10 +4001,16 @@ int32 WorldDatabase::GetSpawnLocationCount(int32 location, Spawn* spawn){
Query query;
int32 ret = 0;
MYSQL_RES* result = 0;
int8 isInstanceType = (spawn && spawn->GetZone() && spawn->GetZone()->GetInstanceType() == Instance_Type::PERSONAL_HOUSE_INSTANCE);
std::string houseTable("");
if(isInstanceType) {
houseTable.append("_houses");
}
if(spawn)
result = query.RunQuery2(Q_SELECT, "SELECT count(id) FROM spawn_location_entry where spawn_location_id=%u and spawn_id=%u", location, spawn->GetDatabaseID());
result = query.RunQuery2(Q_SELECT, "SELECT count(id) FROM spawn_location_entry%s where spawn_location_id=%u and spawn_id=%u", houseTable.c_str(), location, spawn->GetDatabaseID());
else
result = query.RunQuery2(Q_SELECT, "SELECT count(id) FROM spawn_location_entry where spawn_location_id=%u", location);
result = query.RunQuery2(Q_SELECT, "SELECT count(id) FROM spawn_location_entry%s where spawn_location_id=%u", houseTable.c_str(), location);
if(result && mysql_num_rows(result) > 0){
MYSQL_ROW row;
while(result && (row = mysql_fetch_row(result)) && row[0]){
@ -3931,10 +4020,10 @@ int32 WorldDatabase::GetSpawnLocationCount(int32 location, Spawn* spawn){
return ret;
}
int32 WorldDatabase::GetNextSpawnLocation(){
int32 WorldDatabase::GetNextSpawnLocation(bool isInstanceType){
Query query;
int32 ret = 0;
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT max(id) FROM spawn_location_name");
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT max(id) FROM spawn_location_name%s", isInstanceType ? "_houses" : "");
if(result && mysql_num_rows(result) > 0){
MYSQL_ROW row;
while(result && (row = mysql_fetch_row(result)) && row[0]){
@ -3950,11 +4039,17 @@ bool WorldDatabase::RemoveSpawnFromSpawnLocation(Spawn* spawn){
Query query2;
int32 count = GetSpawnLocationCount(spawn->GetSpawnLocationID(), spawn);
query.RunQuery2(Q_DELETE, "delete FROM spawn_location_placement where spawn_location_id=%u", spawn->GetSpawnLocationID());
int8 isInstanceType = (spawn && spawn->GetZone() && spawn->GetZone()->GetInstanceType() == Instance_Type::PERSONAL_HOUSE_INSTANCE);
std::string houseTable("");
if(isInstanceType) {
houseTable.append("_houses");
}
query.RunQuery2(Q_DELETE, "delete FROM spawn_location_placement%s where spawn_location_id=%u", houseTable.c_str(), spawn->GetSpawnLocationID());
if(count == 1)
query.RunQuery2(Q_DELETE, "delete FROM spawn_location_name where id=%u", spawn->GetSpawnLocationID());
query.RunQuery2(Q_DELETE, "delete FROM spawn_location_name%s where id=%u", houseTable.c_str(), spawn->GetSpawnLocationID());
query2.RunQuery2(Q_DELETE, "delete FROM spawn_location_entry where spawn_id=%u and spawn_location_id = %u", spawn->GetDatabaseID(), spawn->GetSpawnLocationID());
query2.RunQuery2(Q_DELETE, "delete FROM spawn_location_entry%s where spawn_id=%u and spawn_location_id = %u", houseTable.c_str(), spawn->GetDatabaseID(), spawn->GetSpawnLocationID());
if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){
LogWrite(WORLD__ERROR, 0, "World", "Error in RemoveSpawnFromSpawnLocation query '%s': %s", query.GetQuery(), query.GetError());
return false;
@ -5288,7 +5383,8 @@ bool WorldDatabase::DeleteCharacter(int32 account_id, int32 character_id){
query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_custom_spell_dataindex where charid=%u", character_id);
query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_custom_spell_display where charid=%u", character_id);
query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_custom_spell_data where charid=%u", character_id);
broker.RemoveSeller(character_id);
return true;
}
@ -7129,13 +7225,17 @@ bool WorldDatabase::LoadNPC(ZoneServer* zone, int32 spawn_id) {
NPC* npc = nullptr;
int32 id = 0;
DatabaseResult result;
database_new.Select(&result, "SELECT npc.spawn_id, s.name, npc.min_level, npc.max_level, npc.enc_level, s.race, s.model_type, npc.class_, npc.gender, s.command_primary, s.command_secondary, s.show_name, npc.min_group_size, npc.max_group_size, npc.hair_type_id, npc.facial_hair_type_id, npc.wing_type_id, npc.chest_type_id, npc.legs_type_id, npc.soga_hair_type_id, npc.soga_facial_hair_type_id, s.attackable, s.show_level, s.targetable, s.show_command_icon, s.display_hand_icon, s.hp, s.power, s.size, s.collision_radius, npc.action_state, s.visual_state, npc.mood_state, npc.initial_state, npc.activity_status, s.faction_id, s.sub_title, s.merchant_id, s.merchant_type, s.size_offset, npc.attack_type, npc.ai_strategy+0, npc.spell_list_id, npc.secondary_spell_list_id, npc.skill_list_id, npc.secondary_skill_list_id, npc.equipment_list_id, npc.str, npc.sta, npc.wis, npc.intel, npc.agi, npc.heat, npc.cold, npc.magic, npc.mental, npc.divine, npc.disease, npc.poison, npc.aggro_radius, npc.cast_percentage, npc.randomize, npc.soga_model_type, npc.heroic_flag, npc.alignment, npc.elemental, npc.arcane, npc.noxious, s.savagery, s.dissonance, npc.hide_hood, npc.emote_state, s.prefix, s.suffix, s.last_name, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards, s.loot_tier, s.loot_drop_type, npc.scared_by_strong_players, npc.action_state_str\n"
"FROM spawn s\n"
"INNER JOIN spawn_npcs npc\n"
int8 isInstanceType = (zone->GetInstanceType() == Instance_Type::PERSONAL_HOUSE_INSTANCE);
std::string houseTable("");
if(isInstanceType) {
houseTable.append("_houses");
}
database_new.Select(&result, "SELECT npc.spawn_id, s.name, npc.min_level, npc.max_level, npc.enc_level, s.race, s.model_type, npc.class_, npc.gender, s.command_primary, s.command_secondary, s.show_name, npc.min_group_size, npc.max_group_size, npc.hair_type_id, npc.facial_hair_type_id, npc.wing_type_id, npc.chest_type_id, npc.legs_type_id, npc.soga_hair_type_id, npc.soga_facial_hair_type_id, s.attackable, s.show_level, s.targetable, s.show_command_icon, s.display_hand_icon, s.hp, s.power, s.size, s.collision_radius, npc.action_state, s.visual_state, npc.mood_state, npc.initial_state, npc.activity_status, s.faction_id, s.sub_title, s.merchant_id, s.merchant_type, s.size_offset, npc.attack_type, npc.ai_strategy+0, npc.spell_list_id, npc.secondary_spell_list_id, npc.skill_list_id, npc.secondary_skill_list_id, npc.equipment_list_id, npc.str, npc.sta, npc.wis, npc.intel, npc.agi, npc.heat, npc.cold, npc.magic, npc.mental, npc.divine, npc.disease, npc.poison, npc.aggro_radius, npc.cast_percentage, npc.randomize, npc.soga_model_type, npc.heroic_flag, npc.alignment, npc.elemental, npc.arcane, npc.noxious, s.savagery, s.dissonance, npc.hide_hood, npc.emote_state, s.prefix, s.suffix, s.last_name, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards, s.loot_tier, s.loot_drop_type, npc.scared_by_strong_players, npc.action_state_str, s.lua_script\n"
"FROM spawn%s s\n"
"INNER JOIN spawn_npcs%s npc\n"
"ON npc.spawn_id = s.id\n"
"WHERE s.id = %u",
spawn_id);
houseTable.c_str(), houseTable.c_str(), spawn_id);
if (result.GetNumRows() > 0 && result.Next()) {
id = result.GetInt32(0);
@ -7273,6 +7373,11 @@ bool WorldDatabase::LoadNPC(ZoneServer* zone, int32 spawn_id) {
npc->GetInfoStruct()->set_action_state(action_state_str);
}
if(!result.IsNull(83)){
std::string lua_script = std::string(result.GetString(83));
npc->SetSpawnScript(lua_script, true);
}
zone->AddNPC(id, npc);
//skipped spells/skills/equipment as it is all loaded, the following rely on a spawn to load
@ -7761,9 +7866,6 @@ void WorldDatabase::GetHouseSpawnInstanceData(ZoneServer* zone, Spawn* spawn)
if (!spawn)
return;
if (zone->house_object_database_lookup.count(spawn->GetModelType()) < 1)
zone->house_object_database_lookup.Put(spawn->GetModelType(), spawn->GetDatabaseID());
DatabaseResult result;
database_new.Select(&result, "SELECT pickup_item_id, pickup_unique_item_id\n"
@ -8851,7 +8953,6 @@ void WorldDatabase::SaveSignMark(int32 char_id, int32 sign_id, char* char_name,
LogWrite(SIGN__DEBUG, 0, "Sign", "ERROR in WorldDatabase::SaveSignMark");
return;
}
}
string WorldDatabase::GetSignMark(int32 char_id, int32 sign_id, char* char_name) {
@ -8884,4 +8985,121 @@ int32 WorldDatabase::GetMysqlExpCurve(int level) {
}
//return 1 so we dont break shit later divide by 0 and all that.
return 1;
}
bool WorldDatabase::RemoveBrokerItem(int32 cid, int64 uid, int32 quantity) {
if (quantity <= 0)
return false;
string update = string("UPDATE broker_items SET count = count - %u WHERE character_id = %u AND unique_id = %llu AND count >= %u");
Query q;
q.RunQuery2(
Q_UPDATE,
update.c_str(), quantity, cid, uid, quantity
);
if (q.GetErrorNumber() && q.GetError() && q.GetErrorNumber() < 0xFFFFFFFF) {
LogWrite(WORLD__ERROR, 0, "Broker",
"DB error removing %u from item %llu for char %u: %s",
quantity, uid, cid, q.GetError());
return false;
}
// exactly one row updated ⇒ we had enough stock (and trigger will
// delete it if count hit zero)
return (q.GetAffectedRows() == 1);
}
int32 WorldDatabase::LoadBrokerSellers(BrokerManager &broker) {
Query query;
MYSQL_ROW row;
int32 count = 0;
MYSQL_RES * result = query.RunQuery2(
Q_SELECT,
"SELECT character_id, seller_name, house_id, sale_enabled, sell_from_inventory, coin_session, coin_total FROM broker_sellers"
);
while (result && (row = mysql_fetch_row(result))) {
int32 cid = atoul(row[0]);
std::string name = row[1] ? row[1] : "";
int32 hid = strtoull(row[2], NULL, 0);
bool en = atoi(row[3]) != 0;
bool inv = atoi(row[4]) != 0;
int64 coin_session = strtoull(row[5], NULL, 0);
int64 coin_total = strtoull(row[6], NULL, 0);
broker.LoadSeller(cid, name, hid, true, inv, coin_session, coin_total);
++count;
LogWrite(PLAYER__ERROR, 5, "Broker",
"--Loaded broker player: %u (%s), house=%u, sale=%u, inv=%u",
cid, name.c_str(), hid, en, inv
);
}
return count;
}
int32 WorldDatabase::LoadBrokerItems(BrokerManager &broker) {
Query query;
MYSQL_ROW row;
int32 count = 0;
MYSQL_RES * result = query.RunQuery2(
Q_SELECT,
"SELECT unique_id, character_id, house_id, item_id,"
" cost_copper, for_sale, inv_slot_id, slot_id, count, from_inventory, creator "
" FROM broker_items"
);
while (result && (row = mysql_fetch_row(result))) {
SaleItem it{};
it.unique_id = strtoull(row[0], NULL, 0);
it.character_id = atoi(row[1]);
it.house_id = atoi(row[2]);
it.item_id = strtoull(row[3], NULL, 0);
it.cost_copper = atoi(row[4]);
it.for_sale = atoi(row[5]) != 0;
it.inv_slot_id = atoi(row[6]);
it.slot_id = atoi(row[7]);
it.count = atoi(row[8]);
it.from_inventory = atoul(row[9]);
if(row[10])
it.creator = std::string(row[10]);
broker.LoadItem(it);
++count;
LogWrite(PLAYER__ERROR, 5, "Broker",
"--Loaded broker item: uid=%llu, char=%u, item=%llu,"
" price=%u, count=%u",
it.unique_id, it.character_id,
it.item_id, it.cost_copper,
it.count
);
}
return count;
}
int32 WorldDatabase::LoadBrokerData(BrokerManager &broker) {
int32 p = LoadBrokerSellers(broker);
int32 i = LoadBrokerItems(broker);
return p + i;
}
bool WorldDatabase::UpdateHouseSpawnScript(int32 dbid, std::string scriptName) {
string update = string("UPDATE spawn_houses SET lua_script='%s' where id=%u");
Query q;
q.RunQuery2(
Q_UPDATE,
update.c_str(), getSafeEscapeString(scriptName.c_str()).c_str(), dbid
);
if (q.GetErrorNumber() && q.GetError() && q.GetErrorNumber() < 0xFFFFFFFF) {
return false;
}
return (q.GetAffectedRows() == 1);
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef EQ2WORLD_EMU_DATABASE_H
#define EQ2WORLD_EMU_DATABASE_H
@ -50,6 +51,7 @@
#include "Rules/Rules.h"
#include "Languages.h"
#include "World.h"
#include "Broker/BrokerManager.h"
using namespace std;
@ -175,13 +177,13 @@ public:
void SaveWorldTime(WorldTime* time);
bool SaveSpawnInfo(Spawn* spawn);
int32 GetNextSpawnIDInZone(int32 zone_id);
int32 GetNextSpawnIDInZone(int32 zone_id, bool isInstanceType = false);
bool SaveSpawnEntry(Spawn* spawn, const char* spawn_location_name, int8 percent, float x_offset, float y_offset, float z_offset, bool save_zonespawn = true, bool create_spawnlocation = true);
float GetSpawnLocationPlacementOffsetX(int32 location_id);
float GetSpawnLocationPlacementOffsetY(int32 location_id);
float GetSpawnLocationPlacementOffsetZ(int32 location_id);
int32 GetNextSpawnLocation();
bool CreateNewSpawnLocation(int32 id, const char* name);
int32 GetNextSpawnLocation(bool isInstanceType = false);
bool CreateNewSpawnLocation(int32 id, const char* name, bool isHouseType = false);
bool RemoveSpawnFromSpawnLocation(Spawn* spawn);
int32 GetSpawnLocationCount(int32 location, Spawn* spawn = 0);
vector<string>* GetSpawnNameList(const char* in_name);
@ -240,7 +242,7 @@ public:
void UpdateStartingSkillbar(int32 char_id, int8 class_id, int8 race_id);
void UpdateStartingTitles(int32 char_id, int8 class_id, int8 race_id, int8 gender_id);
bool UpdateSpawnLocationSpawns(Spawn* spawn);
bool UpdateSpawnWidget(int32 widget_id, char* query);
bool UpdateSpawnWidget(int32 widget_id, char* query, bool is_house = false);
bool CheckVersionTable();
void LoadFactionAlliances();
void LoadFactionList();
@ -275,16 +277,16 @@ public:
void LoadGroundSpawnItems(ZoneServer* zone);
void LoadSpawns(ZoneServer* zone);
int8 GetAppearanceType(string type);
void LoadNPCs(ZoneServer* zone);
void LoadNPCs(ZoneServer* zone, bool isInstanceType = false);
void LoadSpiritShards(ZoneServer* zone);
int32 LoadAppearances(ZoneServer* zone, Client* client = 0);
int32 LoadNPCSpells();
int32 LoadNPCSkills(ZoneServer* zone);
int32 LoadNPCEquipment(ZoneServer* zone);
void LoadObjects(ZoneServer* zone);
void LoadGroundSpawns(ZoneServer* zone);
void LoadWidgets(ZoneServer* zone);
void LoadSigns(ZoneServer* zone);
void LoadObjects(ZoneServer* zone, bool isInstanceType = false);
void LoadGroundSpawns(ZoneServer* zone, bool isInstanceType = false);
void LoadWidgets(ZoneServer* zone, bool isInstanceType = false);
void LoadSigns(ZoneServer* zone, bool isInstanceType = false);
void ReloadItemList(int32 item_id = 0);
void LoadItemList(int32 item_id = 0);
int32 LoadItemStats(int32 item_id = 0);
@ -293,7 +295,8 @@ public:
int32 LoadItemLevelOverride(int32 item_id = 0);
int32 LoadItemEffects(int32 item_id = 0);
int32 LoadBookPages(int32 item_id = 0);
int32 LoadNextUniqueItemID();
int64 LoadNextUniqueItemID();
void ResetNextUniqueItemID();
int32 LoadSkillItems(int32 item_id = 0);
int32 LoadRangeWeapons(int32 item_id = 0);
int32 LoadThrownWeapons(int32 item_id = 0);
@ -662,6 +665,17 @@ public:
void LoadCharacterSpellEffects(int32 char_id, Client *client, int8 db_spell_type);
int32 GetMysqlExpCurve(int level);
bool RemoveBrokerItem(int32 cid, int64 uid, int32 quantity);
int32 LoadBrokerSellers(BrokerManager &broker);
int32 LoadBrokerItems(BrokerManager &broker);
int32 LoadBrokerData(BrokerManager &broker);
void ClearSellerSession(int32 character_id);
void AddToSellerSession(int32 character_id, int64 amount);
int64 GetSellerSession(int32 character_id);
bool UpdateHouseSpawnScript(int32 dbid, std::string scriptName);
private:
DatabaseNew database_new;
std::map<int32, string> zone_names;

View File

@ -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.
@ -94,6 +94,7 @@
#include "AltAdvancement/AltAdvancement.h"
#include "Bots/Bot.h"
#include "VisualStates.h"
#include "Broker/BrokerManager.h"
extern WorldDatabase database;
extern const char* ZONE_NAME;
@ -130,6 +131,7 @@ extern MasterRecipeBookList master_recipebook_list;
extern VisualStates visual_states;
extern PeerManager peer_manager;
extern HTTPSClientPool peer_https_pool;
extern BrokerManager broker;
using namespace std;
@ -242,6 +244,10 @@ Client::Client(EQStream* ieqs) : underworld_cooldown_timer(5000), pos_update(125
recipe_xor_packet = nullptr;
recipe_packet_count = 0;
recipe_orig_packet_size = 0;
gm_store_search = false;
SetShopWindowStatus(false);
search_page = 0;
firstlogin_transmit = false;
}
Client::~Client() {
@ -306,7 +312,7 @@ void Client::RemoveClientFromZone() {
zone_list.RemoveClientFromMap(player->GetName(), this);
safe_delete(camp_timer);
safe_delete(search_items);
ClearItemSearch();
safe_delete(current_rez.expire_timer);
safe_delete(pending_last_name);
safe_delete_array(incoming_paperdoll.image_bytes);
@ -838,8 +844,10 @@ void Client::SendCharInfo() {
if (player->GetHP() < player->GetTotalHP() || player->GetPower() < player->GetTotalPower())
GetCurrentZone()->AddDamagedSpawn(player);
if (firstlogin)
if (firstlogin) {
firstlogin_transmit = true;
firstlogin = false;
}
player->ClearProcs();
items = player->GetEquippedItemList();
@ -910,6 +918,8 @@ void Client::SendCharInfo() {
GetPlayer()->SetSaveSpellEffects(false);
GetPlayer()->SetCharSheetChanged(true);
GetPlayer()->SetReturningFromLD(false);
broker.LockActiveItemsForClient(this);
}
void Client::SendZoneSpawns() {
@ -1491,7 +1501,7 @@ bool Client::HandlePacket(EQApplicationPacket* app) {
spawn->position_changed = true;
_snprintf(query, 256, "open_heading=%f,include_heading=1", newHeading);
if (database.UpdateSpawnWidget(widget->GetWidgetID(), query))
if (database.UpdateSpawnWidget(widget->GetWidgetID(), query, GetCurrentZone()->GetInstanceType() == Instance_Type::PERSONAL_HOUSE_INSTANCE))
SimpleMessage(CHANNEL_COLOR_YELLOW, "Successfully saved widget open heading information.");
}
else
@ -1508,7 +1518,7 @@ bool Client::HandlePacket(EQApplicationPacket* app) {
spawn->position_changed = true;
_snprintf(query, 256, "closed_heading=%f,include_heading=1", newHeading);
if (database.UpdateSpawnWidget(widget->GetWidgetID(), query))
if (database.UpdateSpawnWidget(widget->GetWidgetID(), query, GetCurrentZone()->GetInstanceType() == Instance_Type::PERSONAL_HOUSE_INSTANCE))
SimpleMessage(CHANNEL_COLOR_YELLOW, "Successfully saved widget close heading information.");
if (spawn->GetSpawnLocationID())
@ -1921,6 +1931,26 @@ bool Client::HandlePacket(EQApplicationPacket* app) {
if (GetPlayer()->IsDeletedSpawn()) {
GetPlayer()->SetDeletedSpawn(false);
}
if(firstlogin_transmit) {
if (auto info = broker.GetSellerInfo(GetPlayer()->GetCharacterID())) {
auto logs = broker.GetSellerLog(GetPlayer()->GetCharacterID());
for (auto const & log : logs) {
SendHouseSaleLog(log.message,
0,
0,
0);
}
}
firstlogin_transmit = false;
}
if(HasOwnerOrEditAccess()) { // we are in their own house
int64 coin_session = database.GetSellerSession(GetPlayer()->GetCharacterID());
if(coin_session) {
OpenShopWindow(nullptr, false, 1);
}
}
ResetZoningCoords();
SetReadyForUpdates();
GetPlayer()->SendSpawnChanges(true);
@ -2130,9 +2160,7 @@ bool Client::HandlePacket(EQApplicationPacket* app) {
EQ2_CommandString remote(app->pBuffer, app->size);
LogWrite(PACKET__DEBUG, 1, "Packet", "RemoteCmdMsg Packet dump:");
#if EQDEBUG >= 9
DumpPacket(app);
#endif
commands.Process(remote.handler, &remote.command, this);
}
else //bad client, disconnect
@ -2448,10 +2476,12 @@ bool Client::HandlePacket(EQApplicationPacket* app) {
int32 spawn_index = 0;
if (GetVersion() <= 561) {
spawn_index = packet->getType_int32_ByName("house_id");
house_id = packet->getType_int32_ByName("house_id");
spawn_index = GetPlayer()->GetTarget() ? GetPlayer()->GetTarget()->GetID() : 0;
}
else {
house_id = packet->getType_int64_ByName("house_id");
spawn_index = packet->getType_int32_ByName("spawn_id");
}
ZoneChangeDetails zone_details;
if (GetHouseZoneServer(&zone_details, spawn_index, house_id)) {
@ -2904,8 +2934,8 @@ bool Client::HandleLootItem(Spawn* entity, Item* item, Spawn* target, bool overr
return false;
}
if (lootingPlayer->item_list.HasFreeSlot() || lootingPlayer->item_list.CanStack(item)) {
if (lootingPlayer->item_list.AssignItemToFreeSlot(item)) {
if (lootingPlayer->item_list.HasFreeSlot() || lootingPlayer->item_list.CanStack(item, false)) {
if (lootingPlayer->item_list.AssignItemToFreeSlot(item, true)) {
if (item->CheckFlag2(HEIRLOOM)) { // TODO: RAID Support
GroupMemberInfo* gmi = lootingClient->GetPlayer()->GetGroupMemberInfo();
@ -3396,6 +3426,16 @@ void Client::HandleExamineInfoRequest(EQApplicationPacket* app) {
item = GetPlayer()->GetAppearanceEquipmentList()->GetItemFromUniqueID(id);
if (!item)
item = master_item_list.GetItem(id);
if (!item && HasOwnerOrEditAccess())
item = GetPlayer()->item_list.GetVaultItemFromUniqueID(id, true);
if(!item && GetMerchantTransactionID()) {
Spawn* merchant = GetPlayer()->GetZone()->GetSpawnByID(GetMerchantTransactionID());
if(merchant && merchant->GetHouseCharacterID() && merchant->GetPickupUniqueItemID()) {
if(auto itemInfo = broker.GetActiveItem(merchant->GetHouseCharacterID(), id)) {
item = master_item_list.GetItem(itemInfo->item_id);
}
}
}
if (item) {// && sent_item_details.count(id) == 0){
MItemDetails.writelock(__FUNCTION__, __LINE__);
sent_item_details[id] = true;
@ -3407,7 +3447,7 @@ void Client::HandleExamineInfoRequest(EQApplicationPacket* app) {
delete item;
}
else {
LogWrite(WORLD__ERROR, 0, "World", "HandleExamineInfoRequest: Unknown Item ID = %u", id);
LogWrite(WORLD__ERROR, 0, "World", "HandleExamineInfoRequest#0: Unknown Item ID = %u", id);
DumpPacket(app);
}
}
@ -3430,6 +3470,16 @@ void Client::HandleExamineInfoRequest(EQApplicationPacket* app) {
item = GetPlayer()->GetAppearanceEquipmentList()->GetItemFromUniqueID(unique_id);
if (!item)
item = master_item_list.GetItem(id);
if(!item && GetMerchantTransactionID()) {
Spawn* merchant = GetPlayer()->GetZone()->GetSpawnByID(GetMerchantTransactionID());
if(merchant && merchant->GetHouseCharacterID() && merchant->GetPickupUniqueItemID()) {
if(auto itemInfo = broker.GetActiveItem(merchant->GetHouseCharacterID(), id)) {
item = master_item_list.GetItem(itemInfo->item_id);
}
}
}
if (item) {
MItemDetails.writelock(__FUNCTION__, __LINE__);
sent_item_details[id] = true;
@ -3438,7 +3488,7 @@ void Client::HandleExamineInfoRequest(EQApplicationPacket* app) {
QueuePacket(app);
}
else {
LogWrite(WORLD__ERROR, 0, "World", "HandleExamineInfoRequest: Unknown Item ID = %u", id);
LogWrite(WORLD__ERROR, 0, "World", "HandleExamineInfoRequest#1: Unknown Item ID = %u", id);
DumpPacket(app);
}
}
@ -3462,6 +3512,16 @@ void Client::HandleExamineInfoRequest(EQApplicationPacket* app) {
//int16 unknown5 = request->getType_sint16_ByName("unknown5");
//printf("Type: (%i) Unknown_0: (%u) Unknown_1: (%u) Unknown2: (%i) Unique ID: (%u) Unknown5: (%i) Item ID: (%u)\n",type,unknown_0,unknown_1,unknown2,unique_id,unknown5,id);
Item* item = master_item_list.GetItem(id);
if(!item && GetMerchantTransactionID()) {
Spawn* merchant = GetPlayer()->GetZone()->GetSpawnByID(GetMerchantTransactionID());
if(merchant && merchant->GetHouseCharacterID() && merchant->GetPickupUniqueItemID()) {
if(auto itemInfo = broker.GetActiveItem(merchant->GetHouseCharacterID(), id)) {
item = master_item_list.GetItem(itemInfo->item_id);
}
}
}
if (item) {
//only display popup for non merchant links
EQ2Packet* app = item->serialize(GetVersion(), (request->getType_int8_ByName("show_popup") != 0), GetPlayer(), true, 0, 0, GetVersion() > 561 ? true : false);
@ -3469,7 +3529,7 @@ void Client::HandleExamineInfoRequest(EQApplicationPacket* app) {
QueuePacket(app);
}
else {
LogWrite(WORLD__ERROR, 0, "World", "HandleExamineInfoRequest: Unknown Item ID = %u", id);
LogWrite(WORLD__ERROR, 0, "World", "HandleExamineInfoRequest#2: Unknown Item ID = %u", id);
DumpPacket(app);
}
}
@ -3798,7 +3858,7 @@ bool Client::Process(bool zone_process) {
if (temp_placement_timer.Check()) {
if (GetTempPlacementSpawn() && GetPlayer()->WasSentSpawn(GetTempPlacementSpawn()->GetID()) && !hasSentTempPlacementSpawn) {
int8 placement = 0;
int32 uniqueID = GetPlacementUniqueItemID();
int64 uniqueID = GetPlacementUniqueItemID();
Item* uniqueItem = GetPlayer()->item_list.GetItemFromUniqueID(uniqueID);
if (uniqueItem && uniqueItem->houseitem_info)
placement = uniqueItem->houseitem_info->house_location;
@ -8107,7 +8167,9 @@ bool Client::AddItem(Item* item, bool* item_deleted, AddItemType type) {
return false;
}
OpenShopWindow(nullptr);
return true;
}
@ -8301,6 +8363,9 @@ void Client::SellItem(int32 item_id, int16 quantity, int32 unique_id) {
Guild* guild = GetPlayer()->GetGuild();
if (spawn && spawn->GetMerchantID() > 0 && (!(spawn->GetMerchantType() & MERCHANT_TYPE_NO_BUY)) &&
spawn->IsClientInMerchantLevelRange(this)) {
if((spawn->GetMerchantType() & MERCHANT_TYPE_LOTTO))
return;
int32 total_sell_price = 0;
int32 total_status_sell_price = 0; //for status
float multiplier = CalculateBuyMultiplier(spawn->GetMerchantID());
@ -8316,11 +8381,15 @@ void Client::SellItem(int32 item_id, int16 quantity, int32 unique_id) {
if (!item)
item = player->item_list.GetItemFromID(item_id);
if (item && master_item) {
if(item->details.inv_slot_id == -3 || item->details.inv_slot_id == -4) {
if(item->details.inv_slot_id == InventorySlotType::BANK || item->details.inv_slot_id == InventorySlotType::SHARED_BANK) {
SimpleMessage(CHANNEL_COLOR_RED, "You cannot sell an item in the bank.");
return;
}
if (item->details.item_locked || item->details.equip_slot_id)
if(GetPlayer()->item_list.IsItemInSlotType(item, InventorySlotType::HOUSE_VAULT)) {
SimpleMessage(CHANNEL_COLOR_RED, "You cannot sell an item in the house vault.");
return;
}
if (item->IsItemLocked() || item->details.equip_slot_id)
{
SimpleMessage(CHANNEL_COLOR_RED, "You cannot sell the item in use.");
return;
@ -8418,6 +8487,9 @@ void Client::BuyBack(int32 item_id, int16 quantity) {
Spawn* spawn = GetCurrentZone()->GetSpawnByID(GetMerchantTransactionID());
if (spawn && spawn->GetMerchantID() > 0 && (!(spawn->GetMerchantType() & MERCHANT_TYPE_NO_BUY_BACK)) &&
spawn->IsClientInMerchantLevelRange(this)) {
if((spawn->GetMerchantType() & MERCHANT_TYPE_LOTTO))
return;
deque<BuyBackItem*>::iterator itr;
BuyBackItem* buyback = 0;
BuyBackItem* closest = 0;
@ -8446,7 +8518,7 @@ void Client::BuyBack(int32 item_id, int16 quantity) {
sint64 dispFlags = 0;
if (item && item->GetItemScript() && lua_interface && lua_interface->RunItemScript(item->GetItemScript(), "buyback_display_flags", item, player, nullptr, &dispFlags) && (dispFlags & DISPLAY_FLAG_NO_BUY))
SimpleMessage(CHANNEL_NARRATIVE, "You do not meet all the requirements to buy this item.");
else if (!player->item_list.HasFreeSlot() && !player->item_list.CanStack(item))
else if (!player->item_list.HasFreeSlot() && !player->item_list.CanStack(item, false))
SimpleMessage(CHANNEL_COLOR_RED, "You do not have any slots available for this item.");
else if (player->RemoveCoins(closest->quantity * closest->price)) {
bool removed = false;
@ -8489,9 +8561,15 @@ void Client::BuyBack(int32 item_id, int16 quantity) {
void Client::BuyItem(int32 item_id, int16 quantity) {
// Get the merchant we are buying from
LogWrite(CCLIENT__ERROR, 0, "Client", "buy item %u quantity %u", item_id, quantity);
Spawn* spawn = GetCurrentZone()->GetSpawnByID(GetMerchantTransactionID());
// Make sure the spawn has a merchant list
if (spawn && spawn->GetMerchantID() > 0 && spawn->IsClientInMerchantLevelRange(this)) {
if(spawn && spawn->GetHouseCharacterID()) {
broker.BuyItem(this, spawn->GetHouseCharacterID(), item_id, quantity);
SendMerchantWindow(spawn, false);
}
else if (spawn && spawn->GetMerchantID() > 0 && spawn->IsClientInMerchantLevelRange(this)) {
int32 total_buy_price = 0;
float multiplier = CalculateBuyMultiplier(spawn->GetMerchantID());
int32 sell_price = 0;
@ -8538,7 +8616,7 @@ void Client::BuyItem(int32 item_id, int16 quantity) {
total_buy_price = sell_price * quantity;
item = new Item(master_item);
item->details.count = quantity;
if (!player->item_list.HasFreeSlot() && !player->item_list.CanStack(item)) {
if (!player->item_list.HasFreeSlot() && !player->item_list.CanStack(item, false)) {
SimpleMessage(CHANNEL_COLOR_RED, "You do not have any slots available for this item.");
lua_interface->SetLuaUserDataStale(item);
safe_delete(item);
@ -8683,6 +8761,9 @@ void Client::BuyItem(int32 item_id, int16 quantity) {
void Client::RepairItem(int32 item_id) {
Spawn* spawn = GetCurrentZone()->GetSpawnByID(GetMerchantTransactionID());
if (spawn && (spawn->GetMerchantType() & MERCHANT_TYPE_REPAIR)) {
if((spawn->GetMerchantType() & MERCHANT_TYPE_LOTTO))
return;
Item* item = player->item_list.GetItemFromID(item_id);
if (!item)
item = player->GetEquipmentList()->GetItemFromItemID(item_id);
@ -8721,6 +8802,9 @@ void Client::RepairItem(int32 item_id) {
void Client::RepairAllItems() {
Spawn* spawn = GetCurrentZone()->GetSpawnByID(GetMerchantTransactionID());
if (spawn && (spawn->GetMerchantType() & MERCHANT_TYPE_REPAIR)) {
if((spawn->GetMerchantType() & MERCHANT_TYPE_LOTTO))
return;
vector<Item*>* repairable_items = GetRepairableItems();
if (repairable_items && repairable_items->size() > 0) {
vector<Item*>::iterator itr;
@ -8869,6 +8953,9 @@ void Client::SendAchievementUpdate(bool first_login) {
void Client::SendBuyMerchantList(bool sell) {
Spawn* spawn = GetCurrentZone()->GetSpawnByID(GetMerchantTransactionID());
if (spawn && spawn->GetMerchantID() > 0 && spawn->IsClientInMerchantLevelRange(this)) {
if((spawn->GetMerchantType() & MERCHANT_TYPE_LOTTO))
return;
vector<MerchantItemInfo>* items = world.GetMerchantItemList(spawn->GetMerchantID(), spawn->GetMerchantType(), player);
if (items) {
PacketStruct* packet = configReader.getStruct("WS_UpdateMerchant", GetVersion());
@ -9028,8 +9115,8 @@ void Client::SendSellMerchantList(bool sell) {
for (test_itr = items->begin(); test_itr != items->end(); test_itr++) {
bool isbagwithitems = false;
if(test_itr->second && (test_itr->second->details.inv_slot_id == -3 || test_itr->second->details.inv_slot_id == -4))
continue; // omit bank/shared-bank
if(test_itr->second && (test_itr->second->details.inv_slot_id < 0))
continue; // omit bank/shared-bank/vault/anything with negative inventory slot id
if (test_itr->second && test_itr->second->IsBag() && (test_itr->second->details.num_slots - test_itr->second->details.num_free_slots != test_itr->second->details.num_slots))
isbagwithitems = true;
@ -9479,7 +9566,7 @@ void Client::SendGuildCreateWindow() {
}
}
void Client::AddBuyBack(int32 unique_id, int32 item_id, int16 quantity, int32 price, bool save_needed) {
void Client::AddBuyBack(int64 unique_id, int32 item_id, int16 quantity, int32 price, bool save_needed) {
BuyBackItem* item = new BuyBackItem;
item->item_id = item_id;
item->unique_id = unique_id;
@ -10406,19 +10493,88 @@ void Client::AddSendNewSpells(vector<Spell*>* spells) {
}
}
void Client::SetItemSearch(vector<Item*>* items) {
void Client::SetItemSearch(vector<Item*>* items, map<string, string> values) {
if (items) {
safe_delete(search_items);
search_items = items;
ClearItemSearch();
{
std::lock_guard<std::mutex> L(item_search_mtx_);
search_items = items;
search_values = values;
}
}
}
vector<Item*>* Client::GetSearchItems() {
return search_items;
void Client::ClearItemSearch() {
std::lock_guard<std::mutex> L(item_search_mtx_);
if(search_items) {
for (auto it = search_items->begin(); it != search_items->end(); /* no increment here */) {
Item* item = *it;
if (item->is_search_store_item) {
safe_delete(item);
// erase returns the next iterator
it = search_items->erase(it);
} else {
++it;
}
}
}
safe_delete(search_items);
}
void Client::SendSellerItemByItemUniqueId(int64 unique_id) {
std::lock_guard<std::mutex> L(item_search_mtx_);
if (!search_items) return;
for (Item* item : *search_items) {
if (item && item->details.unique_id == unique_id) {
EQ2Packet* app = item->serialize(GetVersion(), true, GetPlayer());
QueuePacket(app);
break;
}
}
}
void Client::BuySellerItemByItemUniqueId(int64 unique_id, int16 quantity) {
int32 seller_id = 0;
{
std::lock_guard<std::mutex> L(item_search_mtx_);
if (!search_items) return;
for (Item* item : *search_items) {
if (item && item->details.unique_id == unique_id) {
seller_id = item->seller_char_id;
break;
}
}
}
if(seller_id) {
if(broker.BuyItem(this, seller_id, unique_id, quantity)) {
vector<Item*>* items = master_item_list.GetItems(search_values, this);
if(items){
SetItemSearch(items, search_values);
SearchStore(search_page);
}
}
}
else {
Message(CHANNEL_COLOR_RED, "Could not find seller %u when attempting to buy unique id %u.", seller_id, unique_id);
}
}
void Client::SetSellerStatus() {
bool sellInv = broker.CanSellFromInventory(GetPlayer()->GetCharacterID());
bool itemsSelling = broker.IsSellingItems(GetPlayer()->GetCharacterID());
broker.AddSeller(GetPlayer()->GetCharacterID(), std::string(GetPlayer()->GetName()),
GetPlayer()->GetPlayerInfo()->GetHouseZoneID(), true, sellInv);
}
void Client::SearchStore(int32 page) {
std::lock_guard<std::mutex> L(item_search_mtx_);
SetSearchPage(page);
if (search_items) {
PacketStruct* packet = configReader.getStruct("WS_BrokerItems", GetVersion());
if (packet) {
@ -10447,27 +10603,49 @@ void Client::SearchStore(int32 page) {
std::string teststr("test ");
teststr.append(std::to_string(i));
packet->setArrayDataByName("string_one", teststr.c_str(), i);
packet->setArrayDataByName("string_two", "testtwo", i);
packet->setArrayDataByName("seller_name", "EQ2EMuDev", i);
packet->setArrayDataByName("item_id", item->details.item_id, i);
packet->setArrayDataByName("item_id2", item->details.item_id, i);
if(IsGMStoreSearch()) {
packet->setArrayDataByName("seller_name", "EQ2EMuDev", i);
packet->setArrayDataByName("item_id", item->details.item_id, i);
packet->setArrayDataByName("item_id2", item->details.item_id, i);
packet->setArrayDataByName("sell_price", item->sell_price, i);
if (item->stack_count == 0)
packet->setArrayDataByName("quantity", 1, i);
else
packet->setArrayDataByName("quantity", item->stack_count, i);
packet->setArrayDataByName("string_two", "testtwo", i);
}
else {
packet->setArrayDataByName("seller_name", item->seller_name.c_str(), i);
packet->setArrayDataByName("item_id", item->details.unique_id, i);
packet->setArrayDataByName("item_id2", item->details.unique_id, i);
packet->setArrayDataByName("sell_price", item->broker_price, i);
packet->setArrayDataByName("quantity", item->details.count, i);
if(item->seller_house_id) {
HouseZone* hz = world.GetHouseZone(item->seller_house_id);
if(hz && item->seller_name.size() > 0) {
string name;
name = item->seller_name;
name.append("'s ");
name.append(hz->name);
packet->setArrayDataByName("string_two", name.c_str(), i);
}
}
}
packet->setArrayDataByName("icon", item->GetIcon(GetVersion()), i);
//packet->setArrayDataByName("unknown2b", i, i);
packet->setArrayDataByName("item_seller_id", 1, i);
if (item->stack_count == 0)
packet->setArrayDataByName("quantity", 1, i);
else
packet->setArrayDataByName("quantity", item->stack_count, i);
packet->setArrayDataByName("stack_size", item->stack_count, i);
packet->setArrayDataByName("sell_price", item->sell_price, i);
std::string tmpStr("");
tmpStr.append(item->name.c_str());
tmpStr.append(" (");
tmpStr.append(std::to_string(item->details.item_id));
tmpStr.append(")");
if(IsGMStoreSearch()) {
tmpStr.append(" (");
tmpStr.append(std::to_string(item->details.item_id));
tmpStr.append(")");
}
packet->setArrayDataByName("item_name", tmpStr.c_str(), i);
packet->setArrayDataByName("req_level", item->generic_info.adventure_default_level, i);
@ -12035,6 +12213,9 @@ bool Client::HandleNewLogin(int32 account_id, int32 access_code)
new_client_login = NewLoginState::LOGIN_ALLOWED;
}
// vault slots
RefreshVaultSlotCount();
const char* zone_script = world.GetZoneScript(GetCurrentZone()->GetZoneID());
if (zone_script && lua_interface)
lua_interface->RunZoneScript(zone_script, "new_client", GetCurrentZone(), GetPlayer());
@ -12337,6 +12518,126 @@ void Client::SendDefaultCommand(Spawn* spawn, const char* command, float distanc
}
}
void Client::RefreshVaultSlotCount() {
std::vector<PlayerHouse*> houses = world.GetAllPlayerHouses(GetCharacterID());
if (!houses.empty()) {
int8 bestSlots = 0;
int32 bestZoneID = 0;
for (PlayerHouse* ph : houses) {
if (!ph)
continue;
HouseZone* hz = world.GetHouseZone(ph->house_id);
if (!hz)
continue;
if (hz->vault_slots > bestSlots) {
bestSlots = hz->vault_slots;
bestZoneID = ph->house_id;
}
}
if (bestZoneID != 0) {
GetPlayer()->GetPlayerInfo()->SetHouseZone(bestZoneID);
GetPlayer()->SetHouseVaultSlots(bestSlots);
}
}
}
/*
**
October 18, 2005, 07:17:51 PM Rocawne buys Elemental Vestment (Adept I) for 1 Platinum, 0 Gold, 0 Silver, 0 Copper
October 19, 2005, 02:57:20 AM Radian buys A Rusting Gear for 5 Gold, 75 Silver, 0 Copper
but then they stop outputting coin that equals 0
October 20, 2005, 05:29:33 AM Kilea buys Rules of the Sandscrawler Clan - Page 7 for 9 Gold, 75 Silver
October 21, 2005, 12:29:41 AM Meldo buys Oozing Wound (Apprentice IV) for 10 Gold
February 14, 2006, 02:59:32 PM Tom buys 13 pu-erh tea leafes for 4 Gold, 55 Silver
**/
void Client::SendHouseSaleLog(std::string message, int64 coin_session, int64 coin_total, int8 flag) {
PacketStruct* packet = configReader.getStruct("WS_HouseStoreLog", GetVersion());
if (packet) {
packet->setDataByName("data", message.c_str());
packet->setDataByName("coin_gain_session", coin_session);
packet->setDataByName("coin_gain_alltime", coin_total);
packet->setDataByName("sales_log_open", flag);
EQ2Packet* outapp = packet->serialize();
QueuePacket(outapp);
safe_delete(packet);
}
}
void Client::SetItemSaleStatus(int64 unique_id, bool status) {
broker.SetSaleStatus(GetPlayer()->GetCharacterID(), unique_id, status);
OpenShopWindow(nullptr);
}
void Client::OpenShopWindow(Spawn* interaction, bool sendAlways, int8 saleLogOnly) {
if (GetCurrentZone()->GetInstanceType() == PERSONAL_HOUSE_INSTANCE && !HasOwnerOrEditAccess()) {
SimpleMessage(CHANNEL_COLOR_RED, "This is not your home!");
return;
}
auto info = broker.GetSellerInfo(GetPlayer()->GetCharacterID());
bool wasOpen = false;
if(!saleLogOnly) {
wasOpen = GetShopWindowStatus();
if(sendAlways)
SetShopWindowStatus(true);
if(!GetShopWindowStatus() && !sendAlways)
return; // don't send window is not open
if(!info)
broker.AddSeller(GetPlayer()->GetCharacterID(), std::string(GetPlayer()->GetName()), GetPlayer()->GetPlayerInfo()->GetHouseZoneID(), true, false);
GetPlayer()->item_list.GetVaultItems(this, interaction ? interaction->GetID() : GetPlayer()->GetID(), GetPlayer()->GetHouseVaultSlots(), info ? info->sell_from_inventory : false);
broker.LockActiveItemsForClient(this);
}
if(!wasOpen && info) {
int64 coin_session = database.GetSellerSession(GetPlayer()->GetCharacterID());
if(coin_session) {
broker.ResetSellerSessionCoins(GetPlayer()->GetCharacterID());
std::string msg = FormatCoinReceiveMessage(coin_session, "consigned sales");
broker.LogSaleMessage(GetPlayer()->GetCharacterID(), msg);
GetPlayer()->AddCoins(coin_session);
SimpleMessage(CHANNEL_NARRATIVE, msg.c_str());
SendHouseSaleLog(msg, info ? coin_session : 0, info ? info->coin_total : 0, saleLogOnly);
}
else {
SendHouseSaleLog("", 0, info ? info->coin_total : 0, saleLogOnly);
}
}
}
void Client::SetItemSaleCost(int64 unique_id, int32 platinum, int32 gold, int32 silver, int32 copper) {
int64 cost = platinum * 1000000 + gold * 10000 + silver * 100 + copper;
broker.SetSalePrice(GetPlayer()->GetCharacterID(), unique_id, cost);
OpenShopWindow(nullptr);
}
void Client::AddItemSale(int64 unique_id, int32 item_id, int64 price, int32 inv_slot_id, int16 slot_id, int16 count, bool inInventory, bool forSale, std::string itemCreator) {
SaleItem it{};
it.unique_id = unique_id;
it.character_id = GetPlayer()->GetCharacterID();
it.house_id = GetPlayer()->GetPlayerInfo()->GetHouseZoneID();
it.item_id = item_id;
it.cost_copper = price;
it.for_sale = forSale;
it.inv_slot_id = inv_slot_id;
it.slot_id = slot_id;
it.count = count;
it.from_inventory = inInventory;
it.creator = itemCreator;
broker.AddItem(it);
}
bool Client::HandleHouseEntityCommands(Spawn* spawn, int32 spawnid, string command)
{
if (GetCurrentZone()->GetInstanceType() != PERSONAL_HOUSE_INSTANCE)
@ -12369,26 +12670,12 @@ bool Client::PopulateHouseSpawn(PacketStruct* place_object)
{
Spawn* tmp = GetTempPlacementSpawn();
int32 spawn_group_id = database.GetNextSpawnLocation();
int32 spawn_group_id = database.GetNextSpawnLocation(true);
tmp->SetSpawnLocationID(spawn_group_id);
float newHeading = place_object->getType_float_ByName("heading") + 180;
int32 spawnDBID = 0;
if (GetCurrentZone()->house_object_database_lookup.count(tmp->GetModelType()) > 0)
{
spawnDBID = GetCurrentZone()->house_object_database_lookup.Get(tmp->GetModelType());
tmp->SetDatabaseID(spawnDBID);
}
else
{
spawnDBID = database.FindHouseInstanceSpawn(tmp);
if (spawnDBID)
{
GetCurrentZone()->house_object_database_lookup.Put(tmp->GetModelType(), spawnDBID);
tmp->SetDatabaseID(spawnDBID);
}
}
tmp->SetX(place_object->getType_float_ByName("x"));
tmp->SetY(place_object->getType_float_ByName("y"));
@ -12404,7 +12691,6 @@ bool Client::PopulateHouseSpawn(PacketStruct* place_object)
if (!spawnDBID)
{
GetCurrentZone()->house_object_database_lookup.Put(tmp->GetModelType(), tmp->GetDatabaseID());
// we need to copy as to not delete the ZoneServer object_list entry this on house item pickup
GetCurrentZone()->AddObject(tmp->GetDatabaseID(), ((Object*)tmp)->Copy());
}
@ -12423,33 +12709,41 @@ bool Client::PopulateHouseSpawnFinalize()
GetCurrentZone()->AddSpawn(tmp);
GetCurrentZone()->SendSpawnChanges(tmp, this);
SetTempPlacementSpawn(nullptr);
int32 uniqueID = GetPlacementUniqueItemID();
Item* uniqueItem = GetPlayer()->item_list.GetItemFromUniqueID(uniqueID);
int64 uniqueID = GetPlacementUniqueItemID();
Item* uniqueItem = GetPlayer()->item_list.GetVaultItemFromUniqueID(uniqueID);
if(!uniqueItem) {
Message(CHANNEL_COLOR_RED, "Missing unique item!");
return false;
}
if(!uniqueItem->TryLockItem(LockReason::LockReason_House)) {
Message(CHANNEL_COLOR_RED, "Item could not be locked for house placement!");
return false;
}
else {
QueuePacket(GetPlayer()->SendInventoryUpdate(GetVersion()));
}
tmp->SetPickupItemID(uniqueItem->details.item_id);
tmp->SetPickupUniqueItemID(uniqueID);
if (uniqueItem)
if (GetCurrentZone()->GetInstanceType() == PERSONAL_HOUSE_INSTANCE)
{
if (GetCurrentZone()->GetInstanceType() == PERSONAL_HOUSE_INSTANCE)
{
Query query;
query.RunQuery2(Q_INSERT, "insert into spawn_instance_data set spawn_id = %u, spawn_location_id = %u, pickup_item_id = %u, pickup_unique_item_id = %u", tmp->GetDatabaseID(), tmp->GetSpawnLocationID(), tmp->GetPickupItemID(), uniqueID);
}
if (uniqueItem->GetItemScript() &&
lua_interface->RunItemScript(uniqueItem->GetItemScript(), "placed", uniqueItem, GetPlayer(), tmp))
{
uniqueItem = GetPlayer()->item_list.GetItemFromUniqueID(uniqueID);
}
if (uniqueItem) {
database.DeleteItem(GetCharacterID(), uniqueItem, 0);
GetPlayer()->item_list.RemoveItem(uniqueItem, true);
QueuePacket(GetPlayer()->SendInventoryUpdate(GetVersion()));
}
SetPlacementUniqueItemID(0);
Query query;
query.RunQuery2(Q_INSERT, "insert into spawn_instance_data set spawn_id = %u, spawn_location_id = %u, pickup_item_id = %u, pickup_unique_item_id = %u", tmp->GetDatabaseID(), tmp->GetSpawnLocationID(), tmp->GetPickupItemID(), uniqueID);
}
if (uniqueItem->GetItemScript() &&
lua_interface->RunItemScript(uniqueItem->GetItemScript(), "placed", uniqueItem, GetPlayer(), tmp))
{
uniqueItem = GetPlayer()->item_list.GetItemFromUniqueID(uniqueID);
}
if (uniqueItem && uniqueItem->generic_info.item_type != ITEM_TYPE_HOUSE_CONTAINER) {
database.DeleteItem(GetCharacterID(), uniqueItem, 0);
GetPlayer()->item_list.RemoveItem(uniqueItem, true);
QueuePacket(GetPlayer()->SendInventoryUpdate(GetVersion()));
}
SetPlacementUniqueItemID(0);
return true;
}
@ -13648,6 +13942,18 @@ bool Client::GetHouseZoneServer(ZoneChangeDetails* zone_details, int32 spawn_id,
hz = world.GetHouseZone(ph->house_id);
}
}
if(!ph) {
Spawn* houseWidget = GetPlayer()->GetSpawnByIndex(house_id);
if (houseWidget && houseWidget->IsWidget() && ((Widget*)houseWidget)->GetHouseID()) {
hz = world.GetHouseZone(((Widget*)houseWidget)->GetHouseID());
if (hz) {
ph = world.GetPlayerHouseByHouseID(GetPlayer()->GetCharacterID(), hz->id);
}
else {
Message(CHANNEL_COLOR_YELLOW, "HouseWidget#2 spawn index %u house zone could not be found.", spawn_id);
}
}
}
if (ph && hz) {
if (zone_list.GetZoneByInstance(zone_details, ph->instance_id, hz->zone_id)) {
@ -13770,4 +14076,144 @@ bool Client::SendDialogChoice(int32 spawnID, const std::string& windowTextPrompt
safe_delete(p);
return true;
}
void Client::SendMerchantWindow(Spawn* spawn, bool sell) {
if(GetVersion() < 561) {
sell = false; // doesn't support in the same way as AoM just open the normal buy/sell window
}
if(spawn) {
SetMerchantTransaction(spawn);
if (spawn->GetHouseCharacterID() > 0) {
if (auto info = broker.GetSellerInfo(spawn->GetHouseCharacterID())) {
PacketStruct* packet = configReader.getStruct("WS_UpdateMerchant", GetVersion());
if (packet) {
packet->setDataByName("spawn_id", GetPlayer()->GetIDWithPlayerSpawn(spawn));
int32 i = 0;
int tmp_level = 0;
sint8 item_difficulty = 0;
auto items = broker.GetActiveForSaleItems(spawn->GetHouseCharacterID());
int32 itemCount = 0;
for (auto const& itm : items) {
if(itm.inv_slot_id != spawn->GetPickupUniqueItemID())
continue;
if (!itm.for_sale) {
continue;
}
itemCount++;
}
packet->setArrayLengthByName("num_items", itemCount);
for (auto const& itm : items) {
if(itm.inv_slot_id != spawn->GetPickupUniqueItemID())
continue;
if (!itm.for_sale) {
continue;
}
Item* item = master_item_list.GetItem(itm.item_id);
if (!item)
continue;
packet->setArrayDataByName("item_name", item->name.c_str(), i);
packet->setArrayDataByName("item_id", itm.unique_id, i);
//packet->setArrayDataByName("unique_item_id", item->details.item_id, i);
packet->setArrayDataByName("stack_size", itm.count, i);
packet->setArrayDataByName("icon", item->GetIcon(GetVersion()), i);
if (item->generic_info.adventure_default_level > 0)
tmp_level = item->generic_info.adventure_default_level;
else
tmp_level = item->generic_info.tradeskill_default_level;
packet->setArrayDataByName("level", tmp_level, i);
if (rule_manager.GetZoneRule(GetCurrentZoneID(), R_World, DisplayItemTiers)->GetBool()) {
packet->setArrayDataByName("tier", item->details.tier, i);
}
packet->setArrayDataByName("item_id2", item->details.item_id, i);
item_difficulty = player->GetArrowColor(tmp_level);
if (item_difficulty != ARROW_COLOR_WHITE && item_difficulty != ARROW_COLOR_RED && item_difficulty != ARROW_COLOR_GRAY)
item_difficulty = ARROW_COLOR_WHITE;
sint64 overrideValue = 0;
if (item->GetItemScript() && lua_interface && lua_interface->RunItemScript(item->GetItemScript(), "item_difficulty", item, player, nullptr, &overrideValue))
item_difficulty = (sint8)overrideValue;
item_difficulty -= 6;
if (item_difficulty < 0)
item_difficulty *= -1;
packet->setArrayDataByName("item_difficulty", item_difficulty, i);
packet->setArrayDataByName("quantity", 1, i);
packet->setArrayDataByName("unknown5", 255, i);
packet->setArrayDataByName("stack_size2", itm.count, i);
sint64 dispFlags = 0;
if (item->GetItemScript() && lua_interface && lua_interface->RunItemScript(item->GetItemScript(), "buy_display_flags", item, player, nullptr, &dispFlags))
packet->setArrayDataByName("display_flags", (int8)dispFlags, i);
std::string overrideValueStr;
// classic client isn't properly tracking this field, DoF we don't have it identified yet, but no field to cause any issues (can add later if identified)
if (GetVersion() >= 546 && item->GetItemScript() && lua_interface && lua_interface->RunItemScriptWithReturnString(item->GetItemScript(), "item_description", item, player, &overrideValueStr))
packet->setArrayDataByName("description", overrideValueStr.c_str(), i);
packet->setArrayDataByName("price", itm.cost_copper, i);
i++;
}
}
if (GetVersion() < 561) {
//buy is 0 so dont need to set it
if (sell)
packet->setDataByName("type", 1);
}
else if (GetVersion() == 561) {
packet->setDataByName("type", 2);
}
else {
if (sell)
packet->setDataByName("type", 130);
else
packet->setDataByName("type", 2);
}
EQ2Packet* outapp = packet->serialize();
QueuePacket(outapp);
safe_delete(packet);
if(GetVersion() > 561) {
PacketStruct* packet = configReader.getStruct("WS_UpdateMerchant", GetVersion());
if (packet) {
packet->setDataByName("spawn_id", 0xFFFFFFFF);
packet->setDataByName("type", 16);
EQ2Packet* outapp = packet->serialize();
if (outapp)
QueuePacket(outapp);
safe_delete(packet);
}
}
}
}
else if (spawn->GetMerchantID() > 0 && spawn->IsClientInMerchantLevelRange(this)){
SendHailCommand(spawn);
//MerchantFactionMultiplier* multiplier = world.GetMerchantMultiplier(spawn->GetMerchantID());
//if(!multiplier || (multiplier && GetPlayer()->GetFactions()->GetFactionValue(multiplier->faction_id) >= multiplier->faction_min)){
SendBuyMerchantList(sell);
if(!(spawn->GetMerchantType() & MERCHANT_TYPE_NO_BUY))
SendSellMerchantList(sell);
if(!(spawn->GetMerchantType() & MERCHANT_TYPE_NO_BUY_BACK))
SendBuyBackList(sell);
if(GetVersion() > 561) {
PacketStruct* packet = configReader.getStruct("WS_UpdateMerchant", GetVersion());
if (packet) {
packet->setDataByName("spawn_id", 0xFFFFFFFF);
packet->setDataByName("type", 16);
EQ2Packet* outapp = packet->serialize();
if (outapp)
QueuePacket(outapp);
safe_delete(packet);
}
}
}
if (spawn->GetMerchantType() & MERCHANT_TYPE_REPAIR)
SendRepairList();
}
}

View File

@ -1,22 +1,23 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
/*
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.
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 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.
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 <http://www.gnu.org/licenses/>.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef CLIENT_H
#define CLIENT_H
@ -77,7 +78,7 @@ struct QueuedQuest {
struct BuyBackItem {
int32 item_id;
int32 unique_id;
int64 unique_id;
int16 quantity;
int32 price;
bool save_needed;
@ -340,6 +341,7 @@ public:
inline int32 GetAccountID() { return account_id; }
inline const char* GetAccountName() { return account_name; }
inline sint16 GetAdminStatus() { return admin_status; }
inline bool IsGMStoreSearch() { return gm_store_search; }
inline int16 GetVersion() { return version; }
void SetNameCRC(int32 val) { name_crc = val; }
int32 GetNameCRC() { return name_crc; }
@ -348,8 +350,8 @@ public:
void SetVersion(int16 new_version) { version = new_version; }
void SetAccountID(int32 in_accountid) { account_id = in_accountid; }
void SetCharacterID(int32 in_characterid) { character_id = in_characterid; }
void SetAdminStatus(sint16 in_status) { admin_status = in_status; }
void SetAdminStatus(sint16 in_status) { admin_status = in_status; SetGMStoreSearch(false); }
void SetGMStoreSearch(bool setting_val) { gm_store_search = setting_val; }
void DetermineCharacterUpdates();
@ -429,7 +431,7 @@ public:
void BuyBack(int32 item_id, int16 quantity);
void RepairItem(int32 item_id);
void RepairAllItems();
void AddBuyBack(int32 unique_id, int32 item_id, int16 quantity, int32 price, bool save_needed = true);
void AddBuyBack(int64 unique_id, int32 item_id, int16 quantity, int32 price, bool save_needed = true);
deque<BuyBackItem*>* GetBuyBacks();
vector<Item*>* GetRepairableItems();
vector<Item*>* GetItemsByEffectType(ItemEffectType type, ItemEffectType secondary_effect = NO_EFFECT_TYPE);
@ -452,9 +454,13 @@ public:
void SendNewAdventureSpells();
void SendNewTradeskillSpells();
string GetCoinMessage(int32 total_coins);
void SetItemSearch(vector<Item*>* items);
vector<Item*>* GetSearchItems();
void SetItemSearch(vector<Item*>* items, map<string, string> values);
void ClearItemSearch();
void SearchStore(int32 page);
void SendSellerItemByItemUniqueId(int64 unique_id);
void BuySellerItemByItemUniqueId(int64 unique_id, int16 quantity);
void SetSellerStatus();
void SetPlayer(Player* new_player);
void AddPendingQuestAcceptReward(Quest* quest);
@ -564,12 +570,20 @@ public:
Spawn* GetTempPlacementSpawn() { return tempPlacementSpawn; }
void SetPlacementUniqueItemID(int32 id) { placement_unique_item_id = id; }
int32 GetPlacementUniqueItemID() { return placement_unique_item_id; }
void SetPlacementUniqueItemID(int64 id) { placement_unique_item_id = id; }
int64 GetPlacementUniqueItemID() { return placement_unique_item_id; }
void SetHasOwnerOrEditAccess(bool val) { hasOwnerOrEditAccess = val; }
bool HasOwnerOrEditAccess() { return hasOwnerOrEditAccess; }
void RefreshVaultSlotCount();
void SendHouseSaleLog(std::string message, int64 coin_session, int64 coin_total, int8 flag);
void SetItemSaleStatus(int64 unique_id, bool status);
void OpenShopWindow(Spawn* interaction, bool sendAlways = false, int8 saleLogOnly = 0);
void SetShopWindowStatus(bool status) { shop_window_open = status; }
bool GetShopWindowStatus() { return shop_window_open; }// false for disabled , true for enabled
void SetItemSaleCost(int64 unique_id, int32 platinum, int32 gold, int32 silver, int32 copper);
void AddItemSale(int64 unique_id, int32 item_id, int64 price, int32 inv_slot_id, int16 slot_id, int16 count, bool inInventory, bool forSale, std::string itemCreator);
bool HandleHouseEntityCommands(Spawn* spawn, int32 spawnid, string command);
// find an appropriate spawn to use for the house object, save spawn location/entry data to DB
bool PopulateHouseSpawn(PacketStruct* place_object);
@ -725,6 +739,10 @@ public:
void SetLastTellName(std::string tellName) { last_tell_name = tellName; }
std::string GetLastTellName() { return last_tell_name; }
void SetSearchPage(int32 page) { search_page = page; }
void SendMerchantWindow(Spawn* spawn, bool sell);
DialogManager dialog_manager;
private:
void AddRecipeToPlayerPack(Recipe* recipe, PacketStruct* packet, int16* i);
@ -745,6 +763,8 @@ private:
Mutex MQuestQueue;
Mutex MDeletePlayer;
vector<Item*>* search_items;
map<string, string> search_values;
int32 search_page;
int32 waypoint_id = 0;
map<string, WaypointInfo> waypoints;
int32 transport_spawn_id;
@ -775,6 +795,7 @@ private:
int32 account_id;
int32 character_id;
sint16 admin_status; // -2 Banned, -1 Suspended, 0 User, etc.
bool gm_store_search;
char account_name[64];
char zone_name[64];
int32 zoneID;
@ -811,8 +832,9 @@ private:
float zoning_y;
float zoning_z;
float zoning_h;
bool firstlogin;
std::atomic<bool> firstlogin;
std::atomic<bool> firstlogin_transmit;
enum NewLoginState { LOGIN_NONE, LOGIN_DELAYED, LOGIN_ALLOWED, LOGIN_INITIAL_LOAD, LOGIN_SEND };
NewLoginState new_client_login; // 1 = delayed state, 2 = let client in
Timer underworld_cooldown_timer;
@ -857,7 +879,7 @@ private:
int32 delayedAccessKey;
Timer delayTimer;
Spawn* tempPlacementSpawn;
int32 placement_unique_item_id;
int64 placement_unique_item_id;
bool hasOwnerOrEditAccess;
bool hasSentTempPlacementSpawn;
@ -891,6 +913,10 @@ private:
int recipe_orig_packet_size;
std::string last_tell_name;
mutable std::mutex item_search_mtx_;
std::atomic<bool> shop_window_open;
map<string, string> str_values;
};
class ClientList {

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "../common/debug.h"
#include "../common/Log.h"
@ -101,6 +102,7 @@ LuaInterface* lua_interface = new LuaInterface();
#include "Titles.h"
#include "Languages.h"
#include "Achievements/Achievements.h"
#include "./Broker/BrokerManager.h"
volatile bool RunLoops = true;
sint32 numclients = 0;
@ -118,7 +120,6 @@ extern Variables variables;
extern PeerManager peer_manager;
extern HTTPSClientPool peer_https_pool;
ConfigReader configReader;
int32 MasterItemList::next_unique_id = 0;
int last_signal = 0;
RuleManager rule_manager;
MasterTitlesList master_titles_list;
@ -126,7 +127,7 @@ MasterLanguagesList master_languages_list;
ChestTrapList chest_trap_list;
extern MasterAchievementList master_achievement_list;
extern map<int16, int16> EQOpcodeVersions;
extern BrokerManager broker;
ThreadReturnType ItemLoad (void* tmp);
ThreadReturnType AchievmentLoad (void* tmp);
@ -283,7 +284,6 @@ int main(int argc, char** argv) {
// JA: Load all Item info
LogWrite(ITEM__INFO, 0, "Items", "Loading Items...");
database.LoadItemList();
MasterItemList::ResetUniqueID(database.LoadNextUniqueItemID());
LogWrite(SPELL__INFO, 0, "Spells", "Loading Spells...");
database.LoadSpells();
@ -311,9 +311,6 @@ int main(int argc, char** argv) {
database.LoadMerchantInformation();
}
LogWrite(GUILD__INFO, 0, "Guilds", "Loading Guilds...");
database.LoadGuilds();
LogWrite(TRADESKILL__INFO, 0, "Recipes", "Loading Recipe Books...");
database.LoadRecipeBooks();
LogWrite(TRADESKILL__INFO, 0, "Recipes", "Loading Recipes...");
@ -344,7 +341,6 @@ int main(int argc, char** argv) {
LogWrite(WORLD__INFO, 0, "World", "Loading House Zone Data...");
database.LoadHouseZones();
database.LoadPlayerHouses();
LogWrite(WORLD__INFO, 0, "World", "Loading Heroic OP Data...");
database.LoadHOStarters();
@ -368,10 +364,21 @@ int main(int argc, char** argv) {
Sleep(10);
LogWrite(WORLD__INFO, 0, "World", "Load threads finished.");
}
LogWrite(GUILD__INFO, 0, "Guilds", "Loading Guilds...");
database.LoadGuilds();
LogWrite(WORLD__INFO, 0, "World", "Loading Player House Data...");
database.LoadPlayerHouses();
LogWrite(WORLD__INFO, 0, "World", "Total World startup time: %u seconds.", Timer::GetUnixTimeStamp() - t_total);
int ret_code = 0;
if (eqsf.Open(net.GetWorldPort())) {
world.world_loaded = true; // need this set ahead so peering starts sending data also
LogWrite(WORLD__INFO, 0, "World", "Loading Broker Data...");
database.LoadBrokerData(broker);
if (strlen(net.GetWorldAddress()) == 0)
LogWrite(NET__INFO, 0, "Net", "World server listening on port %i", net.GetWorldPort());
else
@ -380,7 +387,6 @@ int main(int argc, char** argv) {
if(strlen(net.GetInternalWorldAddress())>0)
LogWrite(NET__INFO, 0, "Net", "World server listening on: %s:%i", net.GetInternalWorldAddress(), net.GetWorldPort());
world.world_loaded = true;
world.world_uptime = getCurrentTimestamp();
#ifdef WIN32
_beginthread(StartPeerPoll, 0, NULL);
@ -558,8 +564,8 @@ ThreadReturnType ItemLoad (void* tmp)
LogWrite(ITEM__INFO, 0, "Items", "Loading Items...");
db.LoadItemList();
MasterItemList::ResetUniqueID(db.LoadNextUniqueItemID());
db.ResetNextUniqueItemID();
// Relies on the item list so needs to be in the item thread
LogWrite(COLLECTION__INFO, 0, "Collect", "Loading Collections...");
db.LoadCollections();

View File

@ -31,6 +31,7 @@ using namespace std;
#include "Commands/Commands.h"
#include "Zone/pathfinder_interface.h"
#include "NPC_AI.h"
#include "Broker/BrokerManager.h"
#ifdef WIN32
#include <WinSock2.h>
@ -116,7 +117,7 @@ extern Chat chat;
extern MasterRaceTypeList race_types_list;
extern MasterSpellList master_spell_list; // temp - remove later
extern MasterSkillList master_skill_list;
extern BrokerManager broker;
int32 MinInstanceID = 1000;
@ -1641,9 +1642,30 @@ bool ZoneServer::Process()
LogWrite(WIDGET__INFO, 0, "Widget", "-Loading Widget data...");
database.LoadWidgets(this);
LogWrite(WIDGET__INFO, 0, "Widget", "-Load Widget data complete!");
LogWrite(GROUNDSPAWN__INFO, 0, "GSpawn", "-Loading Groundspawn data...");
database.LoadGroundSpawns(this);
LogWrite(GROUNDSPAWN__INFO, 0, "GSpawn", "-Load Groundspawn data complete!");
if(GetInstanceType() == Instance_Type::PERSONAL_HOUSE_INSTANCE) {
LogWrite(NPC__INFO, 0, "NPC", "-Loading House NPC data...");
database.LoadNPCs(this, true);
LogWrite(NPC__INFO, 0, "NPC", "-Load House NPC data complete!");
LogWrite(OBJECT__INFO, 0, "Object", "-Loading House Object data...");
database.LoadObjects(this, true);
LogWrite(OBJECT__INFO, 0, "Object", "-Load House Object data complete!");
LogWrite(SIGN__INFO, 0, "Sign", "-Loading House Sign data...");
database.LoadSigns(this, true);
LogWrite(SIGN__INFO, 0, "Sign", "-Load House Sign data complete!");
LogWrite(GROUNDSPAWN__INFO, 0, "GSpawn", "-Loading House Groundspawn data...");
database.LoadGroundSpawns(this, true);
LogWrite(GROUNDSPAWN__INFO, 0, "GSpawn", "-Load House Groundspawn data complete!");
}
LogWrite(GROUNDSPAWN__INFO, 0, "GSpawn", "-Load Groundspawn entries...");
database.LoadGroundSpawnEntries(this);
LogWrite(GROUNDSPAWN__INFO, 0, "GSpawn", "-Load Groundspawn data complete!");
@ -2542,23 +2564,25 @@ Spawn* ZoneServer::ProcessSpawnLocation(SpawnLocation* spawnlocation, map<int32,
int32 spawnTime = 1;
if(spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_NPC)
spawnTime = database.CheckSpawnRemoveInfo(instNPCs,spawnlocation->entities[i]->spawn_location_id);
else if(spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_OBJECT)
spawnTime = database.CheckSpawnRemoveInfo(instObjSpawns,spawnlocation->entities[i]->spawn_location_id);
else if(spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_WIDGET)
spawnTime = database.CheckSpawnRemoveInfo(instWidgetSpawns,spawnlocation->entities[i]->spawn_location_id);
else if(spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_SIGN)
spawnTime = database.CheckSpawnRemoveInfo(instSignSpawns,spawnlocation->entities[i]->spawn_location_id);
else if(spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_GROUNDSPAWN)
spawnTime = database.CheckSpawnRemoveInfo(instGroundSpawns,spawnlocation->entities[i]->spawn_location_id);
if(spawnTime == 0) { // don't respawn
return nullptr;
}
else if(spawnTime > 1) { // if not 1, respawn after time
AddRespawn(spawnlocation->entities[i]->spawn_location_id, spawnTime);
return nullptr;
if(GetInstanceType() != PERSONAL_HOUSE_INSTANCE) {
if(spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_NPC)
spawnTime = database.CheckSpawnRemoveInfo(instNPCs,spawnlocation->entities[i]->spawn_location_id);
else if(spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_OBJECT)
spawnTime = database.CheckSpawnRemoveInfo(instObjSpawns,spawnlocation->entities[i]->spawn_location_id);
else if(spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_WIDGET)
spawnTime = database.CheckSpawnRemoveInfo(instWidgetSpawns,spawnlocation->entities[i]->spawn_location_id);
else if(spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_SIGN)
spawnTime = database.CheckSpawnRemoveInfo(instSignSpawns,spawnlocation->entities[i]->spawn_location_id);
else if(spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_GROUNDSPAWN)
spawnTime = database.CheckSpawnRemoveInfo(instGroundSpawns,spawnlocation->entities[i]->spawn_location_id);
if(spawnTime == 0) { // don't respawn
return nullptr;
}
else if(spawnTime > 1) { // if not 1, respawn after time
AddRespawn(spawnlocation->entities[i]->spawn_location_id, spawnTime);
return nullptr;
}
}
if (spawnlocation->conditional > 0) {
@ -2642,19 +2666,19 @@ Spawn* ZoneServer::ProcessInstanceSpawnLocation(SpawnLocation* spawnlocation, ma
if(spawnlocation->entities[i]->spawn_percentage >= rand_number)
{
if(spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_NPC &&
(spawnTime = database.CheckSpawnRemoveInfo(instNPCs,spawnlocation->entities[i]->spawn_location_id)) > 0)
(GetInstanceType() == PERSONAL_HOUSE_INSTANCE || (spawnTime = database.CheckSpawnRemoveInfo(instNPCs,spawnlocation->entities[i]->spawn_location_id)) > 0))
spawn = AddNPCSpawn(spawnlocation, spawnlocation->entities[i]);
else if(spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_GROUNDSPAWN &&
(spawnTime = database.CheckSpawnRemoveInfo(instGroundSpawns,spawnlocation->entities[i]->spawn_location_id)) > 0)
(GetInstanceType() == PERSONAL_HOUSE_INSTANCE || (spawnTime = database.CheckSpawnRemoveInfo(instGroundSpawns,spawnlocation->entities[i]->spawn_location_id)) > 0))
spawn = AddGroundSpawn(spawnlocation, spawnlocation->entities[i]);
else if(spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_OBJECT &&
(spawnTime = database.CheckSpawnRemoveInfo(instObjSpawns,spawnlocation->entities[i]->spawn_location_id)) > 0)
(GetInstanceType() == PERSONAL_HOUSE_INSTANCE || (spawnTime = database.CheckSpawnRemoveInfo(instObjSpawns,spawnlocation->entities[i]->spawn_location_id)) > 0))
spawn = AddObjectSpawn(spawnlocation, spawnlocation->entities[i]);
else if(spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_WIDGET &&
(spawnTime = database.CheckSpawnRemoveInfo(instWidgetSpawns,spawnlocation->entities[i]->spawn_location_id)) > 0)
(GetInstanceType() == PERSONAL_HOUSE_INSTANCE || (spawnTime = database.CheckSpawnRemoveInfo(instWidgetSpawns,spawnlocation->entities[i]->spawn_location_id)) > 0))
spawn = AddWidgetSpawn(spawnlocation, spawnlocation->entities[i]);
else if(spawnlocation->entities[i]->spawn_type == SPAWN_ENTRY_TYPE_SIGN &&
(spawnTime = database.CheckSpawnRemoveInfo(instSignSpawns,spawnlocation->entities[i]->spawn_location_id)) > 0)
(GetInstanceType() == PERSONAL_HOUSE_INSTANCE || (spawnTime = database.CheckSpawnRemoveInfo(instSignSpawns,spawnlocation->entities[i]->spawn_location_id)) > 0))
spawn = AddSignSpawn(spawnlocation, spawnlocation->entities[i]);
if(spawn && spawn->IsOmittedByDBFlag())
@ -2669,26 +2693,28 @@ Spawn* ZoneServer::ProcessInstanceSpawnLocation(SpawnLocation* spawnlocation, ma
database.GetHouseSpawnInstanceData(this, spawn);
const char* script = 0;
for(int x=0;x<3;x++)
{
switch(x)
if(spawn && !spawn->GetSpawnScript()) {
for(int x=0;x<3;x++)
{
case 0:
script = world.GetSpawnEntryScript(spawnlocation->entities[i]->spawn_entry_id);
break;
case 1:
script = world.GetSpawnLocationScript(spawnlocation->entities[i]->spawn_location_id);
break;
case 2:
script = world.GetSpawnScript(spawnlocation->entities[i]->spawn_id);
break;
}
switch(x)
{
case 0:
script = world.GetSpawnEntryScript(spawnlocation->entities[i]->spawn_entry_id);
break;
case 1:
script = world.GetSpawnLocationScript(spawnlocation->entities[i]->spawn_location_id);
break;
case 2:
script = world.GetSpawnScript(spawnlocation->entities[i]->spawn_id);
break;
}
if(spawn && script && lua_interface->GetSpawnScript(script) != 0)
{
spawn->SetSpawnScript(string(script));
break;
if(script && lua_interface->GetSpawnScript(script) != 0)
{
spawn->SetSpawnScript(string(script));
break;
}
}
}
@ -3610,7 +3636,10 @@ void ZoneServer::RemoveClient(Client* client)
bool dismissPets = false;
if(client)
{
{
if(client->GetPlayer() && client->GetPlayer()->GetCharacterID())
broker.AddSeller(client->GetPlayer()->GetCharacterID(), std::string(client->GetPlayer()->GetName()), client->GetPlayer()->GetPlayerInfo()->GetHouseZoneID(), true, false);
if (client->GetPlayer())
client_list.RemovePlayerFromInvisHistory(client->GetPlayer()->GetID());
@ -3705,6 +3734,8 @@ void ZoneServer::RemoveClientImmediately(Client* client) {
if(client)
{
if(client->GetPlayer() && client->GetPlayer()->GetCharacterID())
broker.AddSeller(client->GetPlayer()->GetCharacterID(), std::string(client->GetPlayer()->GetName()), client->GetPlayer()->GetPlayerInfo()->GetHouseZoneID(), true, false);
if(client->GetPlayer()) {
if((client->GetPlayer()->GetActivityStatus() & ACTIVITY_STATUS_LINKDEAD) > 0) {
client->GetPlayer()->SetActivityStatus(client->GetPlayer()->GetActivityStatus() - ACTIVITY_STATUS_LINKDEAD);
@ -5421,7 +5452,7 @@ void ZoneServer::KillSpawn(bool spawnListLocked, Spawn* dead, Spawn* killer, boo
spellProcess->RemoveSpellFromQueue((Player*)dead, true);
if (dead->IsNPC())
((NPC*)dead)->Brain()->ClearHate();
((NPC*)dead)->Brain()->ClearHate(!spawnListLocked);
safe_delete(encounter);
@ -5729,9 +5760,13 @@ void ZoneServer::SendInterruptPacket(Spawn* interrupted, LuaSpell* spell, bool f
packet = configReader.getStruct(fizzle ? "WS_SpellFizzle" : "WS_Interrupt", client->GetVersion());
if(packet){
packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(interrupted));
packet->setArrayLengthByName("num_targets", spell->targets.size());
for (int32 i = 0; i < spell->targets.size(); i++)
packet->setArrayDataByName("target_id", client->GetPlayer()->GetIDWithPlayerSpawn(client->GetPlayer()->GetZone()->GetSpawnByID(spell->targets[i])), i);
std::vector<int32> targets = spell->GetTargets(); // snapshot under lock
int i = 0;
packet->setArrayLengthByName("num_targets", targets.size());
for (int32 id : targets) {
packet->setArrayDataByName("target_id", client->GetPlayer()->GetIDWithPlayerSpawn(client->GetPlayer()->GetZone()->GetSpawnByID(id)), i);
i++;
}
packet->setDataByName("spell_id", spell->spell->GetSpellID());
outapp = packet->serialize();
client->QueuePacket(outapp);
@ -5772,15 +5807,18 @@ void ZoneServer::SendCastSpellPacket(LuaSpell* spell, Entity* caster, int32 spel
}
packet->setDataByName("spawn_id", caster_id);
packet->setArrayLengthByName("num_targets", spell->targets.size());
for (int32 i = 0; i < spell->targets.size(); i++) {
int32 target_id = client->GetPlayer()->GetIDWithPlayerSpawn(spell->caster->GetZone()->GetSpawnByID(spell->targets[i]));
std::vector<int32> targets = spell->GetTargets(); // snapshot under lock
int i = 0;
packet->setArrayLengthByName("num_targets", targets.size());
for (int32 id : targets) {
int32 target_id = client->GetPlayer()->GetIDWithPlayerSpawn(spell->caster->GetZone()->GetSpawnByID(id));
if(target_id) {
packet->setArrayDataByName("target", target_id, i);
}
else {
packet->setArrayDataByName("target", 0xFFFFFFFF, i);
}
i++;
}
int32 visual_to_use = spell_visual_override > 0 ? spell_visual_override : spell->spell->GetSpellData()->spell_visual;
@ -8989,25 +9027,27 @@ void ZoneServer::SetSpawnScript(SpawnEntry* entry, Spawn* spawn)
const char* script = 0;
for (int x = 0; x < 3; x++)
{
switch (x)
if(spawn && !spawn->GetSpawnScript()) {
for (int x = 0; x < 3; x++)
{
case 0:
script = world.GetSpawnEntryScript(entry->spawn_entry_id);
break;
case 1:
script = world.GetSpawnLocationScript(entry->spawn_location_id);
break;
case 2:
script = world.GetSpawnScript(entry->spawn_id);
break;
}
switch (x)
{
case 0:
script = world.GetSpawnEntryScript(entry->spawn_entry_id);
break;
case 1:
script = world.GetSpawnLocationScript(entry->spawn_location_id);
break;
case 2:
script = world.GetSpawnScript(entry->spawn_id);
break;
}
if (script && lua_interface && lua_interface->GetSpawnScript(script) != 0)
{
spawn->SetSpawnScript(string(script));
break;
if (script && lua_interface && lua_interface->GetSpawnScript(script) != 0)
{
spawn->SetSpawnScript(string(script));
break;
}
}
}
}
@ -9252,6 +9292,8 @@ void ZoneServer::RemoveClientsFromZone(ZoneServer* zone) {
MClientList.readlock(__FUNCTION__, __LINE__);
for (itr = clients.begin(); itr != clients.end(); itr++) {
Client* client = *itr;
if(client->GetPlayer() && client->GetPlayer()->GetCharacterID())
broker.AddSeller(client->GetPlayer()->GetCharacterID(), std::string(client->GetPlayer()->GetName()), client->GetPlayer()->GetPlayerInfo()->GetHouseZoneID(), true, false);
if(client->GetCurrentZone() == zone) {
client->SetCurrentZone(nullptr);
}
@ -9265,7 +9307,7 @@ void ZoneServer::RemoveClientsFromZone(ZoneServer* zone) {
void ZoneServer::SendSubSpawnUpdates(SUBSPAWN_TYPES subtype) {
std::map<int32, Spawn*>::iterator subitr;
MSpawnList.readlock(__FUNCTION__, __LINE__);
for(subitr = subspawn_list[subtype].begin(); subitr != subspawn_list[subtype].end(); subitr++) {
for(subitr = subspawn_list[subtype].begin(); subitr != subspawn_list[subtype].end(); subitr++) {
subitr->second->changed = true;
subitr->second->info_changed = true;
AddChangedSpawn(subitr->second);

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef ZONESERVER_H
#define ZONESERVER_H
@ -170,7 +171,7 @@ struct TrackedSpawn {
struct HouseItem {
int32 spawn_id;
int32 item_id;
int32 unique_id;
int64 unique_id;
Item* item;
};
@ -701,8 +702,6 @@ public:
Spawn* GetSpawnFromUniqueItemID(int32 unique_id);
void SendHouseItems(Client* client);
MutexMap<int32, int32> house_object_database_lookup; // 1st int32 = model type, 2nd int32 = spawn id
int32 GetWatchdogTime() { return watchdogTimestamp; }
void SetWatchdogTime(int32 time) { watchdogTimestamp = time; }
void CancelThreads();

View File

@ -970,4 +970,4 @@ std::tuple<int64, int64, int64, int64> convertTimestampDuration(int64 total_mill
// Return the result as a tuple
return std::make_tuple(days.count(), hours.count(), minutes.count(), seconds.count());
}
}

View File

@ -27,6 +27,9 @@
#include <vector>
#include <map>
#include <tuple>
#include <string>
#include <sstream>
#include <cstdint>
#ifndef ERRBUF_SIZE
#define ERRBUF_SIZE 1024
@ -122,6 +125,44 @@ static bool IsPrivateAddress(uint32_t ip)
return false;
}
static std::string FormatCoinReceiveMessage(int64 total_copper, const std::string& reason) {
// breakdown into denominations
int64 platinum = total_copper / 1'000'000;
int64 rem = total_copper % 1'000'000;
int64 gold = rem / 10'000;
rem %= 10'000;
int64 silver = rem / 100;
int64 copper = rem % 100;
std::ostringstream oss;
oss << "You received ";
bool first = true;
if (platinum > 0) {
oss << platinum << " platinum";
first = false;
}
if (gold > 0) {
if (!first) oss << ", ";
oss << gold << " gold";
first = false;
}
if (silver > 0) {
if (!first) oss << ", ";
oss << silver << " silver";
first = false;
}
// if nothing else or there's copper, show copper
if (copper > 0 || first) {
if (!first) oss << ", ";
oss << copper << " copper";
}
oss << " earned through " << reason;
return oss.str();
}
template<class Type> void AddData(Type input, string* datastring){
if(datastring)
datastring->append((char*)&input, sizeof(input));