From e65b61a95ff8e1ec38a6fd1d6aeed09159447980 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Sun, 21 Aug 2022 20:55:19 -0400 Subject: [PATCH] [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. --- .../base/base_task_activities_repository.h | 130 +- common/shared_tasks.h | 3 + common/tasks.h | 86 +- common/version.h | 2 +- tests/CMakeLists.txt | 1 + tests/main.cpp | 2 + tests/task_state_test.h | 1183 +++++++++++++++++ utils/sql/db_update_manifest.txt | 1 + .../2022_08_08_task_req_activity_id.sql | 2 + world/shared_task_manager.cpp | 42 +- zone/task_client_state.cpp | 249 +--- zone/task_client_state.h | 1 + zone/task_manager.cpp | 65 +- zone/task_manager.h | 1 - 14 files changed, 1421 insertions(+), 347 deletions(-) create mode 100644 tests/task_state_test.h create mode 100644 utils/sql/git/required/2022_08_08_task_req_activity_id.sql diff --git a/common/repositories/base/base_task_activities_repository.h b/common/repositories/base/base_task_activities_repository.h index 3856aae17..c908c25a9 100644 --- a/common/repositories/base/base_task_activities_repository.h +++ b/common/repositories/base/base_task_activities_repository.h @@ -21,6 +21,7 @@ public: struct TaskActivities { int taskid; int activityid; + int req_activity_id; int step; int activitytype; std::string target_name; @@ -48,6 +49,7 @@ public: return { "taskid", "activityid", + "req_activity_id", "step", "activitytype", "target_name", @@ -71,6 +73,7 @@ public: return { "taskid", "activityid", + "req_activity_id", "step", "activitytype", "target_name", @@ -128,6 +131,7 @@ public: e.taskid = 0; e.activityid = 0; + e.req_activity_id = -1; e.step = 0; e.activitytype = 0; e.target_name = ""; @@ -180,21 +184,22 @@ public: e.taskid = atoi(row[0]); e.activityid = atoi(row[1]); - e.step = atoi(row[2]); - e.activitytype = atoi(row[3]); - e.target_name = row[4] ? row[4] : ""; - e.item_list = row[5] ? row[5] : ""; - e.skill_list = row[6] ? row[6] : ""; - e.spell_list = row[7] ? row[7] : ""; - e.description_override = row[8] ? row[8] : ""; - e.goalid = atoi(row[9]); - e.goal_match_list = row[10] ? row[10] : ""; - e.goalmethod = atoi(row[11]); - e.goalcount = atoi(row[12]); - e.delivertonpc = atoi(row[13]); - e.zones = row[14] ? row[14] : ""; - e.zone_version = atoi(row[15]); - e.optional = atoi(row[16]); + e.req_activity_id = atoi(row[2]); + e.step = atoi(row[3]); + e.activitytype = atoi(row[4]); + e.target_name = row[5] ? row[5] : ""; + e.item_list = row[6] ? row[6] : ""; + e.skill_list = row[7] ? row[7] : ""; + e.spell_list = row[8] ? row[8] : ""; + e.description_override = row[9] ? row[9] : ""; + e.goalid = atoi(row[10]); + e.goal_match_list = row[11] ? row[11] : ""; + e.goalmethod = atoi(row[12]); + e.goalcount = atoi(row[13]); + e.delivertonpc = atoi(row[14]); + e.zones = row[15] ? row[15] : ""; + e.zone_version = atoi(row[16]); + e.optional = atoi(row[17]); return e; } @@ -230,21 +235,22 @@ public: v.push_back(columns[0] + " = " + std::to_string(e.taskid)); v.push_back(columns[1] + " = " + std::to_string(e.activityid)); - v.push_back(columns[2] + " = " + std::to_string(e.step)); - v.push_back(columns[3] + " = " + std::to_string(e.activitytype)); - v.push_back(columns[4] + " = '" + Strings::Escape(e.target_name) + "'"); - v.push_back(columns[5] + " = '" + Strings::Escape(e.item_list) + "'"); - v.push_back(columns[6] + " = '" + Strings::Escape(e.skill_list) + "'"); - v.push_back(columns[7] + " = '" + Strings::Escape(e.spell_list) + "'"); - v.push_back(columns[8] + " = '" + Strings::Escape(e.description_override) + "'"); - v.push_back(columns[9] + " = " + std::to_string(e.goalid)); - v.push_back(columns[10] + " = '" + Strings::Escape(e.goal_match_list) + "'"); - v.push_back(columns[11] + " = " + std::to_string(e.goalmethod)); - v.push_back(columns[12] + " = " + std::to_string(e.goalcount)); - v.push_back(columns[13] + " = " + std::to_string(e.delivertonpc)); - v.push_back(columns[14] + " = '" + Strings::Escape(e.zones) + "'"); - v.push_back(columns[15] + " = " + std::to_string(e.zone_version)); - v.push_back(columns[16] + " = " + std::to_string(e.optional)); + v.push_back(columns[2] + " = " + std::to_string(e.req_activity_id)); + v.push_back(columns[3] + " = " + std::to_string(e.step)); + v.push_back(columns[4] + " = " + std::to_string(e.activitytype)); + v.push_back(columns[5] + " = '" + Strings::Escape(e.target_name) + "'"); + v.push_back(columns[6] + " = '" + Strings::Escape(e.item_list) + "'"); + v.push_back(columns[7] + " = '" + Strings::Escape(e.skill_list) + "'"); + v.push_back(columns[8] + " = '" + Strings::Escape(e.spell_list) + "'"); + v.push_back(columns[9] + " = '" + Strings::Escape(e.description_override) + "'"); + v.push_back(columns[10] + " = " + std::to_string(e.goalid)); + v.push_back(columns[11] + " = '" + Strings::Escape(e.goal_match_list) + "'"); + v.push_back(columns[12] + " = " + std::to_string(e.goalmethod)); + v.push_back(columns[13] + " = " + std::to_string(e.goalcount)); + v.push_back(columns[14] + " = " + std::to_string(e.delivertonpc)); + v.push_back(columns[15] + " = '" + Strings::Escape(e.zones) + "'"); + 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( fmt::format( @@ -268,6 +274,7 @@ public: v.push_back(std::to_string(e.taskid)); 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.activitytype)); 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.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.activitytype)); v.push_back("'" + Strings::Escape(e.target_name) + "'"); @@ -364,21 +372,22 @@ public: e.taskid = atoi(row[0]); e.activityid = atoi(row[1]); - e.step = atoi(row[2]); - e.activitytype = atoi(row[3]); - e.target_name = row[4] ? row[4] : ""; - e.item_list = row[5] ? row[5] : ""; - e.skill_list = row[6] ? row[6] : ""; - e.spell_list = row[7] ? row[7] : ""; - e.description_override = row[8] ? row[8] : ""; - e.goalid = atoi(row[9]); - e.goal_match_list = row[10] ? row[10] : ""; - e.goalmethod = atoi(row[11]); - e.goalcount = atoi(row[12]); - e.delivertonpc = atoi(row[13]); - e.zones = row[14] ? row[14] : ""; - e.zone_version = atoi(row[15]); - e.optional = atoi(row[16]); + e.req_activity_id = atoi(row[2]); + e.step = atoi(row[3]); + e.activitytype = atoi(row[4]); + e.target_name = row[5] ? row[5] : ""; + e.item_list = row[6] ? row[6] : ""; + e.skill_list = row[7] ? row[7] : ""; + e.spell_list = row[8] ? row[8] : ""; + e.description_override = row[9] ? row[9] : ""; + e.goalid = atoi(row[10]); + e.goal_match_list = row[11] ? row[11] : ""; + e.goalmethod = atoi(row[12]); + e.goalcount = atoi(row[13]); + e.delivertonpc = atoi(row[14]); + e.zones = row[15] ? row[15] : ""; + e.zone_version = atoi(row[16]); + e.optional = atoi(row[17]); all_entries.push_back(e); } @@ -405,21 +414,22 @@ public: e.taskid = atoi(row[0]); e.activityid = atoi(row[1]); - e.step = atoi(row[2]); - e.activitytype = atoi(row[3]); - e.target_name = row[4] ? row[4] : ""; - e.item_list = row[5] ? row[5] : ""; - e.skill_list = row[6] ? row[6] : ""; - e.spell_list = row[7] ? row[7] : ""; - e.description_override = row[8] ? row[8] : ""; - e.goalid = atoi(row[9]); - e.goal_match_list = row[10] ? row[10] : ""; - e.goalmethod = atoi(row[11]); - e.goalcount = atoi(row[12]); - e.delivertonpc = atoi(row[13]); - e.zones = row[14] ? row[14] : ""; - e.zone_version = atoi(row[15]); - e.optional = atoi(row[16]); + e.req_activity_id = atoi(row[2]); + e.step = atoi(row[3]); + e.activitytype = atoi(row[4]); + e.target_name = row[5] ? row[5] : ""; + e.item_list = row[6] ? row[6] : ""; + e.skill_list = row[7] ? row[7] : ""; + e.spell_list = row[8] ? row[8] : ""; + e.description_override = row[9] ? row[9] : ""; + e.goalid = atoi(row[10]); + e.goal_match_list = row[11] ? row[11] : ""; + e.goalmethod = atoi(row[12]); + e.goalcount = atoi(row[13]); + e.delivertonpc = atoi(row[14]); + e.zones = row[15] ? row[15] : ""; + e.zone_version = atoi(row[16]); + e.optional = atoi(row[17]); all_entries.push_back(e); } diff --git a/common/shared_tasks.h b/common/shared_tasks.h index 0d16ce8ec..119cdb826 100644 --- a/common/shared_tasks.h +++ b/common/shared_tasks.h @@ -3,6 +3,7 @@ #include "database.h" #include "timer.h" +#include "tasks.h" #include "types.h" #include "repositories/character_data_repository.h" #include "repositories/tasks_repository.h" @@ -108,8 +109,10 @@ struct SharedTaskActivityStateEntry { uint32 max_done_count; // goalcount uint32 updated_time; uint32 completed_time; + int req_activity_id; int step; bool optional; + ActivityState activity_state; // world only uses Hidden and Completed states }; struct ServerSharedTaskActivityUpdate_Struct { diff --git a/common/tasks.h b/common/tasks.h index e68fe6ddf..62f537668 100644 --- a/common/tasks.h +++ b/common/tasks.h @@ -2,6 +2,8 @@ #define EQEMU_TASKS_H #include "serialize_buffer.h" +#include +#include #define MAXTASKS 10000 #define MAXTASKSETS 1000 @@ -65,7 +67,8 @@ enum class AltCurrencyType }; struct ActivityInformation { - int step_number; + int req_activity_id; + int step; TaskActivityType activity_type; std::string target_name; // name mob, location -- default empty, max length 64 std::string item_list; // likely defaults to empty @@ -175,11 +178,6 @@ struct ActivityInformation { } }; -typedef enum { - ActivitiesSequential = 0, - ActivitiesStepped = 1 -} SequenceType; - enum class TaskType { Task = 0, // can have at max 1 Shared = 1, // can have at max 1 @@ -215,8 +213,6 @@ struct TaskInformation { int reward_points; AltCurrencyType reward_point_type; int activity_count{}; - SequenceType sequence_mode; - int last_step{}; short min_level{}; short max_level{}; int level_spread; @@ -270,7 +266,6 @@ struct ClientActivityInformation { struct ClientTaskInformation { int slot; // intrusive, but makes things easier :P int task_id; - int current_step; int accepted_time; bool updated; bool was_rewarded; // character has received reward for this task @@ -342,6 +337,79 @@ namespace Tasks { return "Task"; } } + + struct ActiveElements + { + bool is_task_complete; + std::vector active; + }; + + // Processes task activity states and returns those currently active + // It is templated to support the different structs used by zone and world + template + 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 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::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 { diff --git a/common/version.h b/common/version.h index 4fe0406f6..f01324ebb 100644 --- a/common/version.h +++ b/common/version.h @@ -34,7 +34,7 @@ * 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 #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9029 diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 41c7aa547..811890bb7 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -16,6 +16,7 @@ SET(tests_headers memory_mapped_file_test.h string_util_test.h skills_util_test.h + task_state_test.h ) ADD_EXECUTABLE(tests ${tests_sources} ${tests_headers}) diff --git a/tests/main.cpp b/tests/main.cpp index 9d72da520..13f49f238 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -29,6 +29,7 @@ #include "string_util_test.h" #include "data_verification_test.h" #include "skills_util_test.h" +#include "task_state_test.h" #include "../common/eqemu_config.h" const EQEmuConfig *Config; @@ -49,6 +50,7 @@ int main() { tests.add(new StringUtilTest()); tests.add(new DataVerificationTest()); tests.add(new SkillsUtilsTest()); + tests.add(new TaskStateTest()); tests.run(*output, true); } catch(...) { return -1; diff --git a/tests/task_state_test.h b/tests/task_state_test.h new file mode 100644 index 000000000..d1a0551b6 --- /dev/null +++ b/tests/task_state_test.h @@ -0,0 +1,1183 @@ +#pragma once + +#include "cppunit/cpptest.h" +#include "../common/eqemu_logsys.h" +#include "../common/tasks.h" +#include "../common/shared_tasks.h" + +class TaskStateTest: public Test::Suite +{ +public: + TaskStateTest() + { + TEST_ADD(TaskStateTest::TestSequenceMode); + TEST_ADD(TaskStateTest::TestSteps); + TEST_ADD(TaskStateTest::TestStepGaps); + TEST_ADD(TaskStateTest::TestUnorderedSteps); + TEST_ADD(TaskStateTest::TestOptionalSteps); + TEST_ADD(TaskStateTest::TestOptionalLastSteps); + TEST_ADD(TaskStateTest::TestOptionalSequence); + TEST_ADD(TaskStateTest::TestWorldTemplateSupport); + TEST_ADD(TaskStateTest::TestReqActivityID); + TEST_ADD(TaskStateTest::TestReqActivityIDOverrideStep); + TEST_ADD(TaskStateTest::TestReqActivityIDSteps); + TEST_ADD(TaskStateTest::TestReqActivityIDUnorderedSteps); + TEST_ADD(TaskStateTest::TestReqActivityIDMixSteps); + TEST_ADD(TaskStateTest::TestReqActivityIDSequenceMode); + TEST_ADD(TaskStateTest::TestReqActivityIDOptional); + TEST_ADD(TaskStateTest::TestReqActivityIDOptionalLastSteps); + } + +private: + void TestSequenceMode(); + void TestSteps(); + void TestStepGaps(); + void TestUnorderedSteps(); + void TestOptionalSteps(); + void TestOptionalLastSteps(); + void TestOptionalSequence(); + void TestWorldTemplateSupport(); + void TestReqActivityID(); + void TestReqActivityIDOverrideStep(); + void TestReqActivityIDSteps(); + void TestReqActivityIDUnorderedSteps(); + void TestReqActivityIDMixSteps(); + void TestReqActivityIDSequenceMode(); + void TestReqActivityIDOptional(); + void TestReqActivityIDOptionalLastSteps(); + + TaskInformation GetMockZoneData(int count) + { + TaskInformation task; + task.activity_count = count; + for (int i = 0; i < task.activity_count; ++i) + { + task.activity_information[i].req_activity_id = -1; + task.activity_information[i].step = 0; + task.activity_information[i].optional = false; + } + return task; + } + + ClientTaskInformation GetMockZoneState(int count) + { + ClientTaskInformation state; + for (int i = 0; i < count; ++i) + { + state.activity[i].activity_id = i; + state.activity[i].activity_state = ActivityState::ActivityHidden; + } + return state; + } + + std::vector GetMockWorldData(int count) + { + std::vector data; + data.resize(count); + for (int i = 0; i < count; ++i) + { + data[i].activityid = i; + data[i].req_activity_id = -1; + data[i].step = 0; + data[i].optional = false; + } + return data; + } + + std::vector GetMockWorldState(int count) + { + std::vector states; + states.resize(count); + for (int i = 0; i < count; ++i) + { + states[i].activity_id = i; + states[i].req_activity_id = -1; + states[i].step = 0; + states[i].optional = false; + states[i].activity_state = ActivityState::ActivityHidden; + } + return states; + } +}; + +void TaskStateTest::TestSequenceMode() +{ + int activity_count = 3; + TaskInformation data = GetMockZoneData(activity_count); + ClientTaskInformation state = GetMockZoneState(activity_count); + + { + auto res = Tasks::GetActiveElements(data.activity_information, state.activity, activity_count); + + // activity_id | step | state + // 0 | 0 | hidden (active) + // 1 | 0 | hidden + // 2 | 0 | hidden + + TEST_ASSERT(res.is_task_complete == false); + TEST_ASSERT(res.active.size() == 1); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 0) != res.active.end()); + } + + { + state.activity[0].activity_state = ActivityState::ActivityCompleted; + + auto res = Tasks::GetActiveElements(data.activity_information, state.activity, activity_count); + + // activity_id | step | state + // 0 | 0 | completed + // 1 | 0 | hidden (active) + // 2 | 0 | hidden + + TEST_ASSERT(res.is_task_complete == false); + TEST_ASSERT(res.active.size() == 1); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 1) != res.active.end()); + } + + { + state.activity[0].activity_state = ActivityState::ActivityCompleted; + state.activity[1].activity_state = ActivityState::ActivityCompleted; + + auto res = Tasks::GetActiveElements(data.activity_information, state.activity, activity_count); + + // activity_id |step | state + // 0 | 0 | completed + // 1 | 0 | completed + // 2 | 0 | hidden (active) + + TEST_ASSERT(res.is_task_complete == false); + TEST_ASSERT(res.active.size() == 1); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 2) != res.active.end()); + } + + { + state.activity[0].activity_state = ActivityState::ActivityCompleted; + state.activity[1].activity_state = ActivityState::ActivityCompleted; + state.activity[2].activity_state = ActivityState::ActivityCompleted; + + auto res = Tasks::GetActiveElements(data.activity_information, state.activity, activity_count); + + // activity_id | step | state + // 0 | 0 | completed + // 1 | 0 | completed + // 2 | 0 | completed + + // task completed, none should be active + TEST_ASSERT(res.is_task_complete == true); + TEST_ASSERT(res.active.empty()); + } + + { + state.activity[0].activity_state = ActivityState::ActivityCompleted; + state.activity[1].activity_state = ActivityState::ActivityCompleted; + state.activity[2].activity_state = ActivityState::ActivityHidden; + data.activity_information[2].optional = true; + + auto res = Tasks::GetActiveElements(data.activity_information, state.activity, activity_count); + + // activity_id | step | state + // 0 | 0 | completed + // 1 | 0 | completed + // 2 | 0 | hidden | optional (active) + + TEST_ASSERT(res.is_task_complete == true); + TEST_ASSERT(res.active.size() == 1); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 2) != res.active.end()); + } +} + +void TaskStateTest::TestSteps() +{ + int activity_count = 4; + TaskInformation data = GetMockZoneData(activity_count); + ClientTaskInformation state = GetMockZoneState(activity_count); + data.activity_information[1].step = 1; + data.activity_information[2].step = 1; + data.activity_information[3].step = 2; + + { + auto res = Tasks::GetActiveElements(data.activity_information, state.activity, activity_count); + + // activity_id | step | state + // 0 | 0 | hidden (active) + // 1 | 1 | hidden + // 2 | 1 | hidden + // 3 | 2 | hidden + + TEST_ASSERT(res.is_task_complete == false); + TEST_ASSERT(res.active.size() == 1); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 0) != res.active.end()); + } + + { + state.activity[0].activity_state = ActivityState::ActivityCompleted; + + auto res = Tasks::GetActiveElements(data.activity_information, state.activity, activity_count); + + // activity_id | step | state + // 0 | 0 | completed + // 1 | 1 | hidden (active) + // 2 | 1 | hidden (active) + // 3 | 2 | hidden + + TEST_ASSERT(res.is_task_complete == false); + TEST_ASSERT(res.active.size() == 2); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 1) != res.active.end()); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 2) != res.active.end()); + } + + { + state.activity[0].activity_state = ActivityState::ActivityCompleted; + state.activity[1].activity_state = ActivityState::ActivityCompleted; + + auto res = Tasks::GetActiveElements(data.activity_information, state.activity, activity_count); + + // activity_id | step | state + // 0 | 0 | completed + // 1 | 1 | completed + // 2 | 1 | hidden (active) + // 3 | 2 | hidden + + TEST_ASSERT(res.is_task_complete == false); + TEST_ASSERT(res.active.size() == 1); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 2) != res.active.end()); + } + + { + state.activity[0].activity_state = ActivityState::ActivityCompleted; + state.activity[1].activity_state = ActivityState::ActivityCompleted; + state.activity[2].activity_state = ActivityState::ActivityCompleted; + + auto res = Tasks::GetActiveElements(data.activity_information, state.activity, activity_count); + + // activity_id | step | state + // 0 | 0 | completed + // 1 | 1 | completed + // 2 | 1 | completed + // 3 | 2 | hidden (active) + + TEST_ASSERT(res.is_task_complete == false); + TEST_ASSERT(res.active.size() == 1); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 3) != res.active.end()); + } + + { + state.activity[0].activity_state = ActivityState::ActivityCompleted; + state.activity[1].activity_state = ActivityState::ActivityCompleted; + state.activity[2].activity_state = ActivityState::ActivityCompleted; + state.activity[3].activity_state = ActivityState::ActivityCompleted; + + auto res = Tasks::GetActiveElements(data.activity_information, state.activity, activity_count); + + // activity_id | step | state + // 0 | 0 | completed + // 1 | 1 | completed + // 2 | 1 | completed + // 3 | 2 | completed + + // should be complete with none active + TEST_ASSERT(res.is_task_complete == true); + TEST_ASSERT(res.active.empty()); + } + + { + state.activity[0].activity_state = ActivityState::ActivityCompleted; + state.activity[1].activity_state = ActivityState::ActivityCompleted; + state.activity[2].activity_state = ActivityState::ActivityCompleted; + state.activity[3].activity_state = ActivityState::ActivityHidden; + data.activity_information[3].optional = true; + + auto res = Tasks::GetActiveElements(data.activity_information, state.activity, activity_count); + + // activity_id | step | state + // 0 | 0 | completed + // 1 | 1 | completed + // 2 | 1 | completed + // 3 | 2 | hidden | optional (active) + + TEST_ASSERT(res.is_task_complete == true); + TEST_ASSERT(res.active.size() == 1); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 3) != res.active.end()); + } +} + +void TaskStateTest::TestStepGaps() +{ + int activity_count = 5; + TaskInformation data = GetMockZoneData(activity_count); + ClientTaskInformation state = GetMockZoneState(activity_count); + data.activity_information[0].step = 1; + data.activity_information[1].step = 5; + data.activity_information[2].step = 5; + data.activity_information[3].step = 100; + data.activity_information[4].step = 100; + + { + auto res = Tasks::GetActiveElements(data.activity_information, state.activity, activity_count); + + // index 0 should be active starting at step 1 + TEST_ASSERT(res.is_task_complete == false); + TEST_ASSERT(res.active.size() == 1); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 0) != res.active.end()); + } + + { + state.activity[0].activity_state = ActivityState::ActivityCompleted; + + auto res = Tasks::GetActiveElements(data.activity_information, state.activity, activity_count); + + // indexes 1 and 2 should be active at step 5 + TEST_ASSERT(res.is_task_complete == false); + TEST_ASSERT(res.active.size() == 2); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 1) != res.active.end()); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 2) != res.active.end()); + } + + { + state.activity[0].activity_state = ActivityState::ActivityCompleted; + state.activity[1].activity_state = ActivityState::ActivityCompleted; + + auto res = Tasks::GetActiveElements(data.activity_information, state.activity, activity_count); + + // only index 2 should be active with step 5 since index 1 is completed + TEST_ASSERT(res.is_task_complete == false); + TEST_ASSERT(res.active.size() == 1); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 2) != res.active.end()); + } + + { + state.activity[0].activity_state = ActivityState::ActivityCompleted; + state.activity[1].activity_state = ActivityState::ActivityCompleted; + state.activity[2].activity_state = ActivityState::ActivityCompleted; + + auto res = Tasks::GetActiveElements(data.activity_information, state.activity, activity_count); + + // indexes 3 and 4 should be active for step 100 + TEST_ASSERT(res.is_task_complete == false); + TEST_ASSERT(res.active.size() == 2); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 3) != res.active.end()); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 4) != res.active.end()); + } + + { + state.activity[0].activity_state = ActivityState::ActivityCompleted; + state.activity[1].activity_state = ActivityState::ActivityCompleted; + state.activity[2].activity_state = ActivityState::ActivityCompleted; + state.activity[3].activity_state = ActivityState::ActivityCompleted; + state.activity[4].activity_state = ActivityState::ActivityCompleted; + + auto res = Tasks::GetActiveElements(data.activity_information, state.activity, activity_count); + + TEST_ASSERT(res.is_task_complete == true); + TEST_ASSERT(res.active.empty()); + } +} + +void TaskStateTest::TestUnorderedSteps() +{ + int activity_count = 5; + TaskInformation data = GetMockZoneData(activity_count); + ClientTaskInformation state = GetMockZoneState(activity_count); + data.activity_information[0].step = 100; + data.activity_information[1].step = 100; + data.activity_information[2].step = 3; + data.activity_information[3].step = 20; + data.activity_information[4].step = 1; + + { + auto res = Tasks::GetActiveElements(data.activity_information, state.activity, activity_count); + + // index 4 should be active as the lowest step + TEST_ASSERT(res.is_task_complete == false); + TEST_ASSERT(res.active.size() == 1); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 4) != res.active.end()); + } + + { + state.activity[4].activity_state = ActivityState::ActivityCompleted; + + auto res = Tasks::GetActiveElements(data.activity_information, state.activity, activity_count); + + // index 2 should be the next lowest step (3) after step 1 completed + TEST_ASSERT(res.is_task_complete == false); + TEST_ASSERT(res.active.size() == 1); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 2) != res.active.end()); + } + + { + state.activity[4].activity_state = ActivityState::ActivityCompleted; + state.activity[2].activity_state = ActivityState::ActivityCompleted; + + auto res = Tasks::GetActiveElements(data.activity_information, state.activity, activity_count); + + // index 3 should be active as step 20 + TEST_ASSERT(res.is_task_complete == false); + TEST_ASSERT(res.active.size() == 1); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 3) != res.active.end()); + } + + { + state.activity[4].activity_state = ActivityState::ActivityCompleted; + state.activity[2].activity_state = ActivityState::ActivityCompleted; + state.activity[3].activity_state = ActivityState::ActivityCompleted; + + auto res = Tasks::GetActiveElements(data.activity_information, state.activity, activity_count); + + // indexes 0 and 1 should both be active as step 100 + TEST_ASSERT(res.is_task_complete == false); + TEST_ASSERT(res.active.size() == 2); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 0) != res.active.end()); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 1) != res.active.end()); + } +} + +void TaskStateTest::TestOptionalSteps() +{ + int activity_count = 4; + auto data = GetMockWorldData(activity_count); + auto state = GetMockWorldState(activity_count); + data[0].step = 0; + data[1].step = 1; + data[1].optional = true; + data[2].step = 2; + data[2].optional = true; + data[3].step = 2; + + { + auto res = Tasks::GetActiveElements(data, state, activity_count); + + // activity_id | step | state + // 0 | 0 | hidden (active) + // 1 | 1 | hidden | optional + // 2 | 2 | hidden | optional + // 3 | 2 | hidden + + TEST_ASSERT(res.is_task_complete == false); + TEST_ASSERT(res.active.size() == 1); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 0) != res.active.end()); + } + + { + state[0].activity_state = ActivityState::ActivityCompleted; + + // activity_id | step | state + // 0 | 0 | complete + // 1 | 1 | hidden | optional (active) + // 2 | 2 | hidden | optional + // 3 | 2 | hidden + + auto res = Tasks::GetActiveElements(data, state, activity_count); + + // since optional is on its own step it's effectively non-optional to open next step + TEST_ASSERT(res.is_task_complete == false); + TEST_ASSERT(res.active.size() == 1); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 1) != res.active.end()); + } + + { + state[0].activity_state = ActivityState::ActivityCompleted; + state[1].activity_state = ActivityState::ActivityCompleted; + + // activity_id | step | state + // 0 | 0 | complete + // 1 | 1 | complete | optional + // 2 | 2 | hidden | optional (active) + // 3 | 2 | hidden (active) + + auto res = Tasks::GetActiveElements(data, state, activity_count); + + TEST_ASSERT(res.is_task_complete == false); + TEST_ASSERT(res.active.size() == 2); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 2) != res.active.end()); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 3) != res.active.end()); + } + + { + state[0].activity_state = ActivityState::ActivityCompleted; + state[1].activity_state = ActivityState::ActivityCompleted; + state[3].activity_state = ActivityState::ActivityCompleted; + + // activity_id | step | state + // 0 | 0 | complete + // 1 | 1 | complete | optional + // 2 | 2 | hidden | optional (active) + // 3 | 2 | complete + + auto res = Tasks::GetActiveElements(data, state, activity_count); + + TEST_ASSERT(res.is_task_complete == true); + TEST_ASSERT(res.active.size() == 1); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 2) != res.active.end()); + } + + { + state[0].activity_state = ActivityState::ActivityCompleted; + state[1].activity_state = ActivityState::ActivityCompleted; + state[2].activity_state = ActivityState::ActivityCompleted; + state[3].activity_state = ActivityState::ActivityCompleted; + + auto res = Tasks::GetActiveElements(data, state, activity_count); + + TEST_ASSERT(res.is_task_complete == true); + TEST_ASSERT(res.active.empty()); + } +} + +void TaskStateTest::TestOptionalLastSteps() +{ + int activity_count = 3; + auto data = GetMockWorldData(activity_count); + auto state = GetMockWorldState(activity_count); + data[0].step = 0; + data[1].optional = true; + data[1].step = 2; + data[2].optional = true; + data[2].step = 3; + + { + // activity_id | step | state + // 0 | 0 | hidden (active) + // 1 | 1 | hidden | optional + // 2 | 2 | hidden | optional + + auto res = Tasks::GetActiveElements(data, state, activity_count); + + TEST_ASSERT(res.is_task_complete == false); + TEST_ASSERT(res.active.size() == 1); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 0) != res.active.end()); + } + + { + state[0].activity_state = ActivityState::ActivityCompleted; + + // activity_id | step | state + // 0 | 0 | complete + // 1 | 1 | hidden | optional (active) + // 2 | 2 | hidden | optional + + auto res = Tasks::GetActiveElements(data, state, activity_count); + + TEST_ASSERT(res.is_task_complete == true); + TEST_ASSERT(res.active.size() == 1); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 1) != res.active.end()); + } + + { + state[0].activity_state = ActivityState::ActivityCompleted; + state[1].activity_state = ActivityState::ActivityCompleted; + + // activity_id | step | state + // 0 | 0 | complete + // 1 | 1 | complete | optional + // 2 | 2 | hidden | optional (active) + + auto res = Tasks::GetActiveElements(data, state, activity_count); + + TEST_ASSERT(res.is_task_complete == true); + TEST_ASSERT(res.active.size() == 1); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 2) != res.active.end()); + } +} + +void TaskStateTest::TestOptionalSequence() +{ + int activity_count = 3; + auto data = GetMockWorldData(activity_count); + auto state = GetMockWorldState(activity_count); + data[0].step = 0; + data[1].step = 0; + data[1].optional = true; + data[2].step = 0; + + { + // activity_id | step | state + // 0 | 0 | hidden (active) + // 1 | 0 | hidden | optional + // 2 | 0 | hidden + + auto res = Tasks::GetActiveElements(data, state, activity_count); + + TEST_ASSERT(res.is_task_complete == false); + TEST_ASSERT(res.active.size() == 1); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 0) != res.active.end()); + } + + { + state[0].activity_state = ActivityState::ActivityCompleted; + + // activity_id | step | state + // 0 | 0 | complete + // 1 | 0 | hidden | optional (active) + // 2 | 0 | hidden (active) + + auto res = Tasks::GetActiveElements(data, state, activity_count); + + TEST_ASSERT(res.is_task_complete == false); + TEST_ASSERT(res.active.size() == 2); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 1) != res.active.end()); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 2) != res.active.end()); + } + + { + state[0].activity_state = ActivityState::ActivityCompleted; + state[1].activity_state = ActivityState::ActivityCompleted; + + // activity_id | step | state + // 0 | 0 | complete + // 1 | 0 | complete + // 2 | 0 | hidden (active) + + auto res = Tasks::GetActiveElements(data, state, activity_count); + + TEST_ASSERT(res.is_task_complete == false); + TEST_ASSERT(res.active.size() == 1); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 2) != res.active.end()); + } + + { + state[0].activity_state = ActivityState::ActivityCompleted; + state[1].activity_state = ActivityState::ActivityHidden; + state[2].activity_state = ActivityState::ActivityCompleted; + + // activity_id | step | state + // 0 | 0 | complete + // 1 | 0 | hidden | optional (active) + // 2 | 0 | complete + + auto res = Tasks::GetActiveElements(data, state, activity_count); + + TEST_ASSERT(res.is_task_complete == true); + TEST_ASSERT(res.active.size() == 1); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 1) != res.active.end()); + } + + { + state[0].activity_state = ActivityState::ActivityCompleted; + state[1].activity_state = ActivityState::ActivityCompleted; + state[2].activity_state = ActivityState::ActivityCompleted; + + auto res = Tasks::GetActiveElements(data, state, activity_count); + + TEST_ASSERT(res.is_task_complete == true); + TEST_ASSERT(res.active.empty()); + } +} + +void TaskStateTest::TestWorldTemplateSupport() +{ + int activity_count = 3; + auto data = GetMockWorldData(activity_count); + auto state = GetMockWorldState(activity_count); + data[0].step = 1; + data[1].step = 5; + data[2].step = 10; + state[0].step = 1; + state[1].step = 5; + state[2].step = 10; + state[0].activity_state = ActivityState::ActivityCompleted; + + { + auto res = Tasks::GetActiveElements(data, state, activity_count); + + TEST_ASSERT(res.is_task_complete == false); + TEST_ASSERT(res.active.size() == 1); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 1) != res.active.end()); + } + + { + // using the state struct as both data and state source + auto res = Tasks::GetActiveElements(state, state, activity_count); + + TEST_ASSERT(res.is_task_complete == false); + TEST_ASSERT(res.active.size() == 1); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 1) != res.active.end()); + } +} + +void TaskStateTest::TestReqActivityID() +{ + int activity_count = 5; + TaskInformation data = GetMockZoneData(activity_count); + ClientTaskInformation state = GetMockZoneState(activity_count); + data.activity_information[1].req_activity_id = 0; + data.activity_information[2].req_activity_id = 0; + data.activity_information[3].req_activity_id = 1; + data.activity_information[4].req_activity_id = 2; + + { + auto res = Tasks::GetActiveElements(data.activity_information, state.activity, activity_count); + + TEST_ASSERT(res.is_task_complete == false); + TEST_ASSERT(res.active.size() == 1); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 0) != res.active.end()); + } + + { + state.activity[0].activity_state = ActivityState::ActivityCompleted; + + auto res = Tasks::GetActiveElements(data.activity_information, state.activity, activity_count); + + TEST_ASSERT(res.is_task_complete == false); + TEST_ASSERT(res.active.size() == 2); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 1) != res.active.end()); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 2) != res.active.end()); + } + + { + state.activity[0].activity_state = ActivityState::ActivityCompleted; + state.activity[2].activity_state = ActivityState::ActivityCompleted; + + auto res = Tasks::GetActiveElements(data.activity_information, state.activity, activity_count); + + TEST_ASSERT(res.is_task_complete == false); + TEST_ASSERT(res.active.size() == 2); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 1) != res.active.end()); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 4) != res.active.end()); + } + + { + state.activity[0].activity_state = ActivityState::ActivityCompleted; + state.activity[2].activity_state = ActivityState::ActivityCompleted; + state.activity[1].activity_state = ActivityState::ActivityCompleted; + + auto res = Tasks::GetActiveElements(data.activity_information, state.activity, activity_count); + + TEST_ASSERT(res.is_task_complete == false); + TEST_ASSERT(res.active.size() == 2); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 3) != res.active.end()); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 4) != res.active.end()); + } + + { + state.activity[0].activity_state = ActivityState::ActivityCompleted; + state.activity[2].activity_state = ActivityState::ActivityCompleted; + state.activity[1].activity_state = ActivityState::ActivityCompleted; + state.activity[3].activity_state = ActivityState::ActivityCompleted; + state.activity[4].activity_state = ActivityState::ActivityCompleted; + + auto res = Tasks::GetActiveElements(data.activity_information, state.activity, activity_count); + + TEST_ASSERT(res.is_task_complete == true); + TEST_ASSERT(res.active.empty()); + } +} + +void TaskStateTest::TestReqActivityIDOverrideStep() +{ + int activity_count = 4; + TaskInformation data = GetMockZoneData(activity_count); + ClientTaskInformation state = GetMockZoneState(activity_count); + data.activity_information[0].step = 0; + data.activity_information[1].step = 1; + data.activity_information[2].req_activity_id = 1; + data.activity_information[2].step = 1; + data.activity_information[3].req_activity_id = 1; + data.activity_information[3].step = 1; + + state.activity[0].activity_state = ActivityState::ActivityCompleted; + + auto res = Tasks::GetActiveElements(data.activity_information, state.activity, activity_count); + + // indexes 2 and 3 should require index 1 to be completed instead of activating with step 1 + TEST_ASSERT(res.is_task_complete == false); + TEST_ASSERT(res.active.size() == 1); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 1) != res.active.end()); +} + +void TaskStateTest::TestReqActivityIDSteps() +{ + int activity_count = 5; + TaskInformation data = GetMockZoneData(activity_count); + ClientTaskInformation state = GetMockZoneState(activity_count); + data.activity_information[0].step = 0; + data.activity_information[1].step = 1; + data.activity_information[2].req_activity_id = 0; + data.activity_information[2].step = 2; + data.activity_information[3].req_activity_id = 0; + data.activity_information[3].step = 2; + data.activity_information[4].step = 3; + + { + state.activity[0].activity_state = ActivityState::ActivityCompleted; + + auto res = Tasks::GetActiveElements(data.activity_information, state.activity, activity_count); + + // index 1 should become active as next step, indexes 2 and 3 because of reqs + TEST_ASSERT(res.is_task_complete == false); + TEST_ASSERT(res.active.size() == 3); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 1) != res.active.end()); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 2) != res.active.end()); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 3) != res.active.end()); + } + + { + state.activity[0].activity_state = ActivityState::ActivityCompleted; + state.activity[1].activity_state = ActivityState::ActivityCompleted; + + // activity_id | req_activity_id | step | state + // 0 | -1 | 0 | complete + // 1 | -1 | 1 | complete + // 2 | 0 | 2 | hidden (active) + // 3 | 0 | 2 | hidden (active) + // 4 | -1 | 3 | hidden + + auto res = Tasks::GetActiveElements(data.activity_information, state.activity, activity_count); + + // index 4 (step 3) should not become active until step 2 is completed + // even though index 2 and 3 are active because of reqs + TEST_ASSERT(res.is_task_complete == false); + TEST_ASSERT(res.active.size() == 2); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 2) != res.active.end()); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 3) != res.active.end()); + } + + { + state.activity[0].activity_state = ActivityState::ActivityCompleted; + state.activity[1].activity_state = ActivityState::ActivityCompleted; + state.activity[2].activity_state = ActivityState::ActivityCompleted; + + auto res = Tasks::GetActiveElements(data.activity_information, state.activity, activity_count); + + // should still be on step 2 + TEST_ASSERT(res.is_task_complete == false); + TEST_ASSERT(res.active.size() == 1); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 3) != res.active.end()); + } + + { + state.activity[0].activity_state = ActivityState::ActivityCompleted; + state.activity[1].activity_state = ActivityState::ActivityCompleted; + state.activity[2].activity_state = ActivityState::ActivityCompleted; + state.activity[3].activity_state = ActivityState::ActivityCompleted; + + auto res = Tasks::GetActiveElements(data.activity_information, state.activity, activity_count); + + TEST_ASSERT(res.is_task_complete == false); + TEST_ASSERT(res.active.size() == 1); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 4) != res.active.end()); + } + + { + state.activity[0].activity_state = ActivityState::ActivityCompleted; + state.activity[1].activity_state = ActivityState::ActivityCompleted; + state.activity[2].activity_state = ActivityState::ActivityCompleted; + state.activity[3].activity_state = ActivityState::ActivityCompleted; + state.activity[4].activity_state = ActivityState::ActivityCompleted; + + auto res = Tasks::GetActiveElements(data.activity_information, state.activity, activity_count); + + TEST_ASSERT(res.is_task_complete == true); + TEST_ASSERT(res.active.empty()); + } +} + + +void TaskStateTest::TestReqActivityIDUnorderedSteps() +{ + int activity_count = 5; + TaskInformation data = GetMockZoneData(activity_count); + ClientTaskInformation state = GetMockZoneState(activity_count); + data.activity_information[0].step = 0; + data.activity_information[1].step = 1; + data.activity_information[2].req_activity_id = 0; + data.activity_information[2].step = 0; + data.activity_information[3].req_activity_id = 0; + data.activity_information[3].step = 0; + data.activity_information[4].step = 3; + + { + state.activity[0].activity_state = ActivityState::ActivityCompleted; + + // activity_id | req_activity_id | step | state + // 0 | -1 | 0 | complete + // 1 | -1 | 1 | hidden + // 2 | 0 | 0 | hidden (active) + // 3 | 0 | 0 | hidden (active) + // 4 | -1 | 3 | hidden + + auto res = Tasks::GetActiveElements(data.activity_information, state.activity, activity_count); + + TEST_ASSERT(res.is_task_complete == false); + TEST_ASSERT(res.active.size() == 2); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 2) != res.active.end()); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 3) != res.active.end()); + } + + { + state.activity[0].activity_state = ActivityState::ActivityCompleted; + state.activity[2].activity_state = ActivityState::ActivityCompleted; + state.activity[3].activity_state = ActivityState::ActivityCompleted; + + // activity_id | req_activity_id | step | state + // 0 | -1 | 0 | complete + // 1 | -1 | 1 | hidden (active) + // 2 | 0 | 0 | complete + // 3 | 0 | 0 | complete + // 4 | -1 | 3 | hidden + + auto res = Tasks::GetActiveElements(data.activity_information, state.activity, activity_count); + + TEST_ASSERT(res.is_task_complete == false); + TEST_ASSERT(res.active.size() == 1); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 1) != res.active.end()); + } + + { + state.activity[0].activity_state = ActivityState::ActivityCompleted; + state.activity[1].activity_state = ActivityState::ActivityCompleted; + state.activity[2].activity_state = ActivityState::ActivityCompleted; + state.activity[3].activity_state = ActivityState::ActivityCompleted; + + // activity_id | req_activity_id | step | state + // 0 | -1 | 0 | complete + // 1 | -1 | 1 | complete + // 2 | 0 | 0 | complete + // 3 | 0 | 0 | complete + // 4 | -1 | 3 | hidden (active) + + auto res = Tasks::GetActiveElements(data.activity_information, state.activity, activity_count); + + // should still be on step 3 + TEST_ASSERT(res.is_task_complete == false); + TEST_ASSERT(res.active.size() == 1); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 4) != res.active.end()); + } +} + +void TaskStateTest::TestReqActivityIDMixSteps() +{ + int activity_count = 6; + TaskInformation data = GetMockZoneData(activity_count); + ClientTaskInformation state = GetMockZoneState(activity_count); + data.activity_information[0].step = 0; + data.activity_information[1].step = 1; + data.activity_information[2].step = 2; + data.activity_information[3].req_activity_id = 0; + data.activity_information[3].step = 2; + data.activity_information[4].req_activity_id = 0; + data.activity_information[4].step = 2; + data.activity_information[5].step = 3; + + { + state.activity[0].activity_state = ActivityState::ActivityCompleted; + state.activity[1].activity_state = ActivityState::ActivityCompleted; + + // activity_id | req_activity_id | step | state + // 0 | -1 | 0 | complete + // 1 | -1 | 1 | complete + // 2 | -1 | 2 | hidden (active) + // 3 | 0 | 2 | hidden (active) + // 4 | 0 | 2 | hidden (active) + // 5 | -1 | 3 | hidden + + auto res = Tasks::GetActiveElements(data.activity_information, state.activity, activity_count); + + TEST_ASSERT(res.is_task_complete == false); + TEST_ASSERT(res.active.size() == 3); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 2) != res.active.end()); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 3) != res.active.end()); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 4) != res.active.end()); + } + + { + state.activity[0].activity_state = ActivityState::ActivityCompleted; + state.activity[1].activity_state = ActivityState::ActivityCompleted; + state.activity[2].activity_state = ActivityState::ActivityCompleted; + + // activity_id | req_activity_id | step | state + // 0 | -1 | 0 | complete + // 1 | -1 | 1 | complete + // 2 | -1 | 2 | complete + // 3 | 0 | 2 | hidden (active) + // 4 | 0 | 2 | hidden (active) + // 5 | -1 | 3 | hidden + + auto res = Tasks::GetActiveElements(data.activity_information, state.activity, activity_count); + + // index 5 (step 3) should not be active yet after completing only index with non-req id + TEST_ASSERT(res.is_task_complete == false); + TEST_ASSERT(res.active.size() == 2); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 3) != res.active.end()); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 4) != res.active.end()); + } + + { + state.activity[0].activity_state = ActivityState::ActivityCompleted; + state.activity[1].activity_state = ActivityState::ActivityCompleted; + state.activity[2].activity_state = ActivityState::ActivityCompleted; + state.activity[3].activity_state = ActivityState::ActivityCompleted; + state.activity[4].activity_state = ActivityState::ActivityCompleted; + + // activity_id | req_activity_id | step | state + // 0 | -1 | 0 | complete + // 1 | -1 | 1 | complete + // 2 | -1 | 2 | complete + // 3 | 0 | 2 | complete + // 4 | 0 | 2 | complete + // 5 | -1 | 3 | hidden (active) + + auto res = Tasks::GetActiveElements(data.activity_information, state.activity, activity_count); + + TEST_ASSERT(res.is_task_complete == false); + TEST_ASSERT(res.active.size() == 1); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 5) != res.active.end()); + } +} + +void TaskStateTest::TestReqActivityIDSequenceMode() +{ + int activity_count = 6; + TaskInformation data = GetMockZoneData(activity_count); + ClientTaskInformation state = GetMockZoneState(activity_count); + data.activity_information[4].req_activity_id = 1; + data.activity_information[5].req_activity_id = 0; + + { + auto res = Tasks::GetActiveElements(data.activity_information, state.activity, activity_count); + + TEST_ASSERT(res.is_task_complete == false); + TEST_ASSERT(res.active.size() == 1); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 0) != res.active.end()); + } + + { + state.activity[0].activity_state = ActivityState::ActivityCompleted; + state.activity[1].activity_state = ActivityState::ActivityCompleted; + + auto res = Tasks::GetActiveElements(data.activity_information, state.activity, activity_count); + + // index 2 because of sequence mode, index 4 and 5 due to req indexes being complete + TEST_ASSERT(res.is_task_complete == false); + TEST_ASSERT(res.active.size() == 3); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 2) != res.active.end()); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 4) != res.active.end()); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 5) != res.active.end()); + } +} + +void TaskStateTest::TestReqActivityIDOptional() +{ + int activity_count = 3; + auto data = GetMockWorldData(activity_count); + auto state = GetMockWorldState(activity_count); + data[0].step = 0; + data[1].req_activity_id = 0; + data[1].step = 1; + data[1].optional = true; + data[2].req_activity_id = 1; + data[2].step = 2; + + { + // activity_id | req_activity_id | step | state + // 0 | -1 | 0 | hidden (active) + // 1 | 0 | 1 | hidden | optional + // 2 | 1 | 2 | hidden + + auto res = Tasks::GetActiveElements(data, state, activity_count); + + TEST_ASSERT(res.is_task_complete == false); + TEST_ASSERT(res.active.size() == 1); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 0) != res.active.end()); + } + + { + state[0].activity_state = ActivityState::ActivityCompleted; + + // activity_id | req_activity_id | step | state + // 0 | -1 | 0 | complete + // 1 | 0 | 1 | hidden | optional (active) + // 2 | 1 | 2 | hidden + + auto res = Tasks::GetActiveElements(data, state, activity_count); + + // index 1 is effectively non-optional since non-optional index 2 requires it + TEST_ASSERT(res.is_task_complete == false); + TEST_ASSERT(res.active.size() == 1); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 1) != res.active.end()); + } + + { + state[0].activity_state = ActivityState::ActivityCompleted; + state[1].activity_state = ActivityState::ActivityCompleted; + + // activity_id | req_activity_id | step | state + // 0 | -1 | 0 | complete + // 1 | 0 | 1 | complete | optional + // 2 | 1 | 2 | hidden (active) + + auto res = Tasks::GetActiveElements(data, state, activity_count); + + TEST_ASSERT(res.is_task_complete == false); + TEST_ASSERT(res.active.size() == 1); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 2) != res.active.end()); + } + + { + state[0].activity_state = ActivityState::ActivityCompleted; + state[1].activity_state = ActivityState::ActivityCompleted; + state[2].activity_state = ActivityState::ActivityCompleted; + + auto res = Tasks::GetActiveElements(data, state, activity_count); + + TEST_ASSERT(res.is_task_complete == true); + TEST_ASSERT(res.active.empty()); + } +} + +void TaskStateTest::TestReqActivityIDOptionalLastSteps() +{ + int activity_count = 3; + auto data = GetMockWorldData(activity_count); + auto state = GetMockWorldState(activity_count); + data[0].step = 0; + data[1].req_activity_id = 0; + data[1].step = 1; + data[1].optional = true; + data[2].req_activity_id = 1; + data[2].step = 2; + data[2].optional = true; + + { + // activity_id | req_activity_id | step | state + // 0 | -1 | 0 | hidden (active) + // 1 | 0 | 1 | hidden | optional + // 2 | 1 | 2 | hidden | optional + + auto res = Tasks::GetActiveElements(data, state, activity_count); + + TEST_ASSERT(res.is_task_complete == false); + TEST_ASSERT(res.active.size() == 1); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 0) != res.active.end()); + } + + { + state[0].activity_state = ActivityState::ActivityCompleted; + + // activity_id | step | state + // 0 | -1 | 0 | complete + // 1 | 0 | 1 | hidden | optional (active) + // 2 | 1 | 2 | hidden | optional + + auto res = Tasks::GetActiveElements(data, state, activity_count); + + TEST_ASSERT(res.is_task_complete == true); + TEST_ASSERT(res.active.size() == 1); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 1) != res.active.end()); + } + + { + state[0].activity_state = ActivityState::ActivityCompleted; + state[1].activity_state = ActivityState::ActivityCompleted; + + // activity_id | step | state + // 0 | -1 | 0 | complete + // 1 | 0 | 1 | complete | optional + // 2 | 1 | 2 | hidden | optional (active) + + auto res = Tasks::GetActiveElements(data, state, activity_count); + + TEST_ASSERT(res.is_task_complete == true); + TEST_ASSERT(res.active.size() == 1); + TEST_ASSERT(std::find(res.active.begin(), res.active.end(), 2) != res.active.end()); + } +} diff --git a/utils/sql/db_update_manifest.txt b/utils/sql/db_update_manifest.txt index 680d3c823..0dbc57ef1 100644 --- a/utils/sql/db_update_manifest.txt +++ b/utils/sql/db_update_manifest.txt @@ -452,6 +452,7 @@ 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| 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: # This won't be needed after this system is implemented, but it is used database that are not diff --git a/utils/sql/git/required/2022_08_08_task_req_activity_id.sql b/utils/sql/git/required/2022_08_08_task_req_activity_id.sql new file mode 100644 index 000000000..3f8e127ec --- /dev/null +++ b/utils/sql/git/required/2022_08_08_task_req_activity_id.sql @@ -0,0 +1,2 @@ +ALTER TABLE `task_activities` + ADD COLUMN `req_activity_id` INT SIGNED NOT NULL DEFAULT '-1' AFTER `activityid`; diff --git a/world/shared_task_manager.cpp b/world/shared_task_manager.cpp index b648456ec..4ea3866a7 100644 --- a/world/shared_task_manager.cpp +++ b/world/shared_task_manager.cpp @@ -15,7 +15,6 @@ #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 #include extern ClientList client_list; @@ -130,6 +129,8 @@ void SharedTaskManager::AttemptSharedTaskCreation( 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); } @@ -354,6 +355,8 @@ void SharedTaskManager::LoadSharedTaskState() 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 ? ActivityHidden : ActivityCompleted; } } @@ -524,6 +527,7 @@ void SharedTaskManager::SharedTaskActivityUpdate( // 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) @@ -1702,43 +1706,19 @@ void SharedTaskManager::LockTask(SharedTask* s, bool lock) bool SharedTaskManager::HandleCompletedActivities(SharedTask* s) { - bool is_task_complete = true; - bool lock_task = false; + auto states = s->GetActivityState(); - std::array completed_steps; - completed_steps.fill(true); - - // multiple activity ids may share a step, sort so previous step completions can be checked - auto activity_states = s->GetActivityState(); - std::sort(activity_states.begin(), activity_states.end(), - [](const auto& lhs, const auto& rhs) { return lhs.step < rhs.step; }); - - for (const auto& a : activity_states) - { - if (a.done_count != a.max_done_count && !a.optional) - { - is_task_complete = false; - if (a.step > 0 && a.step <= MAXACTIVITIESPERTASK) - { - completed_steps[a.step - 1] = false; - } - } - - int lock_index = s->GetTaskData().lock_activity_id; - if (a.activity_id == lock_index && a.step > 0 && a.step <= MAXACTIVITIESPERTASK) - { - // lock if element is active (on first step or previous step completed) - lock_task = (a.step == 1 || completed_steps[a.step - 2]); - } - } + // activity state holds both source data and current state + auto res = Tasks::GetActiveElements(states, states, states.size()); // 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); } - return is_task_complete; + return res.is_task_complete; } void SharedTaskManager::HandleCompletedTask(SharedTask* s) diff --git a/zone/task_client_state.cpp b/zone/task_client_state.cpp index 14f12da87..0d90337bb 100644 --- a/zone/task_client_state.cpp +++ b/zone/task_client_state.cpp @@ -366,218 +366,94 @@ static void DeleteCompletedTaskFromDatabase(int character_id, int task_id) bool ClientTaskState::UnlockActivities(int character_id, ClientTaskInformation &task_info) { - bool all_activities_complete = true; - 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, task_info.task_id, task_info.slot, - task_info.current_step, task_info.accepted_time, task_info.updated ); - TaskInformation *p_task_data = task_manager->m_task_data[task_info.task_id]; - if (p_task_data == nullptr) { + const TaskInformation* task = task_manager->m_task_data[task_info.task_id]; + if (!task) + { 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) { 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, task_info.task_id, task_info.activity[i].activity_id, task_info.activity[i].done_count, task_info.activity[i].activity_state, - task_info.activity[i].updated, - p_task_data->sequence_mode + task_info.activity[i].updated ); } } - // On loading the client state, all activities that are not completed, are - // marked as hidden. For Sequential (non-stepped) mode, we mark the first - // activity_information as active if not complete. + auto res = Tasks::GetActiveElements(task->activity_information, task_info.activity, task->activity_count); - if (p_task_data->sequence_mode == ActivitiesSequential) { - if (task_info.activity[0].activity_state != ActivityCompleted) { - task_info.activity[0].activity_state = ActivityActive; - } - - // Enable the next Hidden task. - for (int i = 0; i < p_task_data->activity_count; i++) { - if ((task_info.activity[i].activity_state == ActivityActive) && - (!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 (RuleB(TasksSystem, KeepOneRecordPerCompletedTask)) { - LogTasks("KeepOneRecord enabled"); - 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); - - if (erased_elements) { - m_last_completed_task_loaded -= erased_elements; - DeleteCompletedTaskFromDatabase(character_id, task_info.task_id); - } - } - - if (p_task_data->type != TaskType::Shared) { - 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 - // the same task again, erase the previous completed entry for this task. - if (RuleB(TasksSystem, KeepOneRecordPerCompletedTask)) { - LogTasksDetail("[UnlockActivities] KeepOneRecord enabled"); - 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; - } - } - - LogTasksDetail("[UnlockActivities] Erased Element count is [{}]", erased_elements); - - if (erased_elements) { - m_last_completed_task_loaded -= erased_elements; - DeleteCompletedTaskFromDatabase(character_id, task_info.task_id); - } - } - - if (p_task_data->type != TaskType::Shared) { - CompletedTaskInformation completed_task_information{}; - 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; + for (int activity_id : res.active) + { + ClientActivityInformation& client_activity = task_info.activity[activity_id]; + if (client_activity.activity_state == ActivityHidden) + { + LogTasksDetail("[UnlockActivities] task [{}] activity [{}] (ActivityActive)", task_info.task_id, activity_id); + client_activity.activity_state = ActivityActive; + client_activity.updated = true; } } - return false; + if (res.is_task_complete && RuleB(TaskSystem, RecordCompletedTasks)) + { + RecordCompletedTask(character_id, *task, task_info); + } + + return res.is_task_complete; +} + +void ClientTaskState::RecordCompletedTask(uint32_t character_id, const TaskInformation& task, const ClientTaskInformation& client_task) +{ + // 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. + if (RuleB(TasksSystem, KeepOneRecordPerCompletedTask)) + { + size_t before = m_completed_tasks.size(); + + m_completed_tasks.erase(std::remove_if(m_completed_tasks.begin(), m_completed_tasks.end(), + [&](const CompletedTaskInformation& completed) { return completed.task_id == client_task.task_id; } + ), m_completed_tasks.end()); + + size_t erased = m_completed_tasks.size() - before; + + LogTasksDetail("[RecordCompletedTask] KeepOneRecord erased [{}] elements", erased); + + if (erased > 0) + { + m_last_completed_task_loaded -= erased; + DeleteCompletedTaskFromDatabase(character_id, client_task.task_id); + } + } + + if (task.type != TaskType::Shared) + { + CompletedTaskInformation completed{}; + completed.task_id = client_task.task_id; + completed.completed_time = std::time(nullptr); + + for (int i = 0; i < task.activity_count; ++i) + { + completed.activity_done[i] = (client_task.activity[i].activity_state == ActivityCompleted); + } + + LogTasksDetail("[RecordCompletedTask] [{}] for character [{}]", client_task.task_id, character_id); + m_completed_tasks.push_back(completed); + } } bool ClientTaskState::UpdateTasksOnSpeakWith(Client *client, int npc_type_id) @@ -2396,7 +2272,6 @@ void ClientTaskState::AcceptNewTask( active_slot->task_id = task_id; active_slot->accepted_time = static_cast(accept_time); active_slot->updated = true; - active_slot->current_step = -1; active_slot->was_rewarded = false; for (int activity_id = 0; activity_id < task_manager->m_task_data[task_id]->activity_count; activity_id++) { diff --git a/zone/task_client_state.h b/zone/task_client_state.h index 4e49b4589..bf7dad21a 100644 --- a/zone/task_client_state.h +++ b/zone/task_client_state.h @@ -88,6 +88,7 @@ private: void AddReplayTimer(Client *client, ClientTaskInformation& client_task, TaskInformation& task); 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 RecordCompletedTask(uint32_t character_id, const TaskInformation& task, const ClientTaskInformation& client_task); void IncrementDoneCount( Client *client, diff --git a/zone/task_manager.cpp b/zone/task_manager.cpp index d808e1088..859b466c5 100644 --- a/zone/task_manager.cpp +++ b/zone/task_manager.cpp @@ -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_seconds = task.request_timer_seconds; 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( "[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; 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 // and set the task to nullptr. Subsequent activities for this task will raise // ERR_NOTASK errors. @@ -227,6 +215,8 @@ bool TaskManager::LoadTasks(int single_task) } // set activity data + activity_data->req_activity_id = task_activity.req_activity_id; + activity_data->step = step; activity_data->activity_type = static_cast(task_activity.activitytype); activity_data->target_name = task_activity.target_name; activity_data->item_list = task_activity.item_list; @@ -257,7 +247,7 @@ bool TaskManager::LoadTasks(int single_task) LogTasksDetail( "[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, activity_id, 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->skill_list.c_str(), activity_data->spell_list.c_str(), - activity_data->description_override.c_str(), - (m_task_data[task_id]->sequence_mode == ActivitiesStepped ? "stepped" : "sequential") + activity_data->description_override.c_str() ); m_task_data[task_id]->activity_count++; @@ -852,43 +841,6 @@ int TaskManager::GetActivityCount(int task_id) 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) { 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->current_step = -1; task_info->accepted_time = character_task.acceptedtime; task_info->updated = false; task_info->was_rewarded = character_task.was_rewarded; @@ -1568,11 +1519,10 @@ bool TaskManager::LoadClientState(Client *client, ClientTaskState *client_task_s // purely debugging 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, client_task_state->m_active_task.task_id, 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.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++) { if (client_task_state->m_active_task.activity[i].activity_id >= 0) { 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, 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].done_count, client_task_state->m_active_task.activity[i].activity_state, - client_task_state->m_active_task.activity[i].updated, - p_task_data->sequence_mode + client_task_state->m_active_task.activity[i].updated ); } } diff --git a/zone/task_manager.h b/zone/task_manager.h index 05fd31668..c512709d6 100644 --- a/zone/task_manager.h +++ b/zone/task_manager.h @@ -61,7 +61,6 @@ public: bool task_complete = false ); void SendCompletedTasksToClient(Client *c, ClientTaskState *client_task_state); - void ExplainTask(Client *client, int task_id); int FirstTaskInSet(int task_set); int LastTaskInSet(int task_set); int NextTaskInSet(int task_set, int task_id);