#include "dynamic_zone.h" #include "cliententry.h" #include "clientlist.h" #include "dynamic_zone_manager.h" #include "worlddb.h" #include "zonelist.h" #include "zoneserver.h" #include "../common/eqemu_logsys.h" #include "../common/repositories/instance_list_repository.h" Database& DynamicZone::GetDatabase() { return database; } bool DynamicZone::SendServerPacket(ServerPacket* packet) { return ZSList::Instance()->SendPacket(packet); } DynamicZone* DynamicZone::FindDynamicZoneByID(uint32_t dz_id) { auto dz = dynamic_zone_manager.dynamic_zone_cache.find(dz_id); if (dz != dynamic_zone_manager.dynamic_zone_cache.end()) { return dz->second.get(); } return nullptr; } void DynamicZone::ChooseNewLeader() { if (m_members.empty() || !m_choose_leader_cooldown_timer.Check()) { m_choose_leader_needed = true; return; } auto it = std::find_if(m_members.begin(), m_members.end(), [&](const DynamicZoneMember& member) { if (member.id != GetLeaderID() && member.IsOnline()) { auto member_cle = ClientList::Instance()->FindCLEByCharacterID(member.id); return (member_cle && member_cle->GetOnline() == CLE_Status::InZone); } return false; }); if (it == m_members.end()) { // no online members found, fallback to choosing any member it = std::find_if(m_members.begin(), m_members.end(), [&](const DynamicZoneMember& member) { return member.id != GetLeaderID(); }); } if (it != m_members.end() && SetNewLeader(it->id)) { m_choose_leader_needed = false; } } bool DynamicZone::SetNewLeader(uint32_t member_id) { auto new_leader = GetMemberData(member_id); if (!new_leader.IsValid()) { return false; } LogDynamicZonesDetail("Replacing dz [{}] leader [{}] with [{}]", GetID(), GetLeaderName(), new_leader.name); SetLeader(new_leader, true); SendZonesLeaderChanged(); return true; } void DynamicZone::CheckLeader() { if (m_choose_leader_needed) { ChooseNewLeader(); } } DynamicZoneStatus DynamicZone::Process() { DynamicZoneStatus status = DynamicZoneStatus::Normal; // force expire if no members if (!HasMembers() || IsExpired()) { status = DynamicZoneStatus::Expired; auto dz_zoneserver = ZSList::Instance()->FindByInstanceID(GetInstanceID()); if (!dz_zoneserver || dz_zoneserver->NumPlayers() == 0) // no clients inside dz { status = DynamicZoneStatus::ExpiredEmpty; if (!HasMembers() && !m_is_pending_early_shutdown && RuleB(DynamicZone, EmptyShutdownEnabled)) { SetSecondsRemaining(RuleI(DynamicZone, EmptyShutdownDelaySeconds)); m_is_pending_early_shutdown = true; } } } if (GetType() == DynamicZoneType::Expedition && status != DynamicZoneStatus::ExpiredEmpty) { CheckExpireWarning(); CheckLeader(); } return status; } void DynamicZone::SendZonesDynamicZoneDeleted() { uint32_t pack_size = sizeof(ServerDzID_Struct); auto pack = std::make_unique(ServerOP_DzDeleted, pack_size); auto buf = reinterpret_cast(pack->pBuffer); buf->dz_id = GetID(); ZSList::Instance()->SendPacket(pack.get()); } void DynamicZone::SetSecondsRemaining(uint32_t seconds_remaining) { auto now = std::chrono::system_clock::now(); auto new_remaining = std::chrono::seconds(seconds_remaining); auto current_remaining = m_expire_time - now; if (current_remaining > new_remaining) // reduce only { LogDynamicZonesDetail("Updating dynamic zone [{}] instance [{}] seconds remaining to [{}]s", GetID(), GetInstanceID(), seconds_remaining); // preserve original start time and adjust duration instead m_expire_time = now + new_remaining; m_duration = std::chrono::duration_cast(m_expire_time - m_start_time); InstanceListRepository::UpdateDuration( database, GetInstanceID(), static_cast(m_duration.count()) ); SendZonesDurationUpdate(); // update zone caches and actual instance's timer } } void DynamicZone::SendZonesDurationUpdate() { constexpr uint32_t packsize = sizeof(ServerDzSetDuration_Struct); auto pack = std::make_unique(ServerOP_DzDurationUpdate, packsize); auto packbuf = reinterpret_cast(pack->pBuffer); packbuf->dz_id = GetID(); packbuf->seconds = static_cast(m_duration.count()); ZSList::Instance()->SendPacket(pack.get()); } void DynamicZone::SendZonesLeaderChanged() { uint32_t pack_size = sizeof(ServerDzLeaderID_Struct); auto pack = std::make_unique(ServerOP_DzLeaderChanged, pack_size); auto buf = reinterpret_cast(pack->pBuffer); buf->dz_id = GetID(); buf->leader_id = GetLeaderID(); ZSList::Instance()->SendPacket(pack.get()); } void DynamicZone::ProcessMemberAddRemove(const DynamicZoneMember& member, bool removed) { DynamicZoneBase::ProcessMemberAddRemove(member, removed); if (GetType() == DynamicZoneType::Expedition && removed && member.id == GetLeaderID()) { ChooseNewLeader(); } } bool DynamicZone::ProcessMemberStatusChange(uint32_t character_id, DynamicZoneMemberStatus status) { bool changed = DynamicZoneBase::SetInternalMemberStatus(character_id, status); if (changed && GetType() == DynamicZoneType::Expedition) { // any member status update will trigger a leader fix if leader was offline if (GetLeader().status == DynamicZoneMemberStatus::Offline && GetMemberCount() > 1) { ChooseNewLeader(); } } return changed; } void DynamicZone::SendZonesExpireWarning(uint32_t minutes_remaining) { uint32_t pack_size = sizeof(ServerDzExpireWarning_Struct); auto pack = std::make_unique(ServerOP_DzExpireWarning, pack_size); auto buf = reinterpret_cast(pack->pBuffer); buf->dz_id = GetID(); buf->minutes_remaining = minutes_remaining; ZSList::Instance()->SendPacket(pack.get()); } void DynamicZone::SendZoneMemberStatuses(uint16_t zone_id, uint16_t instance_id) { uint32_t members_count = static_cast(m_members.size()); uint32_t entries_size = sizeof(ServerDzMemberStatusEntry_Struct) * members_count; uint32_t pack_size = sizeof(ServerDzMemberStatuses_Struct) + entries_size; auto pack = std::make_unique(ServerOP_DzGetMemberStatuses, pack_size); auto buf = reinterpret_cast(pack->pBuffer); buf->dz_id = GetID(); buf->count = members_count; for (int i = 0; i < m_members.size(); ++i) { buf->entries[i].character_id = m_members[i].id; buf->entries[i].online_status = static_cast(m_members[i].status); } ZSList::Instance()->SendPacket(zone_id, instance_id, pack.get()); } void DynamicZone::CacheMemberStatuses() { if (m_has_member_statuses || m_members.empty()) { return; } // called when a new dz is cached to fill member statuses std::string zone_name; std::vector all_clients; all_clients.reserve(ClientList::Instance()->GetClientCount()); ClientList::Instance()->GetClients(zone_name.c_str(), all_clients); for (const auto& member : m_members) { auto it = std::find_if(all_clients.begin(), all_clients.end(), [&](const ClientListEntry* cle) { return (cle && cle->CharID() == member.id); }); auto status = DynamicZoneMemberStatus::Offline; if (it != all_clients.end()) { status = DynamicZoneMemberStatus::Online; if (IsSameDz((*it)->zone(), (*it)->instance())) { status = DynamicZoneMemberStatus::InDynamicZone; } } SetInternalMemberStatus(member.id, status); } m_has_member_statuses = true; } void DynamicZone::CheckExpireWarning() { if (m_warning_cooldown_timer.Check(false)) { using namespace std::chrono_literals; auto remaining = GetDurationRemaining(); if ((remaining > 14min && remaining < 15min) || (remaining > 4min && remaining < 5min) || (remaining > 0min && remaining < 1min)) { int minutes = std::chrono::duration_cast(remaining).count() + 1; SendZonesExpireWarning(minutes); m_warning_cooldown_timer.Start(120000); // 2 minute cooldown after a warning } } }