[Tasks] Replace task goals with explicit fields (#2402)

The task goal system made implementing tasks a little confusing since
the goal could be ambiguous depending on type. This also didn't support
filtering on multiple goals (e.g. looting items from matching npc names
inside an area). Deliver types could specify an npc id in `delivertonpc`
but the database may have multiple npcs with the same name or a task
might want to match partial npc names.

This replaces goalids with explicit fields for npcs, items, proximity
areas, and touch switch ids. These changes make managing task data
easier without needing to update multiple tables and allows filtering
task updates by multiple criteria. To mitigate any performance impact
from merging task proximities, only clients with explore tasks in the
current zone are checked during client movement updates.

Items and npcs still support goallists but it would be possible to
denormalize entries into delimited strings to combine with the match
lists. This would also decouple task goals from reward lists.

The client task update functions were refactored to run through a single
filtering function which significantly reduces duplicated code from the
legacy task system. This will also make it easier to later implement
any unhandled types.

Since the new fields will handle filtering single entries and lists
based on having values set, `goalmethod` now only distinguishes quest
controlled from source controlled.

This is a breaking api change, `taskexploredarea` has been removed
since explore ids no longer exist.
This commit is contained in:
hg 2022-09-01 20:18:21 -04:00 committed by GitHub
parent 8851b410d2
commit 7482cfc066
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 659 additions and 1344 deletions

View File

@ -260,7 +260,6 @@ SET(repositories
repositories/base/base_pets_equipmentset_repository.h
repositories/base/base_pets_equipmentset_entries_repository.h
repositories/base/base_player_titlesets_repository.h
repositories/base/base_proximities_repository.h
repositories/base/base_quest_globals_repository.h
repositories/base/base_raid_details_repository.h
repositories/base/base_raid_members_repository.h
@ -437,7 +436,6 @@ SET(repositories
repositories/pets_equipmentset_repository.h
repositories/pets_equipmentset_entries_repository.h
repositories/player_titlesets_repository.h
repositories/proximities_repository.h
repositories/quest_globals_repository.h
repositories/raid_details_repository.h
repositories/raid_members_repository.h

View File

@ -225,7 +225,6 @@ namespace DatabaseSchema {
"pets_beastlord_data",
"pets_equipmentset",
"pets_equipmentset_entries",
"proximities",
"skill_caps",
"spawn2",
"spawn_conditions",

View File

@ -1,393 +0,0 @@
/**
* DO NOT MODIFY THIS FILE
*
* This repository was automatically generated and is NOT to be modified directly.
* Any repository modifications are meant to be made to the repository extending the base.
* Any modifications to base repositories are to be made by the generator only
*
* @generator ./utils/scripts/generators/repository-generator.pl
* @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories
*/
#ifndef EQEMU_BASE_PROXIMITIES_REPOSITORY_H
#define EQEMU_BASE_PROXIMITIES_REPOSITORY_H
#include "../../database.h"
#include "../../strings.h"
#include <ctime>
class BaseProximitiesRepository {
public:
struct Proximities {
uint32_t zoneid;
uint32_t exploreid;
float minx;
float maxx;
float miny;
float maxy;
float minz;
float maxz;
};
static std::string PrimaryKey()
{
return std::string("zoneid");
}
static std::vector<std::string> Columns()
{
return {
"zoneid",
"exploreid",
"minx",
"maxx",
"miny",
"maxy",
"minz",
"maxz",
};
}
static std::vector<std::string> SelectColumns()
{
return {
"zoneid",
"exploreid",
"minx",
"maxx",
"miny",
"maxy",
"minz",
"maxz",
};
}
static std::string ColumnsRaw()
{
return std::string(Strings::Implode(", ", Columns()));
}
static std::string SelectColumnsRaw()
{
return std::string(Strings::Implode(", ", SelectColumns()));
}
static std::string TableName()
{
return std::string("proximities");
}
static std::string BaseSelect()
{
return fmt::format(
"SELECT {} FROM {}",
SelectColumnsRaw(),
TableName()
);
}
static std::string BaseInsert()
{
return fmt::format(
"INSERT INTO {} ({}) ",
TableName(),
ColumnsRaw()
);
}
static Proximities NewEntity()
{
Proximities e{};
e.zoneid = 0;
e.exploreid = 0;
e.minx = 0.000000;
e.maxx = 0.000000;
e.miny = 0.000000;
e.maxy = 0.000000;
e.minz = 0.000000;
e.maxz = 0.000000;
return e;
}
static Proximities GetProximities(
const std::vector<Proximities> &proximitiess,
int proximities_id
)
{
for (auto &proximities : proximitiess) {
if (proximities.zoneid == proximities_id) {
return proximities;
}
}
return NewEntity();
}
static Proximities FindOne(
Database& db,
int proximities_id
)
{
auto results = db.QueryDatabase(
fmt::format(
"{} WHERE id = {} LIMIT 1",
BaseSelect(),
proximities_id
)
);
auto row = results.begin();
if (results.RowCount() == 1) {
Proximities e{};
e.zoneid = static_cast<uint32_t>(strtoul(row[0], nullptr, 10));
e.exploreid = static_cast<uint32_t>(strtoul(row[1], nullptr, 10));
e.minx = strtof(row[2], nullptr);
e.maxx = strtof(row[3], nullptr);
e.miny = strtof(row[4], nullptr);
e.maxy = strtof(row[5], nullptr);
e.minz = strtof(row[6], nullptr);
e.maxz = strtof(row[7], nullptr);
return e;
}
return NewEntity();
}
static int DeleteOne(
Database& db,
int proximities_id
)
{
auto results = db.QueryDatabase(
fmt::format(
"DELETE FROM {} WHERE {} = {}",
TableName(),
PrimaryKey(),
proximities_id
)
);
return (results.Success() ? results.RowsAffected() : 0);
}
static int UpdateOne(
Database& db,
const Proximities &e
)
{
std::vector<std::string> v;
auto columns = Columns();
v.push_back(columns[0] + " = " + std::to_string(e.zoneid));
v.push_back(columns[1] + " = " + std::to_string(e.exploreid));
v.push_back(columns[2] + " = " + std::to_string(e.minx));
v.push_back(columns[3] + " = " + std::to_string(e.maxx));
v.push_back(columns[4] + " = " + std::to_string(e.miny));
v.push_back(columns[5] + " = " + std::to_string(e.maxy));
v.push_back(columns[6] + " = " + std::to_string(e.minz));
v.push_back(columns[7] + " = " + std::to_string(e.maxz));
auto results = db.QueryDatabase(
fmt::format(
"UPDATE {} SET {} WHERE {} = {}",
TableName(),
Strings::Implode(", ", v),
PrimaryKey(),
e.zoneid
)
);
return (results.Success() ? results.RowsAffected() : 0);
}
static Proximities InsertOne(
Database& db,
Proximities e
)
{
std::vector<std::string> v;
v.push_back(std::to_string(e.zoneid));
v.push_back(std::to_string(e.exploreid));
v.push_back(std::to_string(e.minx));
v.push_back(std::to_string(e.maxx));
v.push_back(std::to_string(e.miny));
v.push_back(std::to_string(e.maxy));
v.push_back(std::to_string(e.minz));
v.push_back(std::to_string(e.maxz));
auto results = db.QueryDatabase(
fmt::format(
"{} VALUES ({})",
BaseInsert(),
Strings::Implode(",", v)
)
);
if (results.Success()) {
e.zoneid = results.LastInsertedID();
return e;
}
e = NewEntity();
return e;
}
static int InsertMany(
Database& db,
const std::vector<Proximities> &entries
)
{
std::vector<std::string> insert_chunks;
for (auto &e: entries) {
std::vector<std::string> v;
v.push_back(std::to_string(e.zoneid));
v.push_back(std::to_string(e.exploreid));
v.push_back(std::to_string(e.minx));
v.push_back(std::to_string(e.maxx));
v.push_back(std::to_string(e.miny));
v.push_back(std::to_string(e.maxy));
v.push_back(std::to_string(e.minz));
v.push_back(std::to_string(e.maxz));
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
}
std::vector<std::string> v;
auto results = db.QueryDatabase(
fmt::format(
"{} VALUES {}",
BaseInsert(),
Strings::Implode(",", insert_chunks)
)
);
return (results.Success() ? results.RowsAffected() : 0);
}
static std::vector<Proximities> All(Database& db)
{
std::vector<Proximities> all_entries;
auto results = db.QueryDatabase(
fmt::format(
"{}",
BaseSelect()
)
);
all_entries.reserve(results.RowCount());
for (auto row = results.begin(); row != results.end(); ++row) {
Proximities e{};
e.zoneid = static_cast<uint32_t>(strtoul(row[0], nullptr, 10));
e.exploreid = static_cast<uint32_t>(strtoul(row[1], nullptr, 10));
e.minx = strtof(row[2], nullptr);
e.maxx = strtof(row[3], nullptr);
e.miny = strtof(row[4], nullptr);
e.maxy = strtof(row[5], nullptr);
e.minz = strtof(row[6], nullptr);
e.maxz = strtof(row[7], nullptr);
all_entries.push_back(e);
}
return all_entries;
}
static std::vector<Proximities> GetWhere(Database& db, const std::string &where_filter)
{
std::vector<Proximities> all_entries;
auto results = db.QueryDatabase(
fmt::format(
"{} WHERE {}",
BaseSelect(),
where_filter
)
);
all_entries.reserve(results.RowCount());
for (auto row = results.begin(); row != results.end(); ++row) {
Proximities e{};
e.zoneid = static_cast<uint32_t>(strtoul(row[0], nullptr, 10));
e.exploreid = static_cast<uint32_t>(strtoul(row[1], nullptr, 10));
e.minx = strtof(row[2], nullptr);
e.maxx = strtof(row[3], nullptr);
e.miny = strtof(row[4], nullptr);
e.maxy = strtof(row[5], nullptr);
e.minz = strtof(row[6], nullptr);
e.maxz = strtof(row[7], nullptr);
all_entries.push_back(e);
}
return all_entries;
}
static int DeleteWhere(Database& db, const std::string &where_filter)
{
auto results = db.QueryDatabase(
fmt::format(
"DELETE FROM {} WHERE {}",
TableName(),
where_filter
)
);
return (results.Success() ? results.RowsAffected() : 0);
}
static int Truncate(Database& db)
{
auto results = db.QueryDatabase(
fmt::format(
"TRUNCATE TABLE {}",
TableName()
)
);
return (results.Success() ? results.RowsAffected() : 0);
}
static int64 GetMaxId(Database& db)
{
auto results = db.QueryDatabase(
fmt::format(
"SELECT COALESCE(MAX({}), 0) FROM {}",
PrimaryKey(),
TableName()
)
);
return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0);
}
static int64 Count(Database& db, const std::string &where_filter = "")
{
auto results = db.QueryDatabase(
fmt::format(
"SELECT COUNT(*) FROM {} {}",
TableName(),
(where_filter.empty() ? "" : "WHERE " + where_filter)
)
);
return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0);
}
};
#endif //EQEMU_BASE_PROXIMITIES_REPOSITORY_H

View File

@ -25,15 +25,25 @@ public:
int32_t step;
uint8_t activitytype;
std::string target_name;
std::string item_list;
std::string skill_list;
std::string spell_list;
std::string description_override;
uint32_t goalid;
std::string goal_match_list;
uint32_t goalmethod;
int32_t goalcount;
uint32_t delivertonpc;
std::string description_override;
uint32_t npc_id;
uint32_t npc_goal_id;
std::string npc_match_list;
uint32_t item_id;
uint32_t item_goal_id;
std::string item_id_list;
std::string item_list;
int32_t dz_switch_id;
float min_x;
float min_y;
float min_z;
float max_x;
float max_y;
float max_z;
std::string skill_list;
std::string spell_list;
std::string zones;
int32_t zone_version;
int8_t optional;
@ -53,15 +63,25 @@ public:
"step",
"activitytype",
"target_name",
"item_list",
"skill_list",
"spell_list",
"description_override",
"goalid",
"goal_match_list",
"goalmethod",
"goalcount",
"delivertonpc",
"description_override",
"npc_id",
"npc_goal_id",
"npc_match_list",
"item_id",
"item_goal_id",
"item_id_list",
"item_list",
"dz_switch_id",
"min_x",
"min_y",
"min_z",
"max_x",
"max_y",
"max_z",
"skill_list",
"spell_list",
"zones",
"zone_version",
"optional",
@ -77,15 +97,25 @@ public:
"step",
"activitytype",
"target_name",
"item_list",
"skill_list",
"spell_list",
"description_override",
"goalid",
"goal_match_list",
"goalmethod",
"goalcount",
"delivertonpc",
"description_override",
"npc_id",
"npc_goal_id",
"npc_match_list",
"item_id",
"item_goal_id",
"item_id_list",
"item_list",
"dz_switch_id",
"min_x",
"min_y",
"min_z",
"max_x",
"max_y",
"max_z",
"skill_list",
"spell_list",
"zones",
"zone_version",
"optional",
@ -135,15 +165,25 @@ public:
e.step = 0;
e.activitytype = 0;
e.target_name = "";
e.item_list = "";
e.skill_list = "-1";
e.spell_list = "0";
e.description_override = "";
e.goalid = 0;
e.goal_match_list = "";
e.goalmethod = 0;
e.goalcount = 1;
e.delivertonpc = 0;
e.description_override = "";
e.npc_id = 0;
e.npc_goal_id = 0;
e.npc_match_list = "";
e.item_id = 0;
e.item_goal_id = 0;
e.item_id_list = "";
e.item_list = "";
e.dz_switch_id = 0;
e.min_x = 0;
e.min_y = 0;
e.min_z = 0;
e.max_x = 0;
e.max_y = 0;
e.max_z = 0;
e.skill_list = "-1";
e.spell_list = "0";
e.zones = "";
e.zone_version = -1;
e.optional = 0;
@ -188,18 +228,28 @@ public:
e.step = static_cast<int32_t>(atoi(row[3]));
e.activitytype = static_cast<uint8_t>(strtoul(row[4], nullptr, 10));
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 = static_cast<uint32_t>(strtoul(row[10], nullptr, 10));
e.goal_match_list = row[11] ? row[11] : "";
e.goalmethod = static_cast<uint32_t>(strtoul(row[12], nullptr, 10));
e.goalcount = static_cast<int32_t>(atoi(row[13]));
e.delivertonpc = static_cast<uint32_t>(strtoul(row[14], nullptr, 10));
e.zones = row[15] ? row[15] : "";
e.zone_version = static_cast<int32_t>(atoi(row[16]));
e.optional = static_cast<int8_t>(atoi(row[17]));
e.goalmethod = static_cast<uint32_t>(strtoul(row[6], nullptr, 10));
e.goalcount = static_cast<int32_t>(atoi(row[7]));
e.description_override = row[8] ? row[8] : "";
e.npc_id = static_cast<uint32_t>(strtoul(row[9], nullptr, 10));
e.npc_goal_id = static_cast<uint32_t>(strtoul(row[10], nullptr, 10));
e.npc_match_list = row[11] ? row[11] : "";
e.item_id = static_cast<uint32_t>(strtoul(row[12], nullptr, 10));
e.item_goal_id = static_cast<uint32_t>(strtoul(row[13], nullptr, 10));
e.item_id_list = row[14] ? row[14] : "";
e.item_list = row[15] ? row[15] : "";
e.dz_switch_id = static_cast<int32_t>(atoi(row[16]));
e.min_x = strtof(row[17], nullptr);
e.min_y = strtof(row[18], nullptr);
e.min_z = strtof(row[19], nullptr);
e.max_x = strtof(row[20], nullptr);
e.max_y = strtof(row[21], nullptr);
e.max_z = strtof(row[22], nullptr);
e.skill_list = row[23] ? row[23] : "";
e.spell_list = row[24] ? row[24] : "";
e.zones = row[25] ? row[25] : "";
e.zone_version = static_cast<int32_t>(atoi(row[26]));
e.optional = static_cast<int8_t>(atoi(row[27]));
return e;
}
@ -239,18 +289,28 @@ public:
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));
v.push_back(columns[6] + " = " + std::to_string(e.goalmethod));
v.push_back(columns[7] + " = " + std::to_string(e.goalcount));
v.push_back(columns[8] + " = '" + Strings::Escape(e.description_override) + "'");
v.push_back(columns[9] + " = " + std::to_string(e.npc_id));
v.push_back(columns[10] + " = " + std::to_string(e.npc_goal_id));
v.push_back(columns[11] + " = '" + Strings::Escape(e.npc_match_list) + "'");
v.push_back(columns[12] + " = " + std::to_string(e.item_id));
v.push_back(columns[13] + " = " + std::to_string(e.item_goal_id));
v.push_back(columns[14] + " = '" + Strings::Escape(e.item_id_list) + "'");
v.push_back(columns[15] + " = '" + Strings::Escape(e.item_list) + "'");
v.push_back(columns[16] + " = " + std::to_string(e.dz_switch_id));
v.push_back(columns[17] + " = " + std::to_string(e.min_x));
v.push_back(columns[18] + " = " + std::to_string(e.min_y));
v.push_back(columns[19] + " = " + std::to_string(e.min_z));
v.push_back(columns[20] + " = " + std::to_string(e.max_x));
v.push_back(columns[21] + " = " + std::to_string(e.max_y));
v.push_back(columns[22] + " = " + std::to_string(e.max_z));
v.push_back(columns[23] + " = '" + Strings::Escape(e.skill_list) + "'");
v.push_back(columns[24] + " = '" + Strings::Escape(e.spell_list) + "'");
v.push_back(columns[25] + " = '" + Strings::Escape(e.zones) + "'");
v.push_back(columns[26] + " = " + std::to_string(e.zone_version));
v.push_back(columns[27] + " = " + std::to_string(e.optional));
auto results = db.QueryDatabase(
fmt::format(
@ -278,15 +338,25 @@ public:
v.push_back(std::to_string(e.step));
v.push_back(std::to_string(e.activitytype));
v.push_back("'" + Strings::Escape(e.target_name) + "'");
v.push_back("'" + Strings::Escape(e.item_list) + "'");
v.push_back("'" + Strings::Escape(e.skill_list) + "'");
v.push_back("'" + Strings::Escape(e.spell_list) + "'");
v.push_back("'" + Strings::Escape(e.description_override) + "'");
v.push_back(std::to_string(e.goalid));
v.push_back("'" + Strings::Escape(e.goal_match_list) + "'");
v.push_back(std::to_string(e.goalmethod));
v.push_back(std::to_string(e.goalcount));
v.push_back(std::to_string(e.delivertonpc));
v.push_back("'" + Strings::Escape(e.description_override) + "'");
v.push_back(std::to_string(e.npc_id));
v.push_back(std::to_string(e.npc_goal_id));
v.push_back("'" + Strings::Escape(e.npc_match_list) + "'");
v.push_back(std::to_string(e.item_id));
v.push_back(std::to_string(e.item_goal_id));
v.push_back("'" + Strings::Escape(e.item_id_list) + "'");
v.push_back("'" + Strings::Escape(e.item_list) + "'");
v.push_back(std::to_string(e.dz_switch_id));
v.push_back(std::to_string(e.min_x));
v.push_back(std::to_string(e.min_y));
v.push_back(std::to_string(e.min_z));
v.push_back(std::to_string(e.max_x));
v.push_back(std::to_string(e.max_y));
v.push_back(std::to_string(e.max_z));
v.push_back("'" + Strings::Escape(e.skill_list) + "'");
v.push_back("'" + Strings::Escape(e.spell_list) + "'");
v.push_back("'" + Strings::Escape(e.zones) + "'");
v.push_back(std::to_string(e.zone_version));
v.push_back(std::to_string(e.optional));
@ -325,15 +395,25 @@ public:
v.push_back(std::to_string(e.step));
v.push_back(std::to_string(e.activitytype));
v.push_back("'" + Strings::Escape(e.target_name) + "'");
v.push_back("'" + Strings::Escape(e.item_list) + "'");
v.push_back("'" + Strings::Escape(e.skill_list) + "'");
v.push_back("'" + Strings::Escape(e.spell_list) + "'");
v.push_back("'" + Strings::Escape(e.description_override) + "'");
v.push_back(std::to_string(e.goalid));
v.push_back("'" + Strings::Escape(e.goal_match_list) + "'");
v.push_back(std::to_string(e.goalmethod));
v.push_back(std::to_string(e.goalcount));
v.push_back(std::to_string(e.delivertonpc));
v.push_back("'" + Strings::Escape(e.description_override) + "'");
v.push_back(std::to_string(e.npc_id));
v.push_back(std::to_string(e.npc_goal_id));
v.push_back("'" + Strings::Escape(e.npc_match_list) + "'");
v.push_back(std::to_string(e.item_id));
v.push_back(std::to_string(e.item_goal_id));
v.push_back("'" + Strings::Escape(e.item_id_list) + "'");
v.push_back("'" + Strings::Escape(e.item_list) + "'");
v.push_back(std::to_string(e.dz_switch_id));
v.push_back(std::to_string(e.min_x));
v.push_back(std::to_string(e.min_y));
v.push_back(std::to_string(e.min_z));
v.push_back(std::to_string(e.max_x));
v.push_back(std::to_string(e.max_y));
v.push_back(std::to_string(e.max_z));
v.push_back("'" + Strings::Escape(e.skill_list) + "'");
v.push_back("'" + Strings::Escape(e.spell_list) + "'");
v.push_back("'" + Strings::Escape(e.zones) + "'");
v.push_back(std::to_string(e.zone_version));
v.push_back(std::to_string(e.optional));
@ -376,18 +456,28 @@ public:
e.step = static_cast<int32_t>(atoi(row[3]));
e.activitytype = static_cast<uint8_t>(strtoul(row[4], nullptr, 10));
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 = static_cast<uint32_t>(strtoul(row[10], nullptr, 10));
e.goal_match_list = row[11] ? row[11] : "";
e.goalmethod = static_cast<uint32_t>(strtoul(row[12], nullptr, 10));
e.goalcount = static_cast<int32_t>(atoi(row[13]));
e.delivertonpc = static_cast<uint32_t>(strtoul(row[14], nullptr, 10));
e.zones = row[15] ? row[15] : "";
e.zone_version = static_cast<int32_t>(atoi(row[16]));
e.optional = static_cast<int8_t>(atoi(row[17]));
e.goalmethod = static_cast<uint32_t>(strtoul(row[6], nullptr, 10));
e.goalcount = static_cast<int32_t>(atoi(row[7]));
e.description_override = row[8] ? row[8] : "";
e.npc_id = static_cast<uint32_t>(strtoul(row[9], nullptr, 10));
e.npc_goal_id = static_cast<uint32_t>(strtoul(row[10], nullptr, 10));
e.npc_match_list = row[11] ? row[11] : "";
e.item_id = static_cast<uint32_t>(strtoul(row[12], nullptr, 10));
e.item_goal_id = static_cast<uint32_t>(strtoul(row[13], nullptr, 10));
e.item_id_list = row[14] ? row[14] : "";
e.item_list = row[15] ? row[15] : "";
e.dz_switch_id = static_cast<int32_t>(atoi(row[16]));
e.min_x = strtof(row[17], nullptr);
e.min_y = strtof(row[18], nullptr);
e.min_z = strtof(row[19], nullptr);
e.max_x = strtof(row[20], nullptr);
e.max_y = strtof(row[21], nullptr);
e.max_z = strtof(row[22], nullptr);
e.skill_list = row[23] ? row[23] : "";
e.spell_list = row[24] ? row[24] : "";
e.zones = row[25] ? row[25] : "";
e.zone_version = static_cast<int32_t>(atoi(row[26]));
e.optional = static_cast<int8_t>(atoi(row[27]));
all_entries.push_back(e);
}
@ -418,18 +508,28 @@ public:
e.step = static_cast<int32_t>(atoi(row[3]));
e.activitytype = static_cast<uint8_t>(strtoul(row[4], nullptr, 10));
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 = static_cast<uint32_t>(strtoul(row[10], nullptr, 10));
e.goal_match_list = row[11] ? row[11] : "";
e.goalmethod = static_cast<uint32_t>(strtoul(row[12], nullptr, 10));
e.goalcount = static_cast<int32_t>(atoi(row[13]));
e.delivertonpc = static_cast<uint32_t>(strtoul(row[14], nullptr, 10));
e.zones = row[15] ? row[15] : "";
e.zone_version = static_cast<int32_t>(atoi(row[16]));
e.optional = static_cast<int8_t>(atoi(row[17]));
e.goalmethod = static_cast<uint32_t>(strtoul(row[6], nullptr, 10));
e.goalcount = static_cast<int32_t>(atoi(row[7]));
e.description_override = row[8] ? row[8] : "";
e.npc_id = static_cast<uint32_t>(strtoul(row[9], nullptr, 10));
e.npc_goal_id = static_cast<uint32_t>(strtoul(row[10], nullptr, 10));
e.npc_match_list = row[11] ? row[11] : "";
e.item_id = static_cast<uint32_t>(strtoul(row[12], nullptr, 10));
e.item_goal_id = static_cast<uint32_t>(strtoul(row[13], nullptr, 10));
e.item_id_list = row[14] ? row[14] : "";
e.item_list = row[15] ? row[15] : "";
e.dz_switch_id = static_cast<int32_t>(atoi(row[16]));
e.min_x = strtof(row[17], nullptr);
e.min_y = strtof(row[18], nullptr);
e.min_z = strtof(row[19], nullptr);
e.max_x = strtof(row[20], nullptr);
e.max_y = strtof(row[21], nullptr);
e.max_z = strtof(row[22], nullptr);
e.skill_list = row[23] ? row[23] : "";
e.spell_list = row[24] ? row[24] : "";
e.zones = row[25] ? row[25] : "";
e.zone_version = static_cast<int32_t>(atoi(row[26]));
e.optional = static_cast<int8_t>(atoi(row[27]));
all_entries.push_back(e);
}

View File

@ -1,50 +0,0 @@
#ifndef EQEMU_PROXIMITIES_REPOSITORY_H
#define EQEMU_PROXIMITIES_REPOSITORY_H
#include "../database.h"
#include "../strings.h"
#include "base/base_proximities_repository.h"
class ProximitiesRepository: public BaseProximitiesRepository {
public:
/**
* This file was auto generated and can be modified and extended upon
*
* Base repository methods are automatically
* generated in the "base" version of this repository. The base repository
* is immutable and to be left untouched, while methods in this class
* are used as extension methods for more specific persistence-layer
* accessors or mutators.
*
* Base Methods (Subject to be expanded upon in time)
*
* Note: Not all tables are designed appropriately to fit functionality with all base methods
*
* InsertOne
* UpdateOne
* DeleteOne
* FindOne
* GetWhere(std::string where_filter)
* DeleteWhere(std::string where_filter)
* InsertMany
* All
*
* Example custom methods in a repository
*
* ProximitiesRepository::GetByZoneAndVersion(int zone_id, int zone_version)
* ProximitiesRepository::GetWhereNeverExpires()
* ProximitiesRepository::GetWhereXAndY()
* ProximitiesRepository::DeleteWhereXAndY()
*
* Most of the above could be covered by base methods, but if you as a developer
* find yourself re-using logic for other parts of the code, its best to just make a
* method that can be re-used easily elsewhere especially if it can use a base repository
* method and encapsulate filters there
*/
// Custom extended repository methods here
};
#endif //EQEMU_PROXIMITIES_REPOSITORY_H

View File

@ -18,8 +18,7 @@
// Command Codes for worldserver ServerOP_ReloadTasks
#define RELOADTASKS 0
#define RELOADTASKGOALLISTS 1
#define RELOADTASKPROXIMITIES 2
#define RELOADTASKSETS 3
#define RELOADTASKSETS 2
typedef enum {
METHODSINGLEID = 0,
@ -77,17 +76,28 @@ struct ActivityInformation {
std::string description_override; // overrides auto generated description -- default empty, max length 128
int skill_id; // older clients, first id from above
int spell_id; // older clients, first id from above
int goal_id;
std::string goal_match_list;
TaskMethodType goal_method;
int goal_count;
int deliver_to_npc;
uint32_t npc_id;
uint32_t npc_goal_id;
std::string npc_match_list; // delimited by '|' for partial name matches but also supports ids
uint32_t item_id;
uint32_t item_goal_id;
std::string item_id_list; // delimited by '|' to support multiple item ids
int dz_switch_id;
float min_x;
float min_y;
float min_z;
float max_x;
float max_y;
float max_z;
std::vector<int> zone_ids;
std::string zones; // IDs ; separated, ZoneID is the first in this list for older clients -- default empty string, max length 64
int zone_version;
bool optional;
bool has_area; // non-database field
inline bool CheckZone(int zone_id, int version)
inline bool CheckZone(int zone_id, int version) const
{
if (zone_ids.empty()) {
return true;
@ -166,7 +176,7 @@ struct ActivityInformation {
out.WriteInt32(zone_ids.empty() ? 0 : zone_ids.front());
}
out.WriteInt32(activity_type == TaskActivityType::Touch ? goal_id : 0); // dz_switch_id (maybe add separate field)
out.WriteInt32(dz_switch_id);
out.WriteString(description_override);
out.WriteInt32(done_count);
out.WriteInt8(1); // unknown

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 9202
#define CURRENT_BINARY_DATABASE_VERSION 9203
#ifdef BOTS
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9029

View File

@ -456,6 +456,7 @@
9200|2022_08_19_zone_expansion_consistency.sql|SELECT * FROM db_version WHERE version >= 9200|empty|
9201|2022_08_22_npc_types_heroic_strikethrough.sql|SHOW COLUMNS FROM `npc_types` LIKE 'heroic_strikethrough'|empty|
9202|2022_08_24_task_activities_step.sql|SHOW COLUMNS FROM `task_activities` LIKE 'step'|contains|unsigned
9203|2022_08_07_replace_task_goals.sql|SHOW COLUMNS FROM `task_activities` LIKE 'item_id'|empty|
# Upgrade conditions:
# This won't be needed after this system is implemented, but it is used database that are not

View File

@ -0,0 +1,95 @@
-- backup original since this is a complex migration
CREATE TABLE `task_activities_backup_9203` LIKE `task_activities`;
INSERT INTO `task_activities_backup_9203` SELECT * FROM `task_activities`;
ALTER TABLE `task_activities`
CHANGE COLUMN `description_override` `description_override` VARCHAR(128) NOT NULL DEFAULT '' COLLATE 'latin1_swedish_ci' AFTER `goalcount`,
ADD COLUMN `npc_id` INT UNSIGNED NOT NULL DEFAULT '0' AFTER `description_override`,
ADD COLUMN `npc_goal_id` INT UNSIGNED NOT NULL DEFAULT '0' AFTER `npc_id`,
ADD COLUMN `npc_match_list` TEXT NULL DEFAULT NULL AFTER `npc_goal_id`,
ADD COLUMN `item_id` INT UNSIGNED NOT NULL DEFAULT '0' AFTER `npc_match_list`,
ADD COLUMN `item_goal_id` INT UNSIGNED NOT NULL DEFAULT '0' AFTER `item_id`,
ADD COLUMN `item_id_list` TEXT NULL DEFAULT NULL AFTER `item_goal_id`,
CHANGE COLUMN `item_list` `item_list` VARCHAR(128) NOT NULL DEFAULT '' COLLATE 'latin1_swedish_ci' AFTER `item_id_list`,
ADD COLUMN `dz_switch_id` INT(11) NOT NULL DEFAULT '0' AFTER `delivertonpc`,
ADD COLUMN `min_x` FLOAT NOT NULL DEFAULT 0 AFTER `dz_switch_id`,
ADD COLUMN `min_y` FLOAT NOT NULL DEFAULT 0 AFTER `min_x`,
ADD COLUMN `min_z` FLOAT NOT NULL DEFAULT 0 AFTER `min_y`,
ADD COLUMN `max_x` FLOAT NOT NULL DEFAULT 0 AFTER `min_z`,
ADD COLUMN `max_y` FLOAT NOT NULL DEFAULT 0 AFTER `max_x`,
ADD COLUMN `max_z` FLOAT NOT NULL DEFAULT 0 AFTER `max_y`,
CHANGE COLUMN `skill_list` `skill_list` VARCHAR(64) NOT NULL DEFAULT '-1' COLLATE 'latin1_swedish_ci' AFTER `max_z`,
CHANGE COLUMN `spell_list` `spell_list` VARCHAR(64) NOT NULL DEFAULT '0' COLLATE 'latin1_swedish_ci' AFTER `skill_list`;
-- move Explore (5) goalid proximities to the new location fields
-- does not migrate where zone was different and ignores lists (unsupported)
UPDATE `task_activities`
INNER JOIN `proximities`
ON `task_activities`.`goalid` = `proximities`.`exploreid`
AND CAST(`task_activities`.`zones` AS INT) = `proximities`.`zoneid`
SET
`task_activities`.`goalid` = 0,
`task_activities`.`min_x` = `proximities`.`minx`,
`task_activities`.`min_y` = `proximities`.`miny`,
`task_activities`.`min_z` = `proximities`.`minz`,
`task_activities`.`max_x` = `proximities`.`maxx`,
`task_activities`.`max_y` = `proximities`.`maxy`,
`task_activities`.`max_z` = `proximities`.`maxz`
WHERE
`task_activities`.`goalmethod` = 0
AND `task_activities`.`activitytype` = 5;
-- dz_switch_id for Touch (11)
UPDATE `task_activities`
SET `task_activities`.`dz_switch_id` = `task_activities`.`goalid`
WHERE `task_activities`.`goalmethod` = 0
AND `task_activities`.`activitytype` = 11;
-- single item ids for Deliver (1), Loot (3), TradeSkill (6), Fish (7), Forage (8)
UPDATE `task_activities`
SET `task_activities`.`item_id` = `task_activities`.`goalid`
WHERE `task_activities`.`goalmethod` = 0
AND `task_activities`.`activitytype` IN (1, 3, 6, 7, 8);
-- item goallist id
UPDATE `task_activities`
SET `task_activities`.`item_goal_id` = `task_activities`.`goalid`
WHERE `task_activities`.`goalmethod` = 1
AND `task_activities`.`activitytype` IN (1, 3, 6, 7, 8);
-- item id match list
UPDATE `task_activities`
SET `task_activities`.`item_id_list` = `task_activities`.`goal_match_list`
WHERE `task_activities`.`goalmethod` = 1
AND `task_activities`.`activitytype` IN (1, 3, 6, 7, 8);
-- single npc ids for Kill (2), SpeakWith (4)
UPDATE `task_activities`
SET `task_activities`.`npc_id` = `task_activities`.`goalid`
WHERE `task_activities`.`goalmethod` = 0
AND `task_activities`.`activitytype` IN (2, 4);
-- npc goallist id
UPDATE `task_activities`
SET `task_activities`.`npc_goal_id` = `task_activities`.`goalid`
WHERE `task_activities`.`goalmethod` = 1
AND `task_activities`.`activitytype` IN (2, 4);
-- npc match list
UPDATE `task_activities`
SET `task_activities`.`npc_match_list` = `task_activities`.`goal_match_list`
WHERE `task_activities`.`goalmethod` = 1
AND `task_activities`.`activitytype` IN (2, 4);
-- delivertonpc npc_ids for Deliver (1), GiveCash (100)
UPDATE `task_activities`
SET `task_activities`.`npc_id` = `task_activities`.`delivertonpc`
WHERE `task_activities`.`activitytype` IN (1, 100);
ALTER TABLE `task_activities`
DROP COLUMN `goalid`,
DROP COLUMN `goal_match_list`,
DROP COLUMN `delivertonpc`;
-- leave proximities table backup in case of regressions
ALTER TABLE `proximities` RENAME `proximities_backup_9203`;

View File

@ -142,7 +142,6 @@ SET(zone_sources
task_client_state.cpp
task_goal_list_manager.cpp
task_manager.cpp
task_proximity_manager.cpp
tasks.cpp
titles.cpp
tradeskills.cpp
@ -268,7 +267,6 @@ SET(zone_headers
task_client_state.h
task_goal_list_manager.h
task_manager.h
task_proximity_manager.h
tasks.h
titles.h
trap.h

View File

@ -2474,7 +2474,7 @@ bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillTy
give_exp_client->GetCleanName(),
GetNPCTypeID()
);
task_manager->HandleUpdateTasksOnKill(give_exp_client, GetNPCTypeID(), this);
task_manager->HandleUpdateTasksOnKill(give_exp_client, this);
}
if (kr) {

View File

@ -1193,7 +1193,7 @@ void Client::ChannelMessageReceived(uint8 chan_num, uint8 language, uint8 lang_s
parse->EventNPC(EVENT_SAY, tar->CastToNPC(), this, message, language);
if(RuleB(TaskSystem, EnableTaskSystem)) {
if(UpdateTasksOnSpeakWith(tar->GetNPCTypeID())) {
if (UpdateTasksOnSpeakWith(tar)) {
tar->DoQuestPause(this);
}
}

View File

@ -1139,48 +1139,32 @@ public:
}
inline void UpdateTasksForItem(
TaskActivityType activity_type,
NPC* npc,
int item_id,
int count = 1
)
{
if (task_state) {
task_state->UpdateTasksForItem(this, activity_type, item_id, count);
task_state->UpdateTasksForItem(this, activity_type, npc, item_id, count);
}
}
inline void UpdateTasksOnExplore(int explore_id)
inline void UpdateTasksOnExplore(const glm::vec4& pos)
{
if (task_state) {
task_state->UpdateTasksOnExplore(
this,
explore_id
);
task_state->UpdateTasksOnExplore(this, pos);
}
}
inline bool UpdateTasksOnSpeakWith(int npc_type_id)
inline bool UpdateTasksOnSpeakWith(NPC* npc)
{
if (task_state) {
return task_state->UpdateTasksOnSpeakWith(
this,
npc_type_id
);
}
else { return false; }
return task_state && task_state->UpdateTasksOnSpeakWith(this, npc);
}
inline bool UpdateTasksOnDeliver(
std::list<EQ::ItemInstance *> &items,
int cash,
int npc_type_id
NPC* npc
)
{
if (task_state) {
return task_state->UpdateTasksOnDeliver(
this,
items,
cash,
npc_type_id
);
}
else { return false; }
return task_state && task_state->UpdateTasksOnDeliver(this, items, cash, npc);
}
void UpdateTasksOnTouchSwitch(int dz_switch_id)
{
@ -1235,12 +1219,7 @@ public:
inline void ProcessTaskProximities(float x, float y, float z)
{
if (task_state) {
task_state->ProcessTaskProximities(
this,
x,
y,
z
);
task_state->ProcessTaskProximities(this, x, y, z);
}
}
inline void AssignTask(
@ -1252,22 +1231,19 @@ public:
task_state->AcceptNewTask(this, task_id, npc_id, std::time(nullptr), enforce_level_requirement);
}
}
inline int ActiveSpeakTask(int npc_type_id)
inline int ActiveSpeakTask(NPC* npc)
{
if (task_state) {
return task_state->ActiveSpeakTask(npc_type_id);
return task_state->ActiveSpeakTask(this, npc);
}
else {
return 0;
}
}
inline int ActiveSpeakActivity(int npc_type_id, int task_id)
inline int ActiveSpeakActivity(NPC* npc, int task_id)
{
if (task_state) {
return task_state->ActiveSpeakActivity(
npc_type_id,
task_id
);
return task_state->ActiveSpeakActivity(this, npc, task_id);
}
else { return 0; }
}

View File

@ -1381,7 +1381,7 @@ void Corpse::LootItem(Client *client, const EQApplicationPacket *app)
/* Update any tasks that have an activity to loot this item */
if (RuleB(TaskSystem, EnableTaskSystem))
client->UpdateTasksForItem(TaskActivityType::Loot, item->ID);
client->UpdateTasksForItem(TaskActivityType::Loot, IsNPCCorpse() ? CastToNPC() : nullptr, item->ID);
/* Remove it from Corpse */
if (item_data) {

View File

@ -1162,11 +1162,6 @@ void Perl__resettaskactivity(int task_id, int activity_id)
quest_manager.resettaskactivity(task_id, activity_id);
}
void Perl__taskexploredarea(int explore_id)
{
quest_manager.taskexploredarea(explore_id);
}
void Perl__assigntask(int task_id)
{
quest_manager.assigntask(task_id);
@ -4238,7 +4233,6 @@ void perl_register_quest()
package.add("summonitem", (void(*)(int, int))&Perl__summonitem);
package.add("surname", &Perl__surname);
package.add("targlobal", &Perl__targlobal);
package.add("taskexploredarea", &Perl__taskexploredarea);
package.add("taskselector", &Perl__taskselector);
package.add("task_setselector", &Perl__task_setselector);
package.add("tasktimeleft", &Perl__tasktimeleft);

View File

@ -367,7 +367,7 @@ void Client::GoFish()
PushItemOnCursor(*inst);
SendItemPacket(EQ::invslot::slotCursor, inst, ItemPacketLimbo);
if (RuleB(TaskSystem, EnableTaskSystem))
UpdateTasksForItem(TaskActivityType::Fish, food_id);
UpdateTasksForItem(TaskActivityType::Fish, nullptr, food_id);
safe_delete(inst);
inst = m_inv.GetItem(EQ::invslot::slotCursor);
@ -486,7 +486,7 @@ void Client::ForageItem(bool guarantee) {
PushItemOnCursor(*inst);
SendItemPacket(EQ::invslot::slotCursor, inst, ItemPacketLimbo);
if(RuleB(TaskSystem, EnableTaskSystem)) {
UpdateTasksForItem(TaskActivityType::Forage, foragedfood);
UpdateTasksForItem(TaskActivityType::Forage, nullptr, foragedfood);
}
safe_delete(inst);

View File

@ -45,13 +45,6 @@ void command_task(Client *c, const Seperator *sep)
Saylink::Silent("#task reload lists", "reload lists")
).c_str()
);
c->Message(
Chat::White,
fmt::format(
"--- [{}] Reload proximity information",
Saylink::Silent("#task reload prox", "reload prox")
).c_str()
);
c->Message(
Chat::White,
fmt::format(
@ -139,13 +132,6 @@ void command_task(Client *c, const Seperator *sep)
Saylink::Silent("#task reload lists", "reload lists")
).c_str()
);
c->Message(
Chat::White,
fmt::format(
"--- [{}] Reload proximity information",
Saylink::Silent("#task reload prox", "reload prox")
).c_str()
);
c->Message(
Chat::White,
fmt::format(
@ -210,11 +196,6 @@ void command_task(Client *c, const Seperator *sep)
worldserver.SendReloadTasks(RELOADTASKGOALLISTS);
c->Message(Chat::Yellow, "Successfully reloaded goal lists.");
return;
} else if (!strcasecmp(sep->arg[2], "prox")) {
c->Message(Chat::Yellow, "Attempting to reload task proximites.");
worldserver.SendReloadTasks(RELOADTASKPROXIMITIES);
c->Message(Chat::Yellow, "Successfully reloaded task proximites.");
return;
} else if (!strcasecmp(sep->arg[2], "sets")) {
c->Message(Chat::Yellow, "Attempting to reload task sets.");
worldserver.SendReloadTasks(RELOADTASKSETS);

View File

@ -699,10 +699,6 @@ void lua_reset_task_activity(int task, int activity) {
quest_manager.resettaskactivity(task, activity);
}
void lua_task_explored_area(int explore_id) {
quest_manager.taskexploredarea(explore_id);
}
void lua_assign_task(int task_id) {
quest_manager.assigntask(task_id);
}
@ -3717,7 +3713,6 @@ luabind::scope lua_register_general() {
luabind::def("get_task_activity_done_count", &lua_get_task_activity_done_count),
luabind::def("update_task_activity", &lua_update_task_activity),
luabind::def("reset_task_activity", &lua_reset_task_activity),
luabind::def("task_explored_area", &lua_task_explored_area),
luabind::def("assign_task", &lua_assign_task),
luabind::def("fail_task", &lua_fail_task),
luabind::def("task_time_left", &lua_task_time_left),

View File

@ -2363,13 +2363,6 @@ void QuestManager::resettaskactivity(int task, int activity) {
initiator->ResetTaskActivity(task, activity);
}
void QuestManager::taskexploredarea(int exploreid) {
QuestManagerCurrentQuestVars();
if(RuleB(TaskSystem, EnableTaskSystem) && initiator)
initiator->UpdateTasksOnExplore(exploreid);
}
void QuestManager::assigntask(int taskid, bool enforce_level_requirement) {
QuestManagerCurrentQuestVars();
@ -2432,16 +2425,16 @@ int QuestManager::nexttaskinset(int taskset, int taskid) {
int QuestManager::activespeaktask() {
QuestManagerCurrentQuestVars();
if(RuleB(TaskSystem, EnableTaskSystem) && initiator && owner)
return initiator->ActiveSpeakTask(owner->GetNPCTypeID());
if (RuleB(TaskSystem, EnableTaskSystem) && initiator && owner && owner->IsNPC())
return initiator->ActiveSpeakTask(owner->CastToNPC());
return 0;
}
int QuestManager::activespeakactivity(int taskid) {
QuestManagerCurrentQuestVars();
if(RuleB(TaskSystem, EnableTaskSystem) && initiator && owner)
return initiator->ActiveSpeakActivity(owner->GetNPCTypeID(), taskid);
if (RuleB(TaskSystem, EnableTaskSystem) && initiator && owner && owner->IsNPC())
return initiator->ActiveSpeakActivity(owner->CastToNPC(), taskid);
return 0;
}
@ -3720,4 +3713,4 @@ void QuestManager::LearnRecipe(uint32 recipe_id) {
}
initiator->LearnRecipe(recipe_id);
}
}

View File

@ -219,7 +219,6 @@ public:
int gettaskactivitydonecount(int task, int activity);
void updatetaskactivity(int task, int activity, int count, bool ignore_quest_update = false);
void resettaskactivity(int task, int activity);
void taskexploredarea(int exploreid);
void assigntask(int taskid, bool enforce_level_requirement = false);
void failtask(int taskid);
int tasktimeleft(int taskid);

View File

@ -364,11 +364,11 @@ static void DeleteCompletedTaskFromDatabase(int character_id, int task_id)
);
}
bool ClientTaskState::UnlockActivities(int character_id, ClientTaskInformation &task_info)
bool ClientTaskState::UnlockActivities(Client* client, ClientTaskInformation& task_info)
{
LogTasksDetail(
"[UnlockActivities] Fetching task info for character_id [{}] task [{}] slot [{}] accepted_time [{}] updated [{}]",
character_id,
client->CharacterID(),
task_info.task_id,
task_info.slot,
task_info.accepted_time,
@ -386,7 +386,7 @@ bool ClientTaskState::UnlockActivities(int character_id, ClientTaskInformation &
if (task_info.activity[i].activity_id >= 0) {
LogTasksDetail(
"[UnlockActivities] character_id [{}] task [{}] activity_id [{}] done_count [{}] activity_state [{}] updated [{}]",
character_id,
client->CharacterID(),
task_info.task_id,
task_info.activity[i].activity_id,
task_info.activity[i].done_count,
@ -411,9 +411,12 @@ bool ClientTaskState::UnlockActivities(int character_id, ClientTaskInformation &
if (res.is_task_complete && RuleB(TaskSystem, RecordCompletedTasks))
{
RecordCompletedTask(character_id, *task, task_info);
RecordCompletedTask(client->CharacterID(), *task, task_info);
}
// check if client has an explore task in current zone to enable task explore processing
m_has_explore_task = HasExploreTask(client);
return res.is_task_complete;
}
@ -456,188 +459,237 @@ void ClientTaskState::RecordCompletedTask(uint32_t character_id, const TaskInfor
}
}
bool ClientTaskState::UpdateTasksOnSpeakWith(Client *client, int npc_type_id)
const TaskInformation* ClientTaskState::GetTaskData(const ClientTaskInformation& client_task) const
{
return UpdateTasksByNPC(client, TaskActivityType::SpeakWith, npc_type_id);
if (client_task.task_id == TASKSLOTEMPTY)
{
return nullptr;
}
return task_manager->m_task_data[client_task.task_id];
}
bool ClientTaskState::UpdateTasksByNPC(Client *client, TaskActivityType activity_type, int npc_type_id)
bool ClientTaskState::CanUpdate(Client* client, const TaskUpdateFilter& filter, int task_id,
const ActivityInformation& activity, const ClientActivityInformation& client_activity) const
{
int is_updating = false;
if (!HasActiveTasks()) {
if (activity.goal_method == METHODQUEST && activity.goal_method != filter.method)
{
return false;
}
// loop over the union of tasks and quests
for (auto &active_task : m_active_tasks) {
auto current_task = &active_task;
if (current_task->task_id == TASKSLOTEMPTY) {
continue;
}
// todo: some tasks do allow hidden/unlocked elements to silently update
if (client_activity.activity_state != ActivityActive)
{
return false;
}
// Check if there are any active kill activities for this p_task_data
auto p_task_data = task_manager->m_task_data[current_task->task_id];
if (p_task_data == nullptr) {
if (activity.activity_type != filter.type)
{
return false;
}
if (activity.dz_switch_id != 0 && activity.dz_switch_id != filter.dz_switch_id)
{
return false;
}
// item is only checked for updates that provide an item (unlike npc which may be null for non-npcs)
if (activity.item_id != 0 && filter.item_id != 0 && activity.item_id != filter.item_id)
{
LogTasks("[CanUpdate] client [{}] task [{}]-[{}] failed item id filter", client->GetName(), task_id, client_activity.activity_id);
return false;
}
if (activity.npc_id != 0 && (!filter.npc || activity.npc_id != filter.npc->GetNPCTypeID()))
{
LogTasks("[CanUpdate] client [{}] task [{}]-[{}] failed npc id filter", client->GetName(), task_id, client_activity.activity_id);
return false;
}
if (!activity.CheckZone(zone->GetZoneID(), zone->GetInstanceVersion()))
{
LogTasks("[CanUpdate] client [{}] task [{}]-[{}] failed zone filter", client->GetName(), task_id, client_activity.activity_id);
return false;
}
if (activity.has_area && !filter.ignore_area && RuleB(TaskSystem, EnableTaskProximity))
{
const glm::vec4& pos = filter.use_pos ? filter.pos : client->GetPosition();
if (pos.x < activity.min_x || pos.x > activity.max_x ||
pos.y < activity.min_y || pos.y > activity.max_y ||
pos.z < activity.min_z || pos.z > activity.max_z)
{
LogTasksDetail("[CanUpdate] client [{}] task [{}]-[{}] failed area filter", client->GetName(), task_id, client_activity.activity_id);
return false;
}
}
for (int activity_id = 0; activity_id < p_task_data->activity_count; activity_id++) {
ClientActivityInformation *client_activity = &current_task->activity[activity_id];
ActivityInformation *activity_info = &p_task_data->activity_information[activity_id];
if (activity.item_goal_id != 0 && filter.item_id != 0 &&
!task_manager->m_goal_list_manager.IsInList(activity.item_goal_id, filter.item_id))
{
LogTasks("[CanUpdate] client [{}] task [{}]-[{}] failed item goallist filter", client->GetName(), task_id, client_activity.activity_id);
return false;
}
// We are not interested in completed or hidden activities
if (client_activity->activity_state != ActivityActive) {
continue;
}
// We are only interested in Kill activities
if (activity_info->activity_type != activity_type) {
continue;
}
// Is there a zone restriction on the activity_information ?
if (!activity_info->CheckZone(zone->GetZoneID(), zone->GetInstanceVersion())) {
LogTasks(
"[UPDATE] character [{}] task_id [{}] activity_id [{}] activity_type [{}] for NPC [{}] failed zone check",
client->GetName(),
current_task->task_id,
activity_id,
static_cast<int32_t>(activity_type),
npc_type_id
);
continue;
}
// Is the activity_information to kill this type of NPC ?
switch (activity_info->goal_method) {
case METHODSINGLEID:
if (activity_info->goal_id != npc_type_id) {
continue;
}
break;
if (!activity.item_id_list.empty() && filter.item_id != 0 &&
!TaskGoalListManager::IsInMatchList(activity.item_id_list, std::to_string(filter.item_id)))
{
LogTasks("[CanUpdate] client [{}] task [{}]-[{}] failed item match filter", client->GetName(), task_id, client_activity.activity_id);
return false;
}
case METHODLIST:
if (!task_manager->m_goal_list_manager.IsInList(
activity_info->goal_id,
npc_type_id
) && !TaskGoalListManager::IsInMatchList(
activity_info->goal_match_list,
std::to_string(npc_type_id)
)) {
continue;
}
break;
if (activity.npc_goal_id != 0 && (!filter.npc ||
!task_manager->m_goal_list_manager.IsInList(activity.npc_goal_id, filter.npc->GetNPCTypeID())))
{
LogTasks("[CanUpdate] client [{}] task [{}]-[{}] failed npc goallist filter", client->GetName(), task_id, client_activity.activity_id);
return false;
}
default:
// If METHODQUEST, don't updated the activity_information here
continue;
// npc filter supports both npc names and ids in match lists
if (!activity.npc_match_list.empty() && (!filter.npc ||
(!TaskGoalListManager::IsInMatchListPartial(activity.npc_match_list, filter.npc->GetName()) &&
!TaskGoalListManager::IsInMatchListPartial(activity.npc_match_list, filter.npc->GetCleanName()) &&
!TaskGoalListManager::IsInMatchList(activity.npc_match_list, std::to_string(filter.npc->GetNPCTypeID())))))
{
LogTasks("[CanUpdate] client [{}] task [{}]-[{}] failed npc match filter", client->GetName(), task_id, client_activity.activity_id);
return false;
}
return true;
}
bool ClientTaskState::UpdateTasks(Client* client, const TaskUpdateFilter& filter, int count)
{
if (!task_manager)
{
return false;
}
bool any_updated = false;
for (const auto& client_task : m_active_tasks)
{
const TaskInformation* task = GetTaskData(client_task);
if (!task)
{
continue;
}
// legacy eqemu task update logic loops through group on kill of npc to update a single task
// shared tasks only require one client to receive an update to propagate
if (filter.type == TaskActivityType::Kill && task->type == TaskType::Shared && client != filter.exp_client)
{
continue;
}
for (const ClientActivityInformation& client_activity : client_task.activity)
{
const ActivityInformation& activity = task->activity_information[client_activity.activity_id];
if (CanUpdate(client, filter, client_task.task_id, activity, client_activity))
{
LogTasks("[UpdateTasks] client [{}] task [{}] activity [{}] increment [{}]",
client->GetName(), client_task.task_id, client_activity.activity_id, count);
IncrementDoneCount(client, task, client_task.slot, client_activity.activity_id, count);
any_updated = true;
break; // only one element updated per task, move to next task
}
// We found an active p_task_data to kill this type of NPC, so increment the done count
LogTasksDetail("Calling increment done count ByNPC");
IncrementDoneCount(client, p_task_data, current_task->slot, activity_id);
is_updating = true;
}
}
return is_updating;
return any_updated;
}
int ClientTaskState::ActiveSpeakTask(int npc_type_id)
std::pair<int, int> ClientTaskState::FindTask(Client* client, const TaskUpdateFilter& filter) const
{
if (!task_manager)
{
return std::make_pair(0, 0);
}
for (const auto& client_task : m_active_tasks)
{
const TaskInformation* task = GetTaskData(client_task);
if (!task || (filter.task_id != 0 && client_task.task_id != filter.task_id))
{
continue;
}
for (const ClientActivityInformation& client_activity : client_task.activity)
{
const ActivityInformation& activity = task->activity_information[client_activity.activity_id];
if (CanUpdate(client, filter, client_task.task_id, activity, client_activity))
{
return std::make_pair(client_task.task_id, client_activity.activity_id);
}
}
}
return std::make_pair(0, 0);
}
bool ClientTaskState::HasExploreTask(Client* client) const
{
TaskUpdateFilter filter{};
filter.type = TaskActivityType::Explore;
filter.ignore_area = true; // we don't care if client is currently in the explore area
auto result = FindTask(client, filter);
bool has_explore = result.first != 0;
LogTasksDetail("[HasExploreTask] client [{}] has explore task in current zone [{}]", client->GetName(), has_explore);
return has_explore;
}
bool ClientTaskState::UpdateTasksOnSpeakWith(Client* client, NPC* npc)
{
return UpdateTasksByNPC(client, TaskActivityType::SpeakWith, npc);
}
bool ClientTaskState::UpdateTasksByNPC(Client* client, TaskActivityType type, NPC* npc)
{
TaskUpdateFilter filter{};
filter.type = type;
filter.npc = npc;
return UpdateTasks(client, filter);
}
int ClientTaskState::ActiveSpeakTask(Client* client, NPC* npc)
{
// This method is to be used from Perl quests only and returns the task_id of the first
// active task found which has an active SpeakWith activity_information for this NPC.
if (!HasActiveTasks()) {
return 0;
}
TaskUpdateFilter filter{};
filter.type = TaskActivityType::SpeakWith;
filter.npc = npc;
filter.method = METHODQUEST;
// loop over the union of tasks and quests
for (auto &active_task : m_active_tasks) {
auto current_task = &active_task;
if (current_task->task_id == TASKSLOTEMPTY) {
continue;
}
TaskInformation *p_task_data = task_manager->m_task_data[current_task->task_id];
if (p_task_data == nullptr) {
continue;
}
for (int activity_id = 0; activity_id < p_task_data->activity_count; activity_id++) {
ClientActivityInformation *client_activity = &current_task->activity[activity_id];
ActivityInformation *activity_info = &p_task_data->activity_information[activity_id];
// We are not interested in completed or hidden activities
if (client_activity->activity_state != ActivityActive) {
continue;
}
if (activity_info->activity_type != TaskActivityType::SpeakWith) {
continue;
}
// Is there a zone restriction on the activity_information ?
if (!activity_info->CheckZone(zone->GetZoneID(), zone->GetInstanceVersion())) {
continue;
}
// Is the activity_information to speak with this type of NPC ?
if (activity_info->goal_method == METHODQUEST && activity_info->goal_id == npc_type_id) {
return current_task->task_id;
}
}
}
return 0;
auto result = FindTask(client, filter);
return result.first; // task id
}
int ClientTaskState::ActiveSpeakActivity(int npc_type_id, int task_id)
int ClientTaskState::ActiveSpeakActivity(Client* client, NPC* npc, int task_id)
{
// This method is to be used from Perl quests only and returns the activity_id of the first
// active activity_information found in the specified task which is to SpeakWith this NPC.
if (!HasActiveTasks()) {
return -1;
}
if (task_id <= 0 || task_id >= MAXTASKS) {
if (task_id <= 0 || task_id >= MAXTASKS)
{
return -1;
}
// loop over the union of tasks and quests
for (auto &active_task : m_active_tasks) {
auto current_task = &active_task;
if (current_task->task_id != task_id) {
continue;
}
TaskUpdateFilter filter{};
filter.type = TaskActivityType::SpeakWith;
filter.npc = npc;
filter.method = METHODQUEST;
filter.task_id = task_id;
TaskInformation *p_task_data = task_manager->m_task_data[current_task->task_id];
if (p_task_data == nullptr) {
continue;
}
for (int activity_id = 0; activity_id < p_task_data->activity_count; activity_id++) {
ClientActivityInformation *client_activity = &current_task->activity[activity_id];
ActivityInformation *activity_info = &p_task_data->activity_information[activity_id];
// We are not interested in completed or hidden activities
if (client_activity->activity_state != ActivityActive) {
continue;
}
if (activity_info->activity_type != TaskActivityType::SpeakWith) {
continue;
}
// Is there a zone restriction on the activity_information ?
if (!activity_info->CheckZone(zone->GetZoneID(), zone->GetInstanceVersion())) {
continue;
}
// Is the activity_information to speak with this type of NPC ?
if (activity_info->goal_method == METHODQUEST && activity_info->goal_id == npc_type_id) {
return activity_id;
}
}
return 0;
}
return 0;
auto result = FindTask(client, filter);
return result.first != 0 ? result.second : -1; // activity id
}
void ClientTaskState::UpdateTasksForItem(Client *client, TaskActivityType activity_type, int item_id, int count)
void ClientTaskState::UpdateTasksForItem(Client* client, TaskActivityType type, NPC* npc, int item_id, int count)
{
// This method updates the client's task activities of the specified type which relate
@ -645,269 +697,62 @@ void ClientTaskState::UpdateTasksForItem(Client *client, TaskActivityType activi
//
// Type should be one of ActivityLoot, ActivityTradeSkill, ActivityFish or ActivityForage
// If the client has no tasks, there is nothing further to check.
LogTasks("[UpdateTasksForItem] activity_type [{}] item_id [{}]", static_cast<int>(type), item_id);
LogTasks(
"[UpdateTasksForItem] activity_type [{}] item_id [{}]",
static_cast<int32_t>(activity_type),
item_id
);
TaskUpdateFilter filter{};
filter.type = type;
filter.npc = npc; // looting may filter on npc id or name
filter.item_id = item_id;
if (!HasActiveTasks()) {
return;
}
// loop over the union of tasks and quests
for (auto &active_task : m_active_tasks) {
auto current_task = &active_task;
if (current_task->task_id == TASKSLOTEMPTY) {
continue;
}
// Check if there are any active loot activities for this task
TaskInformation *p_task_data = task_manager->m_task_data[current_task->task_id];
if (p_task_data == nullptr) {
return;
}
for (int activity_id = 0; activity_id < p_task_data->activity_count; activity_id++) {
ClientActivityInformation *client_activity = &current_task->activity[activity_id];
ActivityInformation *activity_info = &p_task_data->activity_information[activity_id];
// We are not interested in completed or hidden activities
if (client_activity->activity_state != ActivityActive) {
continue;
}
// We are only interested in the ActivityType we were called with
if (activity_info->activity_type != activity_type) {
continue;
}
// Is there a zone restriction on the activity_information ?
if (!activity_info->CheckZone(zone->GetZoneID(), zone->GetInstanceVersion())) {
LogTasks(
"[UpdateTasksForItem] Error: Character [{}] activity_information type [{}] for Item [{}] failed zone check",
client->GetName(),
static_cast<int32_t>(activity_type),
item_id
);
continue;
}
// Is the activity_information related to this item ?
//
switch (activity_info->goal_method) {
case METHODSINGLEID:
if (activity_info->goal_id != item_id) { continue; }
break;
case METHODLIST:
if (!task_manager->m_goal_list_manager.IsInList(
activity_info->goal_id,
item_id
) && !TaskGoalListManager::IsInMatchList(
activity_info->goal_match_list,
std::to_string(item_id)
)) { continue; }
break;
default:
// If METHODQUEST, don't updated the activity_information here
continue;
}
// We found an active task related to this item, so increment the done count
LogTasksDetail("[UpdateTasksForItem] Calling increment done count ForItem");
IncrementDoneCount(client, p_task_data, current_task->slot, activity_id, count);
}
}
UpdateTasks(client, filter, count);
}
void ClientTaskState::UpdateTasksOnExplore(Client *client, int explore_id)
void ClientTaskState::UpdateTasksOnExplore(Client* client, const glm::vec4& pos)
{
LogTasks("[UpdateTasksOnExplore] explore_id [{}]", explore_id);
LogTasksDetail("[UpdateTasksOnExplore] client [{}]", client->GetName());
if (!HasActiveTasks()) {
return;
}
TaskUpdateFilter filter{};
filter.type = TaskActivityType::Explore;
filter.pos = pos;
filter.use_pos = true;
// loop over the union of tasks and quests
for (auto &active_task : m_active_tasks) {
auto current_task = &active_task;
if (current_task->task_id == TASKSLOTEMPTY) {
continue;
}
// Check if there are any active explore activities for this task
TaskInformation *task_data = task_manager->m_task_data[current_task->task_id];
if (task_data == nullptr) {
return;
}
for (int activity_id = 0; activity_id < task_data->activity_count; activity_id++) {
ClientActivityInformation *client_activity = &current_task->activity[activity_id];
ActivityInformation *activity_info = &task_data->activity_information[activity_id];
// We are not interested in completed or hidden activities
if (client_activity->activity_state != ActivityActive) {
continue;
}
// We are only interested in explore activities
if (activity_info->activity_type != TaskActivityType::Explore) {
continue;
}
if (!activity_info->CheckZone(zone->GetZoneID(), zone->GetInstanceVersion())) {
LogTasks(
"[UpdateTasksOnExplore] character [{}] explore_id [{}] failed zone check",
client->GetName(),
explore_id
);
continue;
}
// Is the activity_information to explore this area id ?
switch (activity_info->goal_method) {
case METHODSINGLEID:
if (activity_info->goal_id != explore_id) {
continue;
}
break;
case METHODLIST:
if (!task_manager->m_goal_list_manager.IsInList(
activity_info->goal_id,
explore_id
) && !TaskGoalListManager::IsInMatchList(
activity_info->goal_match_list,
std::to_string(explore_id)
)) {
continue;
}
break;
default:
// If METHODQUEST, don't updated the activity_information here
continue;
}
// We found an active task to explore this area, so set done count to goal count
// (Only a goal count of 1 makes sense for explore activities?)
LogTasks(
"[UpdateTasksOnExplore] character [{}] explore_id [{}] increment on explore",
client->GetName(),
explore_id
);
IncrementDoneCount(
client,
task_data,
current_task->slot,
activity_id,
activity_info->goal_count - current_task->activity[activity_id].done_count
);
}
}
UpdateTasks(client, filter);
}
bool ClientTaskState::UpdateTasksOnDeliver(
Client *client,
std::list<EQ::ItemInstance *> &items,
int cash,
int npc_type_id
NPC* npc
)
{
LogTasks("[UpdateTasksOnDeliver] npc [{}]", npc->GetName());
bool is_updated = false;
LogTasks("[UpdateTasksOnDeliver] [{}]", npc_type_id);
TaskUpdateFilter filter{};
filter.npc = npc;
if (!HasActiveTasks()) {
return false;
if (cash != 0)
{
filter.type = TaskActivityType::GiveCash;
if (UpdateTasks(client, filter, cash))
{
// todo: remove used coin and use Deliver with explicit coin fields instead of custom type
is_updated = true;
}
}
// loop over the union of tasks and quests
for (int i = 0; i < MAXACTIVEQUESTS + 1; i++) {
auto current_task = &m_active_tasks[i];
if (current_task->task_id == TASKSLOTEMPTY) {
continue;
}
filter.type = TaskActivityType::Deliver;
for (EQ::ItemInstance* item : items)
{
filter.item_id = item->GetID();
// Check if there are any active deliver activities for this task
TaskInformation *p_task_data = task_manager->m_task_data[current_task->task_id];
if (p_task_data == nullptr) {
return false;
}
for (int activity_id = 0; activity_id < p_task_data->activity_count; activity_id++) {
ClientActivityInformation *client_activity = &current_task->activity[activity_id];
ActivityInformation *activity_info = &p_task_data->activity_information[activity_id];
// We are not interested in completed or hidden activities
if (client_activity->activity_state != ActivityActive) {
continue;
}
// We are only interested in Deliver activities
if (activity_info->activity_type != TaskActivityType::Deliver &&
activity_info->activity_type != TaskActivityType::GiveCash) {
continue;
}
// Is there a zone restriction on the activity_information ?
if (!activity_info->CheckZone(zone->GetZoneID(), zone->GetInstanceVersion())) {
Log(
Logs::General, Logs::Tasks,
"[UPDATE] Char: %s Deliver activity_information failed zone check (current zone %i, need zone "
"%s",
client->GetName(), zone->GetZoneID(), activity_info->zones.c_str());
continue;
}
// Is the activity_information to deliver to this NPCTypeID ?
if (activity_info->deliver_to_npc != npc_type_id) {
continue;
}
// Is the activity_information related to these items ?
//
if ((activity_info->activity_type == TaskActivityType::GiveCash) && cash) {
LogTasks("[UpdateTasksOnDeliver] Increment on GiveCash");
IncrementDoneCount(client, p_task_data, i, activity_id, cash);
is_updated = true;
}
else {
for (auto &item : items) {
switch (activity_info->goal_method) {
case METHODSINGLEID:
if (activity_info->goal_id != item->GetID()) {
continue;
}
break;
case METHODLIST:
if (!task_manager->m_goal_list_manager.IsInList(
activity_info->goal_id,
item->GetID()
) && !TaskGoalListManager::IsInMatchList(
activity_info->goal_match_list,
std::to_string(item->GetID())
)) {
continue;
}
break;
default:
// If METHODQUEST, don't updated the activity_information here
continue;
}
// We found an active task related to this item, so increment the done count
LogTasks("[UpdateTasksOnDeliver] Increment on GiveItem");
IncrementDoneCount(
client,
p_task_data,
current_task->slot,
activity_id,
item->GetCharges() <= 0 ? 1 : item->GetCharges()
);
is_updated = true;
}
}
int count = item->IsStackable() ? item->GetCharges() : 1;
if (UpdateTasks(client, filter, count))
{
// todo: remove items used in update (highest in case multiple tasks consume same item)
is_updated = true;
}
}
@ -916,69 +761,30 @@ bool ClientTaskState::UpdateTasksOnDeliver(
void ClientTaskState::UpdateTasksOnTouch(Client *client, int dz_switch_id)
{
// If the client has no tasks, there is nothing further to check.
LogTasks("[UpdateTasksOnTouch] dz switch [{}] ", dz_switch_id);
LogTasks("[UpdateTasksOnTouch] [{}] ", dz_switch_id);
TaskUpdateFilter filter{};
filter.type = TaskActivityType::Touch;
filter.dz_switch_id = dz_switch_id;
if (!HasActiveTasks()) {
return;
}
UpdateTasks(client, filter);
}
// loop over the union of tasks and quests
for (auto &active_task : m_active_tasks) {
auto current_task = &active_task;
if (current_task->task_id == TASKSLOTEMPTY) {
continue;
}
void ClientTaskState::UpdateTasksOnKill(Client* client, Client* exp_client, NPC* npc)
{
TaskUpdateFilter filter{};
filter.type = TaskActivityType::Kill;
filter.npc = npc;
filter.pos = npc->GetPosition(); // or should areas be filtered by client position?
filter.use_pos = true;
filter.exp_client = exp_client;
// Check if there are any active explore activities for this task
TaskInformation *p_task_data = task_manager->m_task_data[current_task->task_id];
if (p_task_data == nullptr) {
return;
}
for (int activity_id = 0; activity_id < p_task_data->activity_count; activity_id++) {
ClientActivityInformation *client_activity = &current_task->activity[activity_id];
ActivityInformation *activity_info = &p_task_data->activity_information[activity_id];
// We are not interested in completed or hidden activities
if (current_task->activity[activity_id].activity_state != ActivityActive) {
continue;
}
// We are only interested in touch activities
if (activity_info->activity_type != TaskActivityType::Touch) {
continue;
}
if (activity_info->goal_method != METHODSINGLEID) {
continue;
}
if (!activity_info->CheckZone(zone->GetZoneID(), zone->GetInstanceVersion())) {
LogTasks(
"[UpdateTasksOnTouch] character [{}] Touch activity_information failed zone check",
client->GetName()
);
continue;
}
if (activity_info->goal_id != dz_switch_id) {
continue;
}
// We found an active task to zone into this zone, so set done count to goal count
// (Only a goal count of 1 makes sense for touch activities?)
LogTasks("[UpdateTasksOnTouch] Increment on Touch");
IncrementDoneCount(
client,
p_task_data,
current_task->slot,
activity_id,
activity_info->goal_count - current_task->activity[activity_id].done_count
);
}
}
UpdateTasks(client, filter);
}
void ClientTaskState::IncrementDoneCount(
Client *client,
TaskInformation *task_information,
const TaskInformation* task_information,
int task_index,
int activity_id,
int count,
@ -1024,7 +830,7 @@ void ClientTaskState::IncrementDoneCount(
SyncSharedTaskZoneClientDoneCountState(
client,
task_information,
task_information->type,
task_index,
activity_id,
r->done_count
@ -1065,7 +871,7 @@ void ClientTaskState::IncrementDoneCount(
// Flag the activity_information as complete
info->activity[activity_id].activity_state = ActivityCompleted;
// Unlock subsequent activities for this task
bool task_complete = UnlockActivities(client->CharacterID(), *info);
bool task_complete = UnlockActivities(client, *info);
LogTasks("[IncrementDoneCount] task_complete is [{}]", task_complete);
// and by the 'Task Stage Completed' message
client->SendTaskActivityComplete(info->task_id, activity_id, task_index, task_information->type);
@ -1159,7 +965,7 @@ void ClientTaskState::DispatchEventTaskComplete(Client* client, ClientTaskInform
parse->EventPlayer(EVENT_TASK_COMPLETE, client, export_string, 0);
}
void ClientTaskState::RewardTask(Client *client, TaskInformation *task_information, ClientTaskInformation& client_task)
void ClientTaskState::RewardTask(Client *client, const TaskInformation *task_information, ClientTaskInformation& client_task)
{
if (!task_information || !client || client_task.was_rewarded) {
@ -2000,6 +1806,7 @@ void ClientTaskState::CancelTask(Client *c, int sequence_number, TaskType task_t
// persistence
if (remove_from_db) {
RemoveTask(c, sequence_number, task_type);
m_has_explore_task = HasExploreTask(c);
}
}
@ -2281,7 +2088,7 @@ void ClientTaskState::AcceptNewTask(
active_slot->activity[activity_id].updated = true;
}
UnlockActivities(client->CharacterID(), *active_slot);
UnlockActivities(client, *active_slot);
if (task->type == TaskType::Quest) {
m_active_task_count++;
@ -2332,6 +2139,10 @@ void ClientTaskState::AcceptNewTask(
void ClientTaskState::ProcessTaskProximities(Client *client, float x, float y, float z)
{
if (!m_has_explore_task) {
return;
}
float last_x = client->ProximityX();
float last_y = client->ProximityY();
float last_z = client->ProximityZ();
@ -2340,19 +2151,7 @@ void ClientTaskState::ProcessTaskProximities(Client *client, float x, float y, f
return;
}
LogTasksDetail("[ProcessTaskProximities] Checking proximities for Position x[{}] y[{}] z[{}]", x, y, z);
int explore_id = task_manager->m_proximity_manager.CheckProximities(x, y, z);
if (explore_id > 0) {
LogTasksDetail(
"[ProcessTaskProximities] Position x[{}] y[{}] z[{}] is within proximity explore_id [{}]",
x,
y,
z,
explore_id
);
UpdateTasksOnExplore(client, explore_id);
}
UpdateTasksOnExplore(client, glm::vec4(x, y, z, 0.0f));
}
void ClientTaskState::SharedTaskIncrementDoneCount(
@ -2505,7 +2304,7 @@ void ClientTaskState::ListTaskTimers(Client* client)
}
}
void ClientTaskState::AddReplayTimer(Client* client, ClientTaskInformation& client_task, TaskInformation& task)
void ClientTaskState::AddReplayTimer(Client* client, ClientTaskInformation& client_task, const TaskInformation& task)
{
if (task.replay_timer_seconds > 0)
{
@ -2557,7 +2356,7 @@ void ClientTaskState::AddReplayTimer(Client* client, ClientTaskInformation& clie
// zone-level before sending updates to world
void ClientTaskState::SyncSharedTaskZoneClientDoneCountState(
Client *p_client,
TaskInformation *p_information,
TaskType type,
int task_index,
int activity_id,
uint32 done_count
@ -2566,7 +2365,7 @@ void ClientTaskState::SyncSharedTaskZoneClientDoneCountState(
for (auto &e : entity_list.GetClientList()) {
auto c = e.second;
if (c->GetSharedTaskId() == p_client->GetSharedTaskId()) {
auto t = c->GetTaskState()->GetClientTaskInfo(p_information->type, task_index);
auto t = c->GetTaskState()->GetClientTaskInfo(type, task_index);
if (t == nullptr) {
continue;
}

View File

@ -16,6 +16,20 @@ struct TaskOffer
uint16_t npc_entity_id;
};
struct TaskUpdateFilter
{
int task_id = 0;
int dz_switch_id = 0;
uint32_t item_id = 0;
glm::vec4 pos;
bool use_pos = false; // if true uses pos instead of client position for area filters
bool ignore_area = false; // if true, area check is disabled
NPC* npc = nullptr;
Client* exp_client = nullptr; // used by Kill tasks to filter shared task updates
TaskActivityType type = TaskActivityType::None;
TaskMethodType method = TaskMethodType::METHODSINGLEID;
};
class ClientTaskState {
public:
@ -41,23 +55,23 @@ public:
void CancelAllTasks(Client *client);
void RemoveTask(Client *client, int sequence_number, TaskType task_type);
void RemoveTaskByTaskID(Client *client, uint32 task_id);
bool UpdateTasksByNPC(Client *client, TaskActivityType activity_type, int npc_type_id);
void UpdateTasksForItem(Client *client, TaskActivityType activity_type, int item_id, int count = 1);
void UpdateTasksOnExplore(Client *client, int explore_id);
bool UpdateTasksOnSpeakWith(Client *client, int npc_type_id);
bool UpdateTasksOnDeliver(Client *client, std::list<EQ::ItemInstance *> &items, int cash, int npc_type_id);
bool UpdateTasksByNPC(Client* client, TaskActivityType type, NPC* npc);
void UpdateTasksForItem(Client* client, TaskActivityType type, NPC* npc, int item_id, int count = 1);
void UpdateTasksOnExplore(Client* client, const glm::vec4& loc);
bool UpdateTasksOnSpeakWith(Client* client, NPC* npc);
bool UpdateTasksOnDeliver(Client* client, std::list<EQ::ItemInstance*>& items, int cash, NPC* npc);
void UpdateTasksOnTouch(Client *client, int dz_switch_id);
void ProcessTaskProximities(Client *client, float x, float y, float z);
bool TaskOutOfTime(TaskType task_type, int index);
void TaskPeriodicChecks(Client *client);
void SendTaskHistory(Client *client, int task_index);
void RewardTask(Client *client, TaskInformation *task_information, ClientTaskInformation& client_task);
void RewardTask(Client* client, const TaskInformation* task_information, ClientTaskInformation& client_task);
void EnableTask(int character_id, int task_count, int *task_list);
void DisableTask(int character_id, int task_count, int *task_list);
bool IsTaskEnabled(int task_id);
int EnabledTaskCount(int task_set_id);
int ActiveSpeakTask(int npc_type_id);
int ActiveSpeakActivity(int npc_type_id, int task_id);
int ActiveSpeakTask(Client* client, NPC* npc);
int ActiveSpeakActivity(Client* client, NPC* npc, int task_id);
int ActiveTasksInSet(int task_set_id);
int CompletedTasksInSet(int task_set_id);
bool HasSlotForTask(TaskInformation *task);
@ -67,6 +81,7 @@ public:
void LockSharedTask(Client* client, bool lock);
void ClearLastOffers() { m_last_offers.clear(); }
bool CanAcceptNewTask(Client* client, int task_id, int npc_entity_id) const;
bool HasExploreTask(Client* client) const;
inline bool HasFreeTaskSlot() { return m_active_task.task_id == TASKSLOTEMPTY; }
@ -85,21 +100,28 @@ public:
bool HasActiveSharedTask();
private:
void AddReplayTimer(Client *client, ClientTaskInformation& client_task, TaskInformation& task);
void DispatchEventTaskComplete(Client* client, ClientTaskInformation& client_task, int activity_id);
const TaskInformation* GetTaskData(const ClientTaskInformation& client_task) const;
void AddOffer(int task_id, uint16_t npc_entity_id) { m_last_offers.push_back({task_id, npc_entity_id}); };
void AddReplayTimer(Client *client, ClientTaskInformation& client_task, const TaskInformation& task);
bool CanUpdate(Client* client, const TaskUpdateFilter& filter, int task_id,
const ActivityInformation& activity, const ClientActivityInformation& client_activity) const;
void DispatchEventTaskComplete(Client* client, ClientTaskInformation& client_task, int activity_id);
std::pair<int, int> FindTask(Client* client, const TaskUpdateFilter& filter) const;
void RecordCompletedTask(uint32_t character_id, const TaskInformation& task, const ClientTaskInformation& client_task);
void UpdateTasksOnKill(Client* client, Client* exp_client, NPC* npc);
bool UpdateTasks(Client* client, const TaskUpdateFilter& filter, int count = 1);
void IncrementDoneCount(
Client *client,
TaskInformation *task_information,
const TaskInformation* task_information,
int task_index,
int activity_id,
int count = 1,
bool ignore_quest_update = false
);
bool UnlockActivities(int character_id, ClientTaskInformation &task_info);
bool UnlockActivities(Client* client, ClientTaskInformation& task_info);
inline ClientTaskInformation *GetClientTaskInfo(TaskType task_type, int index)
{
@ -143,12 +165,13 @@ private:
std::vector<CompletedTaskInformation> m_completed_tasks;
int m_last_completed_task_loaded;
std::vector<TaskOffer> m_last_offers;
bool m_has_explore_task = false;
static void ShowClientTaskInfoMessage(ClientTaskInformation *task, Client *c);
void SyncSharedTaskZoneClientDoneCountState(
Client *p_client,
TaskInformation *p_information,
TaskType type,
int task_index,
int activity_id,
uint32 done_count
@ -156,5 +179,4 @@ private:
bool HasActiveTasks();
};
#endif //EQEMU_TASK_CLIENT_STATE_H

View File

@ -225,12 +225,30 @@ bool TaskManager::LoadTasks(int single_task)
activity_data->spell_list = task_activity.spell_list;
activity_data->spell_id = Strings::IsNumber(task_activity.spell_list) ? std::stoi(task_activity.spell_list) : 0; // for older clients
activity_data->description_override = task_activity.description_override;
activity_data->goal_id = task_activity.goalid;
activity_data->npc_id = task_activity.npc_id;
activity_data->npc_goal_id = task_activity.npc_goal_id;
activity_data->npc_match_list = task_activity.npc_match_list;
activity_data->item_id = task_activity.item_id;
activity_data->item_goal_id = task_activity.item_goal_id;
activity_data->item_id_list = task_activity.item_id_list;
activity_data->dz_switch_id = task_activity.dz_switch_id;
activity_data->goal_method = (TaskMethodType) task_activity.goalmethod;
activity_data->goal_match_list = task_activity.goal_match_list;
activity_data->goal_count = task_activity.goalcount;
activity_data->deliver_to_npc = task_activity.delivertonpc;
activity_data->min_x = task_activity.min_x;
activity_data->min_y = task_activity.min_y;
activity_data->min_z = task_activity.min_z;
activity_data->max_x = task_activity.max_x;
activity_data->max_y = task_activity.max_y;
activity_data->max_z = task_activity.max_z;
activity_data->zone_version = task_activity.zone_version >= 0 ? task_activity.zone_version : -1;
activity_data->has_area = false;
if (std::abs(task_activity.max_x - task_activity.min_x) > 0.0f &&
std::abs(task_activity.max_y - task_activity.min_y) > 0.0f &&
std::abs(task_activity.max_z - task_activity.min_z) > 0.0f)
{
activity_data->has_area = true;
}
// zones
activity_data->zones = task_activity.zones;
@ -246,13 +264,12 @@ bool TaskManager::LoadTasks(int single_task)
activity_data->optional = task_activity.optional;
LogTasksDetail(
"[LoadTasks] (Activity) task_id [{}] activity_id [{}] slot [{}] activity_type [{}] goal_id [{}] goal_method [{}] goal_count [{}] zones [{}]"
"[LoadTasks] (Activity) task_id [{}] activity_id [{}] slot [{}] activity_type [{}] goal_method [{}] goal_count [{}] zones [{}]"
" target_name [{}] item_list [{}] skill_list [{}] spell_list [{}] description_override [{}]",
task_id,
activity_id,
m_task_data[task_id]->activity_count,
static_cast<int32_t>(activity_data->activity_type),
activity_data->goal_id,
activity_data->goal_method,
activity_data->goal_count,
activity_data->zones.c_str(),
@ -1515,7 +1532,7 @@ bool TaskManager::LoadClientState(Client *client, ClientTaskState *client_task_s
client_task_state->m_active_task.slot
);
if (client_task_state->m_active_task.task_id != TASKSLOTEMPTY) {
client_task_state->UnlockActivities(character_id, client_task_state->m_active_task);
client_task_state->UnlockActivities(client, client_task_state->m_active_task);
// purely debugging
LogTasksDetail(
@ -1552,13 +1569,13 @@ bool TaskManager::LoadClientState(Client *client, ClientTaskState *client_task_s
client_task_state->m_active_shared_task.slot
);
if (client_task_state->m_active_shared_task.task_id != TASKSLOTEMPTY) {
client_task_state->UnlockActivities(character_id, client_task_state->m_active_shared_task);
client_task_state->UnlockActivities(client, client_task_state->m_active_shared_task);
}
// quests (max 20 or 40 depending on client)
for (auto &active_quest : client_task_state->m_active_quests) {
if (active_quest.task_id != TASKSLOTEMPTY) {
client_task_state->UnlockActivities(character_id, active_quest);
client_task_state->UnlockActivities(client, active_quest);
}
}
@ -1781,7 +1798,7 @@ void TaskManager::SyncClientSharedTaskStateToLocal(
}
}
void TaskManager::HandleUpdateTasksOnKill(Client *client, uint32 npc_type_id, NPC* npc)
void TaskManager::HandleUpdateTasksOnKill(Client* client, NPC* npc)
{
for (auto &c: client->GetPartyMembers()) {
if (!c->ClientDataLoaded() || !c->HasTaskState()) {
@ -1790,96 +1807,7 @@ void TaskManager::HandleUpdateTasksOnKill(Client *client, uint32 npc_type_id, NP
LogTasksDetail("[HandleUpdateTasksOnKill] Looping through client [{}]", c->GetCleanName());
// loop over the union of tasks and quests
for (auto &active_task : c->GetTaskState()->m_active_tasks) {
auto current_task = &active_task;
if (current_task->task_id == TASKSLOTEMPTY) {
continue;
}
// Check if there are any active kill activities for this p_task_data
auto p_task_data = m_task_data[current_task->task_id];
if (p_task_data == nullptr) {
return;
}
for (int activity_id = 0; activity_id < p_task_data->activity_count; activity_id++) {
ClientActivityInformation *client_activity = &current_task->activity[activity_id];
ActivityInformation *activity_info = &p_task_data->activity_information[activity_id];
// We are not interested in completed or hidden activities
if (client_activity->activity_state != ActivityActive) {
continue;
}
// We are only interested in Kill activities
if (activity_info->activity_type != TaskActivityType::Kill) {
continue;
}
// Is there a zone restriction on the activity_information ?
if (!activity_info->CheckZone(zone->GetZoneID(), zone->GetInstanceVersion())) {
LogTasks(
"[HandleUpdateTasksOnKill] character [{}] task_id [{}] activity_id [{}] activity_type [{}] for NPC [{}] failed zone check",
client->GetName(),
current_task->task_id,
activity_id,
static_cast<int32_t>(TaskActivityType::Kill),
npc_type_id
);
continue;
}
// Is the activity_information to kill this type of NPC ?
switch (activity_info->goal_method) {
case METHODSINGLEID:
if (activity_info->goal_id != npc_type_id) {
LogTasksDetail("[HandleUpdateTasksOnKill] Matched single goal");
continue;
}
break;
case METHODLIST:
if (!m_goal_list_manager.IsInList(
activity_info->goal_id,
(int) npc_type_id
) && !TaskGoalListManager::IsInMatchList(
activity_info->goal_match_list,
std::to_string(npc_type_id)
) && !TaskGoalListManager::IsInMatchListPartial(
activity_info->goal_match_list,
npc->GetCleanName()
) && !TaskGoalListManager::IsInMatchListPartial(
activity_info->goal_match_list,
npc->GetName()
)) {
LogTasksDetail("[HandleUpdateTasksOnKill] Matched list goal");
continue;
}
break;
default:
// If METHODQUEST, don't updated the activity_information here
continue;
}
LogTasksDetail("[HandleUpdateTasksOnKill] passed checks");
// handle actual update
// legacy eqemu task update logic loops through group on kill of npc to update a single task
if (p_task_data->type != TaskType::Shared) {
LogTasksDetail("[HandleUpdateTasksOnKill] Non-Shared Update");
c->GetTaskState()->IncrementDoneCount(c, p_task_data, current_task->slot, activity_id);
continue;
}
LogTasksDetail("[HandleUpdateTasksOnKill] Shared update");
// shared tasks only require one client to receive an update to propagate
if (c == client) {
c->GetTaskState()->IncrementDoneCount(c, p_task_data, current_task->slot, activity_id);
}
}
}
c->GetTaskState()->UpdateTasksOnKill(c, client, npc);
}
}

View File

@ -3,7 +3,6 @@
#include "tasks.h"
#include "task_client_state.h"
#include "task_proximity_manager.h"
#include "task_goal_list_manager.h"
#include "../common/types.h"
#include "../common/repositories/character_tasks_repository.h"
@ -24,10 +23,6 @@ public:
int GetActivityCount(int task_id);
bool LoadTasks(int single_task = 0);
void ReloadGoalLists();
inline void LoadProximities(int zone_id)
{
m_proximity_manager.LoadProximities(zone_id);
}
bool LoadTaskSets();
bool LoadClientState(Client *client, ClientTaskState *client_task_state);
bool SaveClientState(Client *client, ClientTaskState *client_task_state);
@ -72,11 +67,10 @@ public:
// shared tasks
void SyncClientSharedTaskState(Client *c, ClientTaskState *cts);
void HandleUpdateTasksOnKill(Client *client, uint32 npc_type_id, NPC* npc);
void HandleUpdateTasksOnKill(Client* client, NPC* npc);
private:
TaskGoalListManager m_goal_list_manager;
TaskProximityManager m_proximity_manager;
TaskInformation *m_task_data[MAXTASKS]{};
std::vector<int> m_task_sets[MAXTASKSETS];
void SendActiveTaskDescription(

View File

@ -1,80 +0,0 @@
#include "../common/global_define.h"
#include "../common/repositories/proximities_repository.h"
#include "../common/rulesys.h"
#include "client.h"
#include "mob.h"
#include "quest_parser_collection.h"
#include "task_proximity_manager.h"
#include "tasks.h"
#include "zonedb.h"
TaskProximityManager::TaskProximityManager()
{
}
TaskProximityManager::~TaskProximityManager()
{
}
bool TaskProximityManager::LoadProximities(int zone_id)
{
TaskProximity proximity{};
m_task_proximities.clear();
auto proximities = ProximitiesRepository::GetWhere(
content_db,
fmt::format("zoneid = {} ORDER BY `zoneid` ASC", zone_id)
);
for (auto &row: proximities) {
proximity.explore_id = row.exploreid;
proximity.min_x = row.minx;
proximity.max_x = row.maxx;
proximity.min_y = row.miny;
proximity.max_y = row.maxy;
proximity.min_z = row.minz;
proximity.max_z = row.maxz;
m_task_proximities.push_back(proximity);
}
LogTasks("Loaded [{}] Task Proximities", proximities.size());
return true;
}
int TaskProximityManager::CheckProximities(float x, float y, float z)
{
for (auto &task_proximity : m_task_proximities) {
TaskProximity *p_proximity = &task_proximity;
Log(
Logs::General,
Logs::Tasks,
"[Proximity] Checking %8.3f, %8.3f, %8.3f against %8.3f, %8.3f, %8.3f, %8.3f, %8.3f, %8.3f",
x,
y,
z,
p_proximity->min_x,
p_proximity->max_x,
p_proximity->min_y,
p_proximity->max_y,
p_proximity->min_z,
p_proximity->max_z
);
if (x < p_proximity->min_x || x > p_proximity->max_x || y < p_proximity->min_y || y > p_proximity->max_y ||
z < p_proximity->min_z || z > p_proximity->max_z) {
continue;
}
return p_proximity->explore_id;
}
return 0;
}

View File

@ -1,27 +0,0 @@
#ifndef EQEMU_TASK_PROXIMITY_MANAGER_H
#define EQEMU_TASK_PROXIMITY_MANAGER_H
struct TaskProximity {
int explore_id;
float min_x;
float max_x;
float min_y;
float max_y;
float min_z;
float max_z;
};
// This class is used for managing proximities so that Quest NPC proximities don't need to be used.
class TaskProximityManager {
public:
TaskProximityManager();
~TaskProximityManager();
bool LoadProximities(int zone_id);
int CheckProximities(float x, float y, float z);
private:
std::vector <TaskProximity> m_task_proximities;
};
#endif //EQEMU_TASK_PROXIMITY_MANAGER_H

View File

@ -1096,7 +1096,7 @@ bool Client::TradeskillExecute(DBTradeskillRecipe_Struct *spec) {
}
if (RuleB(TaskSystem, EnableTaskSystem)) {
UpdateTasksForItem(TaskActivityType::TradeSkill, itr->first, itr->second);
UpdateTasksForItem(TaskActivityType::TradeSkill, nullptr, itr->first, itr->second);
}
++itr;

View File

@ -947,7 +947,7 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st
if(RuleB(TaskSystem, EnableTaskSystem)) {
int Cash = trade->cp + (trade->sp * 10) + (trade->gp * 100) + (trade->pp * 1000);
if(UpdateTasksOnDeliver(items, Cash, tradingWith->GetNPCTypeID())) {
if (UpdateTasksOnDeliver(items, Cash, tradingWith->CastToNPC())) {
if(!tradingWith->IsMoving())
tradingWith->FaceTarget(this);

View File

@ -3451,10 +3451,6 @@ void WorldServer::HandleReloadTasks(ServerPacket *pack)
task_manager = new TaskManager;
task_manager->LoadTasks();
if (zone) {
task_manager->LoadProximities(zone->GetZoneID());
}
entity_list.ReloadAllClientsTaskState();
} else {
LogTasks("Global reload of Task ID [{}]", rts->task_id);
@ -3464,15 +3460,6 @@ void WorldServer::HandleReloadTasks(ServerPacket *pack)
break;
}
case RELOADTASKPROXIMITIES:
{
if (zone) {
LogTasks("Global reload of all Task Proximities");
task_manager->LoadProximities(zone->GetZoneID());
}
break;
}
case RELOADTASKGOALLISTS:
{
LogTasks("Global reload of all Task Goal Lists");

View File

@ -977,10 +977,6 @@ Zone::Zone(uint32 in_zoneid, uint32 in_instanceid, const char* in_short_name)
tradevar = 0;
lootvar = 0;
if (RuleB(TaskSystem, EnableTaskSystem)) {
task_manager->LoadProximities(zoneid);
}
short_name = strcpy(new char[strlen(in_short_name)+1], in_short_name);
strlwr(short_name);
memset(file_name, 0, sizeof(file_name));