eqemu-server/common/repositories/dynamic_zones_repository.h
hg 55155ff800
[Expeditions] Move expedition code into DynamicZone (#4672)
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
2025-02-15 18:40:35 -06:00

432 lines
12 KiB
C++

#ifndef EQEMU_DYNAMIC_ZONES_REPOSITORY_H
#define EQEMU_DYNAMIC_ZONES_REPOSITORY_H
#include "../database.h"
#include "../strings.h"
#include "base/base_dynamic_zones_repository.h"
class DynamicZonesRepository: public BaseDynamicZonesRepository {
public:
/**
* This file was auto generated and can be modified and extended upon
*
* Base repository methods are automatically
* generated in the "base" version of this repository. The base repository
* is immutable and to be left untouched, while methods in this class
* are used as extension methods for more specific persistence-layer
* accessors or mutators.
*
* Base Methods (Subject to be expanded upon in time)
*
* Note: Not all tables are designed appropriately to fit functionality with all base methods
*
* InsertOne
* UpdateOne
* DeleteOne
* FindOne
* GetWhere(std::string where_filter)
* DeleteWhere(std::string where_filter)
* InsertMany
* All
*
* Example custom methods in a repository
*
* DynamicZonesRepository::GetByZoneAndVersion(int zone_id, int zone_version)
* DynamicZonesRepository::GetWhereNeverExpires()
* DynamicZonesRepository::GetWhereXAndY()
* DynamicZonesRepository::DeleteWhereXAndY()
*
* Most of the above could be covered by base methods, but if you as a developer
* find yourself re-using logic for other parts of the code, its best to just make a
* method that can be re-used easily elsewhere especially if it can use a base repository
* method and encapsulate filters there
*/
// Custom extended repository methods here
struct DynamicZoneInstance
{
uint32_t id;
std::string uuid;
std::string name;
int leader_id;
int min_players;
int max_players;
int instance_id;
int type;
int dz_switch_id;
int compass_zone_id;
float compass_x;
float compass_y;
float compass_z;
int safe_return_zone_id;
float safe_return_x;
float safe_return_y;
float safe_return_z;
float safe_return_heading;
float zone_in_x;
float zone_in_y;
float zone_in_z;
float zone_in_heading;
int has_zone_in;
int8_t is_locked;
int8_t add_replay;
int zone;
int version;
int is_global;
uint32_t start_time;
int duration;
int never_expires;
};
static std::string SelectDynamicZoneJoinInstance()
{
return std::string(SQL(
SELECT
dynamic_zones.id,
dynamic_zones.uuid,
dynamic_zones.name,
dynamic_zones.leader_id,
dynamic_zones.min_players,
dynamic_zones.max_players,
dynamic_zones.instance_id,
dynamic_zones.type,
dynamic_zones.dz_switch_id,
dynamic_zones.compass_zone_id,
dynamic_zones.compass_x,
dynamic_zones.compass_y,
dynamic_zones.compass_z,
dynamic_zones.safe_return_zone_id,
dynamic_zones.safe_return_x,
dynamic_zones.safe_return_y,
dynamic_zones.safe_return_z,
dynamic_zones.safe_return_heading,
dynamic_zones.zone_in_x,
dynamic_zones.zone_in_y,
dynamic_zones.zone_in_z,
dynamic_zones.zone_in_heading,
dynamic_zones.has_zone_in,
dynamic_zones.is_locked,
dynamic_zones.add_replay,
instance_list.zone,
instance_list.version,
instance_list.is_global,
instance_list.start_time,
instance_list.duration,
instance_list.never_expires
FROM dynamic_zones
INNER JOIN instance_list ON dynamic_zones.instance_id = instance_list.id
));
}
static DynamicZoneInstance FillWithInstanceFromRow(MySQLRequestRow& row)
{
DynamicZoneInstance entry{};
int col = 0;
entry.id = strtoul(row[col++], nullptr, 10);
entry.uuid = row[col++];
entry.name = row[col++];
entry.leader_id = strtol(row[col++], nullptr, 10);
entry.min_players = strtol(row[col++], nullptr, 10);
entry.max_players = strtol(row[col++], nullptr, 10);
entry.instance_id = strtol(row[col++], nullptr, 10);
entry.type = strtol(row[col++], nullptr, 10);
entry.dz_switch_id = strtol(row[col++], nullptr, 10);
entry.compass_zone_id = strtol(row[col++], nullptr, 10);
entry.compass_x = strtof(row[col++], nullptr);
entry.compass_y = strtof(row[col++], nullptr);
entry.compass_z = strtof(row[col++], nullptr);
entry.safe_return_zone_id = strtol(row[col++], nullptr, 10);
entry.safe_return_x = strtof(row[col++], nullptr);
entry.safe_return_y = strtof(row[col++], nullptr);
entry.safe_return_z = strtof(row[col++], nullptr);
entry.safe_return_heading = strtof(row[col++], nullptr);
entry.zone_in_x = strtof(row[col++], nullptr);
entry.zone_in_y = strtof(row[col++], nullptr);
entry.zone_in_z = strtof(row[col++], nullptr);
entry.zone_in_heading = strtof(row[col++], nullptr);
entry.has_zone_in = strtol(row[col++], nullptr, 10) != 0;
entry.is_locked = static_cast<int8_t>(strtol(row[col++], nullptr, 10));
entry.add_replay = static_cast<int8_t>(strtol(row[col++], nullptr, 10));
// from instance_list
entry.zone = strtol(row[col++], nullptr, 10);
entry.version = strtol(row[col++], nullptr, 10);
entry.is_global = strtol(row[col++], nullptr, 10);
entry.start_time = strtoul(row[col++], nullptr, 10);
entry.duration = strtol(row[col++], nullptr, 10);
entry.never_expires = strtol(row[col++], nullptr, 10);
return entry;
}
static std::vector<DynamicZoneInstance> AllWithInstanceNotExpired(Database& db)
{
std::vector<DynamicZoneInstance> all_entries;
auto results = db.QueryDatabase(fmt::format(SQL(
{} WHERE
(instance_list.start_time + instance_list.duration) > UNIX_TIMESTAMP()
AND instance_list.never_expires = 0
), SelectDynamicZoneJoinInstance()));
if (results.Success())
{
all_entries.reserve(results.RowCount());
for (auto row = results.begin(); row != results.end(); ++row)
{
DynamicZoneInstance entry = FillWithInstanceFromRow(row);
all_entries.emplace_back(std::move(entry));
}
}
return all_entries;
}
static void UpdateCompass(Database& db, uint32_t dz_id, int zone_id, float x, float y, float z)
{
if (dz_id != 0)
{
std::string query = fmt::format(SQL(
UPDATE {} SET
compass_zone_id = {},
compass_x = {},
compass_y = {},
compass_z = {}
WHERE {} = {};
), TableName(), zone_id, x, y, z, PrimaryKey(), dz_id);
db.QueryDatabase(query);
}
}
static void UpdateSafeReturn(Database& db, uint32_t dz_id, int zone_id, float x, float y, float z, float heading)
{
if (dz_id != 0)
{
std::string query = fmt::format(SQL(
UPDATE {} SET
safe_return_zone_id = {},
safe_return_x = {},
safe_return_y = {},
safe_return_z = {},
safe_return_heading = {}
WHERE {} = {};
), TableName(), zone_id, x, y, z, heading, PrimaryKey(), dz_id);
db.QueryDatabase(query);
}
}
static void UpdateZoneIn(Database& db, uint32_t dz_id, uint32_t zone_id, float x, float y, float z, float heading, bool has_zone_in)
{
if (dz_id != 0)
{
std::string query = fmt::format(SQL(
UPDATE {} SET
zone_in_x = {},
zone_in_y = {},
zone_in_z = {},
zone_in_heading = {},
has_zone_in = {}
WHERE {} = {};
), TableName(), x, y, z, heading, has_zone_in, PrimaryKey(), dz_id);
db.QueryDatabase(query);
}
}
static void UpdateLeaderID(Database& db, uint32_t dz_id, uint32_t leader_id)
{
if (dz_id != 0)
{
std::string query = fmt::format(SQL(
UPDATE {} SET leader_id = {} WHERE {} = {};
), TableName(), leader_id, PrimaryKey(), dz_id);
db.QueryDatabase(query);
}
}
static void UpdateLocked(Database& db, uint32_t dz_id, bool lock)
{
if (dz_id != 0)
{
db.QueryDatabase(fmt::format("UPDATE dynamic_zones SET is_locked = {} WHERE id = {}", lock, dz_id));
}
}
static void UpdateReplayOnJoin(Database& db, uint32_t dz_id, bool enabled)
{
if (dz_id != 0)
{
db.QueryDatabase(fmt::format("UPDATE dynamic_zones SET add_replay = {} WHERE id = {}", enabled, dz_id));
}
}
static void UpdateSwitchID(Database& db, uint32_t dz_id, int dz_switch_id)
{
if (dz_id != 0)
{
std::string query = fmt::format(SQL(
UPDATE {} SET dz_switch_id = {} WHERE {} = {};
), TableName(), dz_switch_id, PrimaryKey(), dz_id);
db.QueryDatabase(query);
}
}
struct DynamicZoneInstancePlayerCount
{
uint32_t id;
int type;
int instance;
int zone;
int version;
uint32_t start_time;
int duration;
int member_count;
};
static std::string SelectDynamicZoneInstancePlayerCount()
{
return std::string(SQL(
SELECT
dynamic_zones.id,
dynamic_zones.type,
instance_list.id,
instance_list.zone,
instance_list.version,
instance_list.start_time,
instance_list.duration,
COUNT(dynamic_zone_members.character_id) member_count
FROM dynamic_zones
INNER JOIN instance_list ON dynamic_zones.instance_id = instance_list.id
LEFT JOIN dynamic_zone_members ON dynamic_zones.id = dynamic_zone_members.dynamic_zone_id
GROUP BY instance_list.id
ORDER BY dynamic_zones.id;
));
};
static std::vector<DynamicZoneInstancePlayerCount> AllDzInstancePlayerCounts(Database& db)
{
std::vector<DynamicZoneInstancePlayerCount> all_entries;
auto results = db.QueryDatabase(SelectDynamicZoneInstancePlayerCount());
if (results.Success())
{
all_entries.reserve(results.RowCount());
for (auto row = results.begin(); row != results.end(); ++row)
{
DynamicZoneInstancePlayerCount entry{};
int col = 0;
entry.id = strtoul(row[col++], nullptr, 10);
entry.type = strtol(row[col++], nullptr, 10);
entry.instance = strtol(row[col++], nullptr, 10);
entry.zone = strtol(row[col++], nullptr, 10);
entry.version = strtol(row[col++], nullptr, 10);
entry.start_time = strtoul(row[col++], nullptr, 10);
entry.duration = strtol(row[col++], nullptr, 10);
entry.member_count = strtol(row[col++], nullptr, 10);
all_entries.emplace_back(std::move(entry));
}
}
return all_entries;
}
static std::vector<uint32_t> GetStaleIDs(Database& db)
{
std::vector<uint32_t> all_entries;
// dzs with no members, missing instance, or expired instance
auto results = db.QueryDatabase(SQL(
SELECT
dynamic_zones.id
FROM dynamic_zones
LEFT JOIN instance_list ON dynamic_zones.instance_id = instance_list.id
LEFT JOIN
(
SELECT dynamic_zone_id, COUNT(*) member_count
FROM dynamic_zone_members
GROUP BY dynamic_zone_id
) dynamic_zone_members
ON dynamic_zone_members.dynamic_zone_id = dynamic_zones.id
WHERE
instance_list.id IS NULL
OR dynamic_zone_members.member_count IS NULL
OR dynamic_zone_members.member_count = 0
OR ((instance_list.start_time + instance_list.duration) <= UNIX_TIMESTAMP()
AND instance_list.never_expires = 0);
));
all_entries.reserve(results.RowCount());
for (auto row = results.begin(); row != results.end(); ++row)
{
all_entries.push_back(atoi(row[0]));
}
return all_entries;
}
struct CharacterDz
{
uint32_t id;
std::string name;
uint32_t dz_id;
};
// get character ids with possible active dz id by type
static std::vector<CharacterDz> GetCharactersWithDz(Database& db, const std::vector<std::string>& names, int type)
{
if (names.empty())
{
return {};
}
std::vector<CharacterDz> entries;
entries.reserve(names.size());
auto results = db.QueryDatabase(fmt::format(SQL(
SELECT
character_data.id,
character_data.name,
MAX(dynamic_zones.id)
FROM character_data
LEFT JOIN dynamic_zone_members
ON character_data.id = dynamic_zone_members.character_id
LEFT JOIN dynamic_zones
ON dynamic_zone_members.dynamic_zone_id = dynamic_zones.id
AND dynamic_zones.`type` = {0}
WHERE character_data.name IN ('{1}')
GROUP BY character_data.id
ORDER BY FIELD(character_data.name, '{1}')
),
type,
fmt::join(names, "','")
));
if (results.Success())
{
for (auto row = results.begin(); row != results.end(); ++row)
{
CharacterDz entry{};
entry.id = std::strtoul(row[0], nullptr, 10);
entry.name = row[1];
entry.dz_id = row[2] ? std::strtoul(row[2], nullptr, 10) : 0;
entries.push_back(std::move(entry));
}
}
return entries;
}
};
#endif //EQEMU_DYNAMIC_ZONES_REPOSITORY_H