Shared Task WIP

This commit is contained in:
Michael Cook (mackal) 2018-09-01 17:32:13 -04:00
parent d4e0e8aea2
commit 39544b4723
22 changed files with 500 additions and 36 deletions

View File

@ -6,6 +6,7 @@ N(OP_0x0347),
N(OP_AAAction),
N(OP_AAExpUpdate),
N(OP_AcceptNewTask),
N(OP_AcceptNewSharedTask),
N(OP_AckPacket),
N(OP_Action),
N(OP_Action2),

View File

@ -3788,6 +3788,14 @@ struct AcceptNewTask_Struct {
uint32 task_master_id; //entity ID
};
struct AcceptNewSharedTask_Struct {
uint32 unknown00;
uint32 unknown04;
uint32 task_master_id;
uint32 task_id;
float unknown16;
};
//was all 0's from client, server replied with same op, all 0's
struct CancelTask_Struct {
uint32 SequenceNumber;

View File

@ -187,6 +187,7 @@ public:
const unsigned char *buffer() const { return m_buffer; }
friend class BasePacket;
friend class ServerPacket;
private:
void Grow(size_t new_size);

View File

@ -4,6 +4,7 @@
#include "../common/types.h"
#include "../common/packet_functions.h"
#include "../common/eq_packet_structs.h"
#include "../common/serialize_buffer.h"
#include "../net/packet.h"
#include <cereal/cereal.hpp>
#include <cereal/types/string.hpp>
@ -151,6 +152,12 @@
#define ServerOP_LSRemoteAddr 0x1009
#define ServerOP_LSAccountUpdate 0x100A
#define ServerOP_TaskRequest 0x0300
#define ServerOP_TaskGrant 0x0301
#define ServerOP_TaskReject 0x0302
#define ServerOP_TaskAddPlayer 0x0303
#define ServerOP_TaskRemovePlayer 0x0304
#define ServerOP_EncapPacket 0x2007 // Packet within a packet
#define ServerOP_WorldListUpdate 0x2008
#define ServerOP_WorldListRemove 0x2009
@ -246,6 +253,19 @@ public:
_rpos = 0;
}
ServerPacket(uint16 in_opcode, SerializeBuffer &buf)
{
compressed = false;
size = buf.m_pos;
buf.m_pos = 0;
opcode = in_opcode;
pBuffer = buf.m_buffer;
buf.m_buffer = 0;
buf.m_capacity = 0;
_wpos = 0;
_rpos = 0;
}
ServerPacket* Copy() {
if (this == 0) {
return 0;

View File

@ -563,6 +563,7 @@ OP_TaskHistoryRequest=0x6cf6
OP_TaskHistoryReply=0x25eb
OP_DeclineAllTasks=0x0000
OP_TaskRequestTimer=0x4b76
OP_AcceptNewSharedTask=0x3e5e
# Title opcodes
OP_NewTitlesAvailable=0x45d1

View File

@ -568,6 +568,7 @@ OP_TaskHistoryRequest=0x5f1c
OP_TaskHistoryReply=0x3d05
OP_DeclineAllTasks=0x0000
OP_TaskRequestTimer=0x7a48
OP_AcceptNewSharedTask=0x6646
# Title opcodes
OP_NewTitlesAvailable=0x0d32

View File

@ -534,6 +534,7 @@ OP_TaskHistoryReply=0x3d2a # C
OP_CancelTask=0x726b # C
OP_DeclineAllTasks=0x0000 #
OP_TaskRequestTimer=0x2e70
OP_AcceptNewSharedTask=0x4751
OP_Shroud=0x6d1f

View File

@ -510,6 +510,7 @@ OP_TaskRemovePlayer=0x516f
OP_TaskPlayerList=0x0ad6
OP_TaskQuit=0x2c8c
OP_TaskRequestTimer=0x0b08
OP_AcceptNewSharedTask=0x5bed
#Title opcodes
OP_NewTitlesAvailable=0x179c #

View File

@ -475,6 +475,7 @@ OP_TaskRemovePlayer=0x37b9
OP_TaskPlayerList=0x3961
OP_TaskQuit=0x35dd
OP_TaskRequestTimer=0x6a1d
OP_AcceptNewSharedTask=0x194d
#task complete related: 0x0000 (24 bytes), 0x0000 (8 bytes), 0x0000 (4 bytes)

View File

@ -557,6 +557,7 @@ OP_TaskHistoryReply=0x4524 # C
OP_CancelTask=0x3bf5 # C
OP_DeclineAllTasks=0x0000 #
OP_TaskRequestTimer=0x719e
OP_AcceptNewSharedTask=0x6ded
# Title opcodes
OP_NewTitlesAvailable=0x4b49 # C

View File

@ -0,0 +1,22 @@
ALTER TABLE `tasks` ADD `reward_points` INT NOT NULL DEFAULT '0' AFTER `rewardmethod`;
ALTER TABLE `tasks` ADD `reward_type` INT NOT NULL DEFAULT '0' AFTER `reward_points`;
ALTER TABLE `tasks` ADD `replay_group` INT NOT NULL DEFAULT '0';
ALTER TABLE `tasks` ADD `min_players` INT NOT NULL DEFAULT '0';
ALTER TABLE `tasks` ADD `max_players` INT NOT NULL DEFAULT '0';
ALTER TABLE `tasks` ADD `task_lock_step` INT NOT NULL DEFAULT '0';
ALTER TABLE `tasks` ADD `instance_zone_id` INT NOT NULL DEFAULT '0';
ALTER TABLE `tasks` ADD `zone_version` INT NOT NULL DEFAULT '0';
ALTER TABLE `tasks` ADD `zone_in_zone_id` INT NOT NULL DEFAULT '0';
ALTER TABLE `tasks` ADD `zone_in_x` FLOAT NOT NULL DEFAULT '0';
ALTER TABLE `tasks` ADD `zone_in_y` FLOAT NOT NULL DEFAULT '0';
ALTER TABLE `tasks` ADD `zone_in_object_id` TINYINT NOT NULL DEFAULT '0';
ALTER TABLE `tasks` ADD `dest_x` FLOAT NOT NULL DEFAULT '0';
ALTER TABLE `tasks` ADD `dest_y` FLOAT NOT NULL DEFAULT '0';
ALTER TABLE `tasks` ADD `dest_z` FLOAT NOT NULL DEFAULT '0';
ALTER TABLE `tasks` ADD `dest_h` FLOAT NOT NULL DEFAULT '0';
CREATE TABLE `task_replay_groups` (
`id` INT NOT NULL,
`duration` INT NOT NULL,
`name` VARCHAR(128) NOT NULL DEFAULT '',
PRIMARY KEY(`id`)
);

View File

@ -229,6 +229,14 @@ struct ClientReward
uint32 amount;
};
#define PENDING_TASK_TIMEOUT 60000
struct PendingSharedTask {
int id;
int task_master_id;
Timer timeout; // so if we take a very long time to get messages back from world, we time out and fail
PendingSharedTask() : id(0), task_master_id(0) {}
};
class ClientFactory {
public:
Client *MakeClient(std::shared_ptr<EQStreamInterface> ieqs);
@ -1020,8 +1028,8 @@ public:
inline void UpdateTasksOnExplore(int ExploreID) { if(taskstate) taskstate->UpdateTasksOnExplore(this, ExploreID); }
inline bool UpdateTasksOnSpeakWith(int NPCTypeID) { if(taskstate) return taskstate->UpdateTasksOnSpeakWith(this, NPCTypeID); else return false; }
inline bool UpdateTasksOnDeliver(std::list<EQEmu::ItemInstance*>& Items, int Cash, int NPCTypeID) { if (taskstate) return taskstate->UpdateTasksOnDeliver(this, Items, Cash, NPCTypeID); else return false; }
inline void TaskSetSelector(Mob *mob, int TaskSetID) { if(taskmanager) taskmanager->TaskSetSelector(this, taskstate, mob, TaskSetID); }
inline void TaskQuestSetSelector(Mob *mob, int count, int *tasks) { if(taskmanager) taskmanager->TaskQuestSetSelector(this, taskstate, mob, count, tasks); }
inline void TaskSetSelector(Mob *mob, int TaskSetID, bool shared = false) { if(taskmanager) taskmanager->TaskSetSelector(this, taskstate, mob, TaskSetID, shared); }
inline void TaskQuestSetSelector(Mob *mob, int count, int *tasks, bool shared = false) { if(taskmanager) taskmanager->TaskQuestSetSelector(this, taskstate, mob, count, tasks, shared); }
inline void EnableTask(int TaskCount, int *TaskList) { if(taskstate) taskstate->EnableTask(CharacterID(), TaskCount, TaskList); }
inline void DisableTask(int TaskCount, int *TaskList) { if(taskstate) taskstate->DisableTask(CharacterID(), TaskCount, TaskList); }
inline bool IsTaskEnabled(int TaskID) { return (taskstate ? taskstate->IsTaskEnabled(TaskID) : false); }
@ -1043,6 +1051,13 @@ public:
inline int GetTaskActivityDoneCountFromTaskID(int TaskID, int ActivityID) { return (taskstate ? taskstate->GetTaskActivityDoneCountFromTaskID(TaskID, ActivityID) :0); }
inline int ActiveTasksInSet(int TaskSet) { return (taskstate ? taskstate->ActiveTasksInSet(TaskSet) :0); }
inline int CompletedTasksInSet(int TaskSet) { return (taskstate ? taskstate->CompletedTasksInSet(TaskSet) :0); }
inline int GetTaskLockoutExpire(int id) { return 0; } // stub
inline void SetPendingTask(int id, int task_master_id) { pending_task.id = id; pending_task.task_master_id = task_master_id; }
inline bool HasPendingTask() const { return pending_task.id == 0; }
inline int GetPendingTaskID() const { return pending_task.id; }
inline int GetPendingTaskMasterID() const { return pending_task.task_master_id; }
inline void StartPendingTimer() { pending_task.timeout.Start(PENDING_TASK_TIMEOUT); }
inline const EQEmu::versions::ClientVersion ClientVersion() const { return m_ClientVersion; }
inline const uint32 ClientVersionBit() const { return m_ClientVersionBit; }
@ -1569,6 +1584,7 @@ private:
std::set<uint32> zone_flags;
ClientTaskState *taskstate;
PendingSharedTask pending_task;
int TotalSecondsPlayed;
//Anti Spam Stuff

View File

@ -114,6 +114,7 @@ void MapOpcodes()
ConnectedOpcodes[OP_0x0193] = &Client::Handle_0x0193;
ConnectedOpcodes[OP_AAAction] = &Client::Handle_OP_AAAction;
ConnectedOpcodes[OP_AcceptNewTask] = &Client::Handle_OP_AcceptNewTask;
ConnectedOpcodes[OP_AcceptNewSharedTask] = &Client::Handle_OP_AcceptNewSharedTask;
ConnectedOpcodes[OP_AdventureInfoRequest] = &Client::Handle_OP_AdventureInfoRequest;
ConnectedOpcodes[OP_AdventureLeaderboardRequest] = &Client::Handle_OP_AdventureLeaderboardRequest;
ConnectedOpcodes[OP_AdventureMerchantPurchase] = &Client::Handle_OP_AdventureMerchantPurchase;
@ -1840,6 +1841,20 @@ void Client::Handle_OP_AcceptNewTask(const EQApplicationPacket *app)
taskstate->AcceptNewTask(this, ant->task_id, ant->task_master_id);
}
void Client::Handle_OP_AcceptNewSharedTask(const EQApplicationPacket *app)
{
if (app->size != sizeof(AcceptNewSharedTask_Struct)) {
Log(Logs::General, Logs::None, "Size mismatch in OP_AcceptNewSharedTask expected %i got %i",
sizeof(AcceptNewSharedTask_Struct), app->size);
DumpPacket(app);
return;
}
auto *ant = (AcceptNewSharedTask_Struct*)app->pBuffer;
if (ant->task_id > 0 && RuleB(TaskSystem, EnableTaskSystem) && taskstate)
taskstate->PendSharedTask(this, ant->task_id, ant->task_master_id);
}
void Client::Handle_OP_AdventureInfoRequest(const EQApplicationPacket *app)
{
if (app->size < sizeof(EntityId_Struct))

View File

@ -25,6 +25,7 @@
void Handle_0x01e7(const EQApplicationPacket *app);
void Handle_OP_AAAction(const EQApplicationPacket *app);
void Handle_OP_AcceptNewTask(const EQApplicationPacket *app);
void Handle_OP_AcceptNewSharedTask(const EQApplicationPacket *app);
void Handle_OP_AdventureInfoRequest(const EQApplicationPacket *app);
void Handle_OP_AdventureLeaderboardRequest(const EQApplicationPacket *app);
void Handle_OP_AdventureMerchantPurchase(const EQApplicationPacket *app);

View File

@ -159,6 +159,12 @@ bool Client::Process() {
if (TaskPeriodic_Timer.Check() && taskstate)
taskstate->TaskPeriodicChecks(this);
if (pending_task.timeout.Check(false)) {
Message(13, "Shared task timed out.");
pending_task.id = 0;
pending_task.task_master_id = 0;
}
if (linkdead_timer.Check()) {
LeaveGroup();
Save();

View File

@ -2048,6 +2048,23 @@ XS(XS__taskselector) {
XSRETURN_EMPTY;
}
XS(XS__sharedtaskselector);
XS(XS__sharedtaskselector) {
dXSARGS;
if ((items >= 1) && (items <= MAXCHOOSERENTRIES)) {
int tasks[MAXCHOOSERENTRIES];
for (int i = 0; i < items; i++) {
tasks[i] = (int) SvIV(ST(i));
}
quest_manager.taskselector(items, tasks, true);
} else {
Perl_croak(aTHX_ "Usage: quest::sharedtaskselector(int task_id, 2, 3, 4, 5 [up to 40])");
}
XSRETURN_EMPTY;
}
XS(XS__task_setselector);
XS(XS__task_setselector) {
dXSARGS;
@ -3770,6 +3787,7 @@ EXTERN_C XS(boot_quest) {
newXS(strcpy(buf, "targlobal"), XS__targlobal, file);
newXS(strcpy(buf, "taskexploredarea"), XS__taskexploredarea, file);
newXS(strcpy(buf, "taskselector"), XS__taskselector, file);
newXS(strcpy(buf, "sharedtaskselector"), XS__sharedtaskselector, file);
newXS(strcpy(buf, "task_setselector"), XS__task_setselector, file);
newXS(strcpy(buf, "tasktimeleft"), XS__tasktimeleft, file);
newXS(strcpy(buf, "toggle_spawn_event"), XS__toggle_spawn_event, file);

View File

@ -581,6 +581,32 @@ void lua_task_selector(luabind::adl::object table) {
quest_manager.taskselector(count, tasks);
}
void lua_task_selector(luabind::adl::object table, bool shared) {
if(luabind::type(table) != LUA_TTABLE) {
return;
}
int tasks[MAXCHOOSERENTRIES] = { 0 };
int count = 0;
for(int i = 1; i <= MAXCHOOSERENTRIES; ++i) {
auto cur = table[i];
int cur_value = 0;
if(luabind::type(cur) != LUA_TNIL) {
try {
cur_value = luabind::object_cast<int>(cur);
} catch(luabind::cast_failed) {
}
} else {
count = i - 1;
break;
}
tasks[i - 1] = cur_value;
}
quest_manager.taskselector(count, tasks, shared);
}
void lua_task_set_selector(int task_set) {
quest_manager.tasksetselector(task_set);
}
@ -1630,7 +1656,8 @@ luabind::scope lua_register_general() {
luabind::def("summon_all_player_corpses", &lua_summon_all_player_corpses),
luabind::def("get_player_buried_corpse_count", &lua_get_player_buried_corpse_count),
luabind::def("bury_player_corpse", &lua_bury_player_corpse),
luabind::def("task_selector", &lua_task_selector),
luabind::def("task_selector", (void(*)(luabind::adl::object))&lua_task_selector),
luabind::def("task_selector", (void(*)(luabind::adl::object,bool))&lua_task_selector),
luabind::def("task_set_selector", &lua_task_set_selector),
luabind::def("enable_task", &lua_enable_task),
luabind::def("disable_task", &lua_disable_task),

View File

@ -2146,10 +2146,10 @@ bool QuestManager::createBot(const char *name, const char *lastname, uint8 level
#endif //BOTS
void QuestManager::taskselector(int taskcount, int *tasks) {
void QuestManager::taskselector(int taskcount, int *tasks, bool shared) {
QuestManagerCurrentQuestVars();
if(RuleB(TaskSystem, EnableTaskSystem) && initiator && owner && taskmanager)
initiator->TaskQuestSetSelector(owner, taskcount, tasks);
initiator->TaskQuestSetSelector(owner, taskcount, tasks, shared);
}
void QuestManager::enabletask(int taskcount, int *tasks) {
QuestManagerCurrentQuestVars();
@ -2174,11 +2174,11 @@ bool QuestManager::istaskenabled(int taskid) {
return false;
}
void QuestManager::tasksetselector(int tasksetid) {
void QuestManager::tasksetselector(int tasksetid, bool shared) {
QuestManagerCurrentQuestVars();
Log(Logs::General, Logs::Tasks, "[UPDATE] TaskSetSelector called for task set %i", tasksetid);
if(RuleB(TaskSystem, EnableTaskSystem) && initiator && owner && taskmanager)
initiator->TaskSetSelector(owner, tasksetid);
initiator->TaskSetSelector(owner, tasksetid, shared);
}
bool QuestManager::istaskactive(int task) {

View File

@ -188,8 +188,8 @@ public:
void playerfeature(char *feature, int setting);
void npcfeature(char *feature, int setting);
void popup(const char *title, const char *text, uint32 popupid, uint32 buttons, uint32 Duration);
void taskselector(int taskcount, int *tasks);
void tasksetselector(int tasksettid);
void taskselector(int taskcount, int *tasks, bool shared = false);
void tasksetselector(int tasksettid, bool shared = false);
void enabletask(int taskcount, int *tasks);
void disabletask(int taskcount, int *tasks);
bool istaskenabled(int taskid);

View File

@ -359,6 +359,7 @@
#define LDON_NO_LOCKPICK 7564 //You must have a lock pick in your inventory to do this.
#define LDON_WAS_NOT_LOCKED 7565 //%1 was not locked.
#define LDON_WAS_NOT_TRAPPED 7566 //%1 was not trapped
#define TASK_REJECT_LOCKEDOUT_ME 8017 //This task can not be assigned to you because you must wait %1d:%2h:%3m before you can do another task of this type.
#define GAIN_GROUP_LEADERSHIP_POINT 8585 //
#define GAIN_RAID_LEADERSHIP_POINT 8589 //
#define MAX_GROUP_LEADERSHIP_POINTS 8584 //
@ -369,6 +370,15 @@
#define GAIN_GROUP_LEADERSHIP_EXP 8788 //
#define GAIN_RAID_LEADERSHIP_EXP 8789 //
#define BUFF_MINUTES_REMAINING 8799 //%1 (%2 minutes remaining)
#define TASK_REJECT_MAX_COUNT 8891 //You can not be assigned this shared task because your party exceeds the maximum allowed number of players.
#define TASK_REJECT_LEADER_REQ 8892 //You can not be assigned this shared task because the leader does not meet the shared task requirements.
#define TASK_REJECT_MIN_COUNT 8895 //You can not be assigned this shared task because your party does not contain the minimum required number of players.
#define TASK_REJECT_HAVE_ONE 8935 //You may not request a shared task because you already have one.
#define TASK_REJECT_RAID_HAVE_ONE 8936 //You may not request a shared task because someone in your raid, %1, already has one.
#define TASK_REJECT_GROUP_HAVE_ONE 8937 //You may not request a shared task because someone in your group, %1, already has one.
#define TASK_REJECT_LOCKEDOUT 8946 //You may not request this shared task because you must wait %1d:%2h:%3m before you can do another task of this type.
#define TASK_REJECT_LOCKEDOUT_OTHER 8947 //You may not request this shared task because %1 must wait %2d:%3h:%4m before they can do another task of this type.
#define SHARED_TASK_LOCK 8961 //Your shared task is now locked. You may no longer add or remove players.
#define NO_MORE_TRAPS 9002 //You have already placed your maximum number of traps.
#define FEAR_TOO_HIGH 9035 //Your target is too high of a level for your fear spell.
#define SLOW_MOSTLY_SUCCESSFUL 9029 //Your spell was mostly successful.

View File

@ -30,16 +30,19 @@ Copyright (C) 2001-2008 EQEMu Development Team (http://eqemulator.net)
#include "../common/rulesys.h"
#include "../common/string_util.h"
#include "../common/say_link.h"
#include "../common/data_verification.h"
#include "client.h"
#include "entity.h"
#include "mob.h"
#include "string_ids.h"
#include "worldserver.h"
#include "queryserv.h"
#include "quest_parser_collection.h"
extern QueryServ* QServ;
extern WorldServer worldserver;
TaskManager::TaskManager() {
for(int i=0; i<MAXTASKS; i++)
@ -116,15 +119,26 @@ bool TaskManager::LoadTasks(int singleTask)
if (!LoadTaskSets())
Log(Logs::Detail, Logs::Tasks, "TaskManager::LoadTasks LoadTaskSets failed");
query = StringFormat("SELECT `id`, `type`, `duration`, `duration_code`, `title`, `description`, "
"`reward`, `rewardid`, `cashreward`, `xpreward`, `rewardmethod`, `faction_reward`,"
"`minlevel`, `maxlevel`, `repeatable`, `completion_emote` FROM `tasks` WHERE `id` < %i",
MAXTASKS);
if (!LoadReplayGroups())
Log(Logs::Detail, Logs::Tasks, "TaskManager::LoadTasks LoadReplayGroups failed");
query =
StringFormat("SELECT `id`, `type`, `duration`, `duration_code`, `title`, `description`, `reward`, "
"`rewardid`, `cashreward`, `xpreward`, `rewardmethod`, `faction_reward`, `minlevel`, "
"`maxlevel`, `repeatable`, `completion_emote`, `reward_points`, `reward_type`, "
"`replay_group`, `min_players`, `max_players`, `task_lock_step`, `instance_zone_id`, "
"`zone_version`, `zone_in_zone_id`, `zone_in_x`, `zone_in_y`, `zone_in_object_id`, "
"`dest_x`, `dest_y`, `dest_z`, `dest_h` FROM `tasks` WHERE `id` < %i",
MAXTASKS);
} else
query = StringFormat("SELECT `id`, `type`, `duration`, `duration_code`, `title`, `description`, "
"`reward`, `rewardid`, `cashreward`, `xpreward`, `rewardmethod`, `faction_reward`,"
"`minlevel`, `maxlevel`, `repeatable`, `completion_emote` FROM `tasks` WHERE `id` = %i",
singleTask);
query =
StringFormat("SELECT `id`, `type`, `duration`, `duration_code`, `title`, `description`, `reward`, "
"`rewardid`, `cashreward`, `xpreward`, `rewardmethod`, `faction_reward`, `minlevel`, "
"`maxlevel`, `repeatable`, `completion_emote`, `reward_points`, `reward_type`, "
"`replay_group`, `min_players`, `max_players`, `task_lock_step`, `instance_zone_id`, "
"`zone_version`, `zone_in_zone_id`, `zone_in_x`, `zone_in_y`, `zone_in_object_id`, "
"`dest_x`, `dest_y`, `dest_z`, `dest_h` FROM `tasks` WHERE `id` = %i",
singleTask);
const char *ERR_MYSQLERROR = "[TASKS]Error in TaskManager::LoadTasks: %s";
@ -160,6 +174,22 @@ bool TaskManager::LoadTasks(int singleTask)
Tasks[taskID]->MaxLevel = atoi(row[13]);
Tasks[taskID]->Repeatable = atoi(row[14]);
Tasks[taskID]->completion_emote = row[15];
Tasks[taskID]->reward_points = atoi(row[16]);
Tasks[taskID]->reward_type = static_cast<PointType>(atoi(row[17]));
Tasks[taskID]->replay_group = atoi(row[18]);
Tasks[taskID]->min_players = atoi(row[19]);
Tasks[taskID]->max_players = atoi(row[20]);
Tasks[taskID]->task_lock_step = atoi(row[21]);
Tasks[taskID]->instance_zone_id = atoi(row[22]);
Tasks[taskID]->zone_version = atoi(row[23]);
Tasks[taskID]->zone_in_zone_id = atoi(row[24]);
Tasks[taskID]->zone_in_x = atof(row[25]);
Tasks[taskID]->zone_in_y = atof(row[26]);
Tasks[taskID]->zone_in_object_id = atoi(row[27]);
Tasks[taskID]->dest_x = atof(row[28]);
Tasks[taskID]->dest_y = atof(row[29]);
Tasks[taskID]->dest_z = atof(row[30]);
Tasks[taskID]->dest_h = atof(row[31]);
Tasks[taskID]->ActivityCount = 0;
Tasks[taskID]->SequenceMode = ActivitiesSequential;
Tasks[taskID]->LastStep = 0;
@ -281,6 +311,22 @@ bool TaskManager::LoadTasks(int singleTask)
return true;
}
bool TaskManager::LoadReplayGroups()
{
replay_groups.clear();
std::string query = "SELECT `id`, `name`, `duration` FROM `task_replay_groups` WHERE `id` > 0 ORDER BY `id` ASC";
auto results = database.QueryDatabase(query);
if (!results.Success())
return false;
for (auto row = results.begin(); row != results.end(); ++row)
replay_groups[atoi(row[0])] = {row[1], atoi(row[2])};
return true;
}
bool TaskManager::SaveClientState(Client *c, ClientTaskState *state)
{
// I am saving the slot in the ActiveTasks table, because unless a Task is cancelled/completed, the client
@ -905,7 +951,7 @@ bool ClientTaskState::HasSlotForTask(TaskInformation *task)
case TaskType::Task:
return ActiveTask.TaskID == TASKSLOTEMPTY;
case TaskType::Shared:
return false; // todo
return ActiveSharedTask == nullptr; // todo
case TaskType::Quest:
for (int i = 0; i < MAXACTIVEQUESTS; ++i)
if (ActiveQuests[i].TaskID == TASKSLOTEMPTY)
@ -988,7 +1034,7 @@ int TaskManager::GetTaskMaxLevel(int TaskID)
return -1;
}
void TaskManager::TaskSetSelector(Client *c, ClientTaskState *state, Mob *mob, int TaskSetID)
void TaskManager::TaskSetSelector(Client *c, ClientTaskState *state, Mob *mob, int TaskSetID, bool shared)
{
int TaskList[MAXCHOOSERENTRIES];
int TaskListIndex = 0;
@ -1024,14 +1070,14 @@ void TaskManager::TaskSetSelector(Client *c, ClientTaskState *state, Mob *mob, i
// we aren't currently on another, and if it's enabled if not all_enabled
if ((all_enabled || state->IsTaskEnabled(task)) && AppropriateLevel(task, PlayerLevel) &&
!state->IsTaskActive(task) && state->HasSlotForTask(Tasks[task]) && // this slot checking is a bit silly, but we allow mixing of task types ...
(IsTaskRepeatable(task) || !state->IsTaskCompleted(task)))
(IsTaskRepeatable(task) || !state->IsTaskCompleted(task)) && (shared == (Tasks[task]->type == TaskType::Shared)))
TaskList[TaskListIndex++] = task;
++Iterator;
}
if (TaskListIndex > 0) {
SendTaskSelector(c, mob, TaskListIndex, TaskList);
SendTaskSelector(c, mob, TaskListIndex, TaskList, shared);
} else {
mob->SayTo_StringID(c, CC_Yellow, MAX_ACTIVE_TASKS, c->GetName()); // check color, I think this might be only for (Shared) Tasks, w/e -- think should be yellow
}
@ -1041,7 +1087,7 @@ void TaskManager::TaskSetSelector(Client *c, ClientTaskState *state, Mob *mob, i
// unlike the non-Quest version of this function, it does not check enabled, that is assumed the responsibility of the quest to handle
// we do however still want it to check the other stuff like level, active, room, etc
void TaskManager::TaskQuestSetSelector(Client *c, ClientTaskState *state, Mob *mob, int count, int *tasks)
void TaskManager::TaskQuestSetSelector(Client *c, ClientTaskState *state, Mob *mob, int count, int *tasks, bool shared)
{
int TaskList[MAXCHOOSERENTRIES];
int TaskListIndex = 0;
@ -1058,12 +1104,12 @@ void TaskManager::TaskQuestSetSelector(Client *c, ClientTaskState *state, Mob *m
// we aren't currently on another, and if it's enabled if not all_enabled
if (AppropriateLevel(task, PlayerLevel) &&
!state->IsTaskActive(task) && state->HasSlotForTask(Tasks[task]) && // this slot checking is a bit silly, but we allow mixing of task types ...
(IsTaskRepeatable(task) || !state->IsTaskCompleted(task)))
(IsTaskRepeatable(task) || !state->IsTaskCompleted(task)) && (shared == (Tasks[task]->type == TaskType::Shared)))
TaskList[TaskListIndex++] = task;
}
if (TaskListIndex > 0) {
SendTaskSelector(c, mob, TaskListIndex, TaskList);
SendTaskSelector(c, mob, TaskListIndex, TaskList, shared);
} else {
mob->SayTo_StringID(c, CC_Yellow, MAX_ACTIVE_TASKS, c->GetName()); // check color, I think this might be only for (Shared) Tasks, w/e -- think should be yellow
}
@ -1071,11 +1117,11 @@ void TaskManager::TaskQuestSetSelector(Client *c, ClientTaskState *state, Mob *m
return;
}
void TaskManager::SendTaskSelector(Client *c, Mob *mob, int TaskCount, int *TaskList) {
void TaskManager::SendTaskSelector(Client *c, Mob *mob, int TaskCount, int *TaskList, bool shared) {
if (c->ClientVersion() >= EQEmu::versions::ClientVersion::RoF)
{
SendTaskSelectorNew(c, mob, TaskCount, TaskList);
SendTaskSelectorNew(c, mob, TaskCount, TaskList, shared);
return;
}
// Titanium OpCode: 0x5e7c
@ -1108,7 +1154,7 @@ void TaskManager::SendTaskSelector(Client *c, Mob *mob, int TaskCount, int *Task
buf.WriteUInt32(ValidTasks);
buf.WriteUInt32(2); // task type, live doesn't let you send more than one type, but we do?
buf.WriteUInt32(shared ? static_cast<uint32>(TaskType::Shared) : static_cast<uint32>(TaskType::Quest)); // hack, we need to send only shared tasks when doing shared tasks since they use different reply ops
buf.WriteUInt32(mob->GetID());
for (int i = 0; i < TaskCount; i++) {
@ -1158,7 +1204,7 @@ void TaskManager::SendTaskSelector(Client *c, Mob *mob, int TaskCount, int *Task
}
void TaskManager::SendTaskSelectorNew(Client *c, Mob *mob, int TaskCount, int *TaskList)
void TaskManager::SendTaskSelectorNew(Client *c, Mob *mob, int TaskCount, int *TaskList, bool shared)
{
Log(Logs::General, Logs::Tasks, "[UPDATE] TaskSelector for %i Tasks", TaskCount);
@ -1188,9 +1234,7 @@ void TaskManager::SendTaskSelectorNew(Client *c, Mob *mob, int TaskCount, int *T
SerializeBuffer buf(50 * ValidTasks);
buf.WriteUInt32(ValidTasks); // TaskCount
buf.WriteUInt32(2); // Type, valid values: 0-3. 0 = Task, 1 = Shared Task, 2 = Quest, 3 = ??? -- should fix maybe some day, but we let more than 1 type through :P
// so I guess an NPC can only offer one type of quests or we can only open a selection with one type :P (so quest call can tell us I guess)
// this is also sent in OP_TaskDescription
buf.WriteUInt32(shared ? static_cast<uint32>(TaskType::Shared) : static_cast<uint32>(TaskType::Quest)); // hack, we need to send only shared tasks when doing shared tasks since they use different reply ops
buf.WriteUInt32(mob->GetID()); // TaskGiver
for (int i = 0; i < TaskCount; i++) { // max 40
@ -1304,7 +1348,8 @@ ClientTaskState::ClientTaskState() {
ActiveTask.slot = 0;
ActiveTask.TaskID = TASKSLOTEMPTY;
// TODO: shared task
ActiveSharedTask = nullptr;
}
ClientTaskState::~ClientTaskState() {
@ -3324,6 +3369,164 @@ void ClientTaskState::AcceptNewTask(Client *c, int TaskID, int NPCID, bool enfor
parse->EventNPC(EVENT_TASK_ACCEPTED, npc, c, buf.c_str(), 0);
}
// This function will do a bunch of verification, then set up a pending state which will then send a request
// to world and send off requests to out of group zones to ask if they can join the task
// Once the we get all of the replies that pass, we will then assign the task
void ClientTaskState::PendSharedTask(Client *c, int TaskID, int NPCID, bool enforce_level_requirement)
{
if (!taskmanager || TaskID < 0 || TaskID >= MAXTASKS) {
c->Message(13, "Task system not functioning, or TaskID %i out of range.", TaskID);
return;
}
auto task = taskmanager->Tasks[TaskID];
if (task == nullptr) {
c->Message(13, "Invalid TaskID %i", TaskID);
return;
}
if (task->type != TaskType::Shared) {
c->Message(13, "Trying to shared task a non shared task %i", TaskID);
return;
}
if (ActiveSharedTask != nullptr) {
c->Message_StringID(13, TASK_REJECT_HAVE_ONE);
return;
}
if (enforce_level_requirement && !taskmanager->AppropriateLevel(TaskID, c->GetLevel())) {
c->Message(13, "You are outside the level range of this task.");
return;
}
if (!taskmanager->IsTaskRepeatable(TaskID) && IsTaskCompleted(TaskID))
return;
if (task->replay_group) {
auto expires = c->GetTaskLockoutExpire(task->replay_group);
if (expires) {
auto diff = expires - Timer::GetCurrentTime();
std::string days = std::to_string(diff / 86400);
diff = diff % 86400;
std::string hours = std::to_string(diff / 3600);
diff = diff % 3600;
std::string minutes = std::to_string(diff / 60);
c->Message_StringID(13, TASK_REJECT_LOCKEDOUT, days.c_str(), hours.c_str(), minutes.c_str());
return;
}
}
// Now we need to verify we meet min_player and max_players for raid/group
Group *group = nullptr;
Raid *raid = nullptr;
int player_count = 1; // 1 is just us!
if (c->IsGrouped()) {
group = c->GetGroup();
player_count = group->GroupCount();
} else if (c->IsRaidGrouped()) {
raid = c->GetRaid();
player_count = raid->RaidCount();
}
// TODO: check task lockouts I guess it's simpler to require everyone to be in zone so we can verify lockouts ...
if (!EQEmu::ValueWithin(player_count, task->min_players, task->max_players)) {
if (player_count < task->min_players)
c->Message_StringID(13, TASK_REJECT_MIN_COUNT);
else
c->Message_StringID(13, TASK_REJECT_MAX_COUNT);
return;
}
std::vector<std::string> missing_players; // names of players not in this zone so we can put the checks off to world
bool task_failed = false;
if (group) {
for (int i = 0; i < MAX_GROUP_MEMBERS; ++i) {
if (group->members[i] && group->members[i]->IsClient()) {
auto *client = group->members[i]->CastToClient();
auto *task_state = client->GetTaskState();
if (!task_state->HasSlotForTask(task)) {
task_failed = true;
c->Message_StringID(13, TASK_REJECT_GROUP_HAVE_ONE, c->GetName());
} else {
if (task->replay_group) {
auto expires = client->GetTaskLockoutExpire(task->replay_group);
if (expires) {
task_failed = true;
auto diff = expires - Timer::GetCurrentTime();
std::string days = std::to_string(diff / 86400);
diff = diff % 86400;
std::string hours = std::to_string(diff / 3600);
diff = diff % 3600;
std::string minutes = std::to_string(diff / 60);
c->Message_StringID(13, TASK_REJECT_LOCKEDOUT_OTHER,
client->GetName(), days.c_str(),
hours.c_str(), minutes.c_str());
client->Message_StringID(13, TASK_REJECT_LOCKEDOUT_ME,
days.c_str(), hours.c_str(),
minutes.c_str());
}
}
}
} else if (group->members[i] == nullptr) { // out of zone
missing_players.push_back(group->membername[i]);
}
}
} else if (raid) {
for (int i = 0; i < MAX_RAID_MEMBERS; ++i) {
if (raid->members[i].member) {
auto *client = raid->members[i].member;
auto *task_state = client->GetTaskState();
if (!task_state->HasSlotForTask(task)) {
task_failed = true;
c->Message_StringID(13, TASK_REJECT_RAID_HAVE_ONE, c->GetName());
} else {
if (task->replay_group) {
auto expires = client->GetTaskLockoutExpire(task->replay_group);
if (expires) {
task_failed = true;
auto diff = expires - Timer::GetCurrentTime();
std::string days = std::to_string(diff / 86400);
diff = diff % 86400;
std::string hours = std::to_string(diff / 3600);
diff = diff % 3600;
std::string minutes = std::to_string(diff / 60);
c->Message_StringID(13, TASK_REJECT_LOCKEDOUT_OTHER,
client->GetName(), days.c_str(),
hours.c_str(), minutes.c_str());
client->Message_StringID(13, TASK_REJECT_LOCKEDOUT_ME,
days.c_str(), hours.c_str(),
minutes.c_str());
}
}
}
} else if (raid->members[i].membername[0] != '\0') { // out of zone
missing_players.push_back(raid->members[i].membername);
}
}
}
if (task_failed) // we already yelled at them
return;
// so we've verified all the clients we can and didn't fail, time to pend and yell at world
c->SetPendingTask(TaskID, NPCID);
c->StartPendingTimer(); // in case something goes wrong and takes ages, we time out
SerializeBuffer buf(25 + 10 * missing_players.size());
buf.WriteInt32(TaskID); // Task ID
buf.WriteString(c->GetName()); // leader name
buf.WriteInt32(missing_players.size()); // count
for (auto && name : missing_players)
buf.WriteString(name);
auto pack = new ServerPacket(ServerOP_TaskRequest, buf);
worldserver.SendPacket(pack);
delete pack;
}
void ClientTaskState::ProcessTaskProximities(Client *c, float X, float Y, float Z) {
float LastX = c->ProximityX();
@ -3537,3 +3740,34 @@ int TaskProximityManager::CheckProximities(float X, float Y, float Z) {
return 0;
}
void SharedTaskState::LockTask()
{
SetLocked(true);
for (auto & m : members)
if (m.entity != nullptr)
m.entity->Message_StringID(CC_Yellow, SHARED_TASK_LOCK);
}
void SharedTaskState::MemberZoned(Mob *player)
{
auto it = std::find_if(members.begin(), members.end(),
[&player](const SharedTaskMember &a) { return a.name == player->GetName(); });
if (it == members.end()) // guess they weren't in this group, w/e
return;
it->entity = nullptr;
}
void SharedTaskState::MemberEnterZone(Mob *player)
{
auto it = std::find_if(members.begin(), members.end(),
[&player](const SharedTaskMember &a) { return a.name == player->GetName(); });
if (it == members.end()) // guess they weren't in this group, w/e
return;
it->entity = player;
}

View File

@ -26,6 +26,8 @@ Copyright (C) 2001-2004 EQEMu Development Team (http://eqemulator.net)
#include <vector>
#include <string>
#include <algorithm>
#include <unordered_map>
#include <map>
#define MAXTASKS 10000
#define MAXTASKSETS 1000
@ -141,6 +143,13 @@ enum class DurationCode {
Long = 3
};
// need to capture more, shared are just Radiant/Ebon though
enum class PointType {
None = 0,
Radiant = 4,
Ebon = 5,
};
struct TaskInformation {
TaskType type;
int Duration;
@ -155,12 +164,33 @@ struct TaskInformation {
int XPReward;
int faction_reward; // just a npc_faction_id
TaskMethodType RewardMethod;
int reward_points; // DoN crystals for shared. Generic "points" for non-shared
PointType reward_type; // 4 for Radiant Crystals else Ebon crystals when shared task
int ActivityCount;
SequenceType SequenceMode;
int LastStep;
short MinLevel;
short MaxLevel;
bool Repeatable;
int replay_group; // ID of our replay timer group (0 means none)
int min_players; // shared tasks
int max_players;
int task_lock_step; // task locks after this step is completed
uint32 instance_zone_id; // instance shit
uint32 zone_version;
uint16 zone_in_zone_id;
float zone_in_x;
float zone_in_y;
uint16 zone_in_object_id;
float dest_x;
float dest_y;
float dest_z;
float dest_h;
/* int graveyard_zone_id;
float graveyard_x;
float graveyard_y;
float graveyard_z;
float graveyard_radius; */
ActivityInformation Activity[MAXACTIVITIESPERTASK];
};
@ -193,6 +223,35 @@ struct CompletedTaskInformation {
bool ActivityDone[MAXACTIVITIESPERTASK];
};
struct SharedTaskMember {
std::string name;
Mob *entity; // needs to be managed
bool leader;
SharedTaskMember() : entity(nullptr), leader(false) {}
};
class SharedTaskState {
public:
SharedTaskState() : locked(false) {}
// ~SharedTaskState();
inline const bool IsLocked() const { return locked; }
inline void SetLocked(bool v) { locked = v; }
void LockTask(); // notified clients (if they are etc)
void MemberZoned(Mob *player); // player left zone, update their pointer
void MemberEnterZone(Mob *player); // player entered zone, update their pointer
ClientTaskInformation *GetActivity() { return &activity; }
friend class TaskManager;
private:
std::vector<SharedTaskMember> members;
ClientTaskInformation activity;
bool locked;
};
class ClientTaskState {
public:
@ -206,6 +265,8 @@ public:
int GetTaskActivityDoneCountFromTaskID(int TaskID, int ActivityID);
int GetTaskStartTime(TaskType type, int index);
void AcceptNewTask(Client *c, int TaskID, int NPCID, bool enforce_level_requirement = false);
// void AcceptNewSharedTask(Client *c, int TaskID, int NPCID, bool enforce_level_requirement = false);
void PendSharedTask(Client *c, int TaskID, int NPCID, bool enforce_level_requirement = false);
void FailTask(Client *c, int TaskID);
int TaskTimeLeft(int TaskID);
int IsTaskCompleted(int TaskID);
@ -255,6 +316,8 @@ private:
info = &ActiveTask;
break;
case TaskType::Shared:
if (index == 0 && ActiveSharedTask)
info = ActiveSharedTask->GetActivity();
break;
case TaskType::Quest:
if (index < MAXACTIVEQUESTS)
@ -273,6 +336,7 @@ private:
};
ClientTaskInformation ActiveTasks[MAXACTIVEQUESTS + 1];
};
SharedTaskState *ActiveSharedTask; // pointer to our shared task managed by TaskManager
// Shared tasks should be limited to 1 as well
std::vector<int> EnabledTasks;
std::vector<CompletedTaskInformation> CompletedTasks;
@ -280,6 +344,17 @@ private:
bool CheckedTouchActivities;
};
// used for timer lockouts and /tasktimers
struct TaskTimer {
int ID; // ID used in task timer
int original_id; // original ID of the task
int expires; // UNIX timestamp of when it expires, what happens with DLS? Fuck it.
};
struct TaskReplayGroups {
std::string name;
int duration;
};
class TaskManager {
@ -289,18 +364,19 @@ public:
int GetActivityCount(int TaskID);
bool LoadSingleTask(int TaskID);
bool LoadTasks(int SingleTask=0);
bool LoadReplayGroups();
void ReloadGoalLists();
inline void LoadProximities(int ZoneID) { ProximityManager.LoadProximities(ZoneID); }
bool LoadTaskSets();
bool LoadClientState(Client *c, ClientTaskState *state);
bool SaveClientState(Client *c, ClientTaskState *state);
void SendTaskSelector(Client *c, Mob *mob, int TaskCount, int *TaskList);
void SendTaskSelectorNew(Client *c, Mob *mob, int TaskCount, int *TaskList);
void SendTaskSelector(Client *c, Mob *mob, int TaskCount, int *TaskList, bool shared = false); // dumb hack cuz we do dumb things
void SendTaskSelectorNew(Client *c, Mob *mob, int TaskCount, int *TaskList, bool shared = false);
bool AppropriateLevel(int TaskID, int PlayerLevel);
int GetTaskMinLevel(int TaskID);
int GetTaskMaxLevel(int TaskID);
void TaskSetSelector(Client *c, ClientTaskState *state, Mob *mob, int TaskSetID);
void TaskQuestSetSelector(Client *c, ClientTaskState *state, Mob *mob, int count, int *tasks); // task list provided by QuestManager (perl/lua)
void TaskSetSelector(Client *c, ClientTaskState *state, Mob *mob, int TaskSetID, bool shared = false);
void TaskQuestSetSelector(Client *c, ClientTaskState *state, Mob *mob, int count, int *tasks, bool shared = false); // task list provided by QuestManager (perl/lua)
void SendActiveTasksToClient(Client *c, bool TaskComplete=false);
void SendSingleActiveTaskToClient(Client *c, ClientTaskInformation &task_info, bool TaskComplete, bool BringUpTaskJournal = false);
void SendTaskActivityShort(Client *c, int TaskID, int ActivityID, int ClientTaskIndex);
@ -316,6 +392,7 @@ public:
bool IsTaskRepeatable(int TaskID);
friend class ClientTaskState;
void LoadSharedTask(int id); // loads the shared task state
private:
TaskGoalListManager GoalListManager;
@ -323,6 +400,8 @@ private:
TaskInformation* Tasks[MAXTASKS];
std::vector<int> TaskSets[MAXTASKSETS];
void SendActiveTaskDescription(Client *c, int TaskID, ClientTaskInformation &task_info, int StartTime, int Duration, bool BringUpTaskJournal=false);
std::unordered_map<int, SharedTaskState> SharedTasks;
std::map<int, TaskReplayGroups> replay_groups;
};