[Shared Tasks] Implement Activity Locking (#2339)

* Add shared task element locking

This adds the `lock_activity_id` field to the tasks table which will
automatically lock a shared task when that element becomes active.

A method was added to world analogous to zone's UnlockActivities to
determine when an activity is active with respect to task steps.

Also adds quest apis to manually lock or unlock a client's shared task

* Add comment
This commit is contained in:
hg 2022-07-30 20:57:57 -04:00 committed by GitHub
parent ea878ed27f
commit f64d072af7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 149 additions and 30 deletions

View File

@ -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);
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,2 @@
ALTER TABLE `tasks`
ADD COLUMN `lock_activity_id` INT NOT NULL DEFAULT '-1' AFTER `request_timer_seconds`;

View File

@ -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 <array>
#include <ctime>
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<bool, MAXACTIVITIESPERTASK> 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);
}

View File

@ -58,6 +58,7 @@ public:
void SaveSharedTaskActivityState(int64 shared_task_id, std::vector<SharedTaskActivityStateEntry> 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<SharedTaskMember> &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);

View File

@ -349,6 +349,15 @@ void SharedTaskWorldMessaging::HandleZoneMessage(ServerPacket *pack)
break;
}
case ServerOP_SharedTaskLock: {
auto buf = reinterpret_cast<ServerSharedTaskLock_Struct*>(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;
}

View File

@ -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;

View File

@ -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

View File

@ -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)

View File

@ -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);

View File

@ -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);

View File

@ -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<ServerSharedTaskLock_Struct*>(pack.pBuffer);
buf->source_character_id = client->CharacterID();
buf->task_id = m_active_shared_task.task_id;
buf->lock = lock;
worldserver.SendPacket(&pack);
}
}

View File

@ -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; }