Skip to content

Commit

Permalink
[Expeditions] Let dz process its expired state (#1310)
Browse files Browse the repository at this point in the history
Move early empty shutdown and process rate rules to DynamicZone scope

This decouples the expired status check from expeditions into an
internal dz method that can be called by its owning system
  • Loading branch information
hgtw committed Mar 29, 2021
1 parent 97c11a1 commit f5cf566
Show file tree
Hide file tree
Showing 7 changed files with 60 additions and 39 deletions.
6 changes: 3 additions & 3 deletions common/ruletypes.h
Expand Up @@ -736,9 +736,6 @@ RULE_CATEGORY_END()

RULE_CATEGORY(Expedition)
RULE_INT(Expedition, MinStatusToBypassPlayerCountRequirements, 80, "Minimum GM status to bypass minimum player requirements for Expedition creation")
RULE_BOOL(Expedition, EmptyDzShutdownEnabled, true, "Enable early instance shutdown after last member of expedition removed")
RULE_INT(Expedition, EmptyDzShutdownDelaySeconds, 1500, "Seconds to set dynamic zone instance expiration if early shutdown enabled")
RULE_INT(Expedition, WorldExpeditionProcessRateMS, 6000, "Timer interval (milliseconds) that world checks expedition states")
RULE_BOOL(Expedition, AlwaysNotifyNewLeaderOnChange, false, "Always notify clients when made expedition leader. If false (live-like) new leaders are only notified when made leader via /dzmakeleader")
RULE_REAL(Expedition, LockoutDurationMultiplier, 1.0, "Multiplies lockout duration by this value when new lockouts are added")
RULE_BOOL(Expedition, EnableInDynamicZoneStatus, false, "Enables the 'In Dynamic Zone' member status in expedition window. If false (live-like) players inside the dynamic zone will show as 'Online'")
Expand All @@ -747,6 +744,9 @@ RULE_CATEGORY_END()

RULE_CATEGORY(DynamicZone)
RULE_INT(DynamicZone, ClientRemovalDelayMS, 60000, "Delay (milliseconds) until a client is teleported out of dynamic zone after being removed as member")
RULE_BOOL(DynamicZone, EmptyShutdownEnabled, true, "Enable early instance shutdown for dynamic zones that have no members")
RULE_INT(DynamicZone, EmptyShutdownDelaySeconds, 1500, "Seconds to set dynamic zone instance expiration if early shutdown enabled")
RULE_INT(DynamicZone, WorldProcessRate, 6000, "Timer interval (milliseconds) that systems check their dynamic zone states")
RULE_CATEGORY_END()

#undef RULE_CATEGORY
Expand Down
24 changes: 24 additions & 0 deletions world/dynamic_zone.cpp
Expand Up @@ -35,6 +35,30 @@ DynamicZone* DynamicZone::FindDynamicZoneByID(uint32_t dz_id)
return nullptr;
}

DynamicZoneStatus DynamicZone::Process(bool force_expire)
{
DynamicZoneStatus status = DynamicZoneStatus::Normal;

if (force_expire || 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 (force_expire && !m_is_pending_early_shutdown && RuleB(DynamicZone, EmptyShutdownEnabled))
{
SetSecondsRemaining(RuleI(DynamicZone, EmptyShutdownDelaySeconds));
m_is_pending_early_shutdown = true;
}
}
}

return status;
}

void DynamicZone::SetSecondsRemaining(uint32_t seconds_remaining)
{
auto now = std::chrono::system_clock::now();
Expand Down
10 changes: 10 additions & 0 deletions world/dynamic_zone.h
Expand Up @@ -6,6 +6,14 @@

class ServerPacket;

enum class DynamicZoneStatus
{
Unknown = 0,
Normal,
Expired,
ExpiredEmpty,
};

class DynamicZone
{
public:
Expand All @@ -23,6 +31,7 @@ class DynamicZone
std::chrono::system_clock::duration GetRemainingDuration() const {
return m_expire_time - std::chrono::system_clock::now(); }

DynamicZoneStatus Process(bool force_expire);
bool IsExpired() const { return m_expire_time < std::chrono::system_clock::now(); }
void SetSecondsRemaining(uint32_t seconds_remaining);

Expand All @@ -33,6 +42,7 @@ class DynamicZone
uint32_t m_instance_id = 0;
uint32_t m_zone_id = 0;
uint32_t m_zone_version = 0;
bool m_is_pending_early_shutdown = false;
DynamicZoneType m_type{ DynamicZoneType::None };
std::chrono::seconds m_duration;
std::chrono::time_point<std::chrono::system_clock> m_start_time;
Expand Down
18 changes: 18 additions & 0 deletions world/expedition.cpp
Expand Up @@ -161,3 +161,21 @@ void Expedition::CheckLeader()
ChooseNewLeader();
}
}

bool Expedition::Process()
{
// returns true if expedition needs to be deleted from world cache and db
// expedition is not deleted until its dz has no clients to prevent exploits
auto status = m_dynamic_zone.Process(IsEmpty()); // force expire if no members
if (status == DynamicZoneStatus::ExpiredEmpty)
{
LogExpeditions("Expedition [{}] expired or empty, notifying zones and deleting", GetID());
SendZonesExpeditionDeleted();
return true;
}

CheckExpireWarning();
CheckLeader();

return false;
}
4 changes: 1 addition & 3 deletions world/expedition.h
Expand Up @@ -43,19 +43,17 @@ class Expedition
uint32_t GetID() const { return m_expedition_id; }
bool HasMember(uint32_t character_id);
bool IsEmpty() const { return m_member_ids.empty(); }
bool IsPendingDelete() const { return m_pending_delete; }
bool IsValid() const { return m_expedition_id != 0; }
bool Process();
void SendZonesExpeditionDeleted();
void SendZonesExpireWarning(uint32_t minutes_remaining);
bool SetNewLeader(uint32_t new_leader_id);
void SetPendingDelete(bool pending) { m_pending_delete = pending; }

private:
void SendZonesLeaderChanged();

uint32_t m_expedition_id = 0;
uint32_t m_leader_id = 0;
bool m_pending_delete = false;
bool m_choose_leader_needed = false;
Timer m_choose_leader_cooldown_timer;
Timer m_warning_cooldown_timer;
Expand Down
35 changes: 3 additions & 32 deletions world/expedition_state.cpp
Expand Up @@ -21,13 +21,9 @@
#include "expedition_state.h"
#include "expedition.h"
#include "expedition_database.h"
#include "zonelist.h"
#include "zoneserver.h"
#include "../common/eqemu_logsys.h"
#include <algorithm>

extern ZSList zoneserver_list;

ExpeditionState expedition_state;

Expedition* ExpeditionState::GetExpedition(uint32_t expedition_id)
Expand Down Expand Up @@ -117,36 +113,11 @@ void ExpeditionState::Process()

for (auto it = m_expeditions.begin(); it != m_expeditions.end();)
{
bool is_deleted = false;

if (it->IsEmpty() || it->GetDynamicZone().IsExpired())
bool is_deleted = it->Process();
if (is_deleted)
{
// 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->GetDynamicZone().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->GetDynamicZone().SetSecondsRemaining(RuleI(Expedition, EmptyDzShutdownDelaySeconds));
}

it->SetPendingDelete(true);
expedition_ids.emplace_back(it->GetID());
}
else
{
it->CheckExpireWarning();
it->CheckLeader();
}

it = is_deleted ? m_expeditions.erase(it) : it + 1;
}

Expand Down
2 changes: 1 addition & 1 deletion world/expedition_state.h
Expand Up @@ -44,7 +44,7 @@ class ExpeditionState

private:
std::vector<Expedition> m_expeditions;
Timer m_process_throttle_timer{static_cast<uint32_t>(RuleI(Expedition, WorldExpeditionProcessRateMS))};
Timer m_process_throttle_timer{static_cast<uint32_t>(RuleI(DynamicZone, WorldProcessRate))};
};

#endif

0 comments on commit f5cf566

Please sign in to comment.