eqemu-server/world/shared_task_manager.cpp
hg 1488c3685c
[Tasks] Apply full duration mission replay timers (#2299)
Shared task replay timers used to be based on the task start time
(reducing the final lockout timer by elapsed task time).

Live changed this in the 2022 April 20 patch:
"Replay timers will now apply their full duration upon completion of
their associated task/quest."

Solo tasks and quests already did this

This also fixes a small bug where request timers were being
deleted when replacing replay timers for shared tasks
2022-07-14 02:19:27 -05:00

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