[Expeditions] Refactor expedition requests (#1301)

Move ExpeditionLockoutTimer to common

This simplifies expedition request conflict checks and uses repository
for the queries instead of processing the query result directly.
This commit is contained in:
hg
2021-03-19 00:42:41 -04:00
committed by GitHub
parent ee4af65268
commit 5b74f1e756
15 changed files with 207 additions and 173 deletions
-2
View File
@@ -33,7 +33,6 @@ SET(zone_sources
exp.cpp
expedition.cpp
expedition_database.cpp
expedition_lockout_timer.cpp
expedition_request.cpp
fastmath.cpp
fearpath.cpp
@@ -186,7 +185,6 @@ SET(zone_headers
event_codes.h
expedition.h
expedition_database.h
expedition_lockout_timer.h
expedition_request.h
fastmath.h
forage.h
+1 -1
View File
@@ -42,7 +42,6 @@ extern volatile bool RunLoops;
#include "data_bucket.h"
#include "expedition.h"
#include "expedition_database.h"
#include "expedition_lockout_timer.h"
#include "expedition_request.h"
#include "position.h"
#include "worldserver.h"
@@ -61,6 +60,7 @@ extern volatile bool RunLoops;
#include "queryserv.h"
#include "mob_movement_manager.h"
#include "../common/content/world_content_service.h"
#include "../common/expedition_lockout_timer.h"
extern QueryServ* QServ;
extern EntityList entity_list;
+1 -1
View File
@@ -20,13 +20,13 @@
#include "expedition.h"
#include "expedition_database.h"
#include "expedition_lockout_timer.h"
#include "expedition_request.h"
#include "client.h"
#include "string_ids.h"
#include "worldserver.h"
#include "zonedb.h"
#include "../common/eqemu_logsys.h"
#include "../common/expedition_lockout_timer.h"
#include "../common/util/uuid.h"
extern WorldServer worldserver;
+1 -1
View File
@@ -22,8 +22,8 @@
#define EXPEDITION_H
#include "dynamic_zone.h"
#include "expedition_lockout_timer.h"
#include "../common/eq_constants.h"
#include "../common/expedition_lockout_timer.h"
#include <cstdint>
#include <memory>
#include <string>
+1 -48
View File
@@ -20,9 +20,9 @@
#include "expedition_database.h"
#include "expedition.h"
#include "expedition_lockout_timer.h"
#include "zonedb.h"
#include "../common/database.h"
#include "../common/expedition_lockout_timer.h"
#include "../common/string_util.h"
#include <fmt/core.h>
@@ -218,53 +218,6 @@ ExpeditionDatabase::LoadMultipleExpeditionLockouts(
return lockouts;
}
MySQLRequestResult ExpeditionDatabase::LoadMembersForCreateRequest(
const std::vector<std::string>& character_names, const std::string& expedition_name)
{
LogExpeditionsDetail(
"Loading data of [{}] characters for [{}] request", character_names.size(), expedition_name
);
std::string in_character_names_query;
for (const auto& character_name : character_names)
{
fmt::format_to(std::back_inserter(in_character_names_query), "'{}',", character_name);
}
MySQLRequestResult results;
if (!in_character_names_query.empty())
{
in_character_names_query.pop_back(); // trailing comma
// for create validation, loads each character's lockouts and possible current expedition
auto query = fmt::format(SQL(
SELECT
character_data.id,
character_data.name,
member.expedition_id,
lockout.from_expedition_uuid,
UNIX_TIMESTAMP(lockout.expire_time),
lockout.duration,
lockout.event_name
FROM character_data
LEFT JOIN character_expedition_lockouts lockout
ON character_data.id = lockout.character_id
AND lockout.expire_time > NOW()
AND lockout.expedition_name = '{}'
LEFT JOIN expedition_members member
ON character_data.id = member.character_id
AND member.is_current_member = TRUE
WHERE character_data.name IN ({})
ORDER BY FIELD(character_data.name, {})
), EscapeString(expedition_name), in_character_names_query, in_character_names_query);
results = database.QueryDatabase(query);
}
return results;
}
void ExpeditionDatabase::DeleteAllCharacterLockouts(uint32_t character_id)
{
LogExpeditionsDetail("Deleting all character [{}] lockouts", character_id);
-16
View File
@@ -41,8 +41,6 @@ namespace ExpeditionDatabase
std::string LoadExpeditionsSelectQuery();
MySQLRequestResult LoadExpedition(uint32_t expedition_id);
MySQLRequestResult LoadAllExpeditions();
MySQLRequestResult LoadMembersForCreateRequest(
const std::vector<std::string>& character_names, const std::string& expedition_name);
std::vector<ExpeditionLockoutTimer> LoadCharacterLockouts(uint32_t character_id);
std::vector<ExpeditionLockoutTimer> LoadCharacterLockouts(uint32_t character_id,
const std::string& expedition_name);
@@ -94,18 +92,4 @@ namespace LoadExpeditionColumns
};
};
namespace LoadMembersForCreateRequestColumns
{
enum eLoadMembersForCreateRequestColumns
{
character_id = 0,
character_name,
character_expedition_id,
lockout_uuid,
lockout_expire_time,
lockout_duration,
lockout_event_name
};
};
#endif
-101
View File
@@ -1,101 +0,0 @@
/**
* EQEmulator: Everquest Server Emulator
* Copyright (C) 2001-2020 EQEmulator Development Team (https://github.com/EQEmu/Server)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY except by those people which sell it, which
* are required to give you total support for your newly bought product;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include "expedition_lockout_timer.h"
#include "../common/string_util.h"
#include "../common/rulesys.h"
#include "../common/util/uuid.h"
#include <fmt/format.h>
const char* const DZ_REPLAY_TIMER_NAME = "Replay Timer"; // see December 14, 2016 patch notes
ExpeditionLockoutTimer::ExpeditionLockoutTimer(
const std::string& expedition_uuid, const std::string& expedition_name,
const std::string& event_name, uint64_t expire_time, uint32_t duration
) :
m_expedition_uuid(expedition_uuid),
m_expedition_name(expedition_name),
m_event_name(event_name),
m_expire_time(std::chrono::system_clock::from_time_t(expire_time)),
m_duration(duration)
{
if (event_name == DZ_REPLAY_TIMER_NAME)
{
m_is_replay_timer = true;
}
}
ExpeditionLockoutTimer ExpeditionLockoutTimer::CreateLockout(
const std::string& expedition_name, const std::string& event_name, uint32_t seconds, std::string uuid)
{
seconds = static_cast<uint32_t>(seconds * RuleR(Expedition, LockoutDurationMultiplier));
if (uuid.empty())
{
uuid = EQ::Util::UUID::Generate().ToString();
}
ExpeditionLockoutTimer lockout{uuid, expedition_name, event_name, 0, seconds};
lockout.Reset(); // sets expire time
return lockout;
}
uint32_t ExpeditionLockoutTimer::GetSecondsRemaining() const
{
auto now = std::chrono::system_clock::now();
if (m_expire_time > now)
{
auto remaining = m_expire_time - now;
return static_cast<uint32_t>(std::chrono::duration_cast<std::chrono::seconds>(remaining).count());
}
return 0;
}
ExpeditionLockoutTimer::DaysHoursMinutes ExpeditionLockoutTimer::GetDaysHoursMinutesRemaining() const
{
auto seconds = GetSecondsRemaining();
return ExpeditionLockoutTimer::DaysHoursMinutes{
fmt::format_int(seconds / 86400).str(), // days
fmt::format_int((seconds / 3600) % 24).str(), // hours
fmt::format_int((seconds / 60) % 60).str() // minutes
};
}
bool ExpeditionLockoutTimer::IsSameLockout(const ExpeditionLockoutTimer& compare_lockout) const
{
return compare_lockout.IsSameLockout(GetExpeditionName(), GetEventName());
}
bool ExpeditionLockoutTimer::IsSameLockout(
const std::string& expedition_name, const std::string& event_name) const
{
return GetExpeditionName() == expedition_name && GetEventName() == event_name;
}
void ExpeditionLockoutTimer::AddLockoutTime(int seconds)
{
seconds = static_cast<uint32_t>(seconds * RuleR(Expedition, LockoutDurationMultiplier));
auto new_duration = std::max(0, static_cast<int>(m_duration.count()) + seconds);
auto start_time = m_expire_time - m_duration;
m_duration = std::chrono::seconds(new_duration);
m_expire_time = start_time + m_duration;
}
-76
View File
@@ -1,76 +0,0 @@
/**
* EQEmulator: Everquest Server Emulator
* Copyright (C) 2001-2020 EQEmulator Development Team (https://github.com/EQEmu/Server)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY except by those people which sell it, which
* are required to give you total support for your newly bought product;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#ifndef EXPEDITION_LOCKOUT_TIMER_H
#define EXPEDITION_LOCKOUT_TIMER_H
#include <chrono>
#include <string>
extern const char* const DZ_REPLAY_TIMER_NAME;
class ExpeditionLockoutTimer
{
public:
ExpeditionLockoutTimer() = default;
ExpeditionLockoutTimer(
const std::string& expedition_uuid, const std::string& expedition_name,
const std::string& event_name, uint64_t expire_time, uint32_t duration);
static ExpeditionLockoutTimer CreateLockout(
const std::string& expedition_name, const std::string& event_name,
uint32_t seconds, std::string uuid = {});
struct DaysHoursMinutes
{
std::string days;
std::string hours;
std::string mins;
};
void AddLockoutTime(int seconds);
uint32_t GetDuration() const { return static_cast<uint32_t>(m_duration.count()); }
uint64_t GetExpireTime() const { return std::chrono::system_clock::to_time_t(m_expire_time); }
uint64_t GetStartTime() const { return std::chrono::system_clock::to_time_t(m_expire_time - m_duration); }
uint32_t GetSecondsRemaining() const;
DaysHoursMinutes GetDaysHoursMinutesRemaining() const;
const std::string& GetExpeditionName() const { return m_expedition_name; }
const std::string& GetExpeditionUUID() const { return m_expedition_uuid; }
const std::string& GetEventName() const { return m_event_name; }
bool IsExpired() const { return GetSecondsRemaining() == 0; }
bool IsFromExpedition(const std::string& uuid) const { return uuid == m_expedition_uuid; }
bool IsReplayTimer() const { return m_is_replay_timer; }
bool IsSameLockout(const ExpeditionLockoutTimer& compare_lockout) const;
bool IsSameLockout(const std::string& expedition_name, const std::string& event_name) const;
void Reset() { m_expire_time = std::chrono::system_clock::now() + m_duration; }
void SetDuration(uint32_t seconds) { m_duration = std::chrono::seconds(seconds); }
void SetExpireTime(uint64_t expire_time) { m_expire_time = std::chrono::system_clock::from_time_t(expire_time); }
void SetUUID(const std::string& uuid) { m_expedition_uuid = uuid; }
private:
bool m_is_replay_timer = false;
std::string m_expedition_uuid; // expedition received in
std::string m_expedition_name;
std::string m_event_name;
std::chrono::seconds m_duration;
std::chrono::time_point<std::chrono::system_clock> m_expire_time;
};
#endif
+58 -86
View File
@@ -21,27 +21,19 @@
#include "expedition_request.h"
#include "client.h"
#include "expedition.h"
#include "expedition_database.h"
#include "expedition_lockout_timer.h"
#include "groups.h"
#include "raids.h"
#include "string_ids.h"
#include "worldserver.h"
extern WorldServer worldserver;
#include "../common/expedition_lockout_timer.h"
#include "../common/repositories/character_expedition_lockouts_repository.h"
#include "../common/repositories/expeditions_repository.h"
constexpr char SystemName[] = "expedition";
struct ExpeditionRequestConflict
{
std::string character_name;
ExpeditionLockoutTimer lockout;
};
ExpeditionRequest::ExpeditionRequest(
std::string expedition_name, uint32_t min_players, uint32_t max_players, bool disable_messages
ExpeditionRequest::ExpeditionRequest(std::string expedition_name,
uint32_t min_players, uint32_t max_players, bool disable_messages
) :
m_expedition_name(expedition_name),
m_expedition_name(std::move(expedition_name)),
m_min_players(min_players),
m_max_players(max_players),
m_disable_messages(disable_messages)
@@ -186,117 +178,97 @@ bool ExpeditionRequest::CanMembersJoin(const std::vector<std::string>& member_na
return requirements_met;
}
bool ExpeditionRequest::LoadLeaderLockouts()
bool ExpeditionRequest::SaveLeaderLockouts(const std::vector<ExpeditionLockoutTimer>& lockouts)
{
// leader's lockouts are used to check member conflicts and later stored in expedition
auto lockouts = ExpeditionDatabase::LoadCharacterLockouts(m_leader_id, m_expedition_name);
bool has_replay_lockout = false;
for (auto& lockout : lockouts)
for (const auto& lockout : lockouts)
{
if (!lockout.IsExpired())
{
m_lockouts.emplace(lockout.GetEventName(), lockout);
m_lockouts[lockout.GetEventName()] = lockout;
// on live if leader has a replay lockout it never bothers checking for event conflicts
if (m_check_event_lockouts && lockout.IsReplayTimer())
if (lockout.IsReplayTimer())
{
m_check_event_lockouts = false;
has_replay_lockout = true;
}
}
}
return true;
return has_replay_lockout;
}
bool ExpeditionRequest::CheckMembersForConflicts(const std::vector<std::string>& member_names)
{
// load data for each member and compare with leader lockouts
auto results = ExpeditionDatabase::LoadMembersForCreateRequest(member_names, m_expedition_name);
if (!results.Success() || !LoadLeaderLockouts())
// order of member_names is preserved by queries for use with max member truncation
auto entries = ExpeditionsRepository::GetCharactersWithExpedition(database, member_names);
if (entries.empty())
{
LogExpeditions("Failed to load data to verify members for expedition request");
LogExpeditions("Failed to load members for expedition request");
return true;
}
bool is_solo = (member_names.size() == 1);
bool has_conflicts = false;
using col = LoadMembersForCreateRequestColumns::eLoadMembersForCreateRequestColumns;
std::vector<ExpeditionRequestConflict> member_lockout_conflicts;
uint32_t last_character_id = 0;
for (auto row = results.begin(); row != results.end(); ++row)
std::vector<uint32_t> character_ids;
for (const auto& character : entries)
{
uint32_t character_id = std::strtoul(row[col::character_id], nullptr, 10);
std::string character_name = row[col::character_name];
bool has_expedition = (row[col::character_expedition_id] != nullptr);
if (character_id != last_character_id)
if (is_solo && character.expedition_id != 0)
{
// defaults to online status, if offline group members implemented this needs to change
m_members.emplace_back(character_id, character_name);
// process event lockout conflict messages from the previous character
for (const auto& member_lockout : member_lockout_conflicts)
{
SendLeaderMemberEventLockout(member_lockout.character_name, member_lockout.lockout);
}
member_lockout_conflicts.clear();
if (has_expedition)
{
has_conflicts = true;
SendLeaderMemberInExpedition(character_name, is_solo);
// solo requests break out early if requester in an expedition
if (is_solo)
{
return has_conflicts;
}
}
// live doesn't bother checking replay lockout here
SendLeaderMemberInExpedition(character.name, is_solo);
return true;
}
last_character_id = character_id;
m_members.emplace_back(character.id, character.name, ExpeditionMemberStatus::Online);
character_ids.emplace_back(character.id);
}
// compare member lockouts with leader lockouts
if (row[col::lockout_uuid]) // lockout results may be null
auto member_lockouts = CharacterExpeditionLockoutsRepository::GetManyCharacterLockoutTimers(
database, character_ids, m_expedition_name, DZ_REPLAY_TIMER_NAME);
// on live if leader has a replay lockout it never checks for event conflicts
bool leader_has_replay_lockout = false;
auto lockout_iter = member_lockouts.find(m_leader_id);
if (lockout_iter != member_lockouts.end())
{
leader_has_replay_lockout = SaveLeaderLockouts(lockout_iter->second);
}
for (const auto& character : entries)
{
if (character.expedition_id != 0)
{
auto expire_time = strtoull(row[col::lockout_expire_time], nullptr, 10);
uint32_t duration = strtoul(row[col::lockout_duration], nullptr, 10);
has_conflicts = true;
SendLeaderMemberInExpedition(character.name, is_solo);
}
ExpeditionLockoutTimer lockout{
row[col::lockout_uuid], m_expedition_name, row[col::lockout_event_name], expire_time, duration
};
if (!lockout.IsExpired())
auto lockout_iter = member_lockouts.find(character.id);
if (lockout_iter != member_lockouts.end())
{
for (const auto& lockout : lockout_iter->second)
{
if (lockout.IsReplayTimer())
if (!lockout.IsExpired())
{
// replay timer conflict messages always show up before event conflicts
has_conflicts = true;
SendLeaderMemberReplayLockout(character_name, lockout, is_solo);
}
else if (m_check_event_lockouts && character_id != m_leader_id)
{
if (m_lockouts.find(lockout.GetEventName()) == m_lockouts.end())
// replay timers were sorted by query so they show up before event conflicts
if (lockout.IsReplayTimer())
{
// leader doesn't have this lockout. queue instead of messaging
// now so message comes after any replay lockout messages
has_conflicts = true;
member_lockout_conflicts.push_back({character_name, lockout});
SendLeaderMemberReplayLockout(character.name, lockout, is_solo);
}
else if (!leader_has_replay_lockout && character.id != m_leader_id &&
m_lockouts.find(lockout.GetEventName()) == m_lockouts.end())
{
// leader doesn't have this lockout
has_conflicts = true;
SendLeaderMemberEventLockout(character.name, lockout);
}
}
}
}
}
// event lockout messages for last processed character
for (const auto& member_lockout : member_lockout_conflicts)
{
SendLeaderMemberEventLockout(member_lockout.character_name, member_lockout.lockout);
}
return has_conflicts;
}
+4 -8
View File
@@ -22,7 +22,7 @@
#define EXPEDITION_REQUEST_H
#include "expedition.h"
#include "expedition_lockout_timer.h"
#include "../common/expedition_lockout_timer.h"
#include <cstdint>
#include <string>
#include <vector>
@@ -30,16 +30,13 @@
class Client;
class Group;
class MySQLRequestResult;
class Raid;
class ServerPacket;
class ExpeditionRequest
{
public:
ExpeditionRequest(
std::string expedition_name, uint32_t min_players, uint32_t max_players,
bool disable_messages = false);
ExpeditionRequest(std::string expedition_name, uint32_t min_players,
uint32_t max_players, bool disable_messages = false);
bool Validate(Client* requester);
@@ -60,7 +57,7 @@ private:
bool CheckMembersForConflicts(const std::vector<std::string>& member_names);
std::string GetGroupLeaderName(uint32_t group_id);
bool IsPlayerCountValidated();
bool LoadLeaderLockouts();
bool SaveLeaderLockouts(const std::vector<ExpeditionLockoutTimer>& leader_lockouts);
void SendLeaderMemberInExpedition(const std::string& member_name, bool is_solo);
void SendLeaderMemberReplayLockout(const std::string& member_name, const ExpeditionLockoutTimer& lockout, bool is_solo);
void SendLeaderMemberEventLockout(const std::string& member_name, const ExpeditionLockoutTimer& lockout);
@@ -71,7 +68,6 @@ private:
uint32_t m_leader_id = 0;
uint32_t m_min_players = 0;
uint32_t m_max_players = 0;
bool m_check_event_lockouts = true;
bool m_disable_messages = false;
std::string m_expedition_name;
std::string m_leader_name;
+1 -1
View File
@@ -5,7 +5,6 @@
#include "client.h"
#include "dynamic_zone.h"
#include "expedition_lockout_timer.h"
#include "expedition_request.h"
#include "lua_client.h"
#include "lua_expedition.h"
@@ -16,6 +15,7 @@
#include "lua_group.h"
#include "lua_raid.h"
#include "lua_packet.h"
#include "../common/expedition_lockout_timer.h"
struct InventoryWhere { };