diff --git a/common/repositories/base/base_character_tasks_repository.h b/common/repositories/base/base_character_tasks_repository.h index 2f74b3ed6..013d2091c 100644 --- a/common/repositories/base/base_character_tasks_repository.h +++ b/common/repositories/base/base_character_tasks_repository.h @@ -24,6 +24,7 @@ public: int slot; int type; int acceptedtime; + int was_rewarded; }; static std::string PrimaryKey() @@ -39,6 +40,7 @@ public: "slot", "type", "acceptedtime", + "was_rewarded", }; } @@ -50,6 +52,7 @@ public: "slot", "type", "acceptedtime", + "was_rewarded", }; } @@ -95,6 +98,7 @@ public: entry.slot = 0; entry.type = 0; entry.acceptedtime = 0; + entry.was_rewarded = 0; return entry; } @@ -135,6 +139,7 @@ public: entry.slot = atoi(row[2]); entry.type = atoi(row[3]); entry.acceptedtime = atoi(row[4]); + entry.was_rewarded = atoi(row[5]); return entry; } @@ -173,6 +178,7 @@ public: update_values.push_back(columns[2] + " = " + std::to_string(character_tasks_entry.slot)); update_values.push_back(columns[3] + " = " + std::to_string(character_tasks_entry.type)); update_values.push_back(columns[4] + " = " + std::to_string(character_tasks_entry.acceptedtime)); + update_values.push_back(columns[5] + " = " + std::to_string(character_tasks_entry.was_rewarded)); auto results = db.QueryDatabase( fmt::format( @@ -199,6 +205,7 @@ public: insert_values.push_back(std::to_string(character_tasks_entry.slot)); insert_values.push_back(std::to_string(character_tasks_entry.type)); insert_values.push_back(std::to_string(character_tasks_entry.acceptedtime)); + insert_values.push_back(std::to_string(character_tasks_entry.was_rewarded)); auto results = db.QueryDatabase( fmt::format( @@ -233,6 +240,7 @@ public: insert_values.push_back(std::to_string(character_tasks_entry.slot)); insert_values.push_back(std::to_string(character_tasks_entry.type)); insert_values.push_back(std::to_string(character_tasks_entry.acceptedtime)); + insert_values.push_back(std::to_string(character_tasks_entry.was_rewarded)); insert_chunks.push_back("(" + Strings::Implode(",", insert_values) + ")"); } @@ -271,6 +279,7 @@ public: entry.slot = atoi(row[2]); entry.type = atoi(row[3]); entry.acceptedtime = atoi(row[4]); + entry.was_rewarded = atoi(row[5]); all_entries.push_back(entry); } @@ -300,6 +309,7 @@ public: entry.slot = atoi(row[2]); entry.type = atoi(row[3]); entry.acceptedtime = atoi(row[4]); + entry.was_rewarded = atoi(row[5]); all_entries.push_back(entry); } diff --git a/common/tasks.h b/common/tasks.h index d2777e151..d84e435b6 100644 --- a/common/tasks.h +++ b/common/tasks.h @@ -257,6 +257,7 @@ struct ClientTaskInformation { int current_step; int accepted_time; bool updated; + bool was_rewarded; // character has received reward for this task ClientActivityInformation activity[MAXACTIVITIESPERTASK]; }; diff --git a/common/version.h b/common/version.h index df34b1c65..c1b23ee11 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 9188 +#define CURRENT_BINARY_DATABASE_VERSION 9189 #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 00e353cd1..a7eb2d592 100644 --- a/utils/sql/db_update_manifest.txt +++ b/utils/sql/db_update_manifest.txt @@ -442,6 +442,7 @@ 9186|2022_07_09_zone_expansion_deprecate.sql|SHOW COLUMNS FROM `zone` LIKE 'expansion'|notempty| 9187|2022_07_09_task_zone_version_matching.sql|SHOW COLUMNS FROM `task_activities` LIKE 'zone_version'|empty| 9188|2022_07_14_zone_expansion_revert.sql|SHOW COLUMNS FROM `zone` LIKE 'expansion'|empty| +9189|2022_07_10_character_task_rewarded.sql|SHOW COLUMNS FROM `character_tasks` LIKE 'was_rewarded'|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_10_character_task_rewarded.sql b/utils/sql/git/required/2022_07_10_character_task_rewarded.sql new file mode 100644 index 000000000..3e70ef199 --- /dev/null +++ b/utils/sql/git/required/2022_07_10_character_task_rewarded.sql @@ -0,0 +1,2 @@ +ALTER TABLE `character_tasks` + ADD COLUMN `was_rewarded` TINYINT NOT NULL DEFAULT '0' AFTER `acceptedtime`; diff --git a/zone/task_client_state.cpp b/zone/task_client_state.cpp index 78c041794..b615e3812 100644 --- a/zone/task_client_state.cpp +++ b/zone/task_client_state.cpp @@ -1221,13 +1221,12 @@ void ClientTaskState::IncrementDoneCount( // updated in UnlockActivities. Send the completed task list to the // client. This is the same sequence the packets are sent on live. if (task_complete) { - std::string export_string = fmt::format( - "{} {} {}", - info->activity[activity_id].done_count, - info->activity[activity_id].activity_id, - info->task_id - ); - parse->EventPlayer(EVENT_TASK_COMPLETE, client, export_string, 0); + // world adds timers for shared tasks + if (task_information->type != TaskType::Shared) { + AddReplayTimer(client, *info, *task_information); + } + + DispatchEventTaskComplete(client, *info, activity_id); /* QS: PlayerLogTaskUpdates :: Complete */ if (RuleB(QueryServ, PlayerLogTaskUpdates)) { @@ -1242,25 +1241,18 @@ void ClientTaskState::IncrementDoneCount( } client->SendTaskActivityComplete(info->task_id, 0, task_index, task_information->type, 0); - task_manager->SaveClientState(client, this); // If Experience and/or cash rewards are set, reward them from the task even if reward_method is METHODQUEST - RewardTask(client, task_information); + RewardTask(client, task_information, *info); //RemoveTask(c, TaskIndex); - // add replay timer (world adds timers to shared task members) - AddReplayTimer(client, *info, *task_information); - // shared tasks linger at the completion step and do not get removed from the task window unlike quests/task - if (task_information->type == TaskType::Shared) { - return; + if (task_information->type != TaskType::Shared) { + task_manager->SendCompletedTasksToClient(client, this); + + client->CancelTask(task_index, task_information->type); } - - task_manager->SendCompletedTasksToClient(client, this); - - client->CancelTask(task_index, task_information->type); } - } else { // Send an updated packet for this single activity_information @@ -1270,17 +1262,32 @@ void ClientTaskState::IncrementDoneCount( activity_id, task_index ); - task_manager->SaveClientState(client, this); } + + task_manager->SaveClientState(client, this); } -void ClientTaskState::RewardTask(Client *client, TaskInformation *task_information) +void ClientTaskState::DispatchEventTaskComplete(Client* client, ClientTaskInformation& info, int activity_id) +{ + std::string export_string = fmt::format( + "{} {} {}", + info.activity[activity_id].done_count, + info.activity[activity_id].activity_id, + info.task_id + ); + parse->EventPlayer(EVENT_TASK_COMPLETE, client, export_string, 0); +} + +void ClientTaskState::RewardTask(Client *client, TaskInformation *task_information, ClientTaskInformation& client_task) { - if (!task_information || !client) { + if (!task_information || !client || client_task.was_rewarded) { return; } + client_task.was_rewarded = true; + client_task.updated = true; + if (!task_information->completion_emote.empty()) { client->Message(Chat::Yellow, task_information->completion_emote.c_str()); } @@ -2398,6 +2405,7 @@ void ClientTaskState::AcceptNewTask( active_slot->accepted_time = static_cast(accept_time); active_slot->updated = true; active_slot->current_step = -1; + active_slot->was_rewarded = false; for (int activity_id = 0; activity_id < task_manager->m_task_data[task_id]->activity_count; activity_id++) { active_slot->activity[activity_id].activity_id = activity_id; @@ -2631,8 +2639,7 @@ void ClientTaskState::ListTaskTimers(Client* client) void ClientTaskState::AddReplayTimer(Client* client, ClientTaskInformation& client_task, TaskInformation& task) { - // world adds timers for shared tasks and handles messages - if (task.type != TaskType::Shared && task.replay_timer_seconds > 0) + if (task.replay_timer_seconds > 0) { // solo task replay timers are based on completion time auto expire_time = std::time(nullptr) + task.replay_timer_seconds; @@ -2643,6 +2650,11 @@ void ClientTaskState::AddReplayTimer(Client* client, ClientTaskInformation& clie timer.expire_time = expire_time; timer.timer_type = static_cast(TaskTimerType::Replay); + // replace any existing replay timer + CharacterTaskTimersRepository::DeleteWhere(database, fmt::format( + "task_id = {} AND timer_type = {} AND character_id = {}", + client_task.task_id, static_cast(TaskTimerType::Replay), client->CharacterID())); + CharacterTaskTimersRepository::InsertOne(database, timer); client->Message(Chat::Yellow, fmt::format( diff --git a/zone/task_client_state.h b/zone/task_client_state.h index dfbdaaab3..5bd4e0b4a 100644 --- a/zone/task_client_state.h +++ b/zone/task_client_state.h @@ -43,7 +43,7 @@ public: bool TaskOutOfTime(TaskType task_type, int index); void TaskPeriodicChecks(Client *client); void SendTaskHistory(Client *client, int task_index); - void RewardTask(Client *client, TaskInformation *task_information); + void RewardTask(Client *client, TaskInformation *task_information, ClientTaskInformation& client_task); void EnableTask(int character_id, int task_count, int *task_list); void DisableTask(int character_id, int task_count, int *task_list); bool IsTaskEnabled(int task_id); @@ -75,6 +75,7 @@ public: private: void AddReplayTimer(Client *client, ClientTaskInformation& client_task, TaskInformation& task); + void DispatchEventTaskComplete(Client* client, ClientTaskInformation& client_task, int activity_id); void IncrementDoneCount( Client *client, diff --git a/zone/task_manager.cpp b/zone/task_manager.cpp index 501cb295e..00cdc4416 100644 --- a/zone/task_manager.cpp +++ b/zone/task_manager.cpp @@ -314,13 +314,14 @@ bool TaskManager::SaveClientState(Client *client, ClientTaskState *client_task_s ); std::string query = StringFormat( - "REPLACE INTO character_tasks (charid, taskid, slot, type, acceptedtime) " - "VALUES (%i, %i, %i, %i, %i)", + "REPLACE INTO character_tasks (charid, taskid, slot, type, acceptedtime, was_rewarded) " + "VALUES (%i, %i, %i, %i, %i, %d)", character_id, task_id, slot, static_cast(m_task_data[task_id]->type), - active_task.accepted_time + active_task.accepted_time, + active_task.was_rewarded ); auto results = database.QueryDatabase(query); @@ -1326,6 +1327,7 @@ bool TaskManager::LoadClientState(Client *client, ClientTaskState *client_task_s task_info->current_step = -1; task_info->accepted_time = character_task.acceptedtime; task_info->updated = false; + task_info->was_rewarded = character_task.was_rewarded; for (auto &i : task_info->activity) { i.activity_id = -1; @@ -1337,11 +1339,12 @@ bool TaskManager::LoadClientState(Client *client, ClientTaskState *client_task_s } LogTasks( - "[LoadClientState] character_id [{}] task_id [{}] slot [{}] accepted_time [{}]", + "[LoadClientState] character_id [{}] task_id [{}] slot [{}] accepted_time [{}] was_rewarded [{}]", character_id, task_id, slot, - character_task.acceptedtime + character_task.acceptedtime, + character_task.was_rewarded ); } @@ -1685,6 +1688,9 @@ void TaskManager::SyncClientSharedTaskWithPersistedState(Client *c, ClientTaskSt shared_task->activity[a.activity_id].activity_state = (a.completed_time > 0 ? ActivityCompleted : ActivityHidden); + // flag to update character_activities table entry on save + shared_task->activity[a.activity_id].updated = true; + // set flag to persist later fell_behind_state = true; } @@ -1692,6 +1698,17 @@ void TaskManager::SyncClientSharedTaskWithPersistedState(Client *c, ClientTaskSt // fell behind, force a save of client state if (fell_behind_state) { + // give reward if member was offline for shared task completion + // live does this as long as the shared task is still active when entering game + if (!shared_task->was_rewarded && IsActiveTaskComplete(*shared_task)) + { + LogTasksDetail("[LoadClientState] Syncing shared task completion for client [{}]", c->GetName()); + auto task_info = task_manager->m_task_data[shared_task->task_id]; + cts->AddReplayTimer(c, *shared_task, *task_info); // live updates a fresh timer + cts->DispatchEventTaskComplete(c, *shared_task, task_info->activity_count - 1); + cts->RewardTask(c, task_info, *shared_task); + } + SaveClientState(c, cts); } @@ -1915,3 +1932,17 @@ void TaskManager::HandleUpdateTasksOnKill(Client *client, uint32 npc_type_id, st } } } + +bool TaskManager::IsActiveTaskComplete(ClientTaskInformation& client_task) +{ + auto task_info = task_manager->m_task_data[client_task.task_id]; + for (int i = 0; i < task_info->activity_count; ++i) + { + if (client_task.activity[i].activity_state != ActivityCompleted && + !task_info->activity_information[i].optional) + { + return false; + } + } + return true; +} diff --git a/zone/task_manager.h b/zone/task_manager.h index 1acac00d3..0d54c8e37 100644 --- a/zone/task_manager.h +++ b/zone/task_manager.h @@ -66,6 +66,7 @@ public: int LastTaskInSet(int task_set); int NextTaskInSet(int task_set, int task_id); bool IsTaskRepeatable(int task_id); + bool IsActiveTaskComplete(ClientTaskInformation& client_task); friend class ClientTaskState;