eqemu-server/common/database.cpp
Knightly 7ab909ee47 Standardize Licensing
- License was intended to be GPLv3 per earlier commit of GPLv3 LICENSE FILE
- This is confirmed by the inclusion of libraries that are incompatible with GPLv2
- This is also confirmed by KLS and the agreement of KLS's predecessors
- Added GPLv3 license headers to the compilable source files
- Removed Folly licensing in strings.h since the string functions do not match the Folly functions and are standard functions - this must have been left over from previous implementations
- Removed individual contributor license headers since the project has been under the "developer" mantle for many years
- Removed comments on files that were previously automatically generated since they've been manually modified multiple times and there are no automatic scripts referencing them (removed in 2023)
2026-04-01 17:09:57 -07:00

2267 lines
49 KiB
C++

/* EQEmu: EQEmulator
Copyright (C) 2001-2026 EQEmu Development Team
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; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "common/data_verification.h"
#include "common/database_schema.h"
#include "common/database.h"
#include "common/eq_packet_structs.h"
#include "common/events/player_event_logs.h"
#include "common/extprofile.h"
#include "common/http/httplib.h"
#include "common/http/uri.h"
#include "common/platform/win/include_windows.h"
#include "common/repositories/account_repository.h"
#include "common/repositories/adventure_stats_repository.h"
#include "common/repositories/bot_data_repository.h"
#include "common/repositories/buyer_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_pet_name_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/merchantlist_temp_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/trader_repository.h"
#include "common/repositories/variables_repository.h"
#include "common/repositories/zone_repository.h"
#include "common/rulesys.h"
#include "common/strings.h"
#include "common/zone_store.h"
#include <map>
#include <algorithm>
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 loginserver [{}] name [{}] 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();
}
template<typename InputIterator, typename OutputIterator>
inline auto CleanMobName(InputIterator first, InputIterator last, OutputIterator result)
{
for (; first != last; ++first) {
if (*first == '_') {
*result = ' ';
}
else if (isalpha(*first) || *first == '`') {
*result = *first;
}
}
return result;
}
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::UpdateNameByID(const int character_id, const std::string& new_name)
{
LogInfo("Renaming [{}] to [{}]", character_id, new_name);
auto l = CharacterDataRepository::GetWhere(
*this,
fmt::format(
"`id` = {}",
character_id
)
);
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();
}
// Players cannot have the same name as a pet vanity name, or memory corruption occurs.
bool Database::IsPetNameUsed(const std::string& name)
{
const auto& pet_name_data = CharacterPetNameRepository::GetWhere(
*this,
fmt::format(
"`name` = '{}'",
Strings::Escape(name)
)
);
return !pet_name_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 ingame)
{
auto e = CharacterDataRepository::FindOne(*this, character_id);
e.ingame = ingame;
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::SetIngame(uint32 character_id, uint8 ingame)
{
auto e = CharacterDataRepository::FindOne(*this, character_id);
e.ingame = ingame;
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_evolving_items",
"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
)
);
if (!results.Success()) {
LogError("Transaction failed [{}] rolling back", results.ErrorMessage());
TransactionRollback();
return false;
}
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
)
);
if (!results.Success()) {
LogError("Transaction failed [{}] rolling back", results.ErrorMessage());
TransactionRollback();
return false;
}
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()) {
LogError("Error copying table [{}] [{}]", table_name, insert.ErrorMessage());
TransactionRollback();
return false;
}
}
}
auto r = TransactionCommit();
if (!r.Success()) {
LogError("Transaction failed [{}] rolling back", r.ErrorMessage());
return false;
}
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);
PlayerEventLogs::Instance()->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;
}