#include "dynamic_zone_base.h" #include "database.h" #include "eqemu_logsys.h" #include "rulesys.h" #include "servertalk.h" #include "util/uuid.h" #include "repositories/character_expedition_lockouts_repository.h" #include "repositories/dynamic_zone_lockouts_repository.h" #include "repositories/instance_list_repository.h" #include "repositories/instance_list_player_repository.h" DynamicZoneBase::DynamicZoneBase(DynamicZonesRepository::DynamicZoneInstance&& entry) { LoadRepositoryResult(std::move(entry)); } uint32_t DynamicZoneBase::Create() { if (GetInstanceID() == 0) { CreateInstance(); } m_uuid = EQ::Util::UUID::Generate().ToString(); m_id = SaveToDatabase(); return m_id; } uint32_t DynamicZoneBase::CreateInstance() { if (m_instance_id) { LogDynamicZones("CreateInstance failed, instance id [{}] already created", m_instance_id); return 0; } if (!m_zone_id) { LogDynamicZones("CreateInstance failed, invalid zone id [{}]", m_zone_id); return 0; } uint16_t unused_instance_id = 0; if (!GetDatabase().GetUnusedInstanceID(unused_instance_id)) // todo: doesn't this race with insert? { LogDynamicZones("Failed to find unused instance id"); return 0; } m_start_time = std::chrono::system_clock::now(); m_expire_time = m_start_time + m_duration; auto insert_instance = InstanceListRepository::NewEntity(); insert_instance.id = unused_instance_id; insert_instance.zone = m_zone_id; insert_instance.version = m_zone_version; insert_instance.start_time = static_cast(std::chrono::system_clock::to_time_t(m_start_time)); insert_instance.duration = static_cast(m_duration.count()); insert_instance.never_expires = m_never_expires; insert_instance.expire_at = insert_instance.start_time + insert_instance.duration; auto instance = InstanceListRepository::ReplaceOne(GetDatabase(), insert_instance); if (!instance) { LogDynamicZones("Failed to create instance [{}] for zone [{}]", unused_instance_id, m_zone_id); return 0; } m_instance_id = unused_instance_id; return m_instance_id; } void DynamicZoneBase::LoadRepositoryResult(DynamicZonesRepository::DynamicZoneInstance&& dz_entry) { m_id = dz_entry.id; m_uuid = std::move(dz_entry.uuid); m_name = std::move(dz_entry.name); m_leader.id = dz_entry.leader_id; m_min_players = dz_entry.min_players; m_max_players = dz_entry.max_players; m_instance_id = dz_entry.instance_id; m_type = static_cast(dz_entry.type); m_dz_switch_id = dz_entry.dz_switch_id; m_compass.zone_id = dz_entry.compass_zone_id; m_compass.x = dz_entry.compass_x; m_compass.y = dz_entry.compass_y; m_compass.z = dz_entry.compass_z; m_safereturn.zone_id = dz_entry.safe_return_zone_id; m_safereturn.x = dz_entry.safe_return_x; m_safereturn.y = dz_entry.safe_return_y; m_safereturn.z = dz_entry.safe_return_z; m_safereturn.heading = dz_entry.safe_return_heading; m_zonein.x = dz_entry.zone_in_x; m_zonein.y = dz_entry.zone_in_y; m_zonein.z = dz_entry.zone_in_z; m_zonein.heading = dz_entry.zone_in_heading; m_has_zonein = dz_entry.has_zone_in != 0; m_is_locked = dz_entry.is_locked; m_add_replay = dz_entry.add_replay; // instance_list portion m_zone_id = dz_entry.zone; m_zone_version = dz_entry.version; m_start_time = std::chrono::system_clock::from_time_t(dz_entry.start_time); m_duration = std::chrono::seconds(dz_entry.duration); m_never_expires = dz_entry.never_expires != 0; m_expire_time = m_start_time + m_duration; } void DynamicZoneBase::AddMemberFromRepositoryResult( DynamicZoneMembersRepository::MemberWithName&& entry) { auto status = DynamicZoneMemberStatus::Unknown; if (m_leader.id == entry.character_id) { m_leader.name = entry.character_name; } AddInternalMember({ entry.character_id, std::move(entry.character_name), status }); } uint32_t DynamicZoneBase::SaveToDatabase() { LogDynamicZonesDetail("Saving dz instance [{}] to database", m_instance_id); if (m_instance_id == 0) { return 0; } auto dz = DynamicZonesRepository::NewEntity(); dz.uuid = m_uuid; dz.name = m_name; dz.leader_id = m_leader.id; dz.min_players = m_min_players; dz.max_players = m_max_players; dz.instance_id = static_cast(m_instance_id), dz.type = static_cast(m_type); dz.dz_switch_id = m_dz_switch_id; dz.compass_zone_id = m_compass.zone_id; dz.compass_x = m_compass.x; dz.compass_y = m_compass.y; dz.compass_z = m_compass.z; dz.safe_return_zone_id = m_safereturn.zone_id; dz.safe_return_x = m_safereturn.x; dz.safe_return_y = m_safereturn.y; dz.safe_return_z = m_safereturn.z; dz.safe_return_heading = m_safereturn.heading; dz.zone_in_x = m_zonein.x; dz.zone_in_y = m_zonein.y; dz.zone_in_z = m_zonein.z; dz.zone_in_heading = m_zonein.heading; dz.has_zone_in = static_cast(m_has_zonein); dz.is_locked = static_cast(m_is_locked); dz.add_replay = static_cast(m_add_replay); dz = DynamicZonesRepository::InsertOne(GetDatabase(), std::move(dz)); return dz.id; } bool DynamicZoneBase::AddMember(const DynamicZoneMember& add_member) { if (HasMember(add_member.id)) { return false; } DynamicZoneMembersRepository::AddMember(GetDatabase(), m_id, add_member.id); GetDatabase().AddClientToInstance(m_instance_id, add_member.id); ProcessMemberAddRemove(add_member, false); SendServerPacket(CreateServerMemberAddRemovePacket(add_member, false).get()); return true; } bool DynamicZoneBase::RemoveMember(uint32_t character_id) { auto remove_member = GetMemberData(character_id); return RemoveMember(remove_member); } bool DynamicZoneBase::RemoveMember(const std::string& character_name) { auto remove_member = GetMemberData(character_name); return RemoveMember(remove_member); } bool DynamicZoneBase::RemoveMember(const DynamicZoneMember& remove_member) { if (remove_member.id == 0) { return false; } DynamicZoneMembersRepository::RemoveMember(GetDatabase(), m_id, remove_member.id); GetDatabase().RemoveClientFromInstance(m_instance_id, remove_member.id); ProcessMemberAddRemove(remove_member, true); SendServerPacket(CreateServerMemberAddRemovePacket(remove_member, true).get()); return true; } bool DynamicZoneBase::SwapMember(const DynamicZoneMember& add_member, const std::string& remove_name) { auto remove_member = GetMemberData(remove_name); if (!add_member.IsValid() || !remove_member.IsValid()) { return false; } // make remove and add atomic to avoid racing with separate world messages DynamicZoneMembersRepository::RemoveMember(GetDatabase(), m_id, remove_member.id); GetDatabase().RemoveClientFromInstance(m_instance_id, remove_member.id); DynamicZoneMembersRepository::AddMember(GetDatabase(), m_id, add_member.id); GetDatabase().AddClientToInstance(m_instance_id, add_member.id); ProcessMemberAddRemove(remove_member, true); ProcessMemberAddRemove(add_member, false); SendServerPacket(CreateServerMemberSwapPacket(remove_member, add_member).get()); return true; } void DynamicZoneBase::RemoveAllMembers() { DynamicZoneMembersRepository::RemoveAllMembers(GetDatabase(), m_id); GetDatabase().RemoveClientsFromInstance(GetInstanceID()); ProcessRemoveAllMembers(); SendServerPacket(CreateServerRemoveAllMembersPacket().get()); } void DynamicZoneBase::SaveMembers(const std::vector& members) { if (members.empty()) { return; } LogDynamicZonesDetail("Saving [{}] member(s) for dz [{}]", members.size(), m_id); m_members = members; if (m_members.size() > m_max_players) { m_members.resize(m_max_players); } // the lower level instance_list_players needs to be kept updated as well std::vector insert_members; std::vector insert_players; for (const auto& member : m_members) { DynamicZoneMembersRepository::DynamicZoneMembers member_entry{}; member_entry.dynamic_zone_id = m_id; member_entry.character_id = member.id; insert_members.push_back(member_entry); InstanceListPlayerRepository::InstanceListPlayer player_entry{}; player_entry.id = m_instance_id; player_entry.charid = member.id; insert_players.push_back(player_entry); } DynamicZoneMembersRepository::InsertOrUpdateMany(GetDatabase(), insert_members); InstanceListPlayerRepository::InsertOrUpdateMany(GetDatabase(), insert_players); } void DynamicZoneBase::SetCompass(const DynamicZoneLocation& location, bool update_db) { ProcessCompassChange(location); if (update_db) { LogDynamicZonesDetail("Saving [{}] compass zone: [{}] xyz: ([{}], [{}], [{}])", m_id, m_compass.zone_id, m_compass.x, m_compass.y, m_compass.z); DynamicZonesRepository::UpdateCompass(GetDatabase(), m_id, m_compass.zone_id, m_compass.x, m_compass.y, m_compass.z); SendServerPacket(CreateServerDzLocationPacket(ServerOP_DzSetCompass, location).get()); } } void DynamicZoneBase::SetCompass(uint32_t zone_id, float x, float y, float z, bool update_db) { SetCompass({ zone_id, x, y, z, 0.0f }, update_db); } void DynamicZoneBase::SetSafeReturn(const DynamicZoneLocation& location, bool update_db) { m_safereturn = location; if (update_db) { LogDynamicZonesDetail("Saving [{}] safereturn zone: [{}] xyzh: ([{}], [{}], [{}], [{}])", m_id, m_safereturn.zone_id, m_safereturn.x, m_safereturn.y, m_safereturn.z, m_safereturn.heading); DynamicZonesRepository::UpdateSafeReturn(GetDatabase(), m_id, m_safereturn.zone_id, m_safereturn.x, m_safereturn.y, m_safereturn.z, m_safereturn.heading); SendServerPacket(CreateServerDzLocationPacket(ServerOP_DzSetSafeReturn, location).get()); } } void DynamicZoneBase::SetSafeReturn(uint32_t zone_id, float x, float y, float z, float heading, bool update_db) { SetSafeReturn({ zone_id, x, y, z, heading }, update_db); } void DynamicZoneBase::SetZoneInLocation(const DynamicZoneLocation& location, bool update_db) { m_zonein = location; m_has_zonein = true; if (update_db) { LogDynamicZonesDetail("Saving [{}] zonein zone: [{}] xyzh: ([{}], [{}], [{}], [{}])", m_id, m_zone_id, m_zonein.x, m_zonein.y, m_zonein.z, m_zonein.heading); DynamicZonesRepository::UpdateZoneIn(GetDatabase(), m_id, m_zone_id, m_zonein.x, m_zonein.y, m_zonein.z, m_zonein.heading, m_has_zonein); SendServerPacket(CreateServerDzLocationPacket(ServerOP_DzSetZoneIn, location).get()); } } void DynamicZoneBase::SetZoneInLocation(float x, float y, float z, float heading, bool update_db) { SetZoneInLocation({ 0, x, y, z, heading }, update_db); } void DynamicZoneBase::SetSwitchID(int dz_switch_id, bool update_db) { m_dz_switch_id = dz_switch_id; if (update_db) { DynamicZonesRepository::UpdateSwitchID(GetDatabase(), m_id, dz_switch_id); SendServerPacket(CreateServerDzSwitchIDPacket().get()); } } void DynamicZoneBase::SetLeader(const DynamicZoneMember& new_leader, bool update_db) { m_leader = new_leader; if (update_db) { DynamicZonesRepository::UpdateLeaderID(GetDatabase(), m_id, new_leader.id); } } void DynamicZoneBase::SetLocked(bool lock, bool update_db, DzLockMsg lock_msg, uint32_t color) { m_is_locked = lock; if (update_db) { DynamicZonesRepository::UpdateLocked(GetDatabase(), m_id, lock); ServerPacket pack(ServerOP_DzLock, sizeof(ServerDzLock_Struct)); auto buf = reinterpret_cast(pack.pBuffer); buf->dz_id = GetID(); buf->sender_zone_id = GetCurrentZoneID(); buf->sender_instance_id = GetCurrentInstanceID(); buf->lock = m_is_locked; buf->lock_msg = static_cast(lock_msg); buf->color = color; SendServerPacket(&pack); } } void DynamicZoneBase::SetReplayOnJoin(bool enabled, bool update_db) { m_add_replay = enabled; if (update_db) { DynamicZonesRepository::UpdateReplayOnJoin(GetDatabase(), m_id, enabled); ServerPacket pack(ServerOP_DzReplayOnJoin, sizeof(ServerDzBool_Struct)); auto buf = reinterpret_cast(pack.pBuffer); buf->dz_id = GetID(); buf->sender_zone_id = GetCurrentZoneID(); buf->sender_instance_id = GetCurrentInstanceID(); buf->enabled = enabled; SendServerPacket(&pack); } } uint32_t DynamicZoneBase::GetSecondsRemaining() const { auto remaining = std::chrono::duration_cast(GetDurationRemaining()).count(); return std::max(0, static_cast(remaining)); } std::unique_ptr DynamicZoneBase::CreateServerMemberAddRemovePacket( const DynamicZoneMember& member, bool removed) { constexpr uint32_t pack_size = sizeof(ServerDzMember_Struct); auto pack = std::make_unique(ServerOP_DzAddRemoveMember, pack_size); auto buf = reinterpret_cast(pack->pBuffer); buf->dz_id = GetID(); buf->dz_zone_id = GetZoneID(); buf->dz_instance_id = GetInstanceID(); buf->sender_zone_id = GetCurrentZoneID(); buf->sender_instance_id = GetCurrentInstanceID(); buf->removed = removed; buf->character_id = member.id; buf->character_status = static_cast(member.status); strn0cpy(buf->character_name, member.name.c_str(), sizeof(buf->character_name)); return pack; } std::unique_ptr DynamicZoneBase::CreateServerMemberSwapPacket( const DynamicZoneMember& remove_member, const DynamicZoneMember& add_member) { constexpr uint32_t pack_size = sizeof(ServerDzMemberSwap_Struct); auto pack = std::make_unique(ServerOP_DzSwapMembers, pack_size); auto buf = reinterpret_cast(pack->pBuffer); buf->dz_id = GetID(); buf->dz_zone_id = GetZoneID(); buf->dz_instance_id = GetInstanceID(); buf->sender_zone_id = GetCurrentZoneID(); buf->sender_instance_id = GetCurrentInstanceID(); buf->add_character_status = static_cast(add_member.status); buf->add_character_id = add_member.id; buf->remove_character_id = remove_member.id; strn0cpy(buf->add_character_name, add_member.name.c_str(), sizeof(buf->add_character_name)); strn0cpy(buf->remove_character_name, remove_member.name.c_str(), sizeof(buf->remove_character_name)); return pack; } std::unique_ptr DynamicZoneBase::CreateServerRemoveAllMembersPacket() { constexpr uint32_t pack_size = sizeof(ServerDzID_Struct); auto pack = std::make_unique(ServerOP_DzRemoveAllMembers, pack_size); auto buf = reinterpret_cast(pack->pBuffer); buf->dz_id = GetID(); buf->dz_zone_id = GetZoneID(); buf->dz_instance_id = GetInstanceID(); buf->sender_zone_id = GetCurrentZoneID(); buf->sender_instance_id = GetCurrentInstanceID(); return pack; } std::unique_ptr DynamicZoneBase::CreateServerDzLocationPacket( uint16_t server_opcode, const DynamicZoneLocation& location) { constexpr uint32_t pack_size = sizeof(ServerDzLocation_Struct); auto pack = std::make_unique(server_opcode, pack_size); auto buf = reinterpret_cast(pack->pBuffer); buf->dz_id = GetID(); buf->sender_zone_id = GetCurrentZoneID(); buf->sender_instance_id = GetCurrentInstanceID(); buf->zone_id = location.zone_id; buf->x = location.x; buf->y = location.y; buf->z = location.z; buf->heading = location.heading; return pack; } std::unique_ptr DynamicZoneBase::CreateServerDzSwitchIDPacket() { constexpr uint32_t pack_size = sizeof(ServerDzSwitchID_Struct); auto pack = std::make_unique(ServerOP_DzSetSwitchID, pack_size); auto buf = reinterpret_cast(pack->pBuffer); buf->dz_id = GetID(); buf->dz_switch_id = GetSwitchID(); return pack; } std::unique_ptr DynamicZoneBase::CreateServerMemberStatusPacket( uint32_t character_id, DynamicZoneMemberStatus status) { constexpr uint32_t pack_size = sizeof(ServerDzMemberStatus_Struct); auto pack = std::make_unique(ServerOP_DzUpdateMemberStatus, pack_size); auto buf = reinterpret_cast(pack->pBuffer); buf->dz_id = GetID(); buf->sender_zone_id = GetCurrentZoneID(); buf->sender_instance_id = GetCurrentInstanceID(); buf->status = static_cast(status); buf->character_id = character_id; return pack; } uint32_t DynamicZoneBase::GetDatabaseMemberCount() { return DynamicZoneMembersRepository::GetCountWhere(GetDatabase(), fmt::format("dynamic_zone_id = {}", m_id)); } bool DynamicZoneBase::HasDatabaseMember(uint32_t character_id) { if (character_id == 0) { return false; } auto entries = DynamicZoneMembersRepository::GetWhere(GetDatabase(), fmt::format( "dynamic_zone_id = {} AND character_id = {}", m_id, character_id )); return entries.size() != 0; } void DynamicZoneBase::AddInternalMember(const DynamicZoneMember& member) { if (!HasMember(member.id)) { m_members.emplace_back(member); } } void DynamicZoneBase::RemoveInternalMember(uint32_t character_id) { m_members.erase(std::remove_if(m_members.begin(), m_members.end(), [&](const DynamicZoneMember& member) { return member.id == character_id; } ), m_members.end()); } bool DynamicZoneBase::HasMember(uint32_t character_id) const { return std::any_of(m_members.begin(), m_members.end(), [&](const DynamicZoneMember& member) { return member.id == character_id; }); } bool DynamicZoneBase::HasMember(const std::string& character_name) const { return std::any_of(m_members.begin(), m_members.end(), [&](const DynamicZoneMember& member) { return strcasecmp(member.name.c_str(), character_name.c_str()) == 0; }); } DynamicZoneMember DynamicZoneBase::GetMemberData(uint32_t character_id) { auto it = std::find_if(m_members.begin(), m_members.end(), [&](const DynamicZoneMember& member) { return member.id == character_id; }); DynamicZoneMember member_data; if (it != m_members.end()) { member_data = *it; } return member_data; } DynamicZoneMember DynamicZoneBase::GetMemberData(const std::string& character_name) { auto it = std::find_if(m_members.begin(), m_members.end(), [&](const DynamicZoneMember& member) { return strcasecmp(member.name.c_str(), character_name.c_str()) == 0; }); DynamicZoneMember member_data; if (it != m_members.end()) { member_data = *it; } return member_data; } bool DynamicZoneBase::SetInternalMemberStatus(uint32_t character_id, DynamicZoneMemberStatus status) { if (status == DynamicZoneMemberStatus::InDynamicZone && !RuleB(DynamicZone, EnableInDynamicZoneStatus)) { status = DynamicZoneMemberStatus::Online; } if (character_id == m_leader.id) { m_leader.status = status; } auto it = std::find_if(m_members.begin(), m_members.end(), [&](const DynamicZoneMember& member) { return member.id == character_id; }); if (it != m_members.end() && it->status != status) { it->status = status; return true; } return false; } void DynamicZoneBase::SetMemberStatus(uint32_t character_id, DynamicZoneMemberStatus status) { auto update_member = GetMemberData(character_id); if (update_member.IsValid()) { ProcessMemberStatusChange(character_id, status); SendServerPacket(CreateServerMemberStatusPacket(character_id, status).get()); } } void DynamicZoneBase::ProcessMemberAddRemove(const DynamicZoneMember& member, bool removed) { if (!removed) { AddInternalMember(member); } else { RemoveInternalMember(member.id); } } bool DynamicZoneBase::ProcessMemberStatusChange(uint32_t character_id, DynamicZoneMemberStatus status) { return SetInternalMemberStatus(character_id, status); } std::string DynamicZoneBase::GetDynamicZoneTypeName(DynamicZoneType dz_type) { switch (dz_type) { case DynamicZoneType::Expedition: return "Expedition"; case DynamicZoneType::Tutorial: return "Tutorial"; case DynamicZoneType::Task: return "Task"; case DynamicZoneType::Mission: return "Mission"; case DynamicZoneType::Quest: return "Quest"; default: return "Unknown"; } } std::unique_ptr DynamicZoneBase::CreateServerPacket(uint16_t zone_id, uint16_t instance_id) { std::ostringstream ss = GetSerialized(); std::string_view sv = ss.view(); auto pack_size = sizeof(ServerDzCreate_Struct) + sv.size(); auto pack = std::make_unique(ServerOP_DzCreated, static_cast(pack_size)); auto buf = reinterpret_cast(pack->pBuffer); buf->origin_zone_id = zone_id; buf->origin_instance_id = instance_id; buf->dz_id = GetID(); buf->cereal_size = static_cast(sv.size()); memcpy(buf->cereal_data, sv.data(), sv.size()); return pack; } std::ostringstream DynamicZoneBase::GetSerialized() { std::ostringstream ss; cereal::BinaryOutputArchive archive(ss); archive(*this); return ss; } void DynamicZoneBase::Unserialize(std::span buf) { EQ::Util::MemoryStreamReader ss(buf.data(), buf.size()); cereal::BinaryInputArchive archive(ss); archive(*this); } void DynamicZoneBase::LoadTemplate(const DynamicZoneTemplatesRepository::DynamicZoneTemplates& dz_template) { m_zone_id = dz_template.zone_id; m_zone_version = dz_template.zone_version; m_name = dz_template.name; m_min_players = dz_template.min_players; m_max_players = dz_template.max_players; m_duration = std::chrono::seconds(dz_template.duration_seconds); m_dz_switch_id = dz_template.dz_switch_id; m_compass.zone_id = dz_template.compass_zone_id; m_compass.x = dz_template.compass_x; m_compass.y = dz_template.compass_y; m_compass.z = dz_template.compass_z; m_safereturn.zone_id = dz_template.return_zone_id; m_safereturn.x = dz_template.return_x; m_safereturn.y = dz_template.return_y; m_safereturn.z = dz_template.return_z; m_safereturn.heading = dz_template.return_h; m_has_zonein = dz_template.override_zone_in; m_zonein.x = dz_template.zone_in_x; m_zonein.y = dz_template.zone_in_y; m_zonein.z = dz_template.zone_in_z; m_zonein.heading = dz_template.zone_in_h; } std::vector DynamicZoneBase::GetMemberIds() { std::vector ids; ids.reserve(m_members.size()); for (const auto& member : m_members) { ids.push_back(member.id); } return ids; } bool DynamicZoneBase::HasLockout(const std::string& event) { return std::ranges::any_of(m_lockouts, [&](const auto& l) { return l.IsEvent(event); }); } bool DynamicZoneBase::HasReplayLockout() { return HasLockout(DzLockout::ReplayTimer); } void DynamicZoneBase::AddLockout(const std::string& event, uint32_t seconds) { auto lockout = DzLockout::Create(m_name, event, seconds, m_uuid); AddLockout(lockout); } void DynamicZoneBase::AddLockout(const DzLockout& lockout, bool members_only) { if (!members_only) { DynamicZoneLockoutsRepository::InsertLockouts(GetDatabase(), GetID(), { lockout }); } CharacterExpeditionLockoutsRepository::InsertLockout(GetDatabase(), GetMemberIds(), lockout); HandleLockoutUpdate(lockout, false, members_only); SendServerPacket(CreateLockoutPacket(lockout, false, members_only).get()); } void DynamicZoneBase::AddLockoutDuration(const std::string& event, int seconds, bool members_only) { auto lockout = DzLockout::Create(m_name, event, std::max(0, seconds), m_uuid); // lockout has unsigned duration, pass original seconds to support reducing existing timers int secs = static_cast(seconds * RuleR(Expedition, LockoutDurationMultiplier)); CharacterExpeditionLockoutsRepository::AddLockoutDuration(GetDatabase(), GetMemberIds(), lockout, secs); HandleLockoutDuration(lockout, seconds, members_only, true); SendServerPacket(CreateLockoutDurationPacket(lockout, seconds, members_only).get()); } void DynamicZoneBase::UpdateLockoutDuration(const std::string& event, uint32_t seconds, bool members_only) { // some live expeditions update existing lockout timers during progression auto it = std::ranges::find_if(m_lockouts, [&](const auto& l) { return l.IsEvent(event); }); if (it != m_lockouts.end()) { seconds = static_cast(seconds * RuleR(Expedition, LockoutDurationMultiplier)); DzLockout lockout(m_uuid, m_name, event, it->GetStartTime() + seconds, seconds); AddLockout(lockout, members_only); } } void DynamicZoneBase::RemoveLockout(const std::string& event) { DynamicZoneLockoutsRepository::DeleteWhere(GetDatabase(), fmt::format( "dynamic_zone_id = {} AND event_name = '{}'", GetID(), Strings::Escape(event))); CharacterExpeditionLockoutsRepository::DeleteWhere(GetDatabase(), fmt::format( "character_id IN ({}) AND expedition_name = '{}' AND event_name = '{}'", fmt::join(GetMemberIds(), ","), Strings::Escape(m_name), Strings::Escape(event))); DzLockout lockout{m_uuid, m_name, event, 0, 0}; HandleLockoutUpdate(lockout, true, false); SendServerPacket(CreateLockoutPacket(lockout, true).get()); } void DynamicZoneBase::HandleLockoutUpdate(const DzLockout& lockout, bool remove, bool members_only) { if (!members_only) { std::erase_if(m_lockouts, [&](const auto& l) { return l.IsEvent(lockout.Event()); }); if (!remove) { m_lockouts.push_back(lockout); } } } void DynamicZoneBase::HandleLockoutDuration(const DzLockout& lockout, int seconds, bool members_only, bool insert_db) { if (!members_only) { auto it = std::ranges::find_if(m_lockouts, [&](const auto& l) { return l.IsEvent(lockout.Event()); }); if (it != m_lockouts.end()) { it->AddLockoutTime(seconds); } else { it = m_lockouts.insert(m_lockouts.end(), lockout); } if (insert_db) { DynamicZoneLockoutsRepository::InsertLockouts(GetDatabase(), GetID(), { *it }); } } } std::unique_ptr DynamicZoneBase::CreateLockoutPacket(const DzLockout& lockout, bool remove, bool members_only) const { uint32_t pack_size = sizeof(ServerDzLockout_Struct); auto pack = std::make_unique(ServerOP_DzLockout, pack_size); auto buf = reinterpret_cast(pack->pBuffer); buf->dz_id = GetID(); buf->expire_time = lockout.GetExpireTime(); buf->duration = lockout.GetDuration(); buf->sender_zone_id = GetCurrentZoneID(); buf->sender_instance_id = GetCurrentInstanceID(); buf->remove = remove; buf->members_only = members_only; strn0cpy(buf->event_name, lockout.Event().c_str(), sizeof(buf->event_name)); return pack; } std::unique_ptr DynamicZoneBase::CreateLockoutDurationPacket(const DzLockout& lockout, int seconds, bool members_only) const { uint32_t pack_size = sizeof(ServerDzLockout_Struct); auto pack = std::make_unique(ServerOP_DzLockoutDuration, pack_size); auto buf = reinterpret_cast(pack->pBuffer); buf->dz_id = GetID(); buf->expire_time = lockout.GetExpireTime(); buf->duration = lockout.GetDuration(); buf->sender_zone_id = GetCurrentZoneID(); buf->sender_instance_id = GetCurrentInstanceID(); buf->members_only = members_only; buf->seconds = seconds; strn0cpy(buf->event_name, lockout.Event().c_str(), sizeof(buf->event_name)); return pack; } void DynamicZoneBase::SyncCharacterLockouts(uint32_t char_id, std::vector& lockouts) { // adds missing event lockouts to client for this expedition and updates // client timers that are both shorter and from another expedition bool modified = false; for (const auto& lockout : m_lockouts) { if (lockout.IsReplay() || lockout.IsExpired() || lockout.UUID() != m_uuid) { continue; } auto it = std::find_if(lockouts.begin(), lockouts.end(), [&](const DzLockout& l) { return l.IsSame(lockout); }); if (it == lockouts.end()) { modified = true; lockouts.push_back(lockout); // insert missing } else if (it->GetSecondsRemaining() < lockout.GetSecondsRemaining() && it->UUID() != m_uuid) { // only update lockout timer not uuid so loot event apis still work modified = true; it->SetDuration(lockout.GetDuration()); it->SetExpireTime(lockout.GetExpireTime()); } } if (modified) { CharacterExpeditionLockoutsRepository::InsertLockouts(GetDatabase(), char_id, lockouts); } }