From 39544b472337568bfb9580bca6158aebb066c40b Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Sat, 1 Sep 2018 17:32:13 -0400 Subject: [PATCH] Shared Task WIP --- common/emu_oplist.h | 1 + common/eq_packet_structs.h | 8 + common/serialize_buffer.h | 1 + common/servertalk.h | 20 ++ utils/patches/patch_RoF.conf | 1 + utils/patches/patch_RoF2.conf | 1 + utils/patches/patch_SoD.conf | 1 + utils/patches/patch_SoF.conf | 1 + utils/patches/patch_Titanium.conf | 1 + utils/patches/patch_UF.conf | 1 + utils/sql/git/required/sharedtasks.sql | 22 ++ zone/client.h | 20 +- zone/client_packet.cpp | 15 ++ zone/client_packet.h | 1 + zone/client_process.cpp | 6 + zone/embparser_api.cpp | 18 ++ zone/lua_general.cpp | 29 ++- zone/questmgr.cpp | 8 +- zone/questmgr.h | 4 +- zone/string_ids.h | 10 + zone/tasks.cpp | 280 +++++++++++++++++++++++-- zone/tasks.h | 87 +++++++- 22 files changed, 500 insertions(+), 36 deletions(-) create mode 100644 utils/sql/git/required/sharedtasks.sql diff --git a/common/emu_oplist.h b/common/emu_oplist.h index 5078cca68..3f4556a07 100644 --- a/common/emu_oplist.h +++ b/common/emu_oplist.h @@ -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), diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index a35c264e0..7a56d01ab 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -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; diff --git a/common/serialize_buffer.h b/common/serialize_buffer.h index 9b350b4fa..909b9ca9a 100644 --- a/common/serialize_buffer.h +++ b/common/serialize_buffer.h @@ -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); diff --git a/common/servertalk.h b/common/servertalk.h index e1d019fc4..eb357a180 100644 --- a/common/servertalk.h +++ b/common/servertalk.h @@ -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 #include @@ -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; diff --git a/utils/patches/patch_RoF.conf b/utils/patches/patch_RoF.conf index 9467ab337..016586ba8 100644 --- a/utils/patches/patch_RoF.conf +++ b/utils/patches/patch_RoF.conf @@ -563,6 +563,7 @@ OP_TaskHistoryRequest=0x6cf6 OP_TaskHistoryReply=0x25eb OP_DeclineAllTasks=0x0000 OP_TaskRequestTimer=0x4b76 +OP_AcceptNewSharedTask=0x3e5e # Title opcodes OP_NewTitlesAvailable=0x45d1 diff --git a/utils/patches/patch_RoF2.conf b/utils/patches/patch_RoF2.conf index f410a3193..cf6704b55 100644 --- a/utils/patches/patch_RoF2.conf +++ b/utils/patches/patch_RoF2.conf @@ -568,6 +568,7 @@ OP_TaskHistoryRequest=0x5f1c OP_TaskHistoryReply=0x3d05 OP_DeclineAllTasks=0x0000 OP_TaskRequestTimer=0x7a48 +OP_AcceptNewSharedTask=0x6646 # Title opcodes OP_NewTitlesAvailable=0x0d32 diff --git a/utils/patches/patch_SoD.conf b/utils/patches/patch_SoD.conf index d836c6360..d5c3f7931 100644 --- a/utils/patches/patch_SoD.conf +++ b/utils/patches/patch_SoD.conf @@ -534,6 +534,7 @@ OP_TaskHistoryReply=0x3d2a # C OP_CancelTask=0x726b # C OP_DeclineAllTasks=0x0000 # OP_TaskRequestTimer=0x2e70 +OP_AcceptNewSharedTask=0x4751 OP_Shroud=0x6d1f diff --git a/utils/patches/patch_SoF.conf b/utils/patches/patch_SoF.conf index a10715918..ab5bf766a 100644 --- a/utils/patches/patch_SoF.conf +++ b/utils/patches/patch_SoF.conf @@ -510,6 +510,7 @@ OP_TaskRemovePlayer=0x516f OP_TaskPlayerList=0x0ad6 OP_TaskQuit=0x2c8c OP_TaskRequestTimer=0x0b08 +OP_AcceptNewSharedTask=0x5bed #Title opcodes OP_NewTitlesAvailable=0x179c # diff --git a/utils/patches/patch_Titanium.conf b/utils/patches/patch_Titanium.conf index 9a7e1e800..3f6042386 100644 --- a/utils/patches/patch_Titanium.conf +++ b/utils/patches/patch_Titanium.conf @@ -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) diff --git a/utils/patches/patch_UF.conf b/utils/patches/patch_UF.conf index 71966dcbb..9bc3013ff 100644 --- a/utils/patches/patch_UF.conf +++ b/utils/patches/patch_UF.conf @@ -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 diff --git a/utils/sql/git/required/sharedtasks.sql b/utils/sql/git/required/sharedtasks.sql new file mode 100644 index 000000000..af61a7218 --- /dev/null +++ b/utils/sql/git/required/sharedtasks.sql @@ -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`) +); diff --git a/zone/client.h b/zone/client.h index ead552f73..015cc1670 100644 --- a/zone/client.h +++ b/zone/client.h @@ -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 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& 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 zone_flags; ClientTaskState *taskstate; + PendingSharedTask pending_task; int TotalSecondsPlayed; //Anti Spam Stuff diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 5cafbaee2..48552c7ec 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -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)) diff --git a/zone/client_packet.h b/zone/client_packet.h index 9605dc8cf..1163bbba5 100644 --- a/zone/client_packet.h +++ b/zone/client_packet.h @@ -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); diff --git a/zone/client_process.cpp b/zone/client_process.cpp index bd14dc70a..a87529665 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -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(); diff --git a/zone/embparser_api.cpp b/zone/embparser_api.cpp index 18c84ccdd..9a3b183c7 100644 --- a/zone/embparser_api.cpp +++ b/zone/embparser_api.cpp @@ -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); diff --git a/zone/lua_general.cpp b/zone/lua_general.cpp index da62dc305..d4bc7a830 100644 --- a/zone/lua_general.cpp +++ b/zone/lua_general.cpp @@ -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(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), diff --git a/zone/questmgr.cpp b/zone/questmgr.cpp index 1393b0221..39c46da00 100644 --- a/zone/questmgr.cpp +++ b/zone/questmgr.cpp @@ -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) { diff --git a/zone/questmgr.h b/zone/questmgr.h index a3b9fac43..41dff79db 100644 --- a/zone/questmgr.h +++ b/zone/questmgr.h @@ -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); diff --git a/zone/string_ids.h b/zone/string_ids.h index 2c223ffbd..410d718f4 100644 --- a/zone/string_ids.h +++ b/zone/string_ids.h @@ -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. diff --git a/zone/tasks.cpp b/zone/tasks.cpp index 56297bf3b..acffb0ee6 100644 --- a/zone/tasks.cpp +++ b/zone/tasks.cpp @@ -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; iMaxLevel = 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(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(TaskType::Shared) : static_cast(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(TaskType::Shared) : static_cast(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 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; +} + diff --git a/zone/tasks.h b/zone/tasks.h index 0bc45c146..97792bcd2 100644 --- a/zone/tasks.h +++ b/zone/tasks.h @@ -26,6 +26,8 @@ Copyright (C) 2001-2004 EQEMu Development Team (http://eqemulator.net) #include #include #include +#include +#include #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 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 EnabledTasks; std::vector 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 TaskSets[MAXTASKSETS]; void SendActiveTaskDescription(Client *c, int TaskID, ClientTaskInformation &task_info, int StartTime, int Duration, bool BringUpTaskJournal=false); + std::unordered_map SharedTasks; + std::map replay_groups; };