/* 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 #include #include #include #include #include #include #include #include #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/events/player_event_logs.h" // Disgrace: for windows compile #ifdef _WINDOWS #include #define snprintf _snprintf #define strncasecmp _strnicmp #define strcasecmp _stricmp #else #include "unix.h" #include #include #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 auto& l = CharacterDataRepository::GetWhere( *this, fmt::format( "`name` = '{}'", Strings::Escape(name) ) ); if (!l.empty()) { LogInfo("Account: [{}] tried to request name: [{}], but it is already taken", 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 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 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(pp->skills[slot_id]) } ); } CharacterSkillsRepository::ReplaceMany(*this, character_skills); std::vector 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(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::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); std::vector tables_to_zero_id = { "keyring", "data_buckets" }; TransactionBegin(); for (const auto &t : DatabaseSchema::GetCharacterTables()) { const std::string& table_name = t.first; const std::string& character_id_column_name = t.second; auto results = QueryDatabase( fmt::format( "SHOW COLUMNS FROM {}", table_name ) ); std::vector 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> new_rows; for (auto row : results) { std::vector 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); } new_values.emplace_back(value); } new_rows.emplace_back(new_values); } std::vector 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) ) ); if (!insert.ErrorMessage().empty()) { TransactionRollback(); return false; } } } TransactionCommit(); 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.aug_slot_1 = r.aug_slot_1; pd.aug_slot_2 = r.aug_slot_2; pd.aug_slot_3 = r.aug_slot_3; pd.aug_slot_4 = r.aug_slot_4; pd.aug_slot_5 = r.aug_slot_5; pd.aug_slot_6 = 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 [{}] parcels that were over [{}] 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); }