eqemu-server/world/shared_task_manager.cpp
hg 8a962e09f6
[Shared Tasks] Implement task timer groups (#2340)
This adds task replay and request timer groups (an arbitrary id) which
allows for different tasks to share lockouts
2022-07-30 20:18:19 -05:00

1891 lines
56 KiB
C++

#include "shared_task_manager.h"
#include "cliententry.h"
#include "clientlist.h"
#include "dynamic_zone.h"
#include "dynamic_zone_manager.h"
#include "zonelist.h"
#include "zoneserver.h"
#include "shared_task_world_messaging.h"
#include "../common/repositories/character_data_repository.h"
#include "../common/repositories/character_task_timers_repository.h"
#include "../common/repositories/shared_task_members_repository.h"
#include "../common/repositories/shared_task_activity_state_repository.h"
#include "../common/repositories/completed_shared_tasks_repository.h"
#include "../common/repositories/completed_shared_task_members_repository.h"
#include "../common/repositories/completed_shared_task_activity_state_repository.h"
#include "../common/repositories/shared_task_dynamic_zones_repository.h"
#include <array>
#include <ctime>
extern ClientList client_list;
extern ZSList zoneserver_list;
SharedTaskManager *SharedTaskManager::SetDatabase(Database *db)
{
SharedTaskManager::m_database = db;
return this;
}
SharedTaskManager *SharedTaskManager::SetContentDatabase(Database *db)
{
SharedTaskManager::m_content_database = db;
return this;
}
std::vector<SharedTaskMember> SharedTaskManager::GetRequestMembers(
uint32 requestor_character_id,
const std::vector<CharacterDataRepository::CharacterData> &characters
)
{
std::vector<SharedTaskMember> request_members = {};
request_members.reserve(characters.size());
for (const auto &character : characters) {
SharedTaskMember member = {};
member.character_id = character.id;
member.character_name = character.name;
// if the solo/raid/group member is a leader, make sure we tag it as such
if (character.id == requestor_character_id) {
member.is_leader = true;
}
request_members.emplace_back(member);
}
return request_members;
}
void SharedTaskManager::AttemptSharedTaskCreation(
uint32 requested_task_id,
uint32 requested_character_id,
uint32 npc_type_id
)
{
auto task = GetSharedTaskDataByTaskId(requested_task_id);
if (task.id != 0 && task.type == TASK_TYPE_SHARED) {
LogTasksDetail(
"[AttemptSharedTaskCreation] Found Shared Task ({}) [{}]",
requested_task_id,
task.title
);
}
// shared task validation
auto request = SharedTask::GetRequestCharacters(*m_database, requested_character_id);
if (!CanRequestSharedTask(task.id, requested_character_id, request)) {
LogTasksDetail("[AttemptSharedTaskCreation] Shared task validation failed");
return;
}
auto request_members = GetRequestMembers(requested_character_id, request.characters);
if (!request_members.empty()) {
for (auto &m: request_members) {
LogTasksDetail(
"[AttemptSharedTaskCreation] Request Members ({})",
m.character_id
);
}
}
if (request_members.empty()) {
LogTasksDetail("[AttemptSharedTaskCreation] No additional request members found... Just leader");
}
// new shared task instance
auto new_shared_task = SharedTask{};
auto activities = TaskActivitiesRepository::GetWhere(*m_content_database, fmt::format("taskid = {}", task.id));
// new shared task db object
auto shared_task_entity = SharedTasksRepository::NewEntity();
shared_task_entity.task_id = (int) requested_task_id;
shared_task_entity.accepted_time = static_cast<int>(std::time(nullptr));
shared_task_entity.expire_time = task.duration > 0 ? (std::time(nullptr) + task.duration) : 0;
auto created_db_shared_task = SharedTasksRepository::InsertOne(*m_database, shared_task_entity);
// active record
new_shared_task.SetDbSharedTask(created_db_shared_task);
// request timer lockouts
std::vector<CharacterTaskTimersRepository::CharacterTaskTimers> task_timers;
task_timers.reserve(request_members.size());
// persist members
std::vector<SharedTaskMembersRepository::SharedTaskMembers> shared_task_db_members = {};
shared_task_db_members.reserve(request_members.size());
for (auto &m: request_members) {
auto e = SharedTaskMembersRepository::NewEntity();
e.character_id = m.character_id;
e.is_leader = (m.is_leader ? 1 : 0);
e.shared_task_id = new_shared_task.GetDbSharedTask().id;
shared_task_db_members.emplace_back(e);
new_shared_task.AddCharacterToMemberHistory(m.character_id); // memory member history
if (task.request_timer_seconds > 0) {
auto timer = CharacterTaskTimersRepository::NewEntity();
timer.character_id = m.character_id;
timer.task_id = task.id;
timer.timer_type = static_cast<int>(TaskTimerType::Request);
timer.timer_group = task.request_timer_group;
timer.expire_time = shared_task_entity.accepted_time + task.request_timer_seconds;
task_timers.emplace_back(timer);
}
}
SharedTaskMembersRepository::InsertMany(*m_database, shared_task_db_members);
if (!task_timers.empty()) {
CharacterTaskTimersRepository::InsertMany(*m_database, task_timers);
}
// activity state (memory)
std::vector<SharedTaskActivityStateEntry> shared_task_activity_state = {};
shared_task_activity_state.reserve(activities.size());
for (auto &a: activities) {
// entry
auto e = SharedTaskActivityStateEntry{};
e.activity_id = a.activityid;
e.done_count = 0;
e.max_done_count = a.goalcount;
e.step = a.step;
e.optional = a.optional;
shared_task_activity_state.emplace_back(e);
}
// activity state (database)
std::vector<SharedTaskActivityStateRepository::SharedTaskActivityState> shared_task_db_activities = {};
shared_task_db_activities.reserve(activities.size());
for (auto &a: activities) {
// entry
auto e = SharedTaskActivityStateRepository::NewEntity();
e.shared_task_id = new_shared_task.GetDbSharedTask().id;
e.activity_id = a.activityid;
e.done_count = 0;
shared_task_db_activities.emplace_back(e);
}
SharedTaskActivityStateRepository::InsertMany(*m_database, shared_task_db_activities);
// state
new_shared_task.SetSharedTaskActivityState(shared_task_activity_state);
// set database data in memory to make it easier for any later referencing
new_shared_task.SetTaskData(task);
new_shared_task.SetTaskActivityData(activities);
new_shared_task.SetMembers(request_members);
// add to shared tasks list
auto& inserted = m_shared_tasks.emplace_back(new_shared_task);
// send accept to members
for (auto &m: request_members) {
// only requester (leader) receives back the npc context to trigger task accept event
uint32_t npc_context_id = m.character_id == requested_character_id ? npc_type_id : 0;
SendAcceptNewSharedTaskPacket(
m.character_id,
requested_task_id,
npc_context_id,
shared_task_entity.accepted_time
);
}
SendSharedTaskMemberListToAllMembers(&new_shared_task);
// check if task should immediately lock
HandleCompletedActivities(&inserted);
LogTasks(
"[AttemptSharedTaskCreation] shared_task_id [{}] created successfully | task_id [{}] member_count [{}] activity_count [{}] current tasks in state [{}]",
new_shared_task.GetDbSharedTask().id,
task.id,
request_members.size(),
shared_task_activity_state.size(),
m_shared_tasks.size()
);
}
void SharedTaskManager::AttemptSharedTaskRemoval(
uint32 requested_task_id,
uint32 requested_character_id,
bool remove_from_db // inherited from zone logic - we're just passing through
)
{
auto task = GetSharedTaskDataByTaskId(requested_task_id);
if (task.id != 0 && task.type == TASK_TYPE_SHARED) {
LogTasksDetail(
"[AttemptSharedTaskRemoval] Found Shared Task data ({}) [{}]",
requested_task_id,
task.title
);
}
auto t = FindSharedTaskByTaskIdAndCharacterId(requested_task_id, requested_character_id);
if (t) {
auto removed = t->FindMemberFromCharacterID(requested_character_id);
// remove self
RemovePlayerFromSharedTask(t, requested_character_id);
SendRemovePlayerFromSharedTaskPacket(
requested_character_id,
requested_task_id,
remove_from_db
);
// inform clients of removal of self
SendSharedTaskMemberRemovedToAllMembers(t, removed.character_name);
client_list.SendCharacterMessageID(
requested_character_id, Chat::Yellow,
SharedTaskMessage::PLAYER_HAS_BEEN_REMOVED, {removed.character_name, task.title}
);
if (removed.is_leader) {
ChooseNewLeader(t);
}
}
}
void SharedTaskManager::RemoveEveryoneFromSharedTask(SharedTask *t, uint32 requested_character_id)
{
// caller validates leader
LogTasksDetail("[RemoveEveryoneFromSharedTask] Leader [{}]", requested_character_id);
// inform clients of removal
for (auto &m: t->GetMembers()) {
LogTasksDetail(
"[RemoveEveryoneFromSharedTask] Sending removal to [{}] task_id [{}]",
m.character_id,
t->GetTaskData().id
);
SendRemovePlayerFromSharedTaskPacket(m.character_id, t->GetTaskData().id, true);
client_list.SendCharacterMessageID(
m.character_id, Chat::Yellow,
SharedTaskMessage::YOU_HAVE_BEEN_REMOVED, {t->GetTaskData().title}
);
}
client_list.SendCharacterMessageID(
requested_character_id,
Chat::Red,
SharedTaskMessage::PLAYER_HAS_BEEN_REMOVED,
{"Everyone", t->GetTaskData().title}
);
RemoveAllMembersFromDynamicZones(t);
// persistence
DeleteSharedTask(t->GetDbSharedTask().id);
PrintSharedTaskState();
}
void SharedTaskManager::DeleteSharedTask(int64 shared_task_id)
{
LogTasksDetail(
"[DeleteSharedTask] shared_task_id [{}]",
shared_task_id
);
// remove internally
m_shared_tasks.erase(
std::remove_if(
m_shared_tasks.begin(),
m_shared_tasks.end(),
[&](SharedTask const &s) {
return s.GetDbSharedTask().id == shared_task_id;
}
),
m_shared_tasks.end()
);
// database
SharedTasksRepository::DeleteWhere(*m_database, fmt::format("id = {}", shared_task_id));
SharedTaskMembersRepository::DeleteWhere(*m_database, fmt::format("shared_task_id = {}", shared_task_id));
SharedTaskActivityStateRepository::DeleteWhere(*m_database, fmt::format("shared_task_id = {}", shared_task_id));
SharedTaskDynamicZonesRepository::DeleteWhere(*m_database, fmt::format("shared_task_id = {}", shared_task_id));
}
void SharedTaskManager::LoadSharedTaskState()
{
LogTasksDetail("[LoadSharedTaskState] Restoring state from the database");
// load shared tasks
std::vector<SharedTask> shared_tasks = {};
// eager load all activity state data
auto shared_tasks_activity_state_data = SharedTaskActivityStateRepository::All(*m_database);
// eager load all member state data
auto shared_task_members_data = SharedTaskMembersRepository::All(*m_database);
// load character data for member names
std::vector<CharacterDataRepository::CharacterData> shared_task_character_data;
if (!shared_task_members_data.empty()) {
std::vector<uint32_t> character_ids;
for (const auto &m: shared_task_members_data) {
character_ids.emplace_back(m.character_id);
}
shared_task_character_data = CharacterDataRepository::GetWhere(
*m_database,
fmt::format("id IN ({})", fmt::join(character_ids, ","))
);
}
auto shared_task_dynamic_zones_data = SharedTaskDynamicZonesRepository::All(*m_database);
// load shared tasks not already completed
auto st = SharedTasksRepository::GetWhere(*m_database, "TRUE");
shared_tasks.reserve(st.size());
for (auto &s: st) {
SharedTask ns = {};
LogTasksDetail(
"[LoadSharedTaskState] Loading shared_task_id [{}] task_id [{}]",
s.id,
s.task_id
);
// shared task db data
ns.SetDbSharedTask(s);
// set database task data for internal referencing
auto task_data = GetSharedTaskDataByTaskId(s.task_id);
LogTasksDetail("[LoadSharedTaskState] [GetSharedTaskDataByTaskId] task_id [{}]", task_data.id);
ns.SetTaskData(task_data);
// set database task data for internal referencing
auto activities_data = GetSharedTaskActivityDataByTaskId(s.task_id);
ns.SetTaskActivityData(activities_data);
// load activity state into memory
std::vector<SharedTaskActivityStateEntry> shared_task_activity_state = {};
// loop through shared task activity state data referencing from memory instead of
// querying inside this loop each time
for (auto &sta: shared_tasks_activity_state_data) {
// filter by current shared task id
if (sta.shared_task_id == s.id) {
auto e = SharedTaskActivityStateEntry{};
e.activity_id = sta.activity_id;
e.done_count = sta.done_count;
// get max done count from activities data
// loop through activities data in memory and grep on task_id, activity_id to pull goalcount
for (auto &ad: activities_data) {
if (ad.taskid == s.task_id && ad.activityid == sta.activity_id) {
LogTasksDetail(
"[LoadSharedTaskState] shared_task_id [{}] task_id [{}] activity_id [{}] done_count [{}] max_done_count (goalcount) [{}]",
s.id,
s.task_id,
sta.activity_id,
e.done_count,
ad.goalcount
);
e.max_done_count = ad.goalcount;
e.completed_time = sta.completed_time;
e.updated_time = sta.updated_time;
e.step = ad.step;
e.optional = ad.optional;
}
}
shared_task_activity_state.emplace_back(e);
}
}
ns.SetSharedTaskActivityState(shared_task_activity_state);
// members
std::vector<SharedTaskMember> shared_task_members = {};
for (auto &m: shared_task_members_data) {
if (m.shared_task_id == s.id) {
SharedTaskMember member = {};
member.character_id = m.character_id;
member.is_leader = (m.is_leader ? 1 : 0);
auto it = std::find_if(
shared_task_character_data.begin(), shared_task_character_data.end(),
[&](const CharacterDataRepository::CharacterData &character) {
return character.id == m.character_id;
}
);
if (it != shared_task_character_data.end()) {
member.character_name = it->name;
}
shared_task_members.emplace_back(member);
LogTasksDetail(
"[LoadSharedTaskState] shared_task_id [{}] adding member character_id [{}] character_name [{}] is_leader [{}]",
s.id,
member.character_id,
member.character_name,
member.is_leader
);
// add member to history (if restoring state from a world restart we lost real past member history)
ns.AddCharacterToMemberHistory(m.character_id);
}
}
ns.SetMembers(shared_task_members);
// dynamic zones
for (const auto &dz_entry : shared_task_dynamic_zones_data) {
if (dz_entry.shared_task_id == s.id) {
ns.dynamic_zone_ids.emplace_back(static_cast<uint32_t>(dz_entry.dynamic_zone_id));
LogTasksDetail(
"[LoadSharedTaskState] shared_task_id [{}] adding dynamic_zone_id [{}]",
s.id,
dz_entry.dynamic_zone_id
);
}
}
LogTasks(
"[LoadSharedTaskState] Loaded shared task state | shared_task_id [{}] task_id [{}] task_title [{}] member_count [{}] state_activity_count [{}]",
s.id,
task_data.id,
task_data.title,
ns.GetMembers().size(),
ns.GetActivityState().size()
);
shared_tasks.emplace_back(ns);
}
SetSharedTasks(shared_tasks);
LogTasks(
"[LoadSharedTaskState] Loaded [{}] shared tasks",
m_shared_tasks.size()
);
PrintSharedTaskState();
}
SharedTaskManager *SharedTaskManager::LoadTaskData()
{
m_task_data = TasksRepository::All(*m_content_database);
m_task_activity_data = TaskActivitiesRepository::All(*m_content_database);
LogTasks("[LoadTaskData] Loaded tasks [{}] activities [{}]", m_task_data.size(), m_task_activity_data.size());
return this;
}
TasksRepository::Tasks SharedTaskManager::GetSharedTaskDataByTaskId(uint32 task_id)
{
for (auto &t: m_task_data) {
if (t.id == task_id && t.type == TASK_TYPE_SHARED) {
return t;
}
}
return TasksRepository::NewEntity();
}
std::vector<TaskActivitiesRepository::TaskActivities>
SharedTaskManager::GetSharedTaskActivityDataByTaskId(uint32 task_id)
{
std::vector<TaskActivitiesRepository::TaskActivities> activities = {};
for (auto &a: m_task_activity_data) {
if (a.taskid == task_id) {
activities.emplace_back(a);
}
}
return activities;
}
void SharedTaskManager::SharedTaskActivityUpdate(
uint32 source_character_id,
uint32 task_id,
uint32 activity_id,
uint32 done_count,
bool ignore_quest_update
)
{
auto shared_task = FindSharedTaskByTaskIdAndCharacterId(task_id, source_character_id);
if (shared_task) {
LogTasksDetail(
"[SharedTaskActivityUpdate] shared_task_id [{}] character_id [{}] task_id [{}] activity_id [{}] done_count [{}]",
shared_task->GetDbSharedTask().id,
source_character_id,
task_id,
activity_id,
done_count
);
for (auto &a : shared_task->m_shared_task_activity_state) {
if (a.activity_id == activity_id) {
// discard updates out of bounds
if (a.done_count == a.max_done_count) {
LogTasksDetail(
"[SharedTaskActivityUpdate] done_count [{}] is greater than max [{}] discarding...",
done_count,
a.max_done_count
);
return;
}
// if we are progressing
if (a.done_count < done_count) {
LogTasksDetail(
"[SharedTaskActivityUpdate] Propagating update for shared_task_id [{}] character_id [{}] task_id [{}] activity_id [{}] old_done_count [{}] new_done_count [{}]",
shared_task->GetDbSharedTask().id,
source_character_id,
task_id,
activity_id,
a.done_count,
done_count
);
a.done_count = done_count;
a.updated_time = std::time(nullptr);
// if the update came in larger than the max for whatever reason, clamp
if (a.done_count > a.max_done_count) {
a.done_count = a.max_done_count;
}
// if the activity is done, lets mark it as such
if (a.done_count == a.max_done_count) {
a.completed_time = std::time(nullptr);
}
// sync state as each update comes in (for now)
SaveSharedTaskActivityState(
shared_task->GetDbSharedTask().id,
shared_task->m_shared_task_activity_state
);
shared_task->SetSharedTaskActivityState(shared_task->m_shared_task_activity_state);
LogTasksDetail(
"[SharedTaskActivityUpdate] Debug done_count [{}]",
a.done_count
);
// loop through members - send update
for (auto &m: shared_task->GetMembers()) {
// confirm task update to client(s)
auto p = std::make_unique<ServerPacket>(
ServerOP_SharedTaskUpdate,
sizeof(ServerSharedTaskActivityUpdate_Struct)
);
auto d = reinterpret_cast<ServerSharedTaskActivityUpdate_Struct *>(p->pBuffer);
d->source_character_id = m.character_id;
d->task_id = task_id;
d->activity_id = activity_id;
d->done_count = done_count;
d->ignore_quest_update = ignore_quest_update;
// get requested character zone server
ClientListEntry *c = client_list.FindCLEByCharacterID(m.character_id);
if (c && c->Server()) {
c->Server()->SendPacket(p.get());
}
}
break;
}
LogTasksDetail(
"[SharedTaskActivityUpdate] Discarding duplicate update for shared_task_id [{}] character_id [{}] task_id [{}] activity_id [{}] done_count [{}] ignore_quest_update [{}]",
shared_task->GetDbSharedTask().id,
source_character_id,
task_id,
activity_id,
done_count,
(ignore_quest_update ? "true" : "false")
);
}
}
// handle task locking and completion
bool is_shared_task_completed = HandleCompletedActivities(shared_task);
if (is_shared_task_completed) {
HandleCompletedTask(shared_task);
}
}
}
SharedTask *SharedTaskManager::FindSharedTaskByTaskIdAndCharacterId(uint32 task_id, uint32 character_id)
{
for (auto &s: m_shared_tasks) {
// grep for task
if (s.GetTaskData().id == task_id) {
// find member in shared task
for (auto &m: s.GetMembers()) {
if (m.character_id == character_id) {
return &s;
}
}
}
}
return nullptr;
}
void SharedTaskManager::SaveSharedTaskActivityState(
int64 shared_task_id,
std::vector<SharedTaskActivityStateEntry> activity_state
)
{
// transfer from memory to database
std::vector<SharedTaskActivityStateRepository::SharedTaskActivityState> shared_task_db_activities = {};
shared_task_db_activities.reserve(activity_state.size());
for (auto &a: activity_state) {
// entry
auto e = SharedTaskActivityStateRepository::NewEntity();
e.shared_task_id = shared_task_id;
e.activity_id = (int) a.activity_id;
e.done_count = (int) a.done_count;
e.completed_time = (int) a.completed_time;
e.updated_time = (int) a.updated_time;
shared_task_db_activities.emplace_back(e);
}
SharedTaskActivityStateRepository::DeleteWhere(*m_database, fmt::format("shared_task_id = {}", shared_task_id));
SharedTaskActivityStateRepository::InsertMany(*m_database, shared_task_db_activities);
}
bool SharedTaskManager::IsSharedTaskLeader(SharedTask *s, uint32 character_id)
{
for (auto &m: s->GetMembers()) {
if (m.character_id == character_id && m.is_leader) {
return true;
}
}
return false;
}
void SharedTaskManager::SendAcceptNewSharedTaskPacket(
uint32 character_id,
uint32 task_id,
uint32_t npc_context_id,
int accept_time
)
{
auto p = std::make_unique<ServerPacket>(
ServerOP_SharedTaskAcceptNewTask,
sizeof(ServerSharedTaskRequest_Struct)
);
auto d = reinterpret_cast<ServerSharedTaskRequest_Struct *>(p->pBuffer);
d->requested_character_id = character_id;
d->requested_task_id = task_id;
d->requested_npc_type_id = npc_context_id;
d->accept_time = accept_time;
// get requested character zone server
ClientListEntry *cle = client_list.FindCLEByCharacterID(character_id);
if (cle && cle->Server()) {
cle->Server()->SendPacket(p.get());
}
}
void SharedTaskManager::SendRemovePlayerFromSharedTaskPacket(
uint32 character_id,
uint32 task_id,
bool remove_from_db
)
{
// confirm shared task request: inform clients
auto p = std::make_unique<ServerPacket>(
ServerOP_SharedTaskAttemptRemove,
sizeof(ServerSharedTaskAttemptRemove_Struct)
);
auto d = reinterpret_cast<ServerSharedTaskAttemptRemove_Struct *>(p->pBuffer);
d->requested_character_id = character_id;
d->requested_task_id = task_id;
d->remove_from_db = remove_from_db;
// get requested character zone server
ClientListEntry *cle = client_list.FindCLEByCharacterID(character_id);
if (cle && cle->Server()) {
cle->Server()->SendPacket(p.get());
}
}
void SharedTaskManager::SendSharedTaskMemberList(uint32 character_id, const std::vector<SharedTaskMember> &members)
{
EQ::Net::DynamicPacket dyn_pack;
dyn_pack.PutSerialize(0, members);
SendSharedTaskMemberList(character_id, dyn_pack);
}
void SharedTaskManager::SendSharedTaskMemberList(uint32 character_id, const EQ::Net::DynamicPacket &serialized_members)
{
// send member list packet
auto p = std::make_unique<ServerPacket>(
ServerOP_SharedTaskMemberlist,
sizeof(ServerSharedTaskMemberListPacket_Struct) + serialized_members.Length()
);
auto d = reinterpret_cast<ServerSharedTaskMemberListPacket_Struct *>(p->pBuffer);
d->destination_character_id = character_id;
d->cereal_size = static_cast<uint32_t>(serialized_members.Length());
memcpy(d->cereal_serialized_members, serialized_members.Data(), serialized_members.Length());
// send memberlist
ClientListEntry *cle = client_list.FindCLEByCharacterID(character_id);
if (cle && cle->Server()) {
cle->Server()->SendPacket(p.get());
}
}
void SharedTaskManager::SendSharedTaskMemberChange(
uint32 character_id,
int64 shared_task_id,
const std::string &player_name,
bool removed
)
{
uint32_t size = sizeof(ServerSharedTaskMemberChangePacket_Struct);
auto p = std::make_unique<ServerPacket>(ServerOP_SharedTaskMemberChange, size);
auto d = reinterpret_cast<ServerSharedTaskMemberChangePacket_Struct *>(p->pBuffer);
d->destination_character_id = character_id;
d->shared_task_id = shared_task_id;
d->removed = removed;
strn0cpy(d->player_name, player_name.c_str(), sizeof(d->player_name));
ClientListEntry *cle = client_list.FindCLEByCharacterID(character_id);
if (cle && cle->Server()) {
cle->Server()->SendPacket(p.get());
}
}
void SharedTaskManager::RemovePlayerFromSharedTask(SharedTask *s, uint32 character_id)
{
SharedTaskMembersRepository::DeleteWhere(
*m_database,
fmt::format(
"shared_task_id = {} and character_id = {}",
s->GetDbSharedTask().id,
character_id
)
);
// remove internally
s->m_members.erase(
std::remove_if(
s->m_members.begin(),
s->m_members.end(),
[&](SharedTaskMember const &m) {
return m.character_id == character_id;
}
),
s->m_members.end()
);
for (const auto &dz_id : s->dynamic_zone_ids) {
auto dz = DynamicZone::FindDynamicZoneByID(dz_id);
if (dz) {
dz->RemoveMember(character_id);
}
}
}
void SharedTaskManager::PrintSharedTaskState()
{
for (auto &s: m_shared_tasks) {
auto task = GetSharedTaskDataByTaskId(s.GetDbSharedTask().task_id);
LogTasksDetail("[PrintSharedTaskState] # Shared Task");
LogTasksDetail(
"[PrintSharedTaskState] shared_task_id [{}] task_id [{}] task_title [{}] member_count [{}] state_activity_count [{}]",
s.GetDbSharedTask().id,
task.id,
task.title,
s.GetMembers().size(),
s.GetActivityState().size()
);
LogTasksDetail("[PrintSharedTaskState] # Activities");
// activity state
for (auto &a: s.m_shared_task_activity_state) {
LogTasksDetail(
"[PrintSharedTaskState] -- activity_id [{}] done_count [{}] max_done_count [{}] completed_time [{}]",
a.activity_id,
a.done_count,
a.max_done_count,
a.completed_time
);
}
LogTasksDetail("[PrintSharedTaskState] # Members");
// members
for (auto &m: s.m_members) {
LogTasksDetail(
"[PrintSharedTaskState] -- character_id [{}] is_leader [{}]",
m.character_id,
m.is_leader
);
}
LogTasksDetail("[PrintSharedTaskState] # Dynamic Zones");
for (auto &dz_id: s.dynamic_zone_ids) {
LogTasksDetail(
"[PrintSharedTaskState] -- dynamic_zone_id [{}]",
dz_id
);
}
}
}
void SharedTaskManager::RemovePlayerFromSharedTaskByPlayerName(SharedTask *s, const std::string &character_name)
{
auto member = s->FindMemberFromCharacterName(character_name);
if (member.character_id == 0) {
SendLeaderMessageID(s, Chat::Red, SharedTaskMessage::IS_NOT_MEMBER, {character_name});
return;
}
LogTasksDetail(
"[RemovePlayerFromSharedTaskByPlayerName] shared_task_id [{}] character_name [{}]",
s->GetDbSharedTask().id,
character_name
);
auto leader = s->GetLeader(); // get leader now for msg in case leader is one removed
RemovePlayerFromSharedTask(s, member.character_id);
SendRemovePlayerFromSharedTaskPacket(
member.character_id,
s->GetDbSharedTask().task_id,
true
);
SendSharedTaskMemberRemovedToAllMembers(s, member.character_name);
// leader and removed player get server messages (leader sees two messages)
// results in double messages if leader removed self (live behavior)
client_list.SendCharacterMessageID(
leader.character_id, Chat::Yellow,
SharedTaskMessage::PLAYER_HAS_BEEN_REMOVED, {member.character_name, s->GetTaskData().title}
);
client_list.SendCharacterMessageID(
member.character_id, Chat::Yellow,
SharedTaskMessage::PLAYER_HAS_BEEN_REMOVED, {member.character_name, s->GetTaskData().title}
);
if (member.is_leader) {
ChooseNewLeader(s);
}
}
void SharedTaskManager::SendSharedTaskMemberListToAllMembers(SharedTask *s)
{
// serialize once so we don't re-serialize it for every member
EQ::Net::DynamicPacket dyn_pack;
dyn_pack.PutSerialize(0, s->GetMembers());
for (auto &m: s->GetMembers()) {
SendSharedTaskMemberList(
m.character_id,
dyn_pack
);
}
}
void SharedTaskManager::SendSharedTaskMemberAddedToAllMembers(SharedTask *s, const std::string &player_name)
{
for (const auto &m : s->GetMembers()) {
SendSharedTaskMemberChange(m.character_id, s->GetDbSharedTask().id, player_name, false);
}
}
void SharedTaskManager::SendSharedTaskMemberRemovedToAllMembers(SharedTask *s, const std::string &player_name)
{
for (const auto &m : s->GetMembers()) {
SendSharedTaskMemberChange(m.character_id, s->GetDbSharedTask().id, player_name, true);
}
}
void SharedTaskManager::MakeLeaderByPlayerName(SharedTask *s, const std::string &character_name)
{
auto new_leader = s->FindMemberFromCharacterName(character_name);
if (new_leader.character_id == 0) {
SendLeaderMessageID(s, Chat::Red, SharedTaskMessage::IS_NOT_MEMBER, {character_name});
return;
}
bool found_new_leader = false;
std::vector<SharedTaskMember> members = s->GetMembers();
for (auto &m: members) {
LogTasksDetail(
"[MakeLeaderByPlayerName] character_id [{}] m.character_id [{}]",
new_leader.character_id,
m.character_id
);
m.is_leader = false;
// destination character is in shared task, make swap
if (new_leader.character_id == m.character_id) {
found_new_leader = true;
LogTasksDetail(
"[MakeLeaderByPlayerName] shared_task_id [{}] character_name [{}]",
s->GetDbSharedTask().id,
character_name
);
m.is_leader = true;
}
}
if (found_new_leader) {
s->SetMembers(members);
SaveMembers(s, members);
SendSharedTaskMemberListToAllMembers(s);
SendMembersMessageID(
s, Chat::Yellow, SharedTaskMessage::PLAYER_NOW_LEADER,
{new_leader.character_name, s->GetTaskData().title}
);
for (const auto &dz_id : s->dynamic_zone_ids) {
auto dz = DynamicZone::FindDynamicZoneByID(dz_id);
if (dz) {
dz->SetNewLeader(static_cast<uint32_t>(new_leader.character_id));
}
}
}
}
void SharedTaskManager::SaveMembers(SharedTask *s, std::vector<SharedTaskMember> members)
{
std::vector<SharedTaskMembersRepository::SharedTaskMembers> dm = {};
dm.reserve(members.size());
for (auto &m: members) {
auto e = SharedTaskMembersRepository::NewEntity();
e.character_id = m.character_id;
e.is_leader = (m.is_leader ? 1 : 0);
e.shared_task_id = s->GetDbSharedTask().id;
dm.emplace_back(e);
}
SharedTaskMembersRepository::DeleteWhere(*m_database, fmt::format("shared_task_id = {}", s->GetDbSharedTask().id));
SharedTaskMembersRepository::InsertMany(*m_database, dm);
}
void SharedTaskManager::InvitePlayerByPlayerName(SharedTask *s, const std::string &player_name)
{
auto character = CharacterDataRepository::GetWhere(
*m_database,
fmt::format("`name` = '{}' LIMIT 1", Strings::Escape(player_name))
);
auto character_id = !character.empty() ? character.front().id : 0;
// we call validation even for an invalid player so error messages occur
if (CanAddPlayer(s, character_id, player_name, false)) {
// send dialogue window
SendSharedTaskInvitePacket(s, character_id);
// keep track of active invitations at world
QueueActiveInvitation(s->GetDbSharedTask().id, character_id);
}
}
void SharedTaskManager::SendSharedTaskInvitePacket(SharedTask *s, int64 invited_character_id)
{
auto leader = s->GetLeader();
// found leader
if (leader.character_id > 0) {
// init packet
auto p = std::make_unique<ServerPacket>(
ServerOP_SharedTaskInvitePlayer,
sizeof(ServerSharedTaskInvitePlayer_Struct)
);
// fill
auto d = reinterpret_cast<ServerSharedTaskInvitePlayer_Struct *>(p->pBuffer);
d->requested_character_id = invited_character_id;
d->invite_shared_task_id = s->GetDbSharedTask().id;
strn0cpy(d->inviter_name, leader.character_name.c_str(), sizeof(d->inviter_name));
strn0cpy(d->task_name, s->GetTaskData().title.c_str(), sizeof(d->task_name));
// get requested character zone server
ClientListEntry *cle = client_list.FindCLEByCharacterID(invited_character_id);
if (cle && cle->Server()) {
SendLeaderMessageID(s, Chat::Yellow, SharedTaskMessage::SEND_INVITE_TO, {cle->name()});
cle->Server()->SendPacket(p.get());
}
}
}
void SharedTaskManager::AddPlayerByCharacterIdAndName(
SharedTask *s,
int64 character_id,
const std::string &character_name
)
{
// fetch
std::vector<SharedTaskMember> members = s->GetMembers();
// create
auto new_member = SharedTaskMember{};
new_member.character_id = character_id;
new_member.character_name = character_name;
bool does_member_exist = false;
for (auto &m: s->GetMembers()) {
if (m.character_id == character_id) {
does_member_exist = true;
}
}
if (!does_member_exist && CanAddPlayer(s, character_id, character_name, true)) {
members.push_back(new_member);
// add request timer (validation will prevent non-expired duplicates)
if (s->GetTaskData().request_timer_seconds > 0) {
auto expire_time = s->GetDbSharedTask().accepted_time + s->GetTaskData().request_timer_seconds;
if (expire_time > std::time(nullptr)) // not already expired
{
auto timer = CharacterTaskTimersRepository::NewEntity();
timer.character_id = character_id;
timer.task_id = s->GetDbSharedTask().task_id;
timer.timer_type = static_cast<int>(TaskTimerType::Request);
timer.timer_group = s->GetTaskData().request_timer_group;
timer.expire_time = expire_time;
CharacterTaskTimersRepository::InsertOne(*m_database, timer);
}
}
// inform client
SendAcceptNewSharedTaskPacket(character_id, s->GetTaskData().id, 0, s->GetDbSharedTask().accepted_time);
// add to shared task
SendSharedTaskMemberAddedToAllMembers(s, character_name);
s->SetMembers(members);
SaveMembers(s, members);
SendSharedTaskMemberList(character_id, s->GetMembers()); // new member gets full member list
s->AddCharacterToMemberHistory(character_id);
// add to dzs tied to shared task
for (const auto &dz_id : s->dynamic_zone_ids) {
auto dz = DynamicZone::FindDynamicZoneByID(dz_id);
if (dz) {
auto status = DynamicZoneMemberStatus::Online;
dz->AddMember({static_cast<uint32_t>(character_id), character_name, status});
}
}
}
}
SharedTask *SharedTaskManager::FindSharedTaskById(int64 shared_task_id)
{
for (auto &s: m_shared_tasks) {
if (s.GetDbSharedTask().id == shared_task_id) {
return &s;
}
}
return nullptr;
}
void SharedTaskManager::QueueActiveInvitation(int64 shared_task_id, int64 character_id)
{
LogTasksDetail(
"[QueueActiveInvitation] shared_task_id [{}] character_id [{}]",
shared_task_id,
character_id
);
auto active_invitation = SharedTaskActiveInvitation{};
active_invitation.shared_task_id = shared_task_id;
active_invitation.character_id = character_id;
m_active_invitations.emplace_back(active_invitation);
}
bool SharedTaskManager::IsInvitationActive(uint32 shared_task_id, uint32 character_id)
{
LogTasksDetail(
"[IsInvitationActive] shared_task_id [{}] character_id [{}]",
shared_task_id,
character_id
);
for (auto &i: m_active_invitations) {
if (i.character_id == character_id && i.shared_task_id == shared_task_id) {
return true;
}
}
return false;
}
void SharedTaskManager::RemoveActiveInvitation(int64 shared_task_id, int64 character_id)
{
LogTasksDetail(
"[RemoveActiveInvitation] shared_task_id [{}] character_id [{}] pre_removal_count [{}]",
shared_task_id,
character_id,
m_active_invitations.size()
);
// remove internally
m_active_invitations.erase(
std::remove_if(
m_active_invitations.begin(),
m_active_invitations.end(),
[&](SharedTaskActiveInvitation const &i) {
return i.shared_task_id == shared_task_id && i.character_id == character_id;
}
),
m_active_invitations.end()
);
LogTasksDetail(
"[RemoveActiveInvitation] shared_task_id [{}] character_id [{}] post_removal_count [{}]",
shared_task_id,
character_id,
m_active_invitations.size()
);
}
void SharedTaskManager::RemoveActiveInvitationByCharacterID(uint32_t character_id)
{
m_active_invitations.erase(
std::remove_if(
m_active_invitations.begin(), m_active_invitations.end(),
[&](SharedTaskActiveInvitation const &i) {
return i.character_id == character_id;
}
), m_active_invitations.end()
);
}
void SharedTaskManager::CreateDynamicZone(SharedTask *shared_task, DynamicZone &dz_request)
{
std::vector<DynamicZoneMember> dz_members;
for (const auto &member : shared_task->GetMembers()) {
dz_members.emplace_back(member.character_id, member.character_name);
if (member.is_leader) {
dz_request.SetLeader({member.character_id, member.character_name});
}
}
auto new_dz = dynamic_zone_manager.CreateNew(dz_request, dz_members);
if (new_dz) {
auto shared_task_dz = SharedTaskDynamicZonesRepository::NewEntity();
shared_task_dz.shared_task_id = shared_task->GetDbSharedTask().id;
shared_task_dz.dynamic_zone_id = new_dz->GetID();
SharedTaskDynamicZonesRepository::InsertOne(*m_database, shared_task_dz);
shared_task->dynamic_zone_ids.emplace_back(new_dz->GetID());
}
}
void SharedTaskManager::SendLeaderMessage(SharedTask *shared_task, int chat_type, const std::string &message)
{
if (!shared_task) {
return;
}
for (const auto &member : shared_task->GetMembers()) {
if (member.is_leader) {
client_list.SendCharacterMessage(member.character_id, chat_type, message);
break;
}
}
}
void SharedTaskManager::SendLeaderMessageID(
SharedTask *shared_task, int chat_type,
int eqstr_id, std::initializer_list<std::string> args
)
{
if (!shared_task) {
return;
}
for (const auto &member : shared_task->GetMembers()) {
if (member.is_leader) {
client_list.SendCharacterMessageID(member.character_id, chat_type, eqstr_id, args);
break;
}
}
}
void SharedTaskManager::SendMembersMessage(SharedTask *shared_task, int chat_type, const std::string &message)
{
if (!shared_task) {
return;
}
for (const auto &member : shared_task->GetMembers()) {
client_list.SendCharacterMessage(member.character_id, chat_type, message);
}
}
void SharedTaskManager::SendMembersMessageID(
SharedTask *shared_task,
int chat_type,
int eqstr_id,
std::initializer_list<std::string> args
)
{
if (!shared_task || shared_task->GetMembers().empty()) {
return;
}
// serialize here since using client_list methods would re-serialize for every member
SerializeBuffer serialized_args;
for (const auto &arg : args) {
serialized_args.WriteString(arg);
}
uint32_t args_size = static_cast<uint32_t>(serialized_args.size());
uint32_t pack_size = sizeof(CZClientMessageString_Struct) + args_size;
auto pack = std::make_unique<ServerPacket>(ServerOP_CZClientMessageString, pack_size);
auto buf = reinterpret_cast<CZClientMessageString_Struct *>(pack->pBuffer);
buf->string_id = eqstr_id;
buf->chat_type = chat_type;
buf->args_size = args_size;
memcpy(buf->args, serialized_args.buffer(), serialized_args.size());
for (const auto &member : shared_task->GetMembers()) {
auto character = client_list.FindCLEByCharacterID(member.character_id);
if (character && character->Server()) {
strn0cpy(buf->client_name, character->name(), sizeof(buf->client_name));
character->Server()->SendPacket(pack.get());
}
}
}
std::vector<CharacterTaskTimersRepository::CharacterTaskTimers> SharedTaskManager::GetCharacterTimers(
const std::vector<uint32_t>& character_ids, const TasksRepository::Tasks& task)
{
// todo: consider caching character timers in world and zone to avoid queries
auto task_timers = CharacterTaskTimersRepository::GetWhere(*m_database, fmt::format(
SQL(
character_id IN ({})
AND (task_id = {}
OR (timer_group > 0 AND timer_type = {} AND timer_group = {})
OR (timer_group > 0 AND timer_type = {} AND timer_group = {}))
AND expire_time > NOW() ORDER BY timer_type ASC LIMIT 1
),
fmt::join(character_ids, ","),
task.id,
static_cast<int>(TaskTimerType::Replay),
task.replay_timer_group,
static_cast<int>(TaskTimerType::Request),
task.request_timer_group
));
return task_timers;
}
bool SharedTaskManager::CanRequestSharedTask(
uint32_t task_id,
uint32_t character_id,
const SharedTaskRequestCharacters &request
)
{
auto task = GetSharedTaskDataByTaskId(task_id);
if (task.id == 0) {
return false;
}
// this attempts to follow live validation order
// check if any party members are already in a shared task
auto shared_task_members = FindCharactersInSharedTasks(request.character_ids);
if (!shared_task_members.empty()) {
// messages for every character already in a shared task
for (const auto &member : shared_task_members) {
auto it = std::find_if(
request.characters.begin(), request.characters.end(),
[&](const CharacterDataRepository::CharacterData &char_data) {
return char_data.id == member;
}
);
if (it != request.characters.end()) {
if (it->id == character_id) {
client_list.SendCharacterMessageID(
character_id,
Chat::Red,
SharedTaskMessage::NO_REQUEST_BECAUSE_HAVE_ONE
);
}
else if (request.group_type == SharedTaskRequestGroupType::Group) {
client_list.SendCharacterMessageID(
character_id,
Chat::Red,
SharedTaskMessage::NO_REQUEST_BECAUSE_GROUP_HAS_ONE,
{it->name}
);
}
else {
client_list.SendCharacterMessageID(
character_id,
Chat::Red,
SharedTaskMessage::NO_REQUEST_BECAUSE_RAID_HAS_ONE,
{it->name}
);
}
}
}
return false;
}
// check if any party member's minimum level is too low (pre-2014 this was average level)
if (task.minlevel > 0 && request.lowest_level < task.minlevel) {
client_list.SendCharacterMessageID(character_id, Chat::Red, SharedTaskMessage::AVG_LVL_LOW);
return false;
}
// check if any party member's maximum level is too high (pre-2014 this was average level)
if (task.maxlevel > 0 && request.highest_level > task.maxlevel) {
client_list.SendCharacterMessageID(character_id, Chat::Red, SharedTaskMessage::AVG_LVL_HIGH);
return false;
}
// allow gm/dev bypass for minimum player count requirements
auto requester = client_list.FindCLEByCharacterID(character_id);
bool is_gm = (requester && requester->GetGM());
// check if party member count is below the minimum
if (!is_gm && task.min_players > 0 && request.characters.size() < task.min_players) {
client_list.SendCharacterMessageID(
character_id,
Chat::Red,
SharedTaskMessage::SHARED_TASK_NOT_MEET_MIN_NUM_PLAYER
);
return false;
}
// check if party member count is above the maximum
// todo: live creates the shared task but truncates members if it exceeds max (sorted by leader and raid group numbers)
if (task.max_players > 0 && request.characters.size() > task.max_players) {
client_list.SendCharacterMessageID(character_id, Chat::Red, SharedTaskMessage::PARTY_EXCEED_MAX_PLAYER);
return false;
}
// check if party level spread exceeds task's maximum
if (task.level_spread > 0 && (request.highest_level - request.lowest_level) > task.level_spread) {
client_list.SendCharacterMessageID(character_id, Chat::Red, SharedTaskMessage::LVL_SPREAD_HIGH);
return false;
}
// check if any party members have a replay or request timer for the task (limit 1, replay checked first)
auto character_task_timers = GetCharacterTimers(request.character_ids, task);
if (!character_task_timers.empty()) {
auto timer_type = static_cast<TaskTimerType>(character_task_timers.front().timer_type);
auto seconds = character_task_timers.front().expire_time - std::time(nullptr);
auto days = fmt::format_int(seconds / 86400).str();
auto hours = fmt::format_int((seconds / 3600) % 24).str();
auto mins = fmt::format_int((seconds / 60) % 60).str();
if (character_task_timers.front().character_id == character_id) {
if (timer_type == TaskTimerType::Replay) {
client_list.SendCharacterMessageID(
character_id,
Chat::Red,
SharedTaskMessage::YOU_MUST_WAIT_REPLAY_TIMER, {days, hours, mins}
);
}
else if (timer_type == TaskTimerType::Request) {
client_list.SendCharacterMessage(
character_id,
Chat::Red, fmt::format(
SharedTaskMessage::GetEQStr(SharedTaskMessage::YOU_MUST_WAIT_REQUEST_TIMER), days, hours, mins
)
);
}
}
else {
auto it = std::find_if(
request.characters.begin(), request.characters.end(),
[&](const CharacterDataRepository::CharacterData &char_data) {
return char_data.id == character_task_timers.front().character_id;
}
);
if (it != request.characters.end() && timer_type == TaskTimerType::Replay) {
client_list.SendCharacterMessageID(
character_id,
Chat::Red,
SharedTaskMessage::PLAYER_MUST_WAIT_REPLAY_TIMER,
{it->name, days, hours, mins}
);
}
else if (it != request.characters.end() && timer_type == TaskTimerType::Request) {
client_list.SendCharacterMessage(
character_id,
Chat::Red,
fmt::format(
SharedTaskMessage::GetEQStr(SharedTaskMessage::PLAYER_MUST_WAIT_REQUEST_TIMER),
it->name,
days,
hours,
mins
)
);
}
}
return false;
}
return true;
}
bool SharedTaskManager::CanAddPlayer(SharedTask *s, uint32_t character_id, std::string player_name, bool accepted)
{
// this attempts to follow live validation order
bool allow_invite = true;
// check if task is locked
if (s->GetDbSharedTask().is_locked) {
SendLeaderMessageID(s, Chat::Red, SharedTaskMessage::TASK_NOT_ALLOWING_PLAYERS_AT_TIME);
allow_invite = false;
}
// check if player is online and in cle (other checks require online)
auto cle = client_list.FindCLEByCharacterID(character_id);
if (!cle || !cle->Server()) {
SendLeaderMessageID(s, Chat::Red, SharedTaskMessage::PLAYER_NOT_ONLINE_TO_ADD, {player_name});
SendLeaderMessageID(s, Chat::Red, SharedTaskMessage::COULD_NOT_BE_INVITED, {player_name});
return false;
}
player_name = cle->name();
// check if player is already in a shared task
auto shared_task_members = SharedTaskMembersRepository::GetWhere(
*m_database,
fmt::format("character_id = {} LIMIT 1", character_id)
);
if (!shared_task_members.empty()) {
auto shared_task_id = shared_task_members.front().shared_task_id;
if (shared_task_id == s->GetDbSharedTask().id) {
SendLeaderMessageID(s, Chat::Red, SharedTaskMessage::CANT_ADD_PLAYER_ALREADY_MEMBER, {player_name});
}
else {
SendLeaderMessageID(s, Chat::Red, SharedTaskMessage::CANT_ADD_PLAYER_ALREADY_ASSIGNED, {player_name});
}
allow_invite = false;
}
// check if player has an outstanding invite
for (const auto &invite : m_active_invitations) {
if (invite.character_id == character_id) {
if (invite.shared_task_id == s->GetDbSharedTask().id) {
SendLeaderMessageID(
s,
Chat::Red,
SharedTaskMessage::PLAYER_ALREADY_OUTSTANDING_INVITATION_THIS,
{player_name}
);
}
else {
SendLeaderMessageID(s, Chat::Red, SharedTaskMessage::PLAYER_ALREADY_OUTSTANDING_ANOTHER, {player_name});
}
allow_invite = false;
break;
}
}
// check if player has a replay or request timer lockout
// todo: live allows characters with a request timer to be re-invited if they quit, but only until they zone? (investigate/edge case)
auto task_timers = GetCharacterTimers({ character_id }, s->GetTaskData());
if (!task_timers.empty()) {
auto timer_type = static_cast<TaskTimerType>(task_timers.front().timer_type);
auto seconds = task_timers.front().expire_time - std::time(nullptr);
auto days = fmt::format_int(seconds / 86400).str();
auto hours = fmt::format_int((seconds / 3600) % 24).str();
auto mins = fmt::format_int((seconds / 60) % 60).str();
if (timer_type == TaskTimerType::Replay) {
SendLeaderMessageID(
s,
Chat::Red,
SharedTaskMessage::CANT_ADD_PLAYER_REPLAY_TIMER, {player_name, days, hours, mins}
);
}
else {
SendLeaderMessage(
s,
Chat::Red,
fmt::format(
SharedTaskMessage::GetEQStr(SharedTaskMessage::CANT_ADD_PLAYER_REQUEST_TIMER),
player_name,
days,
hours,
mins
)
);
}
allow_invite = false;
}
// check if task has maximum players
if (s->GetTaskData().max_players > 0 && s->GetMembers().size() >= s->GetTaskData().max_players) {
auto max = fmt::format_int(s->GetTaskData().max_players).str();
SendLeaderMessageID(s, Chat::Red, SharedTaskMessage::CANT_ADD_PLAYER_MAX_PLAYERS, {max});
allow_invite = false;
}
// check if task would exceed max level spread
if (s->GetTaskData().level_spread > 0) {
auto characters = CharacterDataRepository::GetWhere(
*m_database,
fmt::format(
"id IN (select character_id from shared_task_members where shared_task_id = {})",
s->GetDbSharedTask().id
)
);
int lowest_level = cle->level();
int highest_level = cle->level();
for (const auto &character : characters) {
lowest_level = std::min(lowest_level, character.level);
highest_level = std::max(highest_level, character.level);
}
if ((highest_level - lowest_level) > s->GetTaskData().level_spread) {
auto max_spread = fmt::format_int(s->GetTaskData().level_spread).str();
SendLeaderMessageID(s, Chat::Red, SharedTaskMessage::CANT_ADD_PLAYER_MAX_LEVEL_SPREAD, {max_spread});
allow_invite = false;
}
}
// check if player is below minimum level of task (pre-2014 this was average level)
if (s->GetTaskData().minlevel > 0 && cle->level() < s->GetTaskData().minlevel) {
SendLeaderMessageID(s, Chat::Red, SharedTaskMessage::CANT_ADD_PLAYER_FALL_MIN_AVG_LEVEL);
allow_invite = false;
}
// check if player is above maximum level of task (pre-2014 this was average level)
if (s->GetTaskData().maxlevel > 0 && cle->level() > s->GetTaskData().maxlevel) {
SendLeaderMessageID(s, Chat::Red, SharedTaskMessage::CANT_ADD_PLAYER_MAX_AVERAGE_LEVEL);
allow_invite = false;
}
if (!allow_invite) {
if (!accepted) {
SendLeaderMessageID(s, Chat::Red, SharedTaskMessage::COULD_NOT_BE_INVITED, {player_name});
}
else {
SendLeaderMessageID(s, Chat::Red, SharedTaskMessage::ACCEPTED_OFFER_TO_JOIN_BUT_COULD_NOT, {player_name});
}
}
return allow_invite;
}
void SharedTaskManager::RecordSharedTaskCompletion(SharedTask *s)
{
// shared task
auto t = s->GetDbSharedTask();
auto ct = CompletedSharedTasksRepository::NewEntity();
ct.id = t.id;
ct.task_id = t.task_id;
ct.accepted_time = t.accepted_time;
ct.expire_time = t.expire_time;
ct.completion_time = t.completion_time;
ct.is_locked = t.is_locked;
CompletedSharedTasksRepository::InsertOne(*m_database, ct);
// completed members
std::vector<CompletedSharedTaskMembersRepository::CompletedSharedTaskMembers> completed_members = {};
for (auto &m: s->GetMembers()) {
auto cm = CompletedSharedTaskMembersRepository::NewEntity();
cm.shared_task_id = t.id;
cm.character_id = m.character_id;
cm.is_leader = m.is_leader;
completed_members.emplace_back(cm);
}
CompletedSharedTaskMembersRepository::InsertMany(*m_database, completed_members);
// activities
std::vector<CompletedSharedTaskActivityStateRepository::CompletedSharedTaskActivityState> completed_states = {};
for (auto &a: s->GetActivityState()) {
auto cs = CompletedSharedTaskActivityStateRepository::NewEntity();
cs.shared_task_id = t.id;
cs.activity_id = (int) a.activity_id;
cs.done_count = (int) a.done_count;
cs.updated_time = a.updated_time;
cs.completed_time = a.completed_time;
completed_states.emplace_back(cs);
}
CompletedSharedTaskActivityStateRepository::InsertMany(*m_database, completed_states);
}
void SharedTaskManager::AddReplayTimers(SharedTask *s)
{
if (s->GetTaskData().replay_timer_seconds > 0) {
auto seconds = s->GetTaskData().replay_timer_seconds;
auto expire_time = std::time(nullptr) + seconds;
std::vector<CharacterTaskTimersRepository::CharacterTaskTimers> task_timers;
// on live past members of the shared task also receive lockouts (use member history)
for (const auto &member_id : s->member_id_history) {
auto timer = CharacterTaskTimersRepository::NewEntity();
timer.character_id = member_id;
timer.task_id = s->GetTaskData().id;
timer.timer_type = static_cast<int>(TaskTimerType::Replay);
timer.timer_group = s->GetTaskData().replay_timer_group;
timer.expire_time = expire_time;
task_timers.emplace_back(timer);
client_list.SendCharacterMessage(
member_id,
Chat::Yellow,
fmt::format(
SharedTaskMessage::GetEQStr(SharedTaskMessage::RECEIVED_REPLAY_TIMER),
s->GetTaskData().title,
fmt::format_int(seconds / 86400).c_str(), // days
fmt::format_int((seconds / 3600) % 24).c_str(), // hours
fmt::format_int((seconds / 60) % 60).c_str() // minutes
)
);
}
if (!task_timers.empty()) {
// replay timers replace any existing timer (even if it expires sooner)
// this can occur if a player has a timer for being a past member of
// a shared task but joined another before the first was completed
CharacterTaskTimersRepository::DeleteWhere(*m_database, fmt::format(
"(task_id = {} OR (timer_group > 0 AND timer_group = {})) AND timer_type = {} AND character_id IN ({})",
s->GetTaskData().id,
s->GetTaskData().replay_timer_group,
static_cast<int>(TaskTimerType::Replay),
fmt::join(s->member_id_history, ",")
));
CharacterTaskTimersRepository::InsertMany(*m_database, task_timers);
}
}
}
// memory search
std::vector<uint32_t> SharedTaskManager::FindCharactersInSharedTasks(const std::vector<uint32_t> &find_characters)
{
std::vector<uint32_t> characters = {};
for (auto &s: m_shared_tasks) {
// loop through members
for (auto &m: s.GetMembers()) {
// compare members with requested characters
for (auto &find_character_id: find_characters) {
// found character, add to list
if (find_character_id == m.character_id) {
characters.emplace_back(m.character_id);
}
}
}
}
return characters;
}
void SharedTaskManager::PurgeAllSharedTasks()
{
for (auto &shared_task : m_shared_tasks) {
RemoveAllMembersFromDynamicZones(&shared_task);
}
SharedTasksRepository::Truncate(*m_database);
SharedTaskMembersRepository::Truncate(*m_database);
SharedTaskActivityStateRepository::Truncate(*m_database);
SharedTaskDynamicZonesRepository::Truncate(*m_database);
CompletedSharedTasksRepository::Truncate(*m_database);
CompletedSharedTaskMembersRepository::Truncate(*m_database);
CompletedSharedTaskActivityStateRepository::Truncate(*m_database);
LoadSharedTaskState();
}
void SharedTaskManager::RemoveAllMembersFromDynamicZones(SharedTask *s)
{
for (const auto &dz_id : s->dynamic_zone_ids) {
auto dz = DynamicZone::FindDynamicZoneByID(dz_id);
if (dz) {
dz->RemoveAllMembers();
}
}
}
void SharedTaskManager::ChooseNewLeader(SharedTask *s)
{
// live doesn't prioritize choosing an online player here
auto members = s->GetMembers();
auto it = std::find_if(
members.begin(), members.end(),
[&](const SharedTaskMember &member) {
return !member.is_leader;
}
);
if (it != members.end()) {
MakeLeaderByPlayerName(s, it->character_name);
}
}
const std::vector<SharedTask> &SharedTaskManager::GetSharedTasks() const
{
return m_shared_tasks;
}
void SharedTaskManager::SetSharedTasks(const std::vector<SharedTask> &shared_tasks)
{
SharedTaskManager::m_shared_tasks = shared_tasks;
}
SharedTaskManager *SharedTaskManager::PurgeExpiredSharedTasks()
{
auto now = std::time(nullptr);
for (auto &s: m_shared_tasks) {
if (s.GetDbSharedTask().expire_time > 0 && s.GetDbSharedTask().expire_time <= now) {
LogTasksDetail("[PurgeExpiredSharedTasks] Deleting expired task [{}]", s.GetDbSharedTask().id);
DeleteSharedTask(s.GetDbSharedTask().id);
}
}
return this;
}
void SharedTaskManager::LockTask(SharedTask* s, bool lock)
{
bool is_locked = (s->GetDbSharedTask().is_locked != 0);
if (is_locked != lock)
{
auto db_task = s->GetDbSharedTask();
db_task.is_locked = lock;
SharedTasksRepository::UpdateOne(*m_database, db_task);
s->SetDbSharedTask(db_task);
if (lock)
{
SendLeaderMessageID(s, Chat::Yellow, SharedTaskMessage::YOUR_TASK_NOW_LOCKED);
}
}
}
bool SharedTaskManager::HandleCompletedActivities(SharedTask* s)
{
bool is_task_complete = true;
bool lock_task = false;
std::array<bool, MAXACTIVITIESPERTASK> completed_steps;
completed_steps.fill(true);
// multiple activity ids may share a step, sort so previous step completions can be checked
auto activity_states = s->GetActivityState();
std::sort(activity_states.begin(), activity_states.end(),
[](const auto& lhs, const auto& rhs) { return lhs.step < rhs.step; });
for (const auto& a : activity_states)
{
if (a.done_count != a.max_done_count && !a.optional)
{
is_task_complete = false;
if (a.step > 0 && a.step <= MAXACTIVITIESPERTASK)
{
completed_steps[a.step - 1] = false;
}
}
int lock_index = s->GetTaskData().lock_activity_id;
if (a.activity_id == lock_index && a.step > 0 && a.step <= MAXACTIVITIESPERTASK)
{
// lock if element is active (on first step or previous step completed)
lock_task = (a.step == 1 || completed_steps[a.step - 2]);
}
}
// completion locks are silent
if (!is_task_complete && lock_task)
{
LockTask(s, true);
}
return is_task_complete;
}
void SharedTaskManager::HandleCompletedTask(SharedTask* s)
{
auto db_task = s->GetDbSharedTask();
LogTasksDetail("[HandleCompletedTask] Marking shared task [{}] completed", db_task.id);
db_task.completion_time = std::time(nullptr);
db_task.is_locked = true;
SharedTasksRepository::UpdateOne(*m_database, db_task);
s->SetDbSharedTask(db_task);
RecordSharedTaskCompletion(s);
AddReplayTimers(s);
}