diff --git a/common/servertalk.h b/common/servertalk.h index d56491299..d537bb825 100644 --- a/common/servertalk.h +++ b/common/servertalk.h @@ -160,6 +160,7 @@ #define ServerOP_ExpeditionReplayOnJoin 0x0410 #define ServerOP_ExpeditionLockState 0x0411 #define ServerOP_ExpeditionMembersRemoved 0x0412 +#define ServerOP_ExpeditionDzDuration 0x0413 #define ServerOP_DzCharacterChange 0x0450 #define ServerOP_DzRemoveAllCharacters 0x0451 @@ -2073,6 +2074,11 @@ struct ServerExpeditionCharacterID_Struct { uint32_t character_id; }; +struct ServerExpeditionUpdateDuration_Struct { + uint32_t expedition_id; + uint32_t new_duration_seconds; +}; + struct ServerDzCommand_Struct { uint32 expedition_id; uint8 is_char_online; // 0: target name is offline, 1: online diff --git a/world/expedition.cpp b/world/expedition.cpp index a3e6e7207..ff2476e50 100644 --- a/world/expedition.cpp +++ b/world/expedition.cpp @@ -54,6 +54,42 @@ void Expedition::SendZonesExpeditionDeleted() zoneserver_list.SendPacket(pack.get()); } +void Expedition::SendZonesDurationUpdate() +{ + uint32_t packsize = sizeof(ServerExpeditionUpdateDuration_Struct); + auto pack = std::unique_ptr(new ServerPacket(ServerOP_ExpeditionDzDuration, packsize)); + auto packbuf = reinterpret_cast(pack->pBuffer); + packbuf->expedition_id = GetID(); + packbuf->new_duration_seconds = m_duration; + zoneserver_list.SendPacket(pack.get()); +} + +void Expedition::UpdateDzSecondsRemaining(uint32_t seconds_remaining) +{ + auto now = std::chrono::system_clock::now(); + auto update_time = std::chrono::seconds(seconds_remaining); + + auto current_remaining = m_expire_time - now; + if (current_remaining > update_time) // reduce only + { + LogExpeditionsDetail( + "Updating expedition [{}] dz instance [{}] seconds remaining to [{}]s", + GetID(), GetInstanceID(), seconds_remaining + ); + + // preserve original start time and adjust duration instead + auto new_expire_time = now + update_time; + auto new_duration = std::chrono::system_clock::to_time_t(new_expire_time) - m_start_time; + m_duration = static_cast(new_duration); + m_expire_time = std::chrono::system_clock::from_time_t(m_start_time + m_duration); + + ExpeditionDatabase::UpdateDzDuration(GetInstanceID(), m_duration); + + // update zone level caches and update the actual dz instance's timer + SendZonesDurationUpdate(); + } +} + void ExpeditionCache::LoadActiveExpeditions() { BenchTimer benchmark; @@ -151,6 +187,13 @@ void ExpeditionCache::Process() it->SendZonesExpeditionDeleted(); is_deleted = true; } + + if (it->IsEmpty() && !it->IsPendingDelete() && RuleB(Expedition, EmptyDzShutdownEnabled)) + { + it->UpdateDzSecondsRemaining(RuleI(Expedition, EmptyDzShutdownDelaySeconds)); + } + + it->SetPendingDelete(true); } it = is_deleted ? m_expeditions.erase(it) : it + 1; @@ -334,6 +377,16 @@ void ExpeditionDatabase::DeleteExpeditions(const std::vector& expediti } } +void ExpeditionDatabase::UpdateDzDuration(uint16_t instance_id, uint32_t new_duration) +{ + std::string query = fmt::format( + "UPDATE instance_list SET duration = {} WHERE id = {};", + new_duration, instance_id + ); + + database.QueryDatabase(query); +} + void ExpeditionMessage::HandleZoneMessage(ServerPacket* pack) { switch (pack->opcode) diff --git a/world/expedition.h b/world/expedition.h index 6c6c78a3a..a933e5d9d 100644 --- a/world/expedition.h +++ b/world/expedition.h @@ -39,6 +39,7 @@ namespace ExpeditionDatabase std::vector LoadExpeditions(); Expedition LoadExpedition(uint32_t expedition_id); void DeleteExpeditions(const std::vector& expedition_ids); + void UpdateDzDuration(uint16_t instance_id, uint32_t new_duration); }; namespace ExpeditionMessage @@ -82,7 +83,11 @@ public: uint16_t GetZoneID() const { return static_cast(m_dz_zone_id); } bool IsEmpty() const { return m_member_ids.empty(); } bool IsExpired() const { return m_expire_time < std::chrono::system_clock::now(); } + bool IsPendingDelete() const { return m_pending_delete; } + void SendZonesDurationUpdate(); void SendZonesExpeditionDeleted(); + void SetPendingDelete(bool pending) { m_pending_delete = pending; } + void UpdateDzSecondsRemaining(uint32_t seconds_remaining); private: uint32_t m_expedition_id = 0; @@ -90,6 +95,7 @@ private: uint32_t m_dz_zone_id = 0; uint32_t m_start_time = 0; uint32_t m_duration = 0; + bool m_pending_delete = false; std::unordered_set m_member_ids; std::chrono::time_point m_expire_time; }; diff --git a/zone/dynamiczone.cpp b/zone/dynamiczone.cpp index 38377fefe..157b487e3 100644 --- a/zone/dynamiczone.cpp +++ b/zone/dynamiczone.cpp @@ -437,32 +437,6 @@ void DynamicZone::SendInstanceCharacterChange(uint32_t character_id, bool remove } } -void DynamicZone::UpdateExpireTime(uint32_t seconds, bool reduce_only) -{ - if (GetInstanceID() == 0 || (reduce_only && GetSecondsRemaining() < seconds)) - { - return; - } - - m_expire_time = std::chrono::system_clock::now() + std::chrono::seconds(seconds); - m_duration = std::chrono::system_clock::to_time_t(m_expire_time) - m_start_time; - - std::string query = fmt::format(SQL( - UPDATE instance_list SET duration = {} WHERE id = {}; - ), m_duration, GetInstanceID()); - - auto results = database.QueryDatabase(query); - if (results.Success()) - { - uint32_t packsize = sizeof(ServerInstanceUpdateTime_Struct); - auto pack = std::unique_ptr(new ServerPacket(ServerOP_InstanceUpdateTime, packsize)); - auto packbuf = reinterpret_cast(pack->pBuffer); - packbuf->instance_id = GetInstanceID(); - packbuf->new_duration = seconds; - worldserver.SendPacket(pack.get()); - } -} - void DynamicZone::SetCompass(const DynamicZoneLocation& location, bool update_db) { m_compass = location; @@ -515,6 +489,18 @@ uint32_t DynamicZone::GetSecondsRemaining() const return 0; } +void DynamicZone::SetUpdatedDuration(uint32_t new_duration) +{ + // preserves original start time, just modifies duration and expire time + m_duration = new_duration; + m_expire_time = std::chrono::system_clock::from_time_t(m_start_time + m_duration); + + if (zone && IsCurrentZoneDzInstance()) + { + zone->SetInstanceTimer(GetSecondsRemaining()); + } +} + void DynamicZone::HandleWorldMessage(ServerPacket* pack) { switch (pack->opcode) diff --git a/zone/dynamiczone.h b/zone/dynamiczone.h index bb2bee16b..f9a64276b 100644 --- a/zone/dynamiczone.h +++ b/zone/dynamiczone.h @@ -89,7 +89,7 @@ public: void SetCompass(const DynamicZoneLocation& location, bool update_db = false); void SetSafeReturn(const DynamicZoneLocation& location, bool update_db = false); void SetZoneInLocation(const DynamicZoneLocation& location, bool update_db = false); - void UpdateExpireTime(uint32_t seconds, bool reduce_only = true); + void SetUpdatedDuration(uint32_t seconds); void LoadFromDatabase(uint32_t instance_id); uint32_t SaveToDatabase(); diff --git a/zone/expedition.cpp b/zone/expedition.cpp index 6ecd960f9..6c388fe7c 100644 --- a/zone/expedition.cpp +++ b/zone/expedition.cpp @@ -477,15 +477,10 @@ bool Expedition::AddMember(const std::string& add_char_name, uint32_t add_char_i return true; } -void Expedition::RemoveAllMembers(bool enable_removal_timers, bool update_dz_expire_time) +void Expedition::RemoveAllMembers(bool enable_removal_timers) { m_dynamiczone.RemoveAllCharacters(enable_removal_timers); - if (update_dz_expire_time && RuleB(Expedition, EmptyDzShutdownEnabled)) - { - m_dynamiczone.UpdateExpireTime(RuleI(Expedition, EmptyDzShutdownDelaySeconds)); - } - ExpeditionDatabase::UpdateAllMembersRemoved(m_id); SendUpdatesToZoneMembers(true); @@ -512,18 +507,6 @@ bool Expedition::RemoveMember(const std::string& remove_char_name) ChooseNewLeader(); } - // we can't check for empty member count via cache because if other zones - // remove members at the same time then we race. cache member count won't - // be accurate until the world messages from other zones are processed - uint32_t member_count = ExpeditionDatabase::GetExpeditionMemberCount(m_id); - if (member_count == 0) - { - if (RuleB(Expedition, EmptyDzShutdownEnabled)) - { - m_dynamiczone.UpdateExpireTime(RuleI(Expedition, EmptyDzShutdownDelaySeconds)); - } - } - return true; } @@ -1813,6 +1796,16 @@ void Expedition::HandleWorldMessage(ServerPacket* pack) } break; } + case ServerOP_ExpeditionDzDuration: + { + auto buf = reinterpret_cast(pack->pBuffer); + auto expedition = Expedition::FindCachedExpeditionByID(buf->expedition_id); + if (expedition) + { + expedition->SetDzDuration(buf->new_duration_seconds); + } + break; + } } } diff --git a/zone/expedition.h b/zone/expedition.h index dfe337907..4ad4460f5 100644 --- a/zone/expedition.h +++ b/zone/expedition.h @@ -102,7 +102,7 @@ public: bool AddMember(const std::string& add_char_name, uint32_t add_char_id); bool HasMember(const std::string& character_name); bool HasMember(uint32_t character_id); - void RemoveAllMembers(bool enable_removal_timers = true, bool update_dz_expire_time = true); + void RemoveAllMembers(bool enable_removal_timers = true); bool RemoveMember(const std::string& remove_char_name); void SetMemberStatus(Client* client, ExpeditionMemberStatus status); void SetNewLeader(uint32_t new_leader_id, const std::string& new_leader_name); @@ -134,6 +134,7 @@ public: void SetDzSafeReturn(uint32_t zone_id, float x, float y, float z, float heading, bool update_db = false); void SetDzSafeReturn(const std::string& zone_name, float x, float y, float z, float heading, bool update_db = false); void SetDzZoneInLocation(float x, float y, float z, float heading, bool update_db = false); + void SetDzDuration(uint32_t new_duration) { m_dynamiczone.SetUpdatedDuration(new_duration); } static const uint32_t REPLAY_TIMER_ID; static const uint32_t EVENT_TIMER_ID; diff --git a/zone/expedition_database.cpp b/zone/expedition_database.cpp index 310613878..fb913a9b2 100644 --- a/zone/expedition_database.cpp +++ b/zone/expedition_database.cpp @@ -395,25 +395,6 @@ ExpeditionMember ExpeditionDatabase::GetExpeditionLeader(uint32_t expedition_id) return leader; } -uint32_t ExpeditionDatabase::GetExpeditionMemberCount(uint32_t expedition_id) -{ - auto query = fmt::format(SQL( - SELECT COUNT(IF(is_current_member = TRUE, 1, NULL)) member_count - FROM expedition_members - WHERE expedition_id = {}; - ), expedition_id); - - auto results = database.QueryDatabase(query); - - uint32_t member_count = 0; - if (results.Success() && results.RowCount() > 0) - { - auto row = results.begin(); - member_count = static_cast(strtoul(row[0], nullptr, 10)); - } - return member_count; -} - void ExpeditionDatabase::InsertCharacterLockouts( uint32_t character_id, const std::vector& lockouts, bool update_expire_times, bool is_pending) diff --git a/zone/expedition_database.h b/zone/expedition_database.h index 7c5692794..1773d2550 100644 --- a/zone/expedition_database.h +++ b/zone/expedition_database.h @@ -55,7 +55,6 @@ namespace ExpeditionDatabase uint32_t GetExpeditionIDFromCharacterID(uint32_t character_id); uint32_t GetExpeditionIDFromInstanceID(uint32_t instance_id); ExpeditionMember GetExpeditionLeader(uint32_t expedition_id); - uint32_t GetExpeditionMemberCount(uint32_t expedition_id); void InsertCharacterLockouts( uint32_t character_id, const std::vector& lockouts, bool update_expire_times, bool is_pending = false); diff --git a/zone/worldserver.cpp b/zone/worldserver.cpp index ee1e0589a..6fb577515 100644 --- a/zone/worldserver.cpp +++ b/zone/worldserver.cpp @@ -2915,6 +2915,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) case ServerOP_ExpeditionDzCompass: case ServerOP_ExpeditionDzSafeReturn: case ServerOP_ExpeditionDzZoneIn: + case ServerOP_ExpeditionDzDuration: case ServerOP_ExpeditionRemoveCharLockouts: { Expedition::HandleWorldMessage(pack); diff --git a/zone/zone.cpp b/zone/zone.cpp index a415e45f0..da2555785 100755 --- a/zone/zone.cpp +++ b/zone/zone.cpp @@ -1495,7 +1495,7 @@ bool Zone::Process() { Expedition* expedition = Expedition::FindExpeditionByInstanceID(GetInstanceID()); if (expedition) { - expedition->RemoveAllMembers(false, false); // entity list will teleport clients out immediately + expedition->RemoveAllMembers(false); // entity list will teleport clients out immediately } // todo: move corpses to non-instanced version of dz at same coords (if no graveyard) entity_list.GateAllClientsToSafeReturn();