diff --git a/world/CMakeLists.txt b/world/CMakeLists.txt index a0830c6a2..1efd2707c 100644 --- a/world/CMakeLists.txt +++ b/world/CMakeLists.txt @@ -10,6 +10,9 @@ SET(world_sources eql_config.cpp eqemu_api_world_data_service.cpp expedition.cpp + expedition_cache.cpp + expedition_database.cpp + expedition_message.cpp launcher_link.cpp launcher_list.cpp lfplist.cpp @@ -41,6 +44,9 @@ SET(world_headers eql_config.h eqemu_api_world_data_service.h expedition.h + expedition_cache.h + expedition_database.h + expedition_message.h launcher_link.h launcher_list.h lfplist.h diff --git a/world/expedition.cpp b/world/expedition.cpp index b88417e85..9a2dd58df 100644 --- a/world/expedition.cpp +++ b/world/expedition.cpp @@ -19,17 +19,11 @@ */ #include "expedition.h" -#include "clientlist.h" -#include "cliententry.h" +#include "expedition_database.h" #include "zonelist.h" #include "zoneserver.h" -#include "worlddb.h" -#include "../common/servertalk.h" -#include "../common/string_util.h" +#include "../common/eqemu_logsys.h" -ExpeditionCache expedition_cache; - -extern ClientList client_list; extern ZSList zoneserver_list; Expedition::Expedition( @@ -120,487 +114,3 @@ void Expedition::CheckExpireWarning() } } } - -void ExpeditionCache::LoadActiveExpeditions() -{ - BenchTimer benchmark; - - m_expeditions = ExpeditionDatabase::LoadExpeditions(); - - auto elapsed = benchmark.elapsed(); - LogExpeditions("World caching [{}] expeditions took [{}s]", m_expeditions.size(), elapsed); -} - -void ExpeditionCache::AddExpedition(uint32_t expedition_id) -{ - if (expedition_id == 0) - { - return; - } - - auto expedition = ExpeditionDatabase::LoadExpedition(expedition_id); - - if (expedition.GetID() == expedition_id) - { - auto it = std::find_if(m_expeditions.begin(), m_expeditions.end(), [&](const Expedition& expedition) { - return expedition.GetID() == expedition_id; - }); - - if (it == m_expeditions.end()) - { - m_expeditions.emplace_back(expedition); - } - } -} - -void ExpeditionCache::RemoveExpedition(uint32_t expedition_id) -{ - m_expeditions.erase(std::remove_if(m_expeditions.begin(), m_expeditions.end(), - [&](const Expedition& expedition) { - return expedition.GetID() == expedition_id; - } - ), m_expeditions.end()); -} - -void ExpeditionCache::MemberChange(uint32_t expedition_id, uint32_t character_id, bool remove) -{ - auto it = std::find_if(m_expeditions.begin(), m_expeditions.end(), [&](const Expedition& expedition) { - return expedition.GetID() == expedition_id; - }); - - if (it != m_expeditions.end()) - { - if (remove) { - it->RemoveMember(character_id); - } else { - it->AddMember(character_id); - } - } -} - -void ExpeditionCache::RemoveAllMembers(uint32_t expedition_id) -{ - auto it = std::find_if(m_expeditions.begin(), m_expeditions.end(), [&](const Expedition& expedition) { - return expedition.GetID() == expedition_id; - }); - - if (it != m_expeditions.end()) - { - it->RemoveAllMembers(); - } -} - -void ExpeditionCache::SetSecondsRemaining(uint32_t expedition_id, uint32_t seconds_remaining) -{ - auto it = std::find_if(m_expeditions.begin(), m_expeditions.end(), [&](const Expedition& expedition) { - return expedition.GetID() == expedition_id; - }); - - if (it != m_expeditions.end()) - { - it->UpdateDzSecondsRemaining(seconds_remaining); - } -} - -void ExpeditionCache::Process() -{ - if (!m_process_throttle_timer.Check()) - { - return; - } - - std::vector expedition_ids; - - // check cache for expired or empty expeditions to delete and notify zones. - for (auto it = m_expeditions.begin(); it != m_expeditions.end();) - { - bool is_deleted = false; - - if (it->IsEmpty() || it->IsExpired()) - { - // don't delete expedition until its dz instance is empty. this prevents - // an exploit where all members leave expedition and complete an event - // before being kicked from removal timer. the lockout could never be - // applied because the zone expedition cache was already invalidated. - auto dz_zoneserver = zoneserver_list.FindByInstanceID(it->GetInstanceID()); - if (!dz_zoneserver || dz_zoneserver->NumPlayers() == 0) - { - LogExpeditions("Expedition [{}] expired or empty, notifying zones and deleting", it->GetID()); - expedition_ids.emplace_back(it->GetID()); - it->SendZonesExpeditionDeleted(); - is_deleted = true; - } - - if (it->IsEmpty() && !it->IsPendingDelete() && RuleB(Expedition, EmptyDzShutdownEnabled)) - { - it->UpdateDzSecondsRemaining(RuleI(Expedition, EmptyDzShutdownDelaySeconds)); - } - - it->SetPendingDelete(true); - } - else - { - it->CheckExpireWarning(); - } - - it = is_deleted ? m_expeditions.erase(it) : it + 1; - } - - if (!expedition_ids.empty()) - { - ExpeditionDatabase::DeleteExpeditions(expedition_ids); - } -} - -void ExpeditionDatabase::PurgeExpiredExpeditions() -{ - std::string query = SQL( - SELECT - expedition_details.id - FROM expedition_details - LEFT JOIN instance_list ON expedition_details.instance_id = instance_list.id - LEFT JOIN - ( - SELECT expedition_id, COUNT(*) member_count - FROM expedition_members - GROUP BY expedition_id - ) expedition_members - ON expedition_members.expedition_id = expedition_details.id - WHERE - instance_list.id IS NULL - OR expedition_members.member_count IS NULL - OR (instance_list.start_time + instance_list.duration) <= UNIX_TIMESTAMP(); - ); - - auto results = database.QueryDatabase(query); - if (results.Success()) - { - std::vector expedition_ids; - for (auto row = results.begin(); row != results.end(); ++row) - { - expedition_ids.emplace_back(static_cast(strtoul(row[0], nullptr, 10))); - } - ExpeditionDatabase::DeleteExpeditions(expedition_ids); - } -} - -void ExpeditionDatabase::PurgeExpiredCharacterLockouts() -{ - std::string query = SQL( - DELETE FROM character_expedition_lockouts - WHERE expire_time <= NOW(); - ); - - database.QueryDatabase(query); -} - -std::vector ExpeditionDatabase::LoadExpeditions() -{ - std::vector expeditions; - - std::string query = SQL( - SELECT - expedition_details.id, - expedition_details.instance_id, - instance_list.zone, - instance_list.start_time, - instance_list.duration, - expedition_members.character_id - FROM expedition_details - INNER JOIN instance_list ON expedition_details.instance_id = instance_list.id - INNER JOIN expedition_members ON expedition_members.expedition_id = expedition_details.id - ORDER BY expedition_details.id; - ); - - auto results = database.QueryDatabase(query); - if (results.Success()) - { - uint32_t last_expedition_id = 0; - - for (auto row = results.begin(); row != results.end(); ++row) - { - uint32_t expedition_id = strtoul(row[0], nullptr, 10); - - if (last_expedition_id != expedition_id) - { - expeditions.emplace_back( - static_cast(strtoul(row[0], nullptr, 10)), // expedition_id - static_cast(strtoul(row[1], nullptr, 10)), // dz_instance_id - static_cast(strtoul(row[2], nullptr, 10)), // dz_zone_id - static_cast(strtoul(row[3], nullptr, 10)), // start_time - static_cast(strtoul(row[4], nullptr, 10)) // duration - ); - } - - last_expedition_id = expedition_id; - - uint32_t member_id = static_cast(strtoul(row[5], nullptr, 10)); - expeditions.back().AddMember(member_id); - } - } - - return expeditions; -} - -Expedition ExpeditionDatabase::LoadExpedition(uint32_t expedition_id) -{ - LogExpeditions("Loading expedition [{}] for world cache", expedition_id); - - Expedition expedition; - - std::string query = fmt::format(SQL( - SELECT - expedition_details.id, - expedition_details.instance_id, - instance_list.zone, - instance_list.start_time, - instance_list.duration, - expedition_members.character_id - FROM expedition_details - INNER JOIN instance_list ON expedition_details.instance_id = instance_list.id - INNER JOIN expedition_members ON expedition_members.expedition_id = expedition_details.id - WHERE expedition_details.id = {}; - ), expedition_id); - - auto results = database.QueryDatabase(query); - if (results.Success()) - { - bool created = false; - for (auto row = results.begin(); row != results.end(); ++row) - { - if (!created) - { - expedition = Expedition{ - static_cast(strtoul(row[0], nullptr, 10)), // expedition_id - static_cast(strtoul(row[1], nullptr, 10)), // dz_instance_id - static_cast(strtoul(row[2], nullptr, 10)), // dz_zone_id - static_cast(strtoul(row[3], nullptr, 10)), // start_time - static_cast(strtoul(row[4], nullptr, 10)) // duration - }; - created = true; - } - - auto member_id = static_cast(strtoul(row[5], nullptr, 10)); - expedition.AddMember(member_id); - } - } - - return expedition; -} - -void ExpeditionDatabase::DeleteExpeditions(const std::vector& expedition_ids) -{ - LogExpeditionsDetail("Deleting [{}] expedition(s)", expedition_ids.size()); - - std::string expedition_ids_query; - for (const auto& expedition_id : expedition_ids) - { - fmt::format_to(std::back_inserter(expedition_ids_query), "{},", expedition_id); - } - - if (!expedition_ids_query.empty()) - { - expedition_ids_query.pop_back(); // trailing comma - - std::string query = fmt::format( - "DELETE FROM expedition_details WHERE id IN ({});", expedition_ids_query - ); - database.QueryDatabase(query); - - query = fmt::format( - "DELETE FROM expedition_members WHERE expedition_id IN ({});", expedition_ids_query - ); - database.QueryDatabase(query); - - query = fmt::format( - "DELETE FROM expedition_lockouts WHERE expedition_id IN ({});", expedition_ids_query - ); - database.QueryDatabase(query); - } -} - -void ExpeditionDatabase::UpdateDzDuration(uint16_t instance_id, uint32_t new_duration) -{ - std::string query = fmt::format( - "UPDATE instance_list SET duration = {} WHERE id = {};", - new_duration, instance_id - ); - - database.QueryDatabase(query); -} - -void ExpeditionMessage::HandleZoneMessage(ServerPacket* pack) -{ - switch (pack->opcode) - { - case ServerOP_ExpeditionCreate: - { - auto buf = reinterpret_cast(pack->pBuffer); - expedition_cache.AddExpedition(buf->expedition_id); - zoneserver_list.SendPacket(pack); - break; - } - case ServerOP_ExpeditionMemberChange: - { - auto buf = reinterpret_cast(pack->pBuffer); - expedition_cache.MemberChange(buf->expedition_id, buf->char_id, buf->removed); - zoneserver_list.SendPacket(pack); - break; - } - case ServerOP_ExpeditionMemberSwap: - { - auto buf = reinterpret_cast(pack->pBuffer); - expedition_cache.MemberChange(buf->expedition_id, buf->remove_char_id, true); - expedition_cache.MemberChange(buf->expedition_id, buf->add_char_id, false); - zoneserver_list.SendPacket(pack); - break; - } - case ServerOP_ExpeditionMembersRemoved: - { - auto buf = reinterpret_cast(pack->pBuffer); - expedition_cache.RemoveAllMembers(buf->expedition_id); - zoneserver_list.SendPacket(pack); - break; - } - case ServerOP_ExpeditionGetOnlineMembers: - { - ExpeditionMessage::GetOnlineMembers(pack); - break; - } - case ServerOP_ExpeditionDzAddPlayer: - { - ExpeditionMessage::AddPlayer(pack); - break; - } - case ServerOP_ExpeditionDzMakeLeader: - { - ExpeditionMessage::MakeLeader(pack); - break; - } - case ServerOP_ExpeditionCharacterLockout: - { - auto buf = reinterpret_cast(pack->pBuffer); - auto cle = client_list.FindCLEByCharacterID(buf->character_id); - if (cle && cle->Server()) - { - cle->Server()->SendPacket(pack); - } - break; - } - case ServerOP_ExpeditionSaveInvite: - { - ExpeditionMessage::SaveInvite(pack); - break; - } - case ServerOP_ExpeditionRequestInvite: - { - ExpeditionMessage::RequestInvite(pack); - break; - } - case ServerOP_ExpeditionSecondsRemaining: - { - auto buf = reinterpret_cast(pack->pBuffer); - expedition_cache.SetSecondsRemaining(buf->expedition_id, buf->new_duration_seconds); - break; - } - } -} - -void ExpeditionMessage::AddPlayer(ServerPacket* pack) -{ - auto buf = reinterpret_cast(pack->pBuffer); - - ClientListEntry* invited_cle = client_list.FindCharacter(buf->target_name); - if (invited_cle && invited_cle->Server()) - { - // continue in the add target's zone - buf->is_char_online = true; - invited_cle->Server()->SendPacket(pack); - } - else - { - // add target not online, return to inviter - ClientListEntry* inviter_cle = client_list.FindCharacter(buf->requester_name); - if (inviter_cle && inviter_cle->Server()) - { - inviter_cle->Server()->SendPacket(pack); - } - } -} - -void ExpeditionMessage::MakeLeader(ServerPacket* pack) -{ - auto buf = reinterpret_cast(pack->pBuffer); - - // notify requester (old leader) and new leader of the result - ZoneServer* new_leader_zs = nullptr; - ClientListEntry* new_leader_cle = client_list.FindCharacter(buf->target_name); - if (new_leader_cle && new_leader_cle->Server()) - { - buf->is_char_online = true; - new_leader_zs = new_leader_cle->Server(); - new_leader_zs->SendPacket(pack); - } - - // if old and new leader are in the same zone only send one message - ClientListEntry* requester_cle = client_list.FindCharacter(buf->requester_name); - if (requester_cle && requester_cle->Server() && requester_cle->Server() != new_leader_zs) - { - requester_cle->Server()->SendPacket(pack); - } -} - -void ExpeditionMessage::GetOnlineMembers(ServerPacket* pack) -{ - auto buf = reinterpret_cast(pack->pBuffer); - - // not efficient but only requested during caching - char zone_name[64] = {0}; - std::vector all_clients; - all_clients.reserve(client_list.GetClientCount()); - client_list.GetClients(zone_name, all_clients); - - for (uint32_t i = 0; i < buf->count; ++i) - { - auto it = std::find_if(all_clients.begin(), all_clients.end(), [&](const ClientListEntry* cle) { - return (cle && cle->CharID() == buf->entries[i].character_id); - }); - - if (it != all_clients.end()) - { - buf->entries[i].character_zone_id = (*it)->zone(); - buf->entries[i].character_instance_id = (*it)->instance(); - buf->entries[i].character_online = true; - } - } - - zoneserver_list.SendPacket(buf->sender_zone_id, buf->sender_instance_id, pack); -} - -void ExpeditionMessage::SaveInvite(ServerPacket* pack) -{ - auto buf = reinterpret_cast(pack->pBuffer); - - ClientListEntry* invited_cle = client_list.FindCharacter(buf->target_name); - if (invited_cle) - { - // store packet on cle and re-send it when client requests it - buf->is_char_online = true; - pack->opcode = ServerOP_ExpeditionDzAddPlayer; - invited_cle->SetPendingExpeditionInvite(pack); - } -} - -void ExpeditionMessage::RequestInvite(ServerPacket* pack) -{ - auto buf = reinterpret_cast(pack->pBuffer); - ClientListEntry* cle = client_list.FindCLEByCharacterID(buf->character_id); - if (cle) - { - auto invite_pack = cle->GetPendingExpeditionInvite(); - if (invite_pack && cle->Server()) - { - cle->Server()->SendPacket(invite_pack.get()); - } - } -} diff --git a/world/expedition.h b/world/expedition.h index 02a460f6e..c0c0da9a9 100644 --- a/world/expedition.h +++ b/world/expedition.h @@ -21,59 +21,16 @@ #ifndef WORLD_EXPEDITION_H #define WORLD_EXPEDITION_H -#include "../common/rulesys.h" #include "../common/timer.h" #include +#include #include -#include - -extern class ExpeditionCache expedition_cache; - -class Expedition; -class ServerPacket; - -namespace ExpeditionDatabase -{ - void PurgeExpiredExpeditions(); - void PurgeExpiredCharacterLockouts(); - std::vector LoadExpeditions(); - Expedition LoadExpedition(uint32_t expedition_id); - void DeleteExpeditions(const std::vector& expedition_ids); - void UpdateDzDuration(uint16_t instance_id, uint32_t new_duration); -}; - -namespace ExpeditionMessage -{ - void HandleZoneMessage(ServerPacket* pack); - void AddPlayer(ServerPacket* pack); - void MakeLeader(ServerPacket* pack); - void GetOnlineMembers(ServerPacket* pack); - void SaveInvite(ServerPacket* pack); - void RequestInvite(ServerPacket* pack); -}; - -class ExpeditionCache -{ -public: - void AddExpedition(uint32_t expedition_id); - void RemoveExpedition(uint32_t expedition_id); - void LoadActiveExpeditions(); - void MemberChange(uint32_t expedition_id, uint32_t character_id, bool remove); - void RemoveAllMembers(uint32_t expedition_id); - void SetSecondsRemaining(uint32_t expedition_id, uint32_t seconds_remaining); - void Process(); - -private: - std::vector m_expeditions; - Timer m_process_throttle_timer{static_cast(RuleI(Expedition, WorldExpeditionProcessRateMS))}; -}; class Expedition { public: Expedition() = default; - Expedition( - uint32_t expedition_id, uint32_t instance_id, uint32_t dz_zone_id, + Expedition(uint32_t expedition_id, uint32_t instance_id, uint32_t dz_zone_id, uint32_t expire_time, uint32_t duration); void AddMember(uint32_t character_id) { m_member_ids.emplace(character_id); } diff --git a/world/expedition_cache.cpp b/world/expedition_cache.cpp new file mode 100644 index 000000000..2c1b68f5b --- /dev/null +++ b/world/expedition_cache.cpp @@ -0,0 +1,161 @@ +/** + * EQEmulator: Everquest Server Emulator + * Copyright (C) 2001-2020 EQEmulator Development Team (https://github.com/EQEmu/Server) + * + * 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 "expedition_cache.h" +#include "expedition.h" +#include "expedition_database.h" +#include "zonelist.h" +#include "zoneserver.h" +#include "../common/eqemu_logsys.h" +#include + +extern ZSList zoneserver_list; + +ExpeditionCache expedition_cache; + +void ExpeditionCache::LoadActiveExpeditions() +{ + BenchTimer benchmark; + + m_expeditions = ExpeditionDatabase::LoadExpeditions(); + + auto elapsed = benchmark.elapsed(); + LogExpeditions("World caching [{}] expeditions took [{}s]", m_expeditions.size(), elapsed); +} + +void ExpeditionCache::AddExpedition(uint32_t expedition_id) +{ + if (expedition_id == 0) + { + return; + } + + auto expedition = ExpeditionDatabase::LoadExpedition(expedition_id); + + if (expedition.GetID() == expedition_id) + { + auto it = std::find_if(m_expeditions.begin(), m_expeditions.end(), [&](const Expedition& expedition) { + return expedition.GetID() == expedition_id; + }); + + if (it == m_expeditions.end()) + { + m_expeditions.emplace_back(expedition); + } + } +} + +void ExpeditionCache::RemoveExpedition(uint32_t expedition_id) +{ + m_expeditions.erase(std::remove_if(m_expeditions.begin(), m_expeditions.end(), + [&](const Expedition& expedition) { + return expedition.GetID() == expedition_id; + } + ), m_expeditions.end()); +} + +void ExpeditionCache::MemberChange(uint32_t expedition_id, uint32_t character_id, bool remove) +{ + auto it = std::find_if(m_expeditions.begin(), m_expeditions.end(), [&](const Expedition& expedition) { + return expedition.GetID() == expedition_id; + }); + + if (it != m_expeditions.end()) + { + if (remove) { + it->RemoveMember(character_id); + } else { + it->AddMember(character_id); + } + } +} + +void ExpeditionCache::RemoveAllMembers(uint32_t expedition_id) +{ + auto it = std::find_if(m_expeditions.begin(), m_expeditions.end(), [&](const Expedition& expedition) { + return expedition.GetID() == expedition_id; + }); + + if (it != m_expeditions.end()) + { + it->RemoveAllMembers(); + } +} + +void ExpeditionCache::SetSecondsRemaining(uint32_t expedition_id, uint32_t seconds_remaining) +{ + auto it = std::find_if(m_expeditions.begin(), m_expeditions.end(), [&](const Expedition& expedition) { + return expedition.GetID() == expedition_id; + }); + + if (it != m_expeditions.end()) + { + it->UpdateDzSecondsRemaining(seconds_remaining); + } +} + +void ExpeditionCache::Process() +{ + if (!m_process_throttle_timer.Check()) + { + return; + } + + std::vector expedition_ids; + + for (auto it = m_expeditions.begin(); it != m_expeditions.end();) + { + bool is_deleted = false; + + if (it->IsEmpty() || it->IsExpired()) + { + // don't delete expedition until its dz instance is empty. this prevents + // an exploit where all members leave expedition and complete an event + // before being kicked from removal timer. the lockout could never be + // applied because the zone expedition cache was already invalidated. + auto dz_zoneserver = zoneserver_list.FindByInstanceID(it->GetInstanceID()); + if (!dz_zoneserver || dz_zoneserver->NumPlayers() == 0) + { + LogExpeditions("Expedition [{}] expired or empty, notifying zones and deleting", it->GetID()); + expedition_ids.emplace_back(it->GetID()); + it->SendZonesExpeditionDeleted(); + is_deleted = true; + } + + if (it->IsEmpty() && !it->IsPendingDelete() && RuleB(Expedition, EmptyDzShutdownEnabled)) + { + it->UpdateDzSecondsRemaining(RuleI(Expedition, EmptyDzShutdownDelaySeconds)); + } + + it->SetPendingDelete(true); + } + else + { + it->CheckExpireWarning(); + } + + it = is_deleted ? m_expeditions.erase(it) : it + 1; + } + + if (!expedition_ids.empty()) + { + ExpeditionDatabase::DeleteExpeditions(expedition_ids); + } +} diff --git a/world/expedition_cache.h b/world/expedition_cache.h new file mode 100644 index 000000000..83632e51e --- /dev/null +++ b/world/expedition_cache.h @@ -0,0 +1,49 @@ +/** + * EQEmulator: Everquest Server Emulator + * Copyright (C) 2001-2020 EQEmulator Development Team (https://github.com/EQEmu/Server) + * + * 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 + * + */ + +#ifndef WORLD_EXPEDITION_CACHE_H +#define WORLD_EXPEDITION_CACHE_H + +#include "../common/rulesys.h" +#include "../common/timer.h" +#include +#include + +extern class ExpeditionCache expedition_cache; + +class Expedition; + +class ExpeditionCache +{ +public: + void AddExpedition(uint32_t expedition_id); + void LoadActiveExpeditions(); + void MemberChange(uint32_t expedition_id, uint32_t character_id, bool remove); + void Process(); + void RemoveAllMembers(uint32_t expedition_id); + void RemoveExpedition(uint32_t expedition_id); + void SetSecondsRemaining(uint32_t expedition_id, uint32_t seconds_remaining); + +private: + std::vector m_expeditions; + Timer m_process_throttle_timer{static_cast(RuleI(Expedition, WorldExpeditionProcessRateMS))}; +}; + +#endif diff --git a/world/expedition_database.cpp b/world/expedition_database.cpp new file mode 100644 index 000000000..7d7387186 --- /dev/null +++ b/world/expedition_database.cpp @@ -0,0 +1,200 @@ +/** + * EQEmulator: Everquest Server Emulator + * Copyright (C) 2001-2020 EQEmulator Development Team (https://github.com/EQEmu/Server) + * + * 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 "expedition_database.h" +#include "expedition.h" +#include "worlddb.h" + +void ExpeditionDatabase::PurgeExpiredExpeditions() +{ + std::string query = SQL( + SELECT + expedition_details.id + FROM expedition_details + LEFT JOIN instance_list ON expedition_details.instance_id = instance_list.id + LEFT JOIN + ( + SELECT expedition_id, COUNT(*) member_count + FROM expedition_members + GROUP BY expedition_id + ) expedition_members + ON expedition_members.expedition_id = expedition_details.id + WHERE + instance_list.id IS NULL + OR expedition_members.member_count IS NULL + OR (instance_list.start_time + instance_list.duration) <= UNIX_TIMESTAMP(); + ); + + auto results = database.QueryDatabase(query); + if (results.Success()) + { + std::vector expedition_ids; + for (auto row = results.begin(); row != results.end(); ++row) + { + expedition_ids.emplace_back(static_cast(strtoul(row[0], nullptr, 10))); + } + ExpeditionDatabase::DeleteExpeditions(expedition_ids); + } +} + +void ExpeditionDatabase::PurgeExpiredCharacterLockouts() +{ + std::string query = SQL( + DELETE FROM character_expedition_lockouts + WHERE expire_time <= NOW(); + ); + + database.QueryDatabase(query); +} + +std::vector ExpeditionDatabase::LoadExpeditions() +{ + std::vector expeditions; + + std::string query = SQL( + SELECT + expedition_details.id, + expedition_details.instance_id, + instance_list.zone, + instance_list.start_time, + instance_list.duration, + expedition_members.character_id + FROM expedition_details + INNER JOIN instance_list ON expedition_details.instance_id = instance_list.id + INNER JOIN expedition_members ON expedition_members.expedition_id = expedition_details.id + ORDER BY expedition_details.id; + ); + + auto results = database.QueryDatabase(query); + if (results.Success()) + { + uint32_t last_expedition_id = 0; + + for (auto row = results.begin(); row != results.end(); ++row) + { + uint32_t expedition_id = strtoul(row[0], nullptr, 10); + + if (last_expedition_id != expedition_id) + { + expeditions.emplace_back( + static_cast(strtoul(row[0], nullptr, 10)), // expedition_id + static_cast(strtoul(row[1], nullptr, 10)), // dz_instance_id + static_cast(strtoul(row[2], nullptr, 10)), // dz_zone_id + static_cast(strtoul(row[3], nullptr, 10)), // start_time + static_cast(strtoul(row[4], nullptr, 10)) // duration + ); + } + + last_expedition_id = expedition_id; + + uint32_t member_id = static_cast(strtoul(row[5], nullptr, 10)); + expeditions.back().AddMember(member_id); + } + } + + return expeditions; +} + +Expedition ExpeditionDatabase::LoadExpedition(uint32_t expedition_id) +{ + LogExpeditions("Loading expedition [{}] for world cache", expedition_id); + + Expedition expedition; + + std::string query = fmt::format(SQL( + SELECT + expedition_details.id, + expedition_details.instance_id, + instance_list.zone, + instance_list.start_time, + instance_list.duration, + expedition_members.character_id + FROM expedition_details + INNER JOIN instance_list ON expedition_details.instance_id = instance_list.id + INNER JOIN expedition_members ON expedition_members.expedition_id = expedition_details.id + WHERE expedition_details.id = {}; + ), expedition_id); + + auto results = database.QueryDatabase(query); + if (results.Success()) + { + bool created = false; + for (auto row = results.begin(); row != results.end(); ++row) + { + if (!created) + { + expedition = Expedition{ + static_cast(strtoul(row[0], nullptr, 10)), // expedition_id + static_cast(strtoul(row[1], nullptr, 10)), // dz_instance_id + static_cast(strtoul(row[2], nullptr, 10)), // dz_zone_id + static_cast(strtoul(row[3], nullptr, 10)), // start_time + static_cast(strtoul(row[4], nullptr, 10)) // duration + }; + created = true; + } + + auto member_id = static_cast(strtoul(row[5], nullptr, 10)); + expedition.AddMember(member_id); + } + } + + return expedition; +} + +void ExpeditionDatabase::DeleteExpeditions(const std::vector& expedition_ids) +{ + LogExpeditionsDetail("Deleting [{}] expedition(s)", expedition_ids.size()); + + std::string expedition_ids_query; + for (const auto& expedition_id : expedition_ids) + { + fmt::format_to(std::back_inserter(expedition_ids_query), "{},", expedition_id); + } + + if (!expedition_ids_query.empty()) + { + expedition_ids_query.pop_back(); // trailing comma + + std::string query = fmt::format( + "DELETE FROM expedition_details WHERE id IN ({});", expedition_ids_query + ); + database.QueryDatabase(query); + + query = fmt::format( + "DELETE FROM expedition_members WHERE expedition_id IN ({});", expedition_ids_query + ); + database.QueryDatabase(query); + + query = fmt::format( + "DELETE FROM expedition_lockouts WHERE expedition_id IN ({});", expedition_ids_query + ); + database.QueryDatabase(query); + } +} + +void ExpeditionDatabase::UpdateDzDuration(uint16_t instance_id, uint32_t new_duration) +{ + std::string query = fmt::format( + "UPDATE instance_list SET duration = {} WHERE id = {};", + new_duration, instance_id + ); + + database.QueryDatabase(query); +} diff --git a/world/expedition_database.h b/world/expedition_database.h new file mode 100644 index 000000000..ea33e823b --- /dev/null +++ b/world/expedition_database.h @@ -0,0 +1,39 @@ +/** + * EQEmulator: Everquest Server Emulator + * Copyright (C) 2001-2020 EQEmulator Development Team (https://github.com/EQEmu/Server) + * + * 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 + * + */ + +#ifndef WORLD_EXPEDITION_DATABASE_H +#define WORLD_EXPEDITION_DATABASE_H + +#include +#include + +class Expedition; + +namespace ExpeditionDatabase +{ + void DeleteExpeditions(const std::vector& expedition_ids); + std::vector LoadExpeditions(); + Expedition LoadExpedition(uint32_t expedition_id); + void PurgeExpiredExpeditions(); + void PurgeExpiredCharacterLockouts(); + void UpdateDzDuration(uint16_t instance_id, uint32_t new_duration); +}; + +#endif diff --git a/world/expedition_message.cpp b/world/expedition_message.cpp new file mode 100644 index 000000000..990c4dc41 --- /dev/null +++ b/world/expedition_message.cpp @@ -0,0 +1,207 @@ +/** + * EQEmulator: Everquest Server Emulator + * Copyright (C) 2001-2020 EQEmulator Development Team (https://github.com/EQEmu/Server) + * + * 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 "expedition_message.h" +#include "expedition_cache.h" +#include "cliententry.h" +#include "clientlist.h" +#include "zonelist.h" +#include "zoneserver.h" +#include "../common/servertalk.h" +#include + +extern ClientList client_list; +extern ZSList zoneserver_list; + +void ExpeditionMessage::HandleZoneMessage(ServerPacket* pack) +{ + switch (pack->opcode) + { + case ServerOP_ExpeditionCreate: + { + auto buf = reinterpret_cast(pack->pBuffer); + expedition_cache.AddExpedition(buf->expedition_id); + zoneserver_list.SendPacket(pack); + break; + } + case ServerOP_ExpeditionMemberChange: + { + auto buf = reinterpret_cast(pack->pBuffer); + expedition_cache.MemberChange(buf->expedition_id, buf->char_id, buf->removed); + zoneserver_list.SendPacket(pack); + break; + } + case ServerOP_ExpeditionMemberSwap: + { + auto buf = reinterpret_cast(pack->pBuffer); + expedition_cache.MemberChange(buf->expedition_id, buf->remove_char_id, true); + expedition_cache.MemberChange(buf->expedition_id, buf->add_char_id, false); + zoneserver_list.SendPacket(pack); + break; + } + case ServerOP_ExpeditionMembersRemoved: + { + auto buf = reinterpret_cast(pack->pBuffer); + expedition_cache.RemoveAllMembers(buf->expedition_id); + zoneserver_list.SendPacket(pack); + break; + } + case ServerOP_ExpeditionGetOnlineMembers: + { + ExpeditionMessage::GetOnlineMembers(pack); + break; + } + case ServerOP_ExpeditionDzAddPlayer: + { + ExpeditionMessage::AddPlayer(pack); + break; + } + case ServerOP_ExpeditionDzMakeLeader: + { + ExpeditionMessage::MakeLeader(pack); + break; + } + case ServerOP_ExpeditionCharacterLockout: + { + auto buf = reinterpret_cast(pack->pBuffer); + auto cle = client_list.FindCLEByCharacterID(buf->character_id); + if (cle && cle->Server()) + { + cle->Server()->SendPacket(pack); + } + break; + } + case ServerOP_ExpeditionSaveInvite: + { + ExpeditionMessage::SaveInvite(pack); + break; + } + case ServerOP_ExpeditionRequestInvite: + { + ExpeditionMessage::RequestInvite(pack); + break; + } + case ServerOP_ExpeditionSecondsRemaining: + { + auto buf = reinterpret_cast(pack->pBuffer); + expedition_cache.SetSecondsRemaining(buf->expedition_id, buf->new_duration_seconds); + break; + } + } +} + +void ExpeditionMessage::AddPlayer(ServerPacket* pack) +{ + auto buf = reinterpret_cast(pack->pBuffer); + + ClientListEntry* invited_cle = client_list.FindCharacter(buf->target_name); + if (invited_cle && invited_cle->Server()) + { + // continue in the add target's zone + buf->is_char_online = true; + invited_cle->Server()->SendPacket(pack); + } + else + { + // add target not online, return to inviter + ClientListEntry* inviter_cle = client_list.FindCharacter(buf->requester_name); + if (inviter_cle && inviter_cle->Server()) + { + inviter_cle->Server()->SendPacket(pack); + } + } +} + +void ExpeditionMessage::MakeLeader(ServerPacket* pack) +{ + auto buf = reinterpret_cast(pack->pBuffer); + + // notify requester (old leader) and new leader of the result + ZoneServer* new_leader_zs = nullptr; + ClientListEntry* new_leader_cle = client_list.FindCharacter(buf->target_name); + if (new_leader_cle && new_leader_cle->Server()) + { + buf->is_char_online = true; + new_leader_zs = new_leader_cle->Server(); + new_leader_zs->SendPacket(pack); + } + + // if old and new leader are in the same zone only send one message + ClientListEntry* requester_cle = client_list.FindCharacter(buf->requester_name); + if (requester_cle && requester_cle->Server() && requester_cle->Server() != new_leader_zs) + { + requester_cle->Server()->SendPacket(pack); + } +} + +void ExpeditionMessage::GetOnlineMembers(ServerPacket* pack) +{ + auto buf = reinterpret_cast(pack->pBuffer); + + // not efficient but only requested during caching + char zone_name[64] = {0}; + std::vector all_clients; + all_clients.reserve(client_list.GetClientCount()); + client_list.GetClients(zone_name, all_clients); + + for (uint32_t i = 0; i < buf->count; ++i) + { + auto it = std::find_if(all_clients.begin(), all_clients.end(), [&](const ClientListEntry* cle) { + return (cle && cle->CharID() == buf->entries[i].character_id); + }); + + if (it != all_clients.end()) + { + buf->entries[i].character_zone_id = (*it)->zone(); + buf->entries[i].character_instance_id = (*it)->instance(); + buf->entries[i].character_online = true; + } + } + + zoneserver_list.SendPacket(buf->sender_zone_id, buf->sender_instance_id, pack); +} + +void ExpeditionMessage::SaveInvite(ServerPacket* pack) +{ + auto buf = reinterpret_cast(pack->pBuffer); + + ClientListEntry* invited_cle = client_list.FindCharacter(buf->target_name); + if (invited_cle) + { + // store packet on cle and re-send it when client requests it + buf->is_char_online = true; + pack->opcode = ServerOP_ExpeditionDzAddPlayer; + invited_cle->SetPendingExpeditionInvite(pack); + } +} + +void ExpeditionMessage::RequestInvite(ServerPacket* pack) +{ + auto buf = reinterpret_cast(pack->pBuffer); + ClientListEntry* cle = client_list.FindCLEByCharacterID(buf->character_id); + if (cle) + { + auto invite_pack = cle->GetPendingExpeditionInvite(); + if (invite_pack && cle->Server()) + { + cle->Server()->SendPacket(invite_pack.get()); + } + } +} diff --git a/world/expedition_message.h b/world/expedition_message.h new file mode 100644 index 000000000..17470d788 --- /dev/null +++ b/world/expedition_message.h @@ -0,0 +1,36 @@ +/** + * EQEmulator: Everquest Server Emulator + * Copyright (C) 2001-2020 EQEmulator Development Team (https://github.com/EQEmu/Server) + * + * 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 + * + */ + +#ifndef WORLD_EXPEDITION_MESSAGE_H +#define WORLD_EXPEDITION_MESSAGE_H + +class ServerPacket; + +namespace ExpeditionMessage +{ + void AddPlayer(ServerPacket* pack); + void GetOnlineMembers(ServerPacket* pack); + void HandleZoneMessage(ServerPacket* pack); + void MakeLeader(ServerPacket* pack); + void RequestInvite(ServerPacket* pack); + void SaveInvite(ServerPacket* pack); +}; + +#endif diff --git a/world/main.cpp b/world/main.cpp index a3ca55e48..e10b09b45 100644 --- a/world/main.cpp +++ b/world/main.cpp @@ -88,7 +88,8 @@ union semun { #include "queryserv.h" #include "web_interface.h" #include "console.h" -#include "expedition.h" +#include "expedition_cache.h" +#include "expedition_database.h" #include "../common/net/servertalk_server.h" #include "../zone/data_bucket.h" diff --git a/world/zoneserver.cpp b/world/zoneserver.cpp index e1beff363..0a1a960d7 100644 --- a/world/zoneserver.cpp +++ b/world/zoneserver.cpp @@ -36,7 +36,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "ucs.h" #include "queryserv.h" #include "world_store.h" -#include "expedition.h" +#include "expedition_message.h" extern ClientList client_list; extern GroupLFPList LFPGroupList;