mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-11 16:51:29 +00:00
1822 lines
55 KiB
C++
1822 lines
55 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/rulesys.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 <ctime>
|
|
|
|
SharedTaskManager::SharedTaskManager()
|
|
: m_process_timer{ static_cast<uint32_t>(RuleI(TaskSystem, SharedTasksWorldProcessRate)) }
|
|
{
|
|
}
|
|
|
|
SharedTaskManager *SharedTaskManager::SetDatabase(Database *db)
|
|
{
|
|
SharedTaskManager::m_database = db;
|
|
|
|
return this;
|
|
}
|
|
|
|
SharedTaskManager *SharedTaskManager::SetContentDatabase(Database *db)
|
|
{
|
|
SharedTaskManager::m_content_database = db;
|
|
|
|
return this;
|
|
}
|
|
|
|
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(
|
|
"Found Shared Task ({}) [{}]",
|
|
requested_task_id,
|
|
task.title
|
|
);
|
|
}
|
|
|
|
// shared task validation
|
|
// todo: this should be online group/raid members only (avoid queries)
|
|
auto request = SharedTask::GetRequestCharacters(*m_database, requested_character_id);
|
|
if (!CanRequestSharedTask(task.id, request)) {
|
|
LogTasksDetail("Shared task validation failed");
|
|
return;
|
|
}
|
|
|
|
for (auto& m: request.members) {
|
|
LogTasksDetail("Request Members [{}]", m.character_name);
|
|
}
|
|
|
|
// 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;
|
|
e.req_activity_id = a.req_activity_id;
|
|
e.activity_state = ActivityState::ActivityHidden;
|
|
|
|
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);
|
|
|
|
LoadDynamicZoneTemplate(&inserted);
|
|
|
|
LogTasks(
|
|
"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::RemoveMember(SharedTask* s, const SharedTaskMember& member, bool remove_from_db)
|
|
{
|
|
LogTasksDetail("shared_task_id [{}] member [{}]", s->GetDbSharedTask().id, member.character_name);
|
|
|
|
RemovePlayerFromSharedTask(s, member.character_id);
|
|
SendRemovePlayerFromSharedTaskPacket(member.character_id, s->GetDbSharedTask().task_id, remove_from_db);
|
|
|
|
SendSharedTaskMemberRemovedToAllMembers(s, member.character_name);
|
|
|
|
ClientList::Instance()->SendCharacterMessageID(member.character_id, Chat::Yellow,
|
|
TaskStr::PLAYER_REMOVED, {member.character_name, s->GetTaskData().title});
|
|
|
|
if (member.is_leader) {
|
|
ChooseNewLeader(s);
|
|
}
|
|
|
|
// todo: filter requirements
|
|
if (s->GetMembers().size() < s->GetTaskData().min_players)
|
|
{
|
|
SendMembersMessageID(s, Chat::Red, TaskStr::MIN_PLAYERS);
|
|
StartTerminateTimer(s);
|
|
}
|
|
}
|
|
|
|
void SharedTaskManager::RemoveEveryoneFromSharedTask(SharedTask *t, uint32 requested_character_id)
|
|
{
|
|
// caller validates leader
|
|
LogTasksDetail("Leader [{}]", requested_character_id);
|
|
|
|
// inform clients of removal
|
|
for (auto &m: t->GetMembers()) {
|
|
LogTasksDetail(
|
|
"Sending removal to [{}] task_id [{}]",
|
|
m.character_id,
|
|
t->GetTaskData().id
|
|
);
|
|
|
|
SendRemovePlayerFromSharedTaskPacket(m.character_id, t->GetTaskData().id, true);
|
|
|
|
ClientList::Instance()->SendCharacterMessageID(m.character_id, Chat::Yellow, TaskStr::YOU_REMOVED, {t->GetTaskData().title});
|
|
}
|
|
|
|
ClientList::Instance()->SendCharacterMessageID(requested_character_id, Chat::Red, TaskStr::PLAYER_REMOVED, {"Everyone", t->GetTaskData().title});
|
|
|
|
RemoveAllMembersFromDynamicZones(t);
|
|
|
|
// persistence
|
|
DeleteSharedTask(t->GetDbSharedTask().id);
|
|
|
|
PrintSharedTaskState();
|
|
}
|
|
|
|
void SharedTaskManager::Terminate(SharedTask& s, bool send_fail, bool erase)
|
|
{
|
|
LogTasksDetail("shared task [{}]", s.GetDbSharedTask().id);
|
|
for (const auto& member : s.GetMembers())
|
|
{
|
|
if (send_fail)
|
|
{
|
|
SendSharedTaskFailed(member.character_id, s.GetTaskData().id);
|
|
}
|
|
SendRemovePlayerFromSharedTaskPacket(member.character_id, s.GetTaskData().id, true);
|
|
ClientList::Instance()->SendCharacterMessageID(member.character_id, Chat::Yellow, TaskStr::HAS_ENDED, {s.GetTaskData().title});
|
|
}
|
|
|
|
RemoveAllMembersFromDynamicZones(&s);
|
|
|
|
if (erase)
|
|
{
|
|
DeleteSharedTask(s.GetDbSharedTask().id);
|
|
}
|
|
}
|
|
|
|
void SharedTaskManager::DeleteSharedTask(int64 shared_task_id)
|
|
{
|
|
LogTasksDetail(
|
|
"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("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 ({})", Strings::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(
|
|
"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("[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(
|
|
"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;
|
|
e.req_activity_id = ad.req_activity_id;
|
|
e.activity_state = sta.completed_time > 0 ? ActivityCompleted : ActivityHidden;
|
|
}
|
|
}
|
|
|
|
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(
|
|
"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(
|
|
"shared_task_id [{}] adding dynamic_zone_id [{}]",
|
|
s.id,
|
|
dz_entry.dynamic_zone_id
|
|
);
|
|
}
|
|
}
|
|
|
|
LogTasks(
|
|
"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(
|
|
"Loaded [{}] shared tasks",
|
|
m_shared_tasks.size()
|
|
);
|
|
|
|
PrintSharedTaskState();
|
|
}
|
|
|
|
SharedTaskManager *SharedTaskManager::LoadTaskData()
|
|
{
|
|
m_task_data = TasksRepository::GetWhere(*m_content_database, "enabled = 1");
|
|
m_task_activity_data = TaskActivitiesRepository::All(*m_content_database);
|
|
|
|
LogTasks("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(
|
|
"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(
|
|
"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(
|
|
"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);
|
|
a.activity_state = ActivityState::ActivityCompleted;
|
|
}
|
|
|
|
// 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(
|
|
"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 = ClientList::Instance()->FindCLEByCharacterID(m.character_id);
|
|
if (c && c->Server()) {
|
|
c->Server()->SendPacket(p.get());
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
LogTasksDetail(
|
|
"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_entity_id = npc_context_id;
|
|
d->accept_time = accept_time;
|
|
|
|
// get requested character zone server
|
|
ClientListEntry *cle = ClientList::Instance()->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_SharedTaskQuit, sizeof(ServerSharedTaskQuit_Struct));
|
|
|
|
auto d = reinterpret_cast<ServerSharedTaskQuit_Struct *>(p->pBuffer);
|
|
d->requested_character_id = character_id;
|
|
d->task_id = task_id;
|
|
d->remove_from_db = remove_from_db;
|
|
|
|
// get requested character zone server
|
|
ClientListEntry *cle = ClientList::Instance()->FindCLEByCharacterID(character_id);
|
|
if (cle && cle->Server()) {
|
|
cle->Server()->SendPacket(p.get());
|
|
}
|
|
}
|
|
|
|
void SharedTaskManager::SendSharedTaskFailed(uint32_t character_id, uint32_t task_id)
|
|
{
|
|
ServerPacket pack(ServerOP_SharedTaskFailed, sizeof(ServerSharedTaskCharacterTask_Struct));
|
|
auto buf = reinterpret_cast<ServerSharedTaskCharacterTask_Struct*>(pack.pBuffer);
|
|
buf->character_id = character_id;
|
|
buf->task_id = task_id;
|
|
|
|
ClientListEntry* cle = ClientList::Instance()->FindCLEByCharacterID(character_id);
|
|
if (cle && cle->Server())
|
|
{
|
|
cle->Server()->SendPacket(&pack);
|
|
}
|
|
}
|
|
|
|
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 = ClientList::Instance()->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 = ClientList::Instance()->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("# Shared Task");
|
|
|
|
LogTasksDetail(
|
|
"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("# Activities");
|
|
|
|
// activity state
|
|
for (auto &a: s.m_shared_task_activity_state) {
|
|
LogTasksDetail(
|
|
"-- activity_id [{}] done_count [{}] max_done_count [{}] completed_time [{}]",
|
|
a.activity_id,
|
|
a.done_count,
|
|
a.max_done_count,
|
|
a.completed_time
|
|
);
|
|
}
|
|
|
|
LogTasksDetail("# Members");
|
|
|
|
// members
|
|
for (auto &m: s.m_members) {
|
|
LogTasksDetail(
|
|
"-- character_id [{}] is_leader [{}]",
|
|
m.character_id,
|
|
m.is_leader
|
|
);
|
|
}
|
|
|
|
LogTasksDetail("# Dynamic Zones");
|
|
|
|
for (auto &dz_id: s.dynamic_zone_ids) {
|
|
LogTasksDetail(
|
|
"-- dynamic_zone_id [{}]",
|
|
dz_id
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
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, TaskStr::IS_NOT_MEMBER, {character_name});
|
|
return;
|
|
}
|
|
|
|
bool found_new_leader = false;
|
|
|
|
std::vector<SharedTaskMember> members = s->GetMembers();
|
|
for (auto &m: members) {
|
|
LogTasksDetail(
|
|
"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(
|
|
"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, TaskStr::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 = ClientList::Instance()->FindCLEByCharacterID(invited_character_id);
|
|
if (cle && cle->Server()) {
|
|
SendLeaderMessageID(s, Chat::Yellow, TaskStr::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);
|
|
|
|
// min players should be the only thing to check, other failed reqs prevent player add
|
|
if (s->GetMembers().size() >= s->GetTaskData().min_players)
|
|
{
|
|
s->terminate_timer.Disable();
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
SharedTask* SharedTaskManager::FindSharedTaskByDzId(uint32_t dz_id)
|
|
{
|
|
for (auto& s: m_shared_tasks) {
|
|
auto it = std::find(s.dynamic_zone_ids.begin(), s.dynamic_zone_ids.end(), dz_id);
|
|
if (it != s.dynamic_zone_ids.end()) {
|
|
return &s;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void SharedTaskManager::QueueActiveInvitation(int64 shared_task_id, int64 character_id)
|
|
{
|
|
LogTasksDetail(
|
|
"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(
|
|
"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(
|
|
"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(
|
|
"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.TryCreate(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::LoadDynamicZoneTemplate(SharedTask* s)
|
|
{
|
|
const auto& task = s->GetTaskData();
|
|
if (task.dz_template_id != 0)
|
|
{
|
|
auto it = dynamic_zone_manager.GetTemplates().find(task.dz_template_id);
|
|
if (it != dynamic_zone_manager.GetTemplates().end())
|
|
{
|
|
DynamicZone dz(DynamicZoneType::Mission);
|
|
dz.LoadTemplate(it->second);
|
|
dz.SetMinPlayers(task.min_players);
|
|
dz.SetMaxPlayers(task.max_players);
|
|
if (task.duration > dz.GetDuration())
|
|
{
|
|
dz.SetDuration(task.duration);
|
|
}
|
|
CreateDynamicZone(s, dz);
|
|
}
|
|
}
|
|
}
|
|
|
|
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) {
|
|
ClientList::Instance()->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) {
|
|
ClientList::Instance()->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()) {
|
|
ClientList::Instance()->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 = ClientList::Instance()->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
|
|
),
|
|
Strings::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, const SharedTaskRequest& 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& it : shared_task_members) {
|
|
if (it.character_id == request.leader_id) {
|
|
ClientList::Instance()->SendCharacterMessageID(request.leader_id, Chat::Red, TaskStr::REQUEST_HAVE);
|
|
}
|
|
else if (request.group_type == SharedTaskRequestGroupType::Group) {
|
|
ClientList::Instance()->SendCharacterMessageID(request.leader_id, Chat::Red, TaskStr::REQUEST_GROUP_HAS, {it.character_name});
|
|
}
|
|
else {
|
|
ClientList::Instance()->SendCharacterMessageID(request.leader_id, Chat::Red, TaskStr::REQUEST_RAID_HAS, {it.character_name});
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// check if any party member's minimum level is too low (pre-2014 this was average level)
|
|
if (task.min_level > 0 && request.lowest_level < task.min_level) {
|
|
ClientList::Instance()->SendCharacterMessage(request.leader_id, Chat::Red, TaskStr::Get(TaskStr::LVL_TOO_LOW));
|
|
return false;
|
|
}
|
|
|
|
// check if any party member's maximum level is too high (pre-2014 this was average level)
|
|
if (task.max_level > 0 && request.highest_level > task.max_level) {
|
|
ClientList::Instance()->SendCharacterMessage(request.leader_id, Chat::Red, TaskStr::Get(TaskStr::LVL_TOO_HIGH));
|
|
return false;
|
|
}
|
|
|
|
// allow gm/dev bypass for minimum player count requirements
|
|
auto requester = ClientList::Instance()->FindCLEByCharacterID(request.leader_id);
|
|
bool is_gm = (requester && requester->GetGM());
|
|
|
|
// check if party member count is below the minimum
|
|
if (!is_gm && task.min_players > 0 && request.members.size() < task.min_players) {
|
|
ClientList::Instance()->SendCharacterMessageID(request.leader_id, Chat::Red, TaskStr::MIN_PLAYERS);
|
|
return false;
|
|
}
|
|
|
|
if (is_gm) {
|
|
ClientList::Instance()->SendCharacterMessage(requester->CharID(), Chat::White, "Your GM flag allows you to bypass shared task minimum player requirements.");
|
|
}
|
|
|
|
// 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.members.size() > task.max_players) {
|
|
ClientList::Instance()->SendCharacterMessageID(request.leader_id, Chat::Red, TaskStr::MAX_PLAYERS);
|
|
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) {
|
|
ClientList::Instance()->SendCharacterMessageID(request.leader_id, Chat::Red, TaskStr::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 == request.leader_id) {
|
|
if (timer_type == TaskTimerType::Replay) {
|
|
ClientList::Instance()->SendCharacterMessageID(request.leader_id, Chat::Red, TaskStr::YOU_REPLAY_TIMER, {days, hours, mins});
|
|
}
|
|
else if (timer_type == TaskTimerType::Request) {
|
|
ClientList::Instance()->SendCharacterMessage(request.leader_id, Chat::Red, fmt::format(
|
|
TaskStr::Get(TaskStr::YOU_REQUEST_TIMER), days, hours, mins));
|
|
}
|
|
}
|
|
else {
|
|
auto it = std::find_if(request.members.begin(), request.members.end(),
|
|
[&](const SharedTaskMember& member) {
|
|
return member.character_id == character_task_timers.front().character_id;
|
|
});
|
|
|
|
if (it != request.members.end() && timer_type == TaskTimerType::Replay) {
|
|
ClientList::Instance()->SendCharacterMessageID(request.leader_id, Chat::Red,
|
|
TaskStr::PLAYER_REPLAY_TIMER, {it->character_name, days, hours, mins});
|
|
}
|
|
else if (it != request.members.end() && timer_type == TaskTimerType::Request) {
|
|
ClientList::Instance()->SendCharacterMessage(request.leader_id, Chat::Red, fmt::format(
|
|
TaskStr::Get(TaskStr::PLAYER_REQUEST_TIMER), it->character_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, TaskStr::NOT_ALLOWING_PLAYERS);
|
|
allow_invite = false;
|
|
}
|
|
|
|
// check if player is online and in cle (other checks require online)
|
|
auto cle = ClientList::Instance()->FindCLEByCharacterID(character_id);
|
|
if (!cle || !cle->Server()) {
|
|
SendLeaderMessageID(s, Chat::Red, TaskStr::PLAYER_NOT_ONLINE, {player_name});
|
|
SendLeaderMessageID(s, Chat::Red, TaskStr::COULD_NOT_BE_INVITED, {player_name});
|
|
return false;
|
|
}
|
|
|
|
player_name = cle->name();
|
|
|
|
// check if player is already in a shared task
|
|
// todo: do a memory search instead of query here
|
|
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, TaskStr::PLAYER_ALREADY_MEMBER, {player_name});
|
|
}
|
|
else {
|
|
SendLeaderMessageID(s, Chat::Red, TaskStr::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, TaskStr::PLAYER_INVITED, {player_name});
|
|
}
|
|
else {
|
|
SendLeaderMessageID(s, Chat::Red, TaskStr::PLAYER_INVITED_OTHER, {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, TaskStr::CANT_ADD_PLAYER_REPLAY_TIMER, {player_name, days, hours, mins});
|
|
}
|
|
else {
|
|
SendLeaderMessage(s, Chat::Red, fmt::format(
|
|
TaskStr::Get(TaskStr::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, TaskStr::CANT_ADD_MAX_PLAYERS, {max});
|
|
allow_invite = false;
|
|
}
|
|
|
|
// check if task would exceed max level spread
|
|
// todo: cache low/high levels on SharedTask to avoid query for offline members (also usable by RemoveMember)
|
|
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
|
|
)
|
|
);
|
|
|
|
auto lowest_level = static_cast<uint32_t>(cle->level());
|
|
auto highest_level = lowest_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, TaskStr::CANT_ADD_LVL_SPREAD, {max_spread});
|
|
allow_invite = false;
|
|
}
|
|
}
|
|
|
|
// check if player is below minimum level of task (pre-2014 this was average level)
|
|
if (s->GetTaskData().min_level > 0 && cle->level() < s->GetTaskData().min_level) {
|
|
SendLeaderMessage(s, Chat::Red, TaskStr::Get(TaskStr::CANT_ADD_MIN_LEVEL));
|
|
allow_invite = false;
|
|
}
|
|
|
|
// check if player is above maximum level of task (pre-2014 this was average level)
|
|
if (s->GetTaskData().max_level > 0 && cle->level() > s->GetTaskData().max_level) {
|
|
SendLeaderMessage(s, Chat::Red, TaskStr::Get(TaskStr::CANT_ADD_MAX_LEVEL));
|
|
allow_invite = false;
|
|
}
|
|
|
|
if (!allow_invite) {
|
|
if (!accepted) {
|
|
SendLeaderMessageID(s, Chat::Red, TaskStr::COULD_NOT_BE_INVITED, {player_name});
|
|
}
|
|
else {
|
|
SendLeaderMessageID(s, Chat::Red, TaskStr::COULD_NOT_JOIN, {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);
|
|
|
|
ClientList::Instance()->SendCharacterMessage(
|
|
member_id,
|
|
Chat::Yellow,
|
|
fmt::format(
|
|
TaskStr::Get(TaskStr::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),
|
|
Strings::Join(s->member_id_history, ",")
|
|
));
|
|
|
|
CharacterTaskTimersRepository::InsertMany(*m_database, task_timers);
|
|
}
|
|
}
|
|
}
|
|
|
|
// memory search
|
|
std::vector<SharedTaskMember> SharedTaskManager::FindCharactersInSharedTasks(const std::vector<uint32_t> &find_characters)
|
|
{
|
|
std::vector<SharedTaskMember> 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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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()
|
|
{
|
|
std::vector<int64_t> delete_tasks;
|
|
|
|
auto now = std::time(nullptr);
|
|
for (auto &s: m_shared_tasks) {
|
|
if (s.GetDbSharedTask().expire_time > 0 && s.GetDbSharedTask().expire_time <= now) {
|
|
LogTasksDetail("Deleting expired task [{}]", s.GetDbSharedTask().id);
|
|
delete_tasks.push_back(s.GetDbSharedTask().id);
|
|
}
|
|
}
|
|
|
|
for (int64_t shared_task_id : delete_tasks)
|
|
{
|
|
DeleteSharedTask(shared_task_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, TaskStr::TASK_LOCKED);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool SharedTaskManager::HandleCompletedActivities(SharedTask* s)
|
|
{
|
|
auto states = s->GetActivityState();
|
|
|
|
// activity state holds both source data and current state
|
|
auto res = Tasks::GetActiveElements(states, states, states.size());
|
|
|
|
// completion locks are silent
|
|
auto it = std::find(res.active.begin(), res.active.end(), s->GetTaskData().lock_activity_id);
|
|
if (!res.is_task_complete && it != res.active.end())
|
|
{
|
|
LockTask(s, true);
|
|
}
|
|
|
|
return res.is_task_complete;
|
|
}
|
|
|
|
void SharedTaskManager::HandleCompletedTask(SharedTask* s)
|
|
{
|
|
auto db_task = s->GetDbSharedTask();
|
|
if (db_task.completion_time > 0)
|
|
{
|
|
LogTasksDetail("shared task [{}] already completed", db_task.id);
|
|
return;
|
|
}
|
|
|
|
LogTasksDetail("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);
|
|
}
|
|
|
|
void SharedTaskManager::StartTerminateTimer(SharedTask* s)
|
|
{
|
|
s->terminate_timer.Start(RuleI(TaskSystem, SharedTasksTerminateTimerMS));
|
|
SendMembersMessageID(s, Chat::Red, TaskStr::REQS_TWO_MIN);
|
|
}
|
|
|
|
void SharedTaskManager::Process()
|
|
{
|
|
if (!m_process_timer.Check())
|
|
{
|
|
return;
|
|
}
|
|
|
|
std::vector<int64_t> delete_tasks;
|
|
for (auto& shared_task : m_shared_tasks)
|
|
{
|
|
if (shared_task.GetMembers().empty() || shared_task.terminate_timer.Check() || shared_task.IsExpired())
|
|
{
|
|
Terminate(shared_task, false, false);
|
|
|
|
// avoid erasing from m_shared_tasks while iterating it
|
|
delete_tasks.push_back(shared_task.GetDbSharedTask().id);
|
|
}
|
|
}
|
|
|
|
for (int64_t shared_task_id : delete_tasks)
|
|
{
|
|
DeleteSharedTask(shared_task_id);
|
|
}
|
|
}
|