diff --git a/common/shared_tasks.cpp b/common/shared_tasks.cpp index 894f9c280..049cc2f6a 100644 --- a/common/shared_tasks.cpp +++ b/common/shared_tasks.cpp @@ -150,3 +150,8 @@ SharedTaskMember SharedTask::GetLeader() const } return {}; } + +bool SharedTask::IsExpired() const +{ + return m_db_shared_task.expire_time > 0 && m_db_shared_task.expire_time < std::time(nullptr); +} diff --git a/common/shared_tasks.h b/common/shared_tasks.h index 826d58911..c11a0a74a 100644 --- a/common/shared_tasks.h +++ b/common/shared_tasks.h @@ -31,6 +31,9 @@ #define ServerOP_SharedTaskMemberChange 0x0314 // world -> zone. Send shared task single member added/removed (client also handles message) #define ServerOP_SharedTaskKickPlayers 0x0315 // zone -> world /kickplayers task #define ServerOP_SharedTaskLock 0x0316 // zone -> world +#define ServerOP_SharedTaskEnd 0x0317 // zone -> world +#define ServerOP_SharedTaskEndByDz 0x0318 // zone -> world +#define ServerOP_SharedTaskFailed 0x0319 // world -> zone. Sends red text task failed banner to client enum class SharedTaskRequestGroupType { Solo = 0, @@ -176,6 +179,18 @@ struct ServerSharedTaskLock_Struct { bool lock; }; +struct ServerSharedTaskCharacterTask_Struct { + uint32 character_id; + uint32 task_id; +}; + +struct ServerSharedTaskEnd_Struct { + uint32 character_id; + uint32 task_id; + uint32 dz_id; + bool send_fail; // if true members receive red text failure banner +}; + class SharedTask { public: // used in both zone and world validation @@ -187,6 +202,7 @@ public: SharedTaskMember GetLeader() const; std::vector GetActivityState() const; const std::vector& GetMembers() const; + bool IsExpired() const; // getters const std::vector &GetTaskActivityData() const; diff --git a/world/shared_task_manager.cpp b/world/shared_task_manager.cpp index 48095c82b..f55f9f6bd 100644 --- a/world/shared_task_manager.cpp +++ b/world/shared_task_manager.cpp @@ -242,6 +242,27 @@ void SharedTaskManager::RemoveEveryoneFromSharedTask(SharedTask *t, uint32 reque PrintSharedTaskState(); } +void SharedTaskManager::Terminate(SharedTask& s, bool send_fail, bool erase) +{ + LogTasksDetail("[Terminate] shared task [{}]", s.GetDbSharedTask().id); + for (const auto& member : s.GetMembers()) + { + if (send_fail) + { + SendSharedTaskFailed(member.character_id, s.GetTaskData().id); + } + SendRemovePlayerFromSharedTaskPacket(member.character_id, s.GetTaskData().id, true); + client_list.SendCharacterMessageID(member.character_id, Chat::Yellow, TaskStr::HAS_ENDED, {s.GetTaskData().title}); + } + + RemoveAllMembersFromDynamicZones(&s); + + if (erase) + { + DeleteSharedTask(s.GetDbSharedTask().id); + } +} + void SharedTaskManager::DeleteSharedTask(int64 shared_task_id) { LogTasksDetail( @@ -689,6 +710,20 @@ void SharedTaskManager::SendRemovePlayerFromSharedTaskPacket( } } +void SharedTaskManager::SendSharedTaskFailed(uint32_t character_id, uint32_t task_id) +{ + ServerPacket pack(ServerOP_SharedTaskFailed, sizeof(ServerSharedTaskCharacterTask_Struct)); + auto buf = reinterpret_cast(pack.pBuffer); + buf->character_id = character_id; + buf->task_id = task_id; + + ClientListEntry* cle = client_list.FindCLEByCharacterID(character_id); + if (cle && cle->Server()) + { + cle->Server()->SendPacket(&pack); + } +} + void SharedTaskManager::SendSharedTaskMemberList(uint32 character_id, const std::vector &members) { EQ::Net::DynamicPacket dyn_pack; @@ -1042,6 +1077,18 @@ SharedTask *SharedTaskManager::FindSharedTaskById(int64 shared_task_id) return nullptr; } +SharedTask* SharedTaskManager::FindSharedTaskByDzId(uint32_t dz_id) +{ + for (auto& s: m_shared_tasks) { + auto it = std::find(s.dynamic_zone_ids.begin(), s.dynamic_zone_ids.end(), dz_id); + if (it != s.dynamic_zone_ids.end()) { + return &s; + } + } + + return nullptr; +} + void SharedTaskManager::QueueActiveInvitation(int64 shared_task_id, int64 character_id) { LogTasksDetail( @@ -1757,16 +1804,9 @@ void SharedTaskManager::Process() std::vector delete_tasks; for (auto& shared_task : m_shared_tasks) { - if (shared_task.GetMembers().empty() || shared_task.terminate_timer.Check()) + if (shared_task.GetMembers().empty() || shared_task.terminate_timer.Check() || shared_task.IsExpired()) { - LogTasksDetail("[Process] Terminating shared task [{}]", shared_task.GetDbSharedTask().id); - for (const auto& member : shared_task.GetMembers()) - { - SendRemovePlayerFromSharedTaskPacket(member.character_id, shared_task.GetTaskData().id, true); - client_list.SendCharacterMessageID(member.character_id, Chat::Yellow, TaskStr::HAS_ENDED, {shared_task.GetTaskData().title}); - } - - RemoveAllMembersFromDynamicZones(&shared_task); + Terminate(shared_task, false, false); // avoid erasing from m_shared_tasks while iterating it delete_tasks.push_back(shared_task.GetDbSharedTask().id); diff --git a/world/shared_task_manager.h b/world/shared_task_manager.h index a456a7139..3f41ef7be 100644 --- a/world/shared_task_manager.h +++ b/world/shared_task_manager.h @@ -50,6 +50,7 @@ public: SharedTask *FindSharedTaskByTaskIdAndCharacterId(uint32 task_id, uint32 character_id); SharedTask *FindSharedTaskById(int64 shared_task_id); + SharedTask* FindSharedTaskByDzId(uint32_t dz_id); void DeleteSharedTask(int64 shared_task_id); void SaveSharedTaskActivityState(int64 shared_task_id, std::vector activity_state); @@ -58,6 +59,7 @@ public: void LockTask(SharedTask* s, bool lock); void SendAcceptNewSharedTaskPacket(uint32 character_id, uint32 task_id, uint32_t npc_context_id, int accept_time); void SendRemovePlayerFromSharedTaskPacket(uint32 character_id, uint32 task_id, bool remove_from_db); + void SendSharedTaskFailed(uint32_t character_id, uint32_t task_id); void SendSharedTaskMemberList(uint32 character_id, const std::vector &members); void SendSharedTaskMemberList(uint32 character_id, const EQ::Net::DynamicPacket &serialized_members); void SendSharedTaskMemberChange( @@ -72,6 +74,9 @@ public: void RemoveMember(SharedTask* s, const SharedTaskMember& member, bool remove_from_db); void RemoveEveryoneFromSharedTask(SharedTask *s, uint32 requested_character_id); + // caller is responsible for removing from db/cache if erase is false + void Terminate(SharedTask& s, bool send_fail, bool erase); + void MakeLeaderByPlayerName(SharedTask *s, const std::string &character_name); void AddPlayerByCharacterIdAndName(SharedTask *s, int64 character_id, const std::string &character_name); void InvitePlayerByPlayerName(SharedTask *s, const std::string &player_name); diff --git a/world/shared_task_world_messaging.cpp b/world/shared_task_world_messaging.cpp index 325008487..a9f513592 100644 --- a/world/shared_task_world_messaging.cpp +++ b/world/shared_task_world_messaging.cpp @@ -338,6 +338,24 @@ void SharedTaskWorldMessaging::HandleZoneMessage(ServerPacket *pack) } break; } + case ServerOP_SharedTaskEndByDz: { + auto buf = reinterpret_cast(pack->pBuffer); + auto shared_task = shared_task_manager.FindSharedTaskByDzId(buf->dz_id); + if (shared_task) + { + shared_task_manager.Terminate(*shared_task, buf->send_fail, true); + } + break; + } + case ServerOP_SharedTaskEnd: { + auto buf = reinterpret_cast(pack->pBuffer); + auto shared_task = shared_task_manager.FindSharedTaskByTaskIdAndCharacterId(buf->task_id, buf->character_id); + if (shared_task) + { + shared_task_manager.Terminate(*shared_task, buf->send_fail, true); + } + break; + } default: break; } diff --git a/world/zoneserver.cpp b/world/zoneserver.cpp index 56ea01412..49140ff83 100644 --- a/world/zoneserver.cpp +++ b/world/zoneserver.cpp @@ -1411,6 +1411,8 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { case ServerOP_SharedTaskPurgeAllCommand: case ServerOP_SharedTaskPlayerList: case ServerOP_SharedTaskLock: + case ServerOP_SharedTaskEndByDz: + case ServerOP_SharedTaskEnd: case ServerOP_SharedTaskKickPlayers: { SharedTaskWorldMessaging::HandleZoneMessage(pack); break; diff --git a/zone/client.h b/zone/client.h index 7f670138b..fabafe2a8 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1274,6 +1274,7 @@ public: } void PurgeTaskTimers(); void LockSharedTask(bool lock) { if (task_state) { task_state->LockSharedTask(this, lock); } } + void EndSharedTask(bool fail = false) { if (task_state) { task_state->EndSharedTask(this, fail); } } // shared task shims / middleware // these variables are used as a shim to intercept normal localized task functionality diff --git a/zone/embparser_api.cpp b/zone/embparser_api.cpp index ffb244759..07be95ce6 100644 --- a/zone/embparser_api.cpp +++ b/zone/embparser_api.cpp @@ -1257,6 +1257,21 @@ std::string Perl__gettaskname(uint32 task_id) return quest_manager.gettaskname(task_id); } +int Perl__get_dz_task_id() +{ + return quest_manager.GetCurrentDzTaskID(); +} + +void Perl__end_dz_task() +{ + quest_manager.EndCurrentDzTask(); +} + +void Perl__end_dz_task(bool send_fail) +{ + quest_manager.EndCurrentDzTask(send_fail); +} + void Perl__popup(const char* window_title, const char* message) { quest_manager.popup(window_title, message, 0, 0, 0); @@ -4078,6 +4093,8 @@ void perl_register_quest() package.add("enablerecipe", &Perl__enablerecipe); package.add("enabletask", &Perl__enabletask); package.add("enabletitle", &Perl__enabletitle); + package.add("end_dz_task", (void(*)())&Perl__end_dz_task); + package.add("end_dz_task", (void(*)(bool))&Perl__end_dz_task); package.add("exp", &Perl__exp); package.add("faction", (void(*)(int, int))&Perl__faction); package.add("faction", (void(*)(int, int, int))&Perl__faction); @@ -4101,6 +4118,7 @@ void perl_register_quest() package.add("getconsiderlevelname", &Perl__getconsiderlevelname); package.add("gethexcolorcode", &Perl__gethexcolorcode); package.add("getcurrencyid", &Perl__getcurrencyid); + package.add("get_dz_task_id", &Perl__get_dz_task_id); package.add("getexpmodifierbycharid", (double(*)(uint32, uint32))&Perl__getexpmodifierbycharid); package.add("getexpmodifierbycharid", (double(*)(uint32, uint32, int16))&Perl__getexpmodifierbycharid); package.add("get_expedition", &Perl__get_expedition); diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index 945f1a572..496d996b9 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -1445,6 +1445,16 @@ void Lua_Client::LockSharedTask(bool lock) { return self->LockSharedTask(lock); } +void Lua_Client::EndSharedTask() { + Lua_Safe_Call_Void(); + self->EndSharedTask(); +} + +void Lua_Client::EndSharedTask(bool send_fail) { + Lua_Safe_Call_Void(); + self->EndSharedTask(send_fail); +} + int Lua_Client::GetCorpseCount() { Lua_Safe_Call_Int(); return self->GetCorpseCount(); @@ -2657,6 +2667,8 @@ luabind::scope lua_register_client() { .def("EnableAreaHPRegen", &Lua_Client::EnableAreaHPRegen) .def("EnableAreaManaRegen", &Lua_Client::EnableAreaManaRegen) .def("EnableAreaRegens", &Lua_Client::EnableAreaRegens) + .def("EndSharedTask", (void(Lua_Client::*)(void))&Lua_Client::EndSharedTask) + .def("EndSharedTask", (void(Lua_Client::*)(bool))&Lua_Client::EndSharedTask) .def("Escape", (void(Lua_Client::*)(void))&Lua_Client::Escape) .def("FailTask", (void(Lua_Client::*)(int))&Lua_Client::FailTask) .def("FilteredMessage", &Lua_Client::FilteredMessage) diff --git a/zone/lua_client.h b/zone/lua_client.h index 148a0846a..f4bc2bff7 100644 --- a/zone/lua_client.h +++ b/zone/lua_client.h @@ -348,6 +348,8 @@ public: bool IsTaskActive(int task); bool IsTaskActivityActive(int task, int activity); void LockSharedTask(bool lock); + void EndSharedTask(); + void EndSharedTask(bool send_fail); int GetCorpseCount(); int GetCorpseID(int corpse); int GetCorpseItemAt(int corpse, int slot); diff --git a/zone/lua_general.cpp b/zone/lua_general.cpp index 7fb6e793c..c9df4db1a 100644 --- a/zone/lua_general.cpp +++ b/zone/lua_general.cpp @@ -760,6 +760,18 @@ std::string lua_get_task_name(uint32 task_id) { return quest_manager.gettaskname(task_id); } +int lua_get_dz_task_id() { + return quest_manager.GetCurrentDzTaskID(); +} + +void lua_end_dz_task() { + quest_manager.EndCurrentDzTask(); +} + +void lua_end_dz_task(bool send_fail) { + quest_manager.EndCurrentDzTask(send_fail); +} + void lua_popup(const char *title, const char *text, uint32 id, uint32 buttons, uint32 duration) { quest_manager.popup(title, text, id, buttons, duration); } @@ -3759,6 +3771,9 @@ luabind::scope lua_register_general() { luabind::def("completed_tasks_in_set", &lua_completed_tasks_in_set), luabind::def("is_task_appropriate", &lua_is_task_appropriate), luabind::def("get_task_name", (std::string(*)(uint32))&lua_get_task_name), + luabind::def("get_dz_task_id", &lua_get_dz_task_id), + luabind::def("end_dz_task", (void(*)())&lua_end_dz_task), + luabind::def("end_dz_task", (void(*)(bool))&lua_end_dz_task), luabind::def("popup", &lua_popup), luabind::def("clear_spawn_timers", &lua_clear_spawn_timers), luabind::def("zone_emote", &lua_zone_emote), diff --git a/zone/perl_client.cpp b/zone/perl_client.cpp index c072f83dd..64b90aa4a 100644 --- a/zone/perl_client.cpp +++ b/zone/perl_client.cpp @@ -1423,6 +1423,16 @@ void Perl_Client_LockSharedTask(Client* self, bool lock) return self->LockSharedTask(lock); } +void Perl_Client_EndSharedTask(Client* self) +{ + return self->EndSharedTask(); +} + +void Perl_Client_EndSharedTask(Client* self, bool send_fail) +{ + return self->EndSharedTask(send_fail); +} + uint32_t Perl_Client_GetCorpseCount(Client* self) // @categories Account and Character, Corpse { return self->GetCorpseCount(); @@ -2548,6 +2558,8 @@ void perl_register_client() package.add("Duck", &Perl_Client_Duck); package.add("DyeArmorBySlot", (void(*)(Client*, uint8, uint8, uint8, uint8))&Perl_Client_DyeArmorBySlot); package.add("DyeArmorBySlot", (void(*)(Client*, uint8, uint8, uint8, uint8, uint8))&Perl_Client_DyeArmorBySlot); + package.add("EndSharedTask", (void(*)(Client*))&Perl_Client_EndSharedTask); + package.add("EndSharedTask", (void(*)(Client*, bool))&Perl_Client_EndSharedTask); package.add("Escape", &Perl_Client_Escape); package.add("ExpeditionMessage", &Perl_Client_ExpeditionMessage); package.add("FailTask", &Perl_Client_FailTask); diff --git a/zone/questmgr.cpp b/zone/questmgr.cpp index 2c7826be7..be40e898f 100644 --- a/zone/questmgr.cpp +++ b/zone/questmgr.cpp @@ -2480,6 +2480,24 @@ std::string QuestManager::gettaskname(uint32 task_id) { return std::string(); } +int QuestManager::GetCurrentDzTaskID() { + QuestManagerCurrentQuestVars(); + + if (RuleB(TaskSystem, EnableTaskSystem) && zone && task_manager) { + return task_manager->GetCurrentDzTaskID(); + } + + return 0; +} + +void QuestManager::EndCurrentDzTask(bool send_fail) { + QuestManagerCurrentQuestVars(); + + if (RuleB(TaskSystem, EnableTaskSystem) && zone && task_manager) { + task_manager->EndCurrentDzTask(send_fail); + } +} + void QuestManager::clearspawntimers() { if (!zone) { return; diff --git a/zone/questmgr.h b/zone/questmgr.h index 6ce2ca003..27ae11d59 100644 --- a/zone/questmgr.h +++ b/zone/questmgr.h @@ -234,6 +234,8 @@ public: int completedtasksinset(int taskset); bool istaskappropriate(int task); std::string gettaskname(uint32 task_id); + int GetCurrentDzTaskID(); + void EndCurrentDzTask(bool send_fail = false); void clearspawntimers(); void ze(int type, const char *str); void we(int type, const char *str); diff --git a/zone/shared_task_zone_messaging.cpp b/zone/shared_task_zone_messaging.cpp index 0797e1dfb..379b5d6f8 100644 --- a/zone/shared_task_zone_messaging.cpp +++ b/zone/shared_task_zone_messaging.cpp @@ -169,6 +169,15 @@ void SharedTaskZoneMessaging::HandleWorldMessage(ServerPacket *pack) break; } + case ServerOP_SharedTaskFailed: { + auto buf = reinterpret_cast(pack->pBuffer); + Client* client = entity_list.GetClientByCharID(buf->character_id); + if (client) + { + client->SendTaskFailed(buf->task_id, TASKSLOTSHAREDTASK, TaskType::Shared); + } + break; + } default: break; } diff --git a/zone/task_client_state.cpp b/zone/task_client_state.cpp index fde9a5bc1..6d14bb4a1 100644 --- a/zone/task_client_state.cpp +++ b/zone/task_client_state.cpp @@ -1122,16 +1122,12 @@ void ClientTaskState::FailTask(Client *client, int task_id) return; } - // type: Shared Task + // type: Shared Task (failed via world for all members) if (m_active_shared_task.task_id == task_id) { - client->SendTaskFailed(task_id, TASKSLOTSHAREDTASK, TaskType::Shared); - // Remove the task from the client - client->CancelTask(TASKSLOTSHAREDTASK, TaskType::Shared); + task_manager->EndSharedTask(*client, task_id, true); return; } - // TODO: shared tasks - if (m_active_task_count == 0) { return; } @@ -1146,7 +1142,6 @@ void ClientTaskState::FailTask(Client *client, int task_id) } } -// TODO: Shared tasks bool ClientTaskState::IsTaskActivityActive(int task_id, int activity_id) { LogTasks("[IsTaskActivityActive] task_id [{}] activity_id [{}]", task_id, activity_id); @@ -1566,6 +1561,7 @@ bool ClientTaskState::TaskOutOfTime(TaskType task_type, int index) void ClientTaskState::TaskPeriodicChecks(Client *client) { + // shared task expiration is handled by world // type "task" if (m_active_task.task_id != TASKSLOTEMPTY) { @@ -1581,20 +1577,6 @@ void ClientTaskState::TaskPeriodicChecks(Client *client) } } - // type "shared" - if (m_active_shared_task.task_id != TASKSLOTEMPTY) { - if (TaskOutOfTime(TaskType::Shared, TASKSLOTSHAREDTASK)) { - // Send Red Task Failed Message - client->SendTaskFailed(m_active_shared_task.task_id, TASKSLOTSHAREDTASK, TaskType::Shared); - // Remove the task from the client - client->CancelTask(TASKSLOTSHAREDTASK, TaskType::Shared); - // It is a conscious decision to only fail one task per call to this method, - // otherwise the player will not see all the failed messages where multiple - // tasks fail at the same time. - return; - } - } - if (m_active_task_count == 0) { return; } @@ -2434,6 +2416,13 @@ void ClientTaskState::LockSharedTask(Client* client, bool lock) } } +void ClientTaskState::EndSharedTask(Client* client, bool send_fail) +{ + if (task_manager && m_active_shared_task.task_id != TASKSLOTEMPTY) + { + task_manager->EndSharedTask(*client, m_active_shared_task.task_id, send_fail); + } +} bool ClientTaskState::CanAcceptNewTask(Client* client, int task_id, int npc_entity_id) const { auto it = std::find_if(m_last_offers.begin(), m_last_offers.end(), diff --git a/zone/task_client_state.h b/zone/task_client_state.h index f7b5aa60a..f1c1d6009 100644 --- a/zone/task_client_state.h +++ b/zone/task_client_state.h @@ -83,6 +83,7 @@ public: void ClearLastOffers() { m_last_offers.clear(); } bool CanAcceptNewTask(Client* client, int task_id, int npc_entity_id) const; bool HasExploreTask(Client* client) const; + void EndSharedTask(Client* client, bool send_fail); inline bool HasFreeTaskSlot() { return m_active_task.task_id == TASKSLOTEMPTY; } diff --git a/zone/task_manager.cpp b/zone/task_manager.cpp index 082f05c72..57ff5bbc3 100644 --- a/zone/task_manager.cpp +++ b/zone/task_manager.cpp @@ -8,9 +8,11 @@ #include "../common/repositories/tasks_repository.h" #include "../common/repositories/tasksets_repository.h" #include "client.h" +#include "dynamic_zone.h" #include "string_ids.h" #include "task_manager.h" #include "../common/repositories/shared_task_activity_state_repository.h" +#include "../common/repositories/shared_task_dynamic_zones_repository.h" #include "../common/repositories/shared_task_members_repository.h" #include "../common/shared_tasks.h" #include "worldserver.h" @@ -1833,3 +1835,48 @@ bool TaskManager::IsActiveTaskComplete(ClientTaskInformation& client_task) } return true; } + +int TaskManager::GetCurrentDzTaskID() +{ + auto dz = zone->GetDynamicZone(); + if (dz) + { + // currently only supports shared tasks + auto res = SharedTasksRepository::GetWhere(database, fmt::format( + "id = (SELECT shared_task_id FROM shared_task_dynamic_zones WHERE dynamic_zone_id = {})", dz->GetID())); + + if (!res.empty()) + { + return res.front().task_id; + } + } + return 0; +} + +void TaskManager::EndCurrentDzTask(bool send_fail) +{ + auto dz = zone->GetDynamicZone(); + if (dz) + { + EndSharedTask(dz->GetID(), send_fail); + } +} + +void TaskManager::EndSharedTask(uint32_t dz_id, bool send_fail) +{ + ServerPacket pack(ServerOP_SharedTaskEndByDz, sizeof(ServerSharedTaskEnd_Struct)); + auto buf = reinterpret_cast(pack.pBuffer); + buf->dz_id = dz_id; + buf->send_fail = send_fail; + worldserver.SendPacket(&pack); +} + +void TaskManager::EndSharedTask(Client& client, int task_id, bool send_fail) +{ + ServerPacket pack(ServerOP_SharedTaskEnd, sizeof(ServerSharedTaskEnd_Struct)); + auto buf = reinterpret_cast(pack.pBuffer); + buf->character_id = client.CharacterID(); + buf->task_id = task_id; + buf->send_fail = send_fail; + worldserver.SendPacket(&pack); +} diff --git a/zone/task_manager.h b/zone/task_manager.h index 4d27d909d..e1f1a1154 100644 --- a/zone/task_manager.h +++ b/zone/task_manager.h @@ -51,6 +51,10 @@ public: int NextTaskInSet(int task_set, int task_id); bool IsTaskRepeatable(int task_id); bool IsActiveTaskComplete(ClientTaskInformation& client_task); + int GetCurrentDzTaskID(); + void EndCurrentDzTask(bool send_fail); + void EndSharedTask(Client& client, int task_id, bool send_fail); + void EndSharedTask(uint32_t dz_id, bool send_fail); friend class ClientTaskState; diff --git a/zone/worldserver.cpp b/zone/worldserver.cpp index ec2cb9b85..c02953b46 100644 --- a/zone/worldserver.cpp +++ b/zone/worldserver.cpp @@ -3291,6 +3291,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) case ServerOP_SharedTaskMemberChange: case ServerOP_SharedTaskInvitePlayer: case ServerOP_SharedTaskPurgeAllCommand: + case ServerOP_SharedTaskFailed: { SharedTaskZoneMessaging::HandleWorldMessage(pack); break;