eqemu-server/world/dynamic_zone_manager.cpp
hg 53b599518a
[Dynamic Zones] Implement dz templates (#2345)
This allows shared tasks to create dz instances automatically through
the `dz_template_id` field instead of using quest scripts. Quest apis
were also added to create expeditions from template ids.
2022-07-30 21:25:43 -05:00

177 lines
5.3 KiB
C++

#include "dynamic_zone_manager.h"
#include "dynamic_zone.h"
#include "worlddb.h"
#include "zonelist.h"
#include "zoneserver.h"
#include "../common/rulesys.h"
#include "../common/repositories/expeditions_repository.h"
#include "../common/repositories/expedition_lockouts_repository.h"
extern ZSList zoneserver_list;
DynamicZoneManager dynamic_zone_manager;
DynamicZoneManager::DynamicZoneManager() :
m_process_throttle_timer{ static_cast<uint32_t>(RuleI(DynamicZone, WorldProcessRate)) }
{
}
void DynamicZoneManager::PurgeExpiredDynamicZones()
{
// purge when no members, instance is expired, or instance doesn't exist.
// this prevents characters remaining members of dzs that expired while
// server was offline but delayed instance purging hasn't cleaned yet
auto dz_ids = DynamicZonesRepository::GetStaleIDs(database);
if (!dz_ids.empty())
{
LogDynamicZones("Purging [{}] dynamic zone(s)", dz_ids.size());
DynamicZoneMembersRepository::DeleteWhere(database,
fmt::format("dynamic_zone_id IN ({})", fmt::join(dz_ids, ",")));
DynamicZonesRepository::DeleteWhere(database,
fmt::format("id IN ({})", fmt::join(dz_ids, ",")));
}
}
DynamicZone* DynamicZoneManager::CreateNew(
DynamicZone& dz_request, const std::vector<DynamicZoneMember>& members)
{
// this creates a new dz instance and saves it to both db and cache
uint32_t dz_id = dz_request.Create();
if (dz_id == 0)
{
LogDynamicZones("Failed to create dynamic zone for zone [{}]", dz_request.GetZoneID());
return nullptr;
}
auto dz = std::make_unique<DynamicZone>(dz_request);
if (!members.empty())
{
dz->SaveMembers(members);
dz->CacheMemberStatuses();
}
LogDynamicZones("Created new dz [{}] for zone [{}]", dz_id, dz_request.GetZoneID());
auto pack = dz->CreateServerDzCreatePacket(0, 0);
zoneserver_list.SendPacket(pack.get());
auto inserted = dynamic_zone_cache.emplace(dz_id, std::move(dz));
return inserted.first->second.get();
}
void DynamicZoneManager::CacheNewDynamicZone(ServerPacket* pack)
{
auto buf = reinterpret_cast<ServerDzCreateSerialized_Struct*>(pack->pBuffer);
auto new_dz = std::make_unique<DynamicZone>();
new_dz->LoadSerializedDzPacket(buf->cereal_data, buf->cereal_size);
new_dz->CacheMemberStatuses();
// reserialize with member statuses cached before forwarding (restore origin zone)
auto repack = new_dz->CreateServerDzCreatePacket(buf->origin_zone_id, buf->origin_instance_id);
uint32_t dz_id = new_dz->GetID();
dynamic_zone_cache.emplace(dz_id, std::move(new_dz));
LogDynamicZones("Cached new dynamic zone [{}]", dz_id);
zoneserver_list.SendPacket(repack.get());
}
void DynamicZoneManager::CacheAllFromDatabase()
{
BenchTimer bench;
auto dynamic_zones = DynamicZonesRepository::AllWithInstanceNotExpired(database);
auto dynamic_zone_members = DynamicZoneMembersRepository::GetAllWithNames(database);
dynamic_zone_cache.clear();
dynamic_zone_cache.reserve(dynamic_zones.size());
for (auto& entry : dynamic_zones)
{
uint32_t dz_id = entry.id;
auto dz = std::make_unique<DynamicZone>(std::move(entry));
for (auto& member : dynamic_zone_members)
{
if (member.dynamic_zone_id == dz_id)
{
dz->AddMemberFromRepositoryResult(std::move(member));
}
}
// note leader status won't be updated here until leader is set by owning system (expeditions)
dz->CacheMemberStatuses();
dynamic_zone_cache.emplace(dz_id, std::move(dz));
}
LogDynamicZones("Caching [{}] dynamic zone(s) took [{}s]", dynamic_zone_cache.size(), bench.elapsed());
}
void DynamicZoneManager::Process()
{
if (!m_process_throttle_timer.Check())
{
return;
}
std::vector<uint32_t> dynamic_zone_ids;
for (const auto& dz_iter : dynamic_zone_cache)
{
DynamicZone* dz = dz_iter.second.get();
// dynamic zone is not deleted until its zone has no clients to prevent exploits
// clients should be removed by zone-based kick timers if expired but not empty
DynamicZoneStatus status = dz->Process();
if (status == DynamicZoneStatus::ExpiredEmpty)
{
LogDynamicZones("[{}] expired with [{}] members, notifying zones and deleting", dz->GetID(), dz->GetMemberCount());
dynamic_zone_ids.emplace_back(dz->GetID());
dz->SendZonesDynamicZoneDeleted(); // delete dz from zone caches
}
}
if (!dynamic_zone_ids.empty())
{
for (const auto& dz_id : dynamic_zone_ids)
{
dynamic_zone_cache.erase(dz_id);
}
// need to look up expedition ids until lockouts are moved to dynamic zones
std::vector<uint32_t> expedition_ids;
auto expeditions = ExpeditionsRepository::GetWhere(database,
fmt::format("dynamic_zone_id IN ({})", fmt::join(dynamic_zone_ids, ",")));
if (!expeditions.empty())
{
for (const auto& expedition : expeditions)
{
expedition_ids.emplace_back(expedition.id);
}
ExpeditionLockoutsRepository::DeleteWhere(database,
fmt::format("expedition_id IN ({})", fmt::join(expedition_ids, ",")));
}
ExpeditionsRepository::DeleteWhere(database,
fmt::format("dynamic_zone_id IN ({})", fmt::join(dynamic_zone_ids, ",")));
DynamicZoneMembersRepository::RemoveAllMembers(database, dynamic_zone_ids);
DynamicZonesRepository::DeleteWhere(database,
fmt::format("id IN ({})", fmt::join(dynamic_zone_ids, ",")));
}
}
void DynamicZoneManager::LoadTemplates()
{
m_dz_templates.clear();
auto dz_templates = DynamicZoneTemplatesRepository::All(content_db);
for (const auto& dz_template : dz_templates)
{
m_dz_templates[dz_template.id] = dz_template;
}
}