mirror of
https://github.com/EQEmu/Server.git
synced 2026-06-13 02:38:45 +00:00
Implement bazaar item identity and offline trading rework
This commit is contained in:
@@ -60,7 +60,7 @@ EQ::Net::WebsocketLoginStatus CheckLogin(
|
||||
|
||||
ret.account_name = database.GetAccountName(static_cast<uint32>(ret.account_id));
|
||||
ret.logged_in = true;
|
||||
ret.status = database.GetAccountStatus(ret.account_id);
|
||||
ret.status = database.GetAccountStatus(ret.account_id).status;
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
+40
-27
@@ -498,10 +498,10 @@ Client::Client(EQStreamInterface *ieqs) : Mob(
|
||||
client_data_loaded = false;
|
||||
berserk = false;
|
||||
dead = false;
|
||||
eqs = ieqs;
|
||||
ip = eqs->GetRemoteIP();
|
||||
port = ntohs(eqs->GetRemotePort());
|
||||
client_state = CLIENT_CONNECTING;
|
||||
eqs = ieqs ? ieqs : nullptr;
|
||||
ip = eqs ? eqs->GetRemoteIP() : 0;
|
||||
port = eqs ? ntohs(eqs->GetRemotePort()) : 0;
|
||||
client_state = eqs ? CLIENT_CONNECTING : CLIENT_CONNECTED;
|
||||
SetTrader(false);
|
||||
Haste = 0;
|
||||
SetCustomerID(0);
|
||||
@@ -688,6 +688,7 @@ Client::Client(EQStreamInterface *ieqs) : Mob(
|
||||
m_parcels.clear();
|
||||
|
||||
m_buyer_id = 0;
|
||||
m_offline = false;
|
||||
|
||||
SetBotPulling(false);
|
||||
SetBotPrecombat(false);
|
||||
@@ -716,10 +717,12 @@ Client::~Client() {
|
||||
zone->ClearEXPModifier(this);
|
||||
}
|
||||
|
||||
if (!IsZoning()) {
|
||||
if(IsInAGuild()) {
|
||||
guild_mgr.UpdateDbMemberOnline(CharacterID(), false);
|
||||
guild_mgr.SendGuildMemberUpdateToWorld(GetName(), GuildID(), 0, time(nullptr));
|
||||
if (!IsZoning() && IsInAGuild()) {
|
||||
guild_mgr.UpdateDbMemberOnline(CharacterID(), false);
|
||||
if (IsOffline()) {
|
||||
guild_mgr.SendGuildMemberUpdateToWorld(GetName(), GuildID(), GetZoneID(), time(nullptr), 1);
|
||||
} else {
|
||||
guild_mgr.SendGuildMemberUpdateToWorld(GetName(), GuildID(), 0, time(nullptr), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -731,11 +734,11 @@ Client::~Client() {
|
||||
if (merc)
|
||||
merc->Depop();
|
||||
|
||||
if(IsTrader()) {
|
||||
if(IsTrader() && !IsOffline()) {
|
||||
TraderEndTrader();
|
||||
}
|
||||
|
||||
if(IsBuyer()) {
|
||||
if(IsBuyer() && !IsOffline()) {
|
||||
ToggleBuyerMode(false);
|
||||
}
|
||||
|
||||
@@ -767,7 +770,9 @@ Client::~Client() {
|
||||
if(isgrouped && !bZoning && is_zone_loaded)
|
||||
LeaveGroup();
|
||||
|
||||
UpdateWho(2);
|
||||
if (!IsOffline() && !IsTrader()) {
|
||||
UpdateWho(2);
|
||||
}
|
||||
|
||||
if(IsHoveringForRespawn())
|
||||
{
|
||||
@@ -2143,6 +2148,9 @@ void Client::UpdateWho(uint8 remove)
|
||||
s->race = GetRace();
|
||||
s->class_ = GetClass();
|
||||
s->level = GetLevel();
|
||||
s->trader = IsTrader();
|
||||
s->buyer = IsBuyer();
|
||||
s->offline = IsOffline();
|
||||
|
||||
if (m_pp.anon == 0) {
|
||||
s->anon = 0;
|
||||
@@ -2211,7 +2219,7 @@ void Client::FriendsWho(char *FriendsString) {
|
||||
void Client::UpdateAdmin(bool from_database) {
|
||||
int16 tmp = admin;
|
||||
if (from_database) {
|
||||
admin = database.GetAccountStatus(account_id);
|
||||
admin = database.GetAccountStatus(account_id).status;
|
||||
}
|
||||
|
||||
if (tmp == admin && from_database) {
|
||||
@@ -2527,6 +2535,7 @@ void Client::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho)
|
||||
ns->spawn.guildID = GuildID();
|
||||
ns->spawn.trader = IsTrader();
|
||||
ns->spawn.buyer = IsBuyer();
|
||||
ns->spawn.offline = IsOffline();
|
||||
// ns->spawn.linkdead = IsLD() ? 1 : 0;
|
||||
// ns->spawn.pvp = GetPVP(false) ? 1 : 0;
|
||||
ns->spawn.show_name = true;
|
||||
@@ -9043,11 +9052,11 @@ void Client::QuestReward(Mob* target, const QuestReward_Struct &reward, bool fac
|
||||
|
||||
void Client::CashReward(uint32 copper, uint32 silver, uint32 gold, uint32 platinum)
|
||||
{
|
||||
auto outapp = std::make_unique<EQApplicationPacket>(OP_CashReward, sizeof(CashReward_Struct));
|
||||
auto outapp = std::make_unique<EQApplicationPacket>(OP_CashReward, static_cast<uint32>(sizeof(CashReward_Struct)));
|
||||
auto outbuf = reinterpret_cast<CashReward_Struct *>(outapp->pBuffer);
|
||||
outbuf->copper = copper;
|
||||
outbuf->silver = silver;
|
||||
outbuf->gold = gold;
|
||||
outbuf->copper = copper;
|
||||
outbuf->silver = silver;
|
||||
outbuf->gold = gold;
|
||||
outbuf->platinum = platinum;
|
||||
|
||||
AddMoneyToPP(copper, silver, gold, platinum);
|
||||
@@ -12689,13 +12698,11 @@ uint16 Client::GetSkill(EQ::skills::SkillType skill_id) const
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Client::RemoveItemBySerialNumber(uint32 serial_number, uint32 quantity)
|
||||
bool Client::RemoveItemByItemUniqueId(const std::string &item_unique_id, uint32 quantity)
|
||||
{
|
||||
EQ::ItemInstance *item = nullptr;
|
||||
|
||||
uint32 removed_count = 0;
|
||||
|
||||
const auto& slot_ids = GetInventorySlots();
|
||||
EQ::ItemInstance *item = nullptr;
|
||||
uint32 removed_count = 0;
|
||||
const auto &slot_ids = GetInventorySlots();
|
||||
|
||||
for (const int16& slot_id : slot_ids) {
|
||||
if (removed_count == quantity) {
|
||||
@@ -12703,21 +12710,27 @@ void Client::RemoveItemBySerialNumber(uint32 serial_number, uint32 quantity)
|
||||
}
|
||||
|
||||
item = GetInv().GetItem(slot_id);
|
||||
if (item && item->GetSerialNumber() == serial_number) {
|
||||
if (item && item->GetUniqueID().compare(item_unique_id) == 0) {
|
||||
uint32 charges = item->IsStackable() ? item->GetCharges() : 0;
|
||||
uint32 stack_size = std::max(charges, static_cast<uint32>(1));
|
||||
if ((removed_count + stack_size) <= quantity) {
|
||||
if (removed_count + stack_size <= quantity) {
|
||||
removed_count += stack_size;
|
||||
DeleteItemInInventory(slot_id, charges, true);
|
||||
if (DeleteItemInInventory(slot_id, charges, true)) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
uint32 amount_left = (quantity - removed_count);
|
||||
uint32 amount_left = quantity - removed_count;
|
||||
if (amount_left > 0 && stack_size >= amount_left) {
|
||||
removed_count += amount_left;
|
||||
DeleteItemInInventory(slot_id, amount_left, true);
|
||||
if (DeleteItemInInventory(slot_id, amount_left, true)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Client::SendTopLevelInventory()
|
||||
|
||||
+98
-16
@@ -303,7 +303,7 @@ public:
|
||||
void Trader_CustomerBrowsing(Client *Customer);
|
||||
|
||||
void TraderEndTrader();
|
||||
void TraderPriceUpdate(const EQApplicationPacket *app);
|
||||
void TraderUpdateItem(const EQApplicationPacket *app);
|
||||
void SendBazaarDone(uint32 trader_id);
|
||||
void SendBulkBazaarTraders();
|
||||
void SendBulkBazaarBuyers();
|
||||
@@ -340,7 +340,7 @@ public:
|
||||
void SendTraderPacket(Client* trader, uint32 Unknown72 = 51);
|
||||
void SendBuyerPacket(Client* Buyer);
|
||||
void SendBuyerToBarterWindow(Client* buyer, uint32 action);
|
||||
GetItems_Struct* GetTraderItems();
|
||||
GetBazaarItems_Struct* GetTraderItems();
|
||||
void SendBazaarWelcome();
|
||||
void SendBarterWelcome();
|
||||
void DyeArmor(EQ::TintProfile* dye);
|
||||
@@ -361,15 +361,17 @@ public:
|
||||
void SendColoredText(uint32 color, std::string message);
|
||||
void SendTraderItem(uint32 item_id,uint16 quantity, TraderRepository::Trader &trader);
|
||||
void DoBazaarSearch(BazaarSearchCriteria_Struct search_criteria);
|
||||
uint16 FindTraderItem(int32 SerialNumber,uint16 Quantity);
|
||||
uint32 FindTraderItemSerialNumber(int32 ItemID);
|
||||
EQ::ItemInstance* FindTraderItemBySerialNumber(int32 SerialNumber);
|
||||
void FindAndNukeTraderItem(int32 serial_number, int16 quantity, Client* customer, uint16 trader_slot);
|
||||
void NukeTraderItem(uint16 slot, int16 charges, int16 quantity, Client* customer, uint16 trader_slot, int32 serial_number, int32 item_id = 0);
|
||||
uint16 FindTraderItem(std::string &SerialNumber,uint16 Quantity);
|
||||
EQ::ItemInstance* FindTraderItemByUniqueID(std::string &unique_id);
|
||||
EQ::ItemInstance* FindTraderItemByUniqueID(const char* unique_id);
|
||||
std::vector<EQ::ItemInstance *> FindTraderItemsByUniqueID(const char* unique_id);
|
||||
void FindAndNukeTraderItem(std::string &item_unique_id, int16 quantity, Client* customer, uint16 trader_slot);
|
||||
void NukeTraderItem(uint16 slot, int16 charges, int16 quantity, Client* customer, uint16 trader_slot, const std::string &serial_number, int32 item_id = 0);
|
||||
void ReturnTraderReq(const EQApplicationPacket* app,int16 traderitemcharges, uint32 itemid = 0);
|
||||
void TradeRequestFailed(const EQApplicationPacket* app);
|
||||
void BuyTraderItem(TraderBuy_Struct* tbs, Client* trader, const EQApplicationPacket* app);
|
||||
void BuyTraderItemOutsideBazaar(TraderBuy_Struct* tbs, const EQApplicationPacket* app);
|
||||
void TradeRequestFailed(TraderBuy_Struct &in);
|
||||
void BuyTraderItem(const EQApplicationPacket* app);
|
||||
void BuyTraderItemFromBazaarWindow(const EQApplicationPacket* app);
|
||||
void FinishTrade(
|
||||
Mob *with,
|
||||
bool finalizer = false,
|
||||
@@ -401,13 +403,22 @@ public:
|
||||
int32 FindNextFreeParcelSlot(uint32 char_id);
|
||||
int32 FindNextFreeParcelSlotUsingMemory();
|
||||
void SendParcelIconStatus();
|
||||
bool IsOffline() { return m_offline; }
|
||||
void SetOffline(bool status) { m_offline = status; }
|
||||
|
||||
void SendBecomeTraderToWorld(Client *trader, BazaarTraderBarterActions action);
|
||||
void SendBecomeTrader(BazaarTraderBarterActions action, uint32 trader_id);
|
||||
|
||||
bool IsThereACustomer() const { return customer_id ? true : false; }
|
||||
bool IsThereACustomer() const { return customer_id ? true : false; }
|
||||
uint32 GetCustomerID() { return customer_id; }
|
||||
void SetCustomerID(uint32 id) { customer_id = id; }
|
||||
void SetCustomerID(uint32 id) { customer_id = id; }
|
||||
void ClearTraderMerchantList() { m_trader_merchant_list.clear(); }
|
||||
void AddDataToMerchantList(int16 slot_id, uint32 item_id, int32 quantity, const std::string &item_unique_id);
|
||||
int16 GetNextFreeSlotFromMerchantList();
|
||||
std::tuple<uint32, int32, std::string> GetDataFromMerchantListByMerchantSlotId(int16 slot_id);
|
||||
int16 GetSlotFromMerchantListByItemUniqueId(const std::string &unique_id);
|
||||
std::pair<int16, std::tuple<uint32, int32, std::string>> GetDataFromMerchantListByItemUniqueId(const std::string &unique_id);
|
||||
std::map<int16, std::tuple<uint32, int32, std::string>>* GetTraderMerchantList() { return &m_trader_merchant_list; }
|
||||
|
||||
void SetBuyerID(uint32 id) { m_buyer_id = id; }
|
||||
uint32 GetBuyerID() { return m_buyer_id; }
|
||||
@@ -486,7 +497,12 @@ public:
|
||||
inline bool ClientDataLoaded() const { return client_data_loaded; }
|
||||
inline bool Connected() const { return (client_state == CLIENT_CONNECTED); }
|
||||
inline bool InZone() const { return (client_state == CLIENT_CONNECTED || client_state == CLIENT_LINKDEAD); }
|
||||
inline void Disconnect() { eqs->Close(); client_state = DISCONNECTED; }
|
||||
inline void Disconnect() {
|
||||
if (eqs) {
|
||||
eqs->Close();
|
||||
client_state = DISCONNECTED;
|
||||
}
|
||||
}
|
||||
inline bool IsLD() const { return (bool) (client_state == CLIENT_LINKDEAD); }
|
||||
void Kick(const std::string &reason);
|
||||
void WorldKick();
|
||||
@@ -576,8 +592,8 @@ public:
|
||||
void DisableAreaRegens();
|
||||
|
||||
void ServerFilter(SetServerFilter_Struct* filter);
|
||||
void BulkSendTraderInventory(uint32 char_id);
|
||||
void SendSingleTraderItem(uint32 char_id, int serial_number);
|
||||
void BulkSendTraderInventory(uint32 character_id);
|
||||
void SendSingleTraderItem(uint32 char_id, const std::string &serial_number);
|
||||
void BulkSendMerchantInventory(int merchant_id, int npcid);
|
||||
|
||||
inline uint8 GetLanguageSkill(uint8 language_id) const { return m_pp.languages[language_id]; }
|
||||
@@ -1134,13 +1150,13 @@ public:
|
||||
bool FindNumberOfFreeInventorySlotsWithSizeCheck(std::vector<BuyerLineTradeItems_Struct> items);
|
||||
bool PushItemOnCursor(const EQ::ItemInstance& inst, bool client_update = false);
|
||||
void SendCursorBuffer();
|
||||
void DeleteItemInInventory(int16 slot_id, int16 quantity = 0, bool client_update = false, bool update_db = true);
|
||||
bool DeleteItemInInventory(int16 slot_id, int16 quantity = 0, bool client_update = false, bool update_db = true);
|
||||
uint32 CountItem(uint32 item_id);
|
||||
void ResetItemCooldown(uint32 item_id);
|
||||
void SetItemCooldown(uint32 item_id, bool use_saved_timer = false, uint32 in_seconds = 1);
|
||||
uint32 GetItemCooldown(uint32 item_id);
|
||||
void RemoveItem(uint32 item_id, uint32 quantity = 1);
|
||||
void RemoveItemBySerialNumber(uint32 serial_number, uint32 quantity = 1);
|
||||
bool RemoveItemByItemUniqueId(const std::string &item_unique_id, uint32 quantity = 1);
|
||||
bool SwapItem(MoveItem_Struct* move_in);
|
||||
void SwapItemResync(MoveItem_Struct* move_slots);
|
||||
void PutLootInInventory(int16 slot_id, const EQ::ItemInstance &inst, LootItem** bag_item_data = 0);
|
||||
@@ -2093,7 +2109,9 @@ private:
|
||||
uint8 mercSlot; // selected merc slot
|
||||
time_t m_trader_transaction_date;
|
||||
uint32 m_trader_count{};
|
||||
std::map<int16, std::tuple<uint32, int32, std::string>> m_trader_merchant_list{}; // itemid, qty, item_unique_id
|
||||
uint32 m_buyer_id;
|
||||
bool m_offline;
|
||||
uint32 m_barter_time;
|
||||
int32 m_parcel_platinum;
|
||||
int32 m_parcel_gold;
|
||||
@@ -2423,4 +2441,68 @@ public:
|
||||
bool IsFilteredAFKPacket(const EQApplicationPacket *p);
|
||||
void CheckAutoIdleAFK(PlayerPositionUpdateClient_Struct *p);
|
||||
void SyncWorldPositionsToClient(bool ignore_idle = false);
|
||||
|
||||
|
||||
Mob* GetMob() {
|
||||
return Mob::GetMob();
|
||||
}
|
||||
|
||||
void Clone(Client& in)
|
||||
{
|
||||
WID = in.WID;
|
||||
admin = in.admin;
|
||||
guild_id = in.guild_id;
|
||||
guildrank = in.guildrank;
|
||||
LFG = in.LFG;
|
||||
m_is_afk = in.m_is_afk;
|
||||
m_is_idle = in.m_is_idle;
|
||||
m_is_manual_afk = in.m_is_manual_afk;
|
||||
trader_id = in.trader_id;
|
||||
m_buyer_id = in.m_buyer_id;
|
||||
race = in.race;
|
||||
class_ = in.class_;
|
||||
size = in.size;
|
||||
deity = in.deity;
|
||||
texture = in.texture;
|
||||
m_ClientVersion = in.m_ClientVersion;
|
||||
m_ClientVersionBit = in.m_ClientVersionBit;
|
||||
character_id = in.character_id;
|
||||
account_id = in.account_id;
|
||||
lsaccountid = in.lsaccountid;
|
||||
|
||||
m_pp.platinum = in.m_pp.platinum;
|
||||
m_pp.gold = in.m_pp.gold;
|
||||
m_pp.silver = in.m_pp.silver;
|
||||
m_pp.copper = in.m_pp.copper;
|
||||
m_pp.platinum_bank = in.m_pp.platinum_bank;
|
||||
m_pp.gold_bank = in.m_pp.gold_bank;
|
||||
m_pp.silver_bank = in.m_pp.silver_bank;
|
||||
m_pp.copper_bank = in.m_pp.copper_bank;
|
||||
m_pp.platinum_cursor = in.m_pp.platinum_cursor;
|
||||
m_pp.gold_cursor = in.m_pp.gold_cursor;
|
||||
m_pp.silver_cursor = in.m_pp.silver_cursor;
|
||||
m_pp.copper_cursor = in.m_pp.copper_cursor;
|
||||
m_pp.currentRadCrystals = in.m_pp.currentRadCrystals;
|
||||
m_pp.careerRadCrystals = in.m_pp.careerRadCrystals;
|
||||
m_pp.currentEbonCrystals = in.m_pp.currentEbonCrystals;
|
||||
m_pp.careerEbonCrystals = in.m_pp.careerEbonCrystals;
|
||||
m_pp.gm = in.m_pp.gm;
|
||||
|
||||
m_inv.SetInventoryVersion(in.m_ClientVersion);
|
||||
SetBodyType(in.GetBodyType(), false);
|
||||
|
||||
for (auto [slot, item] : in.m_inv.GetPersonal()) {
|
||||
if (item) {
|
||||
m_inv.GetPersonal()[slot] = item->Clone();
|
||||
}
|
||||
}
|
||||
|
||||
for (auto [slot, item] : in.m_inv.GetWorn()) {
|
||||
if (item) {
|
||||
m_inv.GetWorn()[slot] = item->Clone();
|
||||
}
|
||||
}
|
||||
|
||||
CloneMob(*in.GetMob());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -388,7 +388,7 @@ bool Client::DoEvolveCheckProgression(EQ::ItemInstance &inst)
|
||||
|
||||
PlayerEvent::EvolveItem e{};
|
||||
|
||||
RemoveItemBySerialNumber(inst.GetSerialNumber());
|
||||
RemoveItemByItemUniqueId(inst.GetUniqueID());
|
||||
EvolvingItemsManager::Instance()->LoadPlayerEvent(inst, e);
|
||||
e.status = "Evolved Item due to obtaining progression - Old Evolve Item removed from inventory.";
|
||||
RecordPlayerEventLog(PlayerEvent::EVOLVE_ITEM, e);
|
||||
@@ -508,7 +508,7 @@ void Client::DoEvolveTransferXP(const EQApplicationPacket *app)
|
||||
|
||||
PlayerEvent::EvolveItem e{};
|
||||
|
||||
RemoveItemBySerialNumber(inst_from->GetSerialNumber());
|
||||
RemoveItemByItemUniqueId(inst_from->GetUniqueID());
|
||||
EvolvingItemsManager::Instance()->LoadPlayerEvent(*inst_from, e);
|
||||
e.status = "Transfer XP - Original FROM Evolve Item removed from inventory.";
|
||||
RecordPlayerEventLog(PlayerEvent::EVOLVE_ITEM, e);
|
||||
@@ -518,7 +518,7 @@ void Client::DoEvolveTransferXP(const EQApplicationPacket *app)
|
||||
e.status = "Transfer XP - Updated FROM item placed in inventory.";
|
||||
RecordPlayerEventLog(PlayerEvent::EVOLVE_ITEM, e);
|
||||
|
||||
RemoveItemBySerialNumber(inst_to->GetSerialNumber());
|
||||
RemoveItemByItemUniqueId(inst_to->GetUniqueID());
|
||||
EvolvingItemsManager::Instance()->LoadPlayerEvent(*inst_to, e);
|
||||
e.status = "Transfer XP - Original TO Evolve Item removed from inventory.";
|
||||
RecordPlayerEventLog(PlayerEvent::EVOLVE_ITEM, e);
|
||||
|
||||
+168
-41
@@ -25,6 +25,8 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#include "common/raid.h"
|
||||
#include "common/rdtsc.h"
|
||||
#include "common/repositories/account_repository.h"
|
||||
#include "common/repositories/character_offline_transactions_repository.h"
|
||||
#include "common/repositories/offline_character_sessions_repository.h"
|
||||
#include "common/repositories/adventure_members_repository.h"
|
||||
#include "common/repositories/buyer_buy_lines_repository.h"
|
||||
#include "common/repositories/character_corpses_repository.h"
|
||||
@@ -309,6 +311,7 @@ void MapOpcodes()
|
||||
ConnectedOpcodes[OP_MoveCoin] = &Client::Handle_OP_MoveCoin;
|
||||
ConnectedOpcodes[OP_MoveItem] = &Client::Handle_OP_MoveItem;
|
||||
ConnectedOpcodes[OP_MoveMultipleItems] = &Client::Handle_OP_MoveMultipleItems;
|
||||
ConnectedOpcodes[OP_Offline] = &Client::Handle_OP_Offline;
|
||||
ConnectedOpcodes[OP_OpenContainer] = &Client::Handle_OP_OpenContainer;
|
||||
ConnectedOpcodes[OP_OpenGuildTributeMaster] = &Client::Handle_OP_OpenGuildTributeMaster;
|
||||
ConnectedOpcodes[OP_OpenInventory] = &Client::Handle_OP_OpenInventory;
|
||||
@@ -738,7 +741,7 @@ void Client::CompleteConnect()
|
||||
|
||||
if (is_first_login) {
|
||||
e.first_login = time(nullptr);
|
||||
TraderRepository::DeleteWhere(database, fmt::format("`char_id` = '{}'", CharacterID()));
|
||||
TraderRepository::DeleteWhere(database, fmt::format("`character_id` = '{}'", CharacterID()));
|
||||
BuyerRepository::DeleteBuyer(database, CharacterID());
|
||||
LogTradingDetail(
|
||||
"Removed trader abd buyer entries for Character ID {} on first logon to ensure table consistency.",
|
||||
@@ -762,6 +765,54 @@ void Client::CompleteConnect()
|
||||
}
|
||||
}
|
||||
|
||||
auto offline_transactions_trader = CharacterOfflineTransactionsRepository::GetWhere(
|
||||
database, fmt::format("`character_id` = {} AND `type` = {}", CharacterID(), TRADER_TRANSACTION)
|
||||
);
|
||||
if (offline_transactions_trader.size() > 0) {
|
||||
Message(Chat::Yellow, "You sold the following items while in offline trader mode:");
|
||||
|
||||
for (auto const &t: offline_transactions_trader) {
|
||||
Message(
|
||||
Chat::Yellow,
|
||||
fmt::format(
|
||||
"You sold {} {}{} to {} for {}.",
|
||||
t.quantity,
|
||||
t.item_name,
|
||||
t.quantity > 1 ? "s" : "",
|
||||
t.buyer_name,
|
||||
DetermineMoneyString(t.price))
|
||||
.c_str());
|
||||
}
|
||||
|
||||
CharacterOfflineTransactionsRepository::DeleteWhere(
|
||||
database, fmt::format("`character_id` = '{}' AND `type` = '{}'", CharacterID(), TRADER_TRANSACTION)
|
||||
);
|
||||
}
|
||||
|
||||
auto offline_transactions_buyer = CharacterOfflineTransactionsRepository::GetWhere(
|
||||
database, fmt::format("`character_id` = {} AND `type` = {}", CharacterID(), BUYER_TRANSACTION)
|
||||
);
|
||||
if (offline_transactions_buyer.size() > 0) {
|
||||
Message(Chat::Yellow, "You bought the following items while in offline buyer mode:");
|
||||
|
||||
for (auto const &t: offline_transactions_buyer) {
|
||||
Message(
|
||||
Chat::Yellow,
|
||||
fmt::format(
|
||||
"You bought {} {}{} from {} for {}.",
|
||||
t.quantity,
|
||||
t.item_name,
|
||||
t.quantity > 1 ? "s" : "",
|
||||
t.buyer_name,
|
||||
DetermineMoneyString(t.price))
|
||||
.c_str());
|
||||
}
|
||||
|
||||
CharacterOfflineTransactionsRepository::DeleteWhere(
|
||||
database, fmt::format("`character_id` = {} AND `type` = {}", CharacterID(), BUYER_TRANSACTION)
|
||||
);
|
||||
}
|
||||
|
||||
if(ClientVersion() == EQ::versions::ClientVersion::RoF2 && RuleB(Parcel, EnableParcelMerchants)) {
|
||||
SendParcelStatus();
|
||||
}
|
||||
@@ -805,7 +856,7 @@ void Client::CompleteConnect()
|
||||
SendGuildMembersList();
|
||||
}
|
||||
|
||||
guild_mgr.SendGuildMemberUpdateToWorld(GetName(), GuildID(), zone->GetZoneID(), time(nullptr));
|
||||
guild_mgr.SendGuildMemberUpdateToWorld(GetName(), GuildID(), zone->GetZoneID(), time(nullptr), 0);
|
||||
|
||||
SendGuildList();
|
||||
if (GetGuildListDirty()) {
|
||||
@@ -15341,10 +15392,10 @@ void Client::Handle_OP_Trader(const EQApplicationPacket *app)
|
||||
TraderStartTrader(app);
|
||||
break;
|
||||
}
|
||||
case PriceUpdate:
|
||||
case ItemMove: {
|
||||
LogTrading("Trader Price Update");
|
||||
TraderPriceUpdate(app);
|
||||
case ItemMove:
|
||||
case PriceUpdate:{
|
||||
LogTrading("Trader item updated - removed, added or price change");
|
||||
TraderUpdateItem(app);
|
||||
break;
|
||||
}
|
||||
case EndTransaction: {
|
||||
@@ -15363,7 +15414,6 @@ void Client::Handle_OP_Trader(const EQApplicationPacket *app)
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
LogError("Unknown size for OP_Trader: [{}]\n", app->size);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15374,28 +15424,12 @@ void Client::Handle_OP_TraderBuy(const EQApplicationPacket *app)
|
||||
//
|
||||
// Client has elected to buy an item from a Trader
|
||||
//
|
||||
auto in = (TraderBuy_Struct *) app->pBuffer;
|
||||
|
||||
if (RuleB(Bazaar, UseAlternateBazaarSearch) && in->trader_id >= TraderRepository::TRADER_CONVERT_ID) {
|
||||
auto trader = TraderRepository::GetTraderByInstanceAndSerialnumber(
|
||||
database,
|
||||
in->trader_id - TraderRepository::TRADER_CONVERT_ID,
|
||||
in->serial_number
|
||||
);
|
||||
|
||||
if (!trader.trader_id) {
|
||||
LogTrading("Unable to convert trader id for {} and serial number {}. Trader Buy aborted.",
|
||||
in->trader_id - TraderRepository::TRADER_CONVERT_ID,
|
||||
in->serial_number
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
in->trader_id = trader.trader_id;
|
||||
strn0cpy(in->seller_name, trader.trader_name.c_str(), sizeof(in->seller_name));
|
||||
}
|
||||
|
||||
auto trader = entity_list.GetClientByID(in->trader_id);
|
||||
auto in = (TraderBuy_Struct *) app->pBuffer;
|
||||
auto item_unique_id = std::string(in->item_unique_id);
|
||||
auto trader_details = TraderRepository::GetTraderByItemUniqueNumber(database, item_unique_id);
|
||||
auto trader = entity_list.GetClientByID(in->trader_id);
|
||||
strn0cpy(in->seller_name, trader_details.trader_name.c_str(), sizeof(in->seller_name));
|
||||
|
||||
switch (in->method) {
|
||||
case BazaarByVendor: {
|
||||
@@ -15405,9 +15439,9 @@ void Client::Handle_OP_TraderBuy(const EQApplicationPacket *app)
|
||||
in->trader_id,
|
||||
in->item_id,
|
||||
in->quantity,
|
||||
in->serial_number
|
||||
in->item_unique_id
|
||||
);
|
||||
BuyTraderItem(in, trader, app);
|
||||
BuyTraderItem(app);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -15431,9 +15465,9 @@ void Client::Handle_OP_TraderBuy(const EQApplicationPacket *app)
|
||||
in->trader_id,
|
||||
in->item_id,
|
||||
in->quantity,
|
||||
in->serial_number
|
||||
in->item_unique_id
|
||||
);
|
||||
BuyTraderItemOutsideBazaar(in, app);
|
||||
BuyTraderItemFromBazaarWindow(app);
|
||||
break;
|
||||
}
|
||||
case BazaarByDirectToInventory: {
|
||||
@@ -15456,7 +15490,7 @@ void Client::Handle_OP_TraderBuy(const EQApplicationPacket *app)
|
||||
in->trader_id,
|
||||
in->item_id,
|
||||
in->quantity,
|
||||
in->serial_number
|
||||
in->item_unique_id
|
||||
);
|
||||
Message(
|
||||
Chat::Yellow,
|
||||
@@ -15467,6 +15501,9 @@ void Client::Handle_OP_TraderBuy(const EQApplicationPacket *app)
|
||||
TradeRequestFailed(app);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15557,17 +15594,18 @@ void Client::Handle_OP_TraderShop(const EQApplicationPacket *app)
|
||||
switch (in->Code) {
|
||||
case ClickTrader: {
|
||||
LogTrading("Handle_OP_TraderShop case ClickTrader [{}]", in->Code);
|
||||
auto outapp =
|
||||
std::make_unique<EQApplicationPacket>(OP_TraderShop, static_cast<uint32>(sizeof(TraderClick_Struct))
|
||||
auto outapp = std::make_unique<EQApplicationPacket>(
|
||||
OP_TraderShop,
|
||||
static_cast<uint32>(sizeof(TraderClick_Struct))
|
||||
);
|
||||
auto data = (TraderClick_Struct *) outapp->pBuffer;
|
||||
auto trader_client = entity_list.GetClientByID(in->TraderID);
|
||||
auto trader = entity_list.GetClientByID(in->TraderID);
|
||||
|
||||
if (trader_client) {
|
||||
data->Approval = trader_client->WithCustomer(GetID());
|
||||
if (trader) {
|
||||
data->Approval = trader->WithCustomer(GetID());
|
||||
LogTrading("Client::Handle_OP_TraderShop: Shop Request ([{}]) to ([{}]) with Approval: [{}]",
|
||||
GetCleanName(),
|
||||
trader_client->GetCleanName(),
|
||||
trader->GetCleanName(),
|
||||
data->Approval
|
||||
);
|
||||
}
|
||||
@@ -15575,6 +15613,9 @@ void Client::Handle_OP_TraderShop(const EQApplicationPacket *app)
|
||||
LogTrading("Client::Handle_OP_TraderShop: entity_list.GetClientByID(tcs->traderid)"
|
||||
" returned a nullptr pointer"
|
||||
);
|
||||
auto outapp = new EQApplicationPacket(OP_ShopEndConfirm);
|
||||
QueuePacket(outapp);
|
||||
safe_delete(outapp);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -15584,8 +15625,9 @@ void Client::Handle_OP_TraderShop(const EQApplicationPacket *app)
|
||||
QueuePacket(outapp.get());
|
||||
|
||||
if (data->Approval) {
|
||||
BulkSendTraderInventory(trader_client->CharacterID());
|
||||
trader_client->Trader_CustomerBrowsing(this);
|
||||
ClearTraderMerchantList();
|
||||
BulkSendTraderInventory(trader->CharacterID());
|
||||
trader->Trader_CustomerBrowsing(this);
|
||||
SetTraderID(in->TraderID);
|
||||
LogTrading("Client::Handle_OP_TraderShop: Trader Inventory Sent to [{}] from [{}]",
|
||||
GetID(),
|
||||
@@ -17275,3 +17317,88 @@ void Client::SyncWorldPositionsToClient(bool ignore_idle)
|
||||
m_is_idle = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Client::Handle_OP_Offline(const EQApplicationPacket *app)
|
||||
{
|
||||
if (IsThereACustomer()) {
|
||||
auto customer = entity_list.GetClientByID(GetCustomerID());
|
||||
if (customer) {
|
||||
auto end_session = new EQApplicationPacket(OP_ShopEnd);
|
||||
customer->FastQueuePacket(&end_session);
|
||||
}
|
||||
}
|
||||
|
||||
EQStreamInterface *eqsi = nullptr;
|
||||
auto offline_client = new Client(eqsi);
|
||||
|
||||
database.LoadCharacterData(CharacterID(), &offline_client->GetPP(), &offline_client->GetEPP());
|
||||
offline_client->Clone(*this);
|
||||
offline_client->GetInv().SetGMInventory(true);
|
||||
offline_client->SetPosition(GetX(), GetY(), GetZ());
|
||||
offline_client->SetHeading(GetHeading());
|
||||
offline_client->SetSpawned();
|
||||
offline_client->SetBecomeNPC(false);
|
||||
offline_client->SetOffline(true);
|
||||
entity_list.AddClient(offline_client);
|
||||
|
||||
bool session_ready = true;
|
||||
const auto previous_entity_id = GetID();
|
||||
const auto next_entity_id = offline_client->GetID();
|
||||
const auto mode = IsBuyer() ? std::string("buyer") : std::string("trader");
|
||||
|
||||
database.TransactionBegin();
|
||||
|
||||
if (IsBuyer()) {
|
||||
offline_client->SetBuyerID(offline_client->CharacterID());
|
||||
session_ready = BuyerRepository::UpdateBuyerEntityID(database, CharacterID(), previous_entity_id, next_entity_id);
|
||||
}
|
||||
else {
|
||||
offline_client->SetTrader(true);
|
||||
session_ready = TraderRepository::UpdateEntityId(database, CharacterID(), previous_entity_id, next_entity_id);
|
||||
}
|
||||
|
||||
if (session_ready) {
|
||||
session_ready = OfflineCharacterSessionsRepository::Upsert(
|
||||
database,
|
||||
AccountID(),
|
||||
CharacterID(),
|
||||
mode,
|
||||
GetZoneID(),
|
||||
GetInstanceID(),
|
||||
next_entity_id
|
||||
);
|
||||
}
|
||||
|
||||
if (session_ready) {
|
||||
AccountRepository::SetOfflineStatus(database, AccountID(), true);
|
||||
auto commit_result = database.TransactionCommit();
|
||||
session_ready = commit_result.Success();
|
||||
if (!session_ready) {
|
||||
LogError(
|
||||
"Failed committing offline {} activation for character [{}] account [{}]: ({}) {}",
|
||||
mode,
|
||||
CharacterID(),
|
||||
AccountID(),
|
||||
commit_result.ErrorNumber(),
|
||||
commit_result.ErrorMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!session_ready) {
|
||||
database.TransactionRollback();
|
||||
entity_list.RemoveMob(offline_client->CastToMob()->GetID());
|
||||
return;
|
||||
}
|
||||
|
||||
SetOffline(true);
|
||||
OnDisconnect(true);
|
||||
|
||||
auto outapp = new EQApplicationPacket();
|
||||
offline_client->CreateSpawnPacket(outapp);
|
||||
entity_list.QueueClients(nullptr, outapp, false);
|
||||
safe_delete(outapp);
|
||||
|
||||
offline_client->UpdateWho(3);
|
||||
}
|
||||
|
||||
@@ -226,6 +226,7 @@
|
||||
void Handle_OP_MoveCoin(const EQApplicationPacket *app);
|
||||
void Handle_OP_MoveItem(const EQApplicationPacket *app);
|
||||
void Handle_OP_MoveMultipleItems(const EQApplicationPacket *app);
|
||||
void Handle_OP_Offline(const EQApplicationPacket *app);
|
||||
void Handle_OP_OpenContainer(const EQApplicationPacket *app);
|
||||
void Handle_OP_OpenGuildTributeMaster(const EQApplicationPacket *app);
|
||||
void Handle_OP_OpenInventory(const EQApplicationPacket *app);
|
||||
|
||||
@@ -168,7 +168,7 @@ bool Client::Process() {
|
||||
}
|
||||
if (IsInAGuild()) {
|
||||
guild_mgr.UpdateDbMemberOnline(CharacterID(), false);
|
||||
guild_mgr.SendGuildMemberUpdateToWorld(GetName(), GuildID(), 0, time(nullptr));
|
||||
guild_mgr.SendGuildMemberUpdateToWorld(GetName(), GuildID(), 0, time(nullptr), 0);
|
||||
}
|
||||
|
||||
SetDynamicZoneMemberStatus(DynamicZoneMemberStatus::Offline);
|
||||
@@ -197,7 +197,7 @@ bool Client::Process() {
|
||||
Save();
|
||||
if (IsInAGuild()) {
|
||||
guild_mgr.UpdateDbMemberOnline(CharacterID(), false);
|
||||
guild_mgr.SendGuildMemberUpdateToWorld(GetName(), GuildID(), 0, time(nullptr));
|
||||
guild_mgr.SendGuildMemberUpdateToWorld(GetName(), GuildID(), 0, time(nullptr), 0);
|
||||
}
|
||||
|
||||
if (GetMerc())
|
||||
@@ -578,7 +578,7 @@ bool Client::Process() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (client_state != CLIENT_LINKDEAD && !eqs->CheckState(ESTABLISHED)) {
|
||||
if (eqs && client_state != CLIENT_LINKDEAD && !eqs->CheckState(ESTABLISHED)) {
|
||||
OnDisconnect(true);
|
||||
LogInfo("Client linkdead: {}", name);
|
||||
|
||||
@@ -589,7 +589,7 @@ bool Client::Process() {
|
||||
}
|
||||
if (IsInAGuild()) {
|
||||
guild_mgr.UpdateDbMemberOnline(CharacterID(), false);
|
||||
guild_mgr.SendGuildMemberUpdateToWorld(GetName(), GuildID(), 0, time(nullptr));
|
||||
guild_mgr.SendGuildMemberUpdateToWorld(GetName(), GuildID(), 0, time(nullptr), 0);
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -607,7 +607,7 @@ bool Client::Process() {
|
||||
|
||||
/************ Get all packets from packet manager out queue and process them ************/
|
||||
EQApplicationPacket *app = nullptr;
|
||||
if (!eqs->CheckState(CLOSING))
|
||||
if (eqs && !eqs->CheckState(CLOSING))
|
||||
{
|
||||
while (app = eqs->PopPacket()) {
|
||||
HandlePacket(app);
|
||||
@@ -617,7 +617,7 @@ bool Client::Process() {
|
||||
|
||||
ClientToNpcAggroProcess();
|
||||
|
||||
if (client_state != CLIENT_LINKDEAD && (client_state == CLIENT_ERROR || client_state == DISCONNECTED || client_state == CLIENT_KICKED || !eqs->CheckState(ESTABLISHED)))
|
||||
if (eqs && client_state != CLIENT_LINKDEAD && (client_state == CLIENT_ERROR || client_state == DISCONNECTED || client_state == CLIENT_KICKED || !eqs->CheckState(ESTABLISHED)))
|
||||
{
|
||||
//client logged out or errored out
|
||||
//ResetTrade();
|
||||
|
||||
+22
-6
@@ -4995,15 +4995,31 @@ void EntityList::ZoneWho(Client *c, Who_All_Struct *Who)
|
||||
strcpy(WAPP1->Name, ClientEntry->GetName());
|
||||
Buffer += sizeof(WhoAllPlayerPart1) + strlen(WAPP1->Name);
|
||||
WhoAllPlayerPart2* WAPP2 = (WhoAllPlayerPart2*)Buffer;
|
||||
WAPP2->RankMSGID = 0xFFFFFFFF;
|
||||
|
||||
if (ClientEntry->IsTrader())
|
||||
WAPP2->RankMSGID = 12315;
|
||||
else if (ClientEntry->IsBuyer())
|
||||
WAPP2->RankMSGID = 6056;
|
||||
else if (ClientEntry->Admin() >= AccountStatus::Steward && ClientEntry->GetGM())
|
||||
if (ClientEntry->IsOffline()) {
|
||||
if (ClientEntry->IsTrader()) {
|
||||
WAPP1->PIDMSGID = 0x0430;
|
||||
}
|
||||
if (ClientEntry->IsBuyer()) {
|
||||
WAPP1->PIDMSGID = 0x0420;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (ClientEntry->IsTrader()) {
|
||||
WAPP2->RankMSGID = 12315;
|
||||
}
|
||||
else if (ClientEntry->IsBuyer()) {
|
||||
WAPP2->RankMSGID = 6056;
|
||||
}
|
||||
}
|
||||
|
||||
if (ClientEntry->Admin() >= AccountStatus::Steward && ClientEntry->GetGM()) {
|
||||
WAPP2->RankMSGID = 12312;
|
||||
else
|
||||
}
|
||||
else {
|
||||
WAPP2->RankMSGID = 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
strcpy(WAPP2->Guild, GuildName.c_str());
|
||||
Buffer += sizeof(WhoAllPlayerPart2) + strlen(WAPP2->Guild);
|
||||
|
||||
@@ -185,7 +185,7 @@ void ShowInventory(Client *c, const Seperator *sep)
|
||||
scope_bit & peekWorld ? EQ::invslot::WORLD_BEGIN + index_main : index_main,
|
||||
linker.GenerateLink(),
|
||||
item_data->ID,
|
||||
inst_main->GetSerialNumber(),
|
||||
inst_main->GetUniqueID().c_str(),
|
||||
inst_main->IsStackable() && inst_main->GetCharges() > 0 ?
|
||||
fmt::format(
|
||||
" (Stack of {})",
|
||||
@@ -254,7 +254,7 @@ void ShowInventory(Client *c, const Seperator *sep)
|
||||
sub_index,
|
||||
linker.GenerateLink(),
|
||||
item_data->ID,
|
||||
inst_sub->GetSerialNumber(),
|
||||
inst_sub->GetUniqueID().c_str(),
|
||||
(
|
||||
inst_sub->IsStackable() && inst_sub->GetCharges() > 0 ?
|
||||
fmt::format(
|
||||
|
||||
+23
-17
@@ -427,10 +427,11 @@ void ZoneGuildManager::ProcessWorldPacket(ServerPacket *pack)
|
||||
auto outapp = new EQApplicationPacket(OP_GuildMemberUpdate, sizeof(GuildMemberUpdate_Struct));
|
||||
auto gmus = (GuildMemberUpdate_Struct *) outapp->pBuffer;
|
||||
|
||||
gmus->GuildID = sgmus->guild_id;
|
||||
gmus->ZoneID = sgmus->zone_id;
|
||||
gmus->InstanceID = 0;
|
||||
gmus->LastSeen = sgmus->last_seen;
|
||||
gmus->GuildID = sgmus->guild_id;
|
||||
gmus->ZoneID = sgmus->zone_id;
|
||||
gmus->InstanceID = 0;
|
||||
gmus->LastSeen = sgmus->last_seen;
|
||||
gmus->offline_mode = sgmus->offline_mode;
|
||||
strn0cpy(gmus->MemberName, sgmus->member_name, sizeof(gmus->MemberName));
|
||||
|
||||
entity_list.QueueClientsGuild(outapp, sgmus->guild_id);
|
||||
@@ -653,17 +654,24 @@ void ZoneGuildManager::ProcessWorldPacket(ServerPacket *pack)
|
||||
}
|
||||
}
|
||||
|
||||
void ZoneGuildManager::SendGuildMemberUpdateToWorld(const char *MemberName, uint32 GuildID, uint16 ZoneID, uint32 LastSeen)
|
||||
void ZoneGuildManager::SendGuildMemberUpdateToWorld(
|
||||
const char *MemberName,
|
||||
uint32 GuildID,
|
||||
uint16 ZoneID,
|
||||
uint32 LastSeen,
|
||||
uint32 offline_mode
|
||||
)
|
||||
{
|
||||
auto pack = new ServerPacket(ServerOP_GuildMemberUpdate, sizeof(ServerGuildMemberUpdate_Struct));
|
||||
|
||||
ServerGuildMemberUpdate_Struct *sgmus = (ServerGuildMemberUpdate_Struct*)pack->pBuffer;
|
||||
sgmus->guild_id = GuildID;
|
||||
auto sgmus = (ServerGuildMemberUpdate_Struct *) pack->pBuffer;
|
||||
sgmus->guild_id = GuildID;
|
||||
sgmus->zone_id = ZoneID;
|
||||
sgmus->last_seen = LastSeen;
|
||||
sgmus->offline_mode = offline_mode;
|
||||
strn0cpy(sgmus->member_name, MemberName, sizeof(sgmus->member_name));
|
||||
sgmus->zone_id = ZoneID;
|
||||
sgmus->last_seen = LastSeen;
|
||||
worldserver.SendPacket(pack);
|
||||
|
||||
worldserver.SendPacket(pack);
|
||||
safe_delete(pack);
|
||||
}
|
||||
|
||||
@@ -1518,14 +1526,12 @@ uint8* ZoneGuildManager::MakeGuildMembers(uint32 guild_id, const char* prefix_na
|
||||
PutField(total_tribute);
|
||||
PutField(last_tribute);
|
||||
SlideStructString(note_buf, ci->public_note);
|
||||
//e->zoneinstance = 0;
|
||||
if (ci->online) {
|
||||
e->zone_id = ci->zone_id; //This routine, if there is a zone_id, will update the entire guild window (roster, notes, tribute) for online characters.
|
||||
e->zone_id = 0; //If zone_id is 0 and we rely on the current world routine, the notes/tribute tabs are not updated for online characters.
|
||||
e->offline_mode = 0;
|
||||
if (ci->online || ci->offline_mode) {
|
||||
e->zone_id = ci->zone_id; //This routine, if there is a zone_id, will update the entire guild window (roster, notes, tribute) for online characters.
|
||||
e->offline_mode = ci->offline_mode;
|
||||
}
|
||||
else {
|
||||
e->zone_id = 0; //If zone_id is 0 and we rely on the current world routine, the notes/tribute tabs are not updated for online characters.
|
||||
}
|
||||
|
||||
#undef SlideStructString
|
||||
#undef PutFieldN
|
||||
|
||||
|
||||
+1
-1
@@ -88,7 +88,7 @@ public:
|
||||
|
||||
void RecordInvite(uint32 char_id, uint32 guild_id, uint8 rank);
|
||||
bool VerifyAndClearInvite(uint32 char_id, uint32 guild_id, uint8 rank);
|
||||
void SendGuildMemberUpdateToWorld(const char *MemberName, uint32 GuildID, uint16 ZoneID, uint32 LastSeen);
|
||||
void SendGuildMemberUpdateToWorld(const char *MemberName, uint32 GuildID, uint16 ZoneID, uint32 LastSeen, uint32 offline_mode);
|
||||
void RequestOnlineGuildMembers(uint32 FromID, uint32 GuildID);
|
||||
void UpdateRankPermission(uint32 gid, uint32 charid, uint32 fid, uint32 rank, uint32 value);
|
||||
void SendPermissionUpdate(uint32 guild_id, uint32 rank, uint32 function_id, uint32 value);
|
||||
|
||||
+116
-12
@@ -650,8 +650,7 @@ bool Client::SummonItem(uint32 item_id, int16 charges, uint32 aug1, uint32 aug2,
|
||||
|
||||
// put item into inventory
|
||||
if (to_slot == EQ::invslot::slotCursor) {
|
||||
PushItemOnCursor(*inst);
|
||||
SendItemPacket(EQ::invslot::slotCursor, inst, ItemPacketLimbo);
|
||||
PushItemOnCursor(*inst, true);
|
||||
} else {
|
||||
PutItemInInventory(to_slot, *inst, true);
|
||||
}
|
||||
@@ -958,7 +957,7 @@ void Client::SendCursorBuffer()
|
||||
}
|
||||
|
||||
// Remove item from inventory
|
||||
void Client::DeleteItemInInventory(int16 slot_id, int16 quantity, bool client_update, bool update_db) {
|
||||
bool Client::DeleteItemInInventory(int16 slot_id, int16 quantity, bool client_update, bool update_db) {
|
||||
#if (EQDEBUG >= 5)
|
||||
LogDebug("DeleteItemInInventory([{}], [{}], [{}])", slot_id, quantity, (client_update) ? "true":"false");
|
||||
#endif
|
||||
@@ -977,7 +976,7 @@ void Client::DeleteItemInInventory(int16 slot_id, int16 quantity, bool client_up
|
||||
QueuePacket(outapp);
|
||||
safe_delete(outapp);
|
||||
}
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64 evolve_id = m_inv[slot_id]->GetEvolveUniqueID();
|
||||
@@ -1031,6 +1030,8 @@ void Client::DeleteItemInInventory(int16 slot_id, int16 quantity, bool client_up
|
||||
safe_delete(outapp);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Client::PushItemOnCursor(const EQ::ItemInstance& inst, bool client_update)
|
||||
@@ -1055,6 +1056,16 @@ bool Client::PushItemOnCursor(const EQ::ItemInstance& inst, bool client_update)
|
||||
bool Client::PutItemInInventory(int16 slot_id, const EQ::ItemInstance& inst, bool client_update) {
|
||||
LogInventory("Putting item [{}] ([{}]) into slot [{}]", inst.GetItem()->Name, inst.GetItem()->ID, slot_id);
|
||||
|
||||
if (inst.GetUniqueID().empty()) {
|
||||
auto item_unique_id = std::string(inst.GetUniqueID());
|
||||
if (!database.EnsureItemUniqueId(item_unique_id)) {
|
||||
LogError("Failed to reserve item_unique_id for item [{}] ([{}])", inst.GetItem()->Name, inst.GetItem()->ID);
|
||||
return false;
|
||||
}
|
||||
|
||||
const_cast<EQ::ItemInstance &>(inst).SetUniqueID(item_unique_id);
|
||||
}
|
||||
|
||||
if (slot_id == EQ::invslot::slotCursor) { // don't trust macros before conditional statements...
|
||||
return PushItemOnCursor(inst, client_update);
|
||||
}
|
||||
@@ -4674,19 +4685,112 @@ bool Client::HasItemOnCorpse(uint32 item_id)
|
||||
bool Client::PutItemInInventoryWithStacking(EQ::ItemInstance *inst)
|
||||
{
|
||||
auto free_id = GetInv().FindFirstFreeSlotThatFitsItem(inst->GetItem());
|
||||
if (inst->IsStackable()) {
|
||||
if (TryStacking(inst, ItemPacketTrade, true, false)) {
|
||||
|
||||
if (!inst->IsStackable()) {
|
||||
if (free_id != INVALID_INDEX &&
|
||||
!EQ::ValueWithin(free_id, EQ::invslot::EQUIPMENT_BEGIN, EQ::invslot::EQUIPMENT_END)) {
|
||||
return PutItemInInventory(free_id, *inst, true);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
struct temp {
|
||||
int16 slot_id;
|
||||
int32 quantity;
|
||||
};
|
||||
|
||||
std::vector<temp> queue;
|
||||
auto quantity = inst->GetCharges();
|
||||
|
||||
for (int i = EQ::invslot::GENERAL_BEGIN; i <= EQ::invslot::GENERAL_END; i++) {
|
||||
auto inv_inst = GetInv().GetItem(i);
|
||||
if (!inv_inst) {
|
||||
LogError("Found a slot {} in general inventory", i);
|
||||
inst->SetCharges(quantity);
|
||||
PutItemInInventory(i, *inst, true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Protect equipment slots (0-22) from being overwritten
|
||||
if (free_id != INVALID_INDEX && !EQ::ValueWithin(free_id, EQ::invslot::EQUIPMENT_BEGIN, EQ::invslot::EQUIPMENT_END)) {
|
||||
if (PutItemInInventory(free_id, *inst, true)) {
|
||||
return true;
|
||||
|
||||
int16 base_slot_id = EQ::InventoryProfile::CalcSlotId(i, EQ::invbag::SLOT_BEGIN);
|
||||
uint8 bag_size = inv_inst->GetItem()->BagSlots;
|
||||
|
||||
for (uint8 bag_slot = EQ::invbag::SLOT_BEGIN; bag_slot < bag_size; bag_slot++) {
|
||||
if (quantity == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto bag_inst = GetInv().GetItem(base_slot_id + bag_slot);
|
||||
if (!bag_inst && inv_inst->GetItem()->BagSize >= inst->GetItem()->Size) {
|
||||
LogError("Found a parent {} base_slot_id {} bag_slot {} in bag", i, base_slot_id, bag_slot);
|
||||
inst->SetCharges(quantity);
|
||||
PutItemInInventory(base_slot_id + bag_slot, *inst, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (bag_inst && bag_inst->IsStackable() && bag_inst->GetID() == inst->GetID()) {
|
||||
auto stack_size = bag_inst->GetItem()->StackSize;
|
||||
auto bag_inst_quantity = bag_inst->GetCharges();
|
||||
int16 temp_slot = base_slot_id + bag_slot;
|
||||
if (stack_size - bag_inst_quantity >= quantity) {
|
||||
temp tmp = {temp_slot, quantity};
|
||||
queue.push_back(tmp);
|
||||
quantity = 0;
|
||||
LogError(
|
||||
"Found an item parent {} base_slot_id {} bag_slot {} in bag with ENOUGH space",
|
||||
i,
|
||||
base_slot_id,
|
||||
bag_slot
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
if (stack_size - bag_inst_quantity > 0) {
|
||||
temp tmp = {temp_slot, stack_size - bag_inst_quantity};
|
||||
queue.push_back(tmp);
|
||||
quantity -= stack_size - bag_inst_quantity;
|
||||
LogError(
|
||||
"Found an item parent {} base_slot_id {} bag_slot {} in bag with SOME space",
|
||||
i,
|
||||
base_slot_id,
|
||||
bag_slot
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!queue.empty()) {
|
||||
database.TransactionBegin();
|
||||
for (auto const &i: queue) {
|
||||
auto bag_inst = GetInv().GetItem(i.slot_id);
|
||||
if (!bag_inst) {
|
||||
LogError("Client inventory error occurred. Character ID {} Slot_ID {}", CharacterID(), i.slot_id);
|
||||
continue;
|
||||
}
|
||||
bag_inst->SetCharges(i.quantity + bag_inst->GetCharges());
|
||||
PutItemInInventory(i.slot_id, *bag_inst, true);
|
||||
LogError("Write out data. Item {} quantity {} slot {}", bag_inst->GetItem()->Name, i.quantity, i.slot_id);
|
||||
}
|
||||
|
||||
database.TransactionCommit();
|
||||
}
|
||||
|
||||
if (quantity == 0) {
|
||||
LogError("Quantity was zero. All items placed in inventory.");
|
||||
return true;
|
||||
}
|
||||
|
||||
inst->SetCharges(quantity);
|
||||
if (free_id != INVALID_INDEX &&
|
||||
!EQ::ValueWithin(free_id, EQ::invslot::EQUIPMENT_BEGIN, EQ::invslot::EQUIPMENT_END) &&
|
||||
PutItemInInventory(free_id, *inst, true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
LogError("Could not find enough room");
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
bool Client::FindNumberOfFreeInventorySlotsWithSizeCheck(std::vector<BuyerLineTradeItems_Struct> items)
|
||||
{
|
||||
|
||||
+99
@@ -1954,4 +1954,103 @@ private:
|
||||
|
||||
void DoSpellInterrupt(uint16 spell_id, int32 mana_cost, int my_curmana);
|
||||
void HandleDoorOpen();
|
||||
|
||||
public:
|
||||
Mob* GetMob() { return this; }
|
||||
|
||||
void CloneMob(Mob& in) {
|
||||
strn0cpy(name, in.name, 64);
|
||||
strn0cpy(orig_name, in.orig_name, 64);
|
||||
strn0cpy(lastname, in.lastname, 64);
|
||||
current_hp = in.current_hp;
|
||||
max_hp = in.max_hp;
|
||||
base_hp = in.base_hp;
|
||||
gender = in.gender;
|
||||
race = in.race;
|
||||
base_gender = in.base_gender;
|
||||
base_race = in.race;
|
||||
use_model = in.use_model;
|
||||
class_ = in.class_;
|
||||
bodytype = in.bodytype;
|
||||
orig_bodytype = in.orig_bodytype;
|
||||
deity = in.deity;
|
||||
level = in.level;
|
||||
orig_level = in.orig_level;
|
||||
npctype_id = in.npctype_id;
|
||||
size = in.size;
|
||||
base_size = in.base_size;
|
||||
runspeed = in.runspeed;
|
||||
texture = in.texture;
|
||||
helmtexture = in.helmtexture;
|
||||
armtexture = in.armtexture;
|
||||
bracertexture = in.bracertexture;
|
||||
handtexture = in.handtexture;
|
||||
legtexture = in.legtexture;
|
||||
feettexture = in.feettexture;
|
||||
multitexture = in.multitexture;
|
||||
haircolor = in.haircolor;
|
||||
beardcolor = in.beardcolor;
|
||||
eyecolor1 = in.eyecolor1;
|
||||
eyecolor2 = in.eyecolor2;
|
||||
hairstyle = in.hairstyle;
|
||||
luclinface = in.luclinface;
|
||||
beard = in.beard;
|
||||
drakkin_heritage = in.drakkin_heritage;
|
||||
drakkin_tattoo = in.drakkin_tattoo;
|
||||
drakkin_details = in.drakkin_details;
|
||||
attack_speed = in.attack_speed;
|
||||
attack_delay = in.attack_delay;
|
||||
slow_mitigation = in.slow_mitigation;
|
||||
findable = in.findable;
|
||||
trackable = in.trackable;
|
||||
has_shield_equipped = in.has_shield_equipped;
|
||||
has_two_hand_blunt_equipped = in.has_two_hand_blunt_equipped;
|
||||
has_two_hander_equipped = in.has_two_hander_equipped;
|
||||
has_dual_weapons_equipped = in.has_dual_weapons_equipped;
|
||||
can_facestab = in.can_facestab;
|
||||
has_numhits = in.has_numhits;
|
||||
has_MGB = in.has_MGB;
|
||||
has_ProjectIllusion = in.has_ProjectIllusion;
|
||||
SpellPowerDistanceMod = in.SpellPowerDistanceMod;
|
||||
last_los_check = in.last_los_check;
|
||||
aa_title = in.aa_title;
|
||||
AC = in.AC;
|
||||
ATK = in.ATK;
|
||||
STR = in.STR;
|
||||
STA = in.STA;
|
||||
DEX = in.DEX;
|
||||
AGI = in.AGI;
|
||||
INT = in.INT;
|
||||
WIS = in.WIS;
|
||||
CHA = in.CHA;
|
||||
MR = in.MR;
|
||||
extra_haste = in.extra_haste;
|
||||
bEnraged = in.bEnraged;
|
||||
current_mana = in.current_mana;
|
||||
max_mana = in.max_mana;
|
||||
hp_regen = in.hp_regen;
|
||||
hp_regen_per_second = in.hp_regen_per_second;
|
||||
mana_regen = in.mana_regen;
|
||||
ooc_regen = in.ooc_regen;
|
||||
maxlevel = in.maxlevel;
|
||||
scalerate = in.scalerate;
|
||||
invisible = in.invisible;
|
||||
invisible_undead = in.invisible_undead;
|
||||
invisible_animals = in.invisible_animals;
|
||||
sneaking = in.sneaking;
|
||||
hidden = in.hidden;
|
||||
improved_hidden = in.improved_hidden;
|
||||
invulnerable = in.invulnerable;
|
||||
qglobal = in.qglobal;
|
||||
spawned = in.spawned;
|
||||
rare_spawn = in.rare_spawn;
|
||||
always_aggro = in.always_aggro;
|
||||
heroic_strikethrough = in.heroic_strikethrough;
|
||||
keeps_sold_items = in.keeps_sold_items;
|
||||
|
||||
for (int i = 0; i < MAX_APPEARANCE_EFFECTS; i++) {
|
||||
appearance_effects_id[i] = in.appearance_effects_id[i];
|
||||
appearance_effects_slot[i] = in.appearance_effects_slot[i];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
+21
-14
@@ -397,6 +397,7 @@ void Client::DoParcelSend(const Parcel_Struct *parcel_in)
|
||||
parcel_out.sent_date = time(nullptr);
|
||||
parcel_out.quantity = quantity;
|
||||
parcel_out.item_id = inst->GetID();
|
||||
parcel_out.item_unique_id = inst->GetUniqueID();
|
||||
parcel_out.char_id = send_to_client.at(0).char_id;
|
||||
parcel_out.slot_id = next_slot;
|
||||
parcel_out.evolve_amount = inst->GetEvolveCurrentAmount();
|
||||
@@ -434,13 +435,14 @@ void Client::DoParcelSend(const Parcel_Struct *parcel_in)
|
||||
|
||||
std::vector<CharacterParcelsContainersRepository::CharacterParcelsContainers> all_entries{};
|
||||
if (inst->IsNoneEmptyContainer()) {
|
||||
for (auto const &kv: *inst->GetContents()) {
|
||||
for (auto const &[slot, item]: *inst->GetContents()) {
|
||||
CharacterParcelsContainersRepository::CharacterParcelsContainers cpc{};
|
||||
cpc.parcels_id = result.id;
|
||||
cpc.slot_id = kv.first;
|
||||
cpc.item_id = kv.second->GetID();
|
||||
if (kv.second->IsAugmented()) {
|
||||
auto augs = kv.second->GetAugmentIDs();
|
||||
cpc.parcels_id = result.id;
|
||||
cpc.slot_id = slot;
|
||||
cpc.item_id = item->GetID();
|
||||
cpc.item_unique_id = item->GetUniqueID();
|
||||
if (item->IsAugmented()) {
|
||||
auto augs = item->GetAugmentIDs();
|
||||
cpc.aug_slot_1 = augs.at(0);
|
||||
cpc.aug_slot_2 = augs.at(1);
|
||||
cpc.aug_slot_3 = augs.at(2);
|
||||
@@ -449,14 +451,15 @@ void Client::DoParcelSend(const Parcel_Struct *parcel_in)
|
||||
cpc.aug_slot_6 = augs.at(5);
|
||||
}
|
||||
|
||||
cpc.quantity = kv.second->GetCharges() >= 0 ? kv.second->GetCharges() : 1;
|
||||
cpc.evolve_amount = kv.second->GetEvolveCurrentAmount();
|
||||
cpc.quantity = item->GetCharges() >= 0 ? item->GetCharges() : 1;
|
||||
cpc.evolve_amount = item->GetEvolveCurrentAmount();
|
||||
cpc.quantity = item->GetCharges() >= 0 ? item->GetCharges() : 1;
|
||||
all_entries.push_back(cpc);
|
||||
}
|
||||
CharacterParcelsContainersRepository::InsertMany(database, all_entries);
|
||||
}
|
||||
|
||||
RemoveItemBySerialNumber(inst->GetSerialNumber(), parcel_out.quantity == 0 ? 1 : parcel_out.quantity);
|
||||
RemoveItemByItemUniqueId(inst->GetUniqueID(), parcel_out.quantity == 0 ? 1 : parcel_out.quantity);
|
||||
std::unique_ptr<EQApplicationPacket> outapp(new EQApplicationPacket(OP_ShopSendParcel));
|
||||
QueuePacket(outapp.get());
|
||||
|
||||
@@ -478,6 +481,7 @@ void Client::DoParcelSend(const Parcel_Struct *parcel_in)
|
||||
e.from_player_name = parcel_out.from_name;
|
||||
e.to_player_name = send_to_client.at(0).character_name;
|
||||
e.item_id = parcel_out.item_id;
|
||||
e.item_unique_id = parcel_out.item_unique_id;
|
||||
e.augment_1_id = parcel_out.aug_slot_1;
|
||||
e.augment_2_id = parcel_out.aug_slot_2;
|
||||
e.augment_3_id = parcel_out.aug_slot_3;
|
||||
@@ -494,6 +498,7 @@ void Client::DoParcelSend(const Parcel_Struct *parcel_in)
|
||||
e.from_player_name = parcel_out.from_name;
|
||||
e.to_player_name = send_to_client.at(0).character_name;
|
||||
e.item_id = i.item_id;
|
||||
e.item_unique_id = i.item_unique_id;
|
||||
e.augment_1_id = i.aug_slot_1;
|
||||
e.augment_2_id = i.aug_slot_2;
|
||||
e.augment_3_id = i.aug_slot_3;
|
||||
@@ -656,8 +661,9 @@ void Client::DoParcelRetrieve(const ParcelRetrieve_Struct &parcel_in)
|
||||
}
|
||||
);
|
||||
if (p != m_parcels.end()) {
|
||||
uint32 item_id = parcel_in.parcel_item_id;
|
||||
uint32 item_quantity = p->second.quantity;
|
||||
uint32 item_id = parcel_in.parcel_item_id;
|
||||
uint32 item_quantity = p->second.quantity;
|
||||
std::string item_unique_id = p->second.item_unique_id;
|
||||
if (!item_id) {
|
||||
LogError(
|
||||
"Attempt to retrieve parcel with erroneous item id for client character id {}.",
|
||||
@@ -699,6 +705,7 @@ void Client::DoParcelRetrieve(const ParcelRetrieve_Struct &parcel_in)
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
inst->SetUniqueID(item_unique_id);
|
||||
std::vector<CharacterParcelsContainersRepository::CharacterParcelsContainers> results{};
|
||||
if (inst->IsClassBag() && inst->GetItem()->BagSlots > 0) {
|
||||
auto contents = inst->GetContents();
|
||||
@@ -723,7 +730,7 @@ void Client::DoParcelRetrieve(const ParcelRetrieve_Struct &parcel_in)
|
||||
}
|
||||
|
||||
item->SetEvolveCurrentAmount(i.evolve_amount);
|
||||
|
||||
item->SetUniqueID(i.item_unique_id);
|
||||
if (CheckLoreConflict(item->GetItem())) {
|
||||
if (RuleB(Parcel, DeleteOnDuplicate)) {
|
||||
MessageString(Chat::Yellow, PARCEL_DUPLICATE_DELETE, inst->GetItem()->Name);
|
||||
@@ -772,6 +779,7 @@ void Client::DoParcelRetrieve(const ParcelRetrieve_Struct &parcel_in)
|
||||
PlayerEvent::ParcelRetrieve e{};
|
||||
e.from_player_name = p->second.from_name;
|
||||
e.item_id = p->second.item_id;
|
||||
e.item_unique_id = p->second.item_unique_id;
|
||||
e.augment_1_id = p->second.aug_slot_1;
|
||||
e.augment_2_id = p->second.aug_slot_2;
|
||||
e.augment_3_id = p->second.aug_slot_3;
|
||||
@@ -785,6 +793,7 @@ void Client::DoParcelRetrieve(const ParcelRetrieve_Struct &parcel_in)
|
||||
for (auto const &i:results) {
|
||||
e.from_player_name = p->second.from_name;
|
||||
e.item_id = i.item_id;
|
||||
e.item_unique_id = i.item_unique_id;
|
||||
e.augment_1_id = i.aug_slot_1;
|
||||
e.augment_2_id = i.aug_slot_2;
|
||||
e.augment_3_id = i.aug_slot_3;
|
||||
@@ -794,8 +803,6 @@ void Client::DoParcelRetrieve(const ParcelRetrieve_Struct &parcel_in)
|
||||
e.quantity = i.quantity;
|
||||
e.sent_date = p->second.sent_date;
|
||||
RecordPlayerEventLog(PlayerEvent::PARCEL_RETRIEVE, e);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,6 +308,7 @@
|
||||
#define PLAYER_CHARMED 1461 //You lose control of yourself!
|
||||
#define TRADER_BUSY 1468 //That Trader is currently with a customer. Please wait until their transaction is finished.
|
||||
#define SENSE_CORPSE_DIRECTION 1563 //You sense a corpse in this direction.
|
||||
#define HOW_CAN_YOU_BUY_MORE 1571 //%1 tells you, 'Your inventory appears full! How can you buy more?'
|
||||
#define DUPE_LORE_MERCHANT 1573 //%1 tells you, 'You already have the lore item, %2, on your person, on your shroud, in the bank, in a real estate, or as an augment in another item. You cannot have more than one of a particular lore item at a time.'
|
||||
#define QUEUED_TELL 2458 //[queued]
|
||||
#define QUEUE_TELL_FULL 2459 //[zoing and queue is full]
|
||||
@@ -414,6 +415,7 @@
|
||||
#define MAX_ACTIVE_TASKS 6010 //Sorry %3, you already have the maximum number of active tasks.
|
||||
#define TASK_REQUEST_COOLDOWN_TIMER 6011 //Sorry, %3, but you can't request another task for %4 minutes and %5 seconds.
|
||||
#define FORAGE_MASTERY 6012 //Your forage mastery has enabled you to find something else!
|
||||
#define BUYER 6056 //BUYER
|
||||
#define BUYER_WELCOME 6065 //There are %1 Buyers waiting to purchase your loot. Type /barter to search for them, or use /buyer to set up your own Buy Lines.
|
||||
#define BUYER_GREETING 6070 //%1 greets you, '%2'
|
||||
#define GUILD_BANK_CANNOT_DEPOSIT 6097 // Cannot deposit this item. Containers must be empty, and only one of each LORE and no NO TRADE or TEMPORARY items may be deposited.
|
||||
@@ -465,6 +467,8 @@
|
||||
#define LDON_NO_LOCKPICK 7564 //You must have a lock pick in your inventory to do this.
|
||||
#define LDON_WAS_NOT_LOCKED 7565 //%1 was not locked.
|
||||
#define LDON_WAS_NOT_TRAPPED 7566 //%1 was not trapped
|
||||
#define DUPLICATE_LORE 7623 //Transaction failed: Duplicate Lore Item!
|
||||
#define INSUFFICIENT_FUNDS 7632 //Transaction failed: Insufficient funds!
|
||||
#define GAIN_SINGLE_AA_SINGLE_AA 8019 //You have gained an ability point! You now have %1 ability point.
|
||||
#define GAIN_SINGLE_AA_MULTI_AA 8020 //You have gained an ability point! You now have %1 ability points.
|
||||
#define GAIN_MULTI_AA_MULTI_AA 8021 //You have gained %1 ability point(s)! You now have %2 ability point(s).
|
||||
@@ -539,6 +543,7 @@
|
||||
#define GROUP_INVITEE_NOT_FOUND 12268 //You must target a player or use /invite <name> to invite someone to your group.
|
||||
#define GROUP_INVITEE_SELF 12270 //12270 You cannot invite yourself.
|
||||
#define ALREADY_IN_PARTY 12272 //That person is already in your party.
|
||||
#define TRADER 12315 //TRADER
|
||||
#define TALKING_TO_SELF 12323 //Talking to yourself again?
|
||||
#define SPLIT_NO_GROUP 12328 //You are not in a group! Keep it all.
|
||||
#define NO_LONGER_HIDDEN 12337 //You are no longer hidden.
|
||||
|
||||
+725
-789
File diff suppressed because it is too large
Load Diff
+394
-52
@@ -23,7 +23,10 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#include "common/misc_functions.h"
|
||||
#include "common/patches/patches.h"
|
||||
#include "common/profanity_manager.h"
|
||||
#include "common/repositories/account_repository.h"
|
||||
#include "common/repositories/guild_tributes_repository.h"
|
||||
#include "common/repositories/character_offline_transactions_repository.h"
|
||||
#include "common/repositories/offline_character_sessions_repository.h"
|
||||
#include "common/rulesys.h"
|
||||
#include "common/say_link.h"
|
||||
#include "common/server_reload_types.h"
|
||||
@@ -54,6 +57,9 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
#include "common/repositories/account_repository.h"
|
||||
#include "common/repositories/character_offline_transactions_repository.h"
|
||||
|
||||
extern EntityList entity_list;
|
||||
extern Zone *zone;
|
||||
extern volatile bool is_zone_loaded;
|
||||
@@ -3766,62 +3772,268 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p)
|
||||
break;
|
||||
}
|
||||
case ServerOP_BazaarPurchase: {
|
||||
auto in = (BazaarPurchaseMessaging_Struct *) pack->pBuffer;
|
||||
auto trader_pc = entity_list.GetClientByCharID(in->trader_buy_struct.trader_id);
|
||||
if (!trader_pc) {
|
||||
LogTrading("Request trader_id <red>[{}] could not be found in zone_id <red>[{}]",
|
||||
in->trader_buy_struct.trader_id,
|
||||
zone->GetZoneID()
|
||||
);
|
||||
return;
|
||||
}
|
||||
auto in = reinterpret_cast<BazaarPurchaseMessaging_Struct *>(pack->pBuffer);
|
||||
switch (in->transaction_status) {
|
||||
case BazaarPurchaseBuyerCompleteSendToSeller: {
|
||||
auto trader_pc = entity_list.GetClientByCharID(in->trader_buy_struct.trader_id);
|
||||
if (!trader_pc) {
|
||||
LogTrading(
|
||||
"Request trader_id [{}] could not be found in zone_id [{}] instance_id [{}]",
|
||||
in->trader_buy_struct.trader_id,
|
||||
zone->GetZoneID(),
|
||||
zone->GetInstanceID()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (trader_pc->IsThereACustomer()) {
|
||||
auto customer = entity_list.GetClientByID(trader_pc->GetCustomerID());
|
||||
if (customer) {
|
||||
customer->CancelTraderTradeWindow();
|
||||
auto item = trader_pc->FindTraderItemByUniqueID(in->trader_buy_struct.item_unique_id);
|
||||
if (!item) {
|
||||
in->transaction_status = BazaarPurchaseTraderFailed;
|
||||
TraderRepository::UpdateActiveTransaction(database, in->id, false);
|
||||
worldserver.SendPacket(pack);
|
||||
break;
|
||||
}
|
||||
|
||||
//if there is a customer currently browsing, close to ensure no conflict of purchase
|
||||
if (trader_pc->IsThereACustomer()) {
|
||||
auto customer = entity_list.GetClientByID(trader_pc->GetCustomerID());
|
||||
if (customer) {
|
||||
customer->CancelTraderTradeWindow();
|
||||
}
|
||||
}
|
||||
|
||||
//Update the trader's db entries
|
||||
if (item->IsStackable() && in->item_quantity != in->item_charges) {
|
||||
TraderRepository::UpdateQuantity(database, in->trader_buy_struct.item_unique_id, item->GetCharges() - in->item_quantity);
|
||||
LogTradingDetail(
|
||||
"Step 4:Bazaar Purchase. Decreased database id {} from [{}] to [{}] charges",
|
||||
in->trader_buy_struct.item_id,
|
||||
item->GetCharges(),
|
||||
item->GetCharges() - in->item_quantity
|
||||
);
|
||||
}
|
||||
else {
|
||||
TraderRepository::DeleteOne(database, in->id);
|
||||
LogTradingDetail(
|
||||
"Step 4:Bazaar Purchase. Deleted database id [{}] because database quantity [{}] equals [{}] purchased quantity",
|
||||
in->trader_buy_struct.item_id,
|
||||
item->GetCharges(),
|
||||
item->GetCharges() - in->item_quantity
|
||||
);
|
||||
}
|
||||
|
||||
//at this time, buyer checks ok, seller checks ok.
|
||||
//perform actions to trader
|
||||
uint64 total_cost = static_cast<uint64>(in->trader_buy_struct.price) * static_cast<uint64>(in->item_quantity);
|
||||
if (!trader_pc->RemoveItemByItemUniqueId(in->trader_buy_struct.item_unique_id, in->item_quantity)) {
|
||||
LogTradingDetail(
|
||||
"Failed to remove item {} quantity [{}] from trader [{}]",
|
||||
in->trader_buy_struct.item_unique_id,
|
||||
in->item_quantity,
|
||||
trader_pc->CharacterID()
|
||||
);
|
||||
in->transaction_status = BazaarPurchaseTraderFailed;
|
||||
TraderRepository::UpdateActiveTransaction(database, in->id, false);
|
||||
worldserver.SendPacket(pack);
|
||||
break;
|
||||
}
|
||||
|
||||
LogTradingDetail(
|
||||
"Step 5:Bazaar Purchase. Removed from inventory of Trader [{}] for sale of [{}] {}{}",
|
||||
trader_pc->CharacterID(),
|
||||
in->item_quantity,
|
||||
in->item_quantity > 1 ? fmt::format("{}s", in->trader_buy_struct.item_name)
|
||||
: in->trader_buy_struct.item_name,
|
||||
item->GetItem()->MaxCharges > 0 ? fmt::format(" with charges of [{}]", in->item_charges)
|
||||
: std::string("")
|
||||
);
|
||||
|
||||
trader_pc->AddMoneyToPP(total_cost, true);
|
||||
|
||||
//Update the trader to indicate the sale has completed
|
||||
EQApplicationPacket outapp(OP_Trader, sizeof(TraderBuy_Struct));
|
||||
auto data = reinterpret_cast<TraderBuy_Struct *>(outapp.pBuffer);
|
||||
|
||||
memcpy(data, &in->trader_buy_struct, sizeof(TraderBuy_Struct));
|
||||
trader_pc->QueuePacket(&outapp);
|
||||
|
||||
if (item && PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::TRADER_SELL)) {
|
||||
auto e = PlayerEvent::TraderSellEvent{
|
||||
.item_id = item->GetID(),
|
||||
.augment_1_id = item->GetAugmentItemID(0),
|
||||
.augment_2_id = item->GetAugmentItemID(1),
|
||||
.augment_3_id = item->GetAugmentItemID(2),
|
||||
.augment_4_id = item->GetAugmentItemID(3),
|
||||
.augment_5_id = item->GetAugmentItemID(4),
|
||||
.augment_6_id = item->GetAugmentItemID(5),
|
||||
.item_name = in->trader_buy_struct.item_name,
|
||||
.buyer_id = in->buyer_id,
|
||||
.buyer_name = in->trader_buy_struct.buyer_name,
|
||||
.price = in->trader_buy_struct.price,
|
||||
.quantity = in->item_quantity,
|
||||
.charges = in->item_charges,
|
||||
.total_cost = total_cost,
|
||||
.player_money_balance = trader_pc->GetCarriedMoney(),
|
||||
.offline_purchase = trader_pc->IsOffline(),
|
||||
};
|
||||
RecordPlayerEventLogWithClient(trader_pc, PlayerEvent::TRADER_SELL, e);
|
||||
}
|
||||
|
||||
if (trader_pc->IsOffline()) {
|
||||
auto e = CharacterOfflineTransactionsRepository::NewEntity();
|
||||
e.character_id = trader_pc->CharacterID();
|
||||
e.item_name = in->trader_buy_struct.item_name;
|
||||
e.price = in->trader_buy_struct.price * in->trader_buy_struct.quantity;
|
||||
e.quantity = in->trader_buy_struct.quantity;
|
||||
e.type = TRADER_TRANSACTION;
|
||||
e.buyer_name = in->trader_buy_struct.buyer_name;
|
||||
|
||||
CharacterOfflineTransactionsRepository::InsertOne(database, e);
|
||||
}
|
||||
|
||||
in->transaction_status = BazaarPurchaseSuccess;
|
||||
TraderRepository::UpdateActiveTransaction(database, in->id, false);
|
||||
worldserver.SendPacket(pack);
|
||||
|
||||
LogTradingDetail("Step 6:Bazaar Purchase. Purchase checks complete for trader. Send Success to buyer via world.");
|
||||
|
||||
break;
|
||||
}
|
||||
case BazaarPurchaseTraderFailed: {
|
||||
auto buyer = entity_list.GetClientByCharID(in->buyer_id);
|
||||
if (!buyer) {
|
||||
LogTrading(
|
||||
"Requested buyer_id [{}] could not be found in zone_id [{}] instance_id [{}]",
|
||||
in->trader_buy_struct.trader_id,
|
||||
zone->GetZoneID(),
|
||||
zone->GetInstanceID()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// return buyer's money including the fee
|
||||
uint64 total_cost =
|
||||
static_cast<uint64>(in->trader_buy_struct.price) * static_cast<uint64>(in->item_quantity);
|
||||
uint64 fee = std::round(total_cost * RuleR(Bazaar, ParcelDeliveryCostMod));
|
||||
buyer->AddMoneyToPP(total_cost + fee, false);
|
||||
buyer->SendMoneyUpdate();
|
||||
|
||||
buyer->Message(Chat::Red, "Bazaar purchased failed. Returning your money.");
|
||||
LogTradingDetail(
|
||||
"Bazaar Purchase Failed. Returning money [{}] + fee [{}] to Buyer [{}]",
|
||||
total_cost,
|
||||
fee,
|
||||
buyer->CharacterID()
|
||||
);
|
||||
buyer->TradeRequestFailed(in->trader_buy_struct);
|
||||
break;
|
||||
}
|
||||
case BazaarPurchaseSuccess: {
|
||||
auto buyer = entity_list.GetClientByCharID(in->buyer_id);
|
||||
if (!buyer) {
|
||||
LogTrading(
|
||||
"Requested buyer_id [{}] could not be found in zone_id [{}] instance_id [{}]",
|
||||
in->trader_buy_struct.trader_id,
|
||||
zone->GetZoneID(),
|
||||
zone->GetInstanceID()
|
||||
);
|
||||
return;
|
||||
}
|
||||
uint64 total_cost =
|
||||
static_cast<uint64>(in->trader_buy_struct.price) * static_cast<uint64>(in->item_quantity);
|
||||
|
||||
if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::TRADER_PURCHASE)) {
|
||||
auto e = PlayerEvent::TraderPurchaseEvent{
|
||||
.item_id = in->trader_buy_struct.item_id,
|
||||
.augment_1_id = in->item_aug_1,
|
||||
.augment_2_id = in->item_aug_2,
|
||||
.augment_3_id = in->item_aug_3,
|
||||
.augment_4_id = in->item_aug_4,
|
||||
.augment_5_id = in->item_aug_5,
|
||||
.augment_6_id = in->item_aug_6,
|
||||
.item_name = in->trader_buy_struct.item_name,
|
||||
.trader_id = in->trader_buy_struct.trader_id,
|
||||
.trader_name = in->trader_buy_struct.seller_name,
|
||||
.price = in->trader_buy_struct.price,
|
||||
.quantity = in->item_quantity,
|
||||
.charges = in->item_charges,
|
||||
.total_cost = total_cost,
|
||||
.player_money_balance = buyer->GetCarriedMoney(),
|
||||
};
|
||||
|
||||
RecordPlayerEventLogWithClient(buyer, PlayerEvent::TRADER_PURCHASE, e);
|
||||
}
|
||||
|
||||
auto item = database.GetItem(in->trader_buy_struct.item_id);
|
||||
auto quantity = in->item_quantity;
|
||||
if (item->MaxCharges > 0) {
|
||||
quantity = in->item_charges;
|
||||
}
|
||||
|
||||
//Send the item via parcel
|
||||
CharacterParcelsRepository::CharacterParcels parcel_out{};
|
||||
parcel_out.from_name = in->trader_buy_struct.seller_name;
|
||||
parcel_out.note = "Delivered from a Bazaar Purchase";
|
||||
parcel_out.sent_date = time(nullptr);
|
||||
parcel_out.quantity = quantity;
|
||||
parcel_out.item_id = in->trader_buy_struct.item_id;
|
||||
parcel_out.item_unique_id = in->trader_buy_struct.item_unique_id;
|
||||
parcel_out.aug_slot_1 = in->item_aug_1;
|
||||
parcel_out.aug_slot_2 = in->item_aug_2;
|
||||
parcel_out.aug_slot_3 = in->item_aug_3;
|
||||
parcel_out.aug_slot_4 = in->item_aug_4;
|
||||
parcel_out.aug_slot_5 = in->item_aug_5;
|
||||
parcel_out.aug_slot_6 = in->item_aug_6;
|
||||
parcel_out.char_id = buyer->CharacterID();
|
||||
parcel_out.slot_id = buyer->FindNextFreeParcelSlot(buyer->CharacterID());
|
||||
parcel_out.id = 0;
|
||||
|
||||
CharacterParcelsRepository::InsertOne(database, parcel_out);
|
||||
|
||||
if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::PARCEL_SEND)) {
|
||||
PlayerEvent::ParcelSend e{};
|
||||
e.from_player_name = parcel_out.from_name;
|
||||
e.to_player_name = buyer->GetCleanName();
|
||||
e.item_id = parcel_out.item_id;
|
||||
e.augment_1_id = parcel_out.aug_slot_1;
|
||||
e.augment_2_id = parcel_out.aug_slot_2;
|
||||
e.augment_3_id = parcel_out.aug_slot_3;
|
||||
e.augment_4_id = parcel_out.aug_slot_4;
|
||||
e.augment_5_id = parcel_out.aug_slot_5;
|
||||
e.augment_6_id = parcel_out.aug_slot_6;
|
||||
e.quantity = in->item_quantity;
|
||||
e.charges = in->item_charges;
|
||||
e.sent_date = parcel_out.sent_date;
|
||||
|
||||
RecordPlayerEventLogWithClient(buyer, PlayerEvent::PARCEL_SEND, e);
|
||||
}
|
||||
|
||||
Parcel_Struct ps{};
|
||||
ps.item_slot = parcel_out.slot_id;
|
||||
strn0cpy(ps.send_to, buyer->GetCleanName(), sizeof(ps.send_to));
|
||||
buyer->SendParcelDeliveryToWorld(ps);
|
||||
|
||||
LogTradingDetail("Step 7:Bazaar Purchase. Sent parcel to Buyer [{}] for purchase of [{}] {}{}",
|
||||
buyer->CharacterID(),
|
||||
quantity,
|
||||
quantity > 1 ? fmt::format("{}s", in->trader_buy_struct.item_name) : in->trader_buy_struct.item_name,
|
||||
item->MaxCharges > 0 ? fmt::format(" with charges of [{}]", in->item_charges) : std::string("")
|
||||
);
|
||||
|
||||
//Update the buyer to indicate the sale has completed
|
||||
EQApplicationPacket outapp(OP_Trader, sizeof(TraderBuy_Struct));
|
||||
auto data = reinterpret_cast<TraderBuy_Struct *>(outapp.pBuffer);
|
||||
|
||||
memcpy(data, &in->trader_buy_struct, sizeof(TraderBuy_Struct));
|
||||
buyer->ReturnTraderReq(&outapp, in->item_quantity, in->trader_buy_struct.item_id);
|
||||
LogTradingDetail("Step 8:Bazaar Purchase. Purchase complete. Sending update packet to buyer.");
|
||||
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
}
|
||||
}
|
||||
|
||||
auto item_sn = Strings::ToUnsignedBigInt(in->trader_buy_struct.serial_number);
|
||||
auto outapp = std::make_unique<EQApplicationPacket>(OP_Trader, static_cast<uint32>(sizeof(TraderBuy_Struct)));
|
||||
auto data = (TraderBuy_Struct *) outapp->pBuffer;
|
||||
|
||||
memcpy(data, &in->trader_buy_struct, sizeof(TraderBuy_Struct));
|
||||
|
||||
if (trader_pc->ClientVersion() < EQ::versions::ClientVersion::RoF) {
|
||||
data->price = in->trader_buy_struct.price * in->trader_buy_struct.quantity;
|
||||
}
|
||||
|
||||
TraderRepository::UpdateActiveTransaction(database, in->id, false);
|
||||
|
||||
auto item = trader_pc->FindTraderItemBySerialNumber(item_sn);
|
||||
|
||||
if (item && PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::TRADER_SELL)) {
|
||||
auto e = PlayerEvent::TraderSellEvent{
|
||||
.item_id = item ? item->GetID() : 0,
|
||||
.augment_1_id = item->GetAugmentItemID(0),
|
||||
.augment_2_id = item->GetAugmentItemID(1),
|
||||
.augment_3_id = item->GetAugmentItemID(2),
|
||||
.augment_4_id = item->GetAugmentItemID(3),
|
||||
.augment_5_id = item->GetAugmentItemID(4),
|
||||
.augment_6_id = item->GetAugmentItemID(5),
|
||||
.item_name = in->trader_buy_struct.item_name,
|
||||
.buyer_id = in->buyer_id,
|
||||
.buyer_name = in->trader_buy_struct.buyer_name,
|
||||
.price = in->trader_buy_struct.price,
|
||||
.quantity = in->trader_buy_struct.quantity,
|
||||
.charges = item ? item->IsStackable() ? 1 : item->GetCharges() : 0,
|
||||
.total_cost = (in->trader_buy_struct.price * in->trader_buy_struct.quantity),
|
||||
.player_money_balance = trader_pc->GetCarriedMoney(),
|
||||
};
|
||||
RecordPlayerEventLogWithClient(trader_pc, PlayerEvent::TRADER_SELL, e);
|
||||
}
|
||||
|
||||
trader_pc->RemoveItemBySerialNumber(item_sn, in->trader_buy_struct.quantity);
|
||||
trader_pc->AddMoneyToPP(in->trader_buy_struct.price * in->trader_buy_struct.quantity, true);
|
||||
trader_pc->QueuePacket(outapp.get());
|
||||
|
||||
break;
|
||||
}
|
||||
case ServerOP_BuyerMessaging: {
|
||||
@@ -4073,6 +4285,18 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p)
|
||||
RecordPlayerEventLogWithClient(buyer, PlayerEvent::BARTER_TRANSACTION, e);
|
||||
}
|
||||
|
||||
if (buyer->IsOffline()) {
|
||||
auto e = CharacterOfflineTransactionsRepository::NewEntity();
|
||||
e.character_id = buyer->CharacterID();
|
||||
e.item_name = sell_line.item_name;
|
||||
e.price = (uint64) sell_line.item_cost * (uint64) in->seller_quantity;
|
||||
e.quantity = sell_line.seller_quantity;
|
||||
e.type = BUYER_TRANSACTION;
|
||||
e.buyer_name = sell_line.seller_name;
|
||||
|
||||
CharacterOfflineTransactionsRepository::InsertOne(database, e);
|
||||
}
|
||||
|
||||
in->action = Barter_BuyerTransactionComplete;
|
||||
worldserver.SendPacket(pack);
|
||||
|
||||
@@ -4133,8 +4357,126 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p)
|
||||
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ServerOP_UsertoWorldCancelOfflineRequest: {
|
||||
auto in = reinterpret_cast<UsertoWorldResponse *>(pack->pBuffer);
|
||||
auto client = entity_list.GetClientByLSID(in->lsaccountid);
|
||||
if (!client) {
|
||||
LogLoginserverDetail("Step 6a(1) - Zone received ServerOP_UsertoWorldCancelOfflineRequest though could "
|
||||
"not find client."
|
||||
);
|
||||
|
||||
auto e = AccountRepository::GetWhere(database, fmt::format("`lsaccount_id` = '{}'", in->lsaccountid));
|
||||
if (!e.empty()) {
|
||||
auto r = e.front();
|
||||
auto session = OfflineCharacterSessionsRepository::GetByAccountId(database, r.id);
|
||||
auto trader = TraderRepository::GetAccountZoneIdAndInstanceIdByAccountId(database, r.id);
|
||||
const uint32 character_id = session.id ? session.character_id : trader.character_id;
|
||||
|
||||
database.TransactionBegin();
|
||||
r.offline = 0;
|
||||
AccountRepository::UpdateOne(database, r);
|
||||
OfflineCharacterSessionsRepository::DeleteByAccountId(database, r.id);
|
||||
if (character_id) {
|
||||
TraderRepository::DeleteWhere(database, fmt::format("`character_id` = '{}'", character_id));
|
||||
BuyerRepository::DeleteBuyer(database, character_id);
|
||||
}
|
||||
|
||||
auto commit_result = database.TransactionCommit();
|
||||
if (!commit_result.Success()) {
|
||||
database.TransactionRollback();
|
||||
LogError(
|
||||
"Failed clearing orphaned offline session state for account [{}]: ({}) {}",
|
||||
r.id,
|
||||
commit_result.ErrorNumber(),
|
||||
commit_result.ErrorMessage()
|
||||
);
|
||||
}
|
||||
|
||||
LogLoginserverDetail(
|
||||
"Step 6a(2) - Zone cleared offline status in account table for user id {} / {}",
|
||||
r.lsaccount_id,
|
||||
r.charname
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
auto sp = new ServerPacket(ServerOP_UsertoWorldCancelOfflineResponse, pack->size);
|
||||
auto out = reinterpret_cast<UsertoWorldResponse *>(sp->pBuffer);
|
||||
sp->opcode = ServerOP_UsertoWorldCancelOfflineResponse;
|
||||
out->FromID = in->FromID;
|
||||
out->lsaccountid = in->lsaccountid;
|
||||
out->response = in->response;
|
||||
out->ToID = in->ToID;
|
||||
out->worldid = in->worldid;
|
||||
strn0cpy(out->login, in->login, 64);
|
||||
|
||||
LogLoginserverDetail("Step 6a(3) - Zone sending ServerOP_UsertoWorldCancelOfflineResponse back to world");
|
||||
worldserver.SendPacket(sp);
|
||||
safe_delete(sp);
|
||||
break;
|
||||
}
|
||||
|
||||
LogLoginserverDetail(
|
||||
"Step 6b(1) - Zone received ServerOP_UsertoWorldCancelOfflineRequest and found client {}",
|
||||
client->GetCleanName()
|
||||
);
|
||||
LogLoginserverDetail(
|
||||
"Step 6b(2) - Zone cleared offline status in account table for user id {} / {}",
|
||||
client->CharacterID(),
|
||||
client->GetCleanName()
|
||||
);
|
||||
AccountRepository::SetOfflineStatus(database, client->AccountID(), false);
|
||||
OfflineCharacterSessionsRepository::DeleteByAccountId(database, client->AccountID());
|
||||
|
||||
if (client->IsThereACustomer()) {
|
||||
auto customer = entity_list.GetClientByID(client->GetCustomerID());
|
||||
if (customer) {
|
||||
auto end_session = new EQApplicationPacket(OP_ShopEnd);
|
||||
customer->FastQueuePacket(&end_session);
|
||||
}
|
||||
}
|
||||
|
||||
if (client->IsTrader()) {
|
||||
LogLoginserverDetail("Step 6b(3) - Zone ending trader mode for client {}", client->GetCleanName());
|
||||
client->TraderEndTrader();
|
||||
}
|
||||
|
||||
if (client->IsBuyer()) {
|
||||
LogLoginserverDetail("Step 6b(4) - Zone ending buyer mode for client {}", client->GetCleanName());
|
||||
client->ToggleBuyerMode(false);
|
||||
}
|
||||
|
||||
LogLoginserverDetail("Step 6b(5) - Zone updating UpdateWho(2) for client {}", client->GetCleanName());
|
||||
client->UpdateWho(2);
|
||||
|
||||
auto outapp = new EQApplicationPacket();
|
||||
LogLoginserverDetail("Step 6b(6) - Zone sending despawn packet for client {}", client->GetCleanName());
|
||||
client->CreateDespawnPacket(outapp, false);
|
||||
entity_list.QueueClients(nullptr, outapp, false);
|
||||
safe_delete(outapp);
|
||||
|
||||
LogLoginserverDetail("Step 6b(7) - Zone removing client from entity_list");
|
||||
entity_list.RemoveMob(client->CastToMob()->GetID());
|
||||
|
||||
auto sp = new ServerPacket(ServerOP_UsertoWorldCancelOfflineResponse, pack->size);
|
||||
auto out = reinterpret_cast<UsertoWorldResponse *>(sp->pBuffer);
|
||||
sp->opcode = ServerOP_UsertoWorldCancelOfflineResponse;
|
||||
out->FromID = in->FromID;
|
||||
out->lsaccountid = in->lsaccountid;
|
||||
out->response = in->response;
|
||||
out->ToID = in->ToID;
|
||||
out->worldid = in->worldid;
|
||||
strn0cpy(out->login, in->login, 64);
|
||||
|
||||
LogLoginserverDetail("Step 6b(8) - Zone sending ServerOP_UsertoWorldCancelOfflineResponse back to world");
|
||||
worldserver.SendPacket(sp);
|
||||
safe_delete(sp);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
LogInfo("Unknown ZS Opcode [{}] size [{}]", (int) pack->opcode, pack->size);
|
||||
break;
|
||||
|
||||
+57
-342
@@ -56,6 +56,9 @@
|
||||
#include <ctime>
|
||||
#include <iostream>
|
||||
|
||||
#include "common/repositories/inventory_repository.h"
|
||||
#include "common/repositories/inventory_snapshots_repository.h"
|
||||
|
||||
extern Zone* zone;
|
||||
|
||||
ZoneDatabase database;
|
||||
@@ -304,19 +307,19 @@ void ZoneDatabase::DeleteWorldContainer(uint32 parent_id, uint32 zone_id)
|
||||
);
|
||||
}
|
||||
|
||||
std::unique_ptr<EQ::ItemInstance> ZoneDatabase::LoadSingleTraderItem(uint32 char_id, int serial_number)
|
||||
std::unique_ptr<EQ::ItemInstance> ZoneDatabase::LoadSingleTraderItem(uint32 character_id, const std::string &unique_item_id)
|
||||
{
|
||||
auto results = TraderRepository::GetWhere(
|
||||
database,
|
||||
fmt::format(
|
||||
"`char_id` = '{}' AND `item_sn` = '{}' ORDER BY slot_id",
|
||||
char_id,
|
||||
serial_number
|
||||
"`character_id` = {} AND `item_unique_id` = '{}' ORDER BY slot_id",
|
||||
character_id,
|
||||
unique_item_id
|
||||
)
|
||||
);
|
||||
|
||||
if (results.empty()) {
|
||||
LogTrading("Could not find item serial number {} for character id {}", serial_number, char_id);
|
||||
LogTrading("Could not find item serial number {} for character id {}", unique_item_id, character_id);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -338,12 +341,12 @@ std::unique_ptr<EQ::ItemInstance> ZoneDatabase::LoadSingleTraderItem(uint32 char
|
||||
database.CreateItem(
|
||||
item_id,
|
||||
charges,
|
||||
results.at(0).aug_slot_1,
|
||||
results.at(0).aug_slot_2,
|
||||
results.at(0).aug_slot_3,
|
||||
results.at(0).aug_slot_4,
|
||||
results.at(0).aug_slot_5,
|
||||
results.at(0).aug_slot_6
|
||||
results.at(0).augment_one,
|
||||
results.at(0).augment_two,
|
||||
results.at(0).augment_three,
|
||||
results.at(0).augment_four,
|
||||
results.at(0).augment_five,
|
||||
results.at(0).augment_six
|
||||
)
|
||||
);
|
||||
if (!inst) {
|
||||
@@ -352,8 +355,7 @@ std::unique_ptr<EQ::ItemInstance> ZoneDatabase::LoadSingleTraderItem(uint32 char
|
||||
}
|
||||
|
||||
inst->SetCharges(charges);
|
||||
inst->SetSerialNumber(serial_number);
|
||||
inst->SetMerchantSlot(serial_number);
|
||||
inst->SetUniqueID(unique_item_id);
|
||||
inst->SetPrice(cost);
|
||||
|
||||
if (inst->IsStackable()) {
|
||||
@@ -363,9 +365,9 @@ std::unique_ptr<EQ::ItemInstance> ZoneDatabase::LoadSingleTraderItem(uint32 char
|
||||
return std::move(inst);
|
||||
}
|
||||
|
||||
void ZoneDatabase::UpdateTraderItemPrice(int char_id, uint32 item_id, uint32 charges, uint32 new_price) {
|
||||
void ZoneDatabase::UpdateTraderItemPrice(int character_id, uint32 item_id, uint32 charges, uint32 new_price) {
|
||||
|
||||
LogTrading("ZoneDatabase::UpdateTraderPrice([{}], [{}], [{}], [{}])", char_id, item_id, charges, new_price);
|
||||
LogTrading("ZoneDatabase::UpdateTraderPrice([{}], [{}], [{}], [{}])", character_id, item_id, charges, new_price);
|
||||
const EQ::ItemData *item = database.GetItem(item_id);
|
||||
|
||||
if(!item) {
|
||||
@@ -373,20 +375,20 @@ void ZoneDatabase::UpdateTraderItemPrice(int char_id, uint32 item_id, uint32 cha
|
||||
}
|
||||
|
||||
if (new_price == 0) {
|
||||
LogTrading("Removing Trader items from the DB for char_id [{}], item_id [{}]", char_id, item_id);
|
||||
LogTrading("Removing Trader items from the DB for char_id [{}], item_id [{}]", character_id, item_id);
|
||||
|
||||
auto results = TraderRepository::DeleteWhere(
|
||||
database,
|
||||
fmt::format(
|
||||
"`char_id` = '{}' AND `item_id` = {}",
|
||||
char_id,
|
||||
"`character_id` = {} AND `item_id` = {}",
|
||||
character_id,
|
||||
item_id
|
||||
)
|
||||
);
|
||||
if (!results) {
|
||||
LogDebug("[CLIENT] Failed to remove trader item(s): [{}] for char_id: [{}]",
|
||||
item_id,
|
||||
char_id
|
||||
character_id
|
||||
);
|
||||
}
|
||||
|
||||
@@ -394,23 +396,23 @@ void ZoneDatabase::UpdateTraderItemPrice(int char_id, uint32 item_id, uint32 cha
|
||||
}
|
||||
|
||||
if (!item->Stackable) {
|
||||
auto results = TraderRepository::UpdateItem(database, char_id, new_price, item_id, charges);
|
||||
auto results = TraderRepository::UpdateItem(database, character_id, new_price, item_id, charges);
|
||||
if (!results) {
|
||||
LogTrading(
|
||||
"Failed to update price for trader item [{}] for char_id: [{}]",
|
||||
item_id,
|
||||
char_id
|
||||
character_id
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
auto results = TraderRepository::UpdateItem(database, char_id, new_price, item_id, 0);
|
||||
auto results = TraderRepository::UpdateItem(database, character_id, new_price, item_id, 0);
|
||||
if (!results) {
|
||||
LogTrading(
|
||||
"Failed to update price for trader item [{}] for char_id: [{}]",
|
||||
item_id,
|
||||
char_id
|
||||
character_id
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1311,269 +1313,48 @@ bool ZoneDatabase::NoRentExpired(const std::string& name)
|
||||
return seconds > 1800;
|
||||
}
|
||||
|
||||
bool ZoneDatabase::SaveCharacterInvSnapshot(uint32 character_id) {
|
||||
uint32 time_index = time(nullptr);
|
||||
std::string query = StringFormat(
|
||||
"INSERT "
|
||||
"INTO"
|
||||
" `inventory_snapshots` "
|
||||
"(`time_index`,"
|
||||
" `charid`,"
|
||||
" `slotid`,"
|
||||
" `itemid`,"
|
||||
" `charges`,"
|
||||
" `color`,"
|
||||
" `augslot1`,"
|
||||
" `augslot2`,"
|
||||
" `augslot3`,"
|
||||
" `augslot4`,"
|
||||
" `augslot5`,"
|
||||
" `augslot6`,"
|
||||
" `instnodrop`,"
|
||||
" `custom_data`,"
|
||||
" `ornamenticon`,"
|
||||
" `ornamentidfile`,"
|
||||
" `ornament_hero_model`,"
|
||||
" `guid`"
|
||||
") "
|
||||
"SELECT"
|
||||
" %u,"
|
||||
" `character_id`,"
|
||||
" `slot_id`,"
|
||||
" `item_id`,"
|
||||
" `charges`,"
|
||||
" `color`,"
|
||||
" `augment_one`,"
|
||||
" `augment_two`,"
|
||||
" `augment_three`,"
|
||||
" `augment_four`,"
|
||||
" `augment_five`,"
|
||||
" `augment_six`,"
|
||||
" `instnodrop`,"
|
||||
" `custom_data`,"
|
||||
" `ornament_icon`,"
|
||||
" `ornament_idfile`,"
|
||||
" `ornament_hero_model`,"
|
||||
" `guid` "
|
||||
"FROM"
|
||||
" `inventory` "
|
||||
"WHERE"
|
||||
" `character_id` = %u",
|
||||
time_index,
|
||||
character_id
|
||||
);
|
||||
auto results = database.QueryDatabase(query);
|
||||
LogInventory("[{}] ([{}])", character_id, (results.Success() ? "pass" : "fail"));
|
||||
return results.Success();
|
||||
}
|
||||
|
||||
int ZoneDatabase::CountCharacterInvSnapshots(uint32 character_id) {
|
||||
std::string query = StringFormat(
|
||||
"SELECT"
|
||||
" COUNT(*) "
|
||||
"FROM "
|
||||
"("
|
||||
"SELECT * FROM"
|
||||
" `inventory_snapshots` a "
|
||||
"WHERE"
|
||||
" `charid` = %u "
|
||||
"GROUP BY"
|
||||
" `time_index`"
|
||||
") b",
|
||||
character_id
|
||||
);
|
||||
auto results = QueryDatabase(query);
|
||||
|
||||
if (!results.Success())
|
||||
return -1;
|
||||
|
||||
auto& row = results.begin();
|
||||
|
||||
int64 count = Strings::ToBigInt(row[0]);
|
||||
if (count > 2147483647)
|
||||
return -2;
|
||||
if (count < 0)
|
||||
return -3;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
void ZoneDatabase::ClearCharacterInvSnapshots(uint32 character_id, bool from_now) {
|
||||
uint32 del_time = time(nullptr);
|
||||
if (!from_now) { del_time -= RuleI(Character, InvSnapshotHistoryD) * 86400; }
|
||||
|
||||
std::string query = StringFormat(
|
||||
"DELETE "
|
||||
"FROM"
|
||||
" `inventory_snapshots` "
|
||||
"WHERE"
|
||||
" `charid` = %u "
|
||||
"AND"
|
||||
" `time_index` <= %lu",
|
||||
character_id,
|
||||
(unsigned long)del_time
|
||||
);
|
||||
QueryDatabase(query);
|
||||
}
|
||||
|
||||
void ZoneDatabase::ListCharacterInvSnapshots(uint32 character_id, std::list<std::pair<uint32, int>> &is_list) {
|
||||
std::string query = StringFormat(
|
||||
"SELECT"
|
||||
" `time_index`,"
|
||||
" COUNT(*) "
|
||||
"FROM"
|
||||
" `inventory_snapshots` "
|
||||
"WHERE"
|
||||
" `charid` = %u "
|
||||
"GROUP BY"
|
||||
" `time_index` "
|
||||
"ORDER BY"
|
||||
" `time_index` "
|
||||
"DESC",
|
||||
character_id
|
||||
);
|
||||
auto results = QueryDatabase(query);
|
||||
|
||||
if (!results.Success())
|
||||
return;
|
||||
|
||||
for (auto row : results)
|
||||
is_list.emplace_back(std::pair<uint32, int>(Strings::ToUnsignedInt(row[0]), Strings::ToInt(row[1])));
|
||||
}
|
||||
|
||||
bool ZoneDatabase::ValidateCharacterInvSnapshotTimestamp(uint32 character_id, uint32 timestamp) {
|
||||
if (!character_id || !timestamp)
|
||||
return false;
|
||||
|
||||
std::string query = StringFormat(
|
||||
"SELECT"
|
||||
" * "
|
||||
"FROM"
|
||||
" `inventory_snapshots` "
|
||||
"WHERE"
|
||||
" `charid` = %u "
|
||||
"AND"
|
||||
" `time_index` = %u "
|
||||
"LIMIT 1",
|
||||
character_id,
|
||||
timestamp
|
||||
);
|
||||
auto results = QueryDatabase(query);
|
||||
|
||||
if (!results.Success() || results.RowCount() == 0)
|
||||
bool ZoneDatabase::SaveCharacterInvSnapshot(uint32 character_id)
|
||||
{
|
||||
if (!InventorySnapshotsRepository::SaveCharacterInvSnapshot(database, character_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ZoneDatabase::ParseCharacterInvSnapshot(uint32 character_id, uint32 timestamp, std::list<std::pair<int16, uint32>> &parse_list) {
|
||||
std::string query = StringFormat(
|
||||
"SELECT"
|
||||
" `slotid`,"
|
||||
" `itemid` "
|
||||
"FROM"
|
||||
" `inventory_snapshots` "
|
||||
"WHERE"
|
||||
" `charid` = %u "
|
||||
"AND"
|
||||
" `time_index` = %u "
|
||||
"ORDER BY"
|
||||
" `slotid`",
|
||||
character_id,
|
||||
timestamp
|
||||
);
|
||||
auto results = QueryDatabase(query);
|
||||
|
||||
if (!results.Success())
|
||||
return;
|
||||
|
||||
for (auto row : results)
|
||||
parse_list.emplace_back(std::pair<int16, uint32>(Strings::ToInt(row[0]), Strings::ToUnsignedInt(row[1])));
|
||||
int ZoneDatabase::CountCharacterInvSnapshots(uint32 character_id)
|
||||
{
|
||||
return InventorySnapshotsRepository::CountCharacterInvSnapshots(*this, character_id);
|
||||
}
|
||||
|
||||
void ZoneDatabase::DivergeCharacterInvSnapshotFromInventory(uint32 character_id, uint32 timestamp, std::list<std::pair<int16, uint32>> &compare_list) {
|
||||
std::string query = StringFormat(
|
||||
"SELECT"
|
||||
" slotid,"
|
||||
" itemid "
|
||||
"FROM"
|
||||
" `inventory_snapshots` "
|
||||
"WHERE"
|
||||
" `time_index` = %u "
|
||||
"AND"
|
||||
" `charid` = %u "
|
||||
"AND"
|
||||
" `slotid` NOT IN "
|
||||
"("
|
||||
"SELECT"
|
||||
" a.`slotid` "
|
||||
"FROM"
|
||||
" `inventory_snapshots` a "
|
||||
"JOIN"
|
||||
" `inventory` b "
|
||||
"USING"
|
||||
" (`slot_id`, `item_id`) "
|
||||
"WHERE"
|
||||
" a.`time_index` = %u "
|
||||
"AND"
|
||||
" a.`charid` = %u "
|
||||
"AND"
|
||||
" b.`character_id` = %u"
|
||||
")",
|
||||
timestamp,
|
||||
character_id,
|
||||
timestamp,
|
||||
character_id,
|
||||
character_id
|
||||
);
|
||||
auto results = QueryDatabase(query);
|
||||
|
||||
if (!results.Success())
|
||||
return;
|
||||
|
||||
for (auto row : results)
|
||||
compare_list.emplace_back(std::pair<int16, uint32>(Strings::ToInt(row[0]), Strings::ToUnsignedInt(row[1])));
|
||||
void ZoneDatabase::ClearCharacterInvSnapshots(uint32 character_id, bool from_now)
|
||||
{
|
||||
InventorySnapshotsRepository::ClearCharacterInvSnapshots(*this, character_id, from_now);
|
||||
}
|
||||
|
||||
void ZoneDatabase::DivergeCharacterInventoryFromInvSnapshot(uint32 character_id, uint32 timestamp, std::list<std::pair<int16, uint32>> &compare_list) {
|
||||
std::string query = StringFormat(
|
||||
"SELECT"
|
||||
" `slotid`,"
|
||||
" `itemid` "
|
||||
"FROM"
|
||||
" `inventory` "
|
||||
"WHERE"
|
||||
" `character_id` = %u "
|
||||
"AND"
|
||||
" `slotid` NOT IN "
|
||||
"("
|
||||
"SELECT"
|
||||
" a.`slotid` "
|
||||
"FROM"
|
||||
" `inventory` a "
|
||||
"JOIN"
|
||||
" `inventory_snapshots` b "
|
||||
"USING"
|
||||
" (`slotid`, `itemid`) "
|
||||
"WHERE"
|
||||
" b.`time_index` = %u "
|
||||
"AND"
|
||||
" b.`charid` = %u "
|
||||
"AND"
|
||||
" a.`character_id` = %u"
|
||||
")",
|
||||
character_id,
|
||||
timestamp,
|
||||
character_id,
|
||||
character_id
|
||||
);
|
||||
auto results = QueryDatabase(query);
|
||||
void ZoneDatabase::ListCharacterInvSnapshots(uint32 character_id, std::list<std::pair<uint32, int>> &is_list)
|
||||
{
|
||||
InventorySnapshotsRepository::ListCharacterInvSnapshots(*this, character_id, is_list);
|
||||
}
|
||||
|
||||
if (!results.Success())
|
||||
return;
|
||||
bool ZoneDatabase::ValidateCharacterInvSnapshotTimestamp(uint32 character_id, uint32 timestamp)
|
||||
{
|
||||
return InventorySnapshotsRepository::ValidateCharacterInvSnapshotTimestamp(*this, character_id, timestamp);
|
||||
}
|
||||
|
||||
for (auto row : results)
|
||||
compare_list.emplace_back(std::pair<int16, uint32>(Strings::ToInt(row[0]), Strings::ToUnsignedInt(row[1])));
|
||||
void ZoneDatabase::ParseCharacterInvSnapshot(uint32 character_id, uint32 timestamp, std::list<std::pair<int16, uint32>> &parse_list)
|
||||
{
|
||||
InventorySnapshotsRepository::ParseCharacterInvSnapshot(*this, character_id, timestamp, parse_list);
|
||||
}
|
||||
|
||||
void ZoneDatabase::DivergeCharacterInvSnapshotFromInventory(uint32 character_id, uint32 timestamp, std::list<std::pair<int16, uint32>> &compare_list)
|
||||
{
|
||||
InventorySnapshotsRepository::DivergeCharacterInvSnapshotFromInventory(*this, character_id, timestamp, compare_list);
|
||||
}
|
||||
|
||||
void ZoneDatabase::DivergeCharacterInventoryFromInvSnapshot(uint32 character_id, uint32 timestamp, std::list<std::pair<int16, uint32>> &compare_list)
|
||||
{
|
||||
InventorySnapshotsRepository::DivergeCharacterInventoryFromInvSnapshot(*this, character_id, timestamp, compare_list);
|
||||
}
|
||||
|
||||
bool ZoneDatabase::RestoreCharacterInvSnapshot(uint32 character_id, uint32 timestamp) {
|
||||
@@ -1584,73 +1365,7 @@ bool ZoneDatabase::RestoreCharacterInvSnapshot(uint32 character_id, uint32 times
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string query = StringFormat(
|
||||
"DELETE "
|
||||
"FROM"
|
||||
" `inventory` "
|
||||
"WHERE"
|
||||
" `character_id` = %u",
|
||||
character_id
|
||||
);
|
||||
auto results = database.QueryDatabase(query);
|
||||
if (!results.Success())
|
||||
return false;
|
||||
|
||||
query = StringFormat(
|
||||
"INSERT "
|
||||
"INTO"
|
||||
" `inventory` "
|
||||
"(`character_id`,"
|
||||
" `slot_id`,"
|
||||
" `item_id`,"
|
||||
" `charges`,"
|
||||
" `color`,"
|
||||
" `augment_one`,"
|
||||
" `augment_two`,"
|
||||
" `augment_three`,"
|
||||
" `augment_four`,"
|
||||
" `augment_five`,"
|
||||
" `augment_six`,"
|
||||
" `instnodrop`,"
|
||||
" `custom_data`,"
|
||||
" `ornament_icon`,"
|
||||
" `ornament_idfile`,"
|
||||
" `ornament_hero_model`,"
|
||||
" `guid` "
|
||||
") "
|
||||
"SELECT"
|
||||
" `charid`,"
|
||||
" `slotid`,"
|
||||
" `itemid`,"
|
||||
" `charges`,"
|
||||
" `color`,"
|
||||
" `augslot1`,"
|
||||
" `augslot2`,"
|
||||
" `augslot3`,"
|
||||
" `augslot4`,"
|
||||
" `augslot5`,"
|
||||
" `augslot6`,"
|
||||
" `instnodrop`,"
|
||||
" `custom_data`,"
|
||||
" `ornamenticon`,"
|
||||
" `ornamentidfile`,"
|
||||
" `ornament_hero_model`, "
|
||||
" `guid` "
|
||||
"FROM"
|
||||
" `inventory_snapshots` "
|
||||
"WHERE"
|
||||
" `charid` = %u "
|
||||
"AND"
|
||||
" `time_index` = %u",
|
||||
character_id,
|
||||
timestamp
|
||||
);
|
||||
results = database.QueryDatabase(query);
|
||||
|
||||
LogInventory("[{}] snapshot for [{}] @ [{}]",
|
||||
(results.Success() ? "restored" : "failed to restore"), character_id, timestamp);
|
||||
|
||||
return results.Success();
|
||||
return InventorySnapshotsRepository::RestoreCharacterInvSnapshot(database, character_id, timestamp);
|
||||
}
|
||||
|
||||
const NPCType *ZoneDatabase::LoadNPCTypesData(uint32 npc_type_id, bool bulk_load /*= false*/)
|
||||
|
||||
+2
-2
@@ -389,11 +389,11 @@ public:
|
||||
/* Traders */
|
||||
void SaveTraderItem(uint32 char_id,uint32 itemid,uint32 uniqueid, int32 charges,uint32 itemcost,uint8 slot);
|
||||
void UpdateTraderItemCharges(int char_id, uint32 ItemInstID, int32 charges);
|
||||
void UpdateTraderItemPrice(int char_id, uint32 item_id, uint32 charges, uint32 new_price);
|
||||
void UpdateTraderItemPrice(int character_id, uint32 item_id, uint32 charges, uint32 new_price);
|
||||
void DeleteTraderItem(uint32 char_id);
|
||||
void DeleteTraderItem(uint32 char_id,uint16 slot_id);
|
||||
|
||||
std::unique_ptr<EQ::ItemInstance> LoadSingleTraderItem(uint32 char_id, int serial_number);
|
||||
std::unique_ptr<EQ::ItemInstance> LoadSingleTraderItem(uint32 char_id, const std::string &serial_number);
|
||||
Trader_Struct* LoadTraderItem(uint32 char_id);
|
||||
TraderCharges_Struct* LoadTraderItemWithCharges(uint32 char_id);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user