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_count" Type="int16" />
<Data ElementName="your_item_array" Type="Array" ArraySizeVariable="your_item_count"> <Data ElementName="your_item_array" Type="Array" ArraySizeVariable="your_item_count">
<Data ElementName="your_item_name" Type="EQ2_8Bit_String" /> <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="unique_id2" Type="int64" />
<Data ElementName="your_item_quantity" Type="int16" /> <Data ElementName="your_item_quantity" Type="int16" />
<Data ElementName="your_item_icon" Type="int16" /> <Data ElementName="your_item_icon" Type="int16" />
<Data ElementName="your_item_unknown4" Type ="int8" Size="4" /> <Data ElementName="your_item_unknown5" Type ="int8" Size="1" />
<Data ElementName="your_item_background" Type="int8" /> <Data ElementName="storage_flags" Type ="int8" />
<Data ElementName="your_item_unknown4" Type ="int8" Size="9" /> <Data ElementName="your_item_unknown6" Type ="int8" Size="12" />
</Data> </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>
<Struct Name="WS_UpdateMerchant" ClientVersion="1" OpcodeName="OP_ClientCmdMsg" OpcodeType="OP_EqUpdateMerchantCmd"> <Struct Name="WS_UpdateMerchant" ClientVersion="1" OpcodeName="OP_ClientCmdMsg" OpcodeType="OP_EqUpdateMerchantCmd">
<Data ElementName="spawn_id" Type="int32" /> <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 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. This file is part of EQ2Emulator.
@ -52,6 +52,7 @@
#include "../Bots/Bot.h" #include "../Bots/Bot.h"
#include "../Web/PeerManager.h" #include "../Web/PeerManager.h"
#include "../../common/GlobalHeaders.h" #include "../../common/GlobalHeaders.h"
#include "../Broker/BrokerManager.h"
extern WorldDatabase database; extern WorldDatabase database;
extern MasterSpellList master_spell_list; extern MasterSpellList master_spell_list;
@ -76,6 +77,7 @@ extern MasterAAList master_aa_list;
extern MasterRaceTypeList race_types_list; extern MasterRaceTypeList race_types_list;
extern Classes classes; extern Classes classes;
extern PeerManager peer_manager; extern PeerManager peer_manager;
extern BrokerManager broker;
//devn00b: Fix for linux builds since we dont use stricmp we use strcasecmp //devn00b: Fix for linux builds since we dont use stricmp we use strcasecmp
#if defined(__GNUC__) #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); 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){ 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]); int64 item_id = strtoull(sep->arg[1], NULL, 0);
Item* item = master_item_list.GetItem(item_id); Item* item = nullptr;
if(item){
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()); EQ2Packet* app = item->serialize(client->GetVersion(), true, client->GetPlayer());
client->QueuePacket(app); client->QueuePacket(app);
} }
else 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) { else if (strcmp(sep->arg[0], "spell") == 0) {
sint32 spell_id = atol(sep->arg[1]); 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()) if (!spawn || !client->HasOwnerOrEditAccess() || !spawn->GetPickupItemID())
break; break;
Item* tmpItem = client->GetPlayer()->item_list.GetVaultItemFromUniqueID(spawn->GetPickupUniqueItemID());
if(client->AddItem(spawn->GetPickupItemID(), 1)) { 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 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()); 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: { case COMMAND_PLACE_HOUSE_ITEM: {
if (sep && sep->IsNumber(0)) 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 = client->GetPlayer()->item_list.GetVaultItemFromUniqueID(uniqueid);
//Item* item = player->GetEquipmentList()->GetItem(slot);
if (item && (item->IsHouseItem() || item->IsHouseContainer())) 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()) if (!client->HasOwnerOrEditAccess())
{ {
client->SimpleMessage(CHANNEL_COLOR_RED, "This is not your home!"); 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(); Object* obj = new Object();
Spawn* spawn = (Spawn*)obj; Spawn* spawn = (Spawn*)obj;
memset(&spawn->appearance, 0, sizeof(spawn->appearance)); 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->SetX(client->GetPlayer()->GetX());
spawn->SetY(client->GetPlayer()->GetY()); spawn->SetY(client->GetPlayer()->GetY());
spawn->SetZ(client->GetPlayer()->GetZ()); 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:{ case COMMAND_BUY_FROM_BROKER:{
if(sep && sep->arg[1][0] && sep->IsNumber(0) && sep->IsNumber(1)){ 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]); int16 quantity = atoul(sep->arg[1]);
Item* item = master_item_list.GetItem(item_id); if(client->IsGMStoreSearch()) {
if(item && item->generic_info.max_charges > 1) Item* item = master_item_list.GetItem(item_id);
quantity = item->generic_info.max_charges; if(item && item->generic_info.max_charges > 1)
client->AddItem(item_id, quantity, AddItemType::BUY_FROM_BROKER); 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; break;
} }
case COMMAND_SEARCH_STORES_PAGE:{ 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)){ if(sep && sep->arg[0][0] && sep->IsNumber(0)){
int32 page = atoul(sep->arg[0]); int32 page = atoul(sep->arg[0]);
client->SearchStore(page); 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; break;
} }
case COMMAND_SEARCH_STORES:{ 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]; const char* values = sep->argplus[0];
if(values){ if(values){
LogWrite(ITEM__WARNING, 0, "Item", "SearchStores: %s", values); LogWrite(ITEM__WARNING, 0, "Item", "SearchStores: %s", values);
map<string, string> str_values = TranslateBrokerRequest(values); map<string, string> str_values = TranslateBrokerRequest(values);
vector<Item*>* items = master_item_list.GetItems(str_values, client); vector<Item*>* items = master_item_list.GetItems(str_values, client);
if(items){ if(items){
client->SetItemSearch(items); client->SetItemSearch(items, str_values);
client->SetSearchPage(0);
client->SearchStore(0); client->SearchStore(0);
} }
} }
@ -5424,6 +5484,13 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
} }
case COMMAND_ITEMSEARCH: case COMMAND_ITEMSEARCH:
case COMMAND_FROMBROKER:{ case COMMAND_FROMBROKER:{
if(command->handler == COMMAND_ITEMSEARCH) {
client->SetGMStoreSearch(true);
}
else {
client->SetGMStoreSearch(false);
}
PacketStruct* packet = configReader.getStruct("WS_StartBroker", client->GetVersion()); PacketStruct* packet = configReader.getStruct("WS_StartBroker", client->GetVersion());
if (packet) { if (packet) {
packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(client->GetPlayer())); 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", 58, 3);
packet->setDataByName("unknown2", 40, 4); packet->setDataByName("unknown2", 40, 4);
client->QueuePacket(packet->serialize()); client->QueuePacket(packet->serialize());
PacketStruct* packet2 = configReader.getStruct("WS_BrokerBags", client->GetVersion()); if(client->GetVersion() > 561) {
if (packet2) { PacketStruct* packet2 = configReader.getStruct("WS_BrokerBags", client->GetVersion());
packet2->setDataByName("char_id", client->GetCharacterID()); if (packet2) {
client->QueuePacket(packet2->serialize()); //send this for now, needed to properly clear data packet2->setDataByName("char_id", client->GetCharacterID());
safe_delete(packet2); client->QueuePacket(packet2->serialize()); //send this for now, needed to properly clear data
safe_delete(packet2);
}
safe_delete(packet);
} }
safe_delete(packet);
} }
break; 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_SPLIT: { Command_Split(client, sep); break; }
case COMMAND_RAIDSAY: { Command_RaidSay(client, sep); break; } case COMMAND_RAIDSAY: { Command_RaidSay(client, sep); break; }
case COMMAND_RELOAD_ZONEINFO: { Command_ReloadZoneInfo(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: default:
{ {
LogWrite(COMMAND__WARNING, 0, "Command", "Unhandled command: %s", command->command.data.c_str()); 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)
{ {
if(item->details.item_locked) { if(item->IsItemLocked()) {
client->SimpleMessage(CHANNEL_COLOR_RED, "You cannot destroy the item in use."); client->SimpleMessage(CHANNEL_COLOR_RED, "You cannot destroy the item in use.");
return; 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."); client->SimpleMessage(CHANNEL_COLOR_RED, "You can't destroy this item.");
return; 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) if(item->GetItemScript() && lua_interface)
lua_interface->RunItemScript(item->GetItemScript(), "destroyed", item, client->GetPlayer()); lua_interface->RunItemScript(item->GetItemScript(), "destroyed", item, client->GetPlayer());
//reobtain item make sure it wasn't removed //reobtain item make sure it wasn't removed
item = player->item_list.GetItemFromIndex(index); item = player->item_list.GetItemFromIndex(index);
int32 bag_id = 0; int32 bag_id = 0;
if(item){ if(item){
bag_id = item->details.inv_slot_id; 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()->item_list.DestroyItem(index);
client->GetPlayer()->UpdateInventory(bag_id); client->GetPlayer()->UpdateInventory(bag_id);
client->GetPlayer()->CalculateApplyWeight(); 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)) 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]); sint32 bag_id = atol(sep->arg[3]);
int8 charges = atoi(sep->arg[4]); int8 charges = atoi(sep->arg[4]);
Item* item = client->GetPlayer()->item_list.GetItemFromIndex(from_index); Item* item = client->GetPlayer()->item_list.GetItemFromIndex(from_index);
int64 unique_id = 0;
int16 count = 0;
if(!item) { if(!item) {
client->SimpleMessage(CHANNEL_COLOR_RED, "You have no item."); client->SimpleMessage(CHANNEL_COLOR_RED, "You have no item.");
return; return;
} }
unique_id = item->details.unique_id;
if(to_slot == item->details.slot_id && (bag_id < 0 || bag_id == item->details.inv_slot_id)) { count = item->details.count;
if(to_slot == item->details.slot_id && (bag_id == item->details.inv_slot_id)) {
return; return;
} }
if(item->details.item_locked) if(item->IsItemLocked())
{ {
client->SimpleMessage(CHANNEL_COLOR_RED, "You cannot move the item in use."); client->SimpleMessage(CHANNEL_COLOR_RED, "You cannot move the item in use.");
return; 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."); client->SimpleMessage(CHANNEL_COLOR_RED, "That item (or an item inside) cannot be shared.");
return; return;
} }
sint32 old_inventory_id = 0; sint32 old_inventory_id = 0;
if(item) if(item)
old_inventory_id = item->details.inv_slot_id; old_inventory_id = item->details.inv_slot_id;
//autobank //autobank
if (bag_id == -3 && to_slot == -1) if (bag_id == InventorySlotType::BANK && to_slot == -1)
{ {
if (player->HasFreeBankSlot()) if (player->HasFreeBankSlot())
to_slot = player->FindFreeBankSlot(); to_slot = player->FindFreeBankSlot();
@ -7129,6 +7214,16 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
} }
client->GetPlayer()->CalculateApplyWeight(); 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)) 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->QueuePacket(characterSheetPackets);
client->GetPlayer()->CalculateBonuses(); 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)) 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]); int16 index = atoi(sep->arg[1]);
Item* item = client->GetPlayer()->item_list.GetItemFromIndex(index); Item* item = client->GetPlayer()->item_list.GetItemFromIndex(index);
if (item) { if (item) {
if(item->details.item_locked) if(item->IsItemLocked())
{ {
client->SimpleMessage(CHANNEL_COLOR_RED, "You cannot unpack the item in use."); client->SimpleMessage(CHANNEL_COLOR_RED, "You cannot unpack the item in use.");
return; return;
@ -7186,6 +7282,7 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
} }
client->RemoveItem(item, 1); 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->UnequipItem(index, bag_id, to_slot, appearance_equip);
client->GetPlayer()->CalculateBonuses(); 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)) 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 // Send the inventory update packet
client->QueuePacket(player->item_list.serialize(player, client->GetVersion())); client->QueuePacket(player->item_list.serialize(player, client->GetVersion()));
client->OpenShopWindow(nullptr); // update the window if it is open
return; return;
} }
else if (bag_id == -3 && to_slot == -1) { else if (bag_id == InventorySlotType::BANK && to_slot == -1) {
// Auto Bank // Auto Bank
if (!player->item_list.GetFirstFreeBankSlot(&bag_id, &to_slot)) { if (!player->item_list.GetFirstFreeBankSlot(&bag_id, &to_slot)) {
client->SimpleMessage(CHANNEL_STATUS, "You do not have any free bank slots."); 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); player->item_list.RemoveOverflowItem(item);
} }
client->QueuePacket(player->item_list.serialize(player, client->GetVersion())); 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 // Shared Bank
if (!player->item_list.SharedBankAddAllowed(item)) { if (!player->item_list.SharedBankAddAllowed(item)) {
client->SimpleMessage(CHANNEL_STATUS, "That item (or an item inside) cannot be shared."); client->SimpleMessage(CHANNEL_STATUS, "That item (or an item inside) cannot be shared.");
return; 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) { if (tmp_item) {
client->SimpleMessage(CHANNEL_STATUS, "You can not place an overflow item into an occupied slot"); client->SimpleMessage(CHANNEL_STATUS, "You can not place an overflow item into an occupied slot");
return; return;
@ -7311,6 +7411,7 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
player->item_list.RemoveOverflowItem(item); player->item_list.RemoveOverflowItem(item);
} }
client->QueuePacket(player->item_list.serialize(player, client->GetVersion())); client->QueuePacket(player->item_list.serialize(player, client->GetVersion()));
client->OpenShopWindow(nullptr); // update the window if it is open
return; return;
} }
} }
@ -7330,6 +7431,7 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
player->item_list.RemoveOverflowItem(item); player->item_list.RemoveOverflowItem(item);
} }
client->QueuePacket(player->item_list.serialize(player, client->GetVersion())); client->QueuePacket(player->item_list.serialize(player, client->GetVersion()));
client->OpenShopWindow(nullptr); // update the window if it is open
return; return;
} }
} }
@ -9976,14 +10078,18 @@ void Commands::Command_TradeAddItem(Client* client, Seperator* sep)
int32 index = atoi(sep->arg[0]); int32 index = atoi(sep->arg[0]);
item = client->GetPlayer()->GetPlayerItemList()->GetItemFromIndex(index); item = client->GetPlayer()->GetPlayerItemList()->GetItemFromIndex(index);
if (item) { 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."); client->SimpleMessage(CHANNEL_COLOR_RED, "You cannot trade an item currently in use.");
return; 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."); client->SimpleMessage(CHANNEL_COLOR_RED, "You cannot trade an item in the bank.");
return; 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])); int8 result = client->GetPlayer()->trade->AddItemToTrade(client->GetPlayer(), item, atoi(sep->arg[2]), atoi(sep->arg[1]));
if (result == 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) { else if(atoi(sep->arg[0]) == 38) {
client->GetPlayer()->GetZone()->SendFlightPathsPackets(client); 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 { else {
PacketStruct* packet2 = configReader.getStruct("WS_ExaminePartialSpellInfo", client->GetVersion()); 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) { void Commands::Command_SendMerchantWindow(Client* client, Seperator* sep, bool sell) {
Spawn* spawn = client->GetPlayer()->GetTarget(); Spawn* spawn = client->GetPlayer()->GetTarget();
if(client->GetVersion() < 561) { if(spawn)
sell = false; // doesn't support in the same way as AoM just open the normal buy/sell window client->SendMerchantWindow(spawn, sell);
}
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.");
} }
@ -12946,4 +13040,301 @@ void Commands::Command_RaidSay(Client* client, Seperator* sep) {
*/ */
void Commands::Command_ReloadZoneInfo(Client* client, Seperator* sep) { void Commands::Command_ReloadZoneInfo(Client* client, Seperator* sep) {
world.ClearZoneInfoCache(); 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 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. This file is part of EQ2Emulator.
@ -467,7 +467,16 @@ public:
void Command_RaidSay(Client* client, Seperator* sep); void Command_RaidSay(Client* client, Seperator* sep);
void Command_ReloadZoneInfo(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 // AA Commands
void Get_AA_Xml(Client* client, Seperator* sep); void Get_AA_Xml(Client* client, Seperator* sep);
void Add_AA(Client* client, Seperator* sep); void Add_AA(Client* client, Seperator* sep);
@ -984,7 +993,16 @@ private:
#define COMMAND_MOOD 800 #define COMMAND_MOOD 800
#define COMMAND_RELOAD_PLAYERSCRIPTS 801 #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 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_CHARACTER 1001
#define COMMAND_MODIFY_FACTION 1002 #define COMMAND_MODIFY_FACTION 1002

View File

@ -1,6 +1,6 @@
/* /*
EQ2Emulator: Everquest II Server Emulator 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. This file is part of EQ2Emulator.
@ -17,6 +17,7 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifdef WIN32 #ifdef WIN32
#include <WinSock2.h> #include <WinSock2.h>
#include <windows.h> #include <windows.h>
@ -118,6 +119,9 @@ bool WorldDatabase::RemoveSpawnTemplate(int32 template_id)
int32 WorldDatabase::CreateSpawnFromTemplateByID(Client* client, 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; Query query, query2, query3, query4, query5, query6;
MYSQL_ROW row; MYSQL_ROW row;
int32 spawn_location_id = 0; int32 spawn_location_id = 0;

View File

@ -1,6 +1,6 @@
/* /*
EQ2Emulator: Everquest II Server Emulator 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. This file is part of EQ2Emulator.
@ -17,6 +17,7 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "Entity.h" #include "Entity.h"
#include <math.h> #include <math.h>
#include "Items/Items.h" #include "Items/Items.h"
@ -149,8 +150,8 @@ void Entity::DeleteSpellEffects(bool removeClient)
GetInfoStruct()->spell_effects[i].spell = nullptr; GetInfoStruct()->spell_effects[i].spell = nullptr;
} }
} }
MMaintainedSpells.releasewritelock(__FUNCTION__, __LINE__);
MSpellEffects.releasewritelock(__FUNCTION__, __LINE__); MSpellEffects.releasewritelock(__FUNCTION__, __LINE__);
MMaintainedSpells.releasewritelock(__FUNCTION__, __LINE__);
map<LuaSpell*,bool>::iterator deletedPtrItrs; map<LuaSpell*,bool>::iterator deletedPtrItrs;
for(deletedPtrItrs = deletedPtrs.begin(); deletedPtrItrs != deletedPtrs.end(); deletedPtrItrs++) { for(deletedPtrItrs = deletedPtrs.begin(); deletedPtrItrs != deletedPtrs.end(); deletedPtrItrs++) {
@ -1091,6 +1092,11 @@ void Entity::DoRegenUpdate(){
void Entity::AddMaintainedSpell(LuaSpell* luaspell){ void Entity::AddMaintainedSpell(LuaSpell* luaspell){
if (!luaspell) if (!luaspell)
return; 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; Spell* spell = luaspell->spell;
MaintainedEffects* effect = GetFreeMaintainedSpellSlot(); MaintainedEffects* effect = GetFreeMaintainedSpellSlot();
@ -1290,7 +1296,7 @@ SpellEffects* Entity::GetSpellEffectBySpellType(int8 spell_type) {
return ret; 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; SpellEffects* ret = 0;
InfoStruct* info = GetInfoStruct(); InfoStruct* info = GetInfoStruct();
MSpellEffects.readlock(__FUNCTION__, __LINE__); 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) || (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)) (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]; ret = &info->spell_effects[i];
break; break;
} }
@ -1312,12 +1318,14 @@ SpellEffects* Entity::GetSpellEffectWithLinkedTimer(int32 id, int32 linked_timer
return ret; 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) if(!spell->spell->GetSpellData()->linked_timer && !spell->spell->GetSpellData()->type_group_spell_id)
return nullptr; return nullptr;
LuaSpell* ret = nullptr; LuaSpell* ret = nullptr;
InfoStruct* info = GetInfoStruct(); 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 //this for loop primarily handles self checks and 'friendly' checks
for(int i = 0; i < NUM_MAINTAINED_EFFECTS; i++) { for(int i = 0; i < NUM_MAINTAINED_EFFECTS; i++) {
if(info->maintained_effects[i].spell_id != 0xFFFFFFFF) 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()->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()->friendly_spell && spell->spell->GetSpellData()->type_group_spell_id >= -1 && spell->caster == info->maintained_effects[i].spell->caster) ) && (!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; ret = info->maintained_effects[i].spell;
break; break;
} }
} }
} }
MSpellEffects.releasereadlock(__FUNCTION__, __LINE__); MMaintainedSpells.releasereadlock(__FUNCTION__, __LINE__);
if(!ret && !stackWithOtherPlayers && target && target->IsEntity()) if(checkNotCaster && ret && ret->caster != spell->caster)
{ return ret;
SpellEffects* effect = ((Entity*)target)->GetSpellEffectWithLinkedTimer(spell->spell->GetSpellID(), spell->spell->GetSpellData()->linked_timer, spell->spell->GetSpellData()->type_group_spell_id, nullptr); else if(checkNotCaster && ret)
if(effect) ret = nullptr;
ret = effect->spell;
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; return ret;
} }

View File

@ -1,6 +1,6 @@
/* /*
EQ2Emulator: Everquest II Server Emulator 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. This file is part of EQ2Emulator.
@ -1496,8 +1496,8 @@ public:
SpellEffects* GetFreeSpellEffectSlot(); SpellEffects* GetFreeSpellEffectSlot();
SpellEffects* GetSpellEffect(int32 id, Entity* caster = 0, bool on_char_load = false); SpellEffects* GetSpellEffect(int32 id, Entity* caster = 0, bool on_char_load = false);
SpellEffects* GetSpellEffectBySpellType(int8 spell_type); SpellEffects* GetSpellEffectBySpellType(int8 spell_type);
SpellEffects* GetSpellEffectWithLinkedTimer(int32 id, int32 linked_timer = 0, sint32 type_group_spell_id = 0, Entity* caster = 0); 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); LuaSpell* HasLinkedTimerID(LuaSpell* spell, Spawn* target = nullptr, bool stackWithOtherPlayers = true, bool checkNotCaster = false);
//flags //flags
int32 GetFlags() { return info_struct.get_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 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. This file is part of EQ2Emulator.
@ -17,11 +17,13 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef __EQ2_ITEMS__ #ifndef __EQ2_ITEMS__
#define __EQ2_ITEMS__ #define __EQ2_ITEMS__
#include <map> #include <map>
#include <vector> #include <vector>
#include <ctime> #include <ctime>
#include <shared_mutex>
#include "../../common/types.h" #include "../../common/types.h"
#include "../../common/DataBuffer.h" #include "../../common/DataBuffer.h"
#include "../Commands/Commands.h" #include "../Commands/Commands.h"
@ -647,6 +649,47 @@ enum ItemEffectType {
EFFECT_CURE_TYPE_MAGIC=6, EFFECT_CURE_TYPE_MAGIC=6,
EFFECT_CURE_TYPE_ALL=7 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) #pragma pack(1)
struct ItemStatsValues{ struct ItemStatsValues{
sint16 str; sint16 str;
@ -710,10 +753,11 @@ struct ItemCore{
int16 count; int16 count;
int8 tier; int8 tier;
int8 num_slots; int8 num_slots;
int32 unique_id; int64 unique_id;
int8 num_free_slots; int8 num_free_slots;
int16 recommended_level; int16 recommended_level;
bool item_locked; bool item_locked;
int32 lock_flags;
bool new_item; bool new_item;
int16 new_index; int16 new_index;
}; };
@ -934,6 +978,8 @@ public:
#pragma pack() #pragma pack()
Item(); Item();
Item(Item* in_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(); ~Item();
string lowername; string lowername;
string name; string name;
@ -942,10 +988,15 @@ public:
int32 sell_price; int32 sell_price;
int32 sell_status; int32 sell_status;
int32 max_sell_value; int32 max_sell_value;
int64 broker_price;
bool is_search_store_item;
bool save_needed; bool save_needed;
int8 weapon_type; int8 weapon_type;
string adornment; string adornment;
string creator; string creator;
string seller_name;
int32 seller_char_id;
int64 seller_house_id;
int32 adorn0; int32 adorn0;
int32 adorn1; int32 adorn1;
int32 adorn2; int32 adorn2;
@ -986,6 +1037,7 @@ public:
bool crafted; bool crafted;
bool tinkered; bool tinkered;
int8 book_language; int8 book_language;
mutable std::shared_mutex item_lock_mtx_;
void AddEffect(string effect, int8 percentage, int8 subbulletflag); void AddEffect(string effect, int8 percentage, int8 subbulletflag);
void AddBookPage(int8 page, string page_text,int8 valign, int8 halign); void AddBookPage(int8 page, string page_text,int8 valign, int8 halign);
@ -1067,6 +1119,10 @@ public:
void AddSlot(int8 slot_id); void AddSlot(int8 slot_id);
void SetSlots(int32 slots); void SetSlots(int32 slots);
int16 GetIcon(int16 version); int16 GetIcon(int16 version);
bool TryLockItem(LockReason reason);
bool TryUnlockItem(LockReason reason);
bool IsItemLocked();
bool IsItemLockedFor(LockReason reason);
}; };
class MasterItemList{ class MasterItemList{
public: public:
@ -1079,14 +1135,16 @@ public:
Item* GetAllItemsByClassification(const char* name); Item* GetAllItemsByClassification(const char* name);
ItemStatsValues* CalculateItemBonuses(int32 item_id, Entity* entity = 0); ItemStatsValues* CalculateItemBonuses(int32 item_id, Entity* entity = 0);
ItemStatsValues* CalculateItemBonuses(Item* desc, Entity* entity = 0, ItemStatsValues* values = 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(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); vector<Item*>* GetItems(map<string, string> criteria, Client* client_to_map);
void AddItem(Item* item); void AddItem(Item* item);
bool IsBag(int32 item_id); bool IsBag(int32 item_id);
void RemoveAll(); void RemoveAll();
static int32 NextUniqueID(); static int64 NextUniqueID();
static void ResetUniqueID(int32 new_id);
static int32 next_unique_id;
int32 GetItemStatIDByName(std::string name); int32 GetItemStatIDByName(std::string name);
std::string GetItemStatNameByID(int32 id); std::string GetItemStatNameByID(int32 id);
void AddMappedItemStat(int32 id, std::string lower_case_name); 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 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); bool MoveItem(sint32 to_bag_id, int16 from_index, sint8 to, int8 appearance_type, int8 charges);
void EraseItem(Item* item); void EraseItem(Item* item);
Item* GetItemFromUniqueID(int32 item_id, bool include_bank = false, bool lock = true); 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); 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); 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 GetNumberOfFreeSlots();
int16 GetNumberOfItems(); int16 GetNumberOfItems();
int32 GetWeight(); int32 GetWeight();
@ -1132,7 +1198,7 @@ public:
void DestroyItem(int16 index); void DestroyItem(int16 index);
Item* CanStack(Item* item, bool include_bank = false); Item* CanStack(Item* item, bool include_bank = false);
vector<Item*> GetAllItemsFromID(int32 item, bool include_bank = false, bool lock = 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); bool AddItem(Item* item);
Item* GetItem(sint32 bag_slot, int16 slot, int8 appearance_type = 0); Item* GetItem(sint32 bag_slot, int16 slot, int8 appearance_type = 0);
@ -1143,7 +1209,10 @@ public:
map<int32, Item*>* GetAllItems(); map<int32, Item*>* GetAllItems();
bool HasFreeBankSlot(); bool HasFreeBankSlot();
int8 FindFreeBankSlot(); 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> ///<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='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> ///<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 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. This file is part of EQ2Emulator.
@ -17,6 +17,7 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifdef WIN32 #ifdef WIN32
#include <WinSock2.h> #include <WinSock2.h>
#include <windows.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->generic_info.part_of_quest_id = result->GetInt32Str("part_of_quest_id");
item->details.recommended_level = result->GetInt16Str("recommended_level"); item->details.recommended_level = result->GetInt16Str("recommended_level");
item->details.item_locked = false; item->details.item_locked = false;
item->details.lock_flags = 0;
item->generic_info.adventure_default_level = result->GetInt16Str("adventure_default_level"); item->generic_info.adventure_default_level = result->GetInt16Str("adventure_default_level");
item->generic_info.max_charges = result->GetInt16Str("max_charges"); item->generic_info.max_charges = result->GetInt16Str("max_charges");
item->generic_info.display_charges = result->GetInt8Str("display_charges"); item->generic_info.display_charges = result->GetInt8Str("display_charges");
@ -541,7 +543,7 @@ int32 WorldDatabase::LoadHouseContainers(int32 item_id){
if (item) if (item)
{ {
LogWrite(ITEM__DEBUG, 5, "Items", "\tHouse Container for item_id %u", id); 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->SetItemType(ITEM_TYPE_HOUSE_CONTAINER);
item->housecontainer_info->num_slots = result.GetInt8Str("num_slots"); 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->broker_commission = result.GetInt8Str("broker_commission");
item->housecontainer_info->fence_commission = result.GetInt8Str("fence_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++; total++;
} }
else 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); 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; Query query;
MYSQL_ROW row; 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(result && (row = mysql_fetch_row(result)))
{ {
if(row[0]) if(row[0])
{ {
LogWrite(ITEM__DEBUG, 0, "Items", "%s: max(id): %u", __FUNCTION__, atoul(row[0])); int64 max_ = strtoull(row[0], NULL, 0);
return strtoul(row[0], NULL, 0); LogWrite(ITEM__DEBUG, 0, "Items", "%s: max(id): %u", __FUNCTION__, max_);
return max_;
} }
else else
return 0; return 0;
@ -1119,6 +1127,31 @@ int32 WorldDatabase::LoadNextUniqueItemID()
return 0; 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) void WorldDatabase::SaveItems(Client* client)
{ {
LogWrite(ITEM__DEBUG, 3, "Items", "Save Items for Player %i", client->GetCharacterID()); 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; int8 remainder = item->details.count % 255;
item->details.count = remainder; item->details.count = remainder;
if (item->details.inv_slot_id == -2) if (item->details.inv_slot_id == InventorySlotType::OVERFLOW)
player->item_list.AddOverflowItem(item); player->item_list.AddOverflowItem(item);
else { else {
if(!player->item_list.AddItem(item)) if(!player->item_list.AddItem(item))
@ -1398,7 +1431,7 @@ void WorldDatabase::LoadCharacterItemList(int32 account_id, int32 char_id, Playe
} }
} }
else { else {
if (item->details.inv_slot_id == -2) if (item->details.inv_slot_id == InventorySlotType::OVERFLOW)
player->item_list.AddOverflowItem(item); player->item_list.AddOverflowItem(item);
else else
player->item_list.AddItem(item); player->item_list.AddItem(item);

View File

@ -1,6 +1,6 @@
/* /*
EQ2Emulator: Everquest II Server Emulator 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. This file is part of EQ2Emulator.
@ -17,6 +17,7 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef __EQ2_ITEMS__ #ifndef __EQ2_ITEMS__
#define __EQ2_ITEMS__ #define __EQ2_ITEMS__
#include <map> #include <map>
@ -730,9 +731,7 @@ public:
void AddItem(Item* item); void AddItem(Item* item);
bool IsBag(int32 item_id); bool IsBag(int32 item_id);
void RemoveAll(); void RemoveAll();
static int32 NextUniqueID(); static int64 NextUniqueID();
static void ResetUniqueID(int32 new_id);
static int32 next_unique_id;
}; };
class PlayerItemList { class PlayerItemList {
public: public:

View File

@ -1,6 +1,6 @@
/* /*
EQ2Emulator: Everquest II Server Emulator 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. This file is part of EQ2Emulator.
@ -17,6 +17,7 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef __EQ2_ITEMS__ #ifndef __EQ2_ITEMS__
#define __EQ2_ITEMS__ #define __EQ2_ITEMS__
#include <map> #include <map>
@ -824,9 +825,7 @@ public:
void AddItem(Item* item); void AddItem(Item* item);
bool IsBag(int32 item_id); bool IsBag(int32 item_id);
void RemoveAll(); void RemoveAll();
static int32 NextUniqueID(); static int64 NextUniqueID();
static void ResetUniqueID(int32 new_id);
static int32 next_unique_id;
}; };
class PlayerItemList { class PlayerItemList {
public: public:

View File

@ -1,22 +1,23 @@
/* /*
EQ2Emulator: Everquest II Server Emulator 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. This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful, EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "LuaFunctions.h" #include "LuaFunctions.h"
#include "Spawn.h" #include "Spawn.h"
#include "WorldDatabase.h" #include "WorldDatabase.h"
@ -37,6 +38,7 @@
#include "ClientPacketFunctions.h" #include "ClientPacketFunctions.h"
#include "Transmute.h" #include "Transmute.h"
#include "Titles.h" #include "Titles.h"
#include "./Broker/BrokerManager.h"
#include <boost/algorithm/string/predicate.hpp> #include <boost/algorithm/string/predicate.hpp>
#include <sstream> #include <sstream>
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
@ -60,6 +62,7 @@ extern MasterRaceTypeList race_types_list;
extern MasterLanguagesList master_languages_list; extern MasterLanguagesList master_languages_list;
extern MasterTitlesList master_titles_list; extern MasterTitlesList master_titles_list;
extern RuleManager rule_manager; extern RuleManager rule_manager;
extern BrokerManager broker;
vector<string> ParseString(string strVal, char delim) { vector<string> ParseString(string strVal, char delim) {
stringstream ss(strVal); stringstream ss(strVal);
@ -9646,7 +9649,6 @@ int EQ2Emu_lua_CureByType(lua_State* state) {
} }
else { else {
ZoneServer* zone = spell->zone; ZoneServer* zone = spell->zone;
vector<int32> targets = spell->targets;
vector<Entity*> targets_to_cure; vector<Entity*> targets_to_cure;
for (int32 id : spell->GetTargets()) { for (int32 id : spell->GetTargets()) {
target = zone->GetSpawnByID(id); target = zone->GetSpawnByID(id);
@ -9797,6 +9799,7 @@ int EQ2Emu_lua_StartHeroicOpportunity(lua_State* state) {
Spawn* caster = lua_interface->GetSpawn(state); Spawn* caster = lua_interface->GetSpawn(state);
int8 class_id = lua_interface->GetInt8Value(state, 2); int8 class_id = lua_interface->GetInt8Value(state, 2);
Spawn* targetOverride = lua_interface->GetSpawn(state, 3);
lua_interface->ResetFunctionStack(state); lua_interface->ResetFunctionStack(state);
if (!caster) { if (!caster) {
@ -9810,10 +9813,13 @@ int EQ2Emu_lua_StartHeroicOpportunity(lua_State* state) {
} }
Spawn* target = caster->GetTarget(); 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)); lua_interface->LogError("%s: LUA StartHeroicOpportunity command error: target is not valid", lua_interface->GetScriptName(state));
return 0; return 0;
} }
if(!target)
target = targetOverride;
Client* client = ((Player*)caster)->GetClient(); Client* client = ((Player*)caster)->GetClient();
if (!client) { if (!client) {
@ -14656,3 +14662,103 @@ int EQ2Emu_lua_GetExpRequiredByLevel(lua_State* state) {
lua_interface->SetInt32Value(state, exp_required); lua_interface->SetInt32Value(state, exp_required);
return 1; 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 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. This file is part of EQ2Emulator.
@ -17,6 +17,7 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef LUA_FUNCTIONS_H #ifndef LUA_FUNCTIONS_H
#define 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_GetSpellRequiredLevel(lua_State* state);
int EQ2Emu_lua_GetExpRequiredByLevel(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 #endif

View File

@ -1,6 +1,6 @@
/* /*
EQ2Emulator: Everquest II Server Emulator 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. 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,"GetSpellRequiredLevel", EQ2Emu_lua_GetSpellRequiredLevel);
lua_register(state,"GetExpRequiredByLevel", EQ2Emu_lua_GetExpRequiredByLevel); 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, ...) { void LuaInterface::LogError(const char* error, ...) {

View File

@ -1,6 +1,6 @@
/* /*
EQ2Emulator: Everquest II Server Emulator 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. This file is part of EQ2Emulator.
@ -17,6 +17,7 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef LUA_INTERFACE_H #ifndef LUA_INTERFACE_H
#define LUA_INTERFACE_H #define LUA_INTERFACE_H
@ -161,7 +162,16 @@ struct LuaSpell{
std::shared_lock lock(targets_mutex); std::shared_lock lock(targets_mutex);
return std::find(targets.begin(), targets.end(), id) != targets.end(); 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::vector<int32> GetRemovedTargets() const {
std::shared_lock lock(targets_mutex); std::shared_lock lock(targets_mutex);

View File

@ -1,6 +1,6 @@
/* /*
EQ2Emulator: Everquest II Server Emulator 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. This file is part of EQ2Emulator.
@ -17,6 +17,7 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "NPC.h" #include "NPC.h"
#include "WorldDatabase.h" #include "WorldDatabase.h"
#include <math.h> #include <math.h>
@ -108,6 +109,8 @@ NPC::NPC(NPC* old_npc){
SetLootDropType(old_npc->GetLootDropType()); SetLootDropType(old_npc->GetLootDropType());
has_spells = old_npc->HasSpells(); has_spells = old_npc->HasSpells();
SetScaredByStrongPlayers(old_npc->IsScaredByStrongPlayers()); 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 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. 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 // Lock the hate list, we are altering the list so use a write lock
MHateList.writelock(__FUNCTION__, __LINE__); MHateList.writelock(__FUNCTION__, __LINE__);
map<int32, sint32>::iterator itr; map<int32, sint32>::iterator itr;
for (itr = m_hatelist.begin(); itr != m_hatelist.end(); 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()) if (spawn && spawn->IsEntity())
{ {
((Entity*)spawn)->MHatedBy.lock(); ((Entity*)spawn)->MHatedBy.lock();

View File

@ -1,6 +1,6 @@
/* /*
EQ2Emulator: Everquest II Server Emulator 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. This file is part of EQ2Emulator.
@ -17,6 +17,7 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef __NPC_AI_H__ #ifndef __NPC_AI_H__
#define __NPC_AI_H__ #define __NPC_AI_H__
#include "NPC.h" #include "NPC.h"
@ -59,7 +60,7 @@ public:
/// <param name="hate">The amount of hate to add</param> /// <param name="hate">The amount of hate to add</param>
virtual void AddHate(Entity* entity, sint32 hate); virtual void AddHate(Entity* entity, sint32 hate);
/// <summary>Completely clears the hate list for this npc</summary> /// <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> /// <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> /// <param name="entity">Entity to remove from this NPC's hate list</param>
void ClearHate(Entity* entity); void ClearHate(Entity* entity);

View File

@ -1,6 +1,6 @@
/* /*
EQ2Emulator: Everquest II Server Emulator 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. This file is part of EQ2Emulator.
@ -17,6 +17,7 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "World.h" #include "World.h"
#include "Object.h" #include "Object.h"
#include "Spells.h" #include "Spells.h"
@ -95,5 +96,7 @@ Object* Object::Copy(){
new_spawn->SetOmittedByDBFlag(IsOmittedByDBFlag()); new_spawn->SetOmittedByDBFlag(IsOmittedByDBFlag());
new_spawn->SetLootTier(GetLootTier()); new_spawn->SetLootTier(GetLootTier());
new_spawn->SetLootDropType(GetLootDropType()); new_spawn->SetLootDropType(GetLootDropType());
if(GetSpawnScriptSetDB() && GetSpawnScript())
new_spawn->SetSpawnScript(std::string(GetSpawnScript()));
return new_spawn; return new_spawn;
} }

View File

@ -1,6 +1,6 @@
/* /*
EQ2Emulator: Everquest II Server Emulator 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. This file is part of EQ2Emulator.
@ -137,6 +137,7 @@ Player::Player(){
active_drink_unique_id = 0; active_drink_unique_id = 0;
raidsheet_changed = false; raidsheet_changed = false;
hassent_raid = false; hassent_raid = false;
house_vault_slots = 0;
} }
Player::~Player(){ Player::~Player(){
SetSaveSpellEffects(true); 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("rain2", info_struct->get_wind()); //-102.24);
packet->setDataByName("status_points", info_struct->get_status_points()); packet->setDataByName("status_points", info_struct->get_status_points());
packet->setDataByName("guild_status", 888888); packet->setDataByName("guild_status", 888888);
packet->setDataByName("vault_slots", player->GetHouseVaultSlots());
if (house_zone_id > 0){ if (house_zone_id > 0){
string house_name = database.GetZoneName(house_zone_id); string house_name = database.GetZoneName(house_zone_id);
if(house_name.length() > 0) if(house_name.length() > 0)
@ -1484,7 +1485,7 @@ vector<EQ2Packet*> Player::UnequipItem(int16 index, sint32 bag_id, int8 slot, in
SetCharSheetChanged(true); SetCharSheetChanged(true);
SetEquipment(0, equip_slot_id ? equip_slot_id : old_slot); 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) if(appearance_type)
database.DeleteItem(GetCharacterID(), item, "APPEARANCE"); database.DeleteItem(GetCharacterID(), item, "APPEARANCE");
else else
@ -1645,8 +1646,8 @@ vector<EQ2Packet*> Player::UnequipItem(int16 index, sint32 bag_id, int8 slot, in
} }
} }
else { 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 == 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 == -4 && item->CheckFlag(NO_TRADE)) { if ((bag_id == InventorySlotType::SHARED_BANK || bag_id == InventorySlotType::HOUSE_VAULT) && !item_list.SharedBankAddAllowed(item)) {
PacketStruct* packet = configReader.getStruct("WS_DisplayText", version); PacketStruct* packet = configReader.getStruct("WS_DisplayText", version);
if (packet) { if (packet) {
packet->setDataByName("color", CHANNEL_COLOR_YELLOW); 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()); const char* zone_script = world.GetZoneScript(GetZone()->GetZoneID());
if (zone_script && lua_interface) 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); 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) { if (item->generic_info.condition == 0) {
Client* client = GetClient(); Client* client = GetClient();
if (client) { if (client) {
@ -2108,7 +2109,7 @@ bool Player::AddItem(Item* item, AddItemType type) {
safe_delete(item); safe_delete(item);
return false; return false;
} }
else if (item_list.AssignItemToFreeSlot(item)) { else if (item_list.AssignItemToFreeSlot(item, true)) {
item->save_needed = true; item->save_needed = true;
CalculateApplyWeight(); CalculateApplyWeight();
return true; 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) { 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); 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); int8 result = item_list.MoveItem(to_bag_id, from_index, new_slot, appearance_type, charges);
if (result == 1) { if (result == 1) {
if(isOverflow && item->details.inv_slot_id != -2) { if(isOverflow && item->details.inv_slot_id != -2) {
@ -3434,6 +3435,11 @@ void Player::AddMaintainedSpell(LuaSpell* luaspell){
if(!luaspell) if(!luaspell)
return; 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; Spell* spell = luaspell->spell;
MaintainedEffects* effect = GetFreeMaintainedSpellSlot(); MaintainedEffects* effect = GetFreeMaintainedSpellSlot();
int32 target_type = 0; 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_display where charid=%u", GetCharacterID());
savedEffects.AddQueryAsync(GetCharacterID(), &database, Q_DELETE, "delete from character_custom_spell_data where charid=%u", GetCharacterID()); savedEffects.AddQueryAsync(GetCharacterID(), &database, Q_DELETE, "delete from character_custom_spell_data where charid=%u", GetCharacterID());
InfoStruct* info = GetInfoStruct(); InfoStruct* info = GetInfoStruct();
MSpellEffects.readlock(__FUNCTION__, __LINE__);
MMaintainedSpells.readlock(__FUNCTION__, __LINE__); MMaintainedSpells.readlock(__FUNCTION__, __LINE__);
MSpellEffects.readlock(__FUNCTION__, __LINE__);
for(int i = 0; i < 45; i++) { for(int i = 0; i < 45; i++) {
if(info->spell_effects[i].spell_id != 0xFFFFFFFF) if(info->spell_effects[i].spell_id != 0xFFFFFFFF)
{ {
@ -7492,7 +7498,8 @@ void Player::SaveSpellEffects()
SaveCustomSpellDataIndex(info->spell_effects[i].spell); SaveCustomSpellDataIndex(info->spell_effects[i].spell);
SaveCustomSpellEffectsDisplay(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); Spawn* spawn = GetZone()->GetSpawnByID(info->maintained_effects[i].spell->initial_target);
int32 target_char_id = 0; int32 target_char_id = 0;
@ -7582,8 +7589,8 @@ void Player::SaveSpellEffects()
} }
} }
} }
MMaintainedSpells.releasereadlock(__FUNCTION__, __LINE__);
MSpellEffects.releasereadlock(__FUNCTION__, __LINE__); MSpellEffects.releasereadlock(__FUNCTION__, __LINE__);
MMaintainedSpells.releasereadlock(__FUNCTION__, __LINE__);
} }
void Player::MentorTarget() void Player::MentorTarget()

View File

@ -1,6 +1,6 @@
/* /*
EQ2Emulator: Everquest II Server Emulator 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. This file is part of EQ2Emulator.
@ -225,7 +225,7 @@ struct QuickBarItem{
int16 icon_type; int16 icon_type;
int32 id; int32 id;
int8 tier; int8 tier;
int32 unique_id; int64 unique_id;
EQ2_16BitString text; EQ2_16BitString text;
}; };
@ -1103,8 +1103,11 @@ public:
void SetActiveFoodUniqueID(int32 unique_id, bool update_db = true); void SetActiveFoodUniqueID(int32 unique_id, bool update_db = true);
void SetActiveDrinkUniqueID(int32 unique_id, bool update_db = true); void SetActiveDrinkUniqueID(int32 unique_id, bool update_db = true);
int32 GetActiveFoodUniqueID() { return active_food_unique_id; } int64 GetActiveFoodUniqueID() { return active_food_unique_id; }
int32 GetActiveDrinkUniqueID() { return active_drink_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; Mutex MPlayerQuests;
float pos_packet_speed; float pos_packet_speed;
@ -1264,8 +1267,10 @@ private:
Quest* GetAnyQuest(int32 quest_id); Quest* GetAnyQuest(int32 quest_id);
Quest* GetCompletedQuest(int32 quest_id); Quest* GetCompletedQuest(int32 quest_id);
std::atomic<int32> active_food_unique_id; std::atomic<int64> active_food_unique_id;
std::atomic<int32> active_drink_unique_id; std::atomic<int64> active_drink_unique_id;
int8 house_vault_slots;
}; };
#pragma pack() #pragma pack()
#endif #endif

View File

@ -1,21 +1,21 @@
/* /*
EQ2Emulator: Everquest II Server Emulator 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. This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful, EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "PlayerGroups.h" #include "PlayerGroups.h"
@ -34,6 +34,7 @@ extern ZoneList zone_list;
extern RuleManager rule_manager; extern RuleManager rule_manager;
extern PeerManager peer_manager; extern PeerManager peer_manager;
extern WorldDatabase database; extern WorldDatabase database;
extern LuaInterface* lua_interface;
/******************************************************** PlayerGroup ********************************************************/ /******************************************************** PlayerGroup ********************************************************/
PlayerGroup::PlayerGroup(int32 id) { PlayerGroup::PlayerGroup(int32 id) {
@ -1094,6 +1095,7 @@ void PlayerGroupManager::UpdateGroupBuffs() {
caster = *vitr; caster = *vitr;
caster->GetMaintainedMutex()->readlock(__FUNCTION__, __LINE__); caster->GetMaintainedMutex()->readlock(__FUNCTION__, __LINE__);
// go through the player's maintained spells // go through the player's maintained spells
bool skipSpell = false;
me = caster->GetMaintainedSpells(); me = caster->GetMaintainedSpells();
for (i = 0; i < NUM_MAINTAINED_EFFECTS; i++) { for (i = 0; i < NUM_MAINTAINED_EFFECTS; i++) {
if (me[i].spell_id == 0xFFFFFFFF) if (me[i].spell_id == 0xFFFFFFFF)
@ -1113,9 +1115,7 @@ void PlayerGroupManager::UpdateGroupBuffs() {
if (spell && spell->GetSpellData()->group_spell && spell->GetSpellData()->friendly_spell && 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)) { (spell->GetSpellData()->target_type == SPELL_TARGET_GROUP_AE || spell->GetSpellData()->target_type == SPELL_TARGET_RAID_AE)) {
luaspell->ClearCharTargets(); luaspell->ClearCharTargets();
for (target_itr = group->GetMembers()->begin(); target_itr != group->GetMembers()->end(); target_itr++) { for (target_itr = group->GetMembers()->begin(); target_itr != group->GetMembers()->end(); target_itr++) {
group_member = (*target_itr)->member; group_member = (*target_itr)->member;
@ -1127,8 +1127,39 @@ void PlayerGroupManager::UpdateGroupBuffs() {
client = (*target_itr)->client; 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; has_effect = false;
if (group_member->GetSpellEffect(spell->GetSpellID(), caster)) { if (group_member->GetSpellEffect(spell->GetSpellID(), caster)) {
has_effect = true; has_effect = true;
} }
@ -1226,13 +1257,18 @@ void PlayerGroupManager::UpdateGroupBuffs() {
client->QueuePacket(packet); client->QueuePacket(packet);
} }
} }
if(!skipSpell) {
luaspell->SwapTargets(new_target_list); luaspell->SwapTargets(new_target_list);
SpellProcess::AddSelfAndPet(luaspell, caster); SpellProcess::AddSelfAndPet(luaspell, caster);
new_target_list.clear(); 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 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. This file is part of EQ2Emulator.
@ -17,6 +17,7 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <assert.h> #include <assert.h>
#include "../../common/debug.h" #include "../../common/debug.h"
#include "../../common/Log.h" #include "../../common/Log.h"
@ -734,7 +735,12 @@ bool Recipe::PlayerHasComponentByItemID(Client* client, vector<Item*>* player_co
cur_qty = track_req_qty; cur_qty = track_req_qty;
track_req_qty -= cur_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_component_pair_qty->push_back({itemss[i]->details.unique_id, cur_qty});
player_components->push_back(itemss[i]); player_components->push_back(itemss[i]);
if(have_qty >= required_qty) if(have_qty >= required_qty)

View File

@ -1,22 +1,23 @@
/* /*
EQ2Emulator: Everquest II Server Emulator 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. This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful, EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "Sign.h" #include "Sign.h"
#include "../common/ConfigReader.h" #include "../common/ConfigReader.h"
@ -152,6 +153,8 @@ Sign* Sign::Copy(){
new_spawn->SetLootTier(GetLootTier()); new_spawn->SetLootTier(GetLootTier());
new_spawn->SetLootDropType(GetLootDropType()); new_spawn->SetLootDropType(GetLootDropType());
new_spawn->SetLanguage(GetLanguage()); new_spawn->SetLanguage(GetLanguage());
if(GetSpawnScriptSetDB() && GetSpawnScript())
new_spawn->SetSpawnScript(std::string(GetSpawnScript()));
return new_spawn; return new_spawn;
} }

View File

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

View File

@ -1,6 +1,6 @@
/* /*
EQ2Emulator: Everquest II Server Emulator 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. This file is part of EQ2Emulator.
@ -17,6 +17,7 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef __EQ2_SPAWN__ #ifndef __EQ2_SPAWN__
#define __EQ2_SPAWN__ #define __EQ2_SPAWN__
@ -1089,8 +1090,9 @@ public:
bool HasProvidedQuests(){ bool HasProvidedQuests(){
return (quest_ids.size() > 0); return (quest_ids.size() > 0);
} }
void SetSpawnScript(string name); void SetSpawnScript(string name, bool db_set = false);
const char* GetSpawnScript(); const char* GetSpawnScript();
bool GetSpawnScriptSetDB() { return spawn_script_setdb; }
vector<Spawn*>* GetSpawnGroup(); vector<Spawn*>* GetSpawnGroup();
bool HasSpawnGroup(); bool HasSpawnGroup();
@ -1321,9 +1323,15 @@ public:
{ {
pickup_unique_item_id = uniqueid; pickup_unique_item_id = uniqueid;
} }
void SetHouseCharacterID(int32 charid)
{
house_character_id = charid;
}
int32 GetPickupItemID() { return pickup_item_id; } int32 GetPickupItemID() { return pickup_item_id; }
int32 GetPickupUniqueItemID() { return pickup_unique_item_id; } int32 GetPickupUniqueItemID() { return pickup_unique_item_id; }
int32 GetHouseCharacterID() { return house_character_id; }
bool IsSoundsDisabled() { return disable_sounds; } bool IsSoundsDisabled() { return disable_sounds; }
void SetSoundsDisabled(bool val) { disable_sounds = val; } void SetSoundsDisabled(bool val) { disable_sounds = val; }
@ -1468,6 +1476,7 @@ protected:
int32 transporter_id; int32 transporter_id;
int32 pickup_item_id; int32 pickup_item_id;
int32 pickup_unique_item_id; int32 pickup_unique_item_id;
int32 house_character_id;
map<int32, vector<int16>* > required_quests; map<int32, vector<int16>* > required_quests;
map<int32, LUAHistory*> required_history; map<int32, LUAHistory*> required_history;
@ -1506,6 +1515,7 @@ private:
int tmp_action_state; int tmp_action_state;
int32 running_to; int32 running_to;
string spawn_script; string spawn_script;
bool spawn_script_setdb;
static int32 next_id; static int32 next_id;
ZoneServer* zone; ZoneServer* zone;
int32 spawn_location_id; int32 spawn_location_id;

View File

@ -1,6 +1,6 @@
/* /*
EQ2Emulator: Everquest II Server Emulator 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. This file is part of EQ2Emulator.
@ -17,6 +17,7 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "SpellProcess.h" #include "SpellProcess.h"
#include "../common/Log.h" #include "../common/Log.h"
#include "Tradeskills/Tradeskills.h" #include "Tradeskills/Tradeskills.h"
@ -433,10 +434,15 @@ bool SpellProcess::DeleteCasterSpell(LuaSpell* spell, string reason, bool removi
bool ret = false; bool ret = false;
Spawn* target = 0; Spawn* target = 0;
bool target_valid = false; bool target_valid = false;
if(spell) { 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) { if(remove_target) {
for (int32 id : spell->GetTargets()) { for (int32 id : spell->GetTargets()) {
if(remove_target->GetID() == id) { 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()){ if(remove_target->IsEntity()){
spell->RemoveTarget(remove_target->GetID()); spell->RemoveTarget(remove_target->GetID());
lua_interface->RemoveSpawnFromSpell(spell, remove_target); 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); CheckRemoveTargetFromSpell(spell, removing_all_spells, removing_all_spells);
ZoneServer* zone = spell->zone; ZoneServer* zone = spell->zone;
if(zone) { if(zone) {
LogWrite(SPELL__DEBUG, 0, "Spell", "SpellProcess::DeleteCasterSpell RemoveTargets Spell: %s.",
spell->spell->GetName());
for (int32 id : spell->GetTargets()) { for (int32 id : spell->GetTargets()) {
target = zone->GetSpawnByID(id); target = zone->GetSpawnByID(id);
if(target && target->IsEntity()){ 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()); spell->RemoveTarget(target->GetID());
lua_interface->RemoveSpawnFromSpell(spell, target); lua_interface->RemoveSpawnFromSpell(spell, target);
} }
else{ 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()); spell->RemoveTarget(spell->caster->GetID());
lua_interface->RemoveSpawnFromSpell(spell, spell->caster); 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 = target->GetID();
lua_spell->initial_target_char_id = (target && target->IsPlayer()) ? ((Player*)target)->GetCharacterID() : 0; lua_spell->initial_target_char_id = (target && target->IsPlayer()) ? ((Player*)target)->GetCharacterID() : 0;
GetSpellTargets(lua_spell); GetSpellTargets(lua_spell);
PrintTargets(lua_spell, "instant");
if (!lua_spell->spell->IsCopiedSpell()) if (!lua_spell->spell->IsCopiedSpell())
{ {
@ -1019,6 +1032,23 @@ Spell* SpellProcess::GetSpell(Entity* caster){
return spell; 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) 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) if((customSpell != 0 || spell != 0) && caster)
@ -1173,6 +1203,8 @@ void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster,
return; return;
} }
PrintTargets(lua_spell, "cast");
LuaSpell* conflictSpell = caster->HasLinkedTimerID(lua_spell, target, (spell_type == SPELL_TYPE_DD || spell_type == SPELL_TYPE_DOT)); LuaSpell* conflictSpell = caster->HasLinkedTimerID(lua_spell, target, (spell_type == SPELL_TYPE_DD || spell_type == SPELL_TYPE_DOT));
if(conflictSpell) if(conflictSpell)
{ {
@ -1185,18 +1217,17 @@ void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster,
if(spell->GetSpellData()->friendly_spell) if(spell->GetSpellData()->friendly_spell)
{ {
ZoneServer* zone = caster->GetZone(); ZoneServer* zone = caster->GetZone();
Spawn* tmpTarget = zone->GetSpawnByID(conflictSpell->initial_target);
if(tmpTarget && tmpTarget->IsEntity()) for (int32 id : conflictSpell->GetTargets()) {
{ Spawn* tmpTarget = zone->GetSpawnByID(id);
((Entity*)tmpTarget)->RemoveEffectsFromLuaSpell(conflictSpell); if(tmpTarget && tmpTarget->IsEntity())
zone->RemoveTargetFromSpell(conflictSpell, tmpTarget, false); {
CheckRemoveTargetFromSpell(conflictSpell); ((Entity*)tmpTarget)->RemoveEffectsFromLuaSpell(conflictSpell);
lua_interface->RemoveSpawnFromSpell(conflictSpell, tmpTarget); zone->RemoveTargetFromSpell(conflictSpell, tmpTarget, false);
if(client && IsReady(conflictSpell->spell, client->GetPlayer())) CheckRemoveTargetFromSpell(conflictSpell);
UnlockSpell(client, conflictSpell->spell); lua_interface->RemoveSpawnFromSpell(conflictSpell, tmpTarget);
}
} }
DeleteSpell(lua_spell);
return;
} }
else if(lua_spell->spell->GetSpellData()->spell_type == SPELL_TYPE_DEBUFF) 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 else
{ {
SpellCannotStack(zone, client, lua_spell->caster, lua_spell, conflictSpell); SpellCannotStack(zone, client, lua_spell->caster, lua_spell, conflictSpell);
return; return;
} }
} }
} }
@ -1717,8 +1748,8 @@ bool SpellProcess::CastProcessedSpell(LuaSpell* spell, bool passive, bool in_her
} }
} }
if(client && client->GetCurrentZone() && PrintTargets(spell, "castprocess");
!spell->spell->GetSpellData()->friendly_spell) if(client && client->GetCurrentZone())
{ {
ZoneServer* zone = client->GetCurrentZone(); ZoneServer* zone = client->GetCurrentZone();
Spawn* tmpTarget = zone->GetSpawnByID(spell->initial_target); 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)); LuaSpell* conflictSpell = spell->caster->HasLinkedTimerID(spell, tmpTarget, (spell_type == SPELL_TYPE_DD || spell_type == SPELL_TYPE_DOT));
if(conflictSpell && tmpTarget && tmpTarget->IsEntity()) if(conflictSpell && tmpTarget && tmpTarget->IsEntity())
{ {
((Entity*)tmpTarget)->RemoveSpellEffect(conflictSpell); if(conflictSpell->spell->GetSpellData()->min_class_skill_req > 0 && spell->spell->GetSpellData()->min_class_skill_req > 0)
zone->RemoveTargetFromSpell(conflictSpell, tmpTarget); {
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; continue;
} }
if (firstTarget && !spell->spell->GetSpellData()->not_maintained) {
spell->caster->AddMaintainedSpell(spell);
}
firstTarget = false;
SpellEffects* effect = ((Entity*)target)->GetSpellEffect(spell->spell->GetSpellID()); 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) { if(client) {
spell->caster->GetZone()->SendSpellFailedPacket(client, SPELL_ERROR_TAKE_EFFECT_MOREPOWERFUL); 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."); 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{ 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); ((Entity*)target)->AddSpellEffect(spell);
if(spell->spell->GetSpellData()->det_type > 0) if(spell->spell->GetSpellData()->det_type > 0)
((Entity*)target)->AddDetrimentalSpell(spell); ((Entity*)target)->AddDetrimentalSpell(spell);
@ -2251,6 +2322,8 @@ void SpellProcess::GetSpellTargets(LuaSpell* luaspell)
} }
else // default self cast for group/raid AE else // default self cast for group/raid AE
{ {
if(caster->IsPlayer())
GetPlayerGroupTargets((Player*)caster, caster, luaspell, true);
target = caster; target = caster;
luaspell->initial_target = caster->GetID(); luaspell->initial_target = caster->GetID();
luaspell->initial_target_char_id = (caster && caster->IsPlayer()) ? ((Player*)caster)->GetCharacterID() : 0; 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()) { for (int32 id : spell->GetTargets()) {
if (remove_spawn->GetID() == id) { if (remove_spawn->GetID() == id) {
found_target = true; found_target = true;
spell->RemoveTarget(id); 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()) if(remove_spawn && std::find(spawnsToRemove.begin(), spawnsToRemove.end(), remove_spawn) == spawnsToRemove.end())
spawnsToRemove.push_back(remove_spawn); spawnsToRemove.push_back(remove_spawn);
break; break;
@ -3035,6 +3109,16 @@ void SpellProcess::AddSelfAndPetToCharTargets(LuaSpell* spell, Spawn* caster, bo
void SpellProcess::DeleteActiveSpell(LuaSpell* spell, bool skipRemoveCurrent) { void SpellProcess::DeleteActiveSpell(LuaSpell* spell, bool skipRemoveCurrent) {
lua_interface->SetLuaUserDataStale(spell); 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) { if(!skipRemoveCurrent) {
lua_interface->RemoveCurrentSpell(spell->state, spell, true); lua_interface->RemoveCurrentSpell(spell->state, spell, true);
lua_interface->DeletePendingSpell(spell); lua_interface->DeletePendingSpell(spell);
@ -3052,7 +3136,21 @@ bool SpellProcess::AddLuaSpellTarget(LuaSpell* lua_spell, int32 id, bool lock_sp
lua_spell->AddTarget(id); lua_spell->AddTarget(id);
ret = true; 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; return ret;
} }

View File

@ -1,6 +1,6 @@
/* /*
EQ2Emulator: Everquest II Server Emulator 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. This file is part of EQ2Emulator.
@ -17,6 +17,7 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef __EQ2_SPELL_PROCESS__ #ifndef __EQ2_SPELL_PROCESS__
#define __EQ2_SPELL_PROCESS__ #define __EQ2_SPELL_PROCESS__
#include <mutex> #include <mutex>
@ -182,7 +183,8 @@ public:
/// <param name='lock'>??? not currently used</param> /// <param name='lock'>??? not currently used</param>
/// <param name='harvest_spell'>Is this a harvest spell?</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 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> /// <summary>Cast an EntityCommand (right click menu)</summary>
/// <param name='zone'>The current ZoneServer</param> /// <param name='zone'>The current ZoneServer</param>
/// <param name='entity_command'>the EntityCommand to cast</param> /// <param name='entity_command'>the EntityCommand to cast</param>

View File

@ -1,6 +1,6 @@
/* /*
EQ2Emulator: Everquest II Server Emulator 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. This file is part of EQ2Emulator.
@ -17,6 +17,7 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <boost/algorithm/string/predicate.hpp> #include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string.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 // TODO: use the vecotr to lock inventory slots
vector<pair<int32, int16>>::iterator itr; vector<pair<int32, int16>>::iterator itr;
vector<pair<int32, int16>>::iterator itr2;
bool missingItem = false; bool missingItem = false;
int32 itemid = 0; int32 itemid = 0;
vector<Item*> tmpItems; vector<Item*> tmpItems;
@ -266,7 +268,16 @@ void TradeskillMgr::BeginCrafting(Client* client, vector<pair<int32,int16>> comp
break; 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); tmpItems.push_back(item);
} }
@ -280,7 +291,8 @@ void TradeskillMgr::BeginCrafting(Client* client, vector<pair<int32,int16>> comp
vector<Item*>::iterator itemitr; vector<Item*>::iterator itemitr;
for (itemitr = tmpItems.begin(); itemitr != tmpItems.end(); itemitr++) { for (itemitr = tmpItems.begin(); itemitr != tmpItems.end(); itemitr++) {
Item* tmpItem = *itemitr; Item* tmpItem = *itemitr;
tmpItem->details.item_locked = false; if(tmpItem)
tmpItem->TryUnlockItem(LockReason::LockReason_Crafting);
} }
ClientPacketFunctions::StopCrafting(client); ClientPacketFunctions::StopCrafting(client);
return; return;
@ -358,13 +370,13 @@ void TradeskillMgr::StopCrafting(Client* client, bool lock) {
item = client->GetPlayer()->item_list.GetItemFromUniqueID(itmid); item = client->GetPlayer()->item_list.GetItemFromUniqueID(itmid);
if (item && item->details.count <= qty) if (item && item->details.count <= qty)
{ {
item->details.item_locked = false; item->TryUnlockItem(LockReason::LockReason_Crafting);
client->GetPlayer()->item_list.RemoveItem(item); client->GetPlayer()->item_list.RemoveItem(item);
updateInvReq = true; updateInvReq = true;
} }
else if(item) { else if(item) {
item->details.count -= qty; item->details.count -= qty;
item->details.item_locked = false; item->TryUnlockItem(LockReason::LockReason_Crafting);
item->save_needed = true; item->save_needed = true;
updateInvReq = true; updateInvReq = true;
} }

View File

@ -1,21 +1,21 @@
/* /*
EQ2Emu: Everquest II Server Emulator EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007-2025 EQ2Emu Development Team (https://www.eq2emu.com) 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 EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
EQ2Emu is distributed in the hope that it will be useful, EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with EQ2Emu. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "PeerManager.h" #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); 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 EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007-2025 EQ2Emu Development Team (https://www.eq2emu.com) 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 EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
EQ2Emu is distributed in the hope that it will be useful, EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with EQ2Emu. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef PEERMANAGER_H #ifndef PEERMANAGER_H
@ -225,6 +225,12 @@ public:
void sendZonePlayerList(std::vector<string>* queries, std::vector<WhoAllPeerPlayer>* peer_list, bool isGM); void sendZonePlayerList(std::vector<string>* queries, std::vector<WhoAllPeerPlayer>* peer_list, bool isGM);
bool GetClientGuildDetails(int32 matchCharID, GuildMember* member_details); 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 EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007-2025 EQ2Emu Development Team (https://www.eq2emu.com) 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 EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
EQ2Emu is distributed in the hope that it will be useful, EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with EQ2Emu. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "HTTPSClientPool.h" #include "HTTPSClientPool.h"
@ -24,6 +24,7 @@ along with EQ2Emu. If not, see <http://www.gnu.org/licenses/>.
#include "../LoginServer.h" #include "../LoginServer.h"
#include "../LuaInterface.h" #include "../LuaInterface.h"
#include "../Guilds/Guild.h" #include "../Guilds/Guild.h"
#include "../Broker/BrokerManager.h"
#include <boost/property_tree/ptree.hpp> #include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp> #include <boost/property_tree/json_parser.hpp>
@ -44,6 +45,7 @@ extern MasterSpellList master_spell_list;
extern MasterFactionList master_faction_list; extern MasterFactionList master_faction_list;
extern ClientList client_list; extern ClientList client_list;
extern GuildList guild_list; extern GuildList guild_list;
extern BrokerManager broker;
PeerManager peer_manager; PeerManager peer_manager;
HTTPSClientPool peer_https_pool; HTTPSClientPool peer_https_pool;
@ -1409,4 +1411,175 @@ void World::Web_worldhandle_activequery(const http::request<http::string_body>&
std::string json = oss.str(); std::string json = oss.str();
res.body() = json; res.body() = json;
res.prepare_payload(); 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 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. This file is part of EQ2Emulator.
@ -167,6 +167,8 @@ Widget* Widget::Copy(){
new_spawn->SetOpenZ(GetOpenZ()); new_spawn->SetOpenZ(GetOpenZ());
new_spawn->SetMultiFloorLift(multi_floor_lift); new_spawn->SetMultiFloorLift(multi_floor_lift);
new_spawn->SetSoundsDisabled(IsSoundsDisabled()); new_spawn->SetSoundsDisabled(IsSoundsDisabled());
if(GetSpawnScriptSetDB() && GetSpawnScript())
new_spawn->SetSpawnScript(std::string(GetSpawnScript()));
return new_spawn; return new_spawn;
} }
@ -417,6 +419,7 @@ void Widget::HandleUse(Client* client, string command, int8 overrideWidgetType){
ClientPacketFunctions::SendHouseVisitWindow(client, world.GetAllPlayerHousesByHouseID(hz->id)); ClientPacketFunctions::SendHouseVisitWindow(client, world.GetAllPlayerHousesByHouseID(hz->id));
ClientPacketFunctions::SendBaseHouseWindow(client, hz, ph, 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); ClientPacketFunctions::SendBaseHouseWindow(client, hz, ph, id);
client->GetCurrentZone()->SendHouseItems(client); client->GetCurrentZone()->SendHouseItems(client);
client->GetPlayer()->SetTarget(this);
} }
else { else {
if (hz) 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("/peerstatus", World::Web_worldhandle_peerstatus);
world_webserver->register_route("/activequery", World::Web_worldhandle_activequery); 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(); world_webserver->run();
LogWrite(INIT__INFO, 0, "Init", "World Web Server is listening on %s:%u..", web_ipaddr.c_str(), web_port); LogWrite(INIT__INFO, 0, "Init", "World Web Server is listening on %s:%u..", web_ipaddr.c_str(), web_port);
web_success = true; 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->escrow_status = escrow_status;
ph->upkeep_due = upkeep_due; ph->upkeep_due = upkeep_due;
ph->player_name = player_name; ph->player_name = player_name;
ph->character_id = char_id;
ReloadHouseData(ph); ReloadHouseData(ph);
m_playerHouses[house_id][char_id] = ph; m_playerHouses[house_id][char_id] = ph;
} }
@ -3709,4 +3715,5 @@ void ZoneInfoMemory::LoadFromDatabaseRow(MYSQL_ROW row) {
canGate = atoul(row[24]); canGate = atoul(row[24]);
cityZone = atoul(row[25]); cityZone = atoul(row[25]);
canEvac = atoul(row[26]); canEvac = atoul(row[26]);
zoneLuaScript = (row[27] != nullptr) ? row[27] : "";
} }

View File

@ -1,6 +1,6 @@
/* /*
EQ2Emulator: Everquest II Server Emulator 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. This file is part of EQ2Emulator.
@ -229,6 +229,7 @@ struct PlayerHouse {
int64 escrow_coins; int64 escrow_coins;
int32 escrow_status; int32 escrow_status;
string player_name; string player_name;
int32 character_id;
list<Deposit> deposits; list<Deposit> deposits;
map<string, Deposit> depositsMap; map<string, Deposit> depositsMap;
list<HouseHistory> history; list<HouseHistory> history;
@ -425,6 +426,7 @@ public:
std::string zoneDescription; std::string zoneDescription;
std::string zoneMotd; std::string zoneMotd;
std::string zoneSkyFile; std::string zoneSkyFile;
std::string zoneLuaScript;
float underworld; float underworld;
float safeX, safeY, safeZ, safeHeading; float safeX, safeY, safeZ, safeHeading;
int16 minimumLevel, maximumLevel, minimumVersion; 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_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_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_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); static void Web_populate_status(boost::property_tree::ptree& pt);

View File

@ -1,21 +1,21 @@
/* /*
EQ2Emulator: Everquest II Server Emulator 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. This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful, EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <math.h> #include <math.h>
@ -53,6 +53,7 @@ along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
#include "SpellProcess.h" #include "SpellProcess.h"
#include "races.h" #include "races.h"
#include "Web/PeerManager.h" #include "Web/PeerManager.h"
#include "Broker/BrokerManager.h"
extern Classes classes; extern Classes classes;
extern Commands commands; extern Commands commands;
@ -75,6 +76,7 @@ extern RuleManager rule_manager;
extern MasterLanguagesList master_languages_list; extern MasterLanguagesList master_languages_list;
extern ChestTrapList chest_trap_list; extern ChestTrapList chest_trap_list;
extern PeerManager peer_manager; extern PeerManager peer_manager;
BrokerManager broker;
//devn00b: Fix for linux builds since we dont use stricmp we use strcasecmp //devn00b: Fix for linux builds since we dont use stricmp we use strcasecmp
#if defined(__GNUC__) #if defined(__GNUC__)
@ -953,23 +955,29 @@ int32 WorldDatabase::LoadAppearances(ZoneServer* zone, Client* client){
return count; return count;
} }
void WorldDatabase::LoadNPCs(ZoneServer* zone){ void WorldDatabase::LoadNPCs(ZoneServer* zone, bool isInstanceType){
Query query; Query query;
MYSQL_ROW row; MYSQL_ROW row;
NPC* npc = 0; NPC* npc = 0;
int32 id = 0; int32 id = 0;
int32 total = 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" std::string houseTable("");
"INNER JOIN spawn_npcs npc\n" 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" "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" "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" "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" "WHERE lp.zone_id = %u and (lp.instance_id = 0 or lp.instance_id = %u)\n"
"GROUP BY s.id", "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))){ while(result && (row = mysql_fetch_row(result))){
/*npc->SetAppearanceID(atoi(row[12])); /*npc->SetAppearanceID(atoi(row[12]));
AppearanceData* appearance = world.GetNPCAppearance(npc->GetAppearanceID()); AppearanceData* appearance = world.GetNPCAppearance(npc->GetAppearanceID());
@ -1128,6 +1136,11 @@ void WorldDatabase::LoadNPCs(ZoneServer* zone){
npc->GetInfoStruct()->set_action_state(action_state_str); 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); zone->AddNPC(id, npc);
total++; total++;
LogWrite(NPC__DEBUG, 5, "NPC", "---Loading NPC: '%s' (%u)", npc->appearance.name, id); 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); 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; Query query;
MYSQL_ROW row; MYSQL_ROW row;
Sign* sign = 0; Sign* sign = 0;
int32 id = 0; int32 id = 0;
int32 total = 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" std::string houseTable("");
"FROM spawn s\n" if(isInstanceType) {
"INNER JOIN spawn_signs ss\n" 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" "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" "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" "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" "WHERE lp.zone_id = %u and (lp.instance_id = 0 or lp.instance_id = %u)\n"
"GROUP BY s.id", "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))){ while(result && (row = mysql_fetch_row(result))){
int32 signXpackFlag = atoul(row[28]); int32 signXpackFlag = atoul(row[28]);
@ -1330,6 +1347,11 @@ void WorldDatabase::LoadSigns(ZoneServer* zone){
sign->SetLanguage(atoul(row[36])); 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); zone->AddSign(id, sign);
total++; total++;
@ -1339,23 +1361,28 @@ void WorldDatabase::LoadSigns(ZoneServer* zone){
LogWrite(SIGN__DEBUG, 0, "Sign", "--Loaded %i Sign(s)", total); LogWrite(SIGN__DEBUG, 0, "Sign", "--Loaded %i Sign(s)", total);
} }
void WorldDatabase::LoadWidgets(ZoneServer* zone){ void WorldDatabase::LoadWidgets(ZoneServer* zone, bool isInstanceType){
Query query; Query query;
MYSQL_ROW row; MYSQL_ROW row;
Widget* widget = 0; Widget* widget = 0;
int32 id = 0; int32 id = 0;
int32 total = 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" std::string houseTable("");
"INNER JOIN spawn_widgets sw\n" 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" "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" "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" "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" "WHERE lp.zone_id = %u and (lp.instance_id = 0 or lp.instance_id = %u)\n"
"GROUP BY s.id", "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))){ while(result && (row = mysql_fetch_row(result))){
int32 widgetXpackFlag = atoul(row[33]); int32 widgetXpackFlag = atoul(row[33]);
int32 widgetHolidayFlag = atoul(row[34]); int32 widgetHolidayFlag = atoul(row[34]);
@ -1437,7 +1464,12 @@ void WorldDatabase::LoadWidgets(ZoneServer* zone){
widget->SetLootTier(atoul(row[39])); widget->SetLootTier(atoul(row[39]));
widget->SetLootDropType(atoul(row[40])); 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); zone->AddWidget(id, widget);
total++; total++;
@ -1447,23 +1479,28 @@ void WorldDatabase::LoadWidgets(ZoneServer* zone){
LogWrite(WIDGET__DEBUG, 0, "Widget", "--Loaded %i Widget(s)", total); LogWrite(WIDGET__DEBUG, 0, "Widget", "--Loaded %i Widget(s)", total);
} }
void WorldDatabase::LoadObjects(ZoneServer* zone){ void WorldDatabase::LoadObjects(ZoneServer* zone, bool isInstanceType){
Query query; Query query;
MYSQL_ROW row; MYSQL_ROW row;
Object* object = 0; Object* object = 0;
int32 id = 0; int32 id = 0;
int32 total = 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" std::string houseTable("");
"INNER JOIN spawn_objects so\n" 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" "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" "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" "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" "WHERE lp.zone_id = %u and (lp.instance_id = 0 or lp.instance_id = %u)\n"
"GROUP BY s.id", "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))){ while(result && (row = mysql_fetch_row(result))){
@ -1519,6 +1556,11 @@ void WorldDatabase::LoadObjects(ZoneServer* zone){
object->SetLootDropType(atoul(row[26])); 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); zone->AddObject(id, object);
total++; total++;
@ -1528,23 +1570,28 @@ void WorldDatabase::LoadObjects(ZoneServer* zone){
LogWrite(OBJECT__DEBUG, 0, "Object", "--Loaded %i Object(s)", total); LogWrite(OBJECT__DEBUG, 0, "Object", "--Loaded %i Object(s)", total);
} }
void WorldDatabase::LoadGroundSpawns(ZoneServer* zone){ void WorldDatabase::LoadGroundSpawns(ZoneServer* zone, bool isInstanceType){
Query query; Query query;
MYSQL_ROW row; MYSQL_ROW row;
GroundSpawn* spawn = 0; GroundSpawn* spawn = 0;
int32 id = 0; int32 id = 0;
int32 total = 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" std::string houseTable("");
"INNER JOIN spawn_ground sg\n" 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" "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" "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" "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" "WHERE lp.zone_id = %u and (lp.instance_id = 0 or lp.instance_id = %u)\n"
"GROUP BY s.id", "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))){ while(result && (row = mysql_fetch_row(result))){
int32 gsXpackFlag = atoul(row[21]); int32 gsXpackFlag = atoul(row[21]);
@ -1601,6 +1648,11 @@ void WorldDatabase::LoadGroundSpawns(ZoneServer* zone){
spawn->SetLootDropType(atoul(row[26])); 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); zone->AddGroundSpawn(id, spawn);
total++; total++;
LogWrite(GROUNDSPAWN__DEBUG, 5, "GSpawn", "---Loading GroundSpawn: '%s' (%u)", spawn->appearance.name, id); 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"); LogWrite(SPAWN__TRACE, 0, "Spawn", "Enter LoadSpawns");
int8 isInstanceType = (zone->GetInstanceType() == Instance_Type::PERSONAL_HOUSE_INSTANCE);
if(zone->GetInstanceID() == 0) { 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); 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); 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_associations = LoadSpawnLocationGroupAssociations(zone);
spawn_group_chances = LoadSpawnGroupChances(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 { 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); 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); 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_groups = LoadSpawnLocationGroups(zone);
spawn_group_associations = LoadSpawnLocationGroupAssociations(zone); spawn_group_associations = LoadSpawnLocationGroupAssociations(zone);
spawn_group_chances = LoadSpawnGroupChances(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"); LogWrite(SPAWN__TRACE, 0, "Spawn", "Exit LoadSpawns");
} }
bool WorldDatabase::UpdateSpawnLocationSpawns(Spawn* spawn) { bool WorldDatabase::UpdateSpawnLocationSpawns(Spawn* spawn) {
Query query; 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", int8 isInstanceType = (spawn && spawn->GetZone() && spawn->GetZone()->GetInstanceType() == Instance_Type::PERSONAL_HOUSE_INSTANCE);
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()); 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) { if (query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF) {
LogWrite(WORLD__ERROR, 0, "World", "Error in UpdateSpawnLocationSpawns query '%s': %s", query.GetQuery(), query.GetError()); LogWrite(WORLD__ERROR, 0, "World", "Error in UpdateSpawnLocationSpawns query '%s': %s", query.GetQuery(), query.GetError());
return false; return false;
@ -3588,10 +3660,10 @@ bool WorldDatabase::UpdateSpawnLocationSpawns(Spawn* spawn) {
return true; return true;
} }
bool WorldDatabase::UpdateSpawnWidget(int32 widget_id, char* queryString) { bool WorldDatabase::UpdateSpawnWidget(int32 widget_id, char* queryString,bool is_house) {
Query query; Query query;
query.RunQuery2(Q_UPDATE, "update spawn_widgets set %s where widget_id = %u", query.RunQuery2(Q_UPDATE, "update spawn_widgets%s set %s where widget_id = %u",
queryString, widget_id); is_house ? "_houses" : "", queryString, widget_id);
if (query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF) { if (query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF) {
LogWrite(WORLD__ERROR, 0, "World", "Error in UpdateSpawnWidget query '%s': %s", query.GetQuery(), query.GetError()); LogWrite(WORLD__ERROR, 0, "World", "Error in UpdateSpawnWidget query '%s': %s", query.GetQuery(), query.GetError());
return false; 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; Query query;
int32 ret = 0; 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) { if(result && mysql_num_rows(result) > 0) {
MYSQL_ROW row; MYSQL_ROW row;
row = mysql_fetch_row(result); row = mysql_fetch_row(result);
@ -3692,14 +3764,18 @@ bool WorldDatabase::SaveSpawnInfo(Spawn* spawn){
string suffix = getSafeEscapeString(spawn->GetSuffixTitle()); string suffix = getSafeEscapeString(spawn->GetSuffixTitle());
string prefix = getSafeEscapeString(spawn->GetPrefixTitle()); string prefix = getSafeEscapeString(spawn->GetPrefixTitle());
string last_name = getSafeEscapeString(spawn->GetLastName()); 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){ if(spawn->GetDatabaseID() == 0){
int32 new_spawn_id = GetNextSpawnIDInZone(spawn->GetZone()->GetZoneID(), isInstanceType);
int8 isInstanceType = (spawn->GetZone()->GetInstanceType() == Instance_Type::PERSONAL_HOUSE_INSTANCE); 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());
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());
if( new_spawn_id > 0 ) if( new_spawn_id > 0 )
spawn->SetDatabaseID(new_spawn_id); // use the new zone_id range 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 return false; // else, hang your head in shame as you are an utter failure
if(spawn->IsNPC()){ 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)", 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)",
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, 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); ((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()){ 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()){ else if(spawn->IsWidget()){
Widget* widget = (Widget*)spawn; 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()){ 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()) { 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{ else{
if(spawn->IsNPC()){ 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", 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",
name.c_str(), spawn->GetLevel(), spawn->GetLevel(), spawn->GetDifficulty(), spawn->GetRace(), spawn->GetModelType(), 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, 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(), ((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->GetTotalHPBase(), spawn->appearance.heroic_flag, spawn->GetTotalPowerBase(), spawn->GetCollisionRadius(), spawn->GetPrimaryCommandListID(),
spawn->GetSecondaryCommandListID(), spawn->GetVisualState(), spawn->GetActionState(), spawn->GetMoodState(), spawn->GetInitialState(), 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, 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()){ 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", 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",
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, 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(), spawn->GetDatabaseID()); spawn->GetCollisionRadius(), spawn->GetTotalHP(), spawn->GetTotalPower(), ((Object*)spawn)->GetDeviceID(), spawn->GetMerchantMinLevel(), spawn->GetMerchantMaxLevel(), houseTable.c_str(), spawn->GetDatabaseID());
} }
else if(spawn->IsWidget()){ else if(spawn->IsWidget()){
Widget* widget = (Widget*)spawn; Widget* widget = (Widget*)spawn;
@ -3750,25 +3826,25 @@ bool WorldDatabase::SaveSpawnInfo(Spawn* spawn){
char* closeSound = 0; char* closeSound = 0;
if (widget->GetOpenSound() != NULL) openSound = (char*)widget->GetOpenSound(); else openSound = (char*)string("0").c_str(); 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(); 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", 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",
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(), 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(), 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(), 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->GetOpenHeading(), widget->GetClosedHeading(), widget->GetOpenX(), widget->GetOpenY(), widget->GetOpenZ(),
widget->GetActionSpawnID(), openSound, closeSound,widget->GetOpenDuration(), widget->GetActionSpawnID(), openSound, closeSound,widget->GetOpenDuration(),
widget->GetCloseX(),widget->GetCloseY(),widget->GetCloseZ(),widget->GetLinkedSpawnID(),widget->GetHouseID(), 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()){ else if(spawn->IsSign()){
Sign* sign = (Sign*)spawn; 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", 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",
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(), 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(), 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(), 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->GetWidgetID(), sign->GetSignTitle(), sign->GetWidgetX(), sign->GetWidgetY(), sign->GetWidgetZ(),
sign->GetIconValue(), sign->GetSignDescription(), sign->GetSignDistance(), sign->GetSignZoneX(), sign->GetIconValue(), sign->GetSignDescription(), sign->GetSignDistance(), sign->GetSignZoneX(),
sign->GetSignZoneY(), sign->GetSignZoneZ(), sign->GetSignZoneHeading(), sign->GetIncludeHeading(), 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){ if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){
@ -3794,7 +3870,7 @@ int32 WorldDatabase::SaveCombinedSpawnLocation(ZoneServer* zone, Spawn* in_spawn
spawnLocationID = spawn_location_id; spawnLocationID = spawn_location_id;
if(!name) if(!name)
name = "Combine SpawnGroup Generated"; 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); safe_delete(spawns);
return 0; return 0;
} }
@ -3841,21 +3917,28 @@ bool WorldDatabase::SaveSpawnEntry(Spawn* spawn, const char* spawn_location_name
Query query; Query query;
Query query2; Query query2;
int32 count = 0; int32 count = 0;
int8 isInstanceType = (spawn->GetZone()->GetInstanceType() == Instance_Type::PERSONAL_HOUSE_INSTANCE);
std::string houseTable("");
if(isInstanceType) {
houseTable.append("_houses");
}
if(create_spawnlocation){ if(create_spawnlocation){
count = GetSpawnLocationCount(spawn->GetSpawnLocationID()); count = GetSpawnLocationCount(spawn->GetSpawnLocationID(), spawn);
if(count == 0){ if(count == 0){
if(!CreateNewSpawnLocation(spawn->GetSpawnLocationID(), spawn_location_name)) if(!CreateNewSpawnLocation(spawn->GetSpawnLocationID(), spawn_location_name, isInstanceType))
return false; 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){ if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){
LogWrite(SPAWN__ERROR, 0, "Spawn", "Error in SaveSpawnEntry query '%s': %s", query.GetQuery(), query.GetError()); LogWrite(SPAWN__ERROR, 0, "Spawn", "Error in SaveSpawnEntry query '%s': %s", query.GetQuery(), query.GetError());
return false; return false;
} }
if(save_zonespawn){ 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){ if(query2.GetErrorNumber() && query2.GetError() && query2.GetErrorNumber() < 0xFFFFFFFF){
LogWrite(SPAWN__ERROR, 0, "Spawn", "Error in SaveSpawnEntry query '%s': %s", query2.GetQuery(), query2.GetError()); LogWrite(SPAWN__ERROR, 0, "Spawn", "Error in SaveSpawnEntry query '%s': %s", query2.GetQuery(), query2.GetError());
return false; return false;
@ -3901,12 +3984,12 @@ float WorldDatabase::GetSpawnLocationPlacementOffsetZ(int32 location_id) {
return ret; return ret;
} }
bool WorldDatabase::CreateNewSpawnLocation(int32 id, const char* name){ bool WorldDatabase::CreateNewSpawnLocation(int32 id, const char* name, bool isHouseType){
Query query; Query query;
if(!name) if(!name)
name = "Unknown Spawn Location Name"; name = "Unknown Spawn Location Name";
string str_name = getSafeEscapeString(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){ if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){
LogWrite(SPAWN__ERROR, 0, "Spawn", "Error in CreateNewSpawnLocation query '%s': %s", query.GetQuery(), query.GetError()); LogWrite(SPAWN__ERROR, 0, "Spawn", "Error in CreateNewSpawnLocation query '%s': %s", query.GetQuery(), query.GetError());
return false; return false;
@ -3918,10 +4001,16 @@ int32 WorldDatabase::GetSpawnLocationCount(int32 location, Spawn* spawn){
Query query; Query query;
int32 ret = 0; int32 ret = 0;
MYSQL_RES* result = 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) 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 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){ if(result && mysql_num_rows(result) > 0){
MYSQL_ROW row; MYSQL_ROW row;
while(result && (row = mysql_fetch_row(result)) && row[0]){ while(result && (row = mysql_fetch_row(result)) && row[0]){
@ -3931,10 +4020,10 @@ int32 WorldDatabase::GetSpawnLocationCount(int32 location, Spawn* spawn){
return ret; return ret;
} }
int32 WorldDatabase::GetNextSpawnLocation(){ int32 WorldDatabase::GetNextSpawnLocation(bool isInstanceType){
Query query; Query query;
int32 ret = 0; 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){ if(result && mysql_num_rows(result) > 0){
MYSQL_ROW row; MYSQL_ROW row;
while(result && (row = mysql_fetch_row(result)) && row[0]){ while(result && (row = mysql_fetch_row(result)) && row[0]){
@ -3950,11 +4039,17 @@ bool WorldDatabase::RemoveSpawnFromSpawnLocation(Spawn* spawn){
Query query2; Query query2;
int32 count = GetSpawnLocationCount(spawn->GetSpawnLocationID(), spawn); 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) 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){ if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){
LogWrite(WORLD__ERROR, 0, "World", "Error in RemoveSpawnFromSpawnLocation query '%s': %s", query.GetQuery(), query.GetError()); LogWrite(WORLD__ERROR, 0, "World", "Error in RemoveSpawnFromSpawnLocation query '%s': %s", query.GetQuery(), query.GetError());
return false; 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_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_display where charid=%u", character_id);
query.AddQueryAsync(character_id, this, Q_DELETE, "delete from character_custom_spell_data 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; return true;
} }
@ -7129,13 +7225,17 @@ bool WorldDatabase::LoadNPC(ZoneServer* zone, int32 spawn_id) {
NPC* npc = nullptr; NPC* npc = nullptr;
int32 id = 0; int32 id = 0;
DatabaseResult result; DatabaseResult result;
int8 isInstanceType = (zone->GetInstanceType() == Instance_Type::PERSONAL_HOUSE_INSTANCE);
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" std::string houseTable("");
"FROM spawn s\n" if(isInstanceType) {
"INNER JOIN spawn_npcs npc\n" 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" "ON npc.spawn_id = s.id\n"
"WHERE s.id = %u", "WHERE s.id = %u",
spawn_id); houseTable.c_str(), houseTable.c_str(), spawn_id);
if (result.GetNumRows() > 0 && result.Next()) { if (result.GetNumRows() > 0 && result.Next()) {
id = result.GetInt32(0); id = result.GetInt32(0);
@ -7273,6 +7373,11 @@ bool WorldDatabase::LoadNPC(ZoneServer* zone, int32 spawn_id) {
npc->GetInfoStruct()->set_action_state(action_state_str); 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); zone->AddNPC(id, npc);
//skipped spells/skills/equipment as it is all loaded, the following rely on a spawn to load //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) if (!spawn)
return; return;
if (zone->house_object_database_lookup.count(spawn->GetModelType()) < 1)
zone->house_object_database_lookup.Put(spawn->GetModelType(), spawn->GetDatabaseID());
DatabaseResult result; DatabaseResult result;
database_new.Select(&result, "SELECT pickup_item_id, pickup_unique_item_id\n" 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"); LogWrite(SIGN__DEBUG, 0, "Sign", "ERROR in WorldDatabase::SaveSignMark");
return; return;
} }
} }
string WorldDatabase::GetSignMark(int32 char_id, int32 sign_id, char* char_name) { 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 so we dont break shit later divide by 0 and all that.
return 1; 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 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. This file is part of EQ2Emulator.
@ -17,6 +17,7 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef EQ2WORLD_EMU_DATABASE_H #ifndef EQ2WORLD_EMU_DATABASE_H
#define EQ2WORLD_EMU_DATABASE_H #define EQ2WORLD_EMU_DATABASE_H
@ -50,6 +51,7 @@
#include "Rules/Rules.h" #include "Rules/Rules.h"
#include "Languages.h" #include "Languages.h"
#include "World.h" #include "World.h"
#include "Broker/BrokerManager.h"
using namespace std; using namespace std;
@ -175,13 +177,13 @@ public:
void SaveWorldTime(WorldTime* time); void SaveWorldTime(WorldTime* time);
bool SaveSpawnInfo(Spawn* spawn); 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); 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 GetSpawnLocationPlacementOffsetX(int32 location_id);
float GetSpawnLocationPlacementOffsetY(int32 location_id); float GetSpawnLocationPlacementOffsetY(int32 location_id);
float GetSpawnLocationPlacementOffsetZ(int32 location_id); float GetSpawnLocationPlacementOffsetZ(int32 location_id);
int32 GetNextSpawnLocation(); int32 GetNextSpawnLocation(bool isInstanceType = false);
bool CreateNewSpawnLocation(int32 id, const char* name); bool CreateNewSpawnLocation(int32 id, const char* name, bool isHouseType = false);
bool RemoveSpawnFromSpawnLocation(Spawn* spawn); bool RemoveSpawnFromSpawnLocation(Spawn* spawn);
int32 GetSpawnLocationCount(int32 location, Spawn* spawn = 0); int32 GetSpawnLocationCount(int32 location, Spawn* spawn = 0);
vector<string>* GetSpawnNameList(const char* in_name); vector<string>* GetSpawnNameList(const char* in_name);
@ -240,7 +242,7 @@ public:
void UpdateStartingSkillbar(int32 char_id, int8 class_id, int8 race_id); 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); void UpdateStartingTitles(int32 char_id, int8 class_id, int8 race_id, int8 gender_id);
bool UpdateSpawnLocationSpawns(Spawn* spawn); bool UpdateSpawnLocationSpawns(Spawn* spawn);
bool UpdateSpawnWidget(int32 widget_id, char* query); bool UpdateSpawnWidget(int32 widget_id, char* query, bool is_house = false);
bool CheckVersionTable(); bool CheckVersionTable();
void LoadFactionAlliances(); void LoadFactionAlliances();
void LoadFactionList(); void LoadFactionList();
@ -275,16 +277,16 @@ public:
void LoadGroundSpawnItems(ZoneServer* zone); void LoadGroundSpawnItems(ZoneServer* zone);
void LoadSpawns(ZoneServer* zone); void LoadSpawns(ZoneServer* zone);
int8 GetAppearanceType(string type); int8 GetAppearanceType(string type);
void LoadNPCs(ZoneServer* zone); void LoadNPCs(ZoneServer* zone, bool isInstanceType = false);
void LoadSpiritShards(ZoneServer* zone); void LoadSpiritShards(ZoneServer* zone);
int32 LoadAppearances(ZoneServer* zone, Client* client = 0); int32 LoadAppearances(ZoneServer* zone, Client* client = 0);
int32 LoadNPCSpells(); int32 LoadNPCSpells();
int32 LoadNPCSkills(ZoneServer* zone); int32 LoadNPCSkills(ZoneServer* zone);
int32 LoadNPCEquipment(ZoneServer* zone); int32 LoadNPCEquipment(ZoneServer* zone);
void LoadObjects(ZoneServer* zone); void LoadObjects(ZoneServer* zone, bool isInstanceType = false);
void LoadGroundSpawns(ZoneServer* zone); void LoadGroundSpawns(ZoneServer* zone, bool isInstanceType = false);
void LoadWidgets(ZoneServer* zone); void LoadWidgets(ZoneServer* zone, bool isInstanceType = false);
void LoadSigns(ZoneServer* zone); void LoadSigns(ZoneServer* zone, bool isInstanceType = false);
void ReloadItemList(int32 item_id = 0); void ReloadItemList(int32 item_id = 0);
void LoadItemList(int32 item_id = 0); void LoadItemList(int32 item_id = 0);
int32 LoadItemStats(int32 item_id = 0); int32 LoadItemStats(int32 item_id = 0);
@ -293,7 +295,8 @@ public:
int32 LoadItemLevelOverride(int32 item_id = 0); int32 LoadItemLevelOverride(int32 item_id = 0);
int32 LoadItemEffects(int32 item_id = 0); int32 LoadItemEffects(int32 item_id = 0);
int32 LoadBookPages(int32 item_id = 0); int32 LoadBookPages(int32 item_id = 0);
int32 LoadNextUniqueItemID(); int64 LoadNextUniqueItemID();
void ResetNextUniqueItemID();
int32 LoadSkillItems(int32 item_id = 0); int32 LoadSkillItems(int32 item_id = 0);
int32 LoadRangeWeapons(int32 item_id = 0); int32 LoadRangeWeapons(int32 item_id = 0);
int32 LoadThrownWeapons(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); void LoadCharacterSpellEffects(int32 char_id, Client *client, int8 db_spell_type);
int32 GetMysqlExpCurve(int level); 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: private:
DatabaseNew database_new; DatabaseNew database_new;
std::map<int32, string> zone_names; std::map<int32, string> zone_names;

View File

@ -1,6 +1,6 @@
/* /*
EQ2Emulator: Everquest II Server Emulator 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. This file is part of EQ2Emulator.
@ -94,6 +94,7 @@
#include "AltAdvancement/AltAdvancement.h" #include "AltAdvancement/AltAdvancement.h"
#include "Bots/Bot.h" #include "Bots/Bot.h"
#include "VisualStates.h" #include "VisualStates.h"
#include "Broker/BrokerManager.h"
extern WorldDatabase database; extern WorldDatabase database;
extern const char* ZONE_NAME; extern const char* ZONE_NAME;
@ -130,6 +131,7 @@ extern MasterRecipeBookList master_recipebook_list;
extern VisualStates visual_states; extern VisualStates visual_states;
extern PeerManager peer_manager; extern PeerManager peer_manager;
extern HTTPSClientPool peer_https_pool; extern HTTPSClientPool peer_https_pool;
extern BrokerManager broker;
using namespace std; using namespace std;
@ -242,6 +244,10 @@ Client::Client(EQStream* ieqs) : underworld_cooldown_timer(5000), pos_update(125
recipe_xor_packet = nullptr; recipe_xor_packet = nullptr;
recipe_packet_count = 0; recipe_packet_count = 0;
recipe_orig_packet_size = 0; recipe_orig_packet_size = 0;
gm_store_search = false;
SetShopWindowStatus(false);
search_page = 0;
firstlogin_transmit = false;
} }
Client::~Client() { Client::~Client() {
@ -306,7 +312,7 @@ void Client::RemoveClientFromZone() {
zone_list.RemoveClientFromMap(player->GetName(), this); zone_list.RemoveClientFromMap(player->GetName(), this);
safe_delete(camp_timer); safe_delete(camp_timer);
safe_delete(search_items); ClearItemSearch();
safe_delete(current_rez.expire_timer); safe_delete(current_rez.expire_timer);
safe_delete(pending_last_name); safe_delete(pending_last_name);
safe_delete_array(incoming_paperdoll.image_bytes); safe_delete_array(incoming_paperdoll.image_bytes);
@ -838,8 +844,10 @@ void Client::SendCharInfo() {
if (player->GetHP() < player->GetTotalHP() || player->GetPower() < player->GetTotalPower()) if (player->GetHP() < player->GetTotalHP() || player->GetPower() < player->GetTotalPower())
GetCurrentZone()->AddDamagedSpawn(player); GetCurrentZone()->AddDamagedSpawn(player);
if (firstlogin) if (firstlogin) {
firstlogin_transmit = true;
firstlogin = false; firstlogin = false;
}
player->ClearProcs(); player->ClearProcs();
items = player->GetEquippedItemList(); items = player->GetEquippedItemList();
@ -910,6 +918,8 @@ void Client::SendCharInfo() {
GetPlayer()->SetSaveSpellEffects(false); GetPlayer()->SetSaveSpellEffects(false);
GetPlayer()->SetCharSheetChanged(true); GetPlayer()->SetCharSheetChanged(true);
GetPlayer()->SetReturningFromLD(false); GetPlayer()->SetReturningFromLD(false);
broker.LockActiveItemsForClient(this);
} }
void Client::SendZoneSpawns() { void Client::SendZoneSpawns() {
@ -1491,7 +1501,7 @@ bool Client::HandlePacket(EQApplicationPacket* app) {
spawn->position_changed = true; spawn->position_changed = true;
_snprintf(query, 256, "open_heading=%f,include_heading=1", newHeading); _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."); SimpleMessage(CHANNEL_COLOR_YELLOW, "Successfully saved widget open heading information.");
} }
else else
@ -1508,7 +1518,7 @@ bool Client::HandlePacket(EQApplicationPacket* app) {
spawn->position_changed = true; spawn->position_changed = true;
_snprintf(query, 256, "closed_heading=%f,include_heading=1", newHeading); _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."); SimpleMessage(CHANNEL_COLOR_YELLOW, "Successfully saved widget close heading information.");
if (spawn->GetSpawnLocationID()) if (spawn->GetSpawnLocationID())
@ -1921,6 +1931,26 @@ bool Client::HandlePacket(EQApplicationPacket* app) {
if (GetPlayer()->IsDeletedSpawn()) { if (GetPlayer()->IsDeletedSpawn()) {
GetPlayer()->SetDeletedSpawn(false); 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(); ResetZoningCoords();
SetReadyForUpdates(); SetReadyForUpdates();
GetPlayer()->SendSpawnChanges(true); GetPlayer()->SendSpawnChanges(true);
@ -2130,9 +2160,7 @@ bool Client::HandlePacket(EQApplicationPacket* app) {
EQ2_CommandString remote(app->pBuffer, app->size); EQ2_CommandString remote(app->pBuffer, app->size);
LogWrite(PACKET__DEBUG, 1, "Packet", "RemoteCmdMsg Packet dump:"); LogWrite(PACKET__DEBUG, 1, "Packet", "RemoteCmdMsg Packet dump:");
#if EQDEBUG >= 9
DumpPacket(app); DumpPacket(app);
#endif
commands.Process(remote.handler, &remote.command, this); commands.Process(remote.handler, &remote.command, this);
} }
else //bad client, disconnect else //bad client, disconnect
@ -2448,10 +2476,12 @@ bool Client::HandlePacket(EQApplicationPacket* app) {
int32 spawn_index = 0; int32 spawn_index = 0;
if (GetVersion() <= 561) { 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 { else {
house_id = packet->getType_int64_ByName("house_id"); house_id = packet->getType_int64_ByName("house_id");
spawn_index = packet->getType_int32_ByName("spawn_id");
} }
ZoneChangeDetails zone_details; ZoneChangeDetails zone_details;
if (GetHouseZoneServer(&zone_details, spawn_index, house_id)) { 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; return false;
} }
if (lootingPlayer->item_list.HasFreeSlot() || lootingPlayer->item_list.CanStack(item)) { if (lootingPlayer->item_list.HasFreeSlot() || lootingPlayer->item_list.CanStack(item, false)) {
if (lootingPlayer->item_list.AssignItemToFreeSlot(item)) { if (lootingPlayer->item_list.AssignItemToFreeSlot(item, true)) {
if (item->CheckFlag2(HEIRLOOM)) { // TODO: RAID Support if (item->CheckFlag2(HEIRLOOM)) { // TODO: RAID Support
GroupMemberInfo* gmi = lootingClient->GetPlayer()->GetGroupMemberInfo(); GroupMemberInfo* gmi = lootingClient->GetPlayer()->GetGroupMemberInfo();
@ -3396,6 +3426,16 @@ void Client::HandleExamineInfoRequest(EQApplicationPacket* app) {
item = GetPlayer()->GetAppearanceEquipmentList()->GetItemFromUniqueID(id); item = GetPlayer()->GetAppearanceEquipmentList()->GetItemFromUniqueID(id);
if (!item) if (!item)
item = master_item_list.GetItem(id); 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){ if (item) {// && sent_item_details.count(id) == 0){
MItemDetails.writelock(__FUNCTION__, __LINE__); MItemDetails.writelock(__FUNCTION__, __LINE__);
sent_item_details[id] = true; sent_item_details[id] = true;
@ -3407,7 +3447,7 @@ void Client::HandleExamineInfoRequest(EQApplicationPacket* app) {
delete item; delete item;
} }
else { 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); DumpPacket(app);
} }
} }
@ -3430,6 +3470,16 @@ void Client::HandleExamineInfoRequest(EQApplicationPacket* app) {
item = GetPlayer()->GetAppearanceEquipmentList()->GetItemFromUniqueID(unique_id); item = GetPlayer()->GetAppearanceEquipmentList()->GetItemFromUniqueID(unique_id);
if (!item) if (!item)
item = master_item_list.GetItem(id); 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) { if (item) {
MItemDetails.writelock(__FUNCTION__, __LINE__); MItemDetails.writelock(__FUNCTION__, __LINE__);
sent_item_details[id] = true; sent_item_details[id] = true;
@ -3438,7 +3488,7 @@ void Client::HandleExamineInfoRequest(EQApplicationPacket* app) {
QueuePacket(app); QueuePacket(app);
} }
else { 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); DumpPacket(app);
} }
} }
@ -3462,6 +3512,16 @@ void Client::HandleExamineInfoRequest(EQApplicationPacket* app) {
//int16 unknown5 = request->getType_sint16_ByName("unknown5"); //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); //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); 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) { if (item) {
//only display popup for non merchant links //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); 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); QueuePacket(app);
} }
else { 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); DumpPacket(app);
} }
} }
@ -3798,7 +3858,7 @@ bool Client::Process(bool zone_process) {
if (temp_placement_timer.Check()) { if (temp_placement_timer.Check()) {
if (GetTempPlacementSpawn() && GetPlayer()->WasSentSpawn(GetTempPlacementSpawn()->GetID()) && !hasSentTempPlacementSpawn) { if (GetTempPlacementSpawn() && GetPlayer()->WasSentSpawn(GetTempPlacementSpawn()->GetID()) && !hasSentTempPlacementSpawn) {
int8 placement = 0; int8 placement = 0;
int32 uniqueID = GetPlacementUniqueItemID(); int64 uniqueID = GetPlacementUniqueItemID();
Item* uniqueItem = GetPlayer()->item_list.GetItemFromUniqueID(uniqueID); Item* uniqueItem = GetPlayer()->item_list.GetItemFromUniqueID(uniqueID);
if (uniqueItem && uniqueItem->houseitem_info) if (uniqueItem && uniqueItem->houseitem_info)
placement = uniqueItem->houseitem_info->house_location; placement = uniqueItem->houseitem_info->house_location;
@ -8107,7 +8167,9 @@ bool Client::AddItem(Item* item, bool* item_deleted, AddItemType type) {
return false; return false;
} }
OpenShopWindow(nullptr);
return true; return true;
} }
@ -8301,6 +8363,9 @@ void Client::SellItem(int32 item_id, int16 quantity, int32 unique_id) {
Guild* guild = GetPlayer()->GetGuild(); Guild* guild = GetPlayer()->GetGuild();
if (spawn && spawn->GetMerchantID() > 0 && (!(spawn->GetMerchantType() & MERCHANT_TYPE_NO_BUY)) && if (spawn && spawn->GetMerchantID() > 0 && (!(spawn->GetMerchantType() & MERCHANT_TYPE_NO_BUY)) &&
spawn->IsClientInMerchantLevelRange(this)) { spawn->IsClientInMerchantLevelRange(this)) {
if((spawn->GetMerchantType() & MERCHANT_TYPE_LOTTO))
return;
int32 total_sell_price = 0; int32 total_sell_price = 0;
int32 total_status_sell_price = 0; //for status int32 total_status_sell_price = 0; //for status
float multiplier = CalculateBuyMultiplier(spawn->GetMerchantID()); float multiplier = CalculateBuyMultiplier(spawn->GetMerchantID());
@ -8316,11 +8381,15 @@ void Client::SellItem(int32 item_id, int16 quantity, int32 unique_id) {
if (!item) if (!item)
item = player->item_list.GetItemFromID(item_id); item = player->item_list.GetItemFromID(item_id);
if (item && master_item) { 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."); SimpleMessage(CHANNEL_COLOR_RED, "You cannot sell an item in the bank.");
return; 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."); SimpleMessage(CHANNEL_COLOR_RED, "You cannot sell the item in use.");
return; return;
@ -8418,6 +8487,9 @@ void Client::BuyBack(int32 item_id, int16 quantity) {
Spawn* spawn = GetCurrentZone()->GetSpawnByID(GetMerchantTransactionID()); Spawn* spawn = GetCurrentZone()->GetSpawnByID(GetMerchantTransactionID());
if (spawn && spawn->GetMerchantID() > 0 && (!(spawn->GetMerchantType() & MERCHANT_TYPE_NO_BUY_BACK)) && if (spawn && spawn->GetMerchantID() > 0 && (!(spawn->GetMerchantType() & MERCHANT_TYPE_NO_BUY_BACK)) &&
spawn->IsClientInMerchantLevelRange(this)) { spawn->IsClientInMerchantLevelRange(this)) {
if((spawn->GetMerchantType() & MERCHANT_TYPE_LOTTO))
return;
deque<BuyBackItem*>::iterator itr; deque<BuyBackItem*>::iterator itr;
BuyBackItem* buyback = 0; BuyBackItem* buyback = 0;
BuyBackItem* closest = 0; BuyBackItem* closest = 0;
@ -8446,7 +8518,7 @@ void Client::BuyBack(int32 item_id, int16 quantity) {
sint64 dispFlags = 0; 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)) 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."); 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."); SimpleMessage(CHANNEL_COLOR_RED, "You do not have any slots available for this item.");
else if (player->RemoveCoins(closest->quantity * closest->price)) { else if (player->RemoveCoins(closest->quantity * closest->price)) {
bool removed = false; bool removed = false;
@ -8489,9 +8561,15 @@ void Client::BuyBack(int32 item_id, int16 quantity) {
void Client::BuyItem(int32 item_id, int16 quantity) { void Client::BuyItem(int32 item_id, int16 quantity) {
// Get the merchant we are buying from // 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()); Spawn* spawn = GetCurrentZone()->GetSpawnByID(GetMerchantTransactionID());
// Make sure the spawn has a merchant list // 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; int32 total_buy_price = 0;
float multiplier = CalculateBuyMultiplier(spawn->GetMerchantID()); float multiplier = CalculateBuyMultiplier(spawn->GetMerchantID());
int32 sell_price = 0; int32 sell_price = 0;
@ -8538,7 +8616,7 @@ void Client::BuyItem(int32 item_id, int16 quantity) {
total_buy_price = sell_price * quantity; total_buy_price = sell_price * quantity;
item = new Item(master_item); item = new Item(master_item);
item->details.count = quantity; 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."); SimpleMessage(CHANNEL_COLOR_RED, "You do not have any slots available for this item.");
lua_interface->SetLuaUserDataStale(item); lua_interface->SetLuaUserDataStale(item);
safe_delete(item); safe_delete(item);
@ -8683,6 +8761,9 @@ void Client::BuyItem(int32 item_id, int16 quantity) {
void Client::RepairItem(int32 item_id) { void Client::RepairItem(int32 item_id) {
Spawn* spawn = GetCurrentZone()->GetSpawnByID(GetMerchantTransactionID()); Spawn* spawn = GetCurrentZone()->GetSpawnByID(GetMerchantTransactionID());
if (spawn && (spawn->GetMerchantType() & MERCHANT_TYPE_REPAIR)) { if (spawn && (spawn->GetMerchantType() & MERCHANT_TYPE_REPAIR)) {
if((spawn->GetMerchantType() & MERCHANT_TYPE_LOTTO))
return;
Item* item = player->item_list.GetItemFromID(item_id); Item* item = player->item_list.GetItemFromID(item_id);
if (!item) if (!item)
item = player->GetEquipmentList()->GetItemFromItemID(item_id); item = player->GetEquipmentList()->GetItemFromItemID(item_id);
@ -8721,6 +8802,9 @@ void Client::RepairItem(int32 item_id) {
void Client::RepairAllItems() { void Client::RepairAllItems() {
Spawn* spawn = GetCurrentZone()->GetSpawnByID(GetMerchantTransactionID()); Spawn* spawn = GetCurrentZone()->GetSpawnByID(GetMerchantTransactionID());
if (spawn && (spawn->GetMerchantType() & MERCHANT_TYPE_REPAIR)) { if (spawn && (spawn->GetMerchantType() & MERCHANT_TYPE_REPAIR)) {
if((spawn->GetMerchantType() & MERCHANT_TYPE_LOTTO))
return;
vector<Item*>* repairable_items = GetRepairableItems(); vector<Item*>* repairable_items = GetRepairableItems();
if (repairable_items && repairable_items->size() > 0) { if (repairable_items && repairable_items->size() > 0) {
vector<Item*>::iterator itr; vector<Item*>::iterator itr;
@ -8869,6 +8953,9 @@ void Client::SendAchievementUpdate(bool first_login) {
void Client::SendBuyMerchantList(bool sell) { void Client::SendBuyMerchantList(bool sell) {
Spawn* spawn = GetCurrentZone()->GetSpawnByID(GetMerchantTransactionID()); Spawn* spawn = GetCurrentZone()->GetSpawnByID(GetMerchantTransactionID());
if (spawn && spawn->GetMerchantID() > 0 && spawn->IsClientInMerchantLevelRange(this)) { 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); vector<MerchantItemInfo>* items = world.GetMerchantItemList(spawn->GetMerchantID(), spawn->GetMerchantType(), player);
if (items) { if (items) {
PacketStruct* packet = configReader.getStruct("WS_UpdateMerchant", GetVersion()); 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++) { for (test_itr = items->begin(); test_itr != items->end(); test_itr++) {
bool isbagwithitems = false; bool isbagwithitems = false;
if(test_itr->second && (test_itr->second->details.inv_slot_id == -3 || test_itr->second->details.inv_slot_id == -4)) if(test_itr->second && (test_itr->second->details.inv_slot_id < 0))
continue; // omit bank/shared-bank 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)) 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; 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; BuyBackItem* item = new BuyBackItem;
item->item_id = item_id; item->item_id = item_id;
item->unique_id = unique_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) { if (items) {
safe_delete(search_items); ClearItemSearch();
search_items = items; {
std::lock_guard<std::mutex> L(item_search_mtx_);
search_items = items;
search_values = values;
}
} }
} }
vector<Item*>* Client::GetSearchItems() { void Client::ClearItemSearch() {
return search_items; 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) { void Client::SearchStore(int32 page) {
std::lock_guard<std::mutex> L(item_search_mtx_);
SetSearchPage(page);
if (search_items) { if (search_items) {
PacketStruct* packet = configReader.getStruct("WS_BrokerItems", GetVersion()); PacketStruct* packet = configReader.getStruct("WS_BrokerItems", GetVersion());
if (packet) { if (packet) {
@ -10447,27 +10603,49 @@ void Client::SearchStore(int32 page) {
std::string teststr("test "); std::string teststr("test ");
teststr.append(std::to_string(i)); teststr.append(std::to_string(i));
packet->setArrayDataByName("string_one", teststr.c_str(), i); packet->setArrayDataByName("string_one", teststr.c_str(), i);
packet->setArrayDataByName("string_two", "testtwo", i); if(IsGMStoreSearch()) {
packet->setArrayDataByName("seller_name", "EQ2EMuDev", i); packet->setArrayDataByName("seller_name", "EQ2EMuDev", i);
packet->setArrayDataByName("item_id", item->details.item_id, i); packet->setArrayDataByName("item_id", item->details.item_id, i);
packet->setArrayDataByName("item_id2", 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("icon", item->GetIcon(GetVersion()), i);
//packet->setArrayDataByName("unknown2b", i, i); //packet->setArrayDataByName("unknown2b", i, i);
packet->setArrayDataByName("item_seller_id", 1, 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("stack_size", item->stack_count, i);
packet->setArrayDataByName("sell_price", item->sell_price, i);
std::string tmpStr(""); std::string tmpStr("");
tmpStr.append(item->name.c_str()); tmpStr.append(item->name.c_str());
tmpStr.append(" ("); if(IsGMStoreSearch()) {
tmpStr.append(std::to_string(item->details.item_id)); tmpStr.append(" (");
tmpStr.append(")"); tmpStr.append(std::to_string(item->details.item_id));
tmpStr.append(")");
}
packet->setArrayDataByName("item_name", tmpStr.c_str(), i); packet->setArrayDataByName("item_name", tmpStr.c_str(), i);
packet->setArrayDataByName("req_level", item->generic_info.adventure_default_level, 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; new_client_login = NewLoginState::LOGIN_ALLOWED;
} }
// vault slots
RefreshVaultSlotCount();
const char* zone_script = world.GetZoneScript(GetCurrentZone()->GetZoneID()); const char* zone_script = world.GetZoneScript(GetCurrentZone()->GetZoneID());
if (zone_script && lua_interface) if (zone_script && lua_interface)
lua_interface->RunZoneScript(zone_script, "new_client", GetCurrentZone(), GetPlayer()); 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) bool Client::HandleHouseEntityCommands(Spawn* spawn, int32 spawnid, string command)
{ {
if (GetCurrentZone()->GetInstanceType() != PERSONAL_HOUSE_INSTANCE) if (GetCurrentZone()->GetInstanceType() != PERSONAL_HOUSE_INSTANCE)
@ -12369,26 +12670,12 @@ bool Client::PopulateHouseSpawn(PacketStruct* place_object)
{ {
Spawn* tmp = GetTempPlacementSpawn(); Spawn* tmp = GetTempPlacementSpawn();
int32 spawn_group_id = database.GetNextSpawnLocation(); int32 spawn_group_id = database.GetNextSpawnLocation(true);
tmp->SetSpawnLocationID(spawn_group_id); tmp->SetSpawnLocationID(spawn_group_id);
float newHeading = place_object->getType_float_ByName("heading") + 180; float newHeading = place_object->getType_float_ByName("heading") + 180;
int32 spawnDBID = 0; 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->SetX(place_object->getType_float_ByName("x"));
tmp->SetY(place_object->getType_float_ByName("y")); tmp->SetY(place_object->getType_float_ByName("y"));
@ -12404,7 +12691,6 @@ bool Client::PopulateHouseSpawn(PacketStruct* place_object)
if (!spawnDBID) 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 // 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()); GetCurrentZone()->AddObject(tmp->GetDatabaseID(), ((Object*)tmp)->Copy());
} }
@ -12423,33 +12709,41 @@ bool Client::PopulateHouseSpawnFinalize()
GetCurrentZone()->AddSpawn(tmp); GetCurrentZone()->AddSpawn(tmp);
GetCurrentZone()->SendSpawnChanges(tmp, this); GetCurrentZone()->SendSpawnChanges(tmp, this);
SetTempPlacementSpawn(nullptr); SetTempPlacementSpawn(nullptr);
int32 uniqueID = GetPlacementUniqueItemID(); int64 uniqueID = GetPlacementUniqueItemID();
Item* uniqueItem = GetPlayer()->item_list.GetItemFromUniqueID(uniqueID); 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->SetPickupItemID(uniqueItem->details.item_id);
tmp->SetPickupUniqueItemID(uniqueID); tmp->SetPickupUniqueItemID(uniqueID);
if (GetCurrentZone()->GetInstanceType() == PERSONAL_HOUSE_INSTANCE)
if (uniqueItem)
{ {
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);
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);
} }
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; return true;
} }
@ -13648,6 +13942,18 @@ bool Client::GetHouseZoneServer(ZoneChangeDetails* zone_details, int32 spawn_id,
hz = world.GetHouseZone(ph->house_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 (ph && hz) {
if (zone_list.GetZoneByInstance(zone_details, ph->instance_id, hz->zone_id)) { 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); safe_delete(p);
return true; 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 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. This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful, EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef CLIENT_H #ifndef CLIENT_H
#define CLIENT_H #define CLIENT_H
@ -77,7 +78,7 @@ struct QueuedQuest {
struct BuyBackItem { struct BuyBackItem {
int32 item_id; int32 item_id;
int32 unique_id; int64 unique_id;
int16 quantity; int16 quantity;
int32 price; int32 price;
bool save_needed; bool save_needed;
@ -340,6 +341,7 @@ public:
inline int32 GetAccountID() { return account_id; } inline int32 GetAccountID() { return account_id; }
inline const char* GetAccountName() { return account_name; } inline const char* GetAccountName() { return account_name; }
inline sint16 GetAdminStatus() { return admin_status; } inline sint16 GetAdminStatus() { return admin_status; }
inline bool IsGMStoreSearch() { return gm_store_search; }
inline int16 GetVersion() { return version; } inline int16 GetVersion() { return version; }
void SetNameCRC(int32 val) { name_crc = val; } void SetNameCRC(int32 val) { name_crc = val; }
int32 GetNameCRC() { return name_crc; } int32 GetNameCRC() { return name_crc; }
@ -348,8 +350,8 @@ public:
void SetVersion(int16 new_version) { version = new_version; } void SetVersion(int16 new_version) { version = new_version; }
void SetAccountID(int32 in_accountid) { account_id = in_accountid; } void SetAccountID(int32 in_accountid) { account_id = in_accountid; }
void SetCharacterID(int32 in_characterid) { character_id = in_characterid; } 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(); void DetermineCharacterUpdates();
@ -429,7 +431,7 @@ public:
void BuyBack(int32 item_id, int16 quantity); void BuyBack(int32 item_id, int16 quantity);
void RepairItem(int32 item_id); void RepairItem(int32 item_id);
void RepairAllItems(); 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(); deque<BuyBackItem*>* GetBuyBacks();
vector<Item*>* GetRepairableItems(); vector<Item*>* GetRepairableItems();
vector<Item*>* GetItemsByEffectType(ItemEffectType type, ItemEffectType secondary_effect = NO_EFFECT_TYPE); vector<Item*>* GetItemsByEffectType(ItemEffectType type, ItemEffectType secondary_effect = NO_EFFECT_TYPE);
@ -452,9 +454,13 @@ public:
void SendNewAdventureSpells(); void SendNewAdventureSpells();
void SendNewTradeskillSpells(); void SendNewTradeskillSpells();
string GetCoinMessage(int32 total_coins); string GetCoinMessage(int32 total_coins);
void SetItemSearch(vector<Item*>* items); void SetItemSearch(vector<Item*>* items, map<string, string> values);
vector<Item*>* GetSearchItems(); void ClearItemSearch();
void SearchStore(int32 page); void SearchStore(int32 page);
void SendSellerItemByItemUniqueId(int64 unique_id);
void BuySellerItemByItemUniqueId(int64 unique_id, int16 quantity);
void SetSellerStatus();
void SetPlayer(Player* new_player); void SetPlayer(Player* new_player);
void AddPendingQuestAcceptReward(Quest* quest); void AddPendingQuestAcceptReward(Quest* quest);
@ -564,12 +570,20 @@ public:
Spawn* GetTempPlacementSpawn() { return tempPlacementSpawn; } Spawn* GetTempPlacementSpawn() { return tempPlacementSpawn; }
void SetPlacementUniqueItemID(int32 id) { placement_unique_item_id = id; } void SetPlacementUniqueItemID(int64 id) { placement_unique_item_id = id; }
int32 GetPlacementUniqueItemID() { return placement_unique_item_id; } int64 GetPlacementUniqueItemID() { return placement_unique_item_id; }
void SetHasOwnerOrEditAccess(bool val) { hasOwnerOrEditAccess = val; } void SetHasOwnerOrEditAccess(bool val) { hasOwnerOrEditAccess = val; }
bool HasOwnerOrEditAccess() { return hasOwnerOrEditAccess; } 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); 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 // find an appropriate spawn to use for the house object, save spawn location/entry data to DB
bool PopulateHouseSpawn(PacketStruct* place_object); bool PopulateHouseSpawn(PacketStruct* place_object);
@ -725,6 +739,10 @@ public:
void SetLastTellName(std::string tellName) { last_tell_name = tellName; } void SetLastTellName(std::string tellName) { last_tell_name = tellName; }
std::string GetLastTellName() { return last_tell_name; } std::string GetLastTellName() { return last_tell_name; }
void SetSearchPage(int32 page) { search_page = page; }
void SendMerchantWindow(Spawn* spawn, bool sell);
DialogManager dialog_manager; DialogManager dialog_manager;
private: private:
void AddRecipeToPlayerPack(Recipe* recipe, PacketStruct* packet, int16* i); void AddRecipeToPlayerPack(Recipe* recipe, PacketStruct* packet, int16* i);
@ -745,6 +763,8 @@ private:
Mutex MQuestQueue; Mutex MQuestQueue;
Mutex MDeletePlayer; Mutex MDeletePlayer;
vector<Item*>* search_items; vector<Item*>* search_items;
map<string, string> search_values;
int32 search_page;
int32 waypoint_id = 0; int32 waypoint_id = 0;
map<string, WaypointInfo> waypoints; map<string, WaypointInfo> waypoints;
int32 transport_spawn_id; int32 transport_spawn_id;
@ -775,6 +795,7 @@ private:
int32 account_id; int32 account_id;
int32 character_id; int32 character_id;
sint16 admin_status; // -2 Banned, -1 Suspended, 0 User, etc. sint16 admin_status; // -2 Banned, -1 Suspended, 0 User, etc.
bool gm_store_search;
char account_name[64]; char account_name[64];
char zone_name[64]; char zone_name[64];
int32 zoneID; int32 zoneID;
@ -811,8 +832,9 @@ private:
float zoning_y; float zoning_y;
float zoning_z; float zoning_z;
float zoning_h; 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 }; enum NewLoginState { LOGIN_NONE, LOGIN_DELAYED, LOGIN_ALLOWED, LOGIN_INITIAL_LOAD, LOGIN_SEND };
NewLoginState new_client_login; // 1 = delayed state, 2 = let client in NewLoginState new_client_login; // 1 = delayed state, 2 = let client in
Timer underworld_cooldown_timer; Timer underworld_cooldown_timer;
@ -857,7 +879,7 @@ private:
int32 delayedAccessKey; int32 delayedAccessKey;
Timer delayTimer; Timer delayTimer;
Spawn* tempPlacementSpawn; Spawn* tempPlacementSpawn;
int32 placement_unique_item_id; int64 placement_unique_item_id;
bool hasOwnerOrEditAccess; bool hasOwnerOrEditAccess;
bool hasSentTempPlacementSpawn; bool hasSentTempPlacementSpawn;
@ -891,6 +913,10 @@ private:
int recipe_orig_packet_size; int recipe_orig_packet_size;
std::string last_tell_name; std::string last_tell_name;
mutable std::mutex item_search_mtx_;
std::atomic<bool> shop_window_open;
map<string, string> str_values;
}; };
class ClientList { class ClientList {

View File

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

View File

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

View File

@ -1,6 +1,6 @@
/* /*
EQ2Emulator: Everquest II Server Emulator 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. This file is part of EQ2Emulator.
@ -17,6 +17,7 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>. along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef ZONESERVER_H #ifndef ZONESERVER_H
#define ZONESERVER_H #define ZONESERVER_H
@ -170,7 +171,7 @@ struct TrackedSpawn {
struct HouseItem { struct HouseItem {
int32 spawn_id; int32 spawn_id;
int32 item_id; int32 item_id;
int32 unique_id; int64 unique_id;
Item* item; Item* item;
}; };
@ -701,8 +702,6 @@ public:
Spawn* GetSpawnFromUniqueItemID(int32 unique_id); Spawn* GetSpawnFromUniqueItemID(int32 unique_id);
void SendHouseItems(Client* client); void SendHouseItems(Client* client);
MutexMap<int32, int32> house_object_database_lookup; // 1st int32 = model type, 2nd int32 = spawn id
int32 GetWatchdogTime() { return watchdogTimestamp; } int32 GetWatchdogTime() { return watchdogTimestamp; }
void SetWatchdogTime(int32 time) { watchdogTimestamp = time; } void SetWatchdogTime(int32 time) { watchdogTimestamp = time; }
void CancelThreads(); 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 the result as a tuple
return std::make_tuple(days.count(), hours.count(), minutes.count(), seconds.count()); return std::make_tuple(days.count(), hours.count(), minutes.count(), seconds.count());
} }

View File

@ -27,6 +27,9 @@
#include <vector> #include <vector>
#include <map> #include <map>
#include <tuple> #include <tuple>
#include <string>
#include <sstream>
#include <cstdint>
#ifndef ERRBUF_SIZE #ifndef ERRBUF_SIZE
#define ERRBUF_SIZE 1024 #define ERRBUF_SIZE 1024
@ -122,6 +125,44 @@ static bool IsPrivateAddress(uint32_t ip)
return false; 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){ template<class Type> void AddData(Type input, string* datastring){
if(datastring) if(datastring)
datastring->append((char*)&input, sizeof(input)); datastring->append((char*)&input, sizeof(input));