mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-11 21:01:29 +00:00
This removes the separate Expedition class and moves lockout code and
/dz command handlers into DynamicZone classes. It also refactors some
code to reduce bloat and some database usage.
This completes the effort of moving everything to DynamicZone that
started when implementing shared tasks. It also makes sense to do this
since expeditions are just dynamic zones internally despite dzs being
used for other types. Expedition specific things are just handled with
dz type checks.
Functionally nothing should change. This is mainly internal refactoring
and moving code around along with some bug fixes and reduced database
usage.
Main changes:
- The `expeditions` database table has been removed
- Expeditions no longer use a separate id, the expedition id is just the dz id
- Expedition lock state and replay timer option were moved to the
`dynamic_zones` table
- Expeditions no longer have a separate cache from dynamic zones
- Expedition creation no longer has every zone query the database to cache it
- Expedition internal lockouts are now stored on DynamicZone
- The `expedition_lockouts` table has been renamed to `dynamic_zone_lockouts`
- Fixed a small bug with the UpdateLockoutDuration api where the
internal lockout would get the time added twice in memory in the
initiating zone (this api is likely rarely used)
- Fixed an issue where use of the group/raid DoesAnyMemberHaveExpeditionLockout
api would query once for every out of zone character.
- This api now checks all members in the current zone first and only
performs a single bulk query for out of zone members if that check
is exhausted
- Deprecated the max_check_count param of DoesAnyMemberHaveExpeditionLockout,
the quest api still exists to avoid api break but a passed arg has no effect
879 lines
28 KiB
C++
879 lines
28 KiB
C++
#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<int>(std::chrono::system_clock::to_time_t(m_start_time));
|
|
insert_instance.duration = static_cast<int>(m_duration.count());
|
|
insert_instance.never_expires = m_never_expires;
|
|
|
|
auto instance = InstanceListRepository::InsertOne(GetDatabase(), insert_instance);
|
|
if (instance.id == 0)
|
|
{
|
|
LogDynamicZones("Failed to create instance [{}] for zone [{}]", unused_instance_id, m_zone_id);
|
|
return 0;
|
|
}
|
|
|
|
m_instance_id = 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<DynamicZoneType>(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<int32_t>(m_instance_id),
|
|
dz.type = static_cast<uint8_t>(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<uint8_t>(m_has_zonein);
|
|
dz.is_locked = static_cast<int8_t>(m_is_locked);
|
|
dz.add_replay = static_cast<int8_t>(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<DynamicZoneMember>& 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<DynamicZoneMembersRepository::DynamicZoneMembers> insert_members;
|
|
std::vector<InstanceListPlayerRepository::InstanceListPlayer> 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<ServerDzLock_Struct*>(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<uint8_t>(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<ServerDzBool_Struct*>(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<std::chrono::seconds>(GetDurationRemaining()).count();
|
|
return std::max(0, static_cast<int>(remaining));
|
|
}
|
|
|
|
std::unique_ptr<ServerPacket> DynamicZoneBase::CreateServerMemberAddRemovePacket(
|
|
const DynamicZoneMember& member, bool removed)
|
|
{
|
|
constexpr uint32_t pack_size = sizeof(ServerDzMember_Struct);
|
|
auto pack = std::make_unique<ServerPacket>(ServerOP_DzAddRemoveMember, pack_size);
|
|
auto buf = reinterpret_cast<ServerDzMember_Struct*>(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<uint8_t>(member.status);
|
|
strn0cpy(buf->character_name, member.name.c_str(), sizeof(buf->character_name));
|
|
|
|
return pack;
|
|
}
|
|
|
|
std::unique_ptr<ServerPacket> DynamicZoneBase::CreateServerMemberSwapPacket(
|
|
const DynamicZoneMember& remove_member, const DynamicZoneMember& add_member)
|
|
{
|
|
constexpr uint32_t pack_size = sizeof(ServerDzMemberSwap_Struct);
|
|
auto pack = std::make_unique<ServerPacket>(ServerOP_DzSwapMembers, pack_size);
|
|
auto buf = reinterpret_cast<ServerDzMemberSwap_Struct*>(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<uint8_t>(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<ServerPacket> DynamicZoneBase::CreateServerRemoveAllMembersPacket()
|
|
{
|
|
constexpr uint32_t pack_size = sizeof(ServerDzID_Struct);
|
|
auto pack = std::make_unique<ServerPacket>(ServerOP_DzRemoveAllMembers, pack_size);
|
|
auto buf = reinterpret_cast<ServerDzID_Struct*>(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<ServerPacket> DynamicZoneBase::CreateServerDzLocationPacket(
|
|
uint16_t server_opcode, const DynamicZoneLocation& location)
|
|
{
|
|
constexpr uint32_t pack_size = sizeof(ServerDzLocation_Struct);
|
|
auto pack = std::make_unique<ServerPacket>(server_opcode, pack_size);
|
|
auto buf = reinterpret_cast<ServerDzLocation_Struct*>(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<ServerPacket> DynamicZoneBase::CreateServerDzSwitchIDPacket()
|
|
{
|
|
constexpr uint32_t pack_size = sizeof(ServerDzSwitchID_Struct);
|
|
auto pack = std::make_unique<ServerPacket>(ServerOP_DzSetSwitchID, pack_size);
|
|
auto buf = reinterpret_cast<ServerDzSwitchID_Struct*>(pack->pBuffer);
|
|
buf->dz_id = GetID();
|
|
buf->dz_switch_id = GetSwitchID();
|
|
|
|
return pack;
|
|
}
|
|
|
|
std::unique_ptr<ServerPacket> DynamicZoneBase::CreateServerMemberStatusPacket(
|
|
uint32_t character_id, DynamicZoneMemberStatus status)
|
|
{
|
|
constexpr uint32_t pack_size = sizeof(ServerDzMemberStatus_Struct);
|
|
auto pack = std::make_unique<ServerPacket>(ServerOP_DzUpdateMemberStatus, pack_size);
|
|
auto buf = reinterpret_cast<ServerDzMemberStatus_Struct*>(pack->pBuffer);
|
|
buf->dz_id = GetID();
|
|
buf->sender_zone_id = GetCurrentZoneID();
|
|
buf->sender_instance_id = GetCurrentInstanceID();
|
|
buf->status = static_cast<uint8_t>(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<ServerPacket> 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<ServerPacket>(ServerOP_DzCreated, static_cast<uint32_t>(pack_size));
|
|
auto buf = reinterpret_cast<ServerDzCreate_Struct*>(pack->pBuffer);
|
|
buf->origin_zone_id = zone_id;
|
|
buf->origin_instance_id = instance_id;
|
|
buf->dz_id = GetID();
|
|
buf->cereal_size = static_cast<uint32_t>(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<char> 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<uint32_t> DynamicZoneBase::GetMemberIds()
|
|
{
|
|
std::vector<uint32_t> 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<int>(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<uint32_t>(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<ServerPacket> DynamicZoneBase::CreateLockoutPacket(const DzLockout& lockout, bool remove, bool members_only) const
|
|
{
|
|
uint32_t pack_size = sizeof(ServerDzLockout_Struct);
|
|
auto pack = std::make_unique<ServerPacket>(ServerOP_DzLockout, pack_size);
|
|
auto buf = reinterpret_cast<ServerDzLockout_Struct*>(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<ServerPacket> DynamicZoneBase::CreateLockoutDurationPacket(const DzLockout& lockout, int seconds, bool members_only) const
|
|
{
|
|
uint32_t pack_size = sizeof(ServerDzLockout_Struct);
|
|
auto pack = std::make_unique<ServerPacket>(ServerOP_DzLockoutDuration, pack_size);
|
|
auto buf = reinterpret_cast<ServerDzLockout_Struct*>(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<DzLockout>& 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);
|
|
}
|
|
}
|