Implement bazaar item identity and offline trading rework

This commit is contained in:
Vayle 2026-03-19 22:18:32 -04:00
commit 2add472199
90 changed files with 5379 additions and 2165 deletions

View File

@ -191,6 +191,7 @@ set(repositories
repositories/base/base_character_leadership_abilities_repository.h
repositories/base/base_character_material_repository.h
repositories/base/base_character_memmed_spells_repository.h
repositories/base/base_character_offline_transactions_repository.h
repositories/base/base_character_parcels_containers_repository.h
repositories/base/base_character_parcels_repository.h
repositories/base/base_character_peqzone_flags_repository.h
@ -389,6 +390,7 @@ set(repositories
repositories/character_leadership_abilities_repository.h
repositories/character_material_repository.h
repositories/character_memmed_spells_repository.h
repositories/character_offline_transactions_repository.h
repositories/character_parcels_containers_repository.h
repositories/character_parcels_repository.h
repositories/character_peqzone_flags_repository.h

View File

@ -188,11 +188,11 @@ Bazaar::GetSearchResults(
);
}
else {
search_criteria_trader.append(fmt::format(" AND trader.char_id = {}", search.trader_id));
search_criteria_trader.append(fmt::format(" AND trader.character_id = {}", search.trader_id));
}
}
else {
search_criteria_trader.append(fmt::format(" AND trader.char_id = {}", search.trader_id));
search_criteria_trader.append(fmt::format(" AND trader.character_id = {}", search.trader_id));
}
}
@ -263,16 +263,24 @@ Bazaar::GetSearchResults(
}
std::vector<BazaarSearchResultsFromDB_Struct> all_entries;
std::vector<std::string> trader_items_ids{};
std::unordered_set<std::string> trader_items_ids{};
auto const trader_results = TraderRepository::GetBazaarTraderDetails(
db,
search_criteria_trader,
search.item_name,
field_criteria_items,
where_criteria_items,
search.max_results
);
auto const trader_results = TraderRepository::GetBazaarTraderDetails(db, search_criteria_trader);
if (trader_results.empty()) {
LogTradingDetail("Bazaar - No traders found in bazaar search.");
return all_entries;
}
for (auto const &i: trader_results) {
trader_items_ids.push_back(std::to_string(i.trader.item_id));
trader_items_ids.emplace(std::to_string(i.trader.item_id));
}
auto const item_results = ItemsRepository::GetItemsForBazaarSearch(
@ -291,43 +299,38 @@ Bazaar::GetSearchResults(
all_entries.reserve(trader_results.size());
for (auto const& t:trader_results) {
if (!item_results.contains(t.trader.item_id)) {
continue;
}
for (auto const& t:trader_results) {
if (!item_results.contains(t.trader.item_id)) {
continue;
}
BazaarSearchResultsFromDB_Struct r{};
r.count = 1;
r.trader_id = t.trader.char_id;
r.serial_number = t.trader.item_sn;
r.cost = t.trader.item_cost;
r.slot_id = t.trader.slot_id;
r.charges = t.trader.item_charges;
r.stackable = item_results.at(t.trader.item_id).stackable;
r.icon_id = item_results.at(t.trader.item_id).icon;
r.trader_zone_id = t.trader.char_zone_id;
r.trader_zone_instance_id = t.trader.char_zone_instance_id;
r.trader_entity_id = t.trader.char_entity_id;
r.serial_number_RoF = fmt::format("{:016}\0", t.trader.item_sn);
r.item_name = fmt::format("{:.63}\0", item_results.at(t.trader.item_id).name);
r.trader_name = fmt::format("{:.63}\0", t.trader_name);
r.item_stat = item_results.at(t.trader.item_id).stats;
BazaarSearchResultsFromDB_Struct r{};
r.count = 1;
r.trader_id = t.trader.character_id;
r.item_unique_id = t.trader.item_unique_id;
r.cost = t.trader.item_cost;
r.slot_id = t.trader.slot_id;
r.charges = t.trader.item_charges;
r.stackable = item_results.at(t.trader.item_id).stackable;
r.icon_id = item_results.at(t.trader.item_id).icon;
r.trader_zone_id = t.trader.char_zone_id;
r.trader_zone_instance_id = t.trader.char_zone_instance_id;
r.trader_entity_id = t.trader.char_entity_id;
r.item_name = fmt::format("{:.63}\0", item_results.at(t.trader.item_id).name);
r.trader_name = fmt::format("{:.63}\0", t.trader_name);
r.item_stat = item_results.at(t.trader.item_id).stats;
if (RuleB(Bazaar, UseAlternateBazaarSearch)) {
if (convert ||
char_zone_id != Zones::BAZAAR ||
(char_zone_id == Zones::BAZAAR && r.trader_zone_instance_id != char_zone_instance_id)
) {
r.trader_id = TraderRepository::TRADER_CONVERT_ID + r.trader_zone_instance_id;
}
}
if (RuleB(Bazaar, UseAlternateBazaarSearch)) {
if (convert ||
char_zone_id != Zones::BAZAAR ||
(char_zone_id == Zones::BAZAAR && r.trader_zone_instance_id != char_zone_instance_id)
) {
r.trader_id = TraderRepository::TRADER_CONVERT_ID + r.trader_zone_instance_id;
}
}
all_entries.push_back(r);
}
if (all_entries.size() > search.max_results) {
all_entries.resize(search.max_results);
}
all_entries.push_back(r);
}
LogTrading("Returning [{}] items from search results", all_entries.size());

View File

@ -33,6 +33,7 @@
#include "common/repositories/character_data_repository.h"
#include "common/repositories/character_languages_repository.h"
#include "common/repositories/character_leadership_abilities_repository.h"
#include "common/repositories/character_parcels_containers_repository.h"
#include "common/repositories/character_parcels_repository.h"
#include "common/repositories/character_pet_name_repository.h"
#include "common/repositories/character_skills_repository.h"
@ -49,6 +50,8 @@
#include "common/repositories/raid_details_repository.h"
#include "common/repositories/raid_members_repository.h"
#include "common/repositories/reports_repository.h"
#include "common/repositories/item_unique_id_reservations_repository.h"
#include "common/repositories/offline_character_sessions_repository.h"
#include "common/repositories/trader_repository.h"
#include "common/repositories/variables_repository.h"
#include "common/repositories/zone_repository.h"
@ -56,8 +59,10 @@
#include "common/strings.h"
#include "common/zone_store.h"
#include <map>
#include <algorithm>
#include <map>
#include "common/repositories/sharedbank_repository.h"
extern Client client;
@ -187,7 +192,7 @@ void Database::LoginIP(uint32 account_id, const std::string& login_ip)
QueryDatabase(query);
}
int16 Database::GetAccountStatus(uint32 account_id)
AccountStatus::StatusRecord Database::GetAccountStatus(uint32 account_id)
{
auto e = AccountRepository::FindOne(*this, account_id);
@ -199,7 +204,11 @@ int16 Database::GetAccountStatus(uint32 account_id)
AccountRepository::UpdateOne(*this, e);
}
return e.status;
AccountStatus::StatusRecord result{};
result.status = e.status;
result.offline = e.offline;
return result;
}
uint32 Database::CreateAccount(
@ -2242,6 +2251,7 @@ void Database::ClearGuildOnlineStatus()
void Database::ClearTraderDetails()
{
TraderRepository::Truncate(*this);
AccountRepository::ClearAllOfflineStatus(*this);
}
void Database::ClearBuyerDetails()
@ -2265,3 +2275,531 @@ uint64_t Database::GetNextTableId(const std::string &table_name)
return 1;
}
bool Database::ReserveItemUniqueId(const std::string &item_unique_id)
{
return ItemUniqueIdReservationsRepository::Reserve(*this, item_unique_id);
}
std::string Database::ReserveNewItemUniqueId()
{
return ItemUniqueIdReservationsRepository::ReserveNew(*this);
}
bool Database::EnsureItemUniqueId(std::string &item_unique_id)
{
if (item_unique_id.empty()) {
item_unique_id = ReserveNewItemUniqueId();
}
if (item_unique_id.empty()) {
return false;
}
return ReserveItemUniqueId(item_unique_id);
}
void Database::ConvertInventoryToNewUniqueId()
{
LogInfo("Converting inventory entries with missing item_unique_id");
auto results = InventoryRepository::GetWhere(*this, "`item_unique_id` IS NULL OR `item_unique_id` = ''");
if (results.empty()) {
return;
}
TransactionBegin();
uint32 index = 0;
const uint32 batch_size = 1000;
std::vector<InventoryRepository::Inventory> queue{};
queue.reserve(batch_size);
for (auto &r: results) {
if (!EnsureItemUniqueId(r.item_unique_id)) {
continue;
}
queue.push_back(r);
index++;
if (index >= batch_size) {
InventoryRepository::ReplaceMany(*this, queue);
index = 0;
queue.clear();
}
}
if (!queue.empty()) {
InventoryRepository::ReplaceMany(*this, queue);
}
TransactionCommit();
LogInfo("Converted {} records", results.size());
}
void Database::ConvertTraderToNewUniqueId()
{
LogInfo("Converting trader entries with missing item_unique_id");
auto results = TraderRepository::GetWhere(*this, "`item_unique_id` IS NULL OR `item_unique_id` = ''");
if (results.empty()) {
return;
}
TransactionBegin();
uint32 index = 0;
const uint32 batch_size = 1000;
std::vector<TraderRepository::Trader> queue{};
queue.reserve(batch_size);
for (auto &r: results) {
if (!EnsureItemUniqueId(r.item_unique_id)) {
continue;
}
queue.push_back(r);
index++;
if (index >= batch_size) {
TraderRepository::ReplaceMany(*this, queue);
index = 0;
queue.clear();
}
}
if (!queue.empty()) {
TraderRepository::ReplaceMany(*this, queue);
}
TransactionCommit();
LogInfo("Converted {} trader records", results.size());
}
void Database::ConvertParcelsToNewUniqueId()
{
LogInfo("Converting parcel entries with missing item_unique_id");
auto parcels = CharacterParcelsRepository::GetWhere(*this, "`item_unique_id` IS NULL OR `item_unique_id` = ''");
auto parcel_contents = CharacterParcelsContainersRepository::GetWhere(*this, "`item_unique_id` IS NULL OR `item_unique_id` = ''");
TransactionBegin();
if (!parcels.empty()) {
std::vector<CharacterParcelsRepository::CharacterParcels> queue{};
queue.reserve(parcels.size());
for (auto &r : parcels) {
if (!EnsureItemUniqueId(r.item_unique_id)) {
continue;
}
queue.push_back(r);
}
if (!queue.empty()) {
CharacterParcelsRepository::ReplaceMany(*this, queue);
}
}
if (!parcel_contents.empty()) {
std::vector<CharacterParcelsContainersRepository::CharacterParcelsContainers> queue{};
queue.reserve(parcel_contents.size());
for (auto &r : parcel_contents) {
if (!EnsureItemUniqueId(r.item_unique_id)) {
continue;
}
queue.push_back(r);
}
if (!queue.empty()) {
CharacterParcelsContainersRepository::ReplaceMany(*this, queue);
}
}
TransactionCommit();
LogInfo(
"Converted {} parcel rows and {} parcel container rows",
parcels.size(),
parcel_contents.size()
);
}
void Database::ConvertInventorySnapshotsToNewUniqueId()
{
LogInfo("Converting inventory snapshots with missing item_unique_id");
auto results = InventorySnapshotsRepository::GetWhere(*this, "`item_unique_id` IS NULL OR `item_unique_id` = ''");
if (results.empty()) {
return;
}
TransactionBegin();
uint32 index = 0;
const uint32 batch_size = 1000;
std::vector<InventorySnapshotsRepository::InventorySnapshots> queue{};
queue.reserve(batch_size);
for (auto &r : results) {
if (!EnsureItemUniqueId(r.item_unique_id)) {
continue;
}
queue.push_back(r);
index++;
if (index >= batch_size) {
InventorySnapshotsRepository::ReplaceMany(*this, queue);
index = 0;
queue.clear();
}
}
if (!queue.empty()) {
InventorySnapshotsRepository::ReplaceMany(*this, queue);
}
TransactionCommit();
LogInfo("Converted {} inventory snapshot rows", results.size());
}
void Database::ConvertSharedbankToNewUniqueId()
{
LogInfo("Converting shared bank entries with missing item_unique_id");
auto results = SharedbankRepository::GetWhere(*this, "`item_unique_id` IS NULL OR `item_unique_id` = ''");
if (results.empty()) {
return;
}
TransactionBegin();
uint32 index = 0;
const uint32 batch_size = 1000;
std::vector<SharedbankRepository::Sharedbank> queue{};
queue.reserve(batch_size);
for (auto &r: results) {
if (!EnsureItemUniqueId(r.item_unique_id)) {
continue;
}
queue.push_back(r);
index++;
if (index >= batch_size) {
SharedbankRepository::ReplaceMany(*this, queue);
index = 0;
queue.clear();
}
}
if (!queue.empty()) {
SharedbankRepository::ReplaceMany(*this, queue);
}
TransactionCommit();
LogInfo("Converted {} records", results.size());
}
void Database::ClearOfflineTradingState()
{
LogInfo("Clearing offline trading state");
ClearTraderDetails();
ClearBuyerDetails();
AccountRepository::ClearAllOfflineStatus(*this);
OfflineCharacterSessionsRepository::Truncate(*this);
}
static bool DoesColumnExist(Database &db, const std::string &table_name, const std::string &column_name)
{
auto results = db.QueryDatabase(
fmt::format(
"SHOW COLUMNS FROM `{}` LIKE '{}'",
table_name,
Strings::Escape(column_name)
)
);
return results.Success() && results.RowCount() == 1;
}
static bool GetSingleCount(Database &db, const std::string &query, uint64 &count)
{
auto results = db.QueryDatabase(query);
if (!results.Success() || results.RowCount() == 0) {
return false;
}
auto row = results.begin();
count = row[0] ? Strings::ToUnsignedBigInt(row[0]) : 0;
return true;
}
static bool ValidateItemUniqueIdMigrationSchema(Database &db, bool verbose)
{
struct ColumnRequirement {
std::string table;
std::string column;
};
const std::vector<ColumnRequirement> required_columns = {
{"inventory", "item_unique_id"},
{"sharedbank", "item_unique_id"},
{"trader", "item_unique_id"},
{"character_parcels", "item_unique_id"},
{"character_parcels_containers", "item_unique_id"},
{"inventory_snapshots", "item_unique_id"},
{"account", "offline"},
};
const std::vector<std::string> required_tables = {
"character_offline_transactions",
"offline_character_sessions",
"item_unique_id_reservations",
};
bool success = true;
for (const auto &table_name : required_tables) {
if (db.DoesTableExist(table_name)) {
continue;
}
LogError(
"Missing required table [{}] for bazaar item unique id migration. Run database updates before continuing.",
table_name
);
success = false;
}
for (const auto &requirement : required_columns) {
if (!db.DoesTableExist(requirement.table)) {
LogError(
"Missing required table [{}] for bazaar item unique id migration. Run database updates before continuing.",
requirement.table
);
success = false;
continue;
}
if (DoesColumnExist(db, requirement.table, requirement.column)) {
continue;
}
LogError(
"Missing required column [{}].[{}] for bazaar item unique id migration. Run database updates before continuing.",
requirement.table,
requirement.column
);
success = false;
}
if (verbose && success) {
LogInfo("Bazaar item unique id migration schema validation passed");
}
return success;
}
bool Database::PreflightItemUniqueIdMigration(bool verbose)
{
struct CheckTarget {
std::string table;
bool requires_uniqueness;
};
const std::vector<CheckTarget> targets = {
{"inventory", true},
{"sharedbank", true},
{"trader", true},
{"character_parcels", false},
{"character_parcels_containers", false},
{"inventory_snapshots", false},
};
if (!ValidateItemUniqueIdMigrationSchema(*this, verbose)) {
return false;
}
bool success = true;
for (const auto &target : targets) {
uint64 missing = 0;
uint64 duplicates = 0;
const auto missing_query = fmt::format(
"SELECT COUNT(*) FROM {} WHERE item_unique_id IS NULL OR item_unique_id = ''",
target.table
);
if (!GetSingleCount(*this, missing_query, missing)) {
LogError("Failed running item unique id preflight query [{}]", missing_query);
success = false;
continue;
}
if (target.requires_uniqueness) {
const auto duplicate_query = fmt::format(
"SELECT COUNT(*) FROM (SELECT item_unique_id FROM {} WHERE item_unique_id IS NOT NULL AND item_unique_id <> '' "
"GROUP BY item_unique_id HAVING COUNT(*) > 1) AS duplicates",
target.table
);
if (!GetSingleCount(*this, duplicate_query, duplicates)) {
LogError("Failed running item unique id duplicate preflight query [{}]", duplicate_query);
success = false;
continue;
}
}
if (verbose || missing || duplicates) {
LogInfo(
"Item unique id preflight [{}] missing [{}] duplicate_groups [{}]",
target.table,
missing,
duplicates
);
}
success = success && duplicates == 0;
}
uint64 live_cross_table_duplicates = 0;
const auto cross_table_duplicate_query =
"SELECT COUNT(*) FROM ("
"SELECT item_unique_id FROM ("
"SELECT item_unique_id FROM inventory WHERE item_unique_id IS NOT NULL AND item_unique_id <> '' "
"UNION ALL "
"SELECT item_unique_id FROM sharedbank WHERE item_unique_id IS NOT NULL AND item_unique_id <> '' "
"UNION ALL "
"SELECT item_unique_id FROM trader WHERE item_unique_id IS NOT NULL AND item_unique_id <> ''"
") AS live_ids GROUP BY item_unique_id HAVING COUNT(*) > 1"
") AS duplicates";
if (!GetSingleCount(*this, cross_table_duplicate_query, live_cross_table_duplicates)) {
LogError("Failed running cross-table item_unique_id preflight query");
success = false;
}
uint64 offline_sessions = 0;
if (!GetSingleCount(*this, "SELECT COUNT(*) FROM offline_character_sessions", offline_sessions)) {
LogError("Failed counting offline_character_sessions during preflight");
success = false;
}
uint64 account_offline = 0;
if (!GetSingleCount(*this, "SELECT COUNT(*) FROM account WHERE offline = 1", account_offline)) {
LogError("Failed counting offline accounts during preflight");
success = false;
}
if (verbose || live_cross_table_duplicates || offline_sessions || account_offline) {
LogInfo(
"Item unique id preflight live_cross_table_duplicates [{}] offline_sessions [{}] account_offline [{}]",
live_cross_table_duplicates,
offline_sessions,
account_offline
);
}
return success && live_cross_table_duplicates == 0;
}
bool Database::MigrateItemUniqueIdData(bool clear_trading_state, bool verbose)
{
if (!ValidateItemUniqueIdMigrationSchema(*this, verbose)) {
return false;
}
if (clear_trading_state) {
ClearOfflineTradingState();
}
ConvertInventoryToNewUniqueId();
ConvertSharedbankToNewUniqueId();
ConvertTraderToNewUniqueId();
ConvertParcelsToNewUniqueId();
ConvertInventorySnapshotsToNewUniqueId();
ItemUniqueIdReservationsRepository::PopulateFromTable(*this, "inventory", "item_unique_id");
ItemUniqueIdReservationsRepository::PopulateFromTable(*this, "sharedbank", "item_unique_id");
ItemUniqueIdReservationsRepository::PopulateFromTable(*this, "trader", "item_unique_id");
return VerifyItemUniqueIdMigration(verbose);
}
bool Database::VerifyItemUniqueIdMigration(bool verbose)
{
if (!ValidateItemUniqueIdMigrationSchema(*this, verbose)) {
return false;
}
uint64 inventory_missing = 0;
uint64 sharedbank_missing = 0;
uint64 trader_missing = 0;
uint64 parcel_missing = 0;
uint64 parcel_container_missing = 0;
uint64 snapshot_missing = 0;
uint64 inventory_duplicates = 0;
uint64 sharedbank_duplicates = 0;
uint64 trader_duplicates = 0;
uint64 live_cross_table_duplicates = 0;
uint64 offline_sessions = 0;
uint64 account_offline = 0;
const bool queries_succeeded =
GetSingleCount(*this, "SELECT COUNT(*) FROM inventory WHERE item_unique_id IS NULL OR item_unique_id = ''", inventory_missing) &&
GetSingleCount(*this, "SELECT COUNT(*) FROM sharedbank WHERE item_unique_id IS NULL OR item_unique_id = ''", sharedbank_missing) &&
GetSingleCount(*this, "SELECT COUNT(*) FROM trader WHERE item_unique_id IS NULL OR item_unique_id = ''", trader_missing) &&
GetSingleCount(*this, "SELECT COUNT(*) FROM character_parcels WHERE item_unique_id IS NULL OR item_unique_id = ''", parcel_missing) &&
GetSingleCount(*this, "SELECT COUNT(*) FROM character_parcels_containers WHERE item_unique_id IS NULL OR item_unique_id = ''", parcel_container_missing) &&
GetSingleCount(*this, "SELECT COUNT(*) FROM inventory_snapshots WHERE item_unique_id IS NULL OR item_unique_id = ''", snapshot_missing) &&
GetSingleCount(*this, "SELECT COUNT(*) FROM (SELECT item_unique_id FROM inventory WHERE item_unique_id IS NOT NULL AND item_unique_id <> '' GROUP BY item_unique_id HAVING COUNT(*) > 1) AS duplicates", inventory_duplicates) &&
GetSingleCount(*this, "SELECT COUNT(*) FROM (SELECT item_unique_id FROM sharedbank WHERE item_unique_id IS NOT NULL AND item_unique_id <> '' GROUP BY item_unique_id HAVING COUNT(*) > 1) AS duplicates", sharedbank_duplicates) &&
GetSingleCount(*this, "SELECT COUNT(*) FROM (SELECT item_unique_id FROM trader WHERE item_unique_id IS NOT NULL AND item_unique_id <> '' GROUP BY item_unique_id HAVING COUNT(*) > 1) AS duplicates", trader_duplicates) &&
GetSingleCount(
*this,
"SELECT COUNT(*) FROM ("
"SELECT item_unique_id FROM ("
"SELECT item_unique_id FROM inventory WHERE item_unique_id IS NOT NULL AND item_unique_id <> '' "
"UNION ALL "
"SELECT item_unique_id FROM sharedbank WHERE item_unique_id IS NOT NULL AND item_unique_id <> '' "
"UNION ALL "
"SELECT item_unique_id FROM trader WHERE item_unique_id IS NOT NULL AND item_unique_id <> ''"
") AS live_ids GROUP BY item_unique_id HAVING COUNT(*) > 1"
") AS duplicates",
live_cross_table_duplicates
) &&
GetSingleCount(*this, "SELECT COUNT(*) FROM offline_character_sessions", offline_sessions) &&
GetSingleCount(*this, "SELECT COUNT(*) FROM account WHERE offline = 1", account_offline);
if (!queries_succeeded) {
LogError("Item unique id verification failed to execute one or more validation queries");
return false;
}
if (verbose || inventory_missing || sharedbank_missing || trader_missing || parcel_missing || parcel_container_missing || snapshot_missing ||
inventory_duplicates || sharedbank_duplicates || trader_duplicates || live_cross_table_duplicates || offline_sessions || account_offline) {
LogInfo(
"Item unique id verification inventory_missing [{}] sharedbank_missing [{}] trader_missing [{}] "
"parcel_missing [{}] parcel_container_missing [{}] snapshot_missing [{}] inventory_duplicates [{}] "
"sharedbank_duplicates [{}] trader_duplicates [{}] live_cross_table_duplicates [{}] offline_sessions [{}] account_offline [{}]",
inventory_missing,
sharedbank_missing,
trader_missing,
parcel_missing,
parcel_container_missing,
snapshot_missing,
inventory_duplicates,
sharedbank_duplicates,
trader_duplicates,
live_cross_table_duplicates,
offline_sessions,
account_offline
);
}
return inventory_missing == 0 &&
sharedbank_missing == 0 &&
trader_missing == 0 &&
parcel_missing == 0 &&
parcel_container_missing == 0 &&
snapshot_missing == 0 &&
inventory_duplicates == 0 &&
sharedbank_duplicates == 0 &&
trader_duplicates == 0 &&
live_cross_table_duplicates == 0 &&
offline_sessions == 0 &&
account_offline == 0;
}

View File

@ -158,8 +158,8 @@ public:
const std::string GetLiveChar(uint32 account_id);
bool SetAccountStatus(const std::string& account_name, int16 status);
bool SetLocalPassword(uint32 account_id, const std::string& password);
AccountStatus::StatusRecord GetAccountStatus(uint32 account_id);
bool UpdateLiveChar(const std::string& name, uint32 account_id);
int16 GetAccountStatus(uint32 account_id);
void SetAccountCRCField(uint32 account_id, const std::string& field_name, uint64 checksum);
uint32 CheckLogin(const std::string& name, const std::string& password, const std::string& loginserver, int16* status = 0);
uint32 CreateAccount(
@ -264,6 +264,18 @@ public:
void Decode(std::string &in);
uint64_t GetNextTableId(const std::string& table_name);
bool ReserveItemUniqueId(const std::string &item_unique_id);
std::string ReserveNewItemUniqueId();
bool EnsureItemUniqueId(std::string &item_unique_id);
bool PreflightItemUniqueIdMigration(bool verbose = false);
bool MigrateItemUniqueIdData(bool clear_trading_state = true, bool verbose = false);
bool VerifyItemUniqueIdMigration(bool verbose = false);
void ConvertInventoryToNewUniqueId();
void ConvertTraderToNewUniqueId();
void ConvertParcelsToNewUniqueId();
void ConvertInventorySnapshotsToNewUniqueId();
void ConvertSharedbankToNewUniqueId();
void ClearOfflineTradingState();
private:
Mutex Mvarcache;

View File

@ -7211,6 +7211,183 @@ ALTER TABLE `character_buffs`
.sql = R"(
ALTER TABLE `character_pet_buffs`
ADD COLUMN `suppressed` tinyint(1) unsigned NOT NULL DEFAULT 0 AFTER `instrument_mod`;
)",
.content_schema_update = false
},
ManifestEntry{
.version = 9331,
.description = "2026_03_19_inventory_item_unique_id.sql",
.check = "SHOW COLUMNS FROM `inventory` LIKE 'item_unique_id'",
.condition = "empty",
.match = "",
.sql = R"(
ALTER TABLE `inventory`
DROP COLUMN `guid`,
ADD COLUMN `item_unique_id` VARCHAR(16) NULL DEFAULT NULL AFTER `ornament_hero_model`,
ADD UNIQUE INDEX `idx_item_unique_id` (`item_unique_id`);
)",
.content_schema_update = false
},
ManifestEntry{
.version = 9332,
.description = "2026_03_19_sharedbank_item_unique_id.sql",
.check = "SHOW COLUMNS FROM `sharedbank` LIKE 'item_unique_id'",
.condition = "empty",
.match = "",
.sql = R"(
ALTER TABLE `sharedbank`
DROP COLUMN `guid`,
ADD COLUMN `item_unique_id` VARCHAR(16) NULL DEFAULT NULL AFTER `ornament_hero_model`,
ADD UNIQUE INDEX `idx_item_unique_id` (`item_unique_id`);
)",
.content_schema_update = false
},
ManifestEntry{
.version = 9333,
.description = "2026_03_19_character_parcels_item_unique_id.sql",
.check = "SHOW COLUMNS FROM `character_parcels` LIKE 'item_unique_id'",
.condition = "empty",
.match = "",
.sql = R"(
ALTER TABLE `character_parcels`
ADD COLUMN `item_unique_id` VARCHAR(16) NULL DEFAULT NULL AFTER `aug_slot_6`;
)",
.content_schema_update = false
},
ManifestEntry{
.version = 9334,
.description = "2026_03_19_character_parcels_containers_item_unique_id.sql",
.check = "SHOW COLUMNS FROM `character_parcels_containers` LIKE 'item_unique_id'",
.condition = "empty",
.match = "",
.sql = R"(
ALTER TABLE `character_parcels_containers`
ADD COLUMN `item_unique_id` VARCHAR(16) NULL DEFAULT NULL AFTER `item_id`;
)",
.content_schema_update = false
},
ManifestEntry{
.version = 9335,
.description = "2026_03_19_inventory_snapshots_item_unique_id.sql",
.check = "SHOW COLUMNS FROM `inventory_snapshots` LIKE 'item_unique_id'",
.condition = "empty",
.match = "",
.sql = R"(
ALTER TABLE `inventory_snapshots`
DROP PRIMARY KEY,
CHANGE COLUMN `charid` `character_id` INT(11) UNSIGNED NOT NULL DEFAULT '0' AFTER `time_index`,
CHANGE COLUMN `slotid` `slot_id` MEDIUMINT(7) UNSIGNED NOT NULL DEFAULT '0' AFTER `character_id`,
CHANGE COLUMN `itemid` `item_id` INT(11) UNSIGNED NULL DEFAULT '0' AFTER `slot_id`,
CHANGE COLUMN `augslot1` `augment_one` MEDIUMINT(7) UNSIGNED NOT NULL DEFAULT '0' AFTER `color`,
CHANGE COLUMN `augslot2` `augment_two` MEDIUMINT(7) UNSIGNED NOT NULL DEFAULT '0' AFTER `augment_one`,
CHANGE COLUMN `augslot3` `augment_three` MEDIUMINT(7) UNSIGNED NOT NULL DEFAULT '0' AFTER `augment_two`,
CHANGE COLUMN `augslot4` `augment_four` MEDIUMINT(7) UNSIGNED NOT NULL DEFAULT '0' AFTER `augment_three`,
CHANGE COLUMN `augslot5` `augment_five` MEDIUMINT(7) UNSIGNED NULL DEFAULT '0' AFTER `augment_four`,
CHANGE COLUMN `augslot6` `augment_six` MEDIUMINT(7) NOT NULL DEFAULT '0' AFTER `augment_five`,
CHANGE COLUMN `ornamenticon` `ornament_icon` INT(11) UNSIGNED NOT NULL DEFAULT '0' AFTER `custom_data`,
CHANGE COLUMN `ornamentidfile` `ornament_idfile` INT(11) UNSIGNED NOT NULL DEFAULT '0' AFTER `ornament_icon`,
ADD COLUMN `item_unique_id` VARCHAR(16) NULL DEFAULT NULL AFTER `ornament_hero_model`,
DROP COLUMN `guid`;
ALTER TABLE `inventory_snapshots`
ADD PRIMARY KEY (`time_index`, `character_id`, `slot_id`) USING BTREE;
)",
.content_schema_update = false
},
ManifestEntry{
.version = 9336,
.description = "2026_03_19_trader_item_unique_id.sql",
.check = "SHOW COLUMNS FROM `trader` LIKE 'item_unique_id'",
.condition = "empty",
.match = "",
.sql = R"(
ALTER TABLE `trader`
CHANGE COLUMN `char_id` `character_id` INT(11) UNSIGNED NOT NULL DEFAULT '0' AFTER `id`,
ADD COLUMN `item_unique_id` VARCHAR(16) NULL DEFAULT NULL AFTER `item_id`,
CHANGE COLUMN `aug_slot_1` `augment_one` INT(10) UNSIGNED NOT NULL DEFAULT '0' AFTER `item_unique_id`,
CHANGE COLUMN `aug_slot_2` `augment_two` INT(10) UNSIGNED NOT NULL DEFAULT '0' AFTER `augment_one`,
CHANGE COLUMN `aug_slot_3` `augment_three` INT(10) UNSIGNED NOT NULL DEFAULT '0' AFTER `augment_two`,
CHANGE COLUMN `aug_slot_4` `augment_four` INT(10) UNSIGNED NOT NULL DEFAULT '0' AFTER `augment_three`,
CHANGE COLUMN `aug_slot_5` `augment_five` INT(10) UNSIGNED NOT NULL DEFAULT '0' AFTER `augment_four`,
CHANGE COLUMN `aug_slot_6` `augment_six` INT(10) UNSIGNED NOT NULL DEFAULT '0' AFTER `augment_five`,
DROP COLUMN `item_sn`,
DROP INDEX `idx_trader_item_sn`,
DROP INDEX `idx_trader_char`,
ADD INDEX `charid_slotid` (`character_id`, `slot_id`) USING BTREE,
ADD INDEX `idx_trader_char` (`character_id`, `char_zone_id`, `char_zone_instance_id`) USING BTREE,
ADD UNIQUE INDEX `idx_item_unique_id` (`item_unique_id`);
)",
.content_schema_update = false
},
ManifestEntry{
.version = 9337,
.description = "2026_03_19_account_offline_status.sql",
.check = "SHOW COLUMNS FROM `account` LIKE 'offline'",
.condition = "empty",
.match = "",
.sql = R"(
ALTER TABLE `account`
ADD COLUMN `offline` TINYINT(1) UNSIGNED NOT NULL DEFAULT '0' AFTER `time_creation`;
)",
.content_schema_update = false
},
ManifestEntry{
.version = 9338,
.description = "2026_03_19_character_offline_transactions.sql",
.check = "SHOW TABLES LIKE 'character_offline_transactions'",
.condition = "empty",
.match = "",
.sql = R"(
CREATE TABLE IF NOT EXISTS `character_offline_transactions` (
`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
`character_id` INT(10) UNSIGNED NOT NULL DEFAULT '0',
`type` INT(10) UNSIGNED NULL DEFAULT '0',
`item_name` VARCHAR(64) NULL DEFAULT NULL COLLATE 'latin1_swedish_ci',
`quantity` INT(11) NULL DEFAULT '0',
`price` BIGINT(20) UNSIGNED NULL DEFAULT '0',
`buyer_name` VARCHAR(64) NULL DEFAULT NULL COLLATE 'latin1_swedish_ci',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_character_id` (`character_id`)
) COLLATE='latin1_swedish_ci' ENGINE=InnoDB;
)",
.content_schema_update = false
},
ManifestEntry{
.version = 9339,
.description = "2026_03_19_offline_character_sessions.sql",
.check = "SHOW TABLES LIKE 'offline_character_sessions'",
.condition = "empty",
.match = "",
.sql = R"(
CREATE TABLE IF NOT EXISTS `offline_character_sessions` (
`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
`account_id` INT(10) UNSIGNED NOT NULL DEFAULT '0',
`character_id` INT(10) UNSIGNED NOT NULL DEFAULT '0',
`mode` VARCHAR(16) NOT NULL DEFAULT 'trader',
`zone_id` INT(10) UNSIGNED NOT NULL DEFAULT '0',
`instance_id` INT(11) NOT NULL DEFAULT '0',
`entity_id` INT(10) UNSIGNED NOT NULL DEFAULT '0',
`started_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `idx_account_id` (`account_id`),
KEY `idx_character_id` (`character_id`),
KEY `idx_zone_instance` (`zone_id`, `instance_id`)
) COLLATE='latin1_swedish_ci' ENGINE=InnoDB;
)",
.content_schema_update = false
},
ManifestEntry{
.version = 9340,
.description = "2026_03_19_item_unique_id_reservations.sql",
.check = "SHOW TABLES LIKE 'item_unique_id_reservations'",
.condition = "empty",
.match = "",
.sql = R"(
CREATE TABLE IF NOT EXISTS `item_unique_id_reservations` (
`item_unique_id` VARCHAR(16) NOT NULL,
`reserved_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`item_unique_id`)
) COLLATE='latin1_swedish_ci' ENGINE=InnoDB;
)",
.content_schema_update = false
},

View File

@ -58,6 +58,7 @@ namespace DatabaseSchema {
{"character_leadership_abilities", "id"},
{"character_material", "id"},
{"character_memmed_spells", "id"},
{"character_offline_transactions", "character_id"},
{"character_parcels", "char_id"},
{"character_parcels_containers", "id"},
{"character_pet_buffs", "char_id"},
@ -80,13 +81,13 @@ namespace DatabaseSchema {
{"guilds", "id"},
{"instance_list_player", "id"},
{"inventory", "character_id"},
{"inventory_snapshots", "charid"},
{"inventory_snapshots", "character_id"},
{"keyring", "char_id"},
{"mail", "charid"},
{"player_titlesets", "char_id"},
{"quest_globals", "charid"},
{"timers", "char_id"},
{"trader", "char_id"},
{"trader", "character_id"},
{"zone_flags", "charID"}
};
}
@ -133,6 +134,7 @@ namespace DatabaseSchema {
"character_leadership_abilities",
"character_material",
"character_memmed_spells",
"character_offline_transactions",
"character_parcels",
"character_parcels_containers",
"character_pet_buffs",
@ -162,8 +164,10 @@ namespace DatabaseSchema {
"instance_list_player",
"inventory",
"inventory_snapshots",
"item_unique_id_reservations",
"keyring",
"mail",
"offline_character_sessions",
"petitions",
"player_titlesets",
"quest_globals",

View File

@ -45,6 +45,11 @@ namespace AccountStatus {
constexpr uint8 Max = 255;
std::string GetName(uint8 account_status);
struct StatusRecord {
int16 status;
uint32 offline;
};
}
static std::map<uint8, std::string> account_status_names = {

View File

@ -71,6 +71,8 @@ N(OP_BuyerItems),
N(OP_CameraEffect),
N(OP_Camp),
N(OP_CancelSneakHide),
N(OP_CancelOfflineTrader),
N(OP_CancelOfflineTraderResponse),
N(OP_CancelTask),
N(OP_CancelTrade),
N(OP_CashReward),
@ -381,6 +383,7 @@ N(OP_MultiLineMsg),
N(OP_NewSpawn),
N(OP_NewTitlesAvailable),
N(OP_NewZone),
N(OP_Offline),
N(OP_OnLevelMessage),
N(OP_OpenContainer),
N(OP_OpenDiscordMerchant),

View File

@ -48,6 +48,7 @@ static const EQ::constants::LookupEntry constants_static_lookup_entries[EQ::vers
ClientUnknown::constants::EXPANSIONS_MASK,
ClientUnknown::INULL,
ClientUnknown::INULL,
ClientUnknown::INULL,
ClientUnknown::INULL
),
/*[ClientVersion::Client62] =*/
@ -57,6 +58,7 @@ static const EQ::constants::LookupEntry constants_static_lookup_entries[EQ::vers
Client62::constants::EXPANSIONS_MASK,
Client62::INULL,
Client62::INULL,
Client62::INULL,
Client62::INULL
),
/*[ClientVersion::Titanium] =*/
@ -66,6 +68,7 @@ static const EQ::constants::LookupEntry constants_static_lookup_entries[EQ::vers
Titanium::constants::EXPANSIONS_MASK,
Titanium::constants::CHARACTER_CREATION_LIMIT,
Titanium::constants::SAY_LINK_BODY_SIZE,
Titanium::INULL,
Titanium::INULL
),
/*[ClientVersion::SoF] =*/
@ -75,6 +78,7 @@ static const EQ::constants::LookupEntry constants_static_lookup_entries[EQ::vers
SoF::constants::EXPANSIONS_MASK,
SoF::constants::CHARACTER_CREATION_LIMIT,
SoF::constants::SAY_LINK_BODY_SIZE,
SoF::INULL,
SoF::INULL
),
/*[ClientVersion::SoD] =*/
@ -84,6 +88,7 @@ static const EQ::constants::LookupEntry constants_static_lookup_entries[EQ::vers
SoD::constants::EXPANSIONS_MASK,
SoD::constants::CHARACTER_CREATION_LIMIT,
SoD::constants::SAY_LINK_BODY_SIZE,
SoD::INULL,
SoD::INULL
),
/*[ClientVersion::UF] =*/
@ -93,6 +98,7 @@ static const EQ::constants::LookupEntry constants_static_lookup_entries[EQ::vers
UF::constants::EXPANSIONS_MASK,
UF::constants::CHARACTER_CREATION_LIMIT,
UF::constants::SAY_LINK_BODY_SIZE,
UF::INULL,
UF::INULL
),
/*[ClientVersion::RoF] =*/
@ -102,6 +108,7 @@ static const EQ::constants::LookupEntry constants_static_lookup_entries[EQ::vers
RoF::constants::EXPANSIONS_MASK,
RoF::constants::CHARACTER_CREATION_LIMIT,
RoF::constants::SAY_LINK_BODY_SIZE,
RoF::INULL,
RoF::INULL
),
/*[ClientVersion::RoF2] =*/
@ -111,7 +118,8 @@ static const EQ::constants::LookupEntry constants_static_lookup_entries[EQ::vers
RoF2::constants::EXPANSIONS_MASK,
RoF2::constants::CHARACTER_CREATION_LIMIT,
RoF2::constants::SAY_LINK_BODY_SIZE,
RoF2::constants::MAX_BAZAAR_TRADERS
RoF2::constants::MAX_BAZAAR_TRADERS,
RoF2::constants::MAX_BAZAAR_TRANSACTION
)
};

View File

@ -42,6 +42,7 @@ namespace EQ
int16 CharacterCreationLimit;
size_t SayLinkBodySize;
uint32 BazaarTraderLimit;
uint64 BazaarMaxTransaction;
LookupEntry(const LookupEntry *lookup_entry) { }
LookupEntry(
@ -50,14 +51,16 @@ namespace EQ
uint32 ExpansionsMask,
int16 CharacterCreationLimit,
size_t SayLinkBodySize,
uint32 BazaarTraderLimit
uint32 BazaarTraderLimit,
uint64 BazaarMaxTransaction
) :
Expansion(Expansion),
ExpansionBit(ExpansionBit),
ExpansionsMask(ExpansionsMask),
CharacterCreationLimit(CharacterCreationLimit),
SayLinkBodySize(SayLinkBodySize),
BazaarTraderLimit(BazaarTraderLimit)
BazaarTraderLimit(BazaarTraderLimit),
BazaarMaxTransaction(BazaarMaxTransaction)
{ }
};

View File

@ -327,6 +327,7 @@ union
bool buyer;
bool untargetable;
uint32 npc_tint_id;
bool offline;
};
struct PlayerState_Struct {
@ -3094,7 +3095,7 @@ struct BazaarSearchCriteria_Struct {
struct BazaarInspect_Struct {
uint32 action;
char player_name[64];
uint32 serial_number;
char item_unique_id[17];
uint32 item_id;
uint32 trader_id;
};
@ -3730,6 +3731,36 @@ struct Trader_Struct {
/*648*/ uint32 item_cost[EQ::invtype::BAZAAR_SIZE];
};
struct TraderItems_Struct {
std::string item_unique_id;
uint32 item_id;
uint64 item_cost;
template<class Archive>
void serialize(Archive &archive)
{
archive(
CEREAL_NVP(item_unique_id),
CEREAL_NVP(item_id),
CEREAL_NVP(item_cost)
);
}
};
struct TraderClientMessaging_Struct {
/*000*/ uint32 action;
/*008*/ std::vector<TraderItems_Struct> items;
template<class Archive>
void serialize(Archive &archive)
{
archive(
CEREAL_NVP(action),
CEREAL_NVP(items)
);
}
};
struct ClickTrader_Struct {
/*000*/ uint32 action;
/*004*/ uint32 unknown_004;
@ -3743,6 +3774,52 @@ struct GetItems_Struct{
int32 charges[EQ::invtype::BAZAAR_SIZE];
};
struct Trader2_Struct {
uint32 action;
uint32 unknown_004;
uint64 items[EQ::invtype::BAZAAR_SIZE];
uint32 item_cost[EQ::invtype::BAZAAR_SIZE];
std::string serial_number[EQ::invtype::BAZAAR_SIZE];
};
struct BazaarTraderDetails {
uint64 item_id;
std::string unique_id;
uint64 cost;
uint64 serial_number; // backwards compatibility. Not used for RoF2 as of March 2025
template<class Archive>
void serialize(Archive &archive)
{
archive(
CEREAL_NVP(item_id),
CEREAL_NVP(unique_id),
CEREAL_NVP(cost),
CEREAL_NVP(serial_number)
);
}
};
struct ClickTraderNew_Struct {
uint32 action;
std::vector<BazaarTraderDetails> items;
template<class Archive>
void serialize(Archive &archive)
{
archive(
CEREAL_NVP(action),
CEREAL_NVP(items)
);
}
};
struct GetBazaarItems_Struct {
uint64 items[EQ::invtype::BAZAAR_SIZE];
std::string serial_number[EQ::invtype::BAZAAR_SIZE];
uint32 charges[EQ::invtype::BAZAAR_SIZE];
};
struct BecomeTrader_Struct {
uint32 action;
uint16 zone_id;
@ -3775,7 +3852,7 @@ struct TraderBuy_Struct {
/*084*/ char seller_name[64];
/*148*/ char unknown_148[32];
/*180*/ char item_name[64];
/*244*/ char serial_number[17];
/*244*/ char item_unique_id[17];
/*261*/ char unknown_261[3];
/*264*/ uint32 item_id;
/*268*/ uint32 price;
@ -3794,12 +3871,12 @@ struct TraderItemUpdate_Struct{
};
struct TraderPriceUpdate_Struct {
/*000*/ uint32 Action;
/*004*/ uint32 SubAction;
/*008*/ int32 SerialNumber;
/*012*/ uint32 Unknown012;
/*016*/ uint32 NewPrice;
/*020*/ uint32 Unknown016;
/*000*/ uint32 action;
/*002*/ uint32 sub_action;
/*004*/ char item_unique_id[17];
/*021*/ char unknown_021[3];
/*024*/ uint32 unknown_024;
/*028*/ uint32 new_price;
};
struct MoneyUpdate_Struct{
@ -3814,6 +3891,7 @@ struct TraderDelItem_Struct{
uint32 trader_id;
uint32 item_id;
uint32 unknown_012;
char item_unique_id[17];
};
struct TraderClick_Struct{
@ -3837,12 +3915,12 @@ struct SimpleMessage_Struct{
};
struct GuildMemberUpdate_Struct {
/*00*/ uint32 GuildID;
/*04*/ char MemberName[64];
/*68*/ uint16 ZoneID;
/*70*/ uint16 InstanceID; //speculated
/*72*/ uint32 LastSeen; //unix timestamp
/*76*/
/*00*/ uint32 GuildID;
/*04*/ char MemberName[64];
/*68*/ uint16 ZoneID;
/*72*/ uint16 InstanceID; //speculated
/*76*/ uint32 LastSeen; //unix timestamp
/*80*/ uint32 offline_mode;
};
struct GuildMemberLevelUpdate_Struct {
@ -3865,6 +3943,7 @@ struct Internal_GuildMemberEntry_Struct {
uint16 zoneinstance; //network byte order
uint16 zone_id; //network byte order
uint32 online;
uint32 offline_mode;
};
struct Internal_GuildMembers_Struct { //just for display purposes, this is not actually used in the message encoding.
@ -6419,7 +6498,7 @@ struct BazaarSearchResultsFromDB_Struct {
uint32 count;
uint32 trader_id;
uint32 item_id;
uint32 serial_number;
std::string item_unique_id;
uint32 charges;
uint32 cost;
uint32 slot_id;
@ -6431,7 +6510,6 @@ struct BazaarSearchResultsFromDB_Struct {
uint32 item_stat;
bool stackable;
std::string item_name;
std::string serial_number_RoF;
std::string trader_name;
template<class Archive>
@ -6441,7 +6519,7 @@ struct BazaarSearchResultsFromDB_Struct {
CEREAL_NVP(count),
CEREAL_NVP(trader_id),
CEREAL_NVP(item_id),
CEREAL_NVP(serial_number),
CEREAL_NVP(item_unique_id),
CEREAL_NVP(charges),
CEREAL_NVP(cost),
CEREAL_NVP(slot_id),
@ -6453,7 +6531,6 @@ struct BazaarSearchResultsFromDB_Struct {
CEREAL_NVP(item_stat),
CEREAL_NVP(stackable),
CEREAL_NVP(item_name),
CEREAL_NVP(serial_number_RoF),
CEREAL_NVP(trader_name)
);
}

View File

@ -1099,6 +1099,7 @@ namespace PlayerEvent {
int32 charges;
uint64 total_cost;
uint64 player_money_balance;
bool offline_purchase;
// cereal
template <class Archive>
@ -1154,7 +1155,8 @@ namespace PlayerEvent {
CEREAL_NVP(quantity),
CEREAL_NVP(charges),
CEREAL_NVP(total_cost),
CEREAL_NVP(player_money_balance)
CEREAL_NVP(player_money_balance),
CEREAL_NVP(offline_purchase)
);
}
};
@ -1175,7 +1177,9 @@ namespace PlayerEvent {
int32 charges;
uint64 total_cost;
uint64 player_money_balance;
bool offline_purchase;
// cereal
template <class Archive>
void serialize(Archive& ar)
{
@ -1229,7 +1233,8 @@ namespace PlayerEvent {
CEREAL_NVP(quantity),
CEREAL_NVP(charges),
CEREAL_NVP(total_cost),
CEREAL_NVP(player_money_balance)
CEREAL_NVP(player_money_balance),
CEREAL_NVP(offline_purchase)
);
}
};
@ -1427,6 +1432,7 @@ namespace PlayerEvent {
struct ParcelRetrieve {
uint32 item_id;
std::string item_unique_id;
uint32 augment_1_id;
uint32 augment_2_id;
uint32 augment_3_id;
@ -1472,6 +1478,7 @@ namespace PlayerEvent {
{
ar(
CEREAL_NVP(item_id),
CEREAL_NVP(item_unique_id),
CEREAL_NVP(augment_1_id),
CEREAL_NVP(augment_2_id),
CEREAL_NVP(augment_3_id),
@ -1487,6 +1494,7 @@ namespace PlayerEvent {
struct ParcelSend {
uint32 item_id;
std::string item_unique_id;
uint32 augment_1_id;
uint32 augment_2_id;
uint32 augment_3_id;
@ -1536,6 +1544,7 @@ namespace PlayerEvent {
{
ar(
CEREAL_NVP(item_id),
CEREAL_NVP(item_unique_id),
CEREAL_NVP(augment_1_id),
CEREAL_NVP(augment_2_id),
CEREAL_NVP(augment_3_id),
@ -1554,6 +1563,7 @@ namespace PlayerEvent {
struct ParcelDelete {
uint32 char_id;
uint32 item_id;
std::string item_unique_id;
uint32 augment_1_id;
uint32 augment_2_id;
uint32 augment_3_id;
@ -1602,6 +1612,7 @@ namespace PlayerEvent {
{
ar(
CEREAL_NVP(item_id),
CEREAL_NVP(item_unique_id),
CEREAL_NVP(augment_1_id),
CEREAL_NVP(augment_2_id),
CEREAL_NVP(augment_3_id),

View File

@ -842,8 +842,10 @@ bool BaseGuildManager::QueryWithLogging(std::string query, const char *errmsg)
#define GuildMemberBaseQuery \
"SELECT c.`id`, c.`name`, c.`class`, c.`level`, c.`last_login`, c.`zone_id`," \
" g.`guild_id`, g.`rank`, g.`tribute_enable`, g.`total_tribute`, g.`last_tribute`," \
" g.`banker`, g.`public_note`, g.`alt`, g.`online` " \
" FROM `character_data` AS c LEFT JOIN `guild_members` AS g ON c.`id` = g.`char_id` "
" g.`banker`, g.`public_note`, g.`alt`, g.`online`, a.`offline` " \
" FROM `character_data` AS c LEFT JOIN `guild_members` AS g ON c.`id` = g.`char_id` " \
" LEFT JOIN `account` AS a ON a.`id` = c.`account_id` "
static void ProcessGuildMember(MySQLRequestRow row, CharGuildInfo &into)
{
//fields from `characer_`
@ -864,6 +866,7 @@ static void ProcessGuildMember(MySQLRequestRow row, CharGuildInfo &into)
into.public_note = row[12] ? row[12] : "";
into.alt = row[13] ? (row[13][0] == '0' ? false : true) : false;
into.online = row[14] ? (row[14][0] == '0' ? false : true) : false;
into.offline_mode = row[15] ? (row[15][0] == '0' ? false : true) : false;
//a little sanity checking/cleanup
if (into.guild_id == 0) {

View File

@ -55,16 +55,17 @@ class CharGuildInfo
uint32 time_last_on;
uint32 zone_id;
//fields from `guild_members`
uint32 guild_id;
uint8 rank;
bool tribute_enable;
uint32 total_tribute;
uint32 last_tribute; //timestamp
bool banker;
bool alt;
std::string public_note;
bool online;
// fields from `guild_members`
uint32 guild_id;
uint8 rank;
bool tribute_enable;
uint32 total_tribute;
uint32 last_tribute; // timestamp
bool banker;
bool alt;
std::string public_note;
bool online;
bool offline_mode;
};
//this object holds guild functionality shared between world and zone.

View File

@ -249,6 +249,10 @@ int16 EQ::InventoryProfile::PutItem(int16 slot_id, const ItemInstance& inst)
}
int16 EQ::InventoryProfile::PushCursor(const ItemInstance &inst) {
if (inst.GetUniqueID().empty()) {
inst.CreateUniqueID();
}
m_cursor.push(inst.Clone());
return invslot::slotCursor;
}

View File

@ -58,8 +58,8 @@ static inline int32 GetNextItemInstSerialNumber()
//
// class EQ::ItemInstance
//
EQ::ItemInstance::ItemInstance(const ItemData* item, int16 charges) {
EQ::ItemInstance::ItemInstance(const ItemData* item, int16 charges)
{
if (item) {
m_item = new ItemData(*item);
}
@ -77,8 +77,31 @@ EQ::ItemInstance::ItemInstance(const ItemData* item, int16 charges) {
m_SerialNumber = GetNextItemInstSerialNumber();
}
EQ::ItemInstance::ItemInstance(SharedDatabase *db, uint32 item_id, int16 charges) {
EQ::ItemInstance::ItemInstance(const ItemData *item, const std::string &item_unique_id, int16 charges)
{
if (item) {
m_item = new ItemData(*item);
}
m_charges = charges;
if (m_item && m_item->IsClassCommon()) {
m_color = m_item->Color;
}
if (m_item && IsEvolving()) {
SetTimer("evolve", RuleI(EvolvingItems, DelayUponEquipping));
}
m_SerialNumber = GetNextItemInstSerialNumber();
if (m_item && !item_unique_id.empty()) {
SetUniqueID(item_unique_id);
}
}
EQ::ItemInstance::ItemInstance(SharedDatabase *db, uint32 item_id, int16 charges)
{
m_item = db->GetItem(item_id);
if (m_item) {
@ -142,9 +165,15 @@ EQ::ItemInstance::ItemInstance(const ItemInstance& copy)
m_custom_data[iter->first] = iter->second;
}
m_SerialNumber = copy.m_SerialNumber;
m_custom_data = copy.m_custom_data;
m_timers = copy.m_timers;
m_SerialNumber = copy.m_SerialNumber;
m_custom_data = copy.m_custom_data;
m_timers = copy.m_timers;
if (copy.GetUniqueID().empty()) {
LogInfo("Creating unique item ID as part of clone process for item id {}", copy.GetID());
copy.CreateUniqueID();
}
m_unique_id = copy.m_unique_id;
m_exp = copy.m_exp;
m_evolveLvl = copy.m_evolveLvl;
@ -1995,3 +2024,11 @@ void EQ::ItemInstance::SetEvolveEquipped(const bool in) const
GetTimers().at("evolve").Disable();
}
std::string EQ::ItemInstance::GenerateUniqueID()
{
std::string unique_hash = UniqueHashGenerator::generate();
LogInventoryDetail("Generated an item serial number {}", unique_hash);
return unique_hash;
}

View File

@ -23,6 +23,10 @@
#pragma once
#include <array>
#include <chrono>
#include <random>
#include "common/bodytypes.h"
#include "common/deity.h"
#include "common/eq_constants.h"
@ -37,7 +41,6 @@
class ItemParse; // Parses item packets
class EvolveInfo; // Stores information about an evolving item family
// Specifies usage type for item inside EQ::ItemInstance
enum ItemInstTypes
{
@ -78,6 +81,8 @@ namespace EQ
ItemInstance(SharedDatabase *db, uint32 item_id, int16 charges = 0);
ItemInstance(const ItemData *item, const std::string &item_unique_id, int16 charges = 0);
ItemInstance(ItemInstTypes use_type);
ItemInstance(const ItemInstance& copy);
@ -161,6 +166,14 @@ namespace EQ
int16 GetCharges() const { return m_charges; }
void SetCharges(int16 charges) { m_charges = charges; }
int16 GetQuantityFromCharges() const
{
if (GetCharges() > 0 || IsStackable() || GetItem()->MaxCharges > 0) {
return GetCharges();
}
return 1;
}
uint32 GetPrice() const { return m_price; }
void SetPrice(uint32 price) { m_price = price; }
@ -226,8 +239,11 @@ namespace EQ
std::string Serialize(int16 slot_id) const { InternalSerializedItem_Struct s; s.slot_id = slot_id; s.inst = (const void*)this; std::string ser; ser.assign((char*)&s, sizeof(InternalSerializedItem_Struct)); return ser; }
void Serialize(OutBuffer& ob, int16 slot_id) const { InternalSerializedItem_Struct isi; isi.slot_id = slot_id; isi.inst = (const void*)this; ob.write((const char*)&isi, sizeof(isi)); }
inline int32 GetSerialNumber() const { return m_SerialNumber; }
inline void SetSerialNumber(int32 id) { m_SerialNumber = id; }
int32 GetSerialNumber() const { return m_SerialNumber; }
void SetSerialNumber(int32 id) { m_SerialNumber = id; }
const std::string &GetUniqueID() const { return m_unique_id; }
void SetUniqueID(std::string sn) { m_unique_id = std::move(sn); }
void CreateUniqueID() const { m_unique_id = GenerateUniqueID(); }
std::map<std::string, ::Timer>& GetTimers() const { return m_timers; }
void SetTimer(std::string name, uint32 time);
@ -344,28 +360,30 @@ namespace EQ
std::map<uint8, ItemInstance*>::const_iterator _cbegin() { return m_contents.cbegin(); }
std::map<uint8, ItemInstance*>::const_iterator _cend() { return m_contents.cend(); }
void _PutItem(uint8 index, ItemInstance* inst) { m_contents[index] = inst; }
void _PutItem(uint8 index, ItemInstance *inst) { m_contents[index] = inst; }
static std::string GenerateUniqueID();
ItemInstTypes m_use_type{ItemInstNormal};// Usage type for item
const ItemData * m_item{nullptr}; // Ptr to item data
int16 m_charges{0}; // # of charges for chargeable items
uint32 m_price{0}; // Bazaar /trader price
uint32 m_color{0};
uint32 m_merchantslot{0};
int16 m_currentslot{0};
bool m_attuned{false};
int32 m_merchantcount{1};//number avaliable on the merchant, -1=unlimited
int32 m_SerialNumber{0}; // Unique identifier for this instance of an item. Needed for Bazaar.
uint32 m_exp{0};
int8 m_evolveLvl{0};
ItemData * m_scaledItem{nullptr};
bool m_scaling{false};
uint32 m_ornamenticon{0};
uint32 m_ornamentidfile{0};
uint32 m_new_id_file{0};
uint32 m_ornament_hero_model{0};
uint32 m_recast_timestamp{0};
int m_task_delivered_count{0};
ItemInstTypes m_use_type{ ItemInstNormal }; // Usage type for item
const ItemData *m_item{ nullptr }; // Ptr to item data
int16 m_charges{ 0 }; // # of charges for chargeable items
uint32 m_price{ 0 }; // Bazaar /trader price
uint32 m_color{ 0 };
uint32 m_merchantslot{ 0 };
int16 m_currentslot{ 0 };
bool m_attuned{ false };
int32 m_merchantcount{ 1 }; // number avaliable on the merchant, -1=unlimited
int32 m_SerialNumber{ 0 }; // Unique identifier for this instance of an item. Needed for Bazaar.
mutable std::string m_unique_id{}; // unique serial number across all zones/world TESTING March 2025
uint32 m_exp{ 0 };
int8 m_evolveLvl{ 0 };
ItemData *m_scaledItem{ nullptr };
bool m_scaling{ false };
uint32 m_ornamenticon{ 0 };
uint32 m_ornamentidfile{ 0 };
uint32 m_new_id_file{ 0 };
uint32 m_ornament_hero_model{ 0 };
uint32 m_recast_timestamp{ 0 };
int m_task_delivered_count{ 0 };
mutable CharacterEvolvingItemsRepository::CharacterEvolvingItems m_evolving_details{};
// Items inside of this item (augs or contents) {};
@ -373,4 +391,48 @@ namespace EQ
std::map<std::string, std::string> m_custom_data {};
mutable std::map<std::string, ::Timer> m_timers {};
};
class UniqueHashGenerator
{
private:
static constexpr char ALPHANUM[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
static constexpr size_t ALPHANUM_SIZE = sizeof(ALPHANUM) - 1;
static std::mt19937_64 &GetRng()
{
thread_local std::mt19937_64 rng = []() {
std::random_device rd;
std::array<uint32_t, 8> entropy{};
for (auto &value : entropy) {
value = rd();
}
auto now = static_cast<uint64_t>(
std::chrono::steady_clock::now().time_since_epoch().count()
);
entropy[0] ^= static_cast<uint32_t>(now);
entropy[1] ^= static_cast<uint32_t>(now >> 32);
std::seed_seq seed(entropy.begin(), entropy.end());
return std::mt19937_64(seed);
}();
return rng;
}
public:
static std::string generate()
{
auto &rng = GetRng();
std::uniform_int_distribution<size_t> dist(0, ALPHANUM_SIZE - 1);
std::array<char, 16> result;
for (int i = 0; i < 16; ++i) {
result[i] = ALPHANUM[dist(rng)];
}
return std::string(result.begin(), result.end());
}
};
}

View File

@ -437,7 +437,6 @@ namespace RoF2
break;
}
default: {
LogTradingDetail("Unhandled action <red>[{}]", sub_action);
dest->FastQueuePacket(&in);
}
}
@ -478,7 +477,7 @@ namespace RoF2
for (auto i: results) {
VARSTRUCT_ENCODE_TYPE(uint32, bufptr, i.trader_id); //trader ID
VARSTRUCT_ENCODE_STRING(bufptr, i.serial_number_RoF.c_str()); //serial
VARSTRUCT_ENCODE_STRING(bufptr, i.item_unique_id.c_str()); //serial
VARSTRUCT_ENCODE_TYPE(uint32, bufptr, i.cost); //cost
VARSTRUCT_ENCODE_TYPE(uint32, bufptr, i.stackable ? i.charges : i.count); //quantity
VARSTRUCT_ENCODE_TYPE(uint32, bufptr, i.item_id); //ID
@ -531,7 +530,6 @@ namespace RoF2
break;
}
default: {
LogTrading("(RoF2) Unhandled action <red>[{}]", action);
dest->FastQueuePacket(&in, ack_req);
}
}
@ -620,10 +618,6 @@ namespace RoF2
break;
}
default: {
LogTrading(
"(RoF2) Unhandled action <red>[{}]",
in->action
);
dest->QueuePacket(inapp);
}
}
@ -755,7 +749,7 @@ namespace RoF2
ar(bl);
//packet size
auto packet_size = bl.item_name.length() + 1 + 34;
uint32 packet_size = bl.item_name.length() + 1 + 34;
for (auto const &b: bl.trade_items) {
packet_size += b.item_name.length() + 1;
packet_size += 12;
@ -1843,7 +1837,7 @@ namespace RoF2
e->zoneinstance = 0;
e->zone_id = htons(emu_e->zone_id);
e->unknown_one2 = htonl(1);
e->unknown04 = 0;
e->offline_mode = htonl(emu_e->offline_mode);
#undef SlideStructString
#undef PutFieldN
@ -1860,14 +1854,12 @@ namespace RoF2
{
SETUP_DIRECT_ENCODE(GuildMemberUpdate_Struct, structs::GuildMemberUpdate_Struct);
OUT(GuildID);
memcpy(eq->MemberName, emu->MemberName, sizeof(eq->MemberName));
//OUT(ZoneID);
//OUT(InstanceID);
eq->InstanceID = emu->InstanceID;
eq->ZoneID = emu->ZoneID;
OUT(LastSeen);
eq->Unknown76 = 0;
eq->guild_id = emu->GuildID;
eq->last_seen = emu->LastSeen;
eq->instance_id = emu->InstanceID;
eq->zone_id = emu->ZoneID;
eq->offline_mode = emu->offline_mode;
memcpy(eq->member_name, emu->MemberName, sizeof(eq->member_name));
FINISH_ENCODE();
}
@ -4113,26 +4105,41 @@ namespace RoF2
break;
}
case ListTraderItems: {
ENCODE_LENGTH_EXACT(Trader_Struct);
SETUP_DIRECT_ENCODE(Trader_Struct, structs::ClickTrader_Struct);
LogTrading("(RoF2) action <green>[{}]", action);
eq->action = structs::RoF2BazaarTraderBuyerActions::ListTraderItems;
std::transform(
std::begin(emu->items),
std::end(emu->items),
std::begin(eq->items),
[&](const uint32 x) {
return x;
}
);
std::copy_n(
std::begin(emu->item_cost),
EQ::invtype::BAZAAR_SIZE,
std::begin(eq->item_cost)
);
EQApplicationPacket *in = *p;
*p = nullptr;
FINISH_ENCODE();
TraderClientMessaging_Struct tcm{};
EQ::Util::MemoryStreamReader ss(reinterpret_cast<char *>(in->pBuffer), in->size);
cereal::BinaryInputArchive ar(ss);
{
ar(tcm);
}
auto buffer = new char[4404]{}; // 4404 is the fixed size of the packet for 200 item limit of RoF2
auto pos = buffer;
auto pos_unique_id = buffer + 4;
auto pos_cost = buffer + 3604;
VARSTRUCT_ENCODE_TYPE(uint32, pos, structs::RoF2BazaarTraderBuyerActions::ListTraderItems);
for (auto const &t: tcm.items) {
strn0cpy(pos_unique_id, t.item_unique_id.data(), t.item_unique_id.length() + 1);
*(uint32 *) pos_cost = t.item_cost;
pos_unique_id += 18;
pos_cost += 4;
}
for (int i = tcm.items.size(); i < EQ::invtype::BAZAAR_SIZE; i++) {
strn0cpy(pos_unique_id, "0000000000000000", 18);
pos_unique_id += 18;
}
safe_delete_array(in->pBuffer);
in->pBuffer = reinterpret_cast<unsigned char *>(buffer);
in->size = 4404;
dest->FastQueuePacket(&in);
break;
}
case TraderAck2: {
@ -4145,7 +4152,7 @@ namespace RoF2
}
case PriceUpdate: {
SETUP_DIRECT_ENCODE(TraderPriceUpdate_Struct, structs::TraderPriceUpdate_Struct);
switch (emu->SubAction) {
switch (emu->sub_action) {
case BazaarPriceChange_AddItem: {
auto outapp = std::make_unique<EQApplicationPacket>(
OP_Trader,
@ -4153,7 +4160,7 @@ namespace RoF2
);
auto data = (structs::TraderStatus_Struct *) outapp->pBuffer;
data->action = emu->Action;
data->action = emu->action;
data->sub_action = BazaarPriceChange_AddItem;
LogTrading(
"(RoF2) PriceUpdate action <green>[{}] AddItem subaction <yellow>[{}]",
@ -4171,7 +4178,7 @@ namespace RoF2
);
auto data = (structs::TraderStatus_Struct *) outapp->pBuffer;
data->action = emu->Action;
data->action = emu->action;
data->sub_action = BazaarPriceChange_RemoveItem;
LogTrading(
"(RoF2) PriceUpdate action <green>[{}] RemoveItem subaction <yellow>[{}]",
@ -4189,7 +4196,7 @@ namespace RoF2
);
auto data = (structs::TraderStatus_Struct *) outapp->pBuffer;
data->action = emu->Action;
data->action = emu->action;
data->sub_action = BazaarPriceChange_UpdatePrice;
LogTrading(
"(RoF2) PriceUpdate action <green>[{}] UpdatePrice subaction <yellow>[{}]",
@ -4214,7 +4221,7 @@ namespace RoF2
"(RoF2) BuyTraderItem action <green>[{}] item_id <green>[{}] item_sn <green>[{}] buyer <green>[{}]",
action,
eq->item_id,
eq->serial_number,
eq->item_unique_id,
eq->buyer_name
);
dest->FastQueuePacket(&in);
@ -4253,7 +4260,7 @@ namespace RoF2
OUT_str(buyer_name);
OUT_str(seller_name);
OUT_str(item_name);
OUT_str(serial_number);
OUT_str(item_unique_id);
FINISH_ENCODE();
}
@ -4263,15 +4270,13 @@ namespace RoF2
ENCODE_LENGTH_EXACT(TraderDelItem_Struct);
SETUP_DIRECT_ENCODE(TraderDelItem_Struct, structs::TraderDelItem_Struct);
LogTrading(
"(RoF2) trader_id <green>[{}] item_id <green>[{}]",
"(RoF2) trader_id <green>[{}] item_unique_id <green>[{}]",
emu->trader_id,
emu->item_id
emu->item_unique_id
);
eq->TraderID = emu->trader_id;
auto serial = fmt::format("{:016}\n", emu->item_id);
strn0cpy(eq->SerialNumber, serial.c_str(), sizeof(eq->SerialNumber));
LogTrading("(RoF2) TraderID <green>[{}], SerialNumber: <green>[{}]", emu->trader_id, emu->item_id);
eq->trader_id = emu->trader_id;
strn0cpy(eq->item_unique_id, emu->item_unique_id, sizeof(eq->item_unique_id));
FINISH_ENCODE();
}
@ -4318,13 +4323,12 @@ namespace RoF2
OUT_str(buyer_name);
OUT_str(seller_name);
OUT_str(item_name);
OUT_str(serial_number);
OUT_str(item_unique_id);
FINISH_ENCODE();
break;
}
default: {
LogTrading("(RoF2) Unhandled action <red>[{}]", action);
EQApplicationPacket *in = *p;
*p = nullptr;
@ -4460,6 +4464,7 @@ namespace RoF2
*p = nullptr;
char *InBuffer = (char *)in->pBuffer;
std::vector<uint32> p_ids { 0x430, 0x420 };
WhoAllReturnStruct *wars = (WhoAllReturnStruct*)InBuffer;
@ -4485,8 +4490,9 @@ namespace RoF2
x = VARSTRUCT_DECODE_TYPE(uint32, InBuffer);
VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, x);
InBuffer += 4;
VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, 0);
x = VARSTRUCT_DECODE_TYPE(uint32, InBuffer);
VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, std::ranges::find(p_ids.begin(), p_ids.end(), x) == p_ids.end() ? 0 : x);
VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, 0xffffffff);
char Name[64];
@ -4722,6 +4728,10 @@ namespace RoF2
OtherData = OtherData | 0x01;
}
if (emu->offline) {
OtherData = OtherData | 0x02;
}
VARSTRUCT_ENCODE_TYPE(uint8, Buffer, OtherData);
// float EmitterScalingRadius
@ -5092,7 +5102,6 @@ namespace RoF2
}
default: {
auto emu = (BuyerGeneric_Struct *) __packet->pBuffer;
LogTradingDetail("(RoF2) Pass thru OP_Barter packet action <red>[{}]", emu->action);
}
}
}
@ -6150,21 +6159,35 @@ namespace RoF2
switch (action) {
case structs::RoF2BazaarTraderBuyerActions::BeginTraderMode: {
DECODE_LENGTH_EXACT(structs::BeginTrader_Struct);
SETUP_DIRECT_DECODE(ClickTrader_Struct, structs::BeginTrader_Struct);
LogTrading("(RoF2) BeginTraderMode action <green>[{}]", action);
emu->action = TraderOn;
std::copy_n(eq->item_cost, RoF2::invtype::BAZAAR_SIZE, emu->item_cost);
std::transform(
std::begin(eq->items),
std::end(eq->items),
std::begin(emu->serial_number),
[&](const structs::TraderItemSerial_Struct x) {
return Strings::ToUnsignedBigInt(x.serial_number,0);
unsigned char *eq_buffer = __packet->pBuffer;
auto eq = (RoF2::structs::BeginTrader_Struct *) eq_buffer;
ClickTraderNew_Struct out{};
out.action = TraderOn;
for (auto i = 0; i < RoF2::invtype::BAZAAR_SIZE; i++) {
if (eq->item_cost[i] == 0) {
continue;
}
);
FINISH_DIRECT_DECODE();
BazaarTraderDetails btd{};
btd.unique_id = eq->item_unique_ids[i].item_unique_id;
btd.cost = eq->item_cost[i];
out.items.push_back(btd);
}
std::stringstream ss{};
cereal::BinaryOutputArchive ar(ss);
{
ar(out);
}
__packet->size = static_cast<uint32>(ss.str().length());
__packet->pBuffer = new unsigned char[__packet->size]{};
memcpy(__packet->pBuffer, ss.str().data(), __packet->size);
safe_delete_array(eq_buffer);
LogTrading("(RoF2) BeginTraderMode action <green>[{}]", action);
break;
}
case structs::RoF2BazaarTraderBuyerActions::EndTraderMode: {
@ -6187,18 +6210,14 @@ namespace RoF2
SETUP_DIRECT_DECODE(TraderPriceUpdate_Struct, structs::TraderPriceUpdate_Struct);
LogTrading("(RoF2) PriceUpdate action <green>[{}]", action);
emu->Action = PriceUpdate;
emu->SerialNumber = Strings::ToUnsignedBigInt(eq->serial_number, 0);
if (emu->SerialNumber == 0) {
LogTrading("(RoF2) Price change with invalid serial number <red>[{}]", eq->serial_number);
}
emu->NewPrice = eq->new_price;
emu->action = PriceUpdate;
strn0cpy(emu->item_unique_id, eq->item_unique_id, sizeof(emu->item_unique_id));
emu->new_price = eq->new_price;
FINISH_DIRECT_DECODE();
break;
}
default: {
LogTrading("(RoF2) Unhandled action <red>[{}]", action);
}
}
}
@ -6282,21 +6301,12 @@ namespace RoF2
IN(item_id);
IN(trader_id);
emu->action = BazaarInspect;
emu->serial_number = Strings::ToUnsignedInt(eq->serial_number, 0);
if (emu->serial_number == 0) {
LogTrading(
"(RoF2) trader_id = <green>[{}] requested a BazaarInspect with an invalid serial number of <red>[{}]",
eq->trader_id,
eq->serial_number
);
FINISH_DIRECT_DECODE();
return;
}
strn0cpy(emu->item_unique_id, eq->item_unique_id, sizeof(emu->item_unique_id));
LogTrading("(RoF2) BazaarInspect action <green>[{}] item_id <green>[{}] serial_number <green>[{}]",
action,
eq->item_id,
eq->serial_number
eq->item_unique_id
);
FINISH_DIRECT_DECODE();
break;
@ -6333,13 +6343,12 @@ namespace RoF2
IN_str(buyer_name);
IN_str(seller_name);
IN_str(item_name);
IN_str(serial_number);
strn0cpy(emu->item_unique_id, eq->item_unique_id, sizeof(emu->item_unique_id));
FINISH_DIRECT_DECODE();
break;
}
default: {
LogTrading("(RoF2) Unhandled action <red>[{}]", action);
}
return;
}
@ -6444,7 +6453,15 @@ namespace RoF2
RoF2::structs::ItemSerializationHeader hdr;
//sprintf(hdr.unknown000, "06e0002Y1W00");
strn0cpy(hdr.unknown000, fmt::format("{:016}\0", inst->GetSerialNumber()).c_str(),sizeof(hdr.unknown000));
if (inst->GetUniqueID().empty()) {
strn0cpy(hdr.unknown000, fmt::format("{:016}\0", inst->GetSerialNumber()).c_str(),sizeof(hdr.unknown000));
}
else {
strn0cpy(hdr.unknown000, inst->GetUniqueID().c_str(),sizeof(hdr.unknown000));
hdr.unknown000[16] = '\0';
}
hdr.stacksize = 1;

View File

@ -306,6 +306,7 @@ namespace RoF2
const size_t SAY_LINK_BODY_SIZE = 56;
const uint32 MAX_GUILD_ID = 50000;
const uint32 MAX_BAZAAR_TRADERS = 600;
const uint64 MAX_BAZAAR_TRANSACTION = 3276700000000; //3276700000000
} /*constants*/

View File

@ -3317,7 +3317,7 @@ struct BazaarInspect_Struct {
uint32 action;
uint32 unknown_004;
uint32 trader_id;
char serial_number[17];
char item_unique_id[17];
char unknown_029[3];
uint32 item_id;
uint32 unknown_036;
@ -3560,19 +3560,21 @@ struct WhoAllPlayerPart4 {
};
struct TraderItemSerial_Struct {
char serial_number[17];
char item_unique_id[17];
uint8 unknown_018;
void operator=(uint32 a) {
auto _tmp = fmt::format("{:016}", a);
strn0cpy(this->serial_number, _tmp.c_str(), sizeof(this->serial_number));
TraderItemSerial_Struct& operator=(const char* a) {
strn0cpy(this->item_unique_id, a, sizeof(this->item_unique_id));
unknown_018 = 0;
return *this;
}
};
struct BeginTrader_Struct {
/*0000*/ uint32 action;
/*0004*/ TraderItemSerial_Struct items[200];
/*3604*/ uint32 item_cost[200];
/*0004*/ TraderItemSerial_Struct item_unique_ids[RoF2::invtype::BAZAAR_SIZE];
/*3604*/ uint32 item_cost[RoF2::invtype::BAZAAR_SIZE];
/*4404*/
};
@ -3598,11 +3600,11 @@ struct BazaarWindowRemoveTrader_Struct {
};
struct TraderPriceUpdate_Struct {
uint32 action;
char serial_number[17];
char unknown_021[3];
uint32 unknown_024;
uint32 new_price;
/*000*/ uint32 action;
/*004*/ char item_unique_id[17];
/*021*/ char unknown_021[3];
/*024*/ uint32 unknown_024;
/*028*/ uint32 new_price;
};
struct Trader_ShowItems_Struct {
@ -3620,22 +3622,22 @@ struct TraderStatus_Struct {
};
struct TraderBuy_Struct {
/*000*/ uint32 action;
/*004*/ uint32 method;
/*008*/ uint32 sub_action;
/*012*/ uint32 unknown_012;
/*016*/ uint32 trader_id;
/*020*/ char buyer_name[64];
/*084*/ char seller_name[64];
/*148*/ char unknown_148[32];
/*180*/ char item_name[64];
/*244*/ char serial_number[17];
/*261*/ char unknown_261[3];
/*264*/ uint32 item_id;
/*268*/ uint32 price;
/*272*/ uint32 already_sold;
/*276*/ uint32 unknown_276;
/*280*/ uint32 quantity;
/*000*/ uint32 action;
/*004*/ uint32 method;
/*008*/ uint32 sub_action;
/*012*/ uint32 unknown_012;
/*016*/ uint32 trader_id;
/*020*/ char buyer_name[64];
/*084*/ char seller_name[64];
/*148*/ char unknown_148[32];
/*180*/ char item_name[64];
/*244*/ char item_unique_id[17];
/*261*/ char unknown_261[3];
/*264*/ uint32 item_id;
/*268*/ uint32 price;
/*272*/ uint32 already_sold;
/*276*/ uint32 unknown_276;
/*280*/ uint32 quantity;
/*284*/
};
@ -3655,10 +3657,10 @@ struct MoneyUpdate_Struct{
};
struct TraderDelItem_Struct{
/*000*/ uint32 Unknown000;
/*004*/ uint32 TraderID;
/*008*/ char SerialNumber[17];
/*024*/ uint32 Unknown012;
/*000*/ uint32 unknown_000;
/*004*/ uint32 trader_id;
/*008*/ char item_unique_id[17];
/*025*/ uint32 unknown_025;
/*028*/
};
@ -3685,22 +3687,22 @@ struct SimpleMessage_Struct{
// Size: 52 + strings
// Other than the strings, all of this packet is network byte order (reverse from normal)
struct GuildMemberEntry_Struct {
char name[1]; // variable length
uint32 level;
uint32 banker; // 1=yes, 0=no
uint32 class_;
uint32 rank;
uint32 time_last_on;
uint32 tribute_enable;
uint32 unknown01; // Seen 0
uint32 total_tribute; // total guild tribute donated, network byte order
uint32 last_tribute; // unix timestamp
uint32 unknown_one; // unknown, set to 1
char public_note[1]; // variable length.
uint16 zoneinstance; // Seen 0s or -1 in RoF2
uint16 zone_id; // Seen 0s or -1 in RoF2
uint32 unknown_one2; // unknown, set to 1
uint32 unknown04; // Seen 0
char name[1]; // variable length
uint32 level;
uint32 banker; // 1=yes, 0=no
uint32 class_;
uint32 rank;
uint32 time_last_on;
uint32 tribute_enable;
uint32 unknown01; // Seen 0
uint32 total_tribute; // total guild tribute donated, network byte order
uint32 last_tribute; // unix timestamp
uint32 unknown_one; // unknown, set to 1
char public_note[1]; // variable length.
uint16 zoneinstance; // Seen 0s or -1 in RoF2
uint16 zone_id; // Seen 0s or -1 in RoF2
uint32 unknown_one2; // unknown, set to 1
uint32 offline_mode; // Displays OFFLINE MODE instead of Zone Name
};
//just for display purposes, this is not actually used in the message encoding other than for size.
@ -3735,13 +3737,12 @@ struct GuildStatus_Struct
};
struct GuildMemberUpdate_Struct {
/*00*/ uint32 GuildID;
/*04*/ char MemberName[64];
/*68*/ uint16 ZoneID;
/*70*/ uint16 InstanceID; //speculated
/*72*/ uint32 LastSeen; //unix timestamp
/*76*/ uint32 Unknown76;
/*80*/
/*00*/ uint32 guild_id;
/*04*/ char member_name[64];
/*68*/ uint16 zone_id;
/*70*/ uint16 instance_id; //speculated
/*72*/ uint32 last_seen; //unix timestamp
/*76*/ uint32 offline_mode;
};
struct GuildMemberLevelUpdate_Struct {

View File

@ -226,7 +226,6 @@ namespace Titanium
VARSTRUCT_ENCODE_TYPE(uint32, bufptr, row->trader_entity_id);
bufptr += 4;
VARSTRUCT_ENCODE_TYPE(int32, bufptr, row->item_id);
VARSTRUCT_ENCODE_TYPE(int32, bufptr, row->serial_number);
bufptr += 4;
if (row->stackable) {
strn0cpy(
@ -2527,7 +2526,6 @@ namespace Titanium
IN(action);
memcpy(emu->player_name, eq->player_name, sizeof(emu->player_name));
IN(serial_number);
FINISH_DIRECT_DECODE();
break;

View File

@ -335,7 +335,6 @@ namespace UF
bufptr += 64;
VARSTRUCT_ENCODE_TYPE(uint32, bufptr, 1);
VARSTRUCT_ENCODE_TYPE(int32, bufptr, row->item_id);
VARSTRUCT_ENCODE_TYPE(int32, bufptr, row->serial_number);
bufptr += 4;
if (row->stackable) {
strn0cpy(
@ -3615,7 +3614,6 @@ namespace UF
IN(action);
memcpy(emu->player_name, eq->player_name, sizeof(emu->player_name));
IN(serial_number);
FINISH_DIRECT_DECODE();
break;

View File

@ -107,4 +107,43 @@ public:
return AccountRepository::UpdateOne(db, e);
}
static void SetOfflineStatus(Database& db, const uint32 account_id, bool offline_status)
{
auto account = FindOne(db, account_id);
if (!account.id) {
return;
}
account.offline = offline_status;
UpdateOne(db, account);
}
static void ClearAllOfflineStatus(Database& db)
{
auto query = fmt::format("UPDATE {} SET `offline` = 0 WHERE `offline` = 1;",
TableName()
);
db.QueryDatabase(query);
}
static bool GetAllOfflineStatus(Database& db, const uint32 character_id)
{
auto query = fmt::format("SELECT a.`offline` "
"FROM `account` AS a "
"INNER JOIN character_data AS c ON c.account_id = a.id "
"WHERE c.id = {}",
character_id
);
auto results = db.QueryDatabase(query);
if (!results.Success() || !results.RowCount()) {
return false;
}
auto row = results.begin();
bool const status = static_cast<int16>(Strings::ToInt(row[0]));
return status;
}
};

View File

@ -39,6 +39,7 @@ public:
uint8_t rulesflag;
time_t suspendeduntil;
uint32_t time_creation;
uint8_t offline;
std::string ban_reason;
std::string suspend_reason;
std::string crc_eqgame;
@ -74,6 +75,7 @@ public:
"rulesflag",
"suspendeduntil",
"time_creation",
"offline",
"ban_reason",
"suspend_reason",
"crc_eqgame",
@ -105,6 +107,7 @@ public:
"rulesflag",
"UNIX_TIMESTAMP(suspendeduntil)",
"time_creation",
"offline",
"ban_reason",
"suspend_reason",
"crc_eqgame",
@ -170,6 +173,7 @@ public:
e.rulesflag = 0;
e.suspendeduntil = 0;
e.time_creation = 0;
e.offline = 0;
e.ban_reason = "";
e.suspend_reason = "";
e.crc_eqgame = "";
@ -231,11 +235,12 @@ public:
e.rulesflag = row[17] ? static_cast<uint8_t>(strtoul(row[17], nullptr, 10)) : 0;
e.suspendeduntil = strtoll(row[18] ? row[18] : "-1", nullptr, 10);
e.time_creation = row[19] ? static_cast<uint32_t>(strtoul(row[19], nullptr, 10)) : 0;
e.ban_reason = row[20] ? row[20] : "";
e.suspend_reason = row[21] ? row[21] : "";
e.crc_eqgame = row[22] ? row[22] : "";
e.crc_skillcaps = row[23] ? row[23] : "";
e.crc_basedata = row[24] ? row[24] : "";
e.offline = row[20] ? static_cast<uint8_t>(strtoul(row[20], nullptr, 10)) : 0;
e.ban_reason = row[21] ? row[21] : "";
e.suspend_reason = row[22] ? row[22] : "";
e.crc_eqgame = row[23] ? row[23] : "";
e.crc_skillcaps = row[24] ? row[24] : "";
e.crc_basedata = row[25] ? row[25] : "";
return e;
}
@ -288,11 +293,12 @@ public:
v.push_back(columns[17] + " = " + std::to_string(e.rulesflag));
v.push_back(columns[18] + " = FROM_UNIXTIME(" + (e.suspendeduntil > 0 ? std::to_string(e.suspendeduntil) : "null") + ")");
v.push_back(columns[19] + " = " + std::to_string(e.time_creation));
v.push_back(columns[20] + " = '" + Strings::Escape(e.ban_reason) + "'");
v.push_back(columns[21] + " = '" + Strings::Escape(e.suspend_reason) + "'");
v.push_back(columns[22] + " = '" + Strings::Escape(e.crc_eqgame) + "'");
v.push_back(columns[23] + " = '" + Strings::Escape(e.crc_skillcaps) + "'");
v.push_back(columns[24] + " = '" + Strings::Escape(e.crc_basedata) + "'");
v.push_back(columns[20] + " = " + std::to_string(e.offline));
v.push_back(columns[21] + " = '" + Strings::Escape(e.ban_reason) + "'");
v.push_back(columns[22] + " = '" + Strings::Escape(e.suspend_reason) + "'");
v.push_back(columns[23] + " = '" + Strings::Escape(e.crc_eqgame) + "'");
v.push_back(columns[24] + " = '" + Strings::Escape(e.crc_skillcaps) + "'");
v.push_back(columns[25] + " = '" + Strings::Escape(e.crc_basedata) + "'");
auto results = db.QueryDatabase(
fmt::format(
@ -334,6 +340,7 @@ public:
v.push_back(std::to_string(e.rulesflag));
v.push_back("FROM_UNIXTIME(" + (e.suspendeduntil > 0 ? std::to_string(e.suspendeduntil) : "null") + ")");
v.push_back(std::to_string(e.time_creation));
v.push_back(std::to_string(e.offline));
v.push_back("'" + Strings::Escape(e.ban_reason) + "'");
v.push_back("'" + Strings::Escape(e.suspend_reason) + "'");
v.push_back("'" + Strings::Escape(e.crc_eqgame) + "'");
@ -388,6 +395,7 @@ public:
v.push_back(std::to_string(e.rulesflag));
v.push_back("FROM_UNIXTIME(" + (e.suspendeduntil > 0 ? std::to_string(e.suspendeduntil) : "null") + ")");
v.push_back(std::to_string(e.time_creation));
v.push_back(std::to_string(e.offline));
v.push_back("'" + Strings::Escape(e.ban_reason) + "'");
v.push_back("'" + Strings::Escape(e.suspend_reason) + "'");
v.push_back("'" + Strings::Escape(e.crc_eqgame) + "'");
@ -446,11 +454,12 @@ public:
e.rulesflag = row[17] ? static_cast<uint8_t>(strtoul(row[17], nullptr, 10)) : 0;
e.suspendeduntil = strtoll(row[18] ? row[18] : "-1", nullptr, 10);
e.time_creation = row[19] ? static_cast<uint32_t>(strtoul(row[19], nullptr, 10)) : 0;
e.ban_reason = row[20] ? row[20] : "";
e.suspend_reason = row[21] ? row[21] : "";
e.crc_eqgame = row[22] ? row[22] : "";
e.crc_skillcaps = row[23] ? row[23] : "";
e.crc_basedata = row[24] ? row[24] : "";
e.offline = row[20] ? static_cast<uint8_t>(strtoul(row[20], nullptr, 10)) : 0;
e.ban_reason = row[21] ? row[21] : "";
e.suspend_reason = row[22] ? row[22] : "";
e.crc_eqgame = row[23] ? row[23] : "";
e.crc_skillcaps = row[24] ? row[24] : "";
e.crc_basedata = row[25] ? row[25] : "";
all_entries.push_back(e);
}
@ -495,11 +504,12 @@ public:
e.rulesflag = row[17] ? static_cast<uint8_t>(strtoul(row[17], nullptr, 10)) : 0;
e.suspendeduntil = strtoll(row[18] ? row[18] : "-1", nullptr, 10);
e.time_creation = row[19] ? static_cast<uint32_t>(strtoul(row[19], nullptr, 10)) : 0;
e.ban_reason = row[20] ? row[20] : "";
e.suspend_reason = row[21] ? row[21] : "";
e.crc_eqgame = row[22] ? row[22] : "";
e.crc_skillcaps = row[23] ? row[23] : "";
e.crc_basedata = row[24] ? row[24] : "";
e.offline = row[20] ? static_cast<uint8_t>(strtoul(row[20], nullptr, 10)) : 0;
e.ban_reason = row[21] ? row[21] : "";
e.suspend_reason = row[22] ? row[22] : "";
e.crc_eqgame = row[23] ? row[23] : "";
e.crc_skillcaps = row[24] ? row[24] : "";
e.crc_basedata = row[25] ? row[25] : "";
all_entries.push_back(e);
}
@ -594,6 +604,7 @@ public:
v.push_back(std::to_string(e.rulesflag));
v.push_back("FROM_UNIXTIME(" + (e.suspendeduntil > 0 ? std::to_string(e.suspendeduntil) : "null") + ")");
v.push_back(std::to_string(e.time_creation));
v.push_back(std::to_string(e.offline));
v.push_back("'" + Strings::Escape(e.ban_reason) + "'");
v.push_back("'" + Strings::Escape(e.suspend_reason) + "'");
v.push_back("'" + Strings::Escape(e.crc_eqgame) + "'");
@ -641,6 +652,7 @@ public:
v.push_back(std::to_string(e.rulesflag));
v.push_back("FROM_UNIXTIME(" + (e.suspendeduntil > 0 ? std::to_string(e.suspendeduntil) : "null") + ")");
v.push_back(std::to_string(e.time_creation));
v.push_back(std::to_string(e.offline));
v.push_back("'" + Strings::Escape(e.ban_reason) + "'");
v.push_back("'" + Strings::Escape(e.suspend_reason) + "'");
v.push_back("'" + Strings::Escape(e.crc_eqgame) + "'");

View File

@ -0,0 +1,451 @@
/**
* DO NOT MODIFY THIS FILE
*
* This repository was automatically generated and is NOT to be modified directly.
* Any repository modifications are meant to be made to the repository extending the base.
* Any modifications to base repositories are to be made by the generator only
*
* @generator ./utils/scripts/generators/repository-generator.pl
* @docs https://docs.eqemu.io/developer/repositories
*/
#ifndef EQEMU_BASE_CHARACTER_OFFLINE_TRANSACTIONS_REPOSITORY_H
#define EQEMU_BASE_CHARACTER_OFFLINE_TRANSACTIONS_REPOSITORY_H
#include "../../database.h"
#include "../../strings.h"
#include <ctime>
class BaseCharacterOfflineTransactionsRepository {
public:
struct CharacterOfflineTransactions {
uint64_t id;
uint32_t character_id;
uint32_t type;
std::string item_name;
int32_t quantity;
uint64_t price;
std::string buyer_name;
};
static std::string PrimaryKey()
{
return std::string("id");
}
static std::vector<std::string> Columns()
{
return {
"id",
"character_id",
"type",
"item_name",
"quantity",
"price",
"buyer_name",
};
}
static std::vector<std::string> SelectColumns()
{
return {
"id",
"character_id",
"type",
"item_name",
"quantity",
"price",
"buyer_name",
};
}
static std::string ColumnsRaw()
{
return std::string(Strings::Implode(", ", Columns()));
}
static std::string SelectColumnsRaw()
{
return std::string(Strings::Implode(", ", SelectColumns()));
}
static std::string TableName()
{
return std::string("character_offline_transactions");
}
static std::string BaseSelect()
{
return fmt::format(
"SELECT {} FROM {}",
SelectColumnsRaw(),
TableName()
);
}
static std::string BaseInsert()
{
return fmt::format(
"INSERT INTO {} ({}) ",
TableName(),
ColumnsRaw()
);
}
static CharacterOfflineTransactions NewEntity()
{
CharacterOfflineTransactions e{};
e.id = 0;
e.character_id = 0;
e.type = 0;
e.item_name = "";
e.quantity = 0;
e.price = 0;
e.buyer_name = "";
return e;
}
static CharacterOfflineTransactions GetCharacterOfflineTransactions(
const std::vector<CharacterOfflineTransactions> &character_offline_transactionss,
int character_offline_transactions_id
)
{
for (auto &character_offline_transactions : character_offline_transactionss) {
if (character_offline_transactions.id == character_offline_transactions_id) {
return character_offline_transactions;
}
}
return NewEntity();
}
static CharacterOfflineTransactions FindOne(
Database& db,
int character_offline_transactions_id
)
{
auto results = db.QueryDatabase(
fmt::format(
"{} WHERE {} = {} LIMIT 1",
BaseSelect(),
PrimaryKey(),
character_offline_transactions_id
)
);
auto row = results.begin();
if (results.RowCount() == 1) {
CharacterOfflineTransactions e{};
e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0;
e.character_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
e.type = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
e.item_name = row[3] ? row[3] : "";
e.quantity = row[4] ? static_cast<int32_t>(atoi(row[4])) : 0;
e.price = row[5] ? strtoull(row[5], nullptr, 10) : 0;
e.buyer_name = row[6] ? row[6] : "";
return e;
}
return NewEntity();
}
static int DeleteOne(
Database& db,
int character_offline_transactions_id
)
{
auto results = db.QueryDatabase(
fmt::format(
"DELETE FROM {} WHERE {} = {}",
TableName(),
PrimaryKey(),
character_offline_transactions_id
)
);
return (results.Success() ? results.RowsAffected() : 0);
}
static int UpdateOne(
Database& db,
const CharacterOfflineTransactions &e
)
{
std::vector<std::string> v;
auto columns = Columns();
v.push_back(columns[1] + " = " + std::to_string(e.character_id));
v.push_back(columns[2] + " = " + std::to_string(e.type));
v.push_back(columns[3] + " = '" + Strings::Escape(e.item_name) + "'");
v.push_back(columns[4] + " = " + std::to_string(e.quantity));
v.push_back(columns[5] + " = " + std::to_string(e.price));
v.push_back(columns[6] + " = '" + Strings::Escape(e.buyer_name) + "'");
auto results = db.QueryDatabase(
fmt::format(
"UPDATE {} SET {} WHERE {} = {}",
TableName(),
Strings::Implode(", ", v),
PrimaryKey(),
e.id
)
);
return (results.Success() ? results.RowsAffected() : 0);
}
static CharacterOfflineTransactions InsertOne(
Database& db,
CharacterOfflineTransactions e
)
{
std::vector<std::string> v;
v.push_back(std::to_string(e.id));
v.push_back(std::to_string(e.character_id));
v.push_back(std::to_string(e.type));
v.push_back("'" + Strings::Escape(e.item_name) + "'");
v.push_back(std::to_string(e.quantity));
v.push_back(std::to_string(e.price));
v.push_back("'" + Strings::Escape(e.buyer_name) + "'");
auto results = db.QueryDatabase(
fmt::format(
"{} VALUES ({})",
BaseInsert(),
Strings::Implode(",", v)
)
);
if (results.Success()) {
e.id = results.LastInsertedID();
return e;
}
e = NewEntity();
return e;
}
static int InsertMany(
Database& db,
const std::vector<CharacterOfflineTransactions> &entries
)
{
std::vector<std::string> insert_chunks;
for (auto &e: entries) {
std::vector<std::string> v;
v.push_back(std::to_string(e.id));
v.push_back(std::to_string(e.character_id));
v.push_back(std::to_string(e.type));
v.push_back("'" + Strings::Escape(e.item_name) + "'");
v.push_back(std::to_string(e.quantity));
v.push_back(std::to_string(e.price));
v.push_back("'" + Strings::Escape(e.buyer_name) + "'");
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
}
std::vector<std::string> v;
auto results = db.QueryDatabase(
fmt::format(
"{} VALUES {}",
BaseInsert(),
Strings::Implode(",", insert_chunks)
)
);
return (results.Success() ? results.RowsAffected() : 0);
}
static std::vector<CharacterOfflineTransactions> All(Database& db)
{
std::vector<CharacterOfflineTransactions> all_entries;
auto results = db.QueryDatabase(
fmt::format(
"{}",
BaseSelect()
)
);
all_entries.reserve(results.RowCount());
for (auto row = results.begin(); row != results.end(); ++row) {
CharacterOfflineTransactions e{};
e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0;
e.character_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
e.type = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
e.item_name = row[3] ? row[3] : "";
e.quantity = row[4] ? static_cast<int32_t>(atoi(row[4])) : 0;
e.price = row[5] ? strtoull(row[5], nullptr, 10) : 0;
e.buyer_name = row[6] ? row[6] : "";
all_entries.push_back(e);
}
return all_entries;
}
static std::vector<CharacterOfflineTransactions> GetWhere(Database& db, const std::string &where_filter)
{
std::vector<CharacterOfflineTransactions> all_entries;
auto results = db.QueryDatabase(
fmt::format(
"{} WHERE {}",
BaseSelect(),
where_filter
)
);
all_entries.reserve(results.RowCount());
for (auto row = results.begin(); row != results.end(); ++row) {
CharacterOfflineTransactions e{};
e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0;
e.character_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
e.type = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
e.item_name = row[3] ? row[3] : "";
e.quantity = row[4] ? static_cast<int32_t>(atoi(row[4])) : 0;
e.price = row[5] ? strtoull(row[5], nullptr, 10) : 0;
e.buyer_name = row[6] ? row[6] : "";
all_entries.push_back(e);
}
return all_entries;
}
static int DeleteWhere(Database& db, const std::string &where_filter)
{
auto results = db.QueryDatabase(
fmt::format(
"DELETE FROM {} WHERE {}",
TableName(),
where_filter
)
);
return (results.Success() ? results.RowsAffected() : 0);
}
static int Truncate(Database& db)
{
auto results = db.QueryDatabase(
fmt::format(
"TRUNCATE TABLE {}",
TableName()
)
);
return (results.Success() ? results.RowsAffected() : 0);
}
static int64 GetMaxId(Database& db)
{
auto results = db.QueryDatabase(
fmt::format(
"SELECT COALESCE(MAX({}), 0) FROM {}",
PrimaryKey(),
TableName()
)
);
return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0);
}
static int64 Count(Database& db, const std::string &where_filter = "")
{
auto results = db.QueryDatabase(
fmt::format(
"SELECT COUNT(*) FROM {} {}",
TableName(),
(where_filter.empty() ? "" : "WHERE " + where_filter)
)
);
return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0);
}
static std::string BaseReplace()
{
return fmt::format(
"REPLACE INTO {} ({}) ",
TableName(),
ColumnsRaw()
);
}
static int ReplaceOne(
Database& db,
const CharacterOfflineTransactions &e
)
{
std::vector<std::string> v;
v.push_back(std::to_string(e.id));
v.push_back(std::to_string(e.character_id));
v.push_back(std::to_string(e.type));
v.push_back("'" + Strings::Escape(e.item_name) + "'");
v.push_back(std::to_string(e.quantity));
v.push_back(std::to_string(e.price));
v.push_back("'" + Strings::Escape(e.buyer_name) + "'");
auto results = db.QueryDatabase(
fmt::format(
"{} VALUES ({})",
BaseReplace(),
Strings::Implode(",", v)
)
);
return (results.Success() ? results.RowsAffected() : 0);
}
static int ReplaceMany(
Database& db,
const std::vector<CharacterOfflineTransactions> &entries
)
{
std::vector<std::string> insert_chunks;
for (auto &e: entries) {
std::vector<std::string> v;
v.push_back(std::to_string(e.id));
v.push_back(std::to_string(e.character_id));
v.push_back(std::to_string(e.type));
v.push_back("'" + Strings::Escape(e.item_name) + "'");
v.push_back(std::to_string(e.quantity));
v.push_back(std::to_string(e.price));
v.push_back("'" + Strings::Escape(e.buyer_name) + "'");
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
}
std::vector<std::string> v;
auto results = db.QueryDatabase(
fmt::format(
"{} VALUES {}",
BaseReplace(),
Strings::Implode(",", insert_chunks)
)
);
return (results.Success() ? results.RowsAffected() : 0);
}
};
#endif //EQEMU_BASE_CHARACTER_OFFLINE_TRANSACTIONS_REPOSITORY_H

View File

@ -19,18 +19,19 @@
class BaseCharacterParcelsContainersRepository {
public:
struct CharacterParcelsContainers {
uint32_t id;
uint32_t parcels_id;
uint32_t slot_id;
uint32_t item_id;
uint32_t aug_slot_1;
uint32_t aug_slot_2;
uint32_t aug_slot_3;
uint32_t aug_slot_4;
uint32_t aug_slot_5;
uint32_t aug_slot_6;
uint32_t quantity;
uint32_t evolve_amount;
uint32_t id;
uint32_t parcels_id;
uint32_t slot_id;
uint32_t item_id;
std::string item_unique_id;
uint32_t aug_slot_1;
uint32_t aug_slot_2;
uint32_t aug_slot_3;
uint32_t aug_slot_4;
uint32_t aug_slot_5;
uint32_t aug_slot_6;
uint32_t quantity;
uint32_t evolve_amount;
};
static std::string PrimaryKey()
@ -45,6 +46,7 @@ public:
"parcels_id",
"slot_id",
"item_id",
"item_unique_id",
"aug_slot_1",
"aug_slot_2",
"aug_slot_3",
@ -63,6 +65,7 @@ public:
"parcels_id",
"slot_id",
"item_id",
"item_unique_id",
"aug_slot_1",
"aug_slot_2",
"aug_slot_3",
@ -111,18 +114,19 @@ public:
{
CharacterParcelsContainers e{};
e.id = 0;
e.parcels_id = 0;
e.slot_id = 0;
e.item_id = 0;
e.aug_slot_1 = 0;
e.aug_slot_2 = 0;
e.aug_slot_3 = 0;
e.aug_slot_4 = 0;
e.aug_slot_5 = 0;
e.aug_slot_6 = 0;
e.quantity = 0;
e.evolve_amount = 0;
e.id = 0;
e.parcels_id = 0;
e.slot_id = 0;
e.item_id = 0;
e.item_unique_id = "";
e.aug_slot_1 = 0;
e.aug_slot_2 = 0;
e.aug_slot_3 = 0;
e.aug_slot_4 = 0;
e.aug_slot_5 = 0;
e.aug_slot_6 = 0;
e.quantity = 0;
e.evolve_amount = 0;
return e;
}
@ -159,18 +163,19 @@ public:
if (results.RowCount() == 1) {
CharacterParcelsContainers e{};
e.id = row[0] ? static_cast<uint32_t>(strtoul(row[0], nullptr, 10)) : 0;
e.parcels_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
e.slot_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
e.item_id = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
e.aug_slot_1 = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
e.aug_slot_2 = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
e.aug_slot_3 = row[6] ? static_cast<uint32_t>(strtoul(row[6], nullptr, 10)) : 0;
e.aug_slot_4 = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
e.aug_slot_5 = row[8] ? static_cast<uint32_t>(strtoul(row[8], nullptr, 10)) : 0;
e.aug_slot_6 = row[9] ? static_cast<uint32_t>(strtoul(row[9], nullptr, 10)) : 0;
e.quantity = row[10] ? static_cast<uint32_t>(strtoul(row[10], nullptr, 10)) : 0;
e.evolve_amount = row[11] ? static_cast<uint32_t>(strtoul(row[11], nullptr, 10)) : 0;
e.id = row[0] ? static_cast<uint32_t>(strtoul(row[0], nullptr, 10)) : 0;
e.parcels_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
e.slot_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
e.item_id = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
e.item_unique_id = row[4] ? row[4] : "";
e.aug_slot_1 = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
e.aug_slot_2 = row[6] ? static_cast<uint32_t>(strtoul(row[6], nullptr, 10)) : 0;
e.aug_slot_3 = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
e.aug_slot_4 = row[8] ? static_cast<uint32_t>(strtoul(row[8], nullptr, 10)) : 0;
e.aug_slot_5 = row[9] ? static_cast<uint32_t>(strtoul(row[9], nullptr, 10)) : 0;
e.aug_slot_6 = row[10] ? static_cast<uint32_t>(strtoul(row[10], nullptr, 10)) : 0;
e.quantity = row[11] ? static_cast<uint32_t>(strtoul(row[11], nullptr, 10)) : 0;
e.evolve_amount = row[12] ? static_cast<uint32_t>(strtoul(row[12], nullptr, 10)) : 0;
return e;
}
@ -207,14 +212,15 @@ public:
v.push_back(columns[1] + " = " + std::to_string(e.parcels_id));
v.push_back(columns[2] + " = " + std::to_string(e.slot_id));
v.push_back(columns[3] + " = " + std::to_string(e.item_id));
v.push_back(columns[4] + " = " + std::to_string(e.aug_slot_1));
v.push_back(columns[5] + " = " + std::to_string(e.aug_slot_2));
v.push_back(columns[6] + " = " + std::to_string(e.aug_slot_3));
v.push_back(columns[7] + " = " + std::to_string(e.aug_slot_4));
v.push_back(columns[8] + " = " + std::to_string(e.aug_slot_5));
v.push_back(columns[9] + " = " + std::to_string(e.aug_slot_6));
v.push_back(columns[10] + " = " + std::to_string(e.quantity));
v.push_back(columns[11] + " = " + std::to_string(e.evolve_amount));
v.push_back(columns[4] + " = '" + Strings::Escape(e.item_unique_id) + "'");
v.push_back(columns[5] + " = " + std::to_string(e.aug_slot_1));
v.push_back(columns[6] + " = " + std::to_string(e.aug_slot_2));
v.push_back(columns[7] + " = " + std::to_string(e.aug_slot_3));
v.push_back(columns[8] + " = " + std::to_string(e.aug_slot_4));
v.push_back(columns[9] + " = " + std::to_string(e.aug_slot_5));
v.push_back(columns[10] + " = " + std::to_string(e.aug_slot_6));
v.push_back(columns[11] + " = " + std::to_string(e.quantity));
v.push_back(columns[12] + " = " + std::to_string(e.evolve_amount));
auto results = db.QueryDatabase(
fmt::format(
@ -240,6 +246,7 @@ public:
v.push_back(std::to_string(e.parcels_id));
v.push_back(std::to_string(e.slot_id));
v.push_back(std::to_string(e.item_id));
v.push_back("'" + Strings::Escape(e.item_unique_id) + "'");
v.push_back(std::to_string(e.aug_slot_1));
v.push_back(std::to_string(e.aug_slot_2));
v.push_back(std::to_string(e.aug_slot_3));
@ -281,6 +288,7 @@ public:
v.push_back(std::to_string(e.parcels_id));
v.push_back(std::to_string(e.slot_id));
v.push_back(std::to_string(e.item_id));
v.push_back("'" + Strings::Escape(e.item_unique_id) + "'");
v.push_back(std::to_string(e.aug_slot_1));
v.push_back(std::to_string(e.aug_slot_2));
v.push_back(std::to_string(e.aug_slot_3));
@ -322,18 +330,19 @@ public:
for (auto row = results.begin(); row != results.end(); ++row) {
CharacterParcelsContainers e{};
e.id = row[0] ? static_cast<uint32_t>(strtoul(row[0], nullptr, 10)) : 0;
e.parcels_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
e.slot_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
e.item_id = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
e.aug_slot_1 = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
e.aug_slot_2 = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
e.aug_slot_3 = row[6] ? static_cast<uint32_t>(strtoul(row[6], nullptr, 10)) : 0;
e.aug_slot_4 = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
e.aug_slot_5 = row[8] ? static_cast<uint32_t>(strtoul(row[8], nullptr, 10)) : 0;
e.aug_slot_6 = row[9] ? static_cast<uint32_t>(strtoul(row[9], nullptr, 10)) : 0;
e.quantity = row[10] ? static_cast<uint32_t>(strtoul(row[10], nullptr, 10)) : 0;
e.evolve_amount = row[11] ? static_cast<uint32_t>(strtoul(row[11], nullptr, 10)) : 0;
e.id = row[0] ? static_cast<uint32_t>(strtoul(row[0], nullptr, 10)) : 0;
e.parcels_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
e.slot_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
e.item_id = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
e.item_unique_id = row[4] ? row[4] : "";
e.aug_slot_1 = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
e.aug_slot_2 = row[6] ? static_cast<uint32_t>(strtoul(row[6], nullptr, 10)) : 0;
e.aug_slot_3 = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
e.aug_slot_4 = row[8] ? static_cast<uint32_t>(strtoul(row[8], nullptr, 10)) : 0;
e.aug_slot_5 = row[9] ? static_cast<uint32_t>(strtoul(row[9], nullptr, 10)) : 0;
e.aug_slot_6 = row[10] ? static_cast<uint32_t>(strtoul(row[10], nullptr, 10)) : 0;
e.quantity = row[11] ? static_cast<uint32_t>(strtoul(row[11], nullptr, 10)) : 0;
e.evolve_amount = row[12] ? static_cast<uint32_t>(strtoul(row[12], nullptr, 10)) : 0;
all_entries.push_back(e);
}
@ -358,18 +367,19 @@ public:
for (auto row = results.begin(); row != results.end(); ++row) {
CharacterParcelsContainers e{};
e.id = row[0] ? static_cast<uint32_t>(strtoul(row[0], nullptr, 10)) : 0;
e.parcels_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
e.slot_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
e.item_id = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
e.aug_slot_1 = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
e.aug_slot_2 = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
e.aug_slot_3 = row[6] ? static_cast<uint32_t>(strtoul(row[6], nullptr, 10)) : 0;
e.aug_slot_4 = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
e.aug_slot_5 = row[8] ? static_cast<uint32_t>(strtoul(row[8], nullptr, 10)) : 0;
e.aug_slot_6 = row[9] ? static_cast<uint32_t>(strtoul(row[9], nullptr, 10)) : 0;
e.quantity = row[10] ? static_cast<uint32_t>(strtoul(row[10], nullptr, 10)) : 0;
e.evolve_amount = row[11] ? static_cast<uint32_t>(strtoul(row[11], nullptr, 10)) : 0;
e.id = row[0] ? static_cast<uint32_t>(strtoul(row[0], nullptr, 10)) : 0;
e.parcels_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
e.slot_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
e.item_id = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
e.item_unique_id = row[4] ? row[4] : "";
e.aug_slot_1 = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
e.aug_slot_2 = row[6] ? static_cast<uint32_t>(strtoul(row[6], nullptr, 10)) : 0;
e.aug_slot_3 = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
e.aug_slot_4 = row[8] ? static_cast<uint32_t>(strtoul(row[8], nullptr, 10)) : 0;
e.aug_slot_5 = row[9] ? static_cast<uint32_t>(strtoul(row[9], nullptr, 10)) : 0;
e.aug_slot_6 = row[10] ? static_cast<uint32_t>(strtoul(row[10], nullptr, 10)) : 0;
e.quantity = row[11] ? static_cast<uint32_t>(strtoul(row[11], nullptr, 10)) : 0;
e.evolve_amount = row[12] ? static_cast<uint32_t>(strtoul(row[12], nullptr, 10)) : 0;
all_entries.push_back(e);
}
@ -448,6 +458,7 @@ public:
v.push_back(std::to_string(e.parcels_id));
v.push_back(std::to_string(e.slot_id));
v.push_back(std::to_string(e.item_id));
v.push_back("'" + Strings::Escape(e.item_unique_id) + "'");
v.push_back(std::to_string(e.aug_slot_1));
v.push_back(std::to_string(e.aug_slot_2));
v.push_back(std::to_string(e.aug_slot_3));
@ -482,6 +493,7 @@ public:
v.push_back(std::to_string(e.parcels_id));
v.push_back(std::to_string(e.slot_id));
v.push_back(std::to_string(e.item_id));
v.push_back("'" + Strings::Escape(e.item_unique_id) + "'");
v.push_back(std::to_string(e.aug_slot_1));
v.push_back(std::to_string(e.aug_slot_2));
v.push_back(std::to_string(e.aug_slot_3));

View File

@ -28,6 +28,7 @@ public:
uint32_t aug_slot_4;
uint32_t aug_slot_5;
uint32_t aug_slot_6;
std::string item_unique_id;
uint32_t slot_id;
uint32_t quantity;
uint32_t evolve_amount;
@ -53,6 +54,7 @@ public:
"aug_slot_4",
"aug_slot_5",
"aug_slot_6",
"item_unique_id",
"slot_id",
"quantity",
"evolve_amount",
@ -74,6 +76,7 @@ public:
"aug_slot_4",
"aug_slot_5",
"aug_slot_6",
"item_unique_id",
"slot_id",
"quantity",
"evolve_amount",
@ -129,6 +132,7 @@ public:
e.aug_slot_4 = 0;
e.aug_slot_5 = 0;
e.aug_slot_6 = 0;
e.item_unique_id = "";
e.slot_id = 0;
e.quantity = 0;
e.evolve_amount = 0;
@ -171,21 +175,22 @@ public:
if (results.RowCount() == 1) {
CharacterParcels e{};
e.id = row[0] ? static_cast<uint32_t>(strtoul(row[0], nullptr, 10)) : 0;
e.char_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
e.item_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
e.aug_slot_1 = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
e.aug_slot_2 = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
e.aug_slot_3 = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
e.aug_slot_4 = row[6] ? static_cast<uint32_t>(strtoul(row[6], nullptr, 10)) : 0;
e.aug_slot_5 = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
e.aug_slot_6 = row[8] ? static_cast<uint32_t>(strtoul(row[8], nullptr, 10)) : 0;
e.slot_id = row[9] ? static_cast<uint32_t>(strtoul(row[9], nullptr, 10)) : 0;
e.quantity = row[10] ? static_cast<uint32_t>(strtoul(row[10], nullptr, 10)) : 0;
e.evolve_amount = row[11] ? static_cast<uint32_t>(strtoul(row[11], nullptr, 10)) : 0;
e.from_name = row[12] ? row[12] : "";
e.note = row[13] ? row[13] : "";
e.sent_date = strtoll(row[14] ? row[14] : "-1", nullptr, 10);
e.id = row[0] ? static_cast<uint32_t>(strtoul(row[0], nullptr, 10)) : 0;
e.char_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
e.item_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
e.aug_slot_1 = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
e.aug_slot_2 = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
e.aug_slot_3 = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
e.aug_slot_4 = row[6] ? static_cast<uint32_t>(strtoul(row[6], nullptr, 10)) : 0;
e.aug_slot_5 = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
e.aug_slot_6 = row[8] ? static_cast<uint32_t>(strtoul(row[8], nullptr, 10)) : 0;
e.item_unique_id = row[9] ? row[9] : "";
e.slot_id = row[10] ? static_cast<uint32_t>(strtoul(row[10], nullptr, 10)) : 0;
e.quantity = row[11] ? static_cast<uint32_t>(strtoul(row[11], nullptr, 10)) : 0;
e.evolve_amount = row[12] ? static_cast<uint32_t>(strtoul(row[12], nullptr, 10)) : 0;
e.from_name = row[13] ? row[13] : "";
e.note = row[14] ? row[14] : "";
e.sent_date = strtoll(row[15] ? row[15] : "-1", nullptr, 10);
return e;
}
@ -227,12 +232,13 @@ public:
v.push_back(columns[6] + " = " + std::to_string(e.aug_slot_4));
v.push_back(columns[7] + " = " + std::to_string(e.aug_slot_5));
v.push_back(columns[8] + " = " + std::to_string(e.aug_slot_6));
v.push_back(columns[9] + " = " + std::to_string(e.slot_id));
v.push_back(columns[10] + " = " + std::to_string(e.quantity));
v.push_back(columns[11] + " = " + std::to_string(e.evolve_amount));
v.push_back(columns[12] + " = '" + Strings::Escape(e.from_name) + "'");
v.push_back(columns[13] + " = '" + Strings::Escape(e.note) + "'");
v.push_back(columns[14] + " = FROM_UNIXTIME(" + (e.sent_date > 0 ? std::to_string(e.sent_date) : "null") + ")");
v.push_back(columns[9] + " = '" + Strings::Escape(e.item_unique_id) + "'");
v.push_back(columns[10] + " = " + std::to_string(e.slot_id));
v.push_back(columns[11] + " = " + std::to_string(e.quantity));
v.push_back(columns[12] + " = " + std::to_string(e.evolve_amount));
v.push_back(columns[13] + " = '" + Strings::Escape(e.from_name) + "'");
v.push_back(columns[14] + " = '" + Strings::Escape(e.note) + "'");
v.push_back(columns[15] + " = FROM_UNIXTIME(" + (e.sent_date > 0 ? std::to_string(e.sent_date) : "null") + ")");
auto results = db.QueryDatabase(
fmt::format(
@ -263,6 +269,7 @@ public:
v.push_back(std::to_string(e.aug_slot_4));
v.push_back(std::to_string(e.aug_slot_5));
v.push_back(std::to_string(e.aug_slot_6));
v.push_back("'" + Strings::Escape(e.item_unique_id) + "'");
v.push_back(std::to_string(e.slot_id));
v.push_back(std::to_string(e.quantity));
v.push_back(std::to_string(e.evolve_amount));
@ -307,6 +314,7 @@ public:
v.push_back(std::to_string(e.aug_slot_4));
v.push_back(std::to_string(e.aug_slot_5));
v.push_back(std::to_string(e.aug_slot_6));
v.push_back("'" + Strings::Escape(e.item_unique_id) + "'");
v.push_back(std::to_string(e.slot_id));
v.push_back(std::to_string(e.quantity));
v.push_back(std::to_string(e.evolve_amount));
@ -346,21 +354,22 @@ public:
for (auto row = results.begin(); row != results.end(); ++row) {
CharacterParcels e{};
e.id = row[0] ? static_cast<uint32_t>(strtoul(row[0], nullptr, 10)) : 0;
e.char_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
e.item_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
e.aug_slot_1 = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
e.aug_slot_2 = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
e.aug_slot_3 = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
e.aug_slot_4 = row[6] ? static_cast<uint32_t>(strtoul(row[6], nullptr, 10)) : 0;
e.aug_slot_5 = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
e.aug_slot_6 = row[8] ? static_cast<uint32_t>(strtoul(row[8], nullptr, 10)) : 0;
e.slot_id = row[9] ? static_cast<uint32_t>(strtoul(row[9], nullptr, 10)) : 0;
e.quantity = row[10] ? static_cast<uint32_t>(strtoul(row[10], nullptr, 10)) : 0;
e.evolve_amount = row[11] ? static_cast<uint32_t>(strtoul(row[11], nullptr, 10)) : 0;
e.from_name = row[12] ? row[12] : "";
e.note = row[13] ? row[13] : "";
e.sent_date = strtoll(row[14] ? row[14] : "-1", nullptr, 10);
e.id = row[0] ? static_cast<uint32_t>(strtoul(row[0], nullptr, 10)) : 0;
e.char_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
e.item_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
e.aug_slot_1 = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
e.aug_slot_2 = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
e.aug_slot_3 = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
e.aug_slot_4 = row[6] ? static_cast<uint32_t>(strtoul(row[6], nullptr, 10)) : 0;
e.aug_slot_5 = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
e.aug_slot_6 = row[8] ? static_cast<uint32_t>(strtoul(row[8], nullptr, 10)) : 0;
e.item_unique_id = row[9] ? row[9] : "";
e.slot_id = row[10] ? static_cast<uint32_t>(strtoul(row[10], nullptr, 10)) : 0;
e.quantity = row[11] ? static_cast<uint32_t>(strtoul(row[11], nullptr, 10)) : 0;
e.evolve_amount = row[12] ? static_cast<uint32_t>(strtoul(row[12], nullptr, 10)) : 0;
e.from_name = row[13] ? row[13] : "";
e.note = row[14] ? row[14] : "";
e.sent_date = strtoll(row[15] ? row[15] : "-1", nullptr, 10);
all_entries.push_back(e);
}
@ -385,21 +394,22 @@ public:
for (auto row = results.begin(); row != results.end(); ++row) {
CharacterParcels e{};
e.id = row[0] ? static_cast<uint32_t>(strtoul(row[0], nullptr, 10)) : 0;
e.char_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
e.item_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
e.aug_slot_1 = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
e.aug_slot_2 = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
e.aug_slot_3 = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
e.aug_slot_4 = row[6] ? static_cast<uint32_t>(strtoul(row[6], nullptr, 10)) : 0;
e.aug_slot_5 = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
e.aug_slot_6 = row[8] ? static_cast<uint32_t>(strtoul(row[8], nullptr, 10)) : 0;
e.slot_id = row[9] ? static_cast<uint32_t>(strtoul(row[9], nullptr, 10)) : 0;
e.quantity = row[10] ? static_cast<uint32_t>(strtoul(row[10], nullptr, 10)) : 0;
e.evolve_amount = row[11] ? static_cast<uint32_t>(strtoul(row[11], nullptr, 10)) : 0;
e.from_name = row[12] ? row[12] : "";
e.note = row[13] ? row[13] : "";
e.sent_date = strtoll(row[14] ? row[14] : "-1", nullptr, 10);
e.id = row[0] ? static_cast<uint32_t>(strtoul(row[0], nullptr, 10)) : 0;
e.char_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
e.item_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
e.aug_slot_1 = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
e.aug_slot_2 = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
e.aug_slot_3 = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
e.aug_slot_4 = row[6] ? static_cast<uint32_t>(strtoul(row[6], nullptr, 10)) : 0;
e.aug_slot_5 = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
e.aug_slot_6 = row[8] ? static_cast<uint32_t>(strtoul(row[8], nullptr, 10)) : 0;
e.item_unique_id = row[9] ? row[9] : "";
e.slot_id = row[10] ? static_cast<uint32_t>(strtoul(row[10], nullptr, 10)) : 0;
e.quantity = row[11] ? static_cast<uint32_t>(strtoul(row[11], nullptr, 10)) : 0;
e.evolve_amount = row[12] ? static_cast<uint32_t>(strtoul(row[12], nullptr, 10)) : 0;
e.from_name = row[13] ? row[13] : "";
e.note = row[14] ? row[14] : "";
e.sent_date = strtoll(row[15] ? row[15] : "-1", nullptr, 10);
all_entries.push_back(e);
}
@ -483,6 +493,7 @@ public:
v.push_back(std::to_string(e.aug_slot_4));
v.push_back(std::to_string(e.aug_slot_5));
v.push_back(std::to_string(e.aug_slot_6));
v.push_back("'" + Strings::Escape(e.item_unique_id) + "'");
v.push_back(std::to_string(e.slot_id));
v.push_back(std::to_string(e.quantity));
v.push_back(std::to_string(e.evolve_amount));
@ -520,6 +531,7 @@ public:
v.push_back(std::to_string(e.aug_slot_4));
v.push_back(std::to_string(e.aug_slot_5));
v.push_back(std::to_string(e.aug_slot_6));
v.push_back("'" + Strings::Escape(e.item_unique_id) + "'");
v.push_back(std::to_string(e.slot_id));
v.push_back(std::to_string(e.quantity));
v.push_back(std::to_string(e.evolve_amount));

View File

@ -35,7 +35,7 @@ public:
uint32_t ornament_icon;
uint32_t ornament_idfile;
int32_t ornament_hero_model;
uint64_t guid;
std::string item_unique_id;
};
static std::string PrimaryKey()
@ -62,7 +62,7 @@ public:
"ornament_icon",
"ornament_idfile",
"ornament_hero_model",
"guid",
"item_unique_id",
};
}
@ -85,7 +85,7 @@ public:
"ornament_icon",
"ornament_idfile",
"ornament_hero_model",
"guid",
"item_unique_id",
};
}
@ -142,7 +142,7 @@ public:
e.ornament_icon = 0;
e.ornament_idfile = 0;
e.ornament_hero_model = 0;
e.guid = 0;
e.item_unique_id = "";
return e;
}
@ -195,7 +195,7 @@ public:
e.ornament_icon = row[13] ? static_cast<uint32_t>(strtoul(row[13], nullptr, 10)) : 0;
e.ornament_idfile = row[14] ? static_cast<uint32_t>(strtoul(row[14], nullptr, 10)) : 0;
e.ornament_hero_model = row[15] ? static_cast<int32_t>(atoi(row[15])) : 0;
e.guid = row[16] ? strtoull(row[16], nullptr, 10) : 0;
e.item_unique_id = row[16] ? row[16] : "";
return e;
}
@ -245,7 +245,7 @@ public:
v.push_back(columns[13] + " = " + std::to_string(e.ornament_icon));
v.push_back(columns[14] + " = " + std::to_string(e.ornament_idfile));
v.push_back(columns[15] + " = " + std::to_string(e.ornament_hero_model));
v.push_back(columns[16] + " = " + std::to_string(e.guid));
v.push_back(columns[16] + " = '" + Strings::Escape(e.item_unique_id) + "'");
auto results = db.QueryDatabase(
fmt::format(
@ -283,7 +283,7 @@ public:
v.push_back(std::to_string(e.ornament_icon));
v.push_back(std::to_string(e.ornament_idfile));
v.push_back(std::to_string(e.ornament_hero_model));
v.push_back(std::to_string(e.guid));
v.push_back("'" + Strings::Escape(e.item_unique_id) + "'");
auto results = db.QueryDatabase(
fmt::format(
@ -329,7 +329,7 @@ public:
v.push_back(std::to_string(e.ornament_icon));
v.push_back(std::to_string(e.ornament_idfile));
v.push_back(std::to_string(e.ornament_hero_model));
v.push_back(std::to_string(e.guid));
v.push_back("'" + Strings::Escape(e.item_unique_id) + "'");
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
}
@ -379,7 +379,7 @@ public:
e.ornament_icon = row[13] ? static_cast<uint32_t>(strtoul(row[13], nullptr, 10)) : 0;
e.ornament_idfile = row[14] ? static_cast<uint32_t>(strtoul(row[14], nullptr, 10)) : 0;
e.ornament_hero_model = row[15] ? static_cast<int32_t>(atoi(row[15])) : 0;
e.guid = row[16] ? strtoull(row[16], nullptr, 10) : 0;
e.item_unique_id = row[16] ? row[16] : "";
all_entries.push_back(e);
}
@ -420,7 +420,7 @@ public:
e.ornament_icon = row[13] ? static_cast<uint32_t>(strtoul(row[13], nullptr, 10)) : 0;
e.ornament_idfile = row[14] ? static_cast<uint32_t>(strtoul(row[14], nullptr, 10)) : 0;
e.ornament_hero_model = row[15] ? static_cast<int32_t>(atoi(row[15])) : 0;
e.guid = row[16] ? strtoull(row[16], nullptr, 10) : 0;
e.item_unique_id = row[16] ? row[16] : "";
all_entries.push_back(e);
}
@ -511,7 +511,7 @@ public:
v.push_back(std::to_string(e.ornament_icon));
v.push_back(std::to_string(e.ornament_idfile));
v.push_back(std::to_string(e.ornament_hero_model));
v.push_back(std::to_string(e.guid));
v.push_back("'" + Strings::Escape(e.item_unique_id) + "'");
auto results = db.QueryDatabase(
fmt::format(
@ -550,7 +550,7 @@ public:
v.push_back(std::to_string(e.ornament_icon));
v.push_back(std::to_string(e.ornament_idfile));
v.push_back(std::to_string(e.ornament_hero_model));
v.push_back(std::to_string(e.guid));
v.push_back("'" + Strings::Escape(e.item_unique_id) + "'");
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
}

View File

@ -20,23 +20,23 @@ class BaseInventorySnapshotsRepository {
public:
struct InventorySnapshots {
uint32_t time_index;
uint32_t charid;
uint32_t slotid;
uint32_t itemid;
uint32_t character_id;
uint32_t slot_id;
uint32_t item_id;
uint16_t charges;
uint32_t color;
uint32_t augslot1;
uint32_t augslot2;
uint32_t augslot3;
uint32_t augslot4;
uint32_t augslot5;
int32_t augslot6;
uint32_t augment_one;
uint32_t augment_two;
uint32_t augment_three;
uint32_t augment_four;
uint32_t augment_five;
int32_t augment_six;
uint8_t instnodrop;
std::string custom_data;
uint32_t ornamenticon;
uint32_t ornamentidfile;
uint32_t ornament_icon;
uint32_t ornament_idfile;
int32_t ornament_hero_model;
uint64_t guid;
std::string item_unique_id;
};
static std::string PrimaryKey()
@ -48,23 +48,23 @@ public:
{
return {
"time_index",
"charid",
"slotid",
"itemid",
"character_id",
"slot_id",
"item_id",
"charges",
"color",
"augslot1",
"augslot2",
"augslot3",
"augslot4",
"augslot5",
"augslot6",
"augment_one",
"augment_two",
"augment_three",
"augment_four",
"augment_five",
"augment_six",
"instnodrop",
"custom_data",
"ornamenticon",
"ornamentidfile",
"ornament_icon",
"ornament_idfile",
"ornament_hero_model",
"guid",
"item_unique_id",
};
}
@ -72,23 +72,23 @@ public:
{
return {
"time_index",
"charid",
"slotid",
"itemid",
"character_id",
"slot_id",
"item_id",
"charges",
"color",
"augslot1",
"augslot2",
"augslot3",
"augslot4",
"augslot5",
"augslot6",
"augment_one",
"augment_two",
"augment_three",
"augment_four",
"augment_five",
"augment_six",
"instnodrop",
"custom_data",
"ornamenticon",
"ornamentidfile",
"ornament_icon",
"ornament_idfile",
"ornament_hero_model",
"guid",
"item_unique_id",
};
}
@ -130,23 +130,23 @@ public:
InventorySnapshots e{};
e.time_index = 0;
e.charid = 0;
e.slotid = 0;
e.itemid = 0;
e.character_id = 0;
e.slot_id = 0;
e.item_id = 0;
e.charges = 0;
e.color = 0;
e.augslot1 = 0;
e.augslot2 = 0;
e.augslot3 = 0;
e.augslot4 = 0;
e.augslot5 = 0;
e.augslot6 = 0;
e.augment_one = 0;
e.augment_two = 0;
e.augment_three = 0;
e.augment_four = 0;
e.augment_five = 0;
e.augment_six = 0;
e.instnodrop = 0;
e.custom_data = "";
e.ornamenticon = 0;
e.ornamentidfile = 0;
e.ornament_icon = 0;
e.ornament_idfile = 0;
e.ornament_hero_model = 0;
e.guid = 0;
e.item_unique_id = "";
return e;
}
@ -184,23 +184,23 @@ public:
InventorySnapshots e{};
e.time_index = row[0] ? static_cast<uint32_t>(strtoul(row[0], nullptr, 10)) : 0;
e.charid = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
e.slotid = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
e.itemid = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
e.character_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
e.slot_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
e.item_id = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
e.charges = row[4] ? static_cast<uint16_t>(strtoul(row[4], nullptr, 10)) : 0;
e.color = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
e.augslot1 = row[6] ? static_cast<uint32_t>(strtoul(row[6], nullptr, 10)) : 0;
e.augslot2 = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
e.augslot3 = row[8] ? static_cast<uint32_t>(strtoul(row[8], nullptr, 10)) : 0;
e.augslot4 = row[9] ? static_cast<uint32_t>(strtoul(row[9], nullptr, 10)) : 0;
e.augslot5 = row[10] ? static_cast<uint32_t>(strtoul(row[10], nullptr, 10)) : 0;
e.augslot6 = row[11] ? static_cast<int32_t>(atoi(row[11])) : 0;
e.augment_one = row[6] ? static_cast<uint32_t>(strtoul(row[6], nullptr, 10)) : 0;
e.augment_two = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
e.augment_three = row[8] ? static_cast<uint32_t>(strtoul(row[8], nullptr, 10)) : 0;
e.augment_four = row[9] ? static_cast<uint32_t>(strtoul(row[9], nullptr, 10)) : 0;
e.augment_five = row[10] ? static_cast<uint32_t>(strtoul(row[10], nullptr, 10)) : 0;
e.augment_six = row[11] ? static_cast<int32_t>(atoi(row[11])) : 0;
e.instnodrop = row[12] ? static_cast<uint8_t>(strtoul(row[12], nullptr, 10)) : 0;
e.custom_data = row[13] ? row[13] : "";
e.ornamenticon = row[14] ? static_cast<uint32_t>(strtoul(row[14], nullptr, 10)) : 0;
e.ornamentidfile = row[15] ? static_cast<uint32_t>(strtoul(row[15], nullptr, 10)) : 0;
e.ornament_icon = row[14] ? static_cast<uint32_t>(strtoul(row[14], nullptr, 10)) : 0;
e.ornament_idfile = row[15] ? static_cast<uint32_t>(strtoul(row[15], nullptr, 10)) : 0;
e.ornament_hero_model = row[16] ? static_cast<int32_t>(atoi(row[16])) : 0;
e.guid = row[17] ? strtoull(row[17], nullptr, 10) : 0;
e.item_unique_id = row[17] ? row[17] : "";
return e;
}
@ -235,23 +235,23 @@ public:
auto columns = Columns();
v.push_back(columns[0] + " = " + std::to_string(e.time_index));
v.push_back(columns[1] + " = " + std::to_string(e.charid));
v.push_back(columns[2] + " = " + std::to_string(e.slotid));
v.push_back(columns[3] + " = " + std::to_string(e.itemid));
v.push_back(columns[1] + " = " + std::to_string(e.character_id));
v.push_back(columns[2] + " = " + std::to_string(e.slot_id));
v.push_back(columns[3] + " = " + std::to_string(e.item_id));
v.push_back(columns[4] + " = " + std::to_string(e.charges));
v.push_back(columns[5] + " = " + std::to_string(e.color));
v.push_back(columns[6] + " = " + std::to_string(e.augslot1));
v.push_back(columns[7] + " = " + std::to_string(e.augslot2));
v.push_back(columns[8] + " = " + std::to_string(e.augslot3));
v.push_back(columns[9] + " = " + std::to_string(e.augslot4));
v.push_back(columns[10] + " = " + std::to_string(e.augslot5));
v.push_back(columns[11] + " = " + std::to_string(e.augslot6));
v.push_back(columns[6] + " = " + std::to_string(e.augment_one));
v.push_back(columns[7] + " = " + std::to_string(e.augment_two));
v.push_back(columns[8] + " = " + std::to_string(e.augment_three));
v.push_back(columns[9] + " = " + std::to_string(e.augment_four));
v.push_back(columns[10] + " = " + std::to_string(e.augment_five));
v.push_back(columns[11] + " = " + std::to_string(e.augment_six));
v.push_back(columns[12] + " = " + std::to_string(e.instnodrop));
v.push_back(columns[13] + " = '" + Strings::Escape(e.custom_data) + "'");
v.push_back(columns[14] + " = " + std::to_string(e.ornamenticon));
v.push_back(columns[15] + " = " + std::to_string(e.ornamentidfile));
v.push_back(columns[14] + " = " + std::to_string(e.ornament_icon));
v.push_back(columns[15] + " = " + std::to_string(e.ornament_idfile));
v.push_back(columns[16] + " = " + std::to_string(e.ornament_hero_model));
v.push_back(columns[17] + " = " + std::to_string(e.guid));
v.push_back(columns[17] + " = '" + Strings::Escape(e.item_unique_id) + "'");
auto results = db.QueryDatabase(
fmt::format(
@ -274,23 +274,23 @@ public:
std::vector<std::string> v;
v.push_back(std::to_string(e.time_index));
v.push_back(std::to_string(e.charid));
v.push_back(std::to_string(e.slotid));
v.push_back(std::to_string(e.itemid));
v.push_back(std::to_string(e.character_id));
v.push_back(std::to_string(e.slot_id));
v.push_back(std::to_string(e.item_id));
v.push_back(std::to_string(e.charges));
v.push_back(std::to_string(e.color));
v.push_back(std::to_string(e.augslot1));
v.push_back(std::to_string(e.augslot2));
v.push_back(std::to_string(e.augslot3));
v.push_back(std::to_string(e.augslot4));
v.push_back(std::to_string(e.augslot5));
v.push_back(std::to_string(e.augslot6));
v.push_back(std::to_string(e.augment_one));
v.push_back(std::to_string(e.augment_two));
v.push_back(std::to_string(e.augment_three));
v.push_back(std::to_string(e.augment_four));
v.push_back(std::to_string(e.augment_five));
v.push_back(std::to_string(e.augment_six));
v.push_back(std::to_string(e.instnodrop));
v.push_back("'" + Strings::Escape(e.custom_data) + "'");
v.push_back(std::to_string(e.ornamenticon));
v.push_back(std::to_string(e.ornamentidfile));
v.push_back(std::to_string(e.ornament_icon));
v.push_back(std::to_string(e.ornament_idfile));
v.push_back(std::to_string(e.ornament_hero_model));
v.push_back(std::to_string(e.guid));
v.push_back("'" + Strings::Escape(e.item_unique_id) + "'");
auto results = db.QueryDatabase(
fmt::format(
@ -321,23 +321,23 @@ public:
std::vector<std::string> v;
v.push_back(std::to_string(e.time_index));
v.push_back(std::to_string(e.charid));
v.push_back(std::to_string(e.slotid));
v.push_back(std::to_string(e.itemid));
v.push_back(std::to_string(e.character_id));
v.push_back(std::to_string(e.slot_id));
v.push_back(std::to_string(e.item_id));
v.push_back(std::to_string(e.charges));
v.push_back(std::to_string(e.color));
v.push_back(std::to_string(e.augslot1));
v.push_back(std::to_string(e.augslot2));
v.push_back(std::to_string(e.augslot3));
v.push_back(std::to_string(e.augslot4));
v.push_back(std::to_string(e.augslot5));
v.push_back(std::to_string(e.augslot6));
v.push_back(std::to_string(e.augment_one));
v.push_back(std::to_string(e.augment_two));
v.push_back(std::to_string(e.augment_three));
v.push_back(std::to_string(e.augment_four));
v.push_back(std::to_string(e.augment_five));
v.push_back(std::to_string(e.augment_six));
v.push_back(std::to_string(e.instnodrop));
v.push_back("'" + Strings::Escape(e.custom_data) + "'");
v.push_back(std::to_string(e.ornamenticon));
v.push_back(std::to_string(e.ornamentidfile));
v.push_back(std::to_string(e.ornament_icon));
v.push_back(std::to_string(e.ornament_idfile));
v.push_back(std::to_string(e.ornament_hero_model));
v.push_back(std::to_string(e.guid));
v.push_back("'" + Strings::Escape(e.item_unique_id) + "'");
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
}
@ -372,23 +372,23 @@ public:
InventorySnapshots e{};
e.time_index = row[0] ? static_cast<uint32_t>(strtoul(row[0], nullptr, 10)) : 0;
e.charid = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
e.slotid = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
e.itemid = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
e.character_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
e.slot_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
e.item_id = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
e.charges = row[4] ? static_cast<uint16_t>(strtoul(row[4], nullptr, 10)) : 0;
e.color = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
e.augslot1 = row[6] ? static_cast<uint32_t>(strtoul(row[6], nullptr, 10)) : 0;
e.augslot2 = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
e.augslot3 = row[8] ? static_cast<uint32_t>(strtoul(row[8], nullptr, 10)) : 0;
e.augslot4 = row[9] ? static_cast<uint32_t>(strtoul(row[9], nullptr, 10)) : 0;
e.augslot5 = row[10] ? static_cast<uint32_t>(strtoul(row[10], nullptr, 10)) : 0;
e.augslot6 = row[11] ? static_cast<int32_t>(atoi(row[11])) : 0;
e.augment_one = row[6] ? static_cast<uint32_t>(strtoul(row[6], nullptr, 10)) : 0;
e.augment_two = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
e.augment_three = row[8] ? static_cast<uint32_t>(strtoul(row[8], nullptr, 10)) : 0;
e.augment_four = row[9] ? static_cast<uint32_t>(strtoul(row[9], nullptr, 10)) : 0;
e.augment_five = row[10] ? static_cast<uint32_t>(strtoul(row[10], nullptr, 10)) : 0;
e.augment_six = row[11] ? static_cast<int32_t>(atoi(row[11])) : 0;
e.instnodrop = row[12] ? static_cast<uint8_t>(strtoul(row[12], nullptr, 10)) : 0;
e.custom_data = row[13] ? row[13] : "";
e.ornamenticon = row[14] ? static_cast<uint32_t>(strtoul(row[14], nullptr, 10)) : 0;
e.ornamentidfile = row[15] ? static_cast<uint32_t>(strtoul(row[15], nullptr, 10)) : 0;
e.ornament_icon = row[14] ? static_cast<uint32_t>(strtoul(row[14], nullptr, 10)) : 0;
e.ornament_idfile = row[15] ? static_cast<uint32_t>(strtoul(row[15], nullptr, 10)) : 0;
e.ornament_hero_model = row[16] ? static_cast<int32_t>(atoi(row[16])) : 0;
e.guid = row[17] ? strtoull(row[17], nullptr, 10) : 0;
e.item_unique_id = row[17] ? row[17] : "";
all_entries.push_back(e);
}
@ -414,23 +414,23 @@ public:
InventorySnapshots e{};
e.time_index = row[0] ? static_cast<uint32_t>(strtoul(row[0], nullptr, 10)) : 0;
e.charid = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
e.slotid = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
e.itemid = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
e.character_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
e.slot_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
e.item_id = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
e.charges = row[4] ? static_cast<uint16_t>(strtoul(row[4], nullptr, 10)) : 0;
e.color = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
e.augslot1 = row[6] ? static_cast<uint32_t>(strtoul(row[6], nullptr, 10)) : 0;
e.augslot2 = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
e.augslot3 = row[8] ? static_cast<uint32_t>(strtoul(row[8], nullptr, 10)) : 0;
e.augslot4 = row[9] ? static_cast<uint32_t>(strtoul(row[9], nullptr, 10)) : 0;
e.augslot5 = row[10] ? static_cast<uint32_t>(strtoul(row[10], nullptr, 10)) : 0;
e.augslot6 = row[11] ? static_cast<int32_t>(atoi(row[11])) : 0;
e.augment_one = row[6] ? static_cast<uint32_t>(strtoul(row[6], nullptr, 10)) : 0;
e.augment_two = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
e.augment_three = row[8] ? static_cast<uint32_t>(strtoul(row[8], nullptr, 10)) : 0;
e.augment_four = row[9] ? static_cast<uint32_t>(strtoul(row[9], nullptr, 10)) : 0;
e.augment_five = row[10] ? static_cast<uint32_t>(strtoul(row[10], nullptr, 10)) : 0;
e.augment_six = row[11] ? static_cast<int32_t>(atoi(row[11])) : 0;
e.instnodrop = row[12] ? static_cast<uint8_t>(strtoul(row[12], nullptr, 10)) : 0;
e.custom_data = row[13] ? row[13] : "";
e.ornamenticon = row[14] ? static_cast<uint32_t>(strtoul(row[14], nullptr, 10)) : 0;
e.ornamentidfile = row[15] ? static_cast<uint32_t>(strtoul(row[15], nullptr, 10)) : 0;
e.ornament_icon = row[14] ? static_cast<uint32_t>(strtoul(row[14], nullptr, 10)) : 0;
e.ornament_idfile = row[15] ? static_cast<uint32_t>(strtoul(row[15], nullptr, 10)) : 0;
e.ornament_hero_model = row[16] ? static_cast<int32_t>(atoi(row[16])) : 0;
e.guid = row[17] ? strtoull(row[17], nullptr, 10) : 0;
e.item_unique_id = row[17] ? row[17] : "";
all_entries.push_back(e);
}
@ -506,23 +506,23 @@ public:
std::vector<std::string> v;
v.push_back(std::to_string(e.time_index));
v.push_back(std::to_string(e.charid));
v.push_back(std::to_string(e.slotid));
v.push_back(std::to_string(e.itemid));
v.push_back(std::to_string(e.character_id));
v.push_back(std::to_string(e.slot_id));
v.push_back(std::to_string(e.item_id));
v.push_back(std::to_string(e.charges));
v.push_back(std::to_string(e.color));
v.push_back(std::to_string(e.augslot1));
v.push_back(std::to_string(e.augslot2));
v.push_back(std::to_string(e.augslot3));
v.push_back(std::to_string(e.augslot4));
v.push_back(std::to_string(e.augslot5));
v.push_back(std::to_string(e.augslot6));
v.push_back(std::to_string(e.augment_one));
v.push_back(std::to_string(e.augment_two));
v.push_back(std::to_string(e.augment_three));
v.push_back(std::to_string(e.augment_four));
v.push_back(std::to_string(e.augment_five));
v.push_back(std::to_string(e.augment_six));
v.push_back(std::to_string(e.instnodrop));
v.push_back("'" + Strings::Escape(e.custom_data) + "'");
v.push_back(std::to_string(e.ornamenticon));
v.push_back(std::to_string(e.ornamentidfile));
v.push_back(std::to_string(e.ornament_icon));
v.push_back(std::to_string(e.ornament_idfile));
v.push_back(std::to_string(e.ornament_hero_model));
v.push_back(std::to_string(e.guid));
v.push_back("'" + Strings::Escape(e.item_unique_id) + "'");
auto results = db.QueryDatabase(
fmt::format(
@ -546,23 +546,23 @@ public:
std::vector<std::string> v;
v.push_back(std::to_string(e.time_index));
v.push_back(std::to_string(e.charid));
v.push_back(std::to_string(e.slotid));
v.push_back(std::to_string(e.itemid));
v.push_back(std::to_string(e.character_id));
v.push_back(std::to_string(e.slot_id));
v.push_back(std::to_string(e.item_id));
v.push_back(std::to_string(e.charges));
v.push_back(std::to_string(e.color));
v.push_back(std::to_string(e.augslot1));
v.push_back(std::to_string(e.augslot2));
v.push_back(std::to_string(e.augslot3));
v.push_back(std::to_string(e.augslot4));
v.push_back(std::to_string(e.augslot5));
v.push_back(std::to_string(e.augslot6));
v.push_back(std::to_string(e.augment_one));
v.push_back(std::to_string(e.augment_two));
v.push_back(std::to_string(e.augment_three));
v.push_back(std::to_string(e.augment_four));
v.push_back(std::to_string(e.augment_five));
v.push_back(std::to_string(e.augment_six));
v.push_back(std::to_string(e.instnodrop));
v.push_back("'" + Strings::Escape(e.custom_data) + "'");
v.push_back(std::to_string(e.ornamenticon));
v.push_back(std::to_string(e.ornamentidfile));
v.push_back(std::to_string(e.ornament_icon));
v.push_back(std::to_string(e.ornament_idfile));
v.push_back(std::to_string(e.ornament_hero_model));
v.push_back(std::to_string(e.guid));
v.push_back("'" + Strings::Escape(e.item_unique_id) + "'");
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
}

View File

@ -34,7 +34,7 @@ public:
uint32_t ornament_icon;
uint32_t ornament_idfile;
int32_t ornament_hero_model;
uint64_t guid;
std::string item_unique_id;
};
static std::string PrimaryKey()
@ -60,7 +60,7 @@ public:
"ornament_icon",
"ornament_idfile",
"ornament_hero_model",
"guid",
"item_unique_id",
};
}
@ -82,7 +82,7 @@ public:
"ornament_icon",
"ornament_idfile",
"ornament_hero_model",
"guid",
"item_unique_id",
};
}
@ -138,7 +138,7 @@ public:
e.ornament_icon = 0;
e.ornament_idfile = 0;
e.ornament_hero_model = 0;
e.guid = 0;
e.item_unique_id = "";
return e;
}
@ -190,7 +190,7 @@ public:
e.ornament_icon = row[12] ? static_cast<uint32_t>(strtoul(row[12], nullptr, 10)) : 0;
e.ornament_idfile = row[13] ? static_cast<uint32_t>(strtoul(row[13], nullptr, 10)) : 0;
e.ornament_hero_model = row[14] ? static_cast<int32_t>(atoi(row[14])) : 0;
e.guid = row[15] ? strtoull(row[15], nullptr, 10) : 0;
e.item_unique_id = row[15] ? row[15] : "";
return e;
}
@ -239,7 +239,7 @@ public:
v.push_back(columns[12] + " = " + std::to_string(e.ornament_icon));
v.push_back(columns[13] + " = " + std::to_string(e.ornament_idfile));
v.push_back(columns[14] + " = " + std::to_string(e.ornament_hero_model));
v.push_back(columns[15] + " = " + std::to_string(e.guid));
v.push_back(columns[15] + " = '" + Strings::Escape(e.item_unique_id) + "'");
auto results = db.QueryDatabase(
fmt::format(
@ -276,7 +276,7 @@ public:
v.push_back(std::to_string(e.ornament_icon));
v.push_back(std::to_string(e.ornament_idfile));
v.push_back(std::to_string(e.ornament_hero_model));
v.push_back(std::to_string(e.guid));
v.push_back("'" + Strings::Escape(e.item_unique_id) + "'");
auto results = db.QueryDatabase(
fmt::format(
@ -321,7 +321,7 @@ public:
v.push_back(std::to_string(e.ornament_icon));
v.push_back(std::to_string(e.ornament_idfile));
v.push_back(std::to_string(e.ornament_hero_model));
v.push_back(std::to_string(e.guid));
v.push_back("'" + Strings::Escape(e.item_unique_id) + "'");
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
}
@ -370,7 +370,7 @@ public:
e.ornament_icon = row[12] ? static_cast<uint32_t>(strtoul(row[12], nullptr, 10)) : 0;
e.ornament_idfile = row[13] ? static_cast<uint32_t>(strtoul(row[13], nullptr, 10)) : 0;
e.ornament_hero_model = row[14] ? static_cast<int32_t>(atoi(row[14])) : 0;
e.guid = row[15] ? strtoull(row[15], nullptr, 10) : 0;
e.item_unique_id = row[15] ? row[15] : "";
all_entries.push_back(e);
}
@ -410,7 +410,7 @@ public:
e.ornament_icon = row[12] ? static_cast<uint32_t>(strtoul(row[12], nullptr, 10)) : 0;
e.ornament_idfile = row[13] ? static_cast<uint32_t>(strtoul(row[13], nullptr, 10)) : 0;
e.ornament_hero_model = row[14] ? static_cast<int32_t>(atoi(row[14])) : 0;
e.guid = row[15] ? strtoull(row[15], nullptr, 10) : 0;
e.item_unique_id = row[15] ? row[15] : "";
all_entries.push_back(e);
}
@ -500,7 +500,7 @@ public:
v.push_back(std::to_string(e.ornament_icon));
v.push_back(std::to_string(e.ornament_idfile));
v.push_back(std::to_string(e.ornament_hero_model));
v.push_back(std::to_string(e.guid));
v.push_back("'" + Strings::Escape(e.item_unique_id) + "'");
auto results = db.QueryDatabase(
fmt::format(
@ -538,7 +538,7 @@ public:
v.push_back(std::to_string(e.ornament_icon));
v.push_back(std::to_string(e.ornament_idfile));
v.push_back(std::to_string(e.ornament_hero_model));
v.push_back(std::to_string(e.guid));
v.push_back("'" + Strings::Escape(e.item_unique_id) + "'");
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
}

View File

@ -19,24 +19,24 @@
class BaseTraderRepository {
public:
struct Trader {
uint64_t id;
uint32_t char_id;
uint32_t item_id;
uint32_t aug_slot_1;
uint32_t aug_slot_2;
uint32_t aug_slot_3;
uint32_t aug_slot_4;
uint32_t aug_slot_5;
uint32_t aug_slot_6;
uint32_t item_sn;
int32_t item_charges;
uint32_t item_cost;
uint8_t slot_id;
uint32_t char_entity_id;
uint32_t char_zone_id;
int32_t char_zone_instance_id;
uint8_t active_transaction;
time_t listing_date;
uint64_t id;
uint32_t character_id;
uint32_t item_id;
std::string item_unique_id;
uint32_t augment_one;
uint32_t augment_two;
uint32_t augment_three;
uint32_t augment_four;
uint32_t augment_five;
uint32_t augment_six;
int32_t item_charges;
uint32_t item_cost;
uint8_t slot_id;
uint32_t char_entity_id;
uint32_t char_zone_id;
int32_t char_zone_instance_id;
uint8_t active_transaction;
time_t listing_date;
};
static std::string PrimaryKey()
@ -48,15 +48,15 @@ public:
{
return {
"id",
"char_id",
"character_id",
"item_id",
"aug_slot_1",
"aug_slot_2",
"aug_slot_3",
"aug_slot_4",
"aug_slot_5",
"aug_slot_6",
"item_sn",
"item_unique_id",
"augment_one",
"augment_two",
"augment_three",
"augment_four",
"augment_five",
"augment_six",
"item_charges",
"item_cost",
"slot_id",
@ -72,15 +72,15 @@ public:
{
return {
"id",
"char_id",
"character_id",
"item_id",
"aug_slot_1",
"aug_slot_2",
"aug_slot_3",
"aug_slot_4",
"aug_slot_5",
"aug_slot_6",
"item_sn",
"item_unique_id",
"augment_one",
"augment_two",
"augment_three",
"augment_four",
"augment_five",
"augment_six",
"item_charges",
"item_cost",
"slot_id",
@ -130,15 +130,15 @@ public:
Trader e{};
e.id = 0;
e.char_id = 0;
e.character_id = 0;
e.item_id = 0;
e.aug_slot_1 = 0;
e.aug_slot_2 = 0;
e.aug_slot_3 = 0;
e.aug_slot_4 = 0;
e.aug_slot_5 = 0;
e.aug_slot_6 = 0;
e.item_sn = 0;
e.item_unique_id = "";
e.augment_one = 0;
e.augment_two = 0;
e.augment_three = 0;
e.augment_four = 0;
e.augment_five = 0;
e.augment_six = 0;
e.item_charges = 0;
e.item_cost = 0;
e.slot_id = 0;
@ -184,15 +184,15 @@ public:
Trader e{};
e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0;
e.char_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
e.character_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
e.item_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
e.aug_slot_1 = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
e.aug_slot_2 = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
e.aug_slot_3 = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
e.aug_slot_4 = row[6] ? static_cast<uint32_t>(strtoul(row[6], nullptr, 10)) : 0;
e.aug_slot_5 = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
e.aug_slot_6 = row[8] ? static_cast<uint32_t>(strtoul(row[8], nullptr, 10)) : 0;
e.item_sn = row[9] ? static_cast<uint32_t>(strtoul(row[9], nullptr, 10)) : 0;
e.item_unique_id = row[3] ? row[3] : "";
e.augment_one = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
e.augment_two = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
e.augment_three = row[6] ? static_cast<uint32_t>(strtoul(row[6], nullptr, 10)) : 0;
e.augment_four = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
e.augment_five = row[8] ? static_cast<uint32_t>(strtoul(row[8], nullptr, 10)) : 0;
e.augment_six = row[9] ? static_cast<uint32_t>(strtoul(row[9], nullptr, 10)) : 0;
e.item_charges = row[10] ? static_cast<int32_t>(atoi(row[10])) : 0;
e.item_cost = row[11] ? static_cast<uint32_t>(strtoul(row[11], nullptr, 10)) : 0;
e.slot_id = row[12] ? static_cast<uint8_t>(strtoul(row[12], nullptr, 10)) : 0;
@ -234,15 +234,15 @@ public:
auto columns = Columns();
v.push_back(columns[1] + " = " + std::to_string(e.char_id));
v.push_back(columns[1] + " = " + std::to_string(e.character_id));
v.push_back(columns[2] + " = " + std::to_string(e.item_id));
v.push_back(columns[3] + " = " + std::to_string(e.aug_slot_1));
v.push_back(columns[4] + " = " + std::to_string(e.aug_slot_2));
v.push_back(columns[5] + " = " + std::to_string(e.aug_slot_3));
v.push_back(columns[6] + " = " + std::to_string(e.aug_slot_4));
v.push_back(columns[7] + " = " + std::to_string(e.aug_slot_5));
v.push_back(columns[8] + " = " + std::to_string(e.aug_slot_6));
v.push_back(columns[9] + " = " + std::to_string(e.item_sn));
v.push_back(columns[3] + " = '" + Strings::Escape(e.item_unique_id) + "'");
v.push_back(columns[4] + " = " + std::to_string(e.augment_one));
v.push_back(columns[5] + " = " + std::to_string(e.augment_two));
v.push_back(columns[6] + " = " + std::to_string(e.augment_three));
v.push_back(columns[7] + " = " + std::to_string(e.augment_four));
v.push_back(columns[8] + " = " + std::to_string(e.augment_five));
v.push_back(columns[9] + " = " + std::to_string(e.augment_six));
v.push_back(columns[10] + " = " + std::to_string(e.item_charges));
v.push_back(columns[11] + " = " + std::to_string(e.item_cost));
v.push_back(columns[12] + " = " + std::to_string(e.slot_id));
@ -273,15 +273,15 @@ public:
std::vector<std::string> v;
v.push_back(std::to_string(e.id));
v.push_back(std::to_string(e.char_id));
v.push_back(std::to_string(e.character_id));
v.push_back(std::to_string(e.item_id));
v.push_back(std::to_string(e.aug_slot_1));
v.push_back(std::to_string(e.aug_slot_2));
v.push_back(std::to_string(e.aug_slot_3));
v.push_back(std::to_string(e.aug_slot_4));
v.push_back(std::to_string(e.aug_slot_5));
v.push_back(std::to_string(e.aug_slot_6));
v.push_back(std::to_string(e.item_sn));
v.push_back("'" + Strings::Escape(e.item_unique_id) + "'");
v.push_back(std::to_string(e.augment_one));
v.push_back(std::to_string(e.augment_two));
v.push_back(std::to_string(e.augment_three));
v.push_back(std::to_string(e.augment_four));
v.push_back(std::to_string(e.augment_five));
v.push_back(std::to_string(e.augment_six));
v.push_back(std::to_string(e.item_charges));
v.push_back(std::to_string(e.item_cost));
v.push_back(std::to_string(e.slot_id));
@ -320,15 +320,15 @@ public:
std::vector<std::string> v;
v.push_back(std::to_string(e.id));
v.push_back(std::to_string(e.char_id));
v.push_back(std::to_string(e.character_id));
v.push_back(std::to_string(e.item_id));
v.push_back(std::to_string(e.aug_slot_1));
v.push_back(std::to_string(e.aug_slot_2));
v.push_back(std::to_string(e.aug_slot_3));
v.push_back(std::to_string(e.aug_slot_4));
v.push_back(std::to_string(e.aug_slot_5));
v.push_back(std::to_string(e.aug_slot_6));
v.push_back(std::to_string(e.item_sn));
v.push_back("'" + Strings::Escape(e.item_unique_id) + "'");
v.push_back(std::to_string(e.augment_one));
v.push_back(std::to_string(e.augment_two));
v.push_back(std::to_string(e.augment_three));
v.push_back(std::to_string(e.augment_four));
v.push_back(std::to_string(e.augment_five));
v.push_back(std::to_string(e.augment_six));
v.push_back(std::to_string(e.item_charges));
v.push_back(std::to_string(e.item_cost));
v.push_back(std::to_string(e.slot_id));
@ -371,15 +371,15 @@ public:
Trader e{};
e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0;
e.char_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
e.character_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
e.item_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
e.aug_slot_1 = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
e.aug_slot_2 = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
e.aug_slot_3 = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
e.aug_slot_4 = row[6] ? static_cast<uint32_t>(strtoul(row[6], nullptr, 10)) : 0;
e.aug_slot_5 = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
e.aug_slot_6 = row[8] ? static_cast<uint32_t>(strtoul(row[8], nullptr, 10)) : 0;
e.item_sn = row[9] ? static_cast<uint32_t>(strtoul(row[9], nullptr, 10)) : 0;
e.item_unique_id = row[3] ? row[3] : "";
e.augment_one = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
e.augment_two = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
e.augment_three = row[6] ? static_cast<uint32_t>(strtoul(row[6], nullptr, 10)) : 0;
e.augment_four = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
e.augment_five = row[8] ? static_cast<uint32_t>(strtoul(row[8], nullptr, 10)) : 0;
e.augment_six = row[9] ? static_cast<uint32_t>(strtoul(row[9], nullptr, 10)) : 0;
e.item_charges = row[10] ? static_cast<int32_t>(atoi(row[10])) : 0;
e.item_cost = row[11] ? static_cast<uint32_t>(strtoul(row[11], nullptr, 10)) : 0;
e.slot_id = row[12] ? static_cast<uint8_t>(strtoul(row[12], nullptr, 10)) : 0;
@ -413,15 +413,15 @@ public:
Trader e{};
e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0;
e.char_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
e.character_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
e.item_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
e.aug_slot_1 = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
e.aug_slot_2 = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
e.aug_slot_3 = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
e.aug_slot_4 = row[6] ? static_cast<uint32_t>(strtoul(row[6], nullptr, 10)) : 0;
e.aug_slot_5 = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
e.aug_slot_6 = row[8] ? static_cast<uint32_t>(strtoul(row[8], nullptr, 10)) : 0;
e.item_sn = row[9] ? static_cast<uint32_t>(strtoul(row[9], nullptr, 10)) : 0;
e.item_unique_id = row[3] ? row[3] : "";
e.augment_one = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
e.augment_two = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
e.augment_three = row[6] ? static_cast<uint32_t>(strtoul(row[6], nullptr, 10)) : 0;
e.augment_four = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
e.augment_five = row[8] ? static_cast<uint32_t>(strtoul(row[8], nullptr, 10)) : 0;
e.augment_six = row[9] ? static_cast<uint32_t>(strtoul(row[9], nullptr, 10)) : 0;
e.item_charges = row[10] ? static_cast<int32_t>(atoi(row[10])) : 0;
e.item_cost = row[11] ? static_cast<uint32_t>(strtoul(row[11], nullptr, 10)) : 0;
e.slot_id = row[12] ? static_cast<uint8_t>(strtoul(row[12], nullptr, 10)) : 0;
@ -505,15 +505,15 @@ public:
std::vector<std::string> v;
v.push_back(std::to_string(e.id));
v.push_back(std::to_string(e.char_id));
v.push_back(std::to_string(e.character_id));
v.push_back(std::to_string(e.item_id));
v.push_back(std::to_string(e.aug_slot_1));
v.push_back(std::to_string(e.aug_slot_2));
v.push_back(std::to_string(e.aug_slot_3));
v.push_back(std::to_string(e.aug_slot_4));
v.push_back(std::to_string(e.aug_slot_5));
v.push_back(std::to_string(e.aug_slot_6));
v.push_back(std::to_string(e.item_sn));
v.push_back("'" + Strings::Escape(e.item_unique_id) + "'");
v.push_back(std::to_string(e.augment_one));
v.push_back(std::to_string(e.augment_two));
v.push_back(std::to_string(e.augment_three));
v.push_back(std::to_string(e.augment_four));
v.push_back(std::to_string(e.augment_five));
v.push_back(std::to_string(e.augment_six));
v.push_back(std::to_string(e.item_charges));
v.push_back(std::to_string(e.item_cost));
v.push_back(std::to_string(e.slot_id));
@ -545,15 +545,15 @@ public:
std::vector<std::string> v;
v.push_back(std::to_string(e.id));
v.push_back(std::to_string(e.char_id));
v.push_back(std::to_string(e.character_id));
v.push_back(std::to_string(e.item_id));
v.push_back(std::to_string(e.aug_slot_1));
v.push_back(std::to_string(e.aug_slot_2));
v.push_back(std::to_string(e.aug_slot_3));
v.push_back(std::to_string(e.aug_slot_4));
v.push_back(std::to_string(e.aug_slot_5));
v.push_back(std::to_string(e.aug_slot_6));
v.push_back(std::to_string(e.item_sn));
v.push_back("'" + Strings::Escape(e.item_unique_id) + "'");
v.push_back(std::to_string(e.augment_one));
v.push_back(std::to_string(e.augment_two));
v.push_back(std::to_string(e.augment_three));
v.push_back(std::to_string(e.augment_four));
v.push_back(std::to_string(e.augment_five));
v.push_back(std::to_string(e.augment_six));
v.push_back(std::to_string(e.item_charges));
v.push_back(std::to_string(e.item_cost));
v.push_back(std::to_string(e.slot_id));

View File

@ -106,8 +106,13 @@ public:
return false;
}
auto buy_lines =
BaseBuyerBuyLinesRepository::GetWhere(db, fmt::format("`buyer_id` = {}", buyer.front().id));
auto buy_lines = BaseBuyerBuyLinesRepository::GetWhere(
db,
fmt::format("`buyer_id` = '{}'", buyer.front().id)
);
if (buy_lines.empty()) {
return false;
}
std::vector<std::string> buy_line_ids{};
for (auto const &bl: buy_lines) {
@ -175,4 +180,24 @@ public:
return true;
}
static bool UpdateBuyerEntityID(Database &db, uint32 char_id, uint32 old_entity_id, uint32 new_entity_id)
{
if (!char_id || !old_entity_id || !new_entity_id) {
return false;
}
auto results = GetWhere(db, fmt::format("`char_id` = {} AND `char_entity_id` = {} LIMIT 1;", char_id, old_entity_id));
if (results.empty()) {
return false;
}
for (auto &e: results) {
e.char_entity_id = new_entity_id;
}
ReplaceMany(db, results);
return true;
}
};

View File

@ -0,0 +1,53 @@
#ifndef EQEMU_CHARACTER_OFFLINE_TRANSACTIONS_REPOSITORY_H
#define EQEMU_CHARACTER_OFFLINE_TRANSACTIONS_REPOSITORY_H
#include "../database.h"
#include "../strings.h"
#include "base/base_character_offline_transactions_repository.h"
class CharacterOfflineTransactionsRepository: public BaseCharacterOfflineTransactionsRepository {
public:
#define TRADER_TRANSACTION 1
#define BUYER_TRANSACTION 2
/**
* This file was auto generated and can be modified and extended upon
*
* Base repository methods are automatically
* generated in the "base" version of this repository. The base repository
* is immutable and to be left untouched, while methods in this class
* are used as extension methods for more specific persistence-layer
* accessors or mutators.
*
* Base Methods (Subject to be expanded upon in time)
*
* Note: Not all tables are designed appropriately to fit functionality with all base methods
*
* InsertOne
* UpdateOne
* DeleteOne
* FindOne
* GetWhere(std::string where_filter)
* DeleteWhere(std::string where_filter)
* InsertMany
* All
*
* Example custom methods in a repository
*
* CharacterOfflineTransactionsRepository::GetByZoneAndVersion(int zone_id, int zone_version)
* CharacterOfflineTransactionsRepository::GetWhereNeverExpires()
* CharacterOfflineTransactionsRepository::GetWhereXAndY()
* CharacterOfflineTransactionsRepository::DeleteWhereXAndY()
*
* Most of the above could be covered by base methods, but if you as a developer
* find yourself re-using logic for other parts of the code, its best to just make a
* method that can be re-used easily elsewhere especially if it can use a base repository
* method and encapsulate filters there
*/
// Custom extended repository methods here
};
#endif //EQEMU_CHARACTER_OFFLINE_TRANSACTIONS_REPOSITORY_H

View File

@ -1,8 +1,8 @@
#pragma once
#include "common/repositories/base/base_inventory_snapshots_repository.h"
#include "common/database.h"
#include "common/repositories/base/base_inventory_snapshots_repository.h"
#include "common/repositories/inventory_repository.h"
#include "common/strings.h"
class InventorySnapshotsRepository: public BaseInventorySnapshotsRepository {
@ -46,16 +46,15 @@ public:
// Custom extended repository methods here
static int64 CountInventorySnapshots(Database& db)
{
const std::string& query = "SELECT COUNT(*) FROM (SELECT * FROM `inventory_snapshots` a GROUP BY `charid`, `time_index`) b";
const std::string &query =
"SELECT COUNT(*) FROM (SELECT * FROM `inventory_snapshots` a GROUP BY `character_id`, `time_index`) b";
auto results = db.QueryDatabase(query);
if (!results.Success() || !results.RowCount()) {
return -1;
}
auto row = results.begin();
auto row = results.begin();
const int64 count = Strings::ToBigInt(row[0]);
if (count > std::numeric_limits<int>::max()) {
@ -68,4 +67,254 @@ public:
return count;
}
static int64 CountCharacterInvSnapshots(Database& db, uint32 character_id)
{
const std::string &query = fmt::format(
"SELECT COUNT(*) FROM (SELECT * FROM `inventory_snapshots` a WHERE "
"`character_id` = {} GROUP BY `time_index`) b",
character_id
);
auto results = db.QueryDatabase(query);
if (!results.Success() || !results.RowCount()) {
return -1;
}
auto &row = results.begin();
const int64 count = Strings::ToBigInt(row[0]);
if (count > std::numeric_limits<int>::max()) {
return -2;
}
if (count < 0) {
return -3;
}
return count;
}
static void ClearCharacterInvSnapshots(Database &db, uint32 character_id, bool from_now)
{
uint32 del_time = time(nullptr);
if (!from_now) {
del_time -= RuleI(Character, InvSnapshotHistoryD) * 86400;
}
DeleteWhere(db, fmt::format("`character_id` = {} AND `time_index` <= {}", character_id, del_time));
}
static void ListCharacterInvSnapshots(Database &db, uint32 character_id, std::list<std::pair<uint32, int>> &is_list)
{
const std::string &query = fmt::format(
"SELECT `time_index`, COUNT(*) FROM `inventory_snapshots` WHERE "
"`character_id` = {} GROUP BY `time_index` ORDER BY `time_index` DESC",
character_id
);
auto results = db.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])));
}
}
static bool ValidateCharacterInvSnapshotTimestamp(Database &db, uint32 character_id, uint32 timestamp)
{
if (!character_id || !timestamp) {
return false;
}
const std::string &query = fmt::format(
"SELECT * FROM `inventory_snapshots` WHERE `character_id` = {} "
"AND `time_index` = {} LIMIT 1",
character_id,
timestamp
);
auto results = db.QueryDatabase(query);
if (!results.Success() || results.RowCount() == 0) {
return false;
}
return true;
}
static void ParseCharacterInvSnapshot(
Database &db,
uint32 character_id,
uint32 timestamp,
std::list<std::pair<int16, uint32>> &parse_list)
{
const std::string &query = fmt::format(
"SELECT `slot_id`, `item_id` FROM `inventory_snapshots` "
"WHERE `character_id` = {} AND `time_index` = {} ORDER BY `slot_id`",
character_id,
timestamp
);
auto results = db.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])));
}
}
static void DivergeCharacterInvSnapshotFromInventory(
Database &db,
uint32 character_id,
uint32 timestamp,
std::list<std::pair<int16, uint32>> &compare_list)
{
const std::string &query = fmt::format(
"SELECT slot_id, item_id FROM `inventory_snapshots` "
"WHERE `time_index` = {0} AND `character_id` = {1} AND `slot_id` NOT IN ("
"SELECT a.`slot_id` FROM `inventory_snapshots` a JOIN `inventory` b USING (`slot_id`, `item_id`) "
"WHERE a.`time_index` = {0} AND a.`character_id` = {1} AND b.`character_id` = {1})",
timestamp,
character_id
);
auto results = db.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])));
}
}
static void DivergeCharacterInventoryFromInvSnapshot(
Database &db, uint32 character_id, uint32 timestamp, std::list<std::pair<int16, uint32>> &compare_list)
{
const std::string &query = fmt::format(
"SELECT `slot_id`, `item_id` FROM `inventory` WHERE "
"`character_id` = {0} AND `slot_id` NOT IN ("
"SELECT a.`slot_id` FROM `inventory` a JOIN `inventory_snapshots` b USING (`slot_id`, `item_id`) "
"WHERE b.`time_index` = {1} AND b.`character_id` = {0} AND a.`character_id` = {0})",
character_id,
timestamp
);
auto results = db.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])));
}
}
static bool SaveCharacterInvSnapshot(Database &db, uint32 character_id)
{
uint32 time_index = time(nullptr);
std::vector<InventorySnapshots> queue{};
auto inventory = InventoryRepository::GetWhere(db, fmt::format("`character_id` = {}", character_id));
if (inventory.empty()) {
LogError("Character ID [{}] inventory is empty. Snapshot not created", character_id);
return false;
}
for (auto const &i: inventory) {
auto s = NewEntity();
s.character_id = i.character_id;
s.item_id = i.item_id;
s.item_unique_id = i.item_unique_id;
s.augment_one = i.augment_one;
s.augment_two = i.augment_two;
s.augment_three = i.augment_three;
s.augment_four = i.augment_four;
s.augment_five = i.augment_five;
s.augment_six = i.augment_six;
s.charges = i.charges;
s.color = i.color;
s.custom_data = i.custom_data;
s.instnodrop = i.instnodrop;
s.ornament_hero_model = i.ornament_hero_model;
s.ornament_icon = i.ornament_icon;
s.ornament_idfile = i.ornament_idfile;
s.slot_id = i.slot_id;
s.time_index = time_index;
s.item_unique_id = i.item_unique_id;
queue.push_back(s);
}
if (queue.empty()) {
LogError("Character ID [{}] inventory is empty. Snapshot not created", character_id);
return false;
}
if (!InsertMany(db, queue)) {
LogError("Failed to created inventory snapshot for [{}]", character_id);
return false;
}
LogInventory("Created inventory snapshot for [{}] with ([{}]) items", character_id, queue.size());
return true;
}
static bool RestoreCharacterInvSnapshot(Database &db, uint32 character_id, uint32 timestamp)
{
auto snapshot = GetWhere(db, fmt::format("`character_id` = {} AND `time_index` = {}", character_id, timestamp));
if (snapshot.empty()) {
LogError("The snapshot requested could not be found. Restore failed for character id [{}] @ [{}] failed",
character_id,
timestamp
);
return false;
}
std::vector<InventoryRepository::Inventory> queue{};
for (auto const &i: snapshot) {
auto e = InventoryRepository::NewEntity();
e.character_id = i.character_id;
e.item_id = i.item_id;
e.item_unique_id = i.item_unique_id;
e.augment_one = i.augment_one;
e.augment_two = i.augment_two;
e.augment_three = i.augment_three;
e.augment_four = i.augment_four;
e.augment_five = i.augment_five;
e.augment_six = i.augment_six;
e.charges = i.charges;
e.color = i.color;
e.custom_data = i.custom_data;
e.instnodrop = i.instnodrop;
e.ornament_hero_model = i.ornament_hero_model;
e.ornament_icon = i.ornament_icon;
e.ornament_idfile = i.ornament_idfile;
e.slot_id = i.slot_id;
e.item_unique_id = i.item_unique_id;
queue.push_back(e);
}
if (queue.empty()) {
LogError("The snapshot is empty. Restore failed for character id [{}] @ [{}] failed", character_id, timestamp);
return false;
}
InventoryRepository::DeleteWhere(db, fmt::format("`character_id` = {}", character_id));
if (!InventoryRepository::InsertMany(db, queue)) {
LogError("A database error occurred. Restore failed for character id [{}] @ [{}] failed", character_id, timestamp);
return false;
}
LogInventory(
"Restore complete for character id [{}] with snapshot @ [{}] with [{}] entries",
character_id,
timestamp,
queue.size()
);
return true;
}
};

View File

@ -0,0 +1,68 @@
#pragma once
#include "common/database.h"
#include "common/strings.h"
#include "common/item_instance.h"
class ItemUniqueIdReservationsRepository {
public:
static bool Reserve(Database &db, const std::string &item_unique_id)
{
if (item_unique_id.empty()) {
return false;
}
auto results = db.QueryDatabase(
fmt::format(
"INSERT IGNORE INTO item_unique_id_reservations (item_unique_id, reserved_at) VALUES ('{}', NOW())",
Strings::Escape(item_unique_id)
)
);
return results.Success();
}
static std::string ReserveNew(Database &db, uint32 max_attempts = 64)
{
for (uint32 attempt = 0; attempt < max_attempts; ++attempt) {
auto candidate = EQ::UniqueHashGenerator::generate();
auto results = db.QueryDatabase(
fmt::format(
"INSERT INTO item_unique_id_reservations (item_unique_id, reserved_at) VALUES ('{}', NOW())",
Strings::Escape(candidate)
)
);
if (results.Success()) {
return candidate;
}
if (results.ErrorNumber() != 1062) {
LogError(
"Failed reserving item_unique_id [{}] (attempt {}): ({}) {}",
candidate,
attempt + 1,
results.ErrorNumber(),
results.ErrorMessage()
);
break;
}
}
return {};
}
static bool PopulateFromTable(Database &db, const std::string &table_name, const std::string &column_name)
{
auto results = db.QueryDatabase(
fmt::format(
"INSERT IGNORE INTO item_unique_id_reservations (item_unique_id, reserved_at) "
"SELECT DISTINCT {1}, NOW() FROM {0} WHERE {1} IS NOT NULL AND {1} <> ''",
table_name,
column_name
)
);
return results.Success();
}
};

View File

@ -47,7 +47,7 @@ public:
static std::unordered_map<uint32, Bazaar_Results> GetItemsForBazaarSearch(
Database& db,
const std::vector<std::string> &search_ids,
const std::unordered_set<std::string> &search_ids,
const std::string &name,
const std::string &field_criteria_items,
const std::string &where_criteria_items,
@ -57,7 +57,7 @@ public:
auto query = fmt::format(
"SELECT id, name, stackable, icon, {} "
"FROM items "
"WHERE `name` LIKE '%%{}%%' AND {} AND id IN({}) "
"WHERE `name` LIKE '%{}%' AND {} AND id IN({}) "
"ORDER BY id ASC",
field_criteria_items,
Strings::Escape(name),

View File

@ -0,0 +1,98 @@
#pragma once
#include "common/database.h"
#include "common/strings.h"
class OfflineCharacterSessionsRepository {
public:
struct OfflineCharacterSession {
uint64_t id{0};
uint32_t account_id{0};
uint32_t character_id{0};
std::string mode{};
uint32_t zone_id{0};
int32_t instance_id{0};
uint32_t entity_id{0};
time_t started_at{0};
};
static OfflineCharacterSession GetByAccountId(Database &db, uint32 account_id)
{
OfflineCharacterSession session{};
auto results = db.QueryDatabase(
fmt::format(
"SELECT id, account_id, character_id, mode, zone_id, instance_id, entity_id, UNIX_TIMESTAMP(started_at) "
"FROM offline_character_sessions WHERE account_id = {} LIMIT 1",
account_id
)
);
if (!results.Success() || results.RowCount() == 0) {
return session;
}
auto row = results.begin();
session.id = row[0] ? strtoull(row[0], nullptr, 10) : 0;
session.account_id = row[1] ? Strings::ToUnsignedInt(row[1]) : 0;
session.character_id = row[2] ? Strings::ToUnsignedInt(row[2]) : 0;
session.mode = row[3] ? row[3] : "";
session.zone_id = row[4] ? Strings::ToUnsignedInt(row[4]) : 0;
session.instance_id = row[5] ? Strings::ToInt(row[5]) : 0;
session.entity_id = row[6] ? Strings::ToUnsignedInt(row[6]) : 0;
session.started_at = row[7] ? Strings::ToUnsignedBigInt(row[7]) : 0;
return session;
}
static bool ExistsByAccountId(Database &db, uint32 account_id)
{
return GetByAccountId(db, account_id).id != 0;
}
static bool Upsert(
Database &db,
uint32 account_id,
uint32 character_id,
const std::string &mode,
uint32 zone_id,
int32 instance_id,
uint32 entity_id
)
{
auto results = db.QueryDatabase(
fmt::format(
"INSERT INTO offline_character_sessions (account_id, character_id, mode, zone_id, instance_id, entity_id, started_at) "
"VALUES ({}, {}, '{}', {}, {}, {}, NOW()) "
"ON DUPLICATE KEY UPDATE character_id = VALUES(character_id), mode = VALUES(mode), zone_id = VALUES(zone_id), "
"instance_id = VALUES(instance_id), entity_id = VALUES(entity_id), started_at = VALUES(started_at)",
account_id,
character_id,
Strings::Escape(mode),
zone_id,
instance_id,
entity_id
)
);
return results.Success();
}
static bool DeleteByAccountId(Database &db, uint32 account_id)
{
return db.QueryDatabase(
fmt::format("DELETE FROM offline_character_sessions WHERE account_id = {}", account_id)
).Success();
}
static bool DeleteByCharacterId(Database &db, uint32 character_id)
{
return db.QueryDatabase(
fmt::format("DELETE FROM offline_character_sessions WHERE character_id = {}", character_id)
).Success();
}
static bool Truncate(Database &db)
{
return db.QueryDatabase("TRUNCATE TABLE offline_character_sessions").Success();
}
};

View File

@ -30,8 +30,12 @@ public:
};
struct BazaarTraderSearch_Struct {
Trader trader;
Trader trader;
std::string trader_name;
std::string name;
bool stackable;
uint32 icon;
uint32 stats;
};
struct WelcomeData_Struct {
@ -59,9 +63,9 @@ public:
if (RuleB(Bazaar, UseAlternateBazaarSearch)) {
results = db.QueryDatabase(fmt::format(
"SELECT DISTINCT(t.char_id), t.char_zone_id, t.char_zone_instance_id, t.char_entity_id, c.name "
"SELECT DISTINCT(t.character_id), t.char_zone_id, t.char_zone_instance_id, t.char_entity_id, c.name "
"FROM trader AS t "
"JOIN character_data AS c ON t.char_id = c.id "
"JOIN character_data AS c ON t.character_id = c.id "
"WHERE t.char_zone_instance_id = {} "
"ORDER BY t.char_zone_instance_id ASC "
"LIMIT {}",
@ -70,13 +74,14 @@ public:
);
}
else {
results = db.QueryDatabase(fmt::format(
"SELECT DISTINCT(t.char_id), t.char_zone_id, t.char_zone_instance_id, t.char_entity_id, c.name "
"FROM trader AS t "
"JOIN character_data AS c ON t.char_id = c.id "
"ORDER BY t.char_zone_instance_id ASC "
"LIMIT {}",
max_results)
results = db.QueryDatabase(
fmt::format(
"SELECT DISTINCT(t.character_id), t.char_zone_id, t.char_zone_instance_id, t.char_entity_id, c.name "
"FROM trader AS t "
"JOIN character_data AS c ON t.character_id = c.id "
"ORDER BY t.char_zone_instance_id ASC "
"LIMIT {}",
max_results)
);
}
@ -102,7 +107,7 @@ public:
{
WelcomeData_Struct e{};
auto results = db.QueryDatabase("SELECT COUNT(DISTINCT char_id), count(char_id) FROM trader;");
auto results = db.QueryDatabase("SELECT COUNT(DISTINCT character_id), count(character_id) FROM trader;");
if (!results.RowCount()) {
return e;
@ -114,15 +119,15 @@ public:
return e;
}
static int UpdateItem(Database &db, uint32 char_id, uint32 new_price, uint32 item_id, uint32 item_charges)
static int UpdateItem(Database &db, uint32 character_id, uint32 new_price, uint32 item_id, uint32 item_charges)
{
std::vector<BaseTraderRepository::Trader> items{};
if (item_charges == 0) {
items = GetWhere(
db,
fmt::format(
"char_id = '{}' AND item_id = '{}'",
char_id,
"character_id = {} AND item_id = {}",
character_id,
item_id
)
);
@ -131,8 +136,8 @@ public:
items = GetWhere(
db,
fmt::format(
"char_id = '{}' AND item_id = '{}' AND item_charges = '{}'",
char_id,
"character_id = {} AND item_id = {} AND item_charges = {}",
character_id,
item_id,
item_charges
)
@ -156,7 +161,7 @@ public:
Trader item{};
auto query = fmt::format(
"SELECT t.char_id, t.item_id, t.serialnumber, t.charges, t.item_cost, t.slot_id, t.entity_id FROM trader AS t "
"SELECT t.character_id, t.item_id, t.item_unique.id, t.charges, t.item_cost, t.slot_id, t.entity_id FROM trader AS t "
"WHERE t.entity_id = {} AND t.item_id = {} AND t.item_cost = {} "
"LIMIT 1;",
trader_id,
@ -169,41 +174,103 @@ public:
return item;
}
auto row = results.begin();
item.char_id = Strings::ToInt(row[0]);
item.item_id = Strings::ToInt(row[1]);
item.item_sn = Strings::ToInt(row[2]);
item.item_charges = Strings::ToInt(row[3]);
item.item_cost = Strings::ToInt(row[4]);
item.slot_id = Strings::ToInt(row[5]);
auto row = results.begin();
item.character_id = Strings::ToInt(row[0]);
item.item_id = Strings::ToInt(row[1]);
item.item_unique_id = row[2] ? row[2] : "";
item.item_charges = Strings::ToInt(row[3]);
item.item_cost = Strings::ToInt(row[4]);
item.slot_id = Strings::ToInt(row[5]);
return item;
}
static int UpdateQuantity(Database &db, uint32 char_id, uint32 serial_number, int16 quantity)
static int UpdateQuantity(Database &db, const std::string &item_unique_id, int16 quantity)
{
const auto trader_item = GetWhere(
db,
fmt::format("char_id = '{}' AND item_sn = '{}' ", char_id, serial_number)
fmt::format("`item_unique_id` = '{}' ", item_unique_id)
);
if (trader_item.empty() || trader_item.size() > 1) {
return 0;
}
auto m = trader_item[0];
auto m = trader_item[0];
m.item_charges = quantity;
m.listing_date = time(nullptr);
return UpdateOne(db, m);
}
static Trader GetItemBySerialNumber(Database &db, uint32 serial_number, uint32 trader_id)
static std::vector<Trader> UpdatePrice(Database &db, const std::string &item_unique_id, uint32 price)
{
std::vector<Trader> all_entries{};
auto target_listing = GetWhere(
db,
fmt::format("`item_unique_id` = '{}' LIMIT 1", item_unique_id)
);
if (target_listing.empty()) {
return all_entries;
}
auto target = target_listing.front();
const auto query = fmt::format(
"UPDATE trader SET `item_cost` = {}, `listing_date` = FROM_UNIXTIME({}) WHERE `character_id` = {} AND "
"`item_unique_id` = '{}'",
price,
time(nullptr),
target.character_id,
item_unique_id
);
auto results = db.QueryDatabase(query);
if (results.RowsAffected() == 0) {
return all_entries;
}
all_entries = GetWhere(
db,
fmt::format(
"`character_id` = {} AND `item_unique_id` = '{}'",
target.character_id,
item_unique_id
)
);
return all_entries;
}
static bool UpdateEntityId(Database &db, uint32 character_id, uint32 old_entity_id, uint32 new_entity_id)
{
if (!character_id || !old_entity_id || !new_entity_id) {
return false;
}
auto results = GetWhere(
db,
fmt::format("`character_id` = {} AND `char_entity_id` = {}", character_id, old_entity_id)
);
if (results.empty()) {
return false;
}
for (auto &entry : results) {
entry.char_entity_id = new_entity_id;
}
return ReplaceMany(db, results);
}
static Trader GetItemByItemUniqueNumber(Database &db, std::string &item_unique_id)
{
Trader e{};
const auto trader_item = GetWhere(
db,
fmt::format("`char_id` = '{}' AND `item_sn` = '{}' LIMIT 1", trader_id, serial_number)
fmt::format("`item_unique_id` = '{}' LIMIT 1", item_unique_id)
);
if (trader_item.empty()) {
@ -213,13 +280,12 @@ public:
return trader_item.at(0);
}
static Trader GetItemBySerialNumber(Database &db, std::string serial_number, uint32 trader_id)
static Trader GetItemByItemUniqueNumber(Database &db, const char* item_unique_id)
{
Trader e{};
auto sn = Strings::ToUnsignedBigInt(serial_number);
const auto trader_item = GetWhere(
db,
fmt::format("`char_id` = '{}' AND `item_sn` = '{}' LIMIT 1", trader_id, sn)
fmt::format("`item_unique_id` = '{}' LIMIT 1", item_unique_id)
);
if (trader_item.empty()) {
@ -257,21 +323,16 @@ public:
return DeleteWhere(db, fmt::format("`id` IN({})", Strings::Implode(",", delete_ids)));
}
static DistinctTraders_Struct GetTraderByInstanceAndSerialnumber(
Database &db,
uint32 instance_id,
const char *serial_number
)
static DistinctTraders_Struct GetTraderByItemUniqueNumber(Database &db, std::string &item_unique_id)
{
DistinctTraders_Struct trader{};
auto query = fmt::format(
"SELECT t.id, t.char_id, c.name "
"SELECT t.id, t.character_id, c.name "
"FROM trader AS t "
"JOIN character_data AS c ON c.id = t.char_id "
"WHERE t.char_zone_id = 151 AND t.char_zone_instance_id = {} AND t.item_sn = {} LIMIT 1",
instance_id,
serial_number
"JOIN character_data AS c ON c.id = t.character_id "
"WHERE t.item_unique_id = '{}' LIMIT 1",
item_unique_id
);
auto results = db.QueryDatabase(query);
@ -281,7 +342,6 @@ public:
}
auto row = results.begin();
std::string name = row[2];
trader.trader_id = Strings::ToUnsignedInt(row[1]);
trader.trader_name = row[2] ? row[2] : "";
@ -290,14 +350,23 @@ public:
static std::vector<BazaarTraderSearch_Struct> GetBazaarTraderDetails(
Database &db,
std::string &search_criteria_trader
const std::string &search_criteria_trader,
const std::string &name,
const std::string &field_criteria_items,
const std::string &where_criteria_items,
uint32 max_results
)
{
std::vector<BazaarTraderSearch_Struct> all_entries{};
auto query = fmt::format(
"SELECT trader.*, c.`name` FROM `trader` INNER JOIN character_data AS c ON trader.char_id = c.id "
"WHERE {} ORDER BY trader.char_id ASC",
"SELECT trader.id, trader.character_id, trader.item_id, trader.item_unique_id, trader.augment_one, "
"trader.augment_two, trader.augment_three, trader.augment_four, trader.augment_five, trader.augment_six, "
"trader.item_charges, trader.item_cost, trader.slot_id, trader.char_entity_id, trader.char_zone_id, "
"trader.char_zone_instance_id, trader.active_transaction, c.`name` FROM `trader` "
"INNER JOIN character_data AS c ON trader.character_id = c.id "
"WHERE {} "
"ORDER BY trader.character_id ASC",
search_criteria_trader
);
@ -312,15 +381,15 @@ public:
BazaarTraderSearch_Struct e{};
e.trader.id = row[0] ? strtoull(row[0], nullptr, 10) : 0;
e.trader.char_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
e.trader.character_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
e.trader.item_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
e.trader.aug_slot_1 = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
e.trader.aug_slot_2 = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
e.trader.aug_slot_3 = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
e.trader.aug_slot_4 = row[6] ? static_cast<uint32_t>(strtoul(row[6], nullptr, 10)) : 0;
e.trader.aug_slot_5 = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
e.trader.aug_slot_6 = row[8] ? static_cast<uint32_t>(strtoul(row[8], nullptr, 10)) : 0;
e.trader.item_sn = row[9] ? static_cast<uint32_t>(strtoul(row[9], nullptr, 10)) : 0;
e.trader.item_unique_id = row[3] ? row[3] : std::string("");
e.trader.augment_one = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
e.trader.augment_two = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
e.trader.augment_three = row[6] ? static_cast<uint32_t>(strtoul(row[6], nullptr, 10)) : 0;
e.trader.augment_four = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
e.trader.augment_five = row[8] ? static_cast<uint32_t>(strtoul(row[8], nullptr, 10)) : 0;
e.trader.augment_six = row[9] ? static_cast<uint32_t>(strtoul(row[9], nullptr, 10)) : 0;
e.trader.item_charges = row[10] ? static_cast<int32_t>(atoi(row[10])) : 0;
e.trader.item_cost = row[11] ? static_cast<uint32_t>(strtoul(row[11], nullptr, 10)) : 0;
e.trader.slot_id = row[12] ? static_cast<uint8_t>(strtoul(row[12], nullptr, 10)) : 0;
@ -335,4 +404,47 @@ public:
return all_entries;
}
static Trader GetAccountZoneIdAndInstanceIdByAccountId(Database &db, uint32 account_id)
{
auto trader_query = fmt::format(
"SELECT t.id, t.character_id, t.char_zone_id, t.char_zone_instance_id "
"FROM trader AS t "
"WHERE t.character_id IN(SELECT c.id FROM character_data AS c WHERE c.account_id = {}) "
"LIMIT 1;",
account_id
);
auto buyer_query = fmt::format(
"SELECT t.id, t.char_id, t.char_zone_id, t.char_zone_instance_id "
"FROM buyer AS t "
"WHERE t.char_id IN(SELECT c.id FROM character_data AS c WHERE c.account_id = {}) "
"LIMIT 1;",
account_id
);
Trader e{};
auto trader_results = db.QueryDatabase(trader_query);
auto buyer_results = db.QueryDatabase(buyer_query);
if (trader_results.RowCount() == 0 && buyer_results.RowCount() == 0) {
return e;
}
MySQLRequestRow row;
if (trader_results.RowCount() > 0) {
row = trader_results.begin();
}
if (buyer_results.RowCount() > 0) {
row = buyer_results.begin();
}
e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0;
e.character_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
e.char_zone_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
e.char_zone_instance_id = row[3] ? static_cast<int32_t>(atoi(row[3])) : 0;
return e;
}
};

View File

@ -229,10 +229,12 @@
#define ServerOP_LSPlayerJoinWorld 0x3007
#define ServerOP_LSPlayerZoneChange 0x3008
#define ServerOP_UsertoWorldReqLeg 0xAB00
#define ServerOP_UsertoWorldRespLeg 0xAB01
#define ServerOP_UsertoWorldReq 0xAB02
#define ServerOP_UsertoWorldResp 0xAB03
#define ServerOP_UsertoWorldReqLeg 0xAB00
#define ServerOP_UsertoWorldRespLeg 0xAB01
#define ServerOP_UsertoWorldReq 0xAB02
#define ServerOP_UsertoWorldResp 0xAB03
#define ServerOP_UsertoWorldCancelOfflineRequest 0xAB04
#define ServerOP_UsertoWorldCancelOfflineResponse 0xAB05
#define ServerOP_LauncherConnectInfo 0x3000
#define ServerOP_LauncherZoneRequest 0x3001
@ -360,14 +362,24 @@ enum { QSG_LFGuild_PlayerMatches = 0, QSG_LFGuild_UpdatePlayerInfo, QSG_LFGuild_
enum {
UserToWorldStatusWorldUnavail = 0,
UserToWorldStatusSuccess = 1,
UserToWorldStatusSuspended = -1,
UserToWorldStatusBanned = -2,
UserToWorldStatusWorldAtCapacity = -3,
UserToWorldStatusAlreadyOnline = -4
UserToWorldStatusWorldUnavail = 0,
UserToWorldStatusSuccess = 1,
UserToWorldStatusSuspended = -1,
UserToWorldStatusBanned = -2,
UserToWorldStatusWorldAtCapacity = -3,
UserToWorldStatusAlreadyOnline = -4,
UserToWorldStatusOffilineTraderBuyer = -5
};
enum {
BazaarPurchaseFailed = 0,
BazaarPurchaseSuccess = 1,
BazaarPurchaseBuyerCompleteSendToSeller = 2,
BazaarPurchaseSellerCompleteSendToBuyer = 3,
BazaarPurchaseBuyerFailed = 4,
BazaarPurchaseBuyerSuccess = 5,
BazaarPurchaseTraderFailed = 6
};
/************ PACKET RELATED STRUCT ************/
class ServerPacket
{
@ -559,6 +571,9 @@ struct ServerClientList_Struct {
uint8 LFGToLevel;
bool LFGMatchFilter;
char LFGComments[64];
bool trader;
bool buyer;
bool offline;
};
struct ServerClientListKeepAlive_Struct {
@ -1022,6 +1037,7 @@ struct ServerGuildMemberUpdate_Struct {
char member_name[64];
uint32 zone_id;
uint32 last_seen;
uint32 offline_mode;
};
struct ServerGuildPermissionUpdate_Struct {
@ -1776,8 +1792,15 @@ struct BazaarPurchaseMessaging_Struct {
uint32 item_aug_5;
uint32 item_aug_6;
uint32 buyer_id;
uint32 item_quantity_available;
uint32 item_quantity;
int16 item_charges;
uint32 id;
uint32 trader_zone_id;
uint32 trader_zone_instance_id;
uint32 buyer_zone_id;
uint32 buyer_zone_instance_id;
uint32 transaction_status;
bool offline_purchase;
};
#pragma pack(pop)

View File

@ -275,7 +275,12 @@ bool SharedDatabase::UpdateInventorySlot(uint32 char_id, const EQ::ItemInstance*
e.ornament_icon = inst->GetOrnamentationIcon();
e.ornament_idfile = inst->GetOrnamentationIDFile();
e.ornament_hero_model = inst->GetOrnamentHeroModel();
e.guid = inst->GetSerialNumber();
e.item_unique_id = inst->GetUniqueID();
if (!EnsureItemUniqueId(e.item_unique_id)) {
return false;
}
const_cast<EQ::ItemInstance *>(inst)->SetUniqueID(e.item_unique_id);
const int replaced = InventoryRepository::ReplaceOne(*this, e);
@ -325,7 +330,12 @@ bool SharedDatabase::UpdateSharedBankSlot(uint32 char_id, const EQ::ItemInstance
e.ornament_icon = inst->GetOrnamentationIcon();
e.ornament_idfile = inst->GetOrnamentationIDFile();
e.ornament_hero_model = inst->GetOrnamentHeroModel();
e.guid = inst->GetSerialNumber();
e.item_unique_id = inst->GetUniqueID();
if (!EnsureItemUniqueId(e.item_unique_id)) {
return false;
}
const_cast<EQ::ItemInstance *>(inst)->SetUniqueID(e.item_unique_id);
const int replaced = SharedbankRepository::ReplaceOne(*this, e);
@ -630,12 +640,6 @@ bool SharedDatabase::GetInventory(Client *c)
return false;
}
for (auto const& row: results) {
if (row.guid != 0) {
EQ::ItemInstance::AddGUIDToMap(row.guid);
}
}
const auto timestamps = GetItemRecastTimestamps(char_id);
auto cv_conflict = false;
const auto pmask = inv.GetLookup()->PossessionsBitmask;
@ -713,6 +717,21 @@ bool SharedDatabase::GetInventory(Client *c)
inst->SetOrnamentationIDFile(ornament_idfile);
inst->SetOrnamentHeroModel(item->HerosForgeModel);
//Mass conversion handled by world
//This remains as a backup. Should not be required.
if (row.item_unique_id.empty()) {
if (!EnsureItemUniqueId(row.item_unique_id)) {
continue;
}
inst->SetUniqueID(row.item_unique_id);
queue.push_back(row);
}
else {
ReserveItemUniqueId(row.item_unique_id);
inst->SetUniqueID(row.item_unique_id);
}
if (
instnodrop ||
(
@ -727,7 +746,7 @@ bool SharedDatabase::GetInventory(Client *c)
inst->SetColor(color);
}
if (charges == std::numeric_limits<int16>::max()) {
if (charges > std::numeric_limits<int16>::max()) {
inst->SetCharges(-1);
} else if (charges == 0 && inst->IsStackable()) {
// Stackable items need a minimum charge of 1 remain moveable.
@ -808,8 +827,7 @@ bool SharedDatabase::GetInventory(Client *c)
put_slot_id = inv.PutItem(slot_id, *inst);
}
row.guid = inst->GetSerialNumber();
queue.push_back(row);
//queue.push_back(row);
safe_delete(inst);
@ -839,8 +857,6 @@ bool SharedDatabase::GetInventory(Client *c)
InventoryRepository::ReplaceMany(*this, queue);
}
EQ::ItemInstance::ClearGUIDMap();
// Retrieve shared inventory
return GetSharedBank(char_id, &inv, true);
}
@ -1401,7 +1417,7 @@ EQ::ItemInstance* SharedDatabase::CreateItem(
return inst;
}
EQ::ItemInstance* SharedDatabase::CreateBaseItem(const EQ::ItemData* item, int16 charges) {
EQ::ItemInstance* SharedDatabase::CreateBaseItem(const EQ::ItemData* item, int16 charges, const std::string &item_unique_id) {
EQ::ItemInstance* inst = nullptr;
if (item) {
// if maxcharges is -1 that means it is an unlimited use item.
@ -1415,7 +1431,7 @@ EQ::ItemInstance* SharedDatabase::CreateBaseItem(const EQ::ItemData* item, int16
charges = 1;
}
inst = new EQ::ItemInstance(item, charges);
inst = new EQ::ItemInstance(item, item_unique_id, charges);
if (!inst) {
LogError("Error: valid item data returned a null reference for EQ::ItemInstance creation in SharedDatabase::CreateBaseItem()");

View File

@ -154,7 +154,7 @@ public:
uint32 ornamentidfile = 0,
uint32 ornament_hero_model = 0
);
EQ::ItemInstance *CreateBaseItem(const EQ::ItemData *item, int16 charges = 0);
EQ::ItemInstance *CreateBaseItem(const EQ::ItemData *item, int16 charges = 0, const std::string &item_unique_id = "");
void GetItemsCount(int32& item_count, uint32& max_id);
void LoadItems(void *data, uint32 size, int32 items, uint32 max_item_id);

View File

@ -960,3 +960,22 @@ bool Strings::IsValidJson(const std::string &json)
return result;
}
std::string Strings::Implode(const std::string& glue, std::unordered_set<std::string> src)
{
if (src.empty()) {
return {};
}
std::ostringstream output;
std::unordered_set<std::string>::iterator src_iter;
for (src_iter = src.begin(); src_iter != src.end(); src_iter++) {
output << *src_iter << glue;
}
std::string final_output = output.str();
final_output.resize(output.str().size() - glue.size());
return final_output;
}

View File

@ -45,6 +45,7 @@
#include <string>
#include <type_traits>
#include <vector>
#include <unordered_set>
class Strings {
public:
@ -76,6 +77,7 @@ public:
static std::string Escape(const std::string &s);
static std::string GetBetween(const std::string &s, std::string start_delim, std::string stop_delim);
static std::string Implode(const std::string& glue, std::vector<std::string> src);
static std::string Implode(const std::string& glue, std::unordered_set<std::string> src);
static std::string Join(const std::vector<std::string> &ar, const std::string &delim);
static std::string Join(const std::vector<uint32_t> &ar, const std::string &delim);
static std::string MillisecondsToTime(int duration);

View File

@ -41,6 +41,6 @@
* Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt
*/
#define CURRENT_BINARY_DATABASE_VERSION 9330
#define CURRENT_BINARY_DATABASE_VERSION 9340
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9054
#define CUSTOM_BINARY_DATABASE_VERSION 0

View File

@ -0,0 +1,109 @@
# Bazaar Item Unique ID And Offline Trading Rollout
## Purpose
This rollout converts persisted item identity and offline trader or buyer session state to the new production-safe model. The migration is designed for a maintenance window and explicitly clears any in-flight trader, buyer, and offline sessions during cutover.
Do not reopen the server until every verification step passes.
## Preconditions
- Schedule a maintenance window.
- Stop new logins before beginning the migration.
- Ensure the `world` binary you are deploying includes this branch.
- Ensure operators have credentials to run schema updates and database dump commands.
## Mandatory Backup
Take a backup before any schema or migration command:
```powershell
world database:dump --player-tables --login-tables --dump-path=backups --compress
```
If you use a separate database backup process, complete it before continuing.
## Local Dev Validation
Run these commands against the local dev database before production:
```powershell
world database:updates --skip-backup
world database:item-unique-ids --preflight --verbose
world database:item-unique-ids --migrate --verbose
world database:item-unique-ids --verify --verbose
```
Validate these gameplay scenarios after the migration:
- Two traders listing the same item at different prices.
- One trader changing a price without affecting another trader.
- Offline trader purchase.
- Offline buyer purchase.
- Parcel retrieval for rows that previously had missing `item_unique_id` values.
- Alternate bazaar shard search.
## Production Sequence
1. Bring the server into maintenance mode and stop new gameplay activity.
2. Take the mandatory backup.
3. Apply schema updates:
```powershell
world database:updates
```
4. Run the migration preflight:
```powershell
world database:item-unique-ids --preflight --verbose
```
5. If preflight reports missing schema, duplicate live item IDs, or other unexpected errors, stop and resolve them before continuing.
6. Run the migration:
```powershell
world database:item-unique-ids --migrate --verbose
```
This step clears active trader, buyer, and offline session state. Players must re-enter those modes after deploy.
7. Run final verification:
```powershell
world database:item-unique-ids --verify --verbose
```
8. Reopen the server only after verification passes.
## What Preflight And Verify Must Show
The migration is not complete unless all of the following are true:
- `inventory`, `sharedbank`, and `trader` contain no null or blank `item_unique_id` values.
- `character_parcels`, `character_parcels_containers`, and `inventory_snapshots` contain no null or blank `item_unique_id` values.
- `inventory`, `sharedbank`, and `trader` contain no duplicate `item_unique_id` groups.
- There are no cross-table duplicate live `item_unique_id` values across `inventory`, `sharedbank`, and `trader`.
- `offline_character_sessions` is empty after migration.
- `account.offline` has no rows left set to `1`.
## Rollback Criteria
Rollback instead of reopening the server if any of the following occur:
- `world database:updates` fails.
- `world database:item-unique-ids --preflight` reports missing required schema.
- `world database:item-unique-ids --migrate` or `--verify` reports missing IDs, duplicate IDs, or stale offline session state.
- Bazaar listing, offline trader, offline buyer, or parcel retrieval smoke tests fail after migration.
## Rollback Actions
1. Keep the server in maintenance mode.
2. Restore the database backup taken before the rollout.
3. Redeploy the previous server build.
4. Confirm login, trader, buyer, and parcel behavior on the restored build before reopening the server.
## Notes
- `world database:item-unique-ids --keep-trading-state` exists for non-production diagnostics only. Do not use it during production cutover.
- The migration command expects schema updates to have been applied first. If required tables or columns are missing, the command fails and should not be bypassed.

View File

@ -71,6 +71,25 @@ bool Client::Process()
SendPlayToWorld((const char *) app->pBuffer);
break;
}
case OP_CancelOfflineTrader: {
if (app->Size() < sizeof(CancelOfflineTrader)) {
LogError("Play received but it is too small, discarding");
break;
}
safe_delete_array(app->pBuffer);
auto buffer = new unsigned char[sizeof(PlayEverquestRequest)];
auto data = (PlayEverquestRequest *) buffer;
data->base_header.sequence = GetCurrentPlaySequence();
data->server_number = GetSelectedPlayServerID();
app->pBuffer = buffer;
app->size = sizeof(PlayEverquestRequest);
LogLoginserverDetail("Step 1 - Hit CancelOfflineTrader Mode Packet for.");
SendCancelOfflineStatusToWorld((const char *) app->pBuffer);
break;
}
}
delete app;
@ -561,3 +580,27 @@ std::string Client::GetClientLoggingDescription()
client_ip
);
}
void Client::SendCancelOfflineStatusToWorld(const char *data)
{
if (m_client_status != cs_logged_in) {
LogError("Client sent a play request when they were not logged in, discarding");
return;
}
const auto *play = (const PlayEverquestRequest *) data;
auto server_id_in = (unsigned int) play->server_number;
auto sequence_in = (unsigned int) play->base_header.sequence;
LogLoginserverDetail(
"Step 2 - Cancel Offline Status Request received from client [{}] server number [{}] sequence [{}]",
GetAccountName(),
server_id_in,
sequence_in
);
m_selected_play_server_id = (unsigned int) play->server_number;
m_play_sequence_id = sequence_in;
m_selected_play_server_id = server_id_in;
server.server_manager->SendUserToWorldCancelOfflineRequest(server_id_in, m_account_id, m_loginserver_name);
}

View File

@ -25,6 +25,7 @@ public:
void SendPlayToWorld(const char *data);
void SendServerListPacket(uint32 seq);
void SendPlayResponse(EQApplicationPacket *outapp);
void SendCancelOfflineStatusToWorld(const char *data);
void GenerateRandomLoginKey();
unsigned int GetAccountID() const { return m_account_id; }
std::string GetLoginServerName() const { return m_loginserver_name; }

View File

@ -83,6 +83,11 @@ struct PlayEverquestResponse {
uint32 server_number;
};
struct CancelOfflineTrader {
LoginBaseMessage base_header;
int16_t unk;
};
#pragma pack()
enum LSClientVersion {
@ -158,11 +163,12 @@ namespace LS {
constexpr static int ERROR_NONE = 101; // No Error
constexpr static int ERROR_UNKNOWN = 102; // Error - Unknown Error Occurred
constexpr static int ERROR_ACTIVE_CHARACTER = 111; // Error 1018: You currently have an active character on that EverQuest Server, please allow a minute for synchronization and try again.
constexpr static int ERROR_OFFLINE_TRADER = 114; // You have a character logged into a world server as an OFFLINE TRADER from this account. You may only have 1 character from a single account logged into a server at a time (even across different servers). Would you like to remove this character from the game so you may login?
constexpr static int ERROR_SERVER_UNAVAILABLE = 326; // That server is currently unavailable. Please check the EverQuest webpage for current server status and try again later.
constexpr static int ERROR_ACCOUNT_SUSPENDED = 337; // This account is currently suspended. Please contact customer service for more information.
constexpr static int ERROR_ACCOUNT_BANNED = 338; // This account is currently banned. Please contact customer service for more information.
constexpr static int ERROR_WORLD_MAX_CAPACITY = 339; // The world server is currently at maximum capacity and not allowing further logins until the number of players online decreases. Please try again later.
};
};
}
#pragma pack(pop)

View File

@ -11,3 +11,5 @@ OP_Poll=0x0029
OP_LoginExpansionPacketData=0x0031
OP_EnterChat=0x000f
OP_PollResponse=0x0011
OP_CancelOfflineTrader=0x0016
OP_CancelOfflineTraderResponse=0x0030

View File

@ -12,7 +12,6 @@
#include "loginserver/login_server.h"
#include "loginserver/loginserver_command_handler.h"
#include "loginserver/loginserver_webserver.h"
#include <string>
#include <thread>

View File

@ -52,6 +52,12 @@ WorldServer::WorldServer(std::shared_ptr<EQ::Net::ServertalkServerConnection> wo
ServerOP_LSAccountUpdate,
std::bind(&WorldServer::ProcessLSAccountUpdate, this, std::placeholders::_1, std::placeholders::_2)
);
worldserver_connection->OnMessage(
ServerOP_UsertoWorldCancelOfflineResponse,
std::bind(
&WorldServer::ProcessUserToWorldCancelOfflineResponse, this, std::placeholders::_1, std::placeholders::_2)
);
}
WorldServer::~WorldServer() = default;
@ -299,6 +305,10 @@ void WorldServer::ProcessUserToWorldResponse(uint16_t opcode, const EQ::Net::Pac
case UserToWorldStatusAlreadyOnline:
r->base_reply.error_str_id = LS::ErrStr::ERROR_ACTIVE_CHARACTER;
break;
case UserToWorldStatusOffilineTraderBuyer:
r->base_reply.success = false;
r->base_reply.error_str_id = LS::ErrStr::ERROR_OFFLINE_TRADER;
break;
default:
r->base_reply.error_str_id = LS::ErrStr::ERROR_UNKNOWN;
break;
@ -775,3 +785,113 @@ void WorldServer::FormatWorldServerName(char *name, int8 server_list_type)
strn0cpy(name, server_long_name.c_str(), 201);
}
void WorldServer::ProcessUserToWorldCancelOfflineResponse(uint16_t opcode, const EQ::Net::Packet &packet)
{
LogNetcode(
"Application packet received from server [{:#04x}] [Size: {}]\n{}",
opcode,
packet.Length(),
packet.ToString()
);
LogLoginserverDetail("Step 8 - back in Login Server from world.");
if (packet.Length() < sizeof(UsertoWorldResponse)) {
LogError(
"Received application packet from server that had opcode ServerOP_UsertoWorldCancelOfflineResp, "
"but was too small. Discarded to avoid buffer overrun"
);
return;
}
auto res = (UsertoWorldResponse *) packet.Data();
LogDebug("Trying to find client with user id of [{}]", res->lsaccountid);
Client *c = server.client_manager->GetClient(
res->lsaccountid,
res->login
);
if (c) {
LogDebug(
"Found client with user id of [{}] and account name of {}",
res->lsaccountid,
c->GetAccountName().c_str()
);
auto client_packet = EQApplicationPacket(OP_CancelOfflineTraderResponse, sizeof(PlayEverquestResponse));
auto client_packet_payload = reinterpret_cast<PlayEverquestResponse *>(client_packet.pBuffer);
client_packet_payload->base_header.sequence = c->GetCurrentPlaySequence();
client_packet_payload->server_number = c->GetSelectedPlayServerID();
LogLoginserverDetail(
"Step 9 - Send Play Response OPCODE 30 to remove the client message about having an offline Trader/Buyer"
);
c->SendPlayResponse(&client_packet);
auto outapp = new EQApplicationPacket(OP_PlayEverquestResponse, sizeof(PlayEverquestResponse));
auto r = reinterpret_cast<PlayEverquestResponse *>(outapp->pBuffer);
r->base_header.sequence = c->GetCurrentPlaySequence();
r->server_number = c->GetSelectedPlayServerID();
LogDebug(
"Found sequence and play of [{}] [{}]",
c->GetCurrentPlaySequence(),
c->GetSelectedPlayServerID()
);
//LogDebug("[Size: [{}]] {}", outapp->size, DumpPacketToString(outapp));
if (res->response > 0) {
r->base_reply.success = true;
SendClientAuthToWorld(c);
}
switch (res->response) {
case UserToWorldStatusSuccess:
r->base_reply.error_str_id = LS::ErrStr::ERROR_NONE;
break;
case UserToWorldStatusWorldUnavail:
r->base_reply.error_str_id = LS::ErrStr::ERROR_SERVER_UNAVAILABLE;
break;
case UserToWorldStatusSuspended:
r->base_reply.error_str_id = LS::ErrStr::ERROR_ACCOUNT_SUSPENDED;
break;
case UserToWorldStatusBanned:
r->base_reply.error_str_id = LS::ErrStr::ERROR_ACCOUNT_BANNED;
break;
case UserToWorldStatusWorldAtCapacity:
r->base_reply.error_str_id = LS::ErrStr::ERROR_WORLD_MAX_CAPACITY;
break;
case UserToWorldStatusAlreadyOnline:
r->base_reply.error_str_id = LS::ErrStr::ERROR_ACTIVE_CHARACTER;
break;
case UserToWorldStatusOffilineTraderBuyer:
r->base_reply.success = false;
r->base_reply.error_str_id = LS::ErrStr::ERROR_OFFLINE_TRADER;
break;
default:
r->base_reply.error_str_id = LS::ErrStr::ERROR_UNKNOWN;
break;
}
LogDebug(
"Sending play response with following data, allowed [{}], sequence {}, server number {}, message {}",
r->base_reply.success,
r->base_header.sequence,
r->server_number,
r->base_reply.error_str_id
);
LogLoginserverDetail("Step 10 - Send Play Response EnterWorld to client");
c->SendPlayResponse(outapp);
delete outapp;
}
else {
LogError(
"Received User-To-World Response for [{}] but could not find the client referenced!.",
res->lsaccountid
);
}
}

View File

@ -58,6 +58,7 @@ private:
void ProcessUserToWorldResponseLegacy(uint16_t opcode, const EQ::Net::Packet &packet);
void ProcessUserToWorldResponse(uint16_t opcode, const EQ::Net::Packet &packet);
void ProcessLSAccountUpdate(uint16_t opcode, const EQ::Net::Packet &packet);
void ProcessUserToWorldCancelOfflineResponse(uint16_t opcode, const EQ::Net::Packet &packet);
std::shared_ptr<EQ::Net::ServertalkServerConnection> m_connection;

View File

@ -216,3 +216,35 @@ const std::list<std::unique_ptr<WorldServer>> &WorldServerManager::GetWorldServe
{
return m_world_servers;
}
void WorldServerManager::SendUserToWorldCancelOfflineRequest(
unsigned int server_id,
unsigned int client_account_id,
const std::string &client_loginserver
)
{
auto iter = std::find_if(
m_world_servers.begin(), m_world_servers.end(),
[&](const std::unique_ptr<WorldServer> &server) {
return server->GetServerId() == server_id;
}
);
if (iter != m_world_servers.end()) {
EQ::Net::DynamicPacket outapp;
outapp.Resize(sizeof(UsertoWorldRequest));
auto *r = reinterpret_cast<UsertoWorldRequest *>(outapp.Data());
r->worldid = server_id;
r->lsaccountid = client_account_id;
strncpy(r->login, client_loginserver.c_str(), 64);
LogLoginserverDetail("Step 3 - Sending ServerOP_UsertoWorldCancelOfflineRequest to world for client account id {}", client_account_id);
(*iter)->GetConnection()->Send(ServerOP_UsertoWorldCancelOfflineRequest, outapp);
LogNetcode("[UsertoWorldRequest] [Size: {}]\n{}", outapp.Length(), outapp.ToString());
}
else {
LogError("Client requested a user to world but supplied an invalid id of {}", server_id);
}
}

View File

@ -16,6 +16,11 @@ public:
unsigned int client_account_id,
const std::string &client_loginserver
);
void SendUserToWorldCancelOfflineRequest(
unsigned int server_id,
unsigned int client_account_id,
const std::string &client_loginserver
);
std::unique_ptr<EQApplicationPacket> CreateServerListPacket(Client *client, uint32 sequence);
bool DoesServerExist(const std::string &s, const std::string &server_short_name, WorldServer *ignore = nullptr);
void DestroyServerByName(std::string s, std::string server_short_name, WorldServer *ignore = nullptr);

View File

@ -749,3 +749,6 @@ OP_ChangePetName=0x5dab
OP_InvokeNameChangeImmediate=0x4fe2
OP_InvokeNameChangeLazy=0x2f2e
#Offline Trading Mode
OP_Offline=0x53d3

View File

@ -9,6 +9,7 @@ set(world_sources
cli/cli_database_concurrency.cpp
cli/cli_database_dump.cpp
cli/cli_database_get_schema.cpp
cli/cli_database_item_unique_ids.cpp
cli/cli_database_set_account_status.cpp
cli/cli_database_updates.cpp
cli/cli_database_version.cpp

View File

@ -0,0 +1,54 @@
#include "world/world_server_cli.h"
#include "world/worlddb.h"
#include <cstdlib>
void WorldserverCLI::DatabaseItemUniqueIds(int argc, char **argv, argh::parser &cmd, std::string &description)
{
description = "Runs item_unique_id preflight, migration, and verification tasks";
std::vector<std::string> arguments = {};
std::vector<std::string> options = {
"--preflight",
"--migrate",
"--verify",
"--verbose",
"--keep-trading-state",
};
if (cmd[{"-h", "--help"}]) {
return;
}
EQEmuCommand::ValidateCmdInput(arguments, options, cmd, argc, argv);
const bool verbose = cmd[{"-v", "--verbose"}];
const bool migrate = cmd[{"--migrate"}];
const bool verify = cmd[{"--verify"}];
const bool preflight = cmd[{"--preflight"}] || (!migrate && !verify);
const bool clear_trading_state = !cmd[{"--keep-trading-state"}];
bool success = true;
if (preflight) {
success = database.PreflightItemUniqueIdMigration(verbose) && success;
}
if (migrate) {
if (!database.MigrateItemUniqueIdData(clear_trading_state, verbose)) {
LogError("Item unique id migration failed verification");
success = false;
}
}
if (verify) {
if (!database.VerifyItemUniqueIdMigration(verbose)) {
LogError("Item unique id verification failed");
success = false;
}
}
if (!success) {
std::exit(1);
}
}

View File

@ -2403,7 +2403,7 @@ bool Client::StoreCharacter(
e.ornament_icon = inst->GetOrnamentationIcon();
e.ornament_idfile = inst->GetOrnamentationIDFile();
e.ornament_hero_model = inst->GetOrnamentHeroModel();
e.guid = inst->GetSerialNumber();
e.item_unique_id = inst->GetUniqueID();
v.emplace_back(e);
}

View File

@ -202,6 +202,9 @@ void ClientListEntry::Update(ZoneServer *iZS, ServerClientList_Struct *scl, CLE_
m_lfg = scl->LFG;
m_gm = scl->gm;
m_client_version = scl->ClientVersion;
m_trader = scl->trader;
m_buyer = scl->buyer;
m_offline = scl->offline;
// Fields from the LFG Window
if ((scl->LFGFromLevel != 0) && (scl->LFGToLevel != 0)) {
@ -219,6 +222,10 @@ void ClientListEntry::LeavingZone(ZoneServer *iZS, CLE_Status iOnline)
if (iZS != 0 && iZS != m_zone_server) {
return;
}
m_trader = false;
m_buyer = false;
m_offline = false;
SetOnline(iOnline);
SharedTaskManager::Instance()->RemoveActiveInvitationByCharacterID(CharID());
@ -260,6 +267,10 @@ void ClientListEntry::ClearVars(bool iAll)
m_lfg = 0;
m_gm = 0;
m_client_version = 0;
m_trader = false;
m_buyer = false;
m_offline = false;
for (auto &elem: m_tell_queue) {
safe_delete_array(elem);
}

View File

@ -13,7 +13,8 @@ typedef enum {
Online,
CharSelect,
Zoning,
InZone
InZone,
OfflineMode
} CLE_Status;
static const char *CLEStatusString[] = {
@ -22,7 +23,8 @@ static const char *CLEStatusString[] = {
"Online",
"CharSelect",
"Zoning",
"InZone"
"InZone",
"OfflineMode"
};
class ZoneServer;
@ -102,6 +104,10 @@ public:
inline bool GetLFGMatchFilter() const { return m_lfg_match_filter; }
inline const char *GetLFGComments() const { return m_lfg_comments; }
inline uint8 GetClientVersion() { return m_client_version; }
bool GetTrader() const { return m_trader; }
bool GetBuyer() const { return m_buyer; }
bool GetOfflineMode() const { return m_offline; }
void SetOfflineMode(bool status) { m_offline = status; }
inline bool TellQueueFull() const { return m_tell_queue.size() >= RuleI(World, TellQueueSize); }
inline bool TellQueueEmpty() const { return m_tell_queue.empty(); }
@ -134,25 +140,28 @@ private:
// Character info
ZoneServer *m_zone_server{};
uint32 m_zone{};
uint16 m_instance{};
uint32 m_char_id{};
char m_char_name[64]{};
uint8 m_level{};
uint8 m_class_{};
uint16 m_race{};
uint8 m_anon{};
uint8 m_tells_off{};
uint32 m_guild_id{};
uint32 m_guild_rank;
bool m_guild_tribute_opt_in{};
bool m_lfg{};
uint8 m_gm{};
uint8 m_client_version{};
uint8 m_lfg_from_level{};
uint8 m_lfg_to_level{};
bool m_lfg_match_filter{};
char m_lfg_comments[64]{};
uint32 m_zone{};
uint16 m_instance{};
uint32 m_char_id{};
char m_char_name[64]{};
uint8 m_level{};
uint8 m_class_{};
uint16 m_race{};
uint8 m_anon{};
uint8 m_tells_off{};
uint32 m_guild_id{};
uint32 m_guild_rank;
bool m_guild_tribute_opt_in{};
bool m_lfg{};
uint8 m_gm{};
uint8 m_client_version{};
uint8 m_lfg_from_level{};
uint8 m_lfg_to_level{};
bool m_lfg_match_filter{};
char m_lfg_comments[64]{};
bool m_trader = false;
bool m_buyer = false;
bool m_offline = false;
// Tell Queue -- really a vector :D
std::vector<ServerChannelMessage_Struct *> m_tell_queue;

View File

@ -35,6 +35,7 @@
#include "world/zoneserver.h"
#include <set>
#include "../zone/string_ids.h"
uint32 numplayers = 0; //this really wants to be a member variable of ClientList...
@ -427,7 +428,10 @@ void ClientList::ClientUpdate(ZoneServer *zoneserver, ServerClientList_Struct *s
while (iterator.MoreElements()) {
if (iterator.GetData()->GetID() == scl->wid) {
cle = iterator.GetData();
if (scl->remove == 2) {
if (scl->remove == 3) {
cle->Update(zoneserver, scl, CLE_Status::OfflineMode);
}
else if (scl->remove == 2) {
cle->LeavingZone(zoneserver, CLE_Status::Offline);
}
else if (scl->remove == 1) {
@ -441,7 +445,11 @@ void ClientList::ClientUpdate(ZoneServer *zoneserver, ServerClientList_Struct *s
}
iterator.Advance();
}
if (scl->remove == 2) {
if (scl->remove == 3) {
cle = new ClientListEntry(GetNextCLEID(), zoneserver, scl, CLE_Status::OfflineMode);
}
else if (scl->remove == 2) {
cle = new ClientListEntry(GetNextCLEID(), zoneserver, scl, CLE_Status::Online);
}
else if (scl->remove == 1) {
@ -479,7 +487,10 @@ void ClientList::ClientUpdate(ZoneServer *zoneserver, ServerClientList_Struct *s
" LFGFromLevel [{}]"
" LFGToLevel [{}]"
" LFGMatchFilter [{}]"
" LFGComments [{}]",
" LFGComments [{}]"
" Trader [{}]"
" Buyer [{}]"
" Offline [{}]",
scl->remove,
scl->wid,
scl->IP,
@ -506,7 +517,10 @@ void ClientList::ClientUpdate(ZoneServer *zoneserver, ServerClientList_Struct *s
scl->LFGFromLevel,
scl->LFGToLevel,
scl->LFGMatchFilter,
scl->LFGComments
scl->LFGComments,
scl->trader,
scl->buyer,
scl->offline
);
clientlist.Insert(cle);
@ -784,7 +798,14 @@ void ClientList::SendWhoAll(uint32 fromid,const char* to, int16 admin, Who_All_S
rankstring = 0;
iterator.Advance();
continue;
} else if (cle->GetGM()) {
}
else if (cle->GetTrader()) {
rankstring = TRADER;
}
else if (cle->GetBuyer()) {
rankstring = BUYER;
}
else if (cle->GetGM()) {
if (cle->Admin() >= AccountStatus::GMImpossible) {
rankstring = 5021;
} else if (cle->Admin() >= AccountStatus::GMMgmt) {
@ -877,6 +898,18 @@ void ClientList::SendWhoAll(uint32 fromid,const char* to, int16 admin, Who_All_S
strcpy(placcount,cle->AccountName());
}
if (cle->GetOfflineMode()) {
if (cle->GetTrader()) {
pidstring = 0x0430;
rankstring = 0xFFFFFFFF;
}
if (cle->GetBuyer()) {
pidstring = 0x0420;
rankstring = 0xFFFFFFFF;
}
}
memcpy(bufptr,&formatstring, sizeof(uint32));
bufptr+=sizeof(uint32);
memcpy(bufptr,&pidstring, sizeof(uint32));
@ -1631,25 +1664,29 @@ void ClientList::OnTick(EQ::Timer *t)
outclient["Server"] = Json::Value();
}
outclient["CharID"] = cle->CharID();
outclient["name"] = cle->name();
outclient["zone"] = cle->zone();
outclient["instance"] = cle->instance();
outclient["level"] = cle->level();
outclient["class_"] = cle->class_();
outclient["race"] = cle->race();
outclient["Anon"] = cle->Anon();
outclient["CharID"] = cle->CharID();
outclient["name"] = cle->name();
outclient["zone"] = cle->zone();
outclient["instance"] = cle->instance();
outclient["level"] = cle->level();
outclient["class_"] = cle->class_();
outclient["race"] = cle->race();
outclient["Anon"] = cle->Anon();
outclient["TellsOff"] = cle->TellsOff();
outclient["GuildID"] = cle->GuildID();
outclient["LFG"] = cle->LFG();
outclient["GM"] = cle->GetGM();
outclient["LocalClient"] = cle->IsLocalClient();
outclient["LFGFromLevel"] = cle->GetLFGFromLevel();
outclient["LFGToLevel"] = cle->GetLFGToLevel();
outclient["TellsOff"] = cle->TellsOff();
outclient["GuildID"] = cle->GuildID();
outclient["LFG"] = cle->LFG();
outclient["GM"] = cle->GetGM();
outclient["LocalClient"] = cle->IsLocalClient();
outclient["LFGFromLevel"] = cle->GetLFGFromLevel();
outclient["LFGToLevel"] = cle->GetLFGToLevel();
outclient["LFGMatchFilter"] = cle->GetLFGMatchFilter();
outclient["LFGComments"] = cle->GetLFGComments();
outclient["ClientVersion"] = cle->GetClientVersion();
outclient["LFGComments"] = cle->GetLFGComments();
outclient["ClientVersion"] = cle->GetClientVersion();
outclient["Trader"] = cle->GetTrader();
outclient["Buyer"] = cle->GetBuyer();
outclient["OfflineMode"] = cle->GetOfflineMode();
out["data"].append(outclient);
Iterator.Advance();

View File

@ -58,7 +58,7 @@ struct EQ::Net::ConsoleLoginStatus CheckLogin(const std::string &username, const
const std::string& account_name = database.GetAccountName(ret.account_id);
ret.account_name = account_name;
ret.status = database.GetAccountStatus(ret.account_id);
ret.status = database.GetAccountStatus(ret.account_id).status;
return ret;
}

View File

@ -4,6 +4,11 @@
#include "common/eqemu_logsys.h"
#include "common/misc_functions.h"
#include "common/packet_dump.h"
#include "common/repositories/account_repository.h"
#include "common/repositories/buyer_repository.h"
#include "common/repositories/character_data_repository.h"
#include "common/repositories/offline_character_sessions_repository.h"
#include "common/repositories/trader_repository.h"
#include "common/servertalk.h"
#include "common/strings.h"
#include "common/version.h"
@ -43,9 +48,9 @@ void LoginServer::ProcessUsertoWorldReqLeg(uint16_t opcode, EQ::Net::Packet &p)
const WorldConfig *Config = WorldConfig::get();
LogNetcode("Received ServerPacket from LS OpCode {:#04x}", opcode);
UsertoWorldRequestLegacy *utwr = (UsertoWorldRequestLegacy *) p.Data();
uint32 id = database.GetAccountIDFromLSID("eqemu", utwr->lsaccountid);
int16 status = database.GetAccountStatus(id);
UsertoWorldRequestLegacy *utwr = (UsertoWorldRequestLegacy *) p.Data();
uint32 id = database.GetAccountIDFromLSID("eqemu", utwr->lsaccountid);
int16 status = database.GetAccountStatus(id).status;
LogDebug(
"id [{}] status [{}] account_id [{}] world_id [{}] from_id [{}] to_id [{}] ip [{}]",
@ -123,14 +128,19 @@ void LoginServer::ProcessUsertoWorldReq(uint16_t opcode, EQ::Net::Packet &p)
const WorldConfig *Config = WorldConfig::get();
LogNetcode("Received ServerPacket from LS OpCode {:#04x}", opcode);
UsertoWorldRequest *utwr = (UsertoWorldRequest *) p.Data();
uint32 id = database.GetAccountIDFromLSID(utwr->login, utwr->lsaccountid);
int16 status = database.GetAccountStatus(id);
UsertoWorldRequest *utwr = (UsertoWorldRequest *) p.Data();
uint32 id = database.GetAccountIDFromLSID(utwr->login, utwr->lsaccountid);
auto status_record = database.GetAccountStatus(id);
auto client = ClientList::Instance()->FindCLEByAccountID(id);
if (client) {
client->SetOfflineMode(status_record.offline);
}
LogDebug(
"id [{}] status [{}] account_id [{}] world_id [{}] from_id [{}] to_id [{}] ip [{}]",
id,
status,
status_record.status,
utwr->lsaccountid,
utwr->worldid,
utwr->FromID,
@ -152,7 +162,7 @@ void LoginServer::ProcessUsertoWorldReq(uint16_t opcode, EQ::Net::Packet &p)
utwrs->response = UserToWorldStatusSuccess;
if (Config->Locked == true) {
if (status < (RuleI(GM, MinStatusToBypassLockedServer))) {
if (status_record.status < (RuleI(GM, MinStatusToBypassLockedServer))) {
LogDebug(
"Server locked and status is not high enough for account_id [{0}]",
utwr->lsaccountid
@ -164,27 +174,34 @@ void LoginServer::ProcessUsertoWorldReq(uint16_t opcode, EQ::Net::Packet &p)
}
int32 x = Config->MaxClients;
if ((int32) numplayers >= x && x != -1 && x != 255 && status < (RuleI(GM, MinStatusToBypassLockedServer))) {
if ((int32) numplayers >= x && x != -1 && x != 255 && status_record.status < (RuleI(GM, MinStatusToBypassLockedServer))) {
LogDebug("World at capacity account_id [{0}]", utwr->lsaccountid);
utwrs->response = UserToWorldStatusWorldAtCapacity;
SendPacket(&outpack);
return;
}
if (status == -1) {
if (status_record.status == -1) {
LogDebug("User suspended account_id [{0}]", utwr->lsaccountid);
utwrs->response = UserToWorldStatusSuspended;
SendPacket(&outpack);
return;
}
if (status == -2) {
if (status_record.status == -2) {
LogDebug("User banned account_id [{0}]", utwr->lsaccountid);
utwrs->response = UserToWorldStatusBanned;
SendPacket(&outpack);
return;
}
if (status_record.offline || OfflineCharacterSessionsRepository::ExistsByAccountId(database, id)) {
LogDebug("User has an offline character for account_id [{0}]", utwr->lsaccountid);
utwrs->response = UserToWorldStatusOffilineTraderBuyer;
SendPacket(&outpack);
return;
}
if (RuleB(World, EnforceCharacterLimitAtLogin)) {
if (ClientList::Instance()->IsAccountInGame(utwr->lsaccountid)) {
LogDebug("User already online account_id [{0}]", utwr->lsaccountid);
@ -572,6 +589,14 @@ bool LoginServer::Connect()
std::placeholders::_2
)
);
m_client->OnMessage(
ServerOP_UsertoWorldCancelOfflineRequest,
std::bind(
&LoginServer::ProcessUserToWorldCancelOfflineRequest,
this,
std::placeholders::_1,
std::placeholders::_2)
);
}
return true;
@ -687,3 +712,108 @@ void LoginServer::SendAccountUpdate(ServerPacket *pack)
}
}
void LoginServer::ProcessUserToWorldCancelOfflineRequest(uint16_t opcode, EQ::Net::Packet &p)
{
auto const Config = WorldConfig::get();
LogNetcode("Received ServerPacket from LS OpCode {:#04x}", opcode);
auto utwr = static_cast<UsertoWorldRequest *>(p.Data());
uint32 id = database.GetAccountIDFromLSID(utwr->login, utwr->lsaccountid);
auto status_record = database.GetAccountStatus(id);
LogLoginserverDetail(
"Step 4 - World received CancelOfflineRequest for client login server account id {} offline mode {}",
id,
status_record.offline
);
LogDebug(
"id [{}] status [{}] account_id [{}] world_id [{}] ip [{}]",
id,
status_record.status,
utwr->lsaccountid,
utwr->worldid,
utwr->IPAddr
);
ServerPacket server_packet;
server_packet.size = sizeof(UsertoWorldResponse);
server_packet.pBuffer = new uchar[server_packet.size];
memset(server_packet.pBuffer, 0, server_packet.size);
auto utwrs = reinterpret_cast<UsertoWorldResponse *>(server_packet.pBuffer);
utwrs->lsaccountid = utwr->lsaccountid;
utwrs->ToID = utwr->FromID;
utwrs->worldid = utwr->worldid;
utwrs->response = UserToWorldStatusSuccess;
strn0cpy(utwrs->login, utwr->login, 64);
if (Config->Locked == true) {
if (status_record.status < RuleI(GM, MinStatusToBypassLockedServer)) {
LogDebug("Server locked and status is not high enough for account_id [{0}]", utwr->lsaccountid);
server_packet.opcode = ServerOP_UsertoWorldCancelOfflineResponse;
utwrs->response = UserToWorldStatusWorldUnavail;
SendPacket(&server_packet);
return;
}
}
int32 x = Config->MaxClients;
if (static_cast<int32>(numplayers) >= x &&
x != -1 &&
x != 255 &&
status_record.status < RuleI(GM, MinStatusToBypassLockedServer)
) {
LogDebug("World at capacity account_id [{0}]", utwr->lsaccountid);
server_packet.opcode = ServerOP_UsertoWorldCancelOfflineResponse;
utwrs->response = UserToWorldStatusWorldAtCapacity;
SendPacket(&server_packet);
return;
}
auto session = OfflineCharacterSessionsRepository::GetByAccountId(database, id);
auto trader = TraderRepository::GetAccountZoneIdAndInstanceIdByAccountId(database, id);
uint32 zone_id = session.id ? session.zone_id : trader.char_zone_id;
int32 instance_id = session.id ? session.instance_id : trader.char_zone_instance_id;
uint32 character_id = session.id ? session.character_id : trader.character_id;
if ((session.id || trader.id) &&
ZSList::Instance()->IsZoneBootedByZoneIdAndInstanceId(zone_id, instance_id)) {
LogLoginserverDetail(
"Step 5a(1) - World Checked offline users zone/instance is booted. "
"Sending packet to zone id {} instance id {}",
zone_id,
instance_id);
server_packet.opcode = ServerOP_UsertoWorldCancelOfflineRequest;
ZSList::Instance()->SendPacketToBootedZones(&server_packet);
return;
}
LogLoginserverDetail("Step 5b(1) - World determined offline users zone/instance is not booted. Ignoring zone.");
LogLoginserverDetail("Step 5b(2) - World clearing users offline status from account table.");
database.TransactionBegin();
AccountRepository::SetOfflineStatus(database, id, false);
OfflineCharacterSessionsRepository::DeleteByAccountId(database, id);
LogLoginserverDetail("Step 5b(3) - World clearing trader and buyer tables.");
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 offline session state for account [{}]: ({}) {}",
id,
commit_result.ErrorNumber(),
commit_result.ErrorMessage()
);
utwrs->response = UserToWorldStatusWorldUnavail;
}
server_packet.opcode = ServerOP_UsertoWorldCancelOfflineResponse;
SendPacket(&server_packet);
}

View File

@ -49,6 +49,7 @@ private:
void ProcessSystemwideMessage(uint16_t opcode, EQ::Net::Packet &p);
void ProcessLSRemoteAddr(uint16_t opcode, EQ::Net::Packet &p);
void ProcessLSAccountUpdate(uint16_t opcode, EQ::Net::Packet &p);
void ProcessUserToWorldCancelOfflineRequest(uint16_t opcode, EQ::Net::Packet &p);
std::unique_ptr<EQ::Timer> m_keepalive;

View File

@ -634,4 +634,3 @@ void WorldBoot::SendDiscordMessage(int webhook_id, const std::string &message)
safe_delete(pack);
}
}

View File

@ -23,6 +23,7 @@ void WorldserverCLI::CommandHandler(int argc, char **argv)
function_map["database:schema"] = &WorldserverCLI::DatabaseGetSchema;
function_map["database:dump"] = &WorldserverCLI::DatabaseDump;
function_map["database:updates"] = &WorldserverCLI::DatabaseUpdates;
function_map["database:item-unique-ids"] = &WorldserverCLI::DatabaseItemUniqueIds;
function_map["test:test"] = &WorldserverCLI::TestCommand;
function_map["test:colors"] = &WorldserverCLI::TestColors;
function_map["test:expansion"] = &WorldserverCLI::ExpansionTestCommand;

View File

@ -17,6 +17,7 @@ public:
static void DatabaseGetSchema(int argc, char **argv, argh::parser &cmd, std::string &description);
static void DatabaseDump(int argc, char **argv, argh::parser &cmd, std::string &description);
static void DatabaseUpdates(int argc, char **argv, argh::parser &cmd, std::string &description);
static void DatabaseItemUniqueIds(int argc, char **argv, argh::parser &cmd, std::string &description);
static void TestCommand(int argc, char **argv, argh::parser &cmd, std::string &description);
static void TestColors(int argc, char **argv, argh::parser &cmd, std::string &description);
static void ExpansionTestCommand(int argc, char **argv, argh::parser &cmd, std::string &description);

View File

@ -876,7 +876,7 @@ bool WorldDatabase::GetCharSelInventory(
continue;
}
EQ::ItemInstance *inst = content_db.CreateBaseItem(item, e.charges);
EQ::ItemInstance *inst = content_db.CreateBaseItem(item, e.charges, e.item_unique_id);
if (!inst) {
continue;

View File

@ -1019,3 +1019,15 @@ void ZSList::QueueServerReload(ServerReload::Type &type)
m_queued_reloads.emplace_back(type);
m_queued_reloads_mutex.unlock();
}
bool ZSList::IsZoneBootedByZoneIdAndInstanceId(uint32 zone_id, uint32 instance_id) const
{
for (auto const& z : zone_server_list) {
auto r = z.get();
if (r && r->GetZoneID() == zone_id && r->GetInstanceID() == instance_id) {
return true;
}
}
return false;
}

View File

@ -34,6 +34,7 @@ public:
bool SendPacketToZonesWithGMs(ServerPacket *pack);
bool SendPacketToBootedZones(ServerPacket* pack);
bool SetLockedZone(uint16 iZoneID, bool iLock);
bool IsZoneBootedByZoneIdAndInstanceId(uint32 zone_id, uint32 instance_id) const;
EQTime worldclock;

View File

@ -1675,21 +1675,23 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
break;
}
case ServerOP_BazaarPurchase: {
auto in = (BazaarPurchaseMessaging_Struct *)pack->pBuffer;
if (in->trader_buy_struct.trader_id <= 0) {
LogTrading(
"World Message <red>[{}] received with invalid trader_id <red>[{}]",
"ServerOP_BazaarPurchase",
in->trader_buy_struct.trader_id
);
return;
auto in = reinterpret_cast<BazaarPurchaseMessaging_Struct *>(pack->pBuffer);
switch (in->transaction_status) {
case BazaarPurchaseBuyerCompleteSendToSeller: {
ZSList::Instance()->SendPacket(in->trader_zone_id, in->trader_zone_instance_id, pack);
break;
}
case BazaarPurchaseTraderFailed:
case BazaarPurchaseSuccess: {
ZSList::Instance()->SendPacket(in->buyer_zone_id, in->buyer_zone_instance_id, pack);
break;
}
default: {
LogError(
"ServerOP_BazaarPurchase received with no corresponding action for [{}]",
in->transaction_status);
}
}
auto trader = ClientList::Instance()->FindCLEByCharacterID(in->trader_buy_struct.trader_id);
if (trader) {
ZSList::Instance()->SendPacket(trader->zone(), trader->instance(), pack);
}
break;
}
case ServerOP_BuyerMessaging: {
@ -1725,9 +1727,34 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
break;
}
default:
return;
default: {
break;
}
}
break;
}
case ServerOP_UsertoWorldCancelOfflineResponse: {
auto utwr = reinterpret_cast<UsertoWorldResponse *>(pack->pBuffer);
ServerPacket server_packet;
server_packet.opcode = ServerOP_UsertoWorldCancelOfflineResponse;
server_packet.size = sizeof(UsertoWorldResponse);
server_packet.pBuffer = new uchar[server_packet.size];
memset(server_packet.pBuffer, 0, server_packet.size);
auto utwrs = reinterpret_cast<UsertoWorldResponse *>(server_packet.pBuffer);
utwrs->lsaccountid = utwr->lsaccountid;
utwrs->ToID = utwr->FromID;
utwrs->worldid = utwr->worldid;
utwrs->response = UserToWorldStatusSuccess;
strn0cpy(utwrs->login, utwr->login, 64);
LogLoginserverDetail(
"Step 7a - World received ServerOP_UsertoWorldCancelOfflineResponse back to login with success."
);
LoginServerList::Instance()->SendPacket(&server_packet);
break;
}
default: {
LogInfo("Unknown ServerOPcode from zone {:#04x}, size [{}]", pack->opcode, pack->size);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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];
}
}
};

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -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*/)

View File

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