From 8dfdca7fbd8cacabebd1bdaf6dd617c2d9ff3dfe Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Tue, 31 Mar 2026 13:02:23 -0700 Subject: [PATCH] feat: add mission progression for behaviors (#1962) * feat: add mission progression for behaviors * Add const to ptr Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- dDatabase/GameDatabase/MySQL/MySQLDatabase.h | 31 +++++++++++++++++-- .../GameDatabase/MySQL/Tables/CharInfo.cpp | 12 +++---- .../GameDatabase/MySQL/Tables/Leaderboard.cpp | 2 +- .../GameDatabase/MySQL/Tables/Property.cpp | 4 +-- dDatabase/GameDatabase/MySQL/Tables/Ugc.cpp | 2 +- dGame/dComponents/ModelComponent.cpp | 4 +++ dGame/dMission/MissionTask.cpp | 2 ++ 7 files changed, 43 insertions(+), 14 deletions(-) diff --git a/dDatabase/GameDatabase/MySQL/MySQLDatabase.h b/dDatabase/GameDatabase/MySQL/MySQLDatabase.h index 456ab5fa..5b26a075 100644 --- a/dDatabase/GameDatabase/MySQL/MySQLDatabase.h +++ b/dDatabase/GameDatabase/MySQL/MySQLDatabase.h @@ -9,6 +9,28 @@ typedef std::unique_ptr& UniquePreppedStmtRef; typedef std::unique_ptr UniqueResultSet; +// Holds a PreparedStatement and its ResultSet together to ensure the statement +// outlives the result. +struct PreparedStmtResultSet { + std::unique_ptr m_stmt; + std::unique_ptr m_resultSet; + + PreparedStmtResultSet(sql::PreparedStatement* stmt = nullptr, sql::ResultSet* resultSet = nullptr) + : m_stmt(stmt), m_resultSet(resultSet) {} + + PreparedStmtResultSet(PreparedStmtResultSet&&) = default; + PreparedStmtResultSet& operator=(PreparedStmtResultSet&&) = default; + + ~PreparedStmtResultSet() { + m_resultSet.reset(); + m_stmt.reset(); + } + + sql::ResultSet* operator->() const { + return m_resultSet.get(); + } +}; + // Purposefully no definition for this to provide linker errors in the case someone tries to // bind a parameter to a type that isn't defined. template @@ -136,12 +158,15 @@ private: // Generic query functions that can be used for any query. // Return type may be different depending on the query, so it is up to the caller to check the return type. // The first argument is the query string, and the rest are the parameters to bind to the query. - // The return type is a unique_ptr to the result set, which is deleted automatically when it goes out of scope + // The return type is a PreparedStmtResultSet which keeps the PreparedStatement alive alongside the ResultSet. template - inline std::unique_ptr ExecuteSelect(const std::string& query, Args&&... args) { + inline PreparedStmtResultSet ExecuteSelect(const std::string& query, Args&&... args) { std::unique_ptr preppedStmt(CreatePreppedStmt(query)); SetParams(preppedStmt, std::forward(args)...); - DLU_SQL_TRY_CATCH_RETHROW(return std::unique_ptr(preppedStmt->executeQuery())); + std::unique_ptr resultSet; + DLU_SQL_TRY_CATCH_RETHROW(resultSet.reset(preppedStmt->executeQuery())); + // Release ownership of the pointers to the PreparedStatement and ResultSet to the PreparedStmtResultSet struct, which will ensure they are properly cleaned up. + return PreparedStmtResultSet(preppedStmt.release(), resultSet.release()); } template diff --git a/dDatabase/GameDatabase/MySQL/Tables/CharInfo.cpp b/dDatabase/GameDatabase/MySQL/Tables/CharInfo.cpp index 5d744b3d..719a8372 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/CharInfo.cpp +++ b/dDatabase/GameDatabase/MySQL/Tables/CharInfo.cpp @@ -12,7 +12,7 @@ std::vector MySQLDatabase::GetApprovedCharacterNames() { return toReturn; } -std::optional CharInfoFromQueryResult(std::unique_ptr stmt) { +std::optional CharInfoFromQueryResult(PreparedStmtResultSet& stmt) { if (!stmt->next()) { return std::nullopt; } @@ -31,15 +31,13 @@ std::optional CharInfoFromQueryResult(std::unique_ptr MySQLDatabase::GetCharacterInfo(const LWOOBJID charId) { - return CharInfoFromQueryResult( - ExecuteSelect("SELECT name, pending_name, needs_rename, prop_clone_id, permission_map, id, account_id FROM charinfo WHERE id = ? LIMIT 1;", charId) - ); + auto result = ExecuteSelect("SELECT name, pending_name, needs_rename, prop_clone_id, permission_map, id, account_id FROM charinfo WHERE id = ? LIMIT 1;", charId); + return CharInfoFromQueryResult(result); } std::optional MySQLDatabase::GetCharacterInfo(const std::string_view name) { - return CharInfoFromQueryResult( - ExecuteSelect("SELECT name, pending_name, needs_rename, prop_clone_id, permission_map, id, account_id FROM charinfo WHERE name = ? LIMIT 1;", name) - ); + auto result = ExecuteSelect("SELECT name, pending_name, needs_rename, prop_clone_id, permission_map, id, account_id FROM charinfo WHERE name = ? LIMIT 1;", name); + return CharInfoFromQueryResult(result); } std::vector MySQLDatabase::GetAccountCharacterIds(const LWOOBJID accountId) { diff --git a/dDatabase/GameDatabase/MySQL/Tables/Leaderboard.cpp b/dDatabase/GameDatabase/MySQL/Tables/Leaderboard.cpp index eb60bf08..81439c56 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/Leaderboard.cpp +++ b/dDatabase/GameDatabase/MySQL/Tables/Leaderboard.cpp @@ -14,7 +14,7 @@ std::optional MySQLDatabase::GetDonationTotal(const uint32_t activityI return donation_total->getUInt("donation_total"); } -std::vector ProcessQuery(UniqueResultSet& rows) { +std::vector ProcessQuery(PreparedStmtResultSet& rows) { std::vector entries; entries.reserve(rows->rowsCount()); diff --git a/dDatabase/GameDatabase/MySQL/Tables/Property.cpp b/dDatabase/GameDatabase/MySQL/Tables/Property.cpp index 18916e24..d554a21d 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/Property.cpp +++ b/dDatabase/GameDatabase/MySQL/Tables/Property.cpp @@ -1,7 +1,7 @@ #include "MySQLDatabase.h" #include "ePropertySortType.h" -IProperty::Info ReadPropertyInfo(UniqueResultSet& result) { +IProperty::Info ReadPropertyInfo(PreparedStmtResultSet& result) { IProperty::Info info; info.id = result->getUInt64("id"); info.ownerId = result->getInt64("owner_id"); @@ -21,7 +21,7 @@ IProperty::Info ReadPropertyInfo(UniqueResultSet& result) { std::optional MySQLDatabase::GetProperties(const IProperty::PropertyLookup& params) { std::optional result; std::string query; - std::unique_ptr properties; + PreparedStmtResultSet properties; if (params.sortChoice == SORT_TYPE_FEATURED || params.sortChoice == SORT_TYPE_FRIENDS) { query = R"QUERY( diff --git a/dDatabase/GameDatabase/MySQL/Tables/Ugc.cpp b/dDatabase/GameDatabase/MySQL/Tables/Ugc.cpp index 06865c5e..6367560a 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/Ugc.cpp +++ b/dDatabase/GameDatabase/MySQL/Tables/Ugc.cpp @@ -1,6 +1,6 @@ #include "MySQLDatabase.h" -IUgc::Model ReadModel(UniqueResultSet& result) { +IUgc::Model ReadModel(PreparedStmtResultSet& result) { IUgc::Model model; // blob is owned by the query, so we need to do a deep copy :/ diff --git a/dGame/dComponents/ModelComponent.cpp b/dGame/dComponents/ModelComponent.cpp index 2c975136..4559ca72 100644 --- a/dGame/dComponents/ModelComponent.cpp +++ b/dGame/dComponents/ModelComponent.cpp @@ -8,7 +8,9 @@ #include "ControlBehaviorMsgs.h" #include "tinyxml2.h" #include "InventoryComponent.h" +#include "MissionComponent.h" #include "SimplePhysicsComponent.h" +#include "eMissionTaskType.h" #include "eObjectBits.h" #include "Database.h" @@ -187,6 +189,8 @@ void ModelComponent::AddBehavior(AddMessage& msg) { // Check if this behavior is able to be found via lot (if so, its a loot behavior). insertedBehavior.SetIsLoot(inventoryComponent->FindItemByLot(msg.GetBehaviorId(), eInventoryType::BEHAVIORS)); } + auto* const missionComponent = playerEntity->GetComponent(); + if (missionComponent) missionComponent->Progress(eMissionTaskType::ADD_BEHAVIOR, 0); } auto* const simplePhysComponent = m_Parent->GetComponent(); diff --git a/dGame/dMission/MissionTask.cpp b/dGame/dMission/MissionTask.cpp index 801eaeda..c8f2f9c0 100644 --- a/dGame/dMission/MissionTask.cpp +++ b/dGame/dMission/MissionTask.cpp @@ -446,6 +446,8 @@ void MissionTask::Progress(int32_t value, LWOOBJID associate, const std::string& break; } case eMissionTaskType::PLACE_MODEL: + [[fallthrough]]; + case eMissionTaskType::ADD_BEHAVIOR: { AddProgress(count); break;