[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
14 changed files with 1421 additions and 347 deletions
@@ -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);
}
+3
View File
@@ -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 {
+77 -9
View File
@@ -2,6 +2,8 @@
#define EQEMU_TASKS_H
#include "serialize_buffer.h"
#include <algorithm>
#include <array>
#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<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 {
+1 -1
View File
@@ -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