eqemu-server/common/database.cpp
Mitch Freeman 8f4f8368df
[Player Event Logs] Migrate and Deprecate QS Legacy Logging (#4542)
* First pass of player_event_loot_items

* Second pass of player_event_loot_items

* Third pass of player_event_loot_items

* Example without RecordDetailEvent template

* Cleanup the removal of the template

* Fourth Pass

Add retention for etl tables
Rename tables/fields to etl nomenclature
Combine database work to one atomic load

* Reposition to reduce db tasks

* Refactor etl processing for easier additions

* Add merchant purchase event
testing passed though appears that the event itself has a few bugs.  Will fix them in another commit

* Fix PlayerEventMerchantPurchase in client_packet.cpp

* WIP - Handin

* Handin Event added

* Cleanup

* All a rentention period of 0 days which deletes all current records.

* Updates

Cleanup and refactor a few items.

* Cleanup and Formatting

Cleanup and Formatting

* Add etl for
Playerevent::Trade
PlayerEvent::Speech (new event to mirror functionality of qs_speech

* Add etl for
Playerevent::KilledNPC, KilledNamedNPC and KilledRaidNPC

* Add etl for Playerevent::AA_purchase

Add etl for Playerevent::AA_purchase

* Cleanup before PR

* Review comment updates.

* Add world cli etl:settings to output a json on all player event details.

* Add reserve for all etl_queues
Correct a failed test case for improper next id for etl tables when table is first created.

* Potential solution for a dedicated database connection for player events.

* Simple thread for player_events.  Likely there is a better way to do this.

* Add zone to qs communications for recordplayerevents

First pass of enabling zone to qs direct transport to allow for PlayerEvents to bypass world.

* Cleanup a linux compile issue

* Add augments to LOOT ITEM and DESTROY ITEM

* Add augments to ITEMCREATION, FORAGESUCCESS, FISHSUCCESS, DESTROYITEM, LOOTITEM, DROPPEDITEM, TRADERPURCHASE, TRADERSELL, GUILDTRIBUTEDONATE and cleaned up the naming convention of augments

* Formatting fixes

* Swap out GetNextTableId

* Statically load counter

* Add counter.clear() since the counter is static

* Upload optional QS conversion scripts

* Remove all qs_tables and code referencing them

* Update database.cpp

* Simplify ProcessBatchQueue

* Simplify PorcessBatchQueue

* Simplify event truncation

* Build event truncation to bulk query by retention groups

* Post rebase

* Update player_events.h

* Fix build

* Update npc.cpp

* First pass of direct zone to qs sending for player events

* Remove keepalive logic

* Fix event ordering

* Cleanup

* Update player_event_logs.cpp

* Wipe event data after ETL processed

* Split up database connections, hot reload logs for QS

* Load rules from database vs qs_database

* Update player_event_logs.cpp

* Hot toggle queryserv connect

---------

Co-authored-by: Akkadius <akkadius1@gmail.com>
2025-02-05 02:02:16 -06:00

2222 lines
48 KiB
C++

/* EQEMu: Everquest Server Emulator
Copyright (C) 2001-2015 EQEMu Development Team (http://eqemulator.net)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY except by those people which sell it, which
are required to give you total support for your newly bought product;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "../common/global_define.h"
#include "../common/rulesys.h"
#include <ctype.h>
#include <iomanip>
#include <iostream>
#include <map>
#include <algorithm>
#include <mysqld_error.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../common/repositories/account_repository.h"
#include "../common/repositories/adventure_stats_repository.h"
#include "../common/repositories/character_bind_repository.h"
#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_repository.h"
#include "../common/repositories/character_skills_repository.h"
#include "../common/repositories/data_buckets_repository.h"
#include "../common/repositories/group_id_repository.h"
#include "../common/repositories/group_leaders_repository.h"
#include "../common/repositories/guild_members_repository.h"
#include "../common/repositories/instance_list_repository.h"
#include "../common/repositories/inventory_snapshots_repository.h"
#include "../common/repositories/ip_exemptions_repository.h"
#include "../common/repositories/name_filter_repository.h"
#include "../common/repositories/npc_types_repository.h"
#include "../common/repositories/raid_details_repository.h"
#include "../common/repositories/raid_members_repository.h"
#include "../common/repositories/reports_repository.h"
#include "../common/repositories/variables_repository.h"
#include "../common/repositories/character_pet_name_repository.h"
#include "../common/events/player_event_logs.h"
// Disgrace: for windows compile
#ifdef _WINDOWS
#include <windows.h>
#define snprintf _snprintf
#define strncasecmp _strnicmp
#define strcasecmp _stricmp
#else
#include "unix.h"
#include <netinet/in.h>
#include <sys/time.h>
#endif
#include "database.h"
#include "data_verification.h"
#include "eq_packet_structs.h"
#include "extprofile.h"
#include "strings.h"
#include "database_schema.h"
#include "http/httplib.h"
#include "http/uri.h"
#include "repositories/zone_repository.h"
#include "zone_store.h"
#include "repositories/merchantlist_temp_repository.h"
#include "repositories/bot_data_repository.h"
#include "repositories/trader_repository.h"
#include "repositories/buyer_repository.h"
extern Client client;
Database::Database() { }
Database::~Database() { }
Database::Database(
const std::string& host,
const std::string& user,
const std::string& password,
const std::string& database,
uint32 port
)
{
Connect(host, user, password, database, port);
}
bool Database::Connect(
const std::string& host,
const std::string& user,
const std::string& password,
const std::string& database,
uint32 port,
std::string connection_label
)
{
uint32 error_number = 0;
char error_buffer[MYSQL_ERRMSG_SIZE];
if (!Open(host.c_str(), user.c_str(), password.c_str(), database.c_str(), port, &error_number, error_buffer)) {
LogError("Connection [{}] Failed to connect to database Error [{}]", connection_label, error_buffer);
return false;
} else {
LogInfo("Connected to database [{}] [{}] @ [{}:{}]", connection_label, database, host, port);
return true;
}
}
uint32 Database::CheckLogin(
const std::string& name,
const std::string& password,
const std::string& loginserver,
int16* status
)
{
if (name.length() >= 50 || password.length() >= 50) {
return 0;
}
const auto& l = AccountRepository::GetWhere(
*this,
fmt::format(
"`name` = '{}' AND `ls_id` = '{}' AND `password` IS NOT NULL AND LENGTH(`password`) > 0 AND (`password` = '{}' OR `password` = MD5('{}'))",
Strings::Escape(name),
Strings::Escape(loginserver),
Strings::Escape(password),
Strings::Escape(password)
)
);
if (l.empty()) {
return 0;
}
auto& e = l.front();
if (status) {
*status = e.status;
}
return e.id;
}
bool Database::CheckBannedIPs(const std::string& login_ip)
{
const std::string& query = fmt::format(
"SELECT `ip_address` FROM `banned_ips` WHERE `ip_address` = '{}'",
Strings::Escape(login_ip)
);
auto results = QueryDatabase(query);
if (!results.Success() || results.RowCount()) {
return true;
}
return false;
}
bool Database::AddBannedIP(const std::string& banned_ip, const std::string& notes)
{
auto query = fmt::format(
"INSERT INTO banned_ips SET `ip_address` = '{}', `notes` = '{}'",
Strings::Escape(banned_ip),
Strings::Escape(notes)
);
auto results = QueryDatabase(query);
return results.Success();
}
bool Database::CheckGMIPs(const std::string& login_ip, uint32 account_id)
{
auto query = fmt::format(
"SELECT `name`, `account_id`, `ip_address` FROM `gm_ips` WHERE `ip_address` = '{}' AND `account_id` = {}",
Strings::Escape(login_ip),
account_id
);
auto results = QueryDatabase(query);
if (!results.Success()) {
return false;
}
return results.RowCount();
}
void Database::LoginIP(uint32 account_id, const std::string& login_ip)
{
auto query = fmt::format(
"INSERT INTO account_ip SET `accid` = {}, `ip` = '{}' ON DUPLICATE KEY UPDATE count = (count + 1), lastused = NOW()",
account_id,
Strings::Escape(login_ip)
);
QueryDatabase(query);
}
int16 Database::GetAccountStatus(uint32 account_id)
{
auto e = AccountRepository::FindOne(*this, account_id);
if (e.suspendeduntil > 0 && e.suspendeduntil < std::time(nullptr)) {
e.status = 0;
e.suspendeduntil = 0;
e.suspend_reason = "";
AccountRepository::UpdateOne(*this, e);
}
return e.status;
}
uint32 Database::CreateAccount(
const std::string& name,
const std::string& password,
int16 status,
const std::string& loginserver,
uint32 lsaccount_id
)
{
auto e = AccountRepository::NewEntity();
e.name = name;
e.status = status;
e.ls_id = loginserver;
e.lsaccount_id = lsaccount_id;
e.time_creation = std::time(nullptr);
if (!password.empty()) {
e.password = password;
}
LogInfo("Account Attempting to be created: [{}:{}] status: {}", loginserver, name, status);
e = AccountRepository::InsertOne(*this, e);
if (!e.id) {
return 0;
}
return e.id;
}
bool Database::SetLocalPassword(uint32 account_id, const std::string& password)
{
return AccountRepository::UpdatePassword(*this, account_id, password);
}
bool Database::SetAccountStatus(const std::string& account_name, int16 status)
{
LogInfo("Account [{}] is attempting to be set to status [{}]", account_name, status);
auto l = AccountRepository::GetWhere(
*this,
fmt::format(
"`name` = '{}'",
Strings::Escape(account_name)
)
);
if (l.empty()) {
return false;
}
auto& e = l.front();
e.status = status;
return AccountRepository::UpdateOne(*this, e);
}
bool Database::ReserveName(uint32 account_id, const std::string& name)
{
const std::string& where_filter = fmt::format(
"`name` = '{}'",
Strings::Escape(name)
);
if (RuleB(Bots, Enabled)) {
const auto& b = BotDataRepository::GetWhere(*this, where_filter);
if (!b.empty()) {
LogInfo("Account [{}] requested name [{}] but name is already taken by a bot", account_id, name);
return false;
}
}
const auto& c = CharacterDataRepository::GetWhere(*this, where_filter);
if (!c.empty()) {
LogInfo("Account [{}] requested name [{}] but name is already taken by a character", account_id, name);
return false;
}
const auto& n = NpcTypesRepository::GetWhere(*this, where_filter);
if (!n.empty()) {
LogInfo("Account [{}] requested name [{}] but name is already taken by an NPC", account_id, name);
return false;
}
const auto& p = CharacterPetNameRepository::GetWhere(*this, where_filter);
if (!p.empty()) {
LogInfo("Account [{}] requested name [{}] but name is already taken by an Pet", account_id, name);
return false;
}
auto e = CharacterDataRepository::NewEntity();
e.account_id = account_id;
e.name = name;
e = CharacterDataRepository::InsertOne(*this, e);
if (!e.id) {
return false;
}
const uint32 guild_id = RuleI(Character, DefaultGuild);
const uint8 guild_rank = EQ::Clamp(RuleI(Character, DefaultGuildRank), 0, 8);
if (guild_id != 0) {
if (e.id) {
auto g = GuildMembersRepository::NewEntity();
g.char_id = e.id;
g.guild_id = guild_id;
g.rank_ = guild_rank;
GuildMembersRepository::InsertOne(*this, g);
}
}
return true;
}
bool Database::DeleteCharacter(const std::string& name)
{
if (name.empty()) {
LogInfo("Request to delete without a name.");
return false;
}
const auto& l = CharacterDataRepository::GetWhere(
*this,
fmt::format(
"`name` = '{}'",
Strings::Escape(name)
)
);
if (l.empty()) {
return false;
}
auto& e = l.front();
if (!e.id) {
return false;
}
std::string delete_type = "hard-deleted";
if (RuleB(Character, SoftDeletes)) {
delete_type = "soft-deleted";
std::string query = fmt::format(
SQL(
UPDATE character_data SET name = SUBSTRING(CONCAT(name, '-deleted-', UNIX_TIMESTAMP()), 1, 64), deleted_at = NOW()
WHERE id = {}
),
e.id
);
QueryDatabase(query);
if (RuleB(Bots, Enabled)) {
query = fmt::format(
SQL(
UPDATE bot_data SET name = SUBSTRING(CONCAT(name, '-deleted-', UNIX_TIMESTAMP()), 1, 64) WHERE owner_id = {}
),
e.id
);
QueryDatabase(query);
LogInfo(
"[DeleteCharacter] character_name [{}] ({}) bots are being [{}]",
e.name,
e.id,
delete_type
);
}
return true;
}
for (const auto &t : DatabaseSchema::GetCharacterTables()) {
const std::string& table_name = t.first;
const std::string& character_id_column_name = t.second;
QueryDatabase(
fmt::format(
"DELETE FROM {} WHERE {} = {}",
table_name,
character_id_column_name,
e.id
)
);
}
LogInfo("character_name [{}] ({}) is being [{}]", e.name, e.id, delete_type);
return true;
}
bool Database::SaveCharacterCreate(uint32 character_id, uint32 account_id, PlayerProfile_Struct *pp)
{
auto c = CharacterDataRepository::NewEntity();
c.id = character_id;
c.account_id = account_id;
c.name = pp->name;
c.last_name = pp->last_name;
c.gender = pp->gender;
c.race = pp->race;
c.class_ = pp->class_;
c.level = pp->level;
c.deity = pp->deity;
c.birthday = pp->birthday;
c.last_login = pp->lastlogin;
c.time_played = pp->timePlayedMin;
c.pvp_status = pp->pvp;
c.level2 = pp->level2;
c.anon = pp->anon;
c.gm = pp->gm;
c.intoxication = pp->intoxication;
c.hair_color = pp->haircolor;
c.beard_color = pp->beardcolor;
c.eye_color_1 = pp->eyecolor1;
c.eye_color_2 = pp->eyecolor2;
c.hair_style = pp->hairstyle;
c.beard = pp->beard;
c.ability_time_seconds = pp->ability_time_seconds;
c.ability_number = pp->ability_number;
c.ability_time_minutes = pp->ability_time_minutes;
c.ability_time_hours = pp->ability_time_hours;
c.title = pp->title;
c.suffix = pp->suffix;
c.exp = pp->exp;
c.points = pp->points;
c.mana = pp->mana;
c.cur_hp = pp->cur_hp;
c.str = pp->STR;
c.sta = pp->STA;
c.cha = pp->CHA;
c.dex = pp->DEX;
c.int_ = pp->INT;
c.agi = pp->AGI;
c.wis = pp->WIS;
c.face = pp->face;
c.y = pp->y;
c.x = pp->x;
c.z = pp->z;
c.heading = pp->heading;
c.pvp2 = pp->pvp2;
c.pvp_type = pp->pvptype;
c.autosplit_enabled = pp->autosplit;
c.zone_change_count = pp->zone_change_count;
c.drakkin_heritage = pp->drakkin_heritage;
c.drakkin_tattoo = pp->drakkin_tattoo;
c.drakkin_details = pp->drakkin_details;
c.toxicity = pp->toxicity;
c.hunger_level = pp->hunger_level;
c.thirst_level = pp->thirst_level;
c.ability_up = pp->ability_up;
c.zone_id = pp->zone_id;
c.zone_instance = pp->zoneInstance;
c.leadership_exp_on = pp->leadAAActive;
c.ldon_points_guk = pp->ldon_points_guk;
c.ldon_points_mir = pp->ldon_points_mir;
c.ldon_points_mmc = pp->ldon_points_mmc;
c.ldon_points_ruj = pp->ldon_points_ruj;
c.ldon_points_tak = pp->ldon_points_tak;
c.ldon_points_available = pp->ldon_points_available;
c.tribute_time_remaining = pp->tribute_time_remaining;
c.show_helm = pp->showhelm;
c.career_tribute_points = pp->career_tribute_points;
c.tribute_points = pp->tribute_points;
c.tribute_active = pp->tribute_active;
c.endurance = pp->endurance;
c.group_leadership_exp = pp->group_leadership_exp;
c.raid_leadership_exp = pp->raid_leadership_exp;
c.group_leadership_points = pp->group_leadership_points;
c.raid_leadership_points = pp->raid_leadership_points;
c.air_remaining = pp->air_remaining;
c.pvp_kills = pp->PVPKills;
c.pvp_deaths = pp->PVPDeaths;
c.pvp_current_points = pp->PVPCurrentPoints;
c.pvp_career_points = pp->PVPCareerPoints;
c.pvp_best_kill_streak = pp->PVPBestKillStreak;
c.pvp_worst_death_streak = pp->PVPWorstDeathStreak;
c.pvp_current_kill_streak = pp->PVPCurrentKillStreak;
c.aa_points_spent = pp->aapoints_spent;
c.aa_exp = pp->expAA;
c.aa_points = pp->aapoints;
c.group_auto_consent = pp->groupAutoconsent;
c.raid_auto_consent = pp->raidAutoconsent;
c.guild_auto_consent = pp->guildAutoconsent;
c.RestTimer = pp->RestTimer;
CharacterDataRepository::ReplaceOne(*this, c);
std::vector<CharacterBindRepository::CharacterBind> character_binds;
character_binds.reserve(5);
auto b = CharacterBindRepository::NewEntity();
b.id = character_id;
for (uint8 slot_id = 0; slot_id < 5; slot_id++) {
b.zone_id = pp->binds[slot_id].zone_id;
b.x = pp->binds[slot_id].x;
b.y = pp->binds[slot_id].y;
b.z = pp->binds[slot_id].z;
b.heading = pp->binds[slot_id].heading;
b.slot = slot_id;
character_binds.emplace_back(b);
}
CharacterBindRepository::ReplaceMany(*this, character_binds);
if (RuleB(Character, GrantHoTTOnCreate)) {
CharacterLeadershipAbilitiesRepository::InsertOne(
*this,
CharacterLeadershipAbilitiesRepository::CharacterLeadershipAbilities{
.id = character_id,
.slot = LeadershipAbilitySlot::HealthOfTargetsTarget,
.rank_ = 1
}
);
}
std::vector<CharacterSkillsRepository::CharacterSkills> character_skills;
character_skills.reserve(MAX_PP_SKILL);
for (uint16 slot_id = 0; slot_id < MAX_PP_SKILL; slot_id++) {
character_skills.emplace_back(
CharacterSkillsRepository::CharacterSkills{
.id = character_id,
.skill_id = slot_id,
.value = static_cast<uint16_t>(pp->skills[slot_id])
}
);
}
CharacterSkillsRepository::ReplaceMany(*this, character_skills);
std::vector<CharacterLanguagesRepository::CharacterLanguages> character_languages;
character_languages.reserve(MAX_PP_LANGUAGE);
for (uint16 slot_id = 0; slot_id < MAX_PP_LANGUAGE; slot_id++) {
character_languages.emplace_back(
CharacterLanguagesRepository::CharacterLanguages{
.id = character_id,
.lang_id = slot_id,
.value = static_cast<uint16_t>(pp->languages[slot_id])
}
);
}
CharacterLanguagesRepository::ReplaceMany(*this, character_languages);
return true;
}
uint32 Database::GetCharacterID(const std::string& name)
{
const auto& l = CharacterDataRepository::GetWhere(
*this,
fmt::format(
"`name` = '{}'",
Strings::Escape(name)
)
);
if (l.empty()) {
return 0;
}
auto& e = l.front();
return e.id;
}
uint32 Database::GetAccountIDByChar(const std::string& name, uint32* character_id)
{
const auto& l = CharacterDataRepository::GetWhere(
*this,
fmt::format(
"`name` = '{}'",
Strings::Escape(name)
)
);
if (l.empty()) {
return 0;
}
auto& e = l.front();
if (character_id) {
*character_id = e.id;
}
return e.account_id;
}
uint32 Database::GetAccountIDByChar(uint32 character_id)
{
const auto& e = CharacterDataRepository::FindOne(*this, character_id);
return e.id ? e.account_id : 0;
}
uint32 Database::GetAccountIDByName(const std::string& account_name, const std::string& loginserver, int16* status, uint32* lsaccount_id)
{
if (!isAlphaNumeric(account_name.c_str())) {
return 0;
}
const auto& l = AccountRepository::GetWhere(
*this,
fmt::format(
"`name` = '{}' AND `ls_id` = '{}' LIMIT 1",
Strings::Escape(account_name),
Strings::Escape(loginserver)
)
);
if (l.empty()) {
return 0;
}
auto& e = l.front();
if (status) {
*status = e.status;
}
if (lsaccount_id) {
*lsaccount_id = e.lsaccount_id;
}
return e.id;
}
const std::string Database::GetAccountName(uint32 account_id, uint32* lsaccount_id)
{
const auto& e = AccountRepository::FindOne(*this, account_id);
if (!e.id) {
return std::string();
}
if (lsaccount_id) {
*lsaccount_id = e.lsaccount_id;
}
return e.name;
}
const std::string Database::GetCharName(uint32 character_id)
{
const auto& e = CharacterDataRepository::FindOne(*this, character_id);
return e.id ? e.name : std::string();
}
const std::string Database::GetCharNameByID(uint32 character_id)
{
const auto& e = CharacterDataRepository::FindOne(*this, character_id);
return e.id ? e.name : std::string();
}
const std::string Database::GetNPCNameByID(uint32 npc_id)
{
const auto& e = NpcTypesRepository::FindOne(*this, npc_id);
return e.id ? e.name : std::string();
}
const std::string Database::GetCleanNPCNameByID(uint32 npc_id)
{
const auto& e = NpcTypesRepository::FindOne(*this, npc_id);
if (!e.id) {
return std::string();
}
std::string mob_name = e.name;
std::string result;
CleanMobName(mob_name.begin(), mob_name.end(), std::back_inserter(result));
return result;
}
bool Database::LoadVariables()
{
const auto& l = VariablesRepository::GetWhere(
*this,
fmt::format(
"UNIX_TIMESTAMP(`ts`) >= {}",
varcache.last_update
)
);
if (l.empty()) {
return true;
}
LockMutex lock(&Mvarcache);
for (const auto& e : l) {
varcache.last_update = std::time(nullptr);
varcache.Add(Strings::ToLower(e.varname), e.value);
}
LogInfo(
"Loaded [{}] Variable{}",
Strings::Commify(l.size()),
l.size() != 1 ? "s" : ""
);
return true;
}
bool Database::GetVariable(const std::string& name, std::string& value)
{
LockMutex lock(&Mvarcache);
if (name.empty()) {
return false;
}
auto v = varcache.Get(Strings::ToLower(name));
if (v) {
value = *v;
return true;
}
return false;
}
bool Database::SetVariable(const std::string& name, const std::string& value)
{
auto l = VariablesRepository::GetWhere(
*this,
fmt::format(
"`varname` = '{}'",
Strings::Escape(name)
)
);
if (l.empty()) {
return false;
}
auto& variable = l.front();
variable.value = value;
const int updated = VariablesRepository::UpdateOne(*this, variable);
if (updated) {
LoadVariables();
return true;
}
auto e = VariablesRepository::NewEntity();
e.varname = name;
e.value = value;
e = VariablesRepository::InsertOne(*this, e);
if (!e.id) {
return false;
}
LoadVariables();
return true;
}
void Database::SetAccountCRCField(uint32 account_id, const std::string& field_name, uint64 checksum)
{
auto e = AccountRepository::FindOne(*this, account_id);
if (Strings::EqualFold(field_name, "crc_basedata")) {
e.crc_basedata = checksum;
} else if (Strings::EqualFold(field_name, "crc_eqgame")) {
e.crc_eqgame = checksum;
} else if (Strings::EqualFold(field_name, "crc_skillcaps")) {
e.crc_skillcaps = checksum;
}
AccountRepository::UpdateOne(*this, e);
}
uint8 Database::GetPEQZone(uint32 zone_id, uint32 version)
{
auto z = GetZoneVersionWithFallback(zone_id, version);
return z ? z->peqzone : 0;
}
bool Database::CheckNameFilter(const std::string& name, bool surname)
{
// the minimum 4 is enforced by the client too
if (name.empty() || name.size() < 4) {
return false;
}
// Given name length is enforced by the client too
if (!surname && name.size() > 15) {
return false;
}
for (size_t i = 0; i < name.size(); i++) {
if (!isalpha(name[i])) {
return false;
}
}
char c = '\0';
uint8 num_c = 0;
for (size_t x = 0; x < name.size(); ++x) {
if (name[x] == c) {
num_c++;
} else {
num_c = 1;
c = name[x];
}
if (num_c > 2) {
return false;
}
}
const auto& l = NameFilterRepository::All(*this);
if (l.empty()) {
return true;
}
for (const auto& e : l) {
if (Strings::Contains(Strings::ToLower(name), Strings::ToLower(e.name))) {
return false;
}
}
return true;
}
bool Database::AddToNameFilter(const std::string& name)
{
auto e = NameFilterRepository::NewEntity();
e.name = name;
return NameFilterRepository::InsertOne(*this, e).id;
}
uint32 Database::GetAccountIDFromLSID(
const std::string& in_loginserver_id,
uint32 in_loginserver_account_id,
char* in_account_name,
int16* in_status
)
{
const auto& l = AccountRepository::GetWhere(
*this,
fmt::format(
"`lsaccount_id` = {} AND `ls_id` = '{}'",
in_loginserver_account_id,
in_loginserver_id
)
);
if (l.empty()) {
return 0;
}
auto& e = l.front();
if (in_account_name) {
strcpy(in_account_name, e.name.c_str());
}
if (in_status) {
*in_status = e.status;
}
return e.id;
}
void Database::ClearMerchantTemp()
{
MerchantlistTempRepository::ClearTemporaryMerchantLists(*this);
}
bool Database::UpdateName(const std::string& old_name, const std::string& new_name)
{
LogInfo("Renaming [{}] to [{}]", old_name, new_name);
auto l = CharacterDataRepository::GetWhere(
*this,
fmt::format(
"`name` = '{}'",
Strings::Escape(old_name)
)
);
if (l.empty()) {
return false;
}
auto& e = l.front();
e.name = new_name;
return CharacterDataRepository::UpdateOne(*this, e);
}
bool Database::IsNameUsed(const std::string& name)
{
if (RuleB(Bots, Enabled)) {
const auto& bot_data = BotDataRepository::GetWhere(
*this,
fmt::format(
"`name` = '{}'",
Strings::Escape(name)
)
);
if (!bot_data.empty()) {
return true;
}
}
const auto& character_data = CharacterDataRepository::GetWhere(
*this,
fmt::format(
"`name` = '{}'",
Strings::Escape(name)
)
);
return !character_data.empty();
}
uint32 Database::GetServerType()
{
const auto& l = VariablesRepository::GetWhere(*this, "`varname` = 'ServerType' LIMIT 1");
if (l.empty()) {
return 0;
}
auto& e = l.front();
return Strings::ToUnsignedInt(e.value);
}
bool Database::MoveCharacterToZone(uint32 character_id, uint32 zone_id)
{
auto e = CharacterDataRepository::FindOne(*this, character_id);
e.zone_id = zone_id;
e.x = e.y = e.z = -1;
return CharacterDataRepository::UpdateOne(*this, e);
}
bool Database::MoveCharacterToZone(const std::string& name, uint32 zone_id)
{
auto l = CharacterDataRepository::GetWhere(
*this,
fmt::format(
"`name` = '{}'",
Strings::Escape(name)
)
);
if (l.empty()) {
return false;
}
auto& e = l.front();
e.zone_id = zone_id;
e.x = e.y = e.z = -1;
return CharacterDataRepository::UpdateOne(*this, e);
}
bool Database::UpdateLiveChar(const std::string& name, uint32 account_id)
{
auto e = AccountRepository::FindOne(*this, account_id);
if (!e.id) {
return false;
}
e.charname = name;
return AccountRepository::UpdateOne(*this, e);
}
const std::string Database::GetLiveChar(uint32 account_id)
{
auto e = AccountRepository::FindOne(*this, account_id);
return e.id ? e.charname : std::string();
}
void Database::SetLFP(uint32 character_id, bool is_lfp)
{
auto e = CharacterDataRepository::FindOne(*this, character_id);
e.lfp = is_lfp ? 1 : 0;
CharacterDataRepository::UpdateOne(*this, e);
}
void Database::SetLoginFlags(uint32 character_id, bool is_lfp, bool is_lfg, uint8 first_logon)
{
auto e = CharacterDataRepository::FindOne(*this, character_id);
e.firstlogon = first_logon;
e.lfg = is_lfg ? 1 : 0;
e.lfp = is_lfp ? 1 : 0;
CharacterDataRepository::UpdateOne(*this, e);
}
void Database::SetLFG(uint32 character_id, bool is_lfg)
{
auto e = CharacterDataRepository::FindOne(*this, character_id);
e.lfg = is_lfg ? 1 : 0;
CharacterDataRepository::UpdateOne(*this, e);
}
void Database::SetFirstLogon(uint32 character_id, uint8 first_logon)
{
auto e = CharacterDataRepository::FindOne(*this, character_id);
e.firstlogon = first_logon;
CharacterDataRepository::UpdateOne(*this, e);
}
void Database::AddReport(const std::string& who, const std::string& against, const std::string& lines)
{
auto e = ReportsRepository::NewEntity();
e.name = Strings::Escape(who);
e.reported = Strings::Escape(against);
e.reported_text = Strings::Escape(lines);
ReportsRepository::InsertOne(*this, e);
}
void Database::ClearAllGroups()
{
GroupIdRepository::ClearAllGroups(*this);
}
void Database::ClearGroup(uint32 group_id) {
ClearGroupLeader(group_id);
if (!group_id) {
//clear all groups
ClearAllGroups();
return;
}
//clear a specific group
GroupIdRepository::DeleteWhere(
*this,
fmt::format(
"`group_id` = {}",
group_id
)
);
}
uint32 Database::GetGroupID(const std::string& name)
{
const auto& l = GroupIdRepository::GetWhere(
*this,
fmt::format(
"`name` = '{}'",
Strings::Escape(name)
)
);
if (l.empty()) {
return 0;
}
auto& e = l.front();
return e.group_id;
}
std::string Database::GetGroupLeaderForLogin(const std::string& character_name)
{
const auto& g = GroupIdRepository::GetWhere(
*this,
fmt::format(
"`name` = '{}'",
Strings::Escape(character_name)
)
);
if (g.empty()) {
return std::string();
}
auto& group = g.front();
const uint32 group_id = group.group_id;
const auto& e = GroupLeadersRepository::FindOne(*this, group_id);
return e.gid ? e.leadername : std::string();
}
void Database::SetGroupLeaderName(uint32 group_id, const std::string &name)
{
auto e = GroupLeadersRepository::FindOne(*this, group_id);
e.leadername = name;
if (e.gid) {
GroupLeadersRepository::UpdateOne(*this, e);
return;
}
e.gid = group_id;
e.marknpc = std::string();
e.leadershipaa = std::string();
e.maintank = std::string();
e.assist = std::string();
e.puller = std::string();
e.mentoree = std::string();
e.mentor_percent = 0;
GroupLeadersRepository::ReplaceOne(*this, e);
}
std::string Database::GetGroupLeaderName(uint32 group_id)
{
const std::string& query = fmt::format(
"SELECT `leadername` FROM `group_leaders` WHERE `gid` = {}",
group_id
);
auto results = QueryDatabase(query);
if (!results.Success() || !results.RowCount()) {
return std::string();
}
auto& row = results.begin();
return row[0];
}
char* Database::GetGroupLeadershipInfo(
uint32 group_id,
char* leaderbuf,
char* maintank,
char* assist,
char* puller,
char* marknpc,
char* mentoree,
int* mentor_percent,
GroupLeadershipAA_Struct* GLAA
)
{
auto e = GroupLeadersRepository::FindOne(*this, group_id);
if (!e.gid) {
if (leaderbuf) {
strcpy(leaderbuf, "UNKNOWN");
}
if (maintank) {
maintank[0] = '\0';
}
if (assist) {
assist[0] = '\0';
}
if (puller) {
puller[0] = '\0';
}
if (marknpc) {
marknpc[0] = '\0';
}
if (mentoree) {
mentoree[0] = '\0';
}
if (mentor_percent) {
*mentor_percent = 0;
}
return leaderbuf;
}
if (leaderbuf) {
strcpy(leaderbuf, e.leadername.c_str());
}
if (maintank) {
strcpy(maintank, e.maintank.c_str());
}
if (assist) {
strcpy(assist, e.assist.c_str());
}
if (puller) {
strcpy(puller, e.puller.c_str());
}
if (marknpc) {
strcpy(marknpc, e.marknpc.c_str());
}
if (mentoree) {
strcpy(mentoree, e.mentoree.c_str());
}
if (mentor_percent) {
*mentor_percent = e.mentor_percent;
}
if(GLAA && e.leadershipaa.length() == sizeof(GroupLeadershipAA_Struct)) {
Decode(e.leadershipaa);
memcpy(GLAA, e.leadershipaa.data(), sizeof(GroupLeadershipAA_Struct));
}
return leaderbuf;
}
void Database::ClearAllGroupLeaders()
{
GroupLeadersRepository::ClearAllGroupLeaders(*this);
}
void Database::ClearGroupLeader(uint32 group_id)
{
if (!group_id) {
ClearAllGroupLeaders();
return;
}
GroupLeadersRepository::DeleteOne(*this, group_id);
}
uint8 Database::GetAgreementFlag(uint32 account_id)
{
const auto& e = AccountRepository::FindOne(*this, account_id);
return !e.id ? 0 : e.rulesflag;
}
void Database::SetAgreementFlag(uint32 account_id)
{
auto e = AccountRepository::FindOne(*this, account_id);
e.rulesflag = 1;
AccountRepository::UpdateOne(*this, e);
}
void Database::ClearRaid(uint32 raid_id)
{
if (!raid_id) {
ClearAllRaids();
return;
}
RaidMembersRepository::DeleteWhere(
*this,
fmt::format(
"`raidid` = {}",
raid_id
)
);
}
void Database::ClearAllRaids()
{
RaidMembersRepository::ClearAllRaids(*this);
}
void Database::ClearAllRaidDetails()
{
RaidDetailsRepository::ClearAllRaidDetails(*this);
}
void Database::ClearRaidDetails(uint32 raid_id) {
if (!raid_id) {
ClearAllRaidDetails();
return;
}
RaidDetailsRepository::DeleteWhere(
*this,
fmt::format(
"`raidid` = {}",
raid_id
)
);
}
void Database::PurgeAllDeletedDataBuckets()
{
DataBucketsRepository::DeleteWhere(
*this,
fmt::format(
"(`expires` < {} AND `expires` > 0)",
std::time(nullptr)
)
);
}
uint32 Database::GetRaidID(const std::string& name)
{
const auto& l = RaidMembersRepository::GetWhere(
*this,
fmt::format(
"`name` = '{}'",
Strings::Escape(name)
)
);
if (l.empty()) {
return 0;
}
auto& e = l.front();
return e.raidid;
}
const std::string Database::GetRaidLeaderName(uint32 raid_id)
{
const auto& l = RaidMembersRepository::GetWhere(
*this,
fmt::format(
"`raidid` = {} AND `israidleader` = 1",
raid_id
)
);
if (l.empty()) {
LogDebug("Unable to get Raid Leader Name for Raid ID [{}]", raid_id);
return "UNKNOWN";
}
auto& e = l.front();
return e.name;
}
void Database::GetGroupLeadershipInfo(
uint32 group_id,
uint32 raid_id,
char* maintank,
char* assist,
char* puller,
char* marknpc,
char* mentoree,
int* mentor_percent,
GroupLeadershipAA_Struct* GLAA
)
{
const std::string& query = fmt::format(
SQL(
SELECT `maintank`, `assist`, `puller`, `marknpc`, `mentoree`, `mentor_percent`, `leadershipaa`
FROM `raid_leaders`
WHERE `gid` = {} AND `rid` = {}
),
group_id,
raid_id
);
auto results = QueryDatabase(query);
if (!results.Success() || !results.RowCount()) {
if (maintank) {
maintank[0] = '\0';
}
if (assist) {
assist[0] = '\0';
}
if (puller) {
puller[0] = '\0';
}
if (marknpc) {
marknpc[0] = '\0';
}
if (mentoree) {
mentoree[0] = '\0';
}
if (mentor_percent) {
*mentor_percent = 0;
}
return;
}
auto& row = results.begin();
if (maintank) {
strcpy(maintank, row[0]);
}
if (assist) {
strcpy(assist, row[1]);
}
if (puller) {
strcpy(puller, row[2]);
}
if (marknpc) {
strcpy(marknpc, row[3]);
}
if (mentoree) {
strcpy(mentoree, row[4]);
}
if (mentor_percent) {
*mentor_percent = Strings::ToInt(row[5]);
}
if (GLAA && results.LengthOfColumn(6) == sizeof(GroupLeadershipAA_Struct)) {
memcpy(GLAA, row[6], sizeof(GroupLeadershipAA_Struct));
}
}
void Database::GetRaidLeadershipInfo(
uint32 raid_id,
char* maintank,
char* assist,
char* puller,
char* marknpc,
RaidLeadershipAA_Struct* RLAA
)
{
std::string query = fmt::format(
SQL(
SELECT `maintank`, `assist`, `puller`, `marknpc`, `leadershipaa`
FROM `raid_leaders`
WHERE `gid` = {} AND `rid` = {}
),
std::numeric_limits<uint32>::max(),
raid_id
);
auto results = QueryDatabase(query);
if (!results.Success() || !results.RowCount()) {
if (maintank) {
maintank[0] = '\0';
}
if (assist) {
assist[0] = '\0';
}
if (puller) {
puller[0] = '\0';
}
if (marknpc) {
marknpc[0] = '\0';
}
return;
}
auto& row = results.begin();
if (maintank) {
strcpy(maintank, row[0]);
}
if (assist) {
strcpy(assist, row[1]);
}
if (puller) {
strcpy(puller, row[2]);
}
if (marknpc) {
strcpy(marknpc, row[3]);
}
if (RLAA && results.LengthOfColumn(4) == sizeof(RaidLeadershipAA_Struct)) {
memcpy(RLAA, row[4], sizeof(RaidLeadershipAA_Struct));
}
}
void Database::SetRaidGroupLeaderInfo(uint32 group_id, uint32 raid_id)
{
std::string query = fmt::format(
"UPDATE `raid_leaders` SET `leadershipaa` = '' WHERE `gid` = {} AND `rid` = {}",
group_id,
raid_id
);
auto results = QueryDatabase(query);
if (results.RowsAffected() != 0) {
return;
}
query = fmt::format(
SQL(
REPLACE INTO `raid_leaders` (`gid`, `rid`, `marknpc`, `leadershipaa`, `maintank`, `assist`, `puller`, `mentoree`, `mentor_percent`) VALUES
({}, {}, '', '', '', '', '', '', 0)
),
group_id,
raid_id
);
QueryDatabase(query);
}
void Database::ClearAllRaidLeaders()
{
QueryDatabase("DELETE FROM `raid_leaders`");
}
void Database::ClearRaidLeader(uint32 group_id, uint32 raid_id)
{
if (!raid_id) {
ClearAllRaidLeaders();
return;
}
const std::string& query = fmt::format(
"DELETE FROM `raid_leaders` WHERE `gid` = {} AND `rid` = {}",
group_id,
raid_id
);
QueryDatabase(query);
}
void Database::UpdateAdventureStatsEntry(uint32 character_id, uint8 theme_id, bool is_win, bool is_remove)
{
AdventureStatsRepository::UpdateAdventureStatsEntry(*this, character_id, theme_id, is_win, is_remove);
}
bool Database::GetAdventureStats(uint32 character_id, AdventureStats_Struct* as)
{
const auto& e = AdventureStatsRepository::FindOne(*this, character_id);
if (!e.player_id) {
return false;
}
as->success.guk = e.guk_wins;
as->failure.guk = e.guk_losses;
as->success.mir = e.mir_wins;
as->failure.mir = e.mir_losses;
as->success.mmc = e.mmc_wins;
as->failure.mmc = e.mmc_losses;
as->success.ruj = e.ruj_wins;
as->failure.ruj = e.ruj_losses;
as->success.tak = e.tak_wins;
as->failure.tak = e.tak_losses;
as->failure.total = (
as->failure.guk +
as->failure.mir +
as->failure.mmc +
as->failure.ruj +
as->failure.tak
);
as->success.total = (
as->success.guk +
as->success.mir +
as->success.mmc +
as->success.ruj +
as->success.tak
);
return true;
}
uint32 Database::GetGuildIDByCharID(uint32 character_id)
{
const auto& e = GuildMembersRepository::FindOne(*this, character_id);
return e.char_id ? e.guild_id : 0;
}
uint32 Database::GetGroupIDByCharID(uint32 character_id)
{
const auto& e = GroupIdRepository::GetWhere(
*this,
fmt::format(
"`character_id` = {}",
character_id
)
);
return e.size() == 1 ? e.front().group_id : 0;
}
uint32 Database::GetRaidIDByCharID(uint32 character_id)
{
const auto& e = RaidMembersRepository::GetWhere(
*this,
fmt::format(
"`charid` = {}",
character_id
)
);
return e.size() == 1 ? e.front().raidid : 0;
}
int64 Database::CountInvSnapshots()
{
return InventorySnapshotsRepository::CountInventorySnapshots(*this);
}
void Database::ClearInvSnapshots(bool from_now)
{
uint32 delete_time = time(nullptr);
if (!from_now) {
delete_time -= RuleI(Character, InvSnapshotHistoryD) * 86400;
}
InventorySnapshotsRepository::DeleteWhere(
*this,
fmt::format(
"`time_index` <= {}",
delete_time
)
);
}
struct TimeOfDay_Struct Database::LoadTime(time_t& realtime)
{
TimeOfDay_Struct t{ };
const std::string& query = "SELECT `minute`, `hour`, `day`, `month`, `year`, `realtime` FROM `eqtime` LIMIT 1";
auto results = QueryDatabase(query);
if (!results.Success() || !results.RowCount()) {
LogInfo("Loading EQ time of day failed. Using defaults");
t.minute = 0;
t.hour = 9;
t.day = 1;
t.month = 1;
t.year = 3100;
realtime = time(nullptr);
return t;
}
auto& row = results.begin();
uint8 hour = Strings::ToUnsignedInt(row[1]);
time_t realtime_ = Strings::ToBigInt(row[5]);
if (RuleI(World, BootHour) > 0 && RuleI(World, BootHour) <= 24) {
hour = RuleI(World, BootHour);
realtime_ = time(nullptr);
}
t.minute = Strings::ToUnsignedInt(row[0]);
t.hour = hour;
t.day = Strings::ToUnsignedInt(row[2]);
t.month = Strings::ToUnsignedInt(row[3]);
t.year = Strings::ToUnsignedInt(row[4]);
realtime = realtime_;
LogEqTime("Setting hour to [{}]", hour);
return t;
}
bool Database::SaveTime(int8 minute, int8 hour, int8 day, int8 month, int16 year)
{
const std::string& query = fmt::format(
"UPDATE `eqtime` SET `minute` = {}, `hour` = {}, `day` = {}, `month` = {}, `year` = {}, `realtime` = {} LIMIT 1",
minute,
hour,
day,
month,
year,
std::time(nullptr)
);
auto results = QueryDatabase(query);
return results.Success();
}
int Database::GetIPExemption(const std::string& account_ip)
{
const auto& l = IpExemptionsRepository::GetWhere(
*this,
fmt::format(
"`exemption_ip` = '{}'",
Strings::Escape(account_ip)
)
);
if (l.empty()) {
return RuleI(World, MaxClientsPerIP);
}
auto& e = l.front();
return e.exemption_amount;
}
void Database::SetIPExemption(const std::string& account_ip, int exemption_amount)
{
auto l = IpExemptionsRepository::GetWhere(
*this,
fmt::format(
"`exemption_ip` = '{}'",
Strings::Escape(account_ip)
)
);
if (l.empty()) {
auto e = IpExemptionsRepository::NewEntity();
e.exemption_ip = account_ip;
e.exemption_amount = exemption_amount;
IpExemptionsRepository::InsertOne(*this, e);
return;
}
auto& e = l.front();
e.exemption_amount = exemption_amount;
IpExemptionsRepository::UpdateOne(*this, e);
}
int Database::GetInstanceID(uint32 character_id, uint32 zone_id)
{
const auto& l = InstanceListRepository::GetWhere(
*this,
fmt::format(
"`zone` = {} AND `id` IN (SELECT `id` FROM `instance_list_player` WHERE `charid` = {})",
zone_id,
character_id
)
);
if (l.empty()) {
return 0;
}
auto& e = l.front();
return e.id;
}
bool Database::CopyCharacter(
const std::string& source_character_name,
const std::string& destination_character_name,
const std::string& destination_account_name
)
{
const auto& characters = CharacterDataRepository::GetWhere(
*this,
fmt::format(
"`name` = '{}' AND `deleted_at` IS NULL LIMIT 1",
Strings::Escape(source_character_name)
)
);
if (characters.empty()) {
LogError("No character found with name [{}]", source_character_name);
return false;
}
auto& character = characters.front();
const uint32 source_character_id = character.id;
const auto& accounts = AccountRepository::GetWhere(
*this,
fmt::format(
"`name` = '{}' LIMIT 1",
Strings::Escape(destination_account_name)
)
);
if (accounts.empty()) {
LogError("No account found with name [{}]", destination_account_name);
return false;
}
auto& account = accounts.front();
const int destination_account_id = account.id;
const int64 new_character_id = (CharacterDataRepository::GetMaxId(*this) + 1);
// validate destination name doesn't exist already
const auto& destination_characters = CharacterDataRepository::GetWhere(
*this,
fmt::format(
"`name` = '{}' AND `deleted_at` IS NULL LIMIT 1",
Strings::Escape(destination_character_name)
)
);
if (!destination_characters.empty()) {
LogError("Character [{}] already exists", destination_character_name);
return false;
}
std::vector<std::string> tables_to_zero_id = {
"keyring",
"data_buckets",
"character_instance_safereturns",
"character_expedition_lockouts",
"character_instance_lockouts",
"character_parcels",
"character_tribute",
"player_titlesets",
};
std::vector<std::string> ignore_tables = {
"guilds",
};
size_t total_rows_copied = 0;
TransactionBegin();
for (const auto &t : DatabaseSchema::GetCharacterTables()) {
const std::string& table_name = t.first;
const std::string& character_id_column_name = t.second;
if (Strings::Contains(ignore_tables, table_name)) {
continue;
}
auto results = QueryDatabase(
fmt::format(
"SHOW COLUMNS FROM {}",
table_name
)
);
std::vector<std::string> columns = {};
int column_count = 0;
for (auto row : results) {
columns.emplace_back(row[0]);
column_count++;
}
results = QueryDatabase(
fmt::format(
"SELECT {} FROM {} WHERE {} = {}",
Strings::Implode(",", Strings::Wrap(columns, "`")),
table_name,
character_id_column_name,
source_character_id
)
);
std::vector<std::vector<std::string>> new_rows;
for (auto row : results) {
std::vector<std::string> new_values = {};
for (int column_index = 0; column_index < column_count; column_index++) {
const std::string& column = columns[column_index];
std::string value = row[column_index] ? row[column_index] : "null";
if (column == "id" && Strings::Contains(tables_to_zero_id, table_name)) {
value = "0";
}
if (column == character_id_column_name) {
value = std::to_string(new_character_id);
}
if (column == "name" && table_name == "character_data") {
value = destination_character_name;
}
if (column == "account_id" && table_name == "character_data") {
value = std::to_string(destination_account_id);
}
if (!Strings::IsNumber(value)) {
value = Strings::Escape(value);
}
new_values.emplace_back(value);
}
new_rows.emplace_back(new_values);
}
std::vector<std::string> insert_rows;
for (auto &r : new_rows) {
const std::string& insert_row = "(" + Strings::Implode(",", Strings::Wrap(r, "'")) + ")";
insert_rows.emplace_back(insert_row);
}
if (!insert_rows.empty()) {
QueryDatabase(
fmt::format(
"DELETE FROM {} WHERE {} = {}",
table_name,
character_id_column_name,
new_character_id
)
);
auto insert = QueryDatabase(
fmt::format(
"INSERT INTO {} ({}) VALUES {}",
table_name,
Strings::Implode(",", Strings::Wrap(columns, "`")),
Strings::Implode(",", insert_rows)
)
);
size_t rows_copied = insert_rows.size(); // Rows copied for this table
total_rows_copied += rows_copied; // Increment grand total
LogInfo("Copying table [{}] rows [{}]", table_name, Strings::Commify(rows_copied));
if (!insert.ErrorMessage().empty()) {
TransactionRollback();
return false;
}
}
}
TransactionCommit();
LogInfo(
"Character [{}] copied to [{}] total rows [{}]",
source_character_name,
destination_character_name,
Strings::Commify(total_rows_copied)
);
return true;
}
void Database::SourceDatabaseTableFromUrl(const std::string& table_name, const std::string& url)
{
try {
uri request_uri(url);
LogHTTP(
"parsing url [{}] path [{}] host [{}] query_string [{}] protocol [{}] port [{}]",
url,
request_uri.get_path(),
request_uri.get_host(),
request_uri.get_query(),
request_uri.get_scheme(),
request_uri.get_port()
);
if (!DoesTableExist(table_name)) {
LogMySQLQuery("Table [{}] does not exist. Downloading and installing...", table_name);
// http get request
httplib::Client cli(
fmt::format(
"{}://{}",
request_uri.get_scheme(),
request_uri.get_host()
)
);
cli.set_connection_timeout(0, 60000000); // 60 sec
cli.set_read_timeout(60, 0); // 60 seconds
cli.set_write_timeout(60, 0); // 60 seconds
int sourced_queries = 0;
if (auto res = cli.Get(request_uri.get_path())) {
if (res->status == 200) {
for (auto& s : Strings::Split(res->body, ';')) {
if (!Strings::Trim(s).empty()) {
auto results = QueryDatabase(s);
if (!results.ErrorMessage().empty()) {
LogError("Error sourcing SQL [{}]", results.ErrorMessage());
return;
}
sourced_queries++;
}
}
}
} else {
LogError("Error retrieving URL [{}]", url);
}
LogMySQLQuery(
"Table [{}] installed. Sourced [{}] queries",
table_name,
sourced_queries
);
}
} catch (std::invalid_argument iae) {
LogError("URI parser error [{}]", iae.what());
}
}
void Database::SourceSqlFromUrl(const std::string& url)
{
try {
uri request_uri(url);
LogHTTPDetail(
"parsing url [{}] path [{}] host [{}] query_string [{}] protocol [{}] port [{}]",
url,
request_uri.get_path(),
request_uri.get_host(),
request_uri.get_query(),
request_uri.get_scheme(),
request_uri.get_port()
);
LogInfo("Downloading and installing from [{}]", url);
// http get request
httplib::Client cli(
fmt::format(
"{}://{}",
request_uri.get_scheme(),
request_uri.get_host()
)
);
cli.set_connection_timeout(0, 60000000); // 60 sec
cli.set_read_timeout(60, 0); // 60 seconds
cli.set_write_timeout(60, 0); // 60 seconds
if (auto res = cli.Get(request_uri.get_path())) {
if (res->status == 200) {
auto results = QueryDatabaseMulti(res->body);
if (!results.ErrorMessage().empty()) {
LogError("Error sourcing SQL [{}]", results.ErrorMessage());
return;
}
}
if (res->status == 404) {
LogError("Error retrieving URL [{}]", url);
}
} else {
LogError("Error retrieving URL [{}]", url);
}
} catch (std::invalid_argument iae) {
LogError("URI parser error [{}]", iae.what());
}
}
void Database::Encode(std::string &in)
{
for(int i = 0; i < in.length(); i++) {
in.at(i) += char('0');
}
};
void Database::Decode(std::string &in)
{
for(int i = 0; i < in.length(); i++) {
in.at(i) -= char('0');
}
};
void Database::PurgeCharacterParcels()
{
auto filter = fmt::format("sent_date < (NOW() - INTERVAL {} DAY)", RuleI(Parcel, ParcelPruneDelay));
auto results = CharacterParcelsRepository::GetWhere(*this, filter);
auto prune = CharacterParcelsRepository::DeleteWhere(*this, filter);
PlayerEvent::ParcelDelete pd{};
PlayerEventLogsRepository::PlayerEventLogs pel{};
pel.event_type_id = PlayerEvent::PARCEL_DELETE;
pel.event_type_name = PlayerEvent::EventName[pel.event_type_id];
std::stringstream ss;
for (auto const &r: results) {
pd.from_name = r.from_name;
pd.item_id = r.item_id;
pd.augment_1_id = r.aug_slot_1;
pd.augment_2_id = r.aug_slot_2;
pd.augment_3_id = r.aug_slot_3;
pd.augment_4_id = r.aug_slot_4;
pd.augment_5_id = r.aug_slot_5;
pd.augment_6_id = r.aug_slot_6;
pd.note = r.note;
pd.quantity = r.quantity;
pd.sent_date = r.sent_date;
pd.char_id = r.char_id;
{
cereal::JSONOutputArchiveSingleLine ar(ss);
pd.serialize(ar);
}
pel.event_data = ss.str();
pel.created_at = std::time(nullptr);
player_event_logs.AddToQueue(pel);
ss.str("");
ss.clear();
}
LogInfo(
"Purged <yellow>[{}] parcels that were over <yellow>[{}] days old.",
results.size(),
RuleI(Parcel, ParcelPruneDelay)
);
}
void Database::ClearGuildOnlineStatus()
{
GuildMembersRepository::ClearOnlineStatus(*this);
}
void Database::ClearTraderDetails()
{
TraderRepository::Truncate(*this);
}
void Database::ClearBuyerDetails()
{
BuyerRepository::DeleteBuyer(*this, 0);
}
uint64_t Database::GetNextTableId(const std::string &table_name)
{
auto results = QueryDatabase(fmt::format("SHOW TABLE STATUS LIKE '{}'", table_name));
for (auto row: results) {
for (int row_index = 0; row_index < results.ColumnCount(); row_index++) {
std::string field_name = Strings::ToLower(results.FieldName(row_index));
if (field_name == "auto_increment") {
std::string value = row[row_index] ? row[row_index] : "null";
return Strings::ToUnsignedBigInt(value, 1);
}
}
}
return 1;
}