From f5cf566fcae1163ac1c85f6a7ad0bd484ec4448c Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sun, 28 Mar 2021 21:43:09 -0400 Subject: [PATCH] [Expeditions] Let dz process its expired state (#1310) 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 --- common/ruletypes.h | 6 +++--- world/dynamic_zone.cpp | 24 ++++++++++++++++++++++++ world/dynamic_zone.h | 10 ++++++++++ world/expedition.cpp | 18 ++++++++++++++++++ world/expedition.h | 4 +--- world/expedition_state.cpp | 35 +++-------------------------------- world/expedition_state.h | 2 +- 7 files changed, 60 insertions(+), 39 deletions(-) diff --git a/common/ruletypes.h b/common/ruletypes.h index 401e55099..4edda9277 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -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'") @@ -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 diff --git a/world/dynamic_zone.cpp b/world/dynamic_zone.cpp index 47e70d00a..252559c29 100644 --- a/world/dynamic_zone.cpp +++ b/world/dynamic_zone.cpp @@ -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(); diff --git a/world/dynamic_zone.h b/world/dynamic_zone.h index c95512260..84455d7eb 100644 --- a/world/dynamic_zone.h +++ b/world/dynamic_zone.h @@ -6,6 +6,14 @@ class ServerPacket; +enum class DynamicZoneStatus +{ + Unknown = 0, + Normal, + Expired, + ExpiredEmpty, +}; + class DynamicZone { public: @@ -23,6 +31,7 @@ public: 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); @@ -33,6 +42,7 @@ private: 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 m_start_time; diff --git a/world/expedition.cpp b/world/expedition.cpp index d983a82d0..2937a20bc 100644 --- a/world/expedition.cpp +++ b/world/expedition.cpp @@ -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; +} diff --git a/world/expedition.h b/world/expedition.h index 952abc1a0..2f48739df 100644 --- a/world/expedition.h +++ b/world/expedition.h @@ -43,19 +43,17 @@ public: 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; diff --git a/world/expedition_state.cpp b/world/expedition_state.cpp index c1cb0fc8c..815ef6c58 100644 --- a/world/expedition_state.cpp +++ b/world/expedition_state.cpp @@ -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 -extern ZSList zoneserver_list; - ExpeditionState expedition_state; Expedition* ExpeditionState::GetExpedition(uint32_t expedition_id) @@ -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; } diff --git a/world/expedition_state.h b/world/expedition_state.h index 58b733989..d7fb27d09 100644 --- a/world/expedition_state.h +++ b/world/expedition_state.h @@ -44,7 +44,7 @@ public: private: std::vector m_expeditions; - Timer m_process_throttle_timer{static_cast(RuleI(Expedition, WorldExpeditionProcessRateMS))}; + Timer m_process_throttle_timer{static_cast(RuleI(DynamicZone, WorldProcessRate))}; }; #endif