mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-11 21:01:29 +00:00
[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:
parent
c954685239
commit
e65b61a95f
@ -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,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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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})
|
||||
|
||||
@ -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;
|
||||
|
||||
1183
tests/task_state_test.h
Normal file
1183
tests/task_state_test.h
Normal file
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
|
||||
@ -0,0 +1,2 @@
|
||||
ALTER TABLE `task_activities`
|
||||
ADD COLUMN `req_activity_id` INT SIGNED NOT NULL DEFAULT '-1' AFTER `activityid`;
|
||||
@ -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 <array>
|
||||
#include <ctime>
|
||||
|
||||
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<bool, MAXACTIVITIESPERTASK> 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)
|
||||
|
||||
@ -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<int>(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++) {
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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<TaskActivityType>(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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user