diff --git a/common/repositories/base/base_character_task_timers_repository.h b/common/repositories/base/base_character_task_timers_repository.h index e22853195..0177b8fbd 100644 --- a/common/repositories/base/base_character_task_timers_repository.h +++ b/common/repositories/base/base_character_task_timers_repository.h @@ -23,6 +23,7 @@ public: int character_id; int task_id; int timer_type; + int timer_group; time_t expire_time; }; @@ -38,6 +39,7 @@ public: "character_id", "task_id", "timer_type", + "timer_group", "expire_time", }; } @@ -49,6 +51,7 @@ public: "character_id", "task_id", "timer_type", + "timer_group", "UNIX_TIMESTAMP(expire_time)", }; } @@ -94,6 +97,7 @@ public: entry.character_id = 0; entry.task_id = 0; entry.timer_type = 0; + entry.timer_group = 0; entry.expire_time = std::time(nullptr); return entry; @@ -134,7 +138,8 @@ public: entry.character_id = atoi(row[1]); entry.task_id = atoi(row[2]); entry.timer_type = atoi(row[3]); - entry.expire_time = strtoll(row[4] ? row[4] : "-1", nullptr, 10); + entry.timer_group = atoi(row[4]); + entry.expire_time = strtoll(row[5] ? row[5] : "-1", nullptr, 10); return entry; } @@ -171,7 +176,8 @@ public: update_values.push_back(columns[1] + " = " + std::to_string(character_task_timers_entry.character_id)); update_values.push_back(columns[2] + " = " + std::to_string(character_task_timers_entry.task_id)); update_values.push_back(columns[3] + " = " + std::to_string(character_task_timers_entry.timer_type)); - update_values.push_back(columns[4] + " = FROM_UNIXTIME(" + (character_task_timers_entry.expire_time > 0 ? std::to_string(character_task_timers_entry.expire_time) : "null") + ")"); + update_values.push_back(columns[4] + " = " + std::to_string(character_task_timers_entry.timer_group)); + update_values.push_back(columns[5] + " = FROM_UNIXTIME(" + (character_task_timers_entry.expire_time > 0 ? std::to_string(character_task_timers_entry.expire_time) : "null") + ")"); auto results = db.QueryDatabase( fmt::format( @@ -197,6 +203,7 @@ public: insert_values.push_back(std::to_string(character_task_timers_entry.character_id)); insert_values.push_back(std::to_string(character_task_timers_entry.task_id)); insert_values.push_back(std::to_string(character_task_timers_entry.timer_type)); + insert_values.push_back(std::to_string(character_task_timers_entry.timer_group)); insert_values.push_back("FROM_UNIXTIME(" + (character_task_timers_entry.expire_time > 0 ? std::to_string(character_task_timers_entry.expire_time) : "null") + ")"); auto results = db.QueryDatabase( @@ -231,6 +238,7 @@ public: insert_values.push_back(std::to_string(character_task_timers_entry.character_id)); insert_values.push_back(std::to_string(character_task_timers_entry.task_id)); insert_values.push_back(std::to_string(character_task_timers_entry.timer_type)); + insert_values.push_back(std::to_string(character_task_timers_entry.timer_group)); insert_values.push_back("FROM_UNIXTIME(" + (character_task_timers_entry.expire_time > 0 ? std::to_string(character_task_timers_entry.expire_time) : "null") + ")"); insert_chunks.push_back("(" + Strings::Implode(",", insert_values) + ")"); @@ -269,7 +277,8 @@ public: entry.character_id = atoi(row[1]); entry.task_id = atoi(row[2]); entry.timer_type = atoi(row[3]); - entry.expire_time = strtoll(row[4] ? row[4] : "-1", nullptr, 10); + entry.timer_group = atoi(row[4]); + entry.expire_time = strtoll(row[5] ? row[5] : "-1", nullptr, 10); all_entries.push_back(entry); } @@ -298,7 +307,8 @@ public: entry.character_id = atoi(row[1]); entry.task_id = atoi(row[2]); entry.timer_type = atoi(row[3]); - entry.expire_time = strtoll(row[4] ? row[4] : "-1", nullptr, 10); + entry.timer_group = atoi(row[4]); + entry.expire_time = strtoll(row[5] ? row[5] : "-1", nullptr, 10); all_entries.push_back(entry); } diff --git a/common/repositories/base/base_tasks_repository.h b/common/repositories/base/base_tasks_repository.h index e7e932dd3..e303290af 100644 --- a/common/repositories/base/base_tasks_repository.h +++ b/common/repositories/base/base_tasks_repository.h @@ -40,7 +40,9 @@ public: int repeatable; int faction_reward; std::string completion_emote; + int replay_timer_group; int replay_timer_seconds; + int request_timer_group; int request_timer_seconds; int lock_activity_id; }; @@ -74,7 +76,9 @@ public: "repeatable", "faction_reward", "completion_emote", + "replay_timer_group", "replay_timer_seconds", + "request_timer_group", "request_timer_seconds", "lock_activity_id", }; @@ -104,7 +108,9 @@ public: "repeatable", "faction_reward", "completion_emote", + "replay_timer_group", "replay_timer_seconds", + "request_timer_group", "request_timer_seconds", "lock_activity_id", }; @@ -168,7 +174,9 @@ public: entry.repeatable = 1; entry.faction_reward = 0; entry.completion_emote = ""; + entry.replay_timer_group = 0; entry.replay_timer_seconds = 0; + entry.request_timer_group = 0; entry.request_timer_seconds = 0; entry.lock_activity_id = -1; @@ -227,9 +235,11 @@ public: entry.repeatable = atoi(row[18]); entry.faction_reward = atoi(row[19]); 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]); + entry.replay_timer_group = atoi(row[21]); + entry.replay_timer_seconds = atoi(row[22]); + entry.request_timer_group = atoi(row[23]); + entry.request_timer_seconds = atoi(row[24]); + entry.lock_activity_id = atoi(row[25]); return entry; } @@ -284,9 +294,11 @@ public: update_values.push_back(columns[18] + " = " + std::to_string(tasks_entry.repeatable)); update_values.push_back(columns[19] + " = " + std::to_string(tasks_entry.faction_reward)); 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)); + update_values.push_back(columns[21] + " = " + std::to_string(tasks_entry.replay_timer_group)); + update_values.push_back(columns[22] + " = " + std::to_string(tasks_entry.replay_timer_seconds)); + update_values.push_back(columns[23] + " = " + std::to_string(tasks_entry.request_timer_group)); + update_values.push_back(columns[24] + " = " + std::to_string(tasks_entry.request_timer_seconds)); + update_values.push_back(columns[25] + " = " + std::to_string(tasks_entry.lock_activity_id)); auto results = db.QueryDatabase( fmt::format( @@ -329,7 +341,9 @@ public: insert_values.push_back(std::to_string(tasks_entry.repeatable)); insert_values.push_back(std::to_string(tasks_entry.faction_reward)); insert_values.push_back("'" + Strings::Escape(tasks_entry.completion_emote) + "'"); + insert_values.push_back(std::to_string(tasks_entry.replay_timer_group)); insert_values.push_back(std::to_string(tasks_entry.replay_timer_seconds)); + insert_values.push_back(std::to_string(tasks_entry.request_timer_group)); insert_values.push_back(std::to_string(tasks_entry.request_timer_seconds)); insert_values.push_back(std::to_string(tasks_entry.lock_activity_id)); @@ -382,7 +396,9 @@ public: insert_values.push_back(std::to_string(tasks_entry.repeatable)); insert_values.push_back(std::to_string(tasks_entry.faction_reward)); insert_values.push_back("'" + Strings::Escape(tasks_entry.completion_emote) + "'"); + insert_values.push_back(std::to_string(tasks_entry.replay_timer_group)); insert_values.push_back(std::to_string(tasks_entry.replay_timer_seconds)); + insert_values.push_back(std::to_string(tasks_entry.request_timer_group)); insert_values.push_back(std::to_string(tasks_entry.request_timer_seconds)); insert_values.push_back(std::to_string(tasks_entry.lock_activity_id)); @@ -439,9 +455,11 @@ public: entry.repeatable = atoi(row[18]); entry.faction_reward = atoi(row[19]); 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]); + entry.replay_timer_group = atoi(row[21]); + entry.replay_timer_seconds = atoi(row[22]); + entry.request_timer_group = atoi(row[23]); + entry.request_timer_seconds = atoi(row[24]); + entry.lock_activity_id = atoi(row[25]); all_entries.push_back(entry); } @@ -487,9 +505,11 @@ public: entry.repeatable = atoi(row[18]); entry.faction_reward = atoi(row[19]); 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]); + entry.replay_timer_group = atoi(row[21]); + entry.replay_timer_seconds = atoi(row[22]); + entry.request_timer_group = atoi(row[23]); + entry.request_timer_seconds = atoi(row[24]); + entry.lock_activity_id = atoi(row[25]); all_entries.push_back(entry); } diff --git a/common/tasks.h b/common/tasks.h index 8b188eef2..45725a794 100644 --- a/common/tasks.h +++ b/common/tasks.h @@ -223,7 +223,9 @@ struct TaskInformation { int min_players; int max_players; bool repeatable{}; + int replay_timer_group; int replay_timer_seconds; + int request_timer_group; int request_timer_seconds; ActivityInformation activity_information[MAXACTIVITIESPERTASK]; diff --git a/common/version.h b/common/version.h index 43478757d..da9c42c28 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 9192 +#define CURRENT_BINARY_DATABASE_VERSION 9193 #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 fc0664394..5af93b53d 100644 --- a/utils/sql/db_update_manifest.txt +++ b/utils/sql/db_update_manifest.txt @@ -446,6 +446,7 @@ 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| +9193|2022_07_16_task_timer_groups.sql|SHOW COLUMNS FROM `tasks` LIKE 'replay_timer_group'|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_16_task_timer_groups.sql b/utils/sql/git/required/2022_07_16_task_timer_groups.sql new file mode 100644 index 000000000..1dcb96516 --- /dev/null +++ b/utils/sql/git/required/2022_07_16_task_timer_groups.sql @@ -0,0 +1,6 @@ +ALTER TABLE `tasks` + ADD COLUMN `replay_timer_group` INT UNSIGNED NOT NULL DEFAULT '0' AFTER `completion_emote`, + ADD COLUMN `request_timer_group` INT UNSIGNED NOT NULL DEFAULT '0' AFTER `replay_timer_seconds`; + +ALTER TABLE `character_task_timers` + ADD COLUMN `timer_group` INT NOT NULL DEFAULT '0' AFTER `timer_type`; diff --git a/world/shared_task_manager.cpp b/world/shared_task_manager.cpp index 4fb8da828..fb33eaf5c 100644 --- a/world/shared_task_manager.cpp +++ b/world/shared_task_manager.cpp @@ -132,6 +132,7 @@ void SharedTaskManager::AttemptSharedTaskCreation( timer.character_id = m.character_id; timer.task_id = task.id; timer.timer_type = static_cast(TaskTimerType::Request); + timer.timer_group = task.request_timer_group; timer.expire_time = shared_task_entity.accepted_time + task.request_timer_seconds; task_timers.emplace_back(timer); @@ -1090,6 +1091,7 @@ void SharedTaskManager::AddPlayerByCharacterIdAndName( timer.character_id = character_id; timer.task_id = s->GetDbSharedTask().task_id; timer.timer_type = static_cast(TaskTimerType::Request); + timer.timer_group = s->GetTaskData().request_timer_group; timer.expire_time = expire_time; CharacterTaskTimersRepository::InsertOne(*m_database, timer); @@ -1300,6 +1302,29 @@ void SharedTaskManager::SendMembersMessageID( } } +std::vector SharedTaskManager::GetCharacterTimers( + const std::vector& character_ids, const TasksRepository::Tasks& task) +{ + // todo: consider caching character timers in world and zone to avoid queries + auto task_timers = CharacterTaskTimersRepository::GetWhere(*m_database, fmt::format( + SQL( + character_id IN ({}) + AND (task_id = {} + OR (timer_group > 0 AND timer_type = {} AND timer_group = {}) + OR (timer_group > 0 AND timer_type = {} AND timer_group = {})) + AND expire_time > NOW() ORDER BY timer_type ASC LIMIT 1 + ), + fmt::join(character_ids, ","), + task.id, + static_cast(TaskTimerType::Replay), + task.replay_timer_group, + static_cast(TaskTimerType::Request), + task.request_timer_group + )); + + return task_timers; +} + bool SharedTaskManager::CanRequestSharedTask( uint32_t task_id, uint32_t character_id, @@ -1395,12 +1420,7 @@ bool SharedTaskManager::CanRequestSharedTask( } // check if any party members have a replay or request timer for the task (limit 1, replay checked first) - auto character_task_timers = CharacterTaskTimersRepository::GetWhere( - *m_database, fmt::format( - "character_id IN ({}) AND task_id = {} AND expire_time > NOW() ORDER BY timer_type ASC LIMIT 1", - fmt::join(request.character_ids, ","), task_id - ) - ); + auto character_task_timers = GetCharacterTimers(request.character_ids, task); if (!character_task_timers.empty()) { auto timer_type = static_cast(character_task_timers.front().timer_type); @@ -1523,11 +1543,7 @@ bool SharedTaskManager::CanAddPlayer(SharedTask *s, uint32_t character_id, std:: // check if player has a replay or request timer lockout // todo: live allows characters with a request timer to be re-invited if they quit, but only until they zone? (investigate/edge case) - auto task_timers = CharacterTaskTimersRepository::GetWhere( - *m_database, fmt::format( - "character_id = {} AND task_id = {} AND expire_time > NOW() ORDER BY timer_type ASC LIMIT 1", - character_id, s->GetDbSharedTask().task_id - )); + auto task_timers = GetCharacterTimers({ character_id }, s->GetTaskData()); if (!task_timers.empty()) { auto timer_type = static_cast(task_timers.front().timer_type); @@ -1679,6 +1695,7 @@ void SharedTaskManager::AddReplayTimers(SharedTask *s) timer.character_id = member_id; timer.task_id = s->GetTaskData().id; timer.timer_type = static_cast(TaskTimerType::Replay); + timer.timer_group = s->GetTaskData().replay_timer_group; timer.expire_time = expire_time; task_timers.emplace_back(timer); @@ -1701,8 +1718,9 @@ void SharedTaskManager::AddReplayTimers(SharedTask *s) // this can occur if a player has a timer for being a past member of // a shared task but joined another before the first was completed CharacterTaskTimersRepository::DeleteWhere(*m_database, fmt::format( - "task_id = {} AND timer_type = {} AND character_id IN ({})", + "(task_id = {} OR (timer_group > 0 AND timer_group = {})) AND timer_type = {} AND character_id IN ({})", s->GetTaskData().id, + s->GetTaskData().replay_timer_group, static_cast(TaskTimerType::Replay), fmt::join(s->member_id_history, ",") )); diff --git a/world/shared_task_manager.h b/world/shared_task_manager.h index e39156ffb..475691105 100644 --- a/world/shared_task_manager.h +++ b/world/shared_task_manager.h @@ -3,6 +3,7 @@ #include "../common/database.h" #include "../common/shared_tasks.h" +#include "../common/repositories/character_task_timers_repository.h" class DynamicZone; @@ -114,6 +115,9 @@ protected: // store a reference of active invitations that have been sent to players std::vector m_active_invitations{}; + std::vector GetCharacterTimers( + const std::vector& character_ids, const TasksRepository::Tasks& task); + void AddReplayTimers(SharedTask *s); 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); diff --git a/zone/task_client_state.cpp b/zone/task_client_state.cpp index a5d5f33b7..49169aa36 100644 --- a/zone/task_client_state.cpp +++ b/zone/task_client_state.cpp @@ -2327,8 +2327,19 @@ void ClientTaskState::AcceptNewTask( if (task->type != TaskType::Shared) { auto task_timers = CharacterTaskTimersRepository::GetWhere(database, fmt::format( - "character_id = {} AND task_id = {} AND expire_time > NOW() ORDER BY timer_type ASC LIMIT 1", - client->CharacterID(), task_id + SQL( + character_id = {} + AND (task_id = {} + OR (timer_group > 0 AND timer_type = {} AND timer_group = {}) + OR (timer_group > 0 AND timer_type = {} AND timer_group = {})) + AND expire_time > NOW() ORDER BY timer_type ASC LIMIT 1 + ), + client->CharacterID(), + task_id, + static_cast(TaskTimerType::Replay), + task->replay_timer_group, + static_cast(TaskTimerType::Request), + task->request_timer_group )); if (!task_timers.empty()) @@ -2426,6 +2437,7 @@ void ClientTaskState::AcceptNewTask( timer.character_id = client->CharacterID(); timer.task_id = task_id; timer.timer_type = static_cast(TaskTimerType::Request); + timer.timer_group = task->request_timer_group; timer.expire_time = expire_time; CharacterTaskTimersRepository::InsertOne(database, timer); @@ -2639,13 +2651,17 @@ void ClientTaskState::AddReplayTimer(Client* client, ClientTaskInformation& clie auto timer = CharacterTaskTimersRepository::NewEntity(); timer.character_id = client->CharacterID(); timer.task_id = client_task.task_id; - timer.expire_time = expire_time; timer.timer_type = static_cast(TaskTimerType::Replay); + timer.timer_group = task.replay_timer_group; + timer.expire_time = expire_time; // 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())); + "(task_id = {} OR (timer_group > 0 AND timer_group = {})) AND timer_type = {} AND character_id = {}", + client_task.task_id, + task.replay_timer_group, + static_cast(TaskTimerType::Replay), + client->CharacterID())); CharacterTaskTimersRepository::InsertOne(database, timer); diff --git a/zone/task_manager.cpp b/zone/task_manager.cpp index 89d70444c..8eefea12e 100644 --- a/zone/task_manager.cpp +++ b/zone/task_manager.cpp @@ -115,7 +115,9 @@ bool TaskManager::LoadTasks(int single_task) m_task_data[task_id]->max_players = task.max_players; m_task_data[task_id]->repeatable = task.repeatable; m_task_data[task_id]->completion_emote = task.completion_emote; + m_task_data[task_id]->replay_timer_group = task.replay_timer_group; m_task_data[task_id]->replay_timer_seconds = task.replay_timer_seconds; + m_task_data[task_id]->request_timer_group = task.request_timer_group; m_task_data[task_id]->request_timer_seconds = task.request_timer_seconds; m_task_data[task_id]->activity_count = 0; m_task_data[task_id]->sequence_mode = ActivitiesSequential; @@ -125,7 +127,7 @@ bool TaskManager::LoadTasks(int single_task) "[LoadTasks] (Task) task_id [{}] type [{}] () duration [{}] duration_code [{}] title [{}] description [{}] " " reward [{}] rewardid [{}] cashreward [{}] xpreward [{}] rewardmethod [{}] faction_reward [{}] minlevel [{}] " " maxlevel [{}] level_spread [{}] min_players [{}] max_players [{}] repeatable [{}] completion_emote [{}]", - " replay_timer_seconds [{}] request_timer_seconds [{}]", + " replay_group [{}] replay_timer_seconds [{}] request_group [{}] request_timer_seconds [{}]", task.id, task.type, task.duration, @@ -145,7 +147,9 @@ bool TaskManager::LoadTasks(int single_task) task.max_players, task.repeatable, task.completion_emote, + task.replay_timer_group, task.replay_timer_seconds, + task.request_timer_group, task.request_timer_seconds ); }