diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 5ccb440a8..5c069b0ff 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -1835,7 +1835,13 @@ void Client::Handle_OP_AcceptNewTask(const EQApplicationPacket *app) AcceptNewTask_Struct *ant = (AcceptNewTask_Struct*)app->pBuffer; if (ant->task_id > 0 && RuleB(TaskSystem, EnableTaskSystem) && task_state) - task_state->AcceptNewTask(this, ant->task_id, ant->task_master_id, std::time(nullptr)); + { + if (task_state->CanAcceptNewTask(this, ant->task_id, ant->task_master_id)) + { + task_state->AcceptNewTask(this, ant->task_id, ant->task_master_id, std::time(nullptr)); + } + task_state->ClearLastOffers(); + } } void Client::Handle_OP_AdventureInfoRequest(const EQApplicationPacket *app) @@ -15552,8 +15558,13 @@ void Client::Handle_OP_SharedTaskAccept(const EQApplicationPacket* app) buf->task_id ); - if (buf->task_id > 0 && RuleB(TaskSystem, EnableTaskSystem) && task_state) { - task_state->AcceptNewTask(this, buf->task_id, buf->npc_entity_id, std::time(nullptr)); + if (buf->task_id > 0 && RuleB(TaskSystem, EnableTaskSystem) && task_state) + { + if (task_state->CanAcceptNewTask(this, buf->task_id, buf->npc_entity_id)) + { + task_state->AcceptNewTask(this, buf->task_id, buf->npc_entity_id, std::time(nullptr)); + } + task_state->ClearLastOffers(); } } diff --git a/zone/task_client_state.cpp b/zone/task_client_state.cpp index c32a6bb0d..64acefb56 100644 --- a/zone/task_client_state.cpp +++ b/zone/task_client_state.cpp @@ -2747,3 +2747,39 @@ void ClientTaskState::LockSharedTask(Client* client, bool lock) worldserver.SendPacket(&pack); } } + +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(), + [&](const TaskOffer& offer) { return offer.task_id == task_id; }); + + if (it == m_last_offers.end()) + { + LogTasks("Client [{}] accepted unoffered task [{}]", client->GetName(), task_id); + return false; + } + + if (npc_entity_id != it->npc_entity_id) + { + LogTasks("Client [{}] accepted task [{}] with wrong npc entity id [{}] vs offered [{}]", + client->GetName(), task_id, npc_entity_id, it->npc_entity_id); + return false; + } + + NPC* npc = entity_list.GetID(it->npc_entity_id)->CastToNPC(); + if (!npc) // client window disappears in this case + { + LogTasks("Client [{}] accepted task [{}] from missing npc", client->GetName(), task_id); + return false; + } + + auto dist = npc->CalculateDistance(client->GetX(), client->GetY(), client->GetZ()); + if (dist > MAX_TASK_SELECT_DISTANCE) + { + LogTasks("Client [{}] accepted task [{}] from npc [{}] out of range [{}]", + client->GetName(), task_id, npc->GetCleanName(), dist); + return false; + } + + return true; +} diff --git a/zone/task_client_state.h b/zone/task_client_state.h index 25f7bf524..4e49b4589 100644 --- a/zone/task_client_state.h +++ b/zone/task_client_state.h @@ -8,6 +8,14 @@ #include #include +constexpr float MAX_TASK_SELECT_DISTANCE = 60.0f; // client closes window at this distance + +struct TaskOffer +{ + int task_id; + uint16_t npc_entity_id; +}; + class ClientTaskState { public: @@ -57,6 +65,8 @@ public: void ListTaskTimers(Client* client); void KickPlayersSharedTask(Client* client); void LockSharedTask(Client* client, bool lock); + void ClearLastOffers() { m_last_offers.clear(); } + bool CanAcceptNewTask(Client* client, int task_id, int npc_entity_id) const; inline bool HasFreeTaskSlot() { return m_active_task.task_id == TASKSLOTEMPTY; } @@ -77,6 +87,7 @@ public: private: void AddReplayTimer(Client *client, ClientTaskInformation& client_task, TaskInformation& task); void DispatchEventTaskComplete(Client* client, ClientTaskInformation& client_task, int activity_id); + void AddOffer(int task_id, uint16_t npc_entity_id) { m_last_offers.push_back({task_id, npc_entity_id}); }; void IncrementDoneCount( Client *client, @@ -130,6 +141,7 @@ private: std::vector m_enabled_tasks; std::vector m_completed_tasks; int m_last_completed_task_loaded; + std::vector m_last_offers; static void ShowClientTaskInfoMessage(ClientTaskInformation *task, Client *c); diff --git a/zone/task_manager.cpp b/zone/task_manager.cpp index f05af4457..a1ee948c7 100644 --- a/zone/task_manager.cpp +++ b/zone/task_manager.cpp @@ -761,6 +761,7 @@ void TaskManager::SendTaskSelector(Client *client, Mob *mob, int task_count, int { LogTasks("TaskSelector for [{}] Tasks", task_count); int player_level = client->GetLevel(); + client->GetTaskState()->ClearLastOffers(); // Check if any of the tasks exist for (int i = 0; i < task_count; i++) { @@ -809,6 +810,7 @@ void TaskManager::SendTaskSelector(Client *client, Mob *mob, int task_count, int buf.WriteUInt32(task_list[i]); // task_id m_task_data[task_list[i]]->SerializeSelector(buf, client->ClientVersion()); + client->GetTaskState()->AddOffer(task_list[i], mob->GetID()); } auto outapp = std::make_unique(OP_TaskSelectWindow, buf); @@ -821,6 +823,7 @@ void TaskManager::SendSharedTaskSelector(Client *client, Mob *mob, int task_coun // request timer is only set when shared task selection shown (not for failed validations) client->StartTaskRequestCooldownTimer(); + client->GetTaskState()->ClearLastOffers(); SerializeBuffer buf; @@ -833,6 +836,7 @@ void TaskManager::SendSharedTaskSelector(Client *client, Mob *mob, int task_coun int task_id = task_list[i]; buf.WriteUInt32(task_id); m_task_data[task_id]->SerializeSelector(buf, client->ClientVersion()); + client->GetTaskState()->AddOffer(task_id, mob->GetID()); } auto outapp = std::make_unique(OP_SharedTaskSelectWindow, buf);