hg 7482cfc066
[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.
2022-09-01 19:18:21 -05:00

319 lines
8.7 KiB
C++
Executable File

#include "../client.h"
#include "../worldserver.h"
extern WorldServer worldserver;
#include "../../common/shared_tasks.h"
#include "../../common/repositories/completed_tasks_repository.h"
void command_task(Client *c, const Seperator *sep)
{
int arguments = sep->argnum;
if (!arguments) {
c->Message(Chat::White, "Syntax: #task [subcommand]");
c->Message(Chat::White, "------------------------------------------------");
c->Message(Chat::White, "# Task System Commands");
c->Message(Chat::White, "------------------------------------------------");
c->Message(
Chat::White,
fmt::format(
"--- [{}] List active tasks for a client",
Saylink::Silent("#task show", "show")
).c_str()
);
c->Message(Chat::White, "--- update <task_id> <activity_id> [count] | Updates task");
c->Message(Chat::White, "--- assign <task_id> | Assigns task to client");
c->Message(Chat::White, "--- uncomplete <task_id> | Uncompletes a task if a client has completed it");
c->Message(
Chat::White,
fmt::format(
"--- [{}] Reload all Task information from the database",
Saylink::Silent("#task reloadall", "reloadall")
).c_str()
);
c->Message(
Chat::White,
fmt::format(
"--- [{}] <task_id> Reload Task and Activity information for a single task",
Saylink::Silent("#task reload task", "reload task")
).c_str()
);
c->Message(
Chat::White,
fmt::format(
"--- [{}] Reload goal/reward list information",
Saylink::Silent("#task reload lists", "reload lists")
).c_str()
);
c->Message(
Chat::White,
fmt::format(
"--- [{}] Reload task set information",
Saylink::Silent("#task reload sets", "reload sets")
).c_str()
);
c->Message(
Chat::White,
fmt::format(
"--- [{}] Purges targeted characters task timers",
Saylink::Silent("#task purgetimers", "purgetimers")
).c_str()
);
c->Message(Chat::White, "------------------------------------------------");
c->Message(Chat::White, "# Shared Task System Commands");
c->Message(Chat::White, "------------------------------------------------");
c->Message(
Chat::White,
fmt::format(
"--- [{}] Purges all active Shared Tasks in memory and database ",
Saylink::Silent("#task sharedpurge", "sharedpurge")
).c_str()
);
return;
}
Client *target = c;
if (c->GetTarget() && c->GetTarget()->IsClient()) {
target = c->GetTarget()->CastToClient();
}
bool is_assign = !strcasecmp(sep->arg[1], "assign");
bool is_purgetimers = !strcasecmp(sep->arg[1], "purgetimers");
bool is_reload = !strcasecmp(sep->arg[1], "reload");
bool is_reloadall = !strcasecmp(sep->arg[1], "reloadall");
bool is_sharedpurge = !strcasecmp(sep->arg[1], "sharedpurge");
bool is_show = !strcasecmp(sep->arg[1], "show");
bool is_uncomplete = !strcasecmp(sep->arg[1], "uncomplete");
bool is_update = !strcasecmp(sep->arg[1], "update");
if (
!is_assign &&
!is_purgetimers &&
!is_reload &&
!is_reloadall &&
!is_sharedpurge &&
!is_show &&
!is_uncomplete &&
!is_update
) {
c->Message(Chat::White, "Syntax: #task [subcommand]");
c->Message(Chat::White, "------------------------------------------------");
c->Message(Chat::White, "# Task System Commands");
c->Message(Chat::White, "------------------------------------------------");
c->Message(
Chat::White,
fmt::format(
"--- [{}] List active tasks for a client",
Saylink::Silent("#task show", "show")
).c_str()
);
c->Message(Chat::White, "--- update <task_id> <activity_id> [count] | Updates task");
c->Message(Chat::White, "--- assign <task_id> | Assigns task to client");
c->Message(Chat::White, "--- uncomplete <task_id> | Uncompletes a task if a client has completed it");
c->Message(
Chat::White,
fmt::format(
"--- [{}] Reload all Task information from the database",
Saylink::Silent("#task reloadall", "reloadall")
).c_str()
);
c->Message(
Chat::White,
fmt::format(
"--- [{}] <task_id> Reload Task and Activity information for a single task",
Saylink::Silent("#task reload task", "reload task")
).c_str()
);
c->Message(
Chat::White,
fmt::format(
"--- [{}] Reload goal/reward list information",
Saylink::Silent("#task reload lists", "reload lists")
).c_str()
);
c->Message(
Chat::White,
fmt::format(
"--- [{}] Reload task set information",
Saylink::Silent("#task reload sets", "reload sets")
).c_str()
);
c->Message(
Chat::White,
fmt::format(
"--- [{}] Purges targeted characters task timers",
Saylink::Silent("#task purgetimers", "purgetimers")
).c_str()
);
c->Message(Chat::White, "------------------------------------------------");
c->Message(Chat::White, "# Shared Task System Commands");
c->Message(Chat::White, "------------------------------------------------");
c->Message(
Chat::White,
fmt::format(
"--- [{}] Purges all active Shared Tasks in memory and database ",
Saylink::Silent("#task sharedpurge", "sharedpurge")
).c_str()
);
return;
}
if (is_assign) {
auto task_id = std::strtoul(sep->arg[2], nullptr, 10);
if (task_id && task_id < MAXTASKS) {
target->AssignTask(task_id, 0, false);
c->Message(
Chat::Yellow,
fmt::format(
"Assigned task ID {} to {}.",
task_id,
c->GetTargetDescription(target)
).c_str()
);
}
return;
} else if (is_purgetimers) {
c->Message(
Chat::Yellow,
fmt::format(
"Task timers have been purged for {}.",
c->GetTargetDescription(target)
).c_str()
);
if (c != target) {
target->Message(Chat::Yellow, "Your task timers have been purged by a GM.");
}
target->PurgeTaskTimers();
return;
} else if (is_reload) {
if (arguments >= 2) {
if (!strcasecmp(sep->arg[2], "lists")) {
c->Message(Chat::Yellow, "Attempting to reload goal lists.");
worldserver.SendReloadTasks(RELOADTASKGOALLISTS);
c->Message(Chat::Yellow, "Successfully reloaded goal lists.");
return;
} else if (!strcasecmp(sep->arg[2], "sets")) {
c->Message(Chat::Yellow, "Attempting to reload task sets.");
worldserver.SendReloadTasks(RELOADTASKSETS);
c->Message(Chat::Yellow, "Successfully reloaded task sets.");
return;
} else if (!strcasecmp(sep->arg[2], "task") && arguments == 3) {
int task_id = std::strtoul(sep->arg[3], nullptr, 10);
if (task_id && task_id < MAXTASKS) {
c->Message(
Chat::Yellow,
fmt::format(
"Attempted to reload task ID {}.",
task_id
).c_str()
);
worldserver.SendReloadTasks(RELOADTASKS, task_id);
c->Message(
Chat::Yellow,
fmt::format(
"Successfully reloaded task ID {}.",
task_id
).c_str()
);
return;
}
}
}
} else if (is_reloadall) {
c->Message(Chat::Yellow, "Attempting to reload tasks.");
worldserver.SendReloadTasks(RELOADTASKS);
c->Message(Chat::Yellow, "Successfully reloaded tasks.");
return;
} else if (is_sharedpurge) {
if (!strcasecmp(sep->arg[2], "confirm")) {
LogTasksDetail("Sending purge request");
auto pack = new ServerPacket(ServerOP_SharedTaskPurgeAllCommand, 0);
worldserver.SendPacket(pack);
safe_delete(pack);
return;
}
c->Message(
Chat::White,
fmt::format(
"[WARNING] This will purge all active Shared Tasks [{}]?",
Saylink::Silent("#task sharedpurge confirm", "confirm")
).c_str()
);
return;
} else if (is_show) {
target->ShowClientTasks(c);
return;
} else if (is_uncomplete) {
if (sep->IsNumber(2)) {
auto task_id = std::stoul(sep->arg[2]);
if (!task_id || task_id > MAXTASKS) {
c->Message(Chat::White, "Invalid task ID specified.");
return;
}
if (
CompletedTasksRepository::DeleteWhere(
database,
fmt::format(
"charid = {} AND taskid = {}",
target->CharacterID(),
task_id
)
)
) {
c->Message(
Chat::Yellow,
fmt::format(
"Successfully uncompleted task ID {} for {}.",
task_id,
c->GetTargetDescription(target)
).c_str()
);
} else {
c->Message(
Chat::Yellow,
fmt::format(
"{} {} not completed task ID {}.",
c->GetTargetDescription(target, TargetDescriptionType::UCYou),
c == target ? "have" : "has",
task_id
).c_str()
);
}
}
} else if (is_update) {
if (arguments >= 3) {
auto task_id = std::strtoul(sep->arg[2], nullptr, 10);
auto activity_id = std::strtoul(sep->arg[3], nullptr, 10);
int count = 1;
if (arguments >= 4) {
count = std::strtol(sep->arg[4], nullptr, 10);
if (count <= 0) {
count = 1;
}
}
c->Message(
Chat::Yellow,
fmt::format(
"Updating task ID {}, activity {} with a count of {} for {}.",
task_id,
activity_id,
count,
c->GetTargetDescription(target)
).c_str()
);
target->UpdateTaskActivity(task_id, activity_id, count);
target->ShowClientTasks(c);
}
}
}