mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-11 21:01:29 +00:00
* Make utils/scripts/generators/repository-generator.pl aware of more datatypes This adds support for unsigned and more integer types. It also avoids using parsing functions that require casting (still needed in some cases) Having the data types in the Repository structs better map to the types in the database will allow us to avoid casting when we pull data out of them. And as a benefit, assume something is wrong if we do :) Hopefully clean up some warnings due to casting too. Co-authored-by: Akkadius <akkadius1@gmail.com>
1775 lines
54 KiB
C++
1775 lines
54 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>
|
|
|
|
extern ClientList client_list;
|
|
extern ZSList zoneserver_list;
|
|
|
|
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(
|
|
"[AttemptSharedTaskCreation] 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("[AttemptSharedTaskCreation] Shared task validation failed");
|
|
return;
|
|
}
|
|
|
|
for (auto& m: request.members) {
|
|
LogTasksDetail("[AttemptSharedTaskCreation] 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(
|
|
"[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::RemoveMember(SharedTask* s, const SharedTaskMember& member, bool remove_from_db)
|
|
{
|
|
LogTasksDetail("[RemoveMember] 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);
|
|
|
|
client_list.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("[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, TaskStr::YOU_REMOVED, {t->GetTaskData().title});
|
|
}
|
|
|
|
client_list.SendCharacterMessageID(requested_character_id, Chat::Red, TaskStr::PLAYER_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;
|
|
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(
|
|
"[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);
|
|
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(
|
|
"[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_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 = 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::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(
|
|
"[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, 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 = client_list.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;
|
|
}
|
|
|
|
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::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) {
|
|
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, 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) {
|
|
client_list.SendCharacterMessageID(request.leader_id, Chat::Red, TaskStr::REQUEST_HAVE);
|
|
}
|
|
else if (request.group_type == SharedTaskRequestGroupType::Group) {
|
|
client_list.SendCharacterMessageID(request.leader_id, Chat::Red, TaskStr::REQUEST_GROUP_HAS, {it.character_name});
|
|
}
|
|
else {
|
|
client_list.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.minlevel > 0 && request.lowest_level < task.minlevel) {
|
|
client_list.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.maxlevel > 0 && request.highest_level > task.maxlevel) {
|
|
client_list.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 = client_list.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) {
|
|
client_list.SendCharacterMessageID(request.leader_id, Chat::Red, TaskStr::MIN_PLAYERS);
|
|
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.members.size() > task.max_players) {
|
|
client_list.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) {
|
|
client_list.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) {
|
|
client_list.SendCharacterMessageID(request.leader_id, Chat::Red, TaskStr::YOU_REPLAY_TIMER, {days, hours, mins});
|
|
}
|
|
else if (timer_type == TaskTimerType::Request) {
|
|
client_list.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) {
|
|
client_list.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) {
|
|
client_list.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 = client_list.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().minlevel > 0 && cle->level() < s->GetTaskData().minlevel) {
|
|
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().maxlevel > 0 && cle->level() > s->GetTaskData().maxlevel) {
|
|
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);
|
|
|
|
client_list.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),
|
|
fmt::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("[PurgeExpiredSharedTasks] 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();
|
|
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);
|
|
}
|
|
|
|
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())
|
|
{
|
|
LogTasksDetail("[Process] Terminating shared task [{}]", shared_task.GetDbSharedTask().id);
|
|
for (const auto& member : shared_task.GetMembers())
|
|
{
|
|
SendRemovePlayerFromSharedTaskPacket(member.character_id, shared_task.GetTaskData().id, true);
|
|
client_list.SendCharacterMessageID(member.character_id, Chat::Yellow, TaskStr::HAS_ENDED, {shared_task.GetTaskData().title});
|
|
}
|
|
|
|
RemoveAllMembersFromDynamicZones(&shared_task);
|
|
|
|
// 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);
|
|
}
|
|
}
|