From f59b4feb94d187542bbac9f930476705680a6ee2 Mon Sep 17 00:00:00 2001 From: hg <4683435+hgtw@users.noreply.github.com> Date: Mon, 8 Jan 2024 23:24:41 -0500 Subject: [PATCH] [Tasks] Add Support for Task Window Element Groups (#3902) Elements that share a group are placed in the same list section and separated from other groups with a divider. Live appears to only use this for optional elements in some tasks and when used each optional always gets its own group. This might indicate it's done automatically under certain criteria to ensure optionals are never grouped with non-optionals regardless of index. Since groups are available in captures and there's very few tasks that use this, we don't need to worry about trying to replicate any automatic behavior since this allows more customization. --- common/database/database_update_manifest.cpp | 14 +- .../base/base_task_activities_repository.h | 120 +++++++++++++++++- common/tasks.h | 1 + common/version.h | 2 +- zone/task_manager.cpp | 8 +- 5 files changed, 137 insertions(+), 8 deletions(-) diff --git a/common/database/database_update_manifest.cpp b/common/database/database_update_manifest.cpp index a61b39489..c30cfcb90 100644 --- a/common/database/database_update_manifest.cpp +++ b/common/database/database_update_manifest.cpp @@ -5162,7 +5162,19 @@ ALTER TABLE `tasks` ADD COLUMN `enabled` smallint NULL DEFAULT 1 AFTER `faction_amount` )", .content_schema_update = true - } + }, + ManifestEntry{ + .version = 9250, + .description = "2023_01_06_task_activities_list_group.sql", + .check = "SHOW COLUMNS FROM `task_activities` LIKE 'list_group'", + .condition = "empty", + .match = "", + .sql = R"( +ALTER TABLE `task_activities` + ADD COLUMN `list_group` TINYINT UNSIGNED NOT NULL DEFAULT '0' AFTER `optional`; +)", + .content_schema_update = true + }, // -- template; copy/paste this when you need to create a new entry // ManifestEntry{ diff --git a/common/repositories/base/base_task_activities_repository.h b/common/repositories/base/base_task_activities_repository.h index e0bb196a6..798a9d5ba 100644 --- a/common/repositories/base/base_task_activities_repository.h +++ b/common/repositories/base/base_task_activities_repository.h @@ -6,7 +6,7 @@ * 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 + * @docs https://docs.eqemu.io/developer/repositories */ #ifndef EQEMU_BASE_TASK_ACTIVITIES_REPOSITORY_H @@ -16,6 +16,7 @@ #include "../../strings.h" #include + class BaseTaskActivitiesRepository { public: struct TaskActivities { @@ -43,6 +44,7 @@ public: std::string zones; int32_t zone_version; int8_t optional; + uint8_t list_group; }; static std::string PrimaryKey() @@ -77,6 +79,7 @@ public: "zones", "zone_version", "optional", + "list_group", }; } @@ -107,6 +110,7 @@ public: "zones", "zone_version", "optional", + "list_group", }; } @@ -171,6 +175,7 @@ public: e.zones = ""; e.zone_version = -1; e.optional = 0; + e.list_group = 0; return e; } @@ -196,8 +201,9 @@ public: { auto results = db.QueryDatabase( fmt::format( - "{} WHERE id = {} LIMIT 1", + "{} WHERE {} = {} LIMIT 1", BaseSelect(), + PrimaryKey(), task_activities_id ) ); @@ -230,6 +236,7 @@ public: e.zones = row[21] ? row[21] : ""; e.zone_version = static_cast(atoi(row[22])); e.optional = static_cast(atoi(row[23])); + e.list_group = static_cast(strtoul(row[24], nullptr, 10)); return e; } @@ -287,6 +294,7 @@ public: v.push_back(columns[21] + " = '" + Strings::Escape(e.zones) + "'"); v.push_back(columns[22] + " = " + std::to_string(e.zone_version)); v.push_back(columns[23] + " = " + std::to_string(e.optional)); + v.push_back(columns[24] + " = " + std::to_string(e.list_group)); auto results = db.QueryDatabase( fmt::format( @@ -332,6 +340,7 @@ public: v.push_back("'" + Strings::Escape(e.zones) + "'"); v.push_back(std::to_string(e.zone_version)); v.push_back(std::to_string(e.optional)); + v.push_back(std::to_string(e.list_group)); auto results = db.QueryDatabase( fmt::format( @@ -385,6 +394,7 @@ public: v.push_back("'" + Strings::Escape(e.zones) + "'"); v.push_back(std::to_string(e.zone_version)); v.push_back(std::to_string(e.optional)); + v.push_back(std::to_string(e.list_group)); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } @@ -442,6 +452,7 @@ public: e.zones = row[21] ? row[21] : ""; e.zone_version = static_cast(atoi(row[22])); e.optional = static_cast(atoi(row[23])); + e.list_group = static_cast(strtoul(row[24], nullptr, 10)); all_entries.push_back(e); } @@ -490,6 +501,7 @@ public: e.zones = row[21] ? row[21] : ""; e.zone_version = static_cast(atoi(row[22])); e.optional = static_cast(atoi(row[23])); + e.list_group = static_cast(strtoul(row[24], nullptr, 10)); all_entries.push_back(e); } @@ -548,6 +560,110 @@ public: return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); } + static std::string BaseReplace() + { + return fmt::format( + "REPLACE INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static int ReplaceOne( + Database& db, + const TaskActivities &e + ) + { + std::vector v; + + 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) + "'"); + v.push_back(std::to_string(e.goalmethod)); + v.push_back(std::to_string(e.goalcount)); + v.push_back("'" + Strings::Escape(e.description_override) + "'"); + v.push_back("'" + Strings::Escape(e.npc_match_list) + "'"); + 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)); + v.push_back(std::to_string(e.list_group)); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseReplace(), + Strings::Implode(",", v) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int ReplaceMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + 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) + "'"); + v.push_back(std::to_string(e.goalmethod)); + v.push_back(std::to_string(e.goalcount)); + v.push_back("'" + Strings::Escape(e.description_override) + "'"); + v.push_back("'" + Strings::Escape(e.npc_match_list) + "'"); + 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)); + v.push_back(std::to_string(e.list_group)); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseReplace(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } }; #endif //EQEMU_BASE_TASK_ACTIVITIES_REPOSITORY_H diff --git a/common/tasks.h b/common/tasks.h index fc08ff225..dab3ff328 100644 --- a/common/tasks.h +++ b/common/tasks.h @@ -75,6 +75,7 @@ struct ActivityInformation { 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; + uint8_t list_group; // element group in window list (groups separated by dividers), valid values are 0-19 bool has_area; // non-database field inline bool CheckZone(int zone_id, int version) const diff --git a/common/version.h b/common/version.h index 445463ddf..70ee76876 100644 --- a/common/version.h +++ b/common/version.h @@ -42,7 +42,7 @@ * Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt */ -#define CURRENT_BINARY_DATABASE_VERSION 9249 +#define CURRENT_BINARY_DATABASE_VERSION 9250 #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9041 diff --git a/zone/task_manager.cpp b/zone/task_manager.cpp index 477533b4c..dc20d122f 100644 --- a/zone/task_manager.cpp +++ b/zone/task_manager.cpp @@ -214,6 +214,8 @@ bool TaskManager::LoadTasks(int single_task) ad->max_y = a.max_y; ad->max_z = a.max_z; ad->zone_version = a.zone_version >= 0 ? a.zone_version : -1; + ad->optional = a.optional; + ad->list_group = a.list_group; ad->has_area = false; if (std::abs(a.max_x - a.min_x) > 0.0f && @@ -236,8 +238,6 @@ bool TaskManager::LoadTasks(int single_task) } } - ad->optional = a.optional; - LogTasksDetail( "(Activity) task_id [{}] activity_id [{}] slot [{}] activity_type [{}] goal_method [{}] goal_count [{}] zones [{}]" " target_name [{}] item_list [{}] skill_list [{}] spell_list [{}] description_override [{}]", @@ -950,7 +950,7 @@ void TaskManager::SendTaskActivityShort(Client *client, int task_id, int activit outapp->WriteUInt32(static_cast(task_data->type)); outapp->WriteUInt32(task_id); outapp->WriteUInt32(activity_id); - outapp->WriteUInt32(0); + outapp->WriteUInt32(task_data->activity_information[activity_id].list_group); outapp->WriteUInt32(0xffffffff); outapp->WriteUInt8(task_data->activity_information[activity_id].optional ? 1 : 0); client->QueuePacket(outapp.get()); @@ -972,7 +972,7 @@ void TaskManager::SendTaskActivityLong( buf.WriteUInt32(static_cast(task_data->type)); // task type buf.WriteUInt32(task_id); buf.WriteUInt32(activity_id); - buf.WriteUInt32(0); // unknown3 + buf.WriteUInt32(task_data->activity_information[activity_id].list_group); const auto& activity = task_data->activity_information[activity_id]; int done_count = client->GetTaskActivityDoneCount(task_data->type, client_task_index, activity_id);