eqemu-server/world/dynamic_zone.cpp
2023-12-16 18:59:11 -06:00

397 lines
11 KiB
C++

#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"
#include <fmt/format.h>
extern ClientList client_list;
extern ZSList zoneserver_list;
Database& DynamicZone::GetDatabase()
{
return database;
}
bool DynamicZone::SendServerPacket(ServerPacket* packet)
{
return zoneserver_list.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 = client_list.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 = zoneserver_list.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<ServerPacket>(ServerOP_DzDeleted, pack_size);
auto buf = reinterpret_cast<ServerDzID_Struct*>(pack->pBuffer);
buf->dz_id = GetID();
zoneserver_list.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<std::chrono::seconds>(m_expire_time - m_start_time);
InstanceListRepository::UpdateDuration(database,
GetInstanceID(), static_cast<uint32_t>(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<ServerPacket>(ServerOP_DzDurationUpdate, packsize);
auto packbuf = reinterpret_cast<ServerDzSetDuration_Struct*>(pack->pBuffer);
packbuf->dz_id = GetID();
packbuf->seconds = static_cast<uint32_t>(m_duration.count());
zoneserver_list.SendPacket(pack.get());
}
void DynamicZone::SendZonesLeaderChanged()
{
uint32_t pack_size = sizeof(ServerDzLeaderID_Struct);
auto pack = std::make_unique<ServerPacket>(ServerOP_DzLeaderChanged, pack_size);
auto buf = reinterpret_cast<ServerDzLeaderID_Struct*>(pack->pBuffer);
buf->dz_id = GetID();
buf->leader_id = GetLeaderID();
zoneserver_list.SendPacket(pack.get());
}
void DynamicZone::HandleZoneMessage(ServerPacket* pack)
{
switch (pack->opcode)
{
case ServerOP_DzCreated:
{
dynamic_zone_manager.CacheNewDynamicZone(pack);
break;
}
case ServerOP_DzSetCompass:
case ServerOP_DzSetSafeReturn:
case ServerOP_DzSetZoneIn:
{
auto buf = reinterpret_cast<ServerDzLocation_Struct*>(pack->pBuffer);
auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id);
if (dz)
{
if (pack->opcode == ServerOP_DzSetCompass)
{
dz->SetCompass(buf->zone_id, buf->x, buf->y, buf->z, false);
}
else if (pack->opcode == ServerOP_DzSetSafeReturn)
{
dz->SetSafeReturn(buf->zone_id, buf->x, buf->y, buf->z, buf->heading, false);
}
else if (pack->opcode == ServerOP_DzSetZoneIn)
{
dz->SetZoneInLocation(buf->x, buf->y, buf->z, buf->heading, false);
}
}
zoneserver_list.SendPacket(pack);
break;
}
case ServerOP_DzSetSwitchID:
{
auto buf = reinterpret_cast<ServerDzSwitchID_Struct*>(pack->pBuffer);
auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id);
if (dz)
{
dz->ProcessSetSwitchID(buf->dz_switch_id);
}
zoneserver_list.SendPacket(pack);
break;
}
case ServerOP_DzAddRemoveMember:
{
auto buf = reinterpret_cast<ServerDzMember_Struct*>(pack->pBuffer);
auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id);
if (dz)
{
auto status = static_cast<DynamicZoneMemberStatus>(buf->character_status);
dz->ProcessMemberAddRemove({ buf->character_id, buf->character_name, status }, buf->removed);
}
zoneserver_list.SendPacket(pack);
break;
}
case ServerOP_DzSwapMembers:
{
auto buf = reinterpret_cast<ServerDzMemberSwap_Struct*>(pack->pBuffer);
auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id);
if (dz)
{
// we add first in world so new member can be chosen if leader is removed
auto status = static_cast<DynamicZoneMemberStatus>(buf->add_character_status);
dz->ProcessMemberAddRemove({ buf->add_character_id, buf->add_character_name, status }, false);
dz->ProcessMemberAddRemove({ buf->remove_character_id, buf->remove_character_name }, true);
}
zoneserver_list.SendPacket(pack);
break;
}
case ServerOP_DzRemoveAllMembers:
{
auto buf = reinterpret_cast<ServerDzID_Struct*>(pack->pBuffer);
auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id);
if (dz)
{
dz->ProcessRemoveAllMembers();
}
zoneserver_list.SendPacket(pack);
break;
}
case ServerOP_DzSetSecondsRemaining:
{
auto buf = reinterpret_cast<ServerDzSetDuration_Struct*>(pack->pBuffer);
auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id);
if (dz)
{
dz->SetSecondsRemaining(buf->seconds);
}
break;
}
case ServerOP_DzGetMemberStatuses:
{
auto buf = reinterpret_cast<ServerDzID_Struct*>(pack->pBuffer);
auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id);
if (dz)
{
dz->SendZoneMemberStatuses(buf->sender_zone_id, buf->sender_instance_id);
}
break;
}
case ServerOP_DzUpdateMemberStatus:
{
auto buf = reinterpret_cast<ServerDzMemberStatus_Struct*>(pack->pBuffer);
auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id);
if (dz)
{
auto status = static_cast<DynamicZoneMemberStatus>(buf->status);
dz->ProcessMemberStatusChange(buf->character_id, status);
}
zoneserver_list.SendPacket(pack);
break;
}
case ServerOP_DzMovePC:
{
auto buf = reinterpret_cast<ServerDzMovePC_Struct*>(pack->pBuffer);
auto dz = DynamicZone::FindDynamicZoneByID(buf->dz_id);
if (dz && dz->HasMember(buf->character_id))
{
zoneserver_list.SendPacket(buf->sender_zone_id, buf->sender_instance_id, pack);
}
break;
}
};
}
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<ServerPacket>(ServerOP_DzExpireWarning, pack_size);
auto buf = reinterpret_cast<ServerDzExpireWarning_Struct*>(pack->pBuffer);
buf->dz_id = GetID();
buf->minutes_remaining = minutes_remaining;
zoneserver_list.SendPacket(pack.get());
}
void DynamicZone::SendZoneMemberStatuses(uint16_t zone_id, uint16_t instance_id)
{
uint32_t members_count = static_cast<uint32_t>(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<ServerPacket>(ServerOP_DzGetMemberStatuses, pack_size);
auto buf = reinterpret_cast<ServerDzMemberStatuses_Struct*>(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<uint8_t>(m_members[i].status);
}
zoneserver_list.SendPacket(zone_id, instance_id, pack.get());
}
void DynamicZone::CacheMemberStatuses()
{
if (m_has_member_statuses)
{
return;
}
// called when a new dz is cached to fill member statuses
std::string zone_name{};
std::vector<ClientListEntry*> all_clients;
all_clients.reserve(client_list.GetClientCount());
client_list.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<std::chrono::minutes>(remaining).count() + 1;
SendZonesExpireWarning(minutes);
m_warning_cooldown_timer.Start(120000); // 2 minute cooldown after a warning
}
}
}