[Tasks] Implement task activity prerequisites (#2374)

Some live tasks make new elements available without requiring all
currently active ones to be completed first.

This adds the `req_activity_id` field to task activities which will mark
an element active if its required activity id is completed. If a valid
value is set then it's used instead of checking the current step.

The `step` field may still be set on rows with a valid `req_activity_id`
to specify its logical step and prevent later steps from becoming active
until completed. It's only ignored when deciding if the current element
is active.

The legacy task logic for unlocking activities was completely refactored
for this. A common method has been added so both zone and world can make
use of it to determine which elements are currently active. The previous
step system should remain unchanged.

The world logic for locking shared tasks when an element became active
did not account for "sequential" mode (all steps 0), unordered steps, or
gaps in step numbers. This also resolves that issue.
This commit is contained in:
hg 2022-08-21 20:55:19 -04:00 committed by GitHub
parent c954685239
commit e65b61a95f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 1421 additions and 347 deletions

View File

@ -21,6 +21,7 @@ public:
struct TaskActivities { struct TaskActivities {
int taskid; int taskid;
int activityid; int activityid;
int req_activity_id;
int step; int step;
int activitytype; int activitytype;
std::string target_name; std::string target_name;
@ -48,6 +49,7 @@ public:
return { return {
"taskid", "taskid",
"activityid", "activityid",
"req_activity_id",
"step", "step",
"activitytype", "activitytype",
"target_name", "target_name",
@ -71,6 +73,7 @@ public:
return { return {
"taskid", "taskid",
"activityid", "activityid",
"req_activity_id",
"step", "step",
"activitytype", "activitytype",
"target_name", "target_name",
@ -128,6 +131,7 @@ public:
e.taskid = 0; e.taskid = 0;
e.activityid = 0; e.activityid = 0;
e.req_activity_id = -1;
e.step = 0; e.step = 0;
e.activitytype = 0; e.activitytype = 0;
e.target_name = ""; e.target_name = "";
@ -180,21 +184,22 @@ public:
e.taskid = atoi(row[0]); e.taskid = atoi(row[0]);
e.activityid = atoi(row[1]); e.activityid = atoi(row[1]);
e.step = atoi(row[2]); e.req_activity_id = atoi(row[2]);
e.activitytype = atoi(row[3]); e.step = atoi(row[3]);
e.target_name = row[4] ? row[4] : ""; e.activitytype = atoi(row[4]);
e.item_list = row[5] ? row[5] : ""; e.target_name = row[5] ? row[5] : "";
e.skill_list = row[6] ? row[6] : ""; e.item_list = row[6] ? row[6] : "";
e.spell_list = row[7] ? row[7] : ""; e.skill_list = row[7] ? row[7] : "";
e.description_override = row[8] ? row[8] : ""; e.spell_list = row[8] ? row[8] : "";
e.goalid = atoi(row[9]); e.description_override = row[9] ? row[9] : "";
e.goal_match_list = row[10] ? row[10] : ""; e.goalid = atoi(row[10]);
e.goalmethod = atoi(row[11]); e.goal_match_list = row[11] ? row[11] : "";
e.goalcount = atoi(row[12]); e.goalmethod = atoi(row[12]);
e.delivertonpc = atoi(row[13]); e.goalcount = atoi(row[13]);
e.zones = row[14] ? row[14] : ""; e.delivertonpc = atoi(row[14]);
e.zone_version = atoi(row[15]); e.zones = row[15] ? row[15] : "";
e.optional = atoi(row[16]); e.zone_version = atoi(row[16]);
e.optional = atoi(row[17]);
return e; return e;
} }
@ -230,21 +235,22 @@ public:
v.push_back(columns[0] + " = " + std::to_string(e.taskid)); v.push_back(columns[0] + " = " + std::to_string(e.taskid));
v.push_back(columns[1] + " = " + std::to_string(e.activityid)); v.push_back(columns[1] + " = " + std::to_string(e.activityid));
v.push_back(columns[2] + " = " + std::to_string(e.step)); v.push_back(columns[2] + " = " + std::to_string(e.req_activity_id));
v.push_back(columns[3] + " = " + std::to_string(e.activitytype)); v.push_back(columns[3] + " = " + std::to_string(e.step));
v.push_back(columns[4] + " = '" + Strings::Escape(e.target_name) + "'"); v.push_back(columns[4] + " = " + std::to_string(e.activitytype));
v.push_back(columns[5] + " = '" + Strings::Escape(e.item_list) + "'"); v.push_back(columns[5] + " = '" + Strings::Escape(e.target_name) + "'");
v.push_back(columns[6] + " = '" + Strings::Escape(e.skill_list) + "'"); v.push_back(columns[6] + " = '" + Strings::Escape(e.item_list) + "'");
v.push_back(columns[7] + " = '" + Strings::Escape(e.spell_list) + "'"); v.push_back(columns[7] + " = '" + Strings::Escape(e.skill_list) + "'");
v.push_back(columns[8] + " = '" + Strings::Escape(e.description_override) + "'"); v.push_back(columns[8] + " = '" + Strings::Escape(e.spell_list) + "'");
v.push_back(columns[9] + " = " + std::to_string(e.goalid)); v.push_back(columns[9] + " = '" + Strings::Escape(e.description_override) + "'");
v.push_back(columns[10] + " = '" + Strings::Escape(e.goal_match_list) + "'"); v.push_back(columns[10] + " = " + std::to_string(e.goalid));
v.push_back(columns[11] + " = " + std::to_string(e.goalmethod)); v.push_back(columns[11] + " = '" + Strings::Escape(e.goal_match_list) + "'");
v.push_back(columns[12] + " = " + std::to_string(e.goalcount)); v.push_back(columns[12] + " = " + std::to_string(e.goalmethod));
v.push_back(columns[13] + " = " + std::to_string(e.delivertonpc)); v.push_back(columns[13] + " = " + std::to_string(e.goalcount));
v.push_back(columns[14] + " = '" + Strings::Escape(e.zones) + "'"); v.push_back(columns[14] + " = " + std::to_string(e.delivertonpc));
v.push_back(columns[15] + " = " + std::to_string(e.zone_version)); v.push_back(columns[15] + " = '" + Strings::Escape(e.zones) + "'");
v.push_back(columns[16] + " = " + std::to_string(e.optional)); v.push_back(columns[16] + " = " + std::to_string(e.zone_version));
v.push_back(columns[17] + " = " + std::to_string(e.optional));
auto results = db.QueryDatabase( auto results = db.QueryDatabase(
fmt::format( fmt::format(
@ -268,6 +274,7 @@ public:
v.push_back(std::to_string(e.taskid)); v.push_back(std::to_string(e.taskid));
v.push_back(std::to_string(e.activityid)); v.push_back(std::to_string(e.activityid));
v.push_back(std::to_string(e.req_activity_id));
v.push_back(std::to_string(e.step)); v.push_back(std::to_string(e.step));
v.push_back(std::to_string(e.activitytype)); v.push_back(std::to_string(e.activitytype));
v.push_back("'" + Strings::Escape(e.target_name) + "'"); v.push_back("'" + Strings::Escape(e.target_name) + "'");
@ -314,6 +321,7 @@ public:
v.push_back(std::to_string(e.taskid)); v.push_back(std::to_string(e.taskid));
v.push_back(std::to_string(e.activityid)); v.push_back(std::to_string(e.activityid));
v.push_back(std::to_string(e.req_activity_id));
v.push_back(std::to_string(e.step)); v.push_back(std::to_string(e.step));
v.push_back(std::to_string(e.activitytype)); v.push_back(std::to_string(e.activitytype));
v.push_back("'" + Strings::Escape(e.target_name) + "'"); v.push_back("'" + Strings::Escape(e.target_name) + "'");
@ -364,21 +372,22 @@ public:
e.taskid = atoi(row[0]); e.taskid = atoi(row[0]);
e.activityid = atoi(row[1]); e.activityid = atoi(row[1]);
e.step = atoi(row[2]); e.req_activity_id = atoi(row[2]);
e.activitytype = atoi(row[3]); e.step = atoi(row[3]);
e.target_name = row[4] ? row[4] : ""; e.activitytype = atoi(row[4]);
e.item_list = row[5] ? row[5] : ""; e.target_name = row[5] ? row[5] : "";
e.skill_list = row[6] ? row[6] : ""; e.item_list = row[6] ? row[6] : "";
e.spell_list = row[7] ? row[7] : ""; e.skill_list = row[7] ? row[7] : "";
e.description_override = row[8] ? row[8] : ""; e.spell_list = row[8] ? row[8] : "";
e.goalid = atoi(row[9]); e.description_override = row[9] ? row[9] : "";
e.goal_match_list = row[10] ? row[10] : ""; e.goalid = atoi(row[10]);
e.goalmethod = atoi(row[11]); e.goal_match_list = row[11] ? row[11] : "";
e.goalcount = atoi(row[12]); e.goalmethod = atoi(row[12]);
e.delivertonpc = atoi(row[13]); e.goalcount = atoi(row[13]);
e.zones = row[14] ? row[14] : ""; e.delivertonpc = atoi(row[14]);
e.zone_version = atoi(row[15]); e.zones = row[15] ? row[15] : "";
e.optional = atoi(row[16]); e.zone_version = atoi(row[16]);
e.optional = atoi(row[17]);
all_entries.push_back(e); all_entries.push_back(e);
} }
@ -405,21 +414,22 @@ public:
e.taskid = atoi(row[0]); e.taskid = atoi(row[0]);
e.activityid = atoi(row[1]); e.activityid = atoi(row[1]);
e.step = atoi(row[2]); e.req_activity_id = atoi(row[2]);
e.activitytype = atoi(row[3]); e.step = atoi(row[3]);
e.target_name = row[4] ? row[4] : ""; e.activitytype = atoi(row[4]);
e.item_list = row[5] ? row[5] : ""; e.target_name = row[5] ? row[5] : "";
e.skill_list = row[6] ? row[6] : ""; e.item_list = row[6] ? row[6] : "";
e.spell_list = row[7] ? row[7] : ""; e.skill_list = row[7] ? row[7] : "";
e.description_override = row[8] ? row[8] : ""; e.spell_list = row[8] ? row[8] : "";
e.goalid = atoi(row[9]); e.description_override = row[9] ? row[9] : "";
e.goal_match_list = row[10] ? row[10] : ""; e.goalid = atoi(row[10]);
e.goalmethod = atoi(row[11]); e.goal_match_list = row[11] ? row[11] : "";
e.goalcount = atoi(row[12]); e.goalmethod = atoi(row[12]);
e.delivertonpc = atoi(row[13]); e.goalcount = atoi(row[13]);
e.zones = row[14] ? row[14] : ""; e.delivertonpc = atoi(row[14]);
e.zone_version = atoi(row[15]); e.zones = row[15] ? row[15] : "";
e.optional = atoi(row[16]); e.zone_version = atoi(row[16]);
e.optional = atoi(row[17]);
all_entries.push_back(e); all_entries.push_back(e);
} }

View File

@ -3,6 +3,7 @@
#include "database.h" #include "database.h"
#include "timer.h" #include "timer.h"
#include "tasks.h"
#include "types.h" #include "types.h"
#include "repositories/character_data_repository.h" #include "repositories/character_data_repository.h"
#include "repositories/tasks_repository.h" #include "repositories/tasks_repository.h"
@ -108,8 +109,10 @@ struct SharedTaskActivityStateEntry {
uint32 max_done_count; // goalcount uint32 max_done_count; // goalcount
uint32 updated_time; uint32 updated_time;
uint32 completed_time; uint32 completed_time;
int req_activity_id;
int step; int step;
bool optional; bool optional;
ActivityState activity_state; // world only uses Hidden and Completed states
}; };
struct ServerSharedTaskActivityUpdate_Struct { struct ServerSharedTaskActivityUpdate_Struct {

View File

@ -2,6 +2,8 @@
#define EQEMU_TASKS_H #define EQEMU_TASKS_H
#include "serialize_buffer.h" #include "serialize_buffer.h"
#include <algorithm>
#include <array>
#define MAXTASKS 10000 #define MAXTASKS 10000
#define MAXTASKSETS 1000 #define MAXTASKSETS 1000
@ -65,7 +67,8 @@ enum class AltCurrencyType
}; };
struct ActivityInformation { struct ActivityInformation {
int step_number; int req_activity_id;
int step;
TaskActivityType activity_type; TaskActivityType activity_type;
std::string target_name; // name mob, location -- default empty, max length 64 std::string target_name; // name mob, location -- default empty, max length 64
std::string item_list; // likely defaults to empty std::string item_list; // likely defaults to empty
@ -175,11 +178,6 @@ struct ActivityInformation {
} }
}; };
typedef enum {
ActivitiesSequential = 0,
ActivitiesStepped = 1
} SequenceType;
enum class TaskType { enum class TaskType {
Task = 0, // can have at max 1 Task = 0, // can have at max 1
Shared = 1, // can have at max 1 Shared = 1, // can have at max 1
@ -215,8 +213,6 @@ struct TaskInformation {
int reward_points; int reward_points;
AltCurrencyType reward_point_type; AltCurrencyType reward_point_type;
int activity_count{}; int activity_count{};
SequenceType sequence_mode;
int last_step{};
short min_level{}; short min_level{};
short max_level{}; short max_level{};
int level_spread; int level_spread;
@ -270,7 +266,6 @@ struct ClientActivityInformation {
struct ClientTaskInformation { struct ClientTaskInformation {
int slot; // intrusive, but makes things easier :P int slot; // intrusive, but makes things easier :P
int task_id; int task_id;
int current_step;
int accepted_time; int accepted_time;
bool updated; bool updated;
bool was_rewarded; // character has received reward for this task bool was_rewarded; // character has received reward for this task
@ -342,6 +337,79 @@ namespace Tasks {
return "Task"; return "Task";
} }
} }
struct ActiveElements
{
bool is_task_complete;
std::vector<int> active;
};
// Processes task activity states and returns those currently active
// It is templated to support the different structs used by zone and world
template <typename Td, typename Ts>
ActiveElements GetActiveElements(const Td& activity_data, const Ts& activity_states, size_t activity_count)
{
ActiveElements result;
result.is_task_complete = true;
result.active.reserve(activity_count);
std::array<bool, MAXACTIVITIESPERTASK> completed_ids;
completed_ids.fill(false);
std::fill_n(completed_ids.begin(), activity_count, true);
bool sequence_mode = true;
int current_step = std::numeric_limits<int>::max(); // lowest step not completed
// fill non-completed elements and find the current task step
for (int i = 0; i < activity_count; ++i)
{
const auto& el = activity_data[i];
if (activity_states[i].activity_state != ActivityCompleted)
{
completed_ids[i] = false;
current_step = std::min(current_step, el.step);
if (!el.optional)
{
result.is_task_complete = false;
}
}
// if all steps are 0 treat each as a separate step (previously called "sequential" mode)
if (el.step != 0)
{
sequence_mode = false;
}
}
// fill active elements based on current step and req activity ids
bool added_sequence = false;
for (int i = 0; i < activity_count; ++i)
{
const auto& el = activity_data[i];
if (activity_states[i].activity_state != ActivityCompleted)
{
bool has_req_id = el.req_activity_id >= 0 && el.req_activity_id < activity_count;
// if a valid requirement is set then it's active if its req is completed
// in non-sequence mode all on current step and optionals in previous steps are active
// in sequence mode the first non-complete is active (and any optionals up to it)
if ((has_req_id && completed_ids[el.req_activity_id]) ||
(!has_req_id && !sequence_mode && el.step <= current_step) ||
(!has_req_id && sequence_mode && !added_sequence))
{
result.active.push_back(i);
if (!has_req_id && sequence_mode)
{
added_sequence = !el.optional;
}
}
}
}
return result;
}
} }
namespace TaskStr { namespace TaskStr {

View File

@ -34,7 +34,7 @@
* Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt * Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt
*/ */
#define CURRENT_BINARY_DATABASE_VERSION 9198 #define CURRENT_BINARY_DATABASE_VERSION 9199
#ifdef BOTS #ifdef BOTS
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9029 #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9029

View File

@ -16,6 +16,7 @@ SET(tests_headers
memory_mapped_file_test.h memory_mapped_file_test.h
string_util_test.h string_util_test.h
skills_util_test.h skills_util_test.h
task_state_test.h
) )
ADD_EXECUTABLE(tests ${tests_sources} ${tests_headers}) ADD_EXECUTABLE(tests ${tests_sources} ${tests_headers})

View File

@ -29,6 +29,7 @@
#include "string_util_test.h" #include "string_util_test.h"
#include "data_verification_test.h" #include "data_verification_test.h"
#include "skills_util_test.h" #include "skills_util_test.h"
#include "task_state_test.h"
#include "../common/eqemu_config.h" #include "../common/eqemu_config.h"
const EQEmuConfig *Config; const EQEmuConfig *Config;
@ -49,6 +50,7 @@ int main() {
tests.add(new StringUtilTest()); tests.add(new StringUtilTest());
tests.add(new DataVerificationTest()); tests.add(new DataVerificationTest());
tests.add(new SkillsUtilsTest()); tests.add(new SkillsUtilsTest());
tests.add(new TaskStateTest());
tests.run(*output, true); tests.run(*output, true);
} catch(...) { } catch(...) {
return -1; return -1;

1183
tests/task_state_test.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -452,6 +452,7 @@
9196|2022_07_30_merchantlist_temp.sql|SHOW COLUMNS FROM `merchantlist_temp` LIKE 'zone_id'|empty| 9196|2022_07_30_merchantlist_temp.sql|SHOW COLUMNS FROM `merchantlist_temp` LIKE 'zone_id'|empty|
9197|2022_08_01_drop_expansion_account.sql|SHOW COLUMNS FROM `account` LIKE 'expansion'|notempty| 9197|2022_08_01_drop_expansion_account.sql|SHOW COLUMNS FROM `account` LIKE 'expansion'|notempty|
9198|2022_08_14_exp_modifier_instance_versions.sql|SHOW COLUMNS FROM `character_exp_modifiers` LIKE 'instance_version'|empty| 9198|2022_08_14_exp_modifier_instance_versions.sql|SHOW COLUMNS FROM `character_exp_modifiers` LIKE 'instance_version'|empty|
9199|2022_08_08_task_req_activity_id.sql|SHOW COLUMNS FROM `task_activities` LIKE 'req_activity_id'|empty|
# Upgrade conditions: # Upgrade conditions:
# This won't be needed after this system is implemented, but it is used database that are not # This won't be needed after this system is implemented, but it is used database that are not

View File

@ -0,0 +1,2 @@
ALTER TABLE `task_activities`
ADD COLUMN `req_activity_id` INT SIGNED NOT NULL DEFAULT '-1' AFTER `activityid`;

View File

@ -15,7 +15,6 @@
#include "../common/repositories/completed_shared_task_members_repository.h" #include "../common/repositories/completed_shared_task_members_repository.h"
#include "../common/repositories/completed_shared_task_activity_state_repository.h" #include "../common/repositories/completed_shared_task_activity_state_repository.h"
#include "../common/repositories/shared_task_dynamic_zones_repository.h" #include "../common/repositories/shared_task_dynamic_zones_repository.h"
#include <array>
#include <ctime> #include <ctime>
extern ClientList client_list; extern ClientList client_list;
@ -130,6 +129,8 @@ void SharedTaskManager::AttemptSharedTaskCreation(
e.max_done_count = a.goalcount; e.max_done_count = a.goalcount;
e.step = a.step; e.step = a.step;
e.optional = a.optional; e.optional = a.optional;
e.req_activity_id = a.req_activity_id;
e.activity_state = ActivityState::ActivityHidden;
shared_task_activity_state.emplace_back(e); shared_task_activity_state.emplace_back(e);
} }
@ -354,6 +355,8 @@ void SharedTaskManager::LoadSharedTaskState()
e.updated_time = sta.updated_time; e.updated_time = sta.updated_time;
e.step = ad.step; e.step = ad.step;
e.optional = ad.optional; e.optional = ad.optional;
e.req_activity_id = ad.req_activity_id;
e.activity_state = sta.completed_time == 0 ? ActivityHidden : ActivityCompleted;
} }
} }
@ -524,6 +527,7 @@ void SharedTaskManager::SharedTaskActivityUpdate(
// if the activity is done, lets mark it as such // if the activity is done, lets mark it as such
if (a.done_count == a.max_done_count) { if (a.done_count == a.max_done_count) {
a.completed_time = std::time(nullptr); a.completed_time = std::time(nullptr);
a.activity_state = ActivityState::ActivityCompleted;
} }
// sync state as each update comes in (for now) // sync state as each update comes in (for now)
@ -1702,43 +1706,19 @@ void SharedTaskManager::LockTask(SharedTask* s, bool lock)
bool SharedTaskManager::HandleCompletedActivities(SharedTask* s) bool SharedTaskManager::HandleCompletedActivities(SharedTask* s)
{ {
bool is_task_complete = true; auto states = s->GetActivityState();
bool lock_task = false;
std::array<bool, MAXACTIVITIESPERTASK> completed_steps; // activity state holds both source data and current state
completed_steps.fill(true); auto res = Tasks::GetActiveElements(states, states, states.size());
// multiple activity ids may share a step, sort so previous step completions can be checked
auto activity_states = s->GetActivityState();
std::sort(activity_states.begin(), activity_states.end(),
[](const auto& lhs, const auto& rhs) { return lhs.step < rhs.step; });
for (const auto& a : activity_states)
{
if (a.done_count != a.max_done_count && !a.optional)
{
is_task_complete = false;
if (a.step > 0 && a.step <= MAXACTIVITIESPERTASK)
{
completed_steps[a.step - 1] = false;
}
}
int lock_index = s->GetTaskData().lock_activity_id;
if (a.activity_id == lock_index && a.step > 0 && a.step <= MAXACTIVITIESPERTASK)
{
// lock if element is active (on first step or previous step completed)
lock_task = (a.step == 1 || completed_steps[a.step - 2]);
}
}
// completion locks are silent // completion locks are silent
if (!is_task_complete && lock_task) 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); LockTask(s, true);
} }
return is_task_complete; return res.is_task_complete;
} }
void SharedTaskManager::HandleCompletedTask(SharedTask* s) void SharedTaskManager::HandleCompletedTask(SharedTask* s)

View File

@ -366,218 +366,94 @@ static void DeleteCompletedTaskFromDatabase(int character_id, int task_id)
bool ClientTaskState::UnlockActivities(int character_id, ClientTaskInformation &task_info) bool ClientTaskState::UnlockActivities(int character_id, ClientTaskInformation &task_info)
{ {
bool all_activities_complete = true;
LogTasksDetail( LogTasksDetail(
"[UnlockActivities] Fetching task info for character_id [{}] task [{}] slot [{}] current_step [{}] accepted_time [{}] updated [{}]", "[UnlockActivities] Fetching task info for character_id [{}] task [{}] slot [{}] accepted_time [{}] updated [{}]",
character_id, character_id,
task_info.task_id, task_info.task_id,
task_info.slot, task_info.slot,
task_info.current_step,
task_info.accepted_time, task_info.accepted_time,
task_info.updated task_info.updated
); );
TaskInformation *p_task_data = task_manager->m_task_data[task_info.task_id]; const TaskInformation* task = task_manager->m_task_data[task_info.task_id];
if (p_task_data == nullptr) { if (!task)
{
return true; return true;
} }
for (int i = 0; i < p_task_data->activity_count; i++) { for (int i = 0; i < task->activity_count; ++i)
{
if (task_info.activity[i].activity_id >= 0) { if (task_info.activity[i].activity_id >= 0) {
LogTasksDetail( LogTasksDetail(
"[UnlockActivities] character_id [{}] task [{}] activity_id [{}] done_count [{}] activity_state [{}] updated [{}] sequence [{}]", "[UnlockActivities] character_id [{}] task [{}] activity_id [{}] done_count [{}] activity_state [{}] updated [{}]",
character_id, character_id,
task_info.task_id, task_info.task_id,
task_info.activity[i].activity_id, task_info.activity[i].activity_id,
task_info.activity[i].done_count, task_info.activity[i].done_count,
task_info.activity[i].activity_state, task_info.activity[i].activity_state,
task_info.activity[i].updated, task_info.activity[i].updated
p_task_data->sequence_mode
); );
} }
} }
// On loading the client state, all activities that are not completed, are auto res = Tasks::GetActiveElements(task->activity_information, task_info.activity, task->activity_count);
// marked as hidden. For Sequential (non-stepped) mode, we mark the first
// activity_information as active if not complete.
if (p_task_data->sequence_mode == ActivitiesSequential) { for (int activity_id : res.active)
if (task_info.activity[0].activity_state != ActivityCompleted) { {
task_info.activity[0].activity_state = ActivityActive; ClientActivityInformation& client_activity = task_info.activity[activity_id];
} if (client_activity.activity_state == ActivityHidden)
{
// Enable the next Hidden task. LogTasksDetail("[UnlockActivities] task [{}] activity [{}] (ActivityActive)", task_info.task_id, activity_id);
for (int i = 0; i < p_task_data->activity_count; i++) { client_activity.activity_state = ActivityActive;
if ((task_info.activity[i].activity_state == ActivityActive) && client_activity.updated = true;
(!p_task_data->activity_information[i].optional)) {
all_activities_complete = false;
break;
}
if (task_info.activity[i].activity_state == ActivityHidden) {
task_info.activity[i].activity_state = ActivityActive;
all_activities_complete = false;
break;
} }
} }
if (all_activities_complete && RuleB(TaskSystem, RecordCompletedTasks)) { if (res.is_task_complete && RuleB(TaskSystem, RecordCompletedTasks))
if (RuleB(TasksSystem, KeepOneRecordPerCompletedTask)) { {
LogTasks("KeepOneRecord enabled"); RecordCompletedTask(character_id, *task, task_info);
auto iterator = m_completed_tasks.begin();
int erased_elements = 0;
while (iterator != m_completed_tasks.end()) {
int task_id = (*iterator).task_id;
if (task_id == task_info.task_id) {
iterator = m_completed_tasks.erase(iterator);
erased_elements++;
}
else {
++iterator;
}
} }
LogTasks("Erased Element count is [{}]", erased_elements); return res.is_task_complete;
if (erased_elements) {
m_last_completed_task_loaded -= erased_elements;
DeleteCompletedTaskFromDatabase(character_id, task_info.task_id);
}
} }
if (p_task_data->type != TaskType::Shared) { void ClientTaskState::RecordCompletedTask(uint32_t character_id, const TaskInformation& task, const ClientTaskInformation& client_task)
CompletedTaskInformation completed_task_information{}; {
completed_task_information.task_id = task_info.task_id;
completed_task_information.completed_time = time(nullptr);
for (int i = 0; i < p_task_data->activity_count; i++) {
completed_task_information.activity_done[i] = (task_info.activity[i].activity_state ==
ActivityCompleted);
}
m_completed_tasks.push_back(completed_task_information);
}
}
LogTasks("Returning sequential task, AllActivitiesComplete is [{}]", all_activities_complete);
return all_activities_complete;
}
// Stepped Mode
// TODO: This code is probably more complex than it needs to be
bool current_step_complete = true;
LogTasks(
"[UnlockActivities] Current step [{}] last_step [{}]",
task_info.current_step,
p_task_data->last_step
);
// If current_step is -1, this is the first call to this method since loading the
// client state. Unlock all activities with a step number of 0
if (task_info.current_step == -1) {
for (int i = 0; i < p_task_data->activity_count; i++) {
if (p_task_data->activity_information[i].step_number == 0 &&
task_info.activity[i].activity_state == ActivityHidden) {
task_info.activity[i].activity_state = ActivityActive;
// task_info.activity_information[i].updated=true;
}
}
task_info.current_step = 0;
}
for (int current_step = task_info.current_step; current_step <= p_task_data->last_step; current_step++) {
for (int activity = 0; activity < p_task_data->activity_count; activity++) {
if (p_task_data->activity_information[activity].step_number == (int) task_info.current_step) {
if ((task_info.activity[activity].activity_state != ActivityCompleted) &&
(!p_task_data->activity_information[activity].optional)) {
current_step_complete = false;
all_activities_complete = false;
break;
}
}
}
if (!current_step_complete) {
break;
}
task_info.current_step++;
}
if (all_activities_complete) {
if (RuleB(TaskSystem, RecordCompletedTasks)) {
// If we are only keeping one completed record per task, and the player has done // If we are only keeping one completed record per task, and the player has done
// the same task again, erase the previous completed entry for this task. // the same task again, erase the previous completed entry for this task.
if (RuleB(TasksSystem, KeepOneRecordPerCompletedTask)) { if (RuleB(TasksSystem, KeepOneRecordPerCompletedTask))
LogTasksDetail("[UnlockActivities] KeepOneRecord enabled"); {
auto iterator = m_completed_tasks.begin(); size_t before = m_completed_tasks.size();
int erased_elements = 0;
while (iterator != m_completed_tasks.end()) { m_completed_tasks.erase(std::remove_if(m_completed_tasks.begin(), m_completed_tasks.end(),
int task_id = (*iterator).task_id; [&](const CompletedTaskInformation& completed) { return completed.task_id == client_task.task_id; }
if (task_id == task_info.task_id) { ), m_completed_tasks.end());
iterator = m_completed_tasks.erase(iterator);
erased_elements++; size_t erased = m_completed_tasks.size() - before;
}
else { LogTasksDetail("[RecordCompletedTask] KeepOneRecord erased [{}] elements", erased);
++iterator;
if (erased > 0)
{
m_last_completed_task_loaded -= erased;
DeleteCompletedTaskFromDatabase(character_id, client_task.task_id);
} }
} }
LogTasksDetail("[UnlockActivities] Erased Element count is [{}]", erased_elements); if (task.type != TaskType::Shared)
{
CompletedTaskInformation completed{};
completed.task_id = client_task.task_id;
completed.completed_time = std::time(nullptr);
if (erased_elements) { for (int i = 0; i < task.activity_count; ++i)
m_last_completed_task_loaded -= erased_elements; {
DeleteCompletedTaskFromDatabase(character_id, task_info.task_id); completed.activity_done[i] = (client_task.activity[i].activity_state == ActivityCompleted);
}
} }
if (p_task_data->type != TaskType::Shared) { LogTasksDetail("[RecordCompletedTask] [{}] for character [{}]", client_task.task_id, character_id);
CompletedTaskInformation completed_task_information{}; m_completed_tasks.push_back(completed);
completed_task_information.task_id = task_info.task_id;
completed_task_information.completed_time = time(nullptr);
for (int activity_id = 0; activity_id < p_task_data->activity_count; activity_id++) {
completed_task_information.activity_done[activity_id] =
(task_info.activity[activity_id].activity_state == ActivityCompleted);
} }
m_completed_tasks.push_back(completed_task_information);
}
}
return true;
}
// Mark all non-completed tasks in the current step as active
for (int activity = 0; activity < p_task_data->activity_count; activity++) {
LogTasksDetail(
"[UnlockActivities] - Debug task [{}] activity [{}] step_number [{}] current_step [{}]",
task_info.task_id,
activity,
p_task_data->activity_information[activity].step_number,
(int) task_info.current_step
);
if ((p_task_data->activity_information[activity].step_number == (int) task_info.current_step) &&
(task_info.activity[activity].activity_state == ActivityHidden)) {
LogTasksDetail(
"[UnlockActivities] -- Debug task [{}] activity [{}] (ActivityActive)",
task_info.task_id,
activity
);
task_info.activity[activity].activity_state = ActivityActive;
task_info.activity[activity].updated = true;
}
}
return false;
} }
bool ClientTaskState::UpdateTasksOnSpeakWith(Client *client, int npc_type_id) bool ClientTaskState::UpdateTasksOnSpeakWith(Client *client, int npc_type_id)
@ -2396,7 +2272,6 @@ void ClientTaskState::AcceptNewTask(
active_slot->task_id = task_id; active_slot->task_id = task_id;
active_slot->accepted_time = static_cast<int>(accept_time); active_slot->accepted_time = static_cast<int>(accept_time);
active_slot->updated = true; active_slot->updated = true;
active_slot->current_step = -1;
active_slot->was_rewarded = false; active_slot->was_rewarded = false;
for (int activity_id = 0; activity_id < task_manager->m_task_data[task_id]->activity_count; activity_id++) { for (int activity_id = 0; activity_id < task_manager->m_task_data[task_id]->activity_count; activity_id++) {

View File

@ -88,6 +88,7 @@ private:
void AddReplayTimer(Client *client, ClientTaskInformation& client_task, TaskInformation& task); void AddReplayTimer(Client *client, ClientTaskInformation& client_task, TaskInformation& task);
void DispatchEventTaskComplete(Client* client, ClientTaskInformation& client_task, int activity_id); void DispatchEventTaskComplete(Client* client, ClientTaskInformation& client_task, int activity_id);
void AddOffer(int task_id, uint16_t npc_entity_id) { m_last_offers.push_back({task_id, npc_entity_id}); }; void AddOffer(int task_id, uint16_t npc_entity_id) { m_last_offers.push_back({task_id, npc_entity_id}); };
void RecordCompletedTask(uint32_t character_id, const TaskInformation& task, const ClientTaskInformation& client_task);
void IncrementDoneCount( void IncrementDoneCount(
Client *client, Client *client,

View File

@ -120,8 +120,6 @@ bool TaskManager::LoadTasks(int single_task)
m_task_data[task_id]->request_timer_group = task.request_timer_group; m_task_data[task_id]->request_timer_group = task.request_timer_group;
m_task_data[task_id]->request_timer_seconds = task.request_timer_seconds; m_task_data[task_id]->request_timer_seconds = task.request_timer_seconds;
m_task_data[task_id]->activity_count = 0; m_task_data[task_id]->activity_count = 0;
m_task_data[task_id]->sequence_mode = ActivitiesSequential;
m_task_data[task_id]->last_step = 0;
LogTasksDetail( LogTasksDetail(
"[LoadTasks] (Task) task_id [{}] type [{}] () duration [{}] duration_code [{}] title [{}] description [{}] " "[LoadTasks] (Task) task_id [{}] type [{}] () duration [{}] duration_code [{}] title [{}] description [{}] "
@ -202,16 +200,6 @@ bool TaskManager::LoadTasks(int single_task)
int activity_index = m_task_data[task_id]->activity_count; int activity_index = m_task_data[task_id]->activity_count;
ActivityInformation *activity_data = &m_task_data[task_id]->activity_information[activity_index]; ActivityInformation *activity_data = &m_task_data[task_id]->activity_information[activity_index];
m_task_data[task_id]->activity_information[m_task_data[task_id]->activity_count].step_number = step;
if (step != 0) {
m_task_data[task_id]->sequence_mode = ActivitiesStepped;
}
if (step > m_task_data[task_id]->last_step) {
m_task_data[task_id]->last_step = step;
}
// Task Activities MUST be numbered sequentially from 0. If not, log an error // Task Activities MUST be numbered sequentially from 0. If not, log an error
// and set the task to nullptr. Subsequent activities for this task will raise // and set the task to nullptr. Subsequent activities for this task will raise
// ERR_NOTASK errors. // ERR_NOTASK errors.
@ -227,6 +215,8 @@ bool TaskManager::LoadTasks(int single_task)
} }
// set activity data // set activity data
activity_data->req_activity_id = task_activity.req_activity_id;
activity_data->step = step;
activity_data->activity_type = static_cast<TaskActivityType>(task_activity.activitytype); activity_data->activity_type = static_cast<TaskActivityType>(task_activity.activitytype);
activity_data->target_name = task_activity.target_name; activity_data->target_name = task_activity.target_name;
activity_data->item_list = task_activity.item_list; activity_data->item_list = task_activity.item_list;
@ -257,7 +247,7 @@ bool TaskManager::LoadTasks(int single_task)
LogTasksDetail( LogTasksDetail(
"[LoadTasks] (Activity) task_id [{}] activity_id [{}] slot [{}] activity_type [{}] goal_id [{}] goal_method [{}] goal_count [{}] zones [{}]" "[LoadTasks] (Activity) task_id [{}] activity_id [{}] slot [{}] activity_type [{}] goal_id [{}] goal_method [{}] goal_count [{}] zones [{}]"
" target_name [{}] item_list [{}] skill_list [{}] spell_list [{}] description_override [{}] sequence [{}]", " target_name [{}] item_list [{}] skill_list [{}] spell_list [{}] description_override [{}]",
task_id, task_id,
activity_id, activity_id,
m_task_data[task_id]->activity_count, m_task_data[task_id]->activity_count,
@ -270,8 +260,7 @@ bool TaskManager::LoadTasks(int single_task)
activity_data->item_list.c_str(), activity_data->item_list.c_str(),
activity_data->skill_list.c_str(), activity_data->skill_list.c_str(),
activity_data->spell_list.c_str(), activity_data->spell_list.c_str(),
activity_data->description_override.c_str(), activity_data->description_override.c_str()
(m_task_data[task_id]->sequence_mode == ActivitiesStepped ? "stepped" : "sequential")
); );
m_task_data[task_id]->activity_count++; m_task_data[task_id]->activity_count++;
@ -852,43 +841,6 @@ int TaskManager::GetActivityCount(int task_id)
return 0; return 0;
} }
void TaskManager::ExplainTask(Client *client, int task_id)
{
// TODO: This method is not finished (hardly started). It was intended to
// explain in English, what each activity_information did, conditions for step unlocking, etc.
//
return;
if (!client) { return; }
if ((task_id <= 0) || (task_id >= MAXTASKS)) {
client->Message(Chat::White, "task_id out-of-range.");
return;
}
if (m_task_data[task_id] == nullptr) {
client->Message(Chat::White, "Task does not exist.");
return;
}
char explanation[1000], *ptr;
client->Message(Chat::White, "Task %4i: title: %s", task_id, m_task_data[task_id]->description.c_str());
client->Message(Chat::White, "%3i Activities", m_task_data[task_id]->activity_count);
ptr = explanation;
for (int i = 0; i < m_task_data[task_id]->activity_count; i++) {
sprintf(ptr, "Act: %3i: ", i);
ptr = ptr + strlen(ptr);
switch (m_task_data[task_id]->activity_information[i].activity_type) {
case TaskActivityType::Deliver:
sprintf(ptr, "Deliver");
break;
}
}
}
bool TaskManager::IsTaskRepeatable(int task_id) bool TaskManager::IsTaskRepeatable(int task_id)
{ {
if ((task_id <= 0) || (task_id >= MAXTASKS)) { if ((task_id <= 0) || (task_id >= MAXTASKS)) {
@ -1322,7 +1274,6 @@ bool TaskManager::LoadClientState(Client *client, ClientTaskState *client_task_s
} }
task_info->task_id = task_id; task_info->task_id = task_id;
task_info->current_step = -1;
task_info->accepted_time = character_task.acceptedtime; task_info->accepted_time = character_task.acceptedtime;
task_info->updated = false; task_info->updated = false;
task_info->was_rewarded = character_task.was_rewarded; task_info->was_rewarded = character_task.was_rewarded;
@ -1568,11 +1519,10 @@ bool TaskManager::LoadClientState(Client *client, ClientTaskState *client_task_s
// purely debugging // purely debugging
LogTasksDetail( LogTasksDetail(
"[LoadClientState] Fetching task info for character_id [{}] task [{}] slot [{}] current_step [{}] accepted_time [{}] updated [{}]", "[LoadClientState] Fetching task info for character_id [{}] task [{}] slot [{}] accepted_time [{}] updated [{}]",
character_id, character_id,
client_task_state->m_active_task.task_id, client_task_state->m_active_task.task_id,
client_task_state->m_active_task.slot, client_task_state->m_active_task.slot,
client_task_state->m_active_task.current_step,
client_task_state->m_active_task.accepted_time, client_task_state->m_active_task.accepted_time,
client_task_state->m_active_task.updated client_task_state->m_active_task.updated
); );
@ -1582,14 +1532,13 @@ bool TaskManager::LoadClientState(Client *client, ClientTaskState *client_task_s
for (int i = 0; i < p_task_data->activity_count; i++) { for (int i = 0; i < p_task_data->activity_count; i++) {
if (client_task_state->m_active_task.activity[i].activity_id >= 0) { if (client_task_state->m_active_task.activity[i].activity_id >= 0) {
LogTasksDetail( LogTasksDetail(
"[LoadClientState] -- character_id [{}] task [{}] activity_id [{}] done_count [{}] activity_state [{}] updated [{}] sequence [{}]", "[LoadClientState] -- character_id [{}] task [{}] activity_id [{}] done_count [{}] activity_state [{}] updated [{}]",
character_id, character_id,
client_task_state->m_active_task.task_id, client_task_state->m_active_task.task_id,
client_task_state->m_active_task.activity[i].activity_id, client_task_state->m_active_task.activity[i].activity_id,
client_task_state->m_active_task.activity[i].done_count, client_task_state->m_active_task.activity[i].done_count,
client_task_state->m_active_task.activity[i].activity_state, client_task_state->m_active_task.activity[i].activity_state,
client_task_state->m_active_task.activity[i].updated, client_task_state->m_active_task.activity[i].updated
p_task_data->sequence_mode
); );
} }
} }

View File

@ -61,7 +61,6 @@ public:
bool task_complete = false bool task_complete = false
); );
void SendCompletedTasksToClient(Client *c, ClientTaskState *client_task_state); void SendCompletedTasksToClient(Client *c, ClientTaskState *client_task_state);
void ExplainTask(Client *client, int task_id);
int FirstTaskInSet(int task_set); int FirstTaskInSet(int task_set);
int LastTaskInSet(int task_set); int LastTaskInSet(int task_set);
int NextTaskInSet(int task_set, int task_id); int NextTaskInSet(int task_set, int task_id);