diff --git a/common/repositories/base/base_tasks_repository.h b/common/repositories/base/base_tasks_repository.h index 1d576cfcc..e7e932dd3 100644 --- a/common/repositories/base/base_tasks_repository.h +++ b/common/repositories/base/base_tasks_repository.h @@ -42,6 +42,7 @@ public: std::string completion_emote; int replay_timer_seconds; int request_timer_seconds; + int lock_activity_id; }; static std::string PrimaryKey() @@ -75,6 +76,7 @@ public: "completion_emote", "replay_timer_seconds", "request_timer_seconds", + "lock_activity_id", }; } @@ -104,6 +106,7 @@ public: "completion_emote", "replay_timer_seconds", "request_timer_seconds", + "lock_activity_id", }; } @@ -167,6 +170,7 @@ public: entry.completion_emote = ""; entry.replay_timer_seconds = 0; entry.request_timer_seconds = 0; + entry.lock_activity_id = -1; return entry; } @@ -225,6 +229,7 @@ public: entry.completion_emote = row[20] ? row[20] : ""; entry.replay_timer_seconds = atoi(row[21]); entry.request_timer_seconds = atoi(row[22]); + entry.lock_activity_id = atoi(row[23]); return entry; } @@ -281,6 +286,7 @@ public: update_values.push_back(columns[20] + " = '" + Strings::Escape(tasks_entry.completion_emote) + "'"); update_values.push_back(columns[21] + " = " + std::to_string(tasks_entry.replay_timer_seconds)); update_values.push_back(columns[22] + " = " + std::to_string(tasks_entry.request_timer_seconds)); + update_values.push_back(columns[23] + " = " + std::to_string(tasks_entry.lock_activity_id)); auto results = db.QueryDatabase( fmt::format( @@ -325,6 +331,7 @@ public: insert_values.push_back("'" + Strings::Escape(tasks_entry.completion_emote) + "'"); insert_values.push_back(std::to_string(tasks_entry.replay_timer_seconds)); insert_values.push_back(std::to_string(tasks_entry.request_timer_seconds)); + insert_values.push_back(std::to_string(tasks_entry.lock_activity_id)); auto results = db.QueryDatabase( fmt::format( @@ -377,6 +384,7 @@ public: insert_values.push_back("'" + Strings::Escape(tasks_entry.completion_emote) + "'"); insert_values.push_back(std::to_string(tasks_entry.replay_timer_seconds)); insert_values.push_back(std::to_string(tasks_entry.request_timer_seconds)); + insert_values.push_back(std::to_string(tasks_entry.lock_activity_id)); insert_chunks.push_back("(" + Strings::Implode(",", insert_values) + ")"); } @@ -433,6 +441,7 @@ public: entry.completion_emote = row[20] ? row[20] : ""; entry.replay_timer_seconds = atoi(row[21]); entry.request_timer_seconds = atoi(row[22]); + entry.lock_activity_id = atoi(row[23]); all_entries.push_back(entry); } @@ -480,6 +489,7 @@ public: entry.completion_emote = row[20] ? row[20] : ""; entry.replay_timer_seconds = atoi(row[21]); entry.request_timer_seconds = atoi(row[22]); + entry.lock_activity_id = atoi(row[23]); all_entries.push_back(entry); } diff --git a/common/shared_tasks.h b/common/shared_tasks.h index 2a2363ffd..c37e63dc9 100644 --- a/common/shared_tasks.h +++ b/common/shared_tasks.h @@ -28,6 +28,7 @@ #define ServerOP_SharedTaskPlayerList 0x0313 // zone -> world /taskplayerlist command #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 enum class SharedTaskRequestGroupType { Solo = 0, @@ -105,6 +106,8 @@ struct SharedTaskActivityStateEntry { uint32 max_done_count; // goalcount uint32 updated_time; uint32 completed_time; + int step; + bool optional; }; struct ServerSharedTaskActivityUpdate_Struct { @@ -162,6 +165,12 @@ struct ServerSharedTaskKickPlayers_Struct { uint32 task_id; }; +struct ServerSharedTaskLock_Struct { + uint32 source_character_id; + uint32 task_id; + bool lock; +}; + class SharedTask { public: // used in both zone and world validation diff --git a/common/version.h b/common/version.h index 73447aa27..43478757d 100644 --- a/common/version.h +++ b/common/version.h @@ -34,7 +34,7 @@ * Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt */ -#define CURRENT_BINARY_DATABASE_VERSION 9190 +#define CURRENT_BINARY_DATABASE_VERSION 9192 #ifdef BOTS #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9029 diff --git a/utils/sql/db_update_manifest.txt b/utils/sql/db_update_manifest.txt index 6a34ac161..fc0664394 100644 --- a/utils/sql/db_update_manifest.txt +++ b/utils/sql/db_update_manifest.txt @@ -445,6 +445,7 @@ 9189|2022_07_10_character_task_rewarded.sql|SHOW COLUMNS FROM `character_tasks` LIKE 'was_rewarded'|empty| 9190|2022_07_13_task_reward_points.sql|SHOW COLUMNS FROM `tasks` LIKE 'reward_points'|empty| 9191|2022_07_28_gm_state_changes.sql|SHOW COLUMNS FROM `account` LIKE 'invulnerable'|empty| +9192|2022_07_13_task_lock_activity.sql|SHOW COLUMNS FROM `tasks` LIKE 'lock_activity_id'|empty| # Upgrade conditions: # This won't be needed after this system is implemented, but it is used database that are not diff --git a/utils/sql/git/required/2022_07_13_task_lock_activity.sql b/utils/sql/git/required/2022_07_13_task_lock_activity.sql new file mode 100644 index 000000000..2c4f15321 --- /dev/null +++ b/utils/sql/git/required/2022_07_13_task_lock_activity.sql @@ -0,0 +1,2 @@ +ALTER TABLE `tasks` + ADD COLUMN `lock_activity_id` INT NOT NULL DEFAULT '-1' AFTER `request_timer_seconds`; diff --git a/world/shared_task_manager.cpp b/world/shared_task_manager.cpp index 5139b3d62..4fb8da828 100644 --- a/world/shared_task_manager.cpp +++ b/world/shared_task_manager.cpp @@ -14,6 +14,7 @@ #include "../common/repositories/completed_shared_task_members_repository.h" #include "../common/repositories/completed_shared_task_activity_state_repository.h" #include "../common/repositories/shared_task_dynamic_zones_repository.h" +#include #include extern ClientList client_list; @@ -153,6 +154,8 @@ void SharedTaskManager::AttemptSharedTaskCreation( e.activity_id = a.activityid; e.done_count = 0; e.max_done_count = a.goalcount; + e.step = a.step; + e.optional = a.optional; shared_task_activity_state.emplace_back(e); } @@ -182,7 +185,7 @@ void SharedTaskManager::AttemptSharedTaskCreation( new_shared_task.SetMembers(request_members); // add to shared tasks list - m_shared_tasks.emplace_back(new_shared_task); + auto& inserted = m_shared_tasks.emplace_back(new_shared_task); // send accept to members for (auto &m: request_members) { @@ -197,6 +200,9 @@ void SharedTaskManager::AttemptSharedTaskCreation( } SendSharedTaskMemberListToAllMembers(&new_shared_task); + // check if task should immediately lock + HandleCompletedActivities(&inserted); + LogTasks( "[AttemptSharedTaskCreation] shared_task_id [{}] created successfully | task_id [{}] member_count [{}] activity_count [{}] current tasks in state [{}]", new_shared_task.GetDbSharedTask().id, @@ -395,6 +401,8 @@ void SharedTaskManager::LoadSharedTaskState() e.max_done_count = ad.goalcount; e.completed_time = sta.completed_time; e.updated_time = sta.updated_time; + e.step = ad.step; + e.optional = ad.optional; } } @@ -618,35 +626,10 @@ void SharedTaskManager::SharedTaskActivityUpdate( } } - // check if completed - bool is_shared_task_completed = true; - for (auto &a : shared_task->m_shared_task_activity_state) { - if (a.done_count != a.max_done_count) { - is_shared_task_completed = false; - } - } - - // mark completed + // handle task locking and completion + bool is_shared_task_completed = HandleCompletedActivities(shared_task); if (is_shared_task_completed) { - auto t = shared_task->GetDbSharedTask(); - if (t.id > 0) { - LogTasksDetail( - "[SharedTaskActivityUpdate] Marking shared task [{}] completed", - shared_task->GetDbSharedTask().id - ); - - // set record - t.completion_time = std::time(nullptr); - t.is_locked = true; - // update database - SharedTasksRepository::UpdateOne(*m_database, t); - // update internally - shared_task->SetDbSharedTask(t); - // record completion - RecordSharedTaskCompletion(shared_task); - // replay timer lockouts - AddReplayTimers(shared_task); - } + HandleCompletedTask(shared_task); } } } @@ -1815,3 +1798,75 @@ SharedTaskManager *SharedTaskManager::PurgeExpiredSharedTasks() return this; } + +void SharedTaskManager::LockTask(SharedTask* s, bool lock) +{ + bool is_locked = (s->GetDbSharedTask().is_locked != 0); + if (is_locked != lock) + { + auto db_task = s->GetDbSharedTask(); + db_task.is_locked = lock; + SharedTasksRepository::UpdateOne(*m_database, db_task); + s->SetDbSharedTask(db_task); + + if (lock) + { + SendLeaderMessageID(s, Chat::Yellow, SharedTaskMessage::YOUR_TASK_NOW_LOCKED); + } + } +} + +bool SharedTaskManager::HandleCompletedActivities(SharedTask* s) +{ + bool is_task_complete = true; + bool lock_task = false; + + std::array completed_steps; + completed_steps.fill(true); + + // multiple activity ids may share a step, sort so previous step completions can be checked + auto activity_states = s->GetActivityState(); + std::sort(activity_states.begin(), activity_states.end(), + [](const auto& lhs, const auto& rhs) { return lhs.step < rhs.step; }); + + for (const auto& a : activity_states) + { + if (a.done_count != a.max_done_count && !a.optional) + { + is_task_complete = false; + if (a.step > 0 && a.step <= MAXACTIVITIESPERTASK) + { + completed_steps[a.step - 1] = false; + } + } + + int lock_index = s->GetTaskData().lock_activity_id; + if (a.activity_id == lock_index && a.step > 0 && a.step <= MAXACTIVITIESPERTASK) + { + // lock if element is active (on first step or previous step completed) + lock_task = (a.step == 1 || completed_steps[a.step - 2]); + } + } + + // completion locks are silent + if (!is_task_complete && lock_task) + { + LockTask(s, true); + } + + return is_task_complete; +} + +void SharedTaskManager::HandleCompletedTask(SharedTask* s) +{ + auto db_task = s->GetDbSharedTask(); + LogTasksDetail("[HandleCompletedTask] Marking shared task [{}] completed", db_task.id); + db_task.completion_time = std::time(nullptr); + db_task.is_locked = true; + SharedTasksRepository::UpdateOne(*m_database, db_task); + s->SetDbSharedTask(db_task); + + RecordSharedTaskCompletion(s); + + AddReplayTimers(s); +} diff --git a/world/shared_task_manager.h b/world/shared_task_manager.h index 1bbd071cd..e39156ffb 100644 --- a/world/shared_task_manager.h +++ b/world/shared_task_manager.h @@ -58,6 +58,7 @@ public: void SaveSharedTaskActivityState(int64 shared_task_id, std::vector activity_state); bool IsSharedTaskLeader(SharedTask *s, uint32 character_id); + 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 SendSharedTaskMemberList(uint32 character_id, const std::vector &members); @@ -117,6 +118,8 @@ protected: bool CanAddPlayer(SharedTask *s, uint32_t character_id, std::string player_name, bool accepted); bool CanRequestSharedTask(uint32_t task_id, uint32_t character_id, const SharedTaskRequestCharacters &request); void ChooseNewLeader(SharedTask *s); + bool HandleCompletedActivities(SharedTask* s); + void HandleCompletedTask(SharedTask* s); void SendSharedTaskMemberListToAllMembers(SharedTask *s); void SendSharedTaskMemberAddedToAllMembers(SharedTask *s, const std::string &player_name); void SendSharedTaskMemberRemovedToAllMembers(SharedTask *s, const std::string &player_name); diff --git a/world/shared_task_world_messaging.cpp b/world/shared_task_world_messaging.cpp index 26da007f2..2788c9b3e 100644 --- a/world/shared_task_world_messaging.cpp +++ b/world/shared_task_world_messaging.cpp @@ -349,6 +349,15 @@ void SharedTaskWorldMessaging::HandleZoneMessage(ServerPacket *pack) break; } + case ServerOP_SharedTaskLock: { + auto buf = reinterpret_cast(pack->pBuffer); + auto shared_task = shared_task_manager.FindSharedTaskByTaskIdAndCharacterId(buf->task_id, buf->source_character_id); + if (shared_task) + { + shared_task_manager.LockTask(shared_task, buf->lock); + } + break; + } default: break; } diff --git a/world/zoneserver.cpp b/world/zoneserver.cpp index 50b35351d..4d3494fc7 100644 --- a/world/zoneserver.cpp +++ b/world/zoneserver.cpp @@ -1394,6 +1394,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { case ServerOP_SharedTaskCreateDynamicZone: case ServerOP_SharedTaskPurgeAllCommand: case ServerOP_SharedTaskPlayerList: + case ServerOP_SharedTaskLock: case ServerOP_SharedTaskKickPlayers: { SharedTaskWorldMessaging::HandleZoneMessage(pack); break; diff --git a/zone/client.h b/zone/client.h index c45914fc0..e1b8db9b7 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1302,6 +1302,7 @@ public: return (task_state ? task_state->CompletedTasksInSet(task_set_id) : 0); } void PurgeTaskTimers(); + void LockSharedTask(bool lock) { if (task_state) { task_state->LockSharedTask(this, lock); } } // shared task shims / middleware // these variables are used as a shim to intercept normal localized task functionality diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index 46cceb8c4..e706fa98f 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -1430,6 +1430,11 @@ bool Lua_Client::IsTaskActivityActive(int task, int activity) { return self->IsTaskActivityActive(task, activity); } +void Lua_Client::LockSharedTask(bool lock) { + Lua_Safe_Call_Void(); + return self->LockSharedTask(lock); +} + int Lua_Client::GetCorpseCount() { Lua_Safe_Call_Int(); return self->GetCorpseCount(); @@ -2772,6 +2777,7 @@ luabind::scope lua_register_client() { .def("LeaveGroup", (void(Lua_Client::*)(void))&Lua_Client::LeaveGroup) .def("LoadPEQZoneFlags", (void(Lua_Client::*)(void))&Lua_Client::LoadPEQZoneFlags) .def("LoadZoneFlags", (void(Lua_Client::*)(void))&Lua_Client::LoadZoneFlags) + .def("LockSharedTask", &Lua_Client::LockSharedTask) .def("MarkSingleCompassLoc", (void(Lua_Client::*)(float,float,float))&Lua_Client::MarkSingleCompassLoc) .def("MarkSingleCompassLoc", (void(Lua_Client::*)(float,float,float,int))&Lua_Client::MarkSingleCompassLoc) .def("MaxSkill", (int(Lua_Client::*)(int))&Lua_Client::MaxSkill) diff --git a/zone/lua_client.h b/zone/lua_client.h index 2a47da7fc..7520a96db 100644 --- a/zone/lua_client.h +++ b/zone/lua_client.h @@ -333,6 +333,7 @@ public: bool IsTaskCompleted(int task); bool IsTaskActive(int task); bool IsTaskActivityActive(int task, int activity); + void LockSharedTask(bool lock); int GetCorpseCount(); int GetCorpseID(int corpse); int GetCorpseItemAt(int corpse, int slot); diff --git a/zone/perl_client.cpp b/zone/perl_client.cpp index 1775b2dc9..948a7077c 100644 --- a/zone/perl_client.cpp +++ b/zone/perl_client.cpp @@ -1408,6 +1408,11 @@ bool Perl_Client_IsTaskActivityActive(Client* self, int task_id, int activity_id return self->IsTaskActivityActive(task_id, activity_id); } +void Perl_Client_LockSharedTask(Client* self, bool lock) +{ + return self->LockSharedTask(lock); +} + uint32_t Perl_Client_GetCorpseCount(Client* self) // @categories Account and Character, Corpse { return self->GetCorpseCount(); @@ -2621,6 +2626,7 @@ void perl_register_client() package.add("LeaveGroup", &Perl_Client_LeaveGroup); package.add("LoadPEQZoneFlags", &Perl_Client_LoadPEQZoneFlags); package.add("LoadZoneFlags", &Perl_Client_LoadZoneFlags); + package.add("LockSharedTask", &Perl_Client_LockSharedTask); package.add("MarkCompassLoc", &Perl_Client_MarkCompassLoc); package.add("MaxSkill", (int(*)(Client*, uint16))&Perl_Client_MaxSkill); package.add("MaxSkill", (int(*)(Client*, uint16, uint16))&Perl_Client_MaxSkill); diff --git a/zone/task_client_state.cpp b/zone/task_client_state.cpp index cb67388f0..a5d5f33b7 100644 --- a/zone/task_client_state.cpp +++ b/zone/task_client_state.cpp @@ -2727,3 +2727,17 @@ bool ClientTaskState::HasActiveTasks() return false; } + +void ClientTaskState::LockSharedTask(Client* client, bool lock) +{ + if (m_active_shared_task.task_id != TASKSLOTEMPTY) + { + ServerPacket pack(ServerOP_SharedTaskLock, sizeof(ServerSharedTaskLock_Struct)); + auto buf = reinterpret_cast(pack.pBuffer); + buf->source_character_id = client->CharacterID(); + buf->task_id = m_active_shared_task.task_id; + buf->lock = lock; + + worldserver.SendPacket(&pack); + } +} diff --git a/zone/task_client_state.h b/zone/task_client_state.h index 5bd4e0b4a..97d854ca0 100644 --- a/zone/task_client_state.h +++ b/zone/task_client_state.h @@ -56,6 +56,7 @@ public: void CreateTaskDynamicZone(Client* client, int task_id, DynamicZone& dz); void ListTaskTimers(Client* client); void KickPlayersSharedTask(Client* client); + void LockSharedTask(Client* client, bool lock); inline bool HasFreeTaskSlot() { return m_active_task.task_id == TASKSLOTEMPTY; }