From 17f81d13a3d39ac88c211d6bb1cc01b6bef1f2f0 Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Sat, 23 Nov 2024 03:32:31 -0600 Subject: [PATCH 1/6] fix: normalize mixed slashes when looking for files (#1654) --- dZoneManager/Zone.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/dZoneManager/Zone.cpp b/dZoneManager/Zone.cpp index 44532fc9d..1d6570754 100644 --- a/dZoneManager/Zone.cpp +++ b/dZoneManager/Zone.cpp @@ -160,6 +160,7 @@ std::string Zone::GetFilePathForZoneID() { if (zone != nullptr) { std::string toReturn = "maps/" + zone->zoneName; std::transform(toReturn.begin(), toReturn.end(), toReturn.begin(), ::tolower); + std::ranges::replace(toReturn, '\\', '/'); return toReturn; } From 5b8d2b19a337e2190da68eecbd789eeefd269521 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sat, 23 Nov 2024 01:33:21 -0800 Subject: [PATCH 2/6] fix: stack traces work again (#1653) --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0d9d394b2..4a6c4b235 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,7 +75,7 @@ if(UNIX) endif() if(${DYNAMIC} AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - add_compile_options("-rdynamic") + add_link_options("-export-dynamic") endif() if(${GGDB}) From ec501831e60251fd0fca29179160ebd7dd6b7fe1 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sat, 23 Nov 2024 13:56:31 -0800 Subject: [PATCH 3/6] fix: Remove database requirements for Property Entrance Component and greatly simplify logic (#1650) * remove complex queries and move logic to dDatabase remove unused code Use correct id fix arrows use correct parameter fix queries Update Property.cpp remove unused header remove extra include * fix tests * Update dGame/dComponents/PropertyEntranceComponent.h Co-authored-by: jadebenn * Update dGame/User.h Co-authored-by: jadebenn --------- Co-authored-by: jadebenn --- dCommon/dEnums/ePropertySortType.h | 11 + dDatabase/GameDatabase/ITables/IProperty.h | 24 ++ dDatabase/GameDatabase/MySQL/MySQLDatabase.h | 1 + .../GameDatabase/MySQL/Tables/Property.cpp | 135 +++++++- .../GameDatabase/TestSQL/TestSQLDatabase.h | 1 + dGame/User.h | 2 +- .../dComponents/PropertyEntranceComponent.cpp | 314 +++++------------- dGame/dComponents/PropertyEntranceComponent.h | 15 +- .../PropertySelectQueryProperty.cpp | 2 +- .../PropertySelectQueryProperty.h | 6 +- 10 files changed, 255 insertions(+), 256 deletions(-) create mode 100644 dCommon/dEnums/ePropertySortType.h diff --git a/dCommon/dEnums/ePropertySortType.h b/dCommon/dEnums/ePropertySortType.h new file mode 100644 index 000000000..f8e9b1cf6 --- /dev/null +++ b/dCommon/dEnums/ePropertySortType.h @@ -0,0 +1,11 @@ +#ifndef EPROPERTYSORTTYPE_H +#define EPROPERTYSORTTYPE_H + +enum ePropertySortType : int32_t { + SORT_TYPE_FRIENDS = 0, + SORT_TYPE_REPUTATION = 1, + SORT_TYPE_RECENT = 3, + SORT_TYPE_FEATURED = 5 +}; + +#endif //!EPROPERTYSORTTYPE_H diff --git a/dDatabase/GameDatabase/ITables/IProperty.h b/dDatabase/GameDatabase/ITables/IProperty.h index 54994b51a..f3437154f 100644 --- a/dDatabase/GameDatabase/ITables/IProperty.h +++ b/dDatabase/GameDatabase/ITables/IProperty.h @@ -4,6 +4,8 @@ #include #include +enum ePropertySortType : int32_t; + class IProperty { public: struct Info { @@ -18,11 +20,33 @@ class IProperty { uint32_t lastUpdatedTime{}; uint32_t claimedTime{}; uint32_t reputation{}; + float performanceCost{}; + }; + + struct PropertyLookup { + uint32_t mapId{}; + std::string searchString; + ePropertySortType sortChoice{}; + uint32_t playerId{}; + uint32_t numResults{}; + uint32_t startIndex{}; + uint32_t playerSort{}; + }; + + struct PropertyEntranceResult { + int32_t totalEntriesMatchingQuery{}; + // The entries that match the query. This should only contain up to 12 entries. + std::vector entries; }; // Get the property info for the given property id. virtual std::optional GetPropertyInfo(const LWOMAPID mapId, const LWOCLONEID cloneId) = 0; + // Get the properties for the given property lookup params. + // This is expected to return a result set of up to 12 properties + // so as not to transfer too much data at once. + virtual std::optional GetProperties(const PropertyLookup& params) = 0; + // Update the property moderation info for the given property id. virtual void UpdatePropertyModerationInfo(const IProperty::Info& info) = 0; diff --git a/dDatabase/GameDatabase/MySQL/MySQLDatabase.h b/dDatabase/GameDatabase/MySQL/MySQLDatabase.h index a3019bea3..f30e33cee 100644 --- a/dDatabase/GameDatabase/MySQL/MySQLDatabase.h +++ b/dDatabase/GameDatabase/MySQL/MySQLDatabase.h @@ -112,6 +112,7 @@ class MySQLDatabase : public GameDatabase { std::string GetBehavior(const int32_t behaviorId) override; void RemoveBehavior(const int32_t characterId) override; void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override; + std::optional GetProperties(const IProperty::PropertyLookup& params) override; private: // Generic query functions that can be used for any query. diff --git a/dDatabase/GameDatabase/MySQL/Tables/Property.cpp b/dDatabase/GameDatabase/MySQL/Tables/Property.cpp index 5d72a3b59..8aaf93c4a 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/Property.cpp +++ b/dDatabase/GameDatabase/MySQL/Tables/Property.cpp @@ -1,8 +1,140 @@ #include "MySQLDatabase.h" +#include "ePropertySortType.h" + +std::optional MySQLDatabase::GetProperties(const IProperty::PropertyLookup& params) { + std::optional result; + std::string query; + std::unique_ptr properties; + + if (params.sortChoice == SORT_TYPE_FEATURED || params.sortChoice == SORT_TYPE_FRIENDS) { + query = R"QUERY( + FROM properties as p + JOIN charinfo as ci + ON ci.prop_clone_id = p.clone_id + where p.zone_id = ? + AND ( + p.description LIKE ? + OR p.name LIKE ? + OR ci.name LIKE ? + ) + AND p.privacy_option >= ? + AND p.owner_id IN ( + SELECT fr.requested_player AS player FROM ( + SELECT CASE + WHEN player_id = ? THEN friend_id + WHEN friend_id = ? THEN player_id + END AS requested_player FROM friends + ) AS fr + JOIN charinfo AS ci ON ci.id = fr.requested_player + WHERE fr.requested_player IS NOT NULL AND fr.requested_player != ? + ) ORDER BY ci.name ASC + )QUERY"; + const auto completeQuery = "SELECT p.* " + query + " LIMIT ? OFFSET ?;"; + properties = ExecuteSelect( + completeQuery, + params.mapId, + "%" + params.searchString + "%", + "%" + params.searchString + "%", + "%" + params.searchString + "%", + params.playerSort, + params.playerId, + params.playerId, + params.playerId, + params.numResults, + params.startIndex + ); + const auto countQuery = "SELECT COUNT(*) as count" + query + ";"; + auto count = ExecuteSelect( + countQuery, + params.mapId, + "%" + params.searchString + "%", + "%" + params.searchString + "%", + "%" + params.searchString + "%", + params.playerSort, + params.playerId, + params.playerId, + params.playerId + ); + if (count->next()) { + result->totalEntriesMatchingQuery = count->getUInt("count"); + } + } else { + if (params.sortChoice == SORT_TYPE_REPUTATION) { + query = R"QUERY( + FROM properties as p + JOIN charinfo as ci + ON ci.prop_clone_id = p.clone_id + where p.zone_id = ? + AND ( + p.description LIKE ? + OR p.name LIKE ? + OR ci.name LIKE ? + ) + AND p.privacy_option >= ? + ORDER BY p.reputation DESC, p.last_updated DESC + )QUERY"; + } else { + query = R"QUERY( + FROM properties as p + JOIN charinfo as ci + ON ci.prop_clone_id = p.clone_id + where p.zone_id = ? + AND ( + p.description LIKE ? + OR p.name LIKE ? + OR ci.name LIKE ? + ) + AND p.privacy_option >= ? + ORDER BY p.last_updated DESC + )QUERY"; + } + const auto completeQuery = "SELECT p.* " + query + " LIMIT ? OFFSET ?;"; + properties = ExecuteSelect( + completeQuery, + params.mapId, + "%" + params.searchString + "%", + "%" + params.searchString + "%", + "%" + params.searchString + "%", + params.playerSort, + params.numResults, + params.startIndex + ); + const auto countQuery = "SELECT COUNT(*) as count" + query + ";"; + auto count = ExecuteSelect( + countQuery, + params.mapId, + "%" + params.searchString + "%", + "%" + params.searchString + "%", + "%" + params.searchString + "%", + params.playerSort + ); + if (count->next()) { + result->totalEntriesMatchingQuery = count->getUInt("count"); + } + } + + while (properties->next()) { + auto& entry = result->entries.emplace_back(); + entry.id = properties->getUInt64("id"); + entry.ownerId = properties->getUInt64("owner_id"); + entry.cloneId = properties->getUInt64("clone_id"); + entry.name = properties->getString("name").c_str(); + entry.description = properties->getString("description").c_str(); + entry.privacyOption = properties->getInt("privacy_option"); + entry.rejectionReason = properties->getString("rejection_reason").c_str(); + entry.lastUpdatedTime = properties->getUInt("last_updated"); + entry.claimedTime = properties->getUInt("time_claimed"); + entry.reputation = properties->getUInt("reputation"); + entry.modApproved = properties->getUInt("mod_approved"); + entry.performanceCost = properties->getFloat("performance_cost"); + } + + return result; +} std::optional MySQLDatabase::GetPropertyInfo(const LWOMAPID mapId, const LWOCLONEID cloneId) { auto propertyEntry = ExecuteSelect( - "SELECT id, owner_id, clone_id, name, description, privacy_option, rejection_reason, last_updated, time_claimed, reputation, mod_approved " + "SELECT id, owner_id, clone_id, name, description, privacy_option, rejection_reason, last_updated, time_claimed, reputation, mod_approved, performance_cost " "FROM properties WHERE zone_id = ? AND clone_id = ?;", mapId, cloneId); if (!propertyEntry->next()) { @@ -21,6 +153,7 @@ std::optional MySQLDatabase::GetPropertyInfo(const LWOMAPID map toReturn.claimedTime = propertyEntry->getUInt("time_claimed"); toReturn.reputation = propertyEntry->getUInt("reputation"); toReturn.modApproved = propertyEntry->getUInt("mod_approved"); + toReturn.performanceCost = propertyEntry->getFloat("performance_cost"); return toReturn; } diff --git a/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.h b/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.h index 6fbdf879f..1fbb18451 100644 --- a/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.h +++ b/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.h @@ -90,6 +90,7 @@ class TestSQLDatabase : public GameDatabase { std::string GetBehavior(const int32_t behaviorId) override; void RemoveBehavior(const int32_t behaviorId) override; void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override; + std::optional GetProperties(const IProperty::PropertyLookup& params) override { return {}; }; }; #endif //!TESTSQLDATABASE_H diff --git a/dGame/User.h b/dGame/User.h index 54e6ad529..662842a8a 100644 --- a/dGame/User.h +++ b/dGame/User.h @@ -25,7 +25,7 @@ class User { User& operator=(const User& other); bool operator==(const User& other) const; - uint32_t GetAccountID() { return m_AccountID; } + uint32_t GetAccountID() const noexcept { return m_AccountID; } std::string& GetUsername() { return m_Username; } std::string& GetSessionKey() { return m_SessionKey; } SystemAddress& GetSystemAddress() { return m_SystemAddress; } diff --git a/dGame/dComponents/PropertyEntranceComponent.cpp b/dGame/dComponents/PropertyEntranceComponent.cpp index ab3bb5daf..783de9a9d 100644 --- a/dGame/dComponents/PropertyEntranceComponent.cpp +++ b/dGame/dComponents/PropertyEntranceComponent.cpp @@ -14,6 +14,8 @@ #include "Amf3.h" #include "eObjectBits.h" #include "eGameMasterLevel.h" +#include "ePropertySortType.h" +#include "User.h" PropertyEntranceComponent::PropertyEntranceComponent(Entity* parent, uint32_t componentID) : Component(parent) { this->propertyQueries = {}; @@ -74,261 +76,103 @@ void PropertyEntranceComponent::OnEnterProperty(Entity* entity, uint32_t index, launcher->Launch(entity, launcher->GetTargetZone(), cloneId); } -PropertySelectQueryProperty PropertyEntranceComponent::SetPropertyValues(PropertySelectQueryProperty property, LWOCLONEID cloneId, std::string ownerName, std::string propertyName, std::string propertyDescription, float reputation, bool isBFF, bool isFriend, bool isModeratorApproved, bool isAlt, bool isOwned, uint32_t privacyOption, uint32_t timeLastUpdated, float performanceCost) { - property.CloneId = cloneId; - property.OwnerName = ownerName; - property.Name = propertyName; - property.Description = propertyDescription; - property.Reputation = reputation; - property.IsBestFriend = isBFF; - property.IsFriend = isFriend; - property.IsModeratorApproved = isModeratorApproved; - property.IsAlt = isAlt; - property.IsOwned = isOwned; - property.AccessType = privacyOption; - property.DateLastPublished = timeLastUpdated; - property.PerformanceCost = performanceCost; - - return property; -} - -std::string PropertyEntranceComponent::BuildQuery(Entity* entity, int32_t sortMethod, Character* character, std::string customQuery, bool wantLimits) { - std::string base; - if (customQuery == "") { - base = baseQueryForProperties; - } else { - base = customQuery; - } - std::string orderBy = ""; - if (sortMethod == SORT_TYPE_FEATURED || sortMethod == SORT_TYPE_FRIENDS) { - std::string friendsList = " AND p.owner_id IN ("; - - auto friendsListQuery = Database::Get()->CreatePreppedStmt("SELECT * FROM (SELECT CASE WHEN player_id = ? THEN friend_id WHEN friend_id = ? THEN player_id END AS requested_player FROM friends ) AS fr WHERE requested_player IS NOT NULL ORDER BY requested_player DESC;"); - - friendsListQuery->setUInt(1, character->GetID()); - friendsListQuery->setUInt(2, character->GetID()); - - auto friendsListQueryResult = friendsListQuery->executeQuery(); - - while (friendsListQueryResult->next()) { - auto playerIDToConvert = friendsListQueryResult->getInt(1); - friendsList = friendsList + std::to_string(playerIDToConvert) + ","; - } - // Replace trailing comma with the closing parenthesis. - if (friendsList.at(friendsList.size() - 1) == ',') friendsList.erase(friendsList.size() - 1, 1); - friendsList += ") "; - - // If we have no friends then use a -1 for the query. - if (friendsList.find("()") != std::string::npos) friendsList = " AND p.owner_id IN (-1) "; - - orderBy += friendsList + "ORDER BY ci.name ASC "; - - delete friendsListQueryResult; - friendsListQueryResult = nullptr; - - delete friendsListQuery; - friendsListQuery = nullptr; - } else if (sortMethod == SORT_TYPE_RECENT) { - orderBy = "ORDER BY p.last_updated DESC "; - } else if (sortMethod == SORT_TYPE_REPUTATION) { - orderBy = "ORDER BY p.reputation DESC, p.last_updated DESC "; - } else { - orderBy = "ORDER BY p.last_updated DESC "; - } - return base + orderBy + (wantLimits ? "LIMIT ? OFFSET ?;" : ";"); -} - void PropertyEntranceComponent::OnPropertyEntranceSync(Entity* entity, bool includeNullAddress, bool includeNullDescription, bool playerOwn, bool updateUi, int32_t numResults, int32_t lReputationTime, int32_t sortMethod, int32_t startIndex, std::string filterText, const SystemAddress& sysAddr) { - - std::vector entries{}; - PropertySelectQueryProperty playerEntry{}; - - auto character = entity->GetCharacter(); + const auto* const character = entity->GetCharacter(); if (!character) return; + const auto* const user = character->GetParentUser(); + if (!user) return; + auto& entries = propertyQueries[entity->GetObjectID()]; + entries.clear(); // Player property goes in index 1 of the vector. This is how the client expects it. - auto playerPropertyLookup = Database::Get()->CreatePreppedStmt("SELECT * FROM properties WHERE owner_id = ? AND zone_id = ?"); - - playerPropertyLookup->setInt(1, character->GetID()); - playerPropertyLookup->setInt(2, this->m_MapID); - - auto playerPropertyLookupResults = playerPropertyLookup->executeQuery(); + const auto playerProperty = Database::Get()->GetPropertyInfo(m_MapID, character->GetPropertyCloneID()); // If the player has a property this query will have a single result. - if (playerPropertyLookupResults->next()) { - const auto cloneId = playerPropertyLookupResults->getUInt64(4); - const auto propertyName = std::string(playerPropertyLookupResults->getString(5).c_str()); - const auto propertyDescription = std::string(playerPropertyLookupResults->getString(6).c_str()); - const auto privacyOption = playerPropertyLookupResults->getInt(9); - const auto modApproved = playerPropertyLookupResults->getBoolean(10); - const auto dateLastUpdated = playerPropertyLookupResults->getInt64(11); - const auto reputation = playerPropertyLookupResults->getUInt(14); - const auto performanceCost = playerPropertyLookupResults->getFloat(16); - - playerEntry = SetPropertyValues(playerEntry, cloneId, character->GetName(), propertyName, propertyDescription, reputation, true, true, modApproved, true, true, privacyOption, dateLastUpdated, performanceCost); + auto& playerEntry = entries.emplace_back(); + if (playerProperty.has_value()) { + playerEntry.OwnerName = character->GetName(); + playerEntry.IsBestFriend = true; + playerEntry.IsFriend = true; + playerEntry.IsAlt = true; + playerEntry.IsOwned = true; + playerEntry.CloneId = playerProperty->cloneId; + playerEntry.Name = playerProperty->name; + playerEntry.Description = playerProperty->description; + playerEntry.AccessType = playerProperty->privacyOption; + playerEntry.IsModeratorApproved = playerProperty->modApproved; + playerEntry.DateLastPublished = playerProperty->lastUpdatedTime; + playerEntry.Reputation = playerProperty->reputation; + playerEntry.PerformanceCost = playerProperty->performanceCost; + auto& entry = playerEntry; } else { - playerEntry = SetPropertyValues(playerEntry, character->GetPropertyCloneID(), character->GetName(), "", "", 0, true, true); + playerEntry.OwnerName = character->GetName(); + playerEntry.IsBestFriend = true; + playerEntry.IsFriend = true; + playerEntry.IsAlt = false; + playerEntry.IsOwned = false; + playerEntry.CloneId = character->GetPropertyCloneID(); + playerEntry.Name = ""; + playerEntry.Description = ""; + playerEntry.AccessType = 0; + playerEntry.IsModeratorApproved = false; + playerEntry.DateLastPublished = 0; + playerEntry.Reputation = 0; + playerEntry.PerformanceCost = 0.0f; } - delete playerPropertyLookupResults; - playerPropertyLookupResults = nullptr; - - delete playerPropertyLookup; - playerPropertyLookup = nullptr; - - entries.push_back(playerEntry); - - const auto query = BuildQuery(entity, sortMethod, character); - - auto propertyLookup = Database::Get()->CreatePreppedStmt(query); - - const auto searchString = "%" + filterText + "%"; - propertyLookup->setUInt(1, this->m_MapID); - propertyLookup->setString(2, searchString.c_str()); - propertyLookup->setString(3, searchString.c_str()); - propertyLookup->setString(4, searchString.c_str()); - propertyLookup->setInt(5, sortMethod == SORT_TYPE_FEATURED || sortMethod == SORT_TYPE_FRIENDS ? static_cast(PropertyPrivacyOption::Friends) : static_cast(PropertyPrivacyOption::Public)); - propertyLookup->setInt(6, numResults); - propertyLookup->setInt(7, startIndex); - - auto propertyEntry = propertyLookup->executeQuery(); - - while (propertyEntry->next()) { - const auto propertyId = propertyEntry->getUInt64(1); - const auto owner = propertyEntry->getInt(2); - const auto cloneId = propertyEntry->getUInt64(4); - const auto propertyNameFromDb = std::string(propertyEntry->getString(5).c_str()); - const auto propertyDescriptionFromDb = std::string(propertyEntry->getString(6).c_str()); - const auto privacyOption = propertyEntry->getInt(9); - const auto modApproved = propertyEntry->getBoolean(10); - const auto dateLastUpdated = propertyEntry->getInt(11); - const float reputation = propertyEntry->getInt(14); - const auto performanceCost = propertyEntry->getFloat(16); - - PropertySelectQueryProperty entry{}; - - std::string ownerName = ""; - bool isOwned = true; - auto nameLookup = Database::Get()->CreatePreppedStmt("SELECT name FROM charinfo WHERE prop_clone_id = ?;"); - - nameLookup->setUInt64(1, cloneId); - - auto nameResult = nameLookup->executeQuery(); - - if (!nameResult->next()) { - delete nameLookup; - nameLookup = nullptr; - - LOG("Failed to find property owner name for %llu!", cloneId); - + IProperty::PropertyLookup propertyLookup; + propertyLookup.mapId = m_MapID; + propertyLookup.searchString = filterText; + propertyLookup.sortChoice = static_cast(sortMethod); + propertyLookup.playerSort = static_cast(sortMethod == SORT_TYPE_FEATURED || sortMethod == SORT_TYPE_FRIENDS ? PropertyPrivacyOption::Friends : PropertyPrivacyOption::Public); + propertyLookup.playerId = character->GetID(); + propertyLookup.numResults = numResults; + propertyLookup.startIndex = startIndex; + + const auto lookupResult = Database::Get()->GetProperties(propertyLookup); + + for (const auto& propertyEntry : lookupResult->entries) { + const auto owner = propertyEntry.ownerId; + const auto otherCharacter = Database::Get()->GetCharacterInfo(owner); + if (!otherCharacter.has_value()) { + LOG("Failed to find property owner name for %u!", owner); continue; - } else { - isOwned = cloneId == character->GetPropertyCloneID(); - ownerName = std::string(nameResult->getString(1).c_str()); } - - delete nameResult; - nameResult = nullptr; - - delete nameLookup; - nameLookup = nullptr; - - std::string propertyName = propertyNameFromDb; - std::string propertyDescription = propertyDescriptionFromDb; - - bool isBestFriend = false; - bool isFriend = false; - - // Convert owner char id to LWOOBJID - LWOOBJID ownerObjId = owner; - GeneralUtils::SetBit(ownerObjId, eObjectBits::CHARACTER); - GeneralUtils::SetBit(ownerObjId, eObjectBits::PERSISTENT); - + auto& entry = entries.emplace_back(); + + entry.IsOwned = entry.CloneId == otherCharacter->cloneId; + entry.OwnerName = otherCharacter->name; + entry.CloneId = propertyEntry.cloneId; + entry.Name = propertyEntry.name; + entry.Description = propertyEntry.description; + entry.AccessType = propertyEntry.privacyOption; + entry.IsModeratorApproved = propertyEntry.modApproved; + entry.DateLastPublished = propertyEntry.lastUpdatedTime; + entry.Reputation = propertyEntry.reputation; + entry.PerformanceCost = propertyEntry.performanceCost; + entry.IsBestFriend = false; + entry.IsFriend = false; // Query to get friend and best friend fields - auto friendCheck = Database::Get()->CreatePreppedStmt("SELECT best_friend FROM friends WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?)"); - - friendCheck->setUInt(1, character->GetID()); - friendCheck->setUInt(2, ownerObjId); - friendCheck->setUInt(3, ownerObjId); - friendCheck->setUInt(4, character->GetID()); - - auto friendResult = friendCheck->executeQuery(); - + const auto friendCheck = Database::Get()->GetBestFriendStatus(character->GetID(), owner); // If we got a result than the two players are friends. - if (friendResult->next()) { - isFriend = true; - if (friendResult->getInt(1) == 3) { - isBestFriend = true; - } + if (friendCheck.has_value()) { + entry.IsFriend = true; + entry.IsBestFriend = friendCheck->bestFriendStatus == 3; } - delete friendCheck; - friendCheck = nullptr; - - delete friendResult; - friendResult = nullptr; - - bool isModeratorApproved = propertyEntry->getBoolean(10); - - if (!isModeratorApproved && entity->GetGMLevel() >= eGameMasterLevel::LEAD_MODERATOR) { - propertyName = "[AWAITING APPROVAL]"; - propertyDescription = "[AWAITING APPROVAL]"; - isModeratorApproved = true; + if (!entry.IsModeratorApproved && entity->GetGMLevel() >= eGameMasterLevel::LEAD_MODERATOR) { + entry.Name = "[AWAITING APPROVAL]"; + entry.Description = "[AWAITING APPROVAL]"; + entry.IsModeratorApproved = true; } - bool isAlt = false; // Query to determine whether this property is an alt character of the entity. - auto isAltQuery = Database::Get()->CreatePreppedStmt("SELECT id FROM charinfo where account_id in (SELECT account_id from charinfo WHERE id = ?) AND id = ?;"); - - isAltQuery->setInt(1, character->GetID()); - isAltQuery->setInt(2, owner); - - auto isAltQueryResults = isAltQuery->executeQuery(); - - if (isAltQueryResults->next()) { - isAlt = true; + for (const auto charid : Database::Get()->GetAccountCharacterIds(user->GetAccountID())) { + entry.IsAlt = charid == owner; + if (entry.IsAlt) break; } - - delete isAltQueryResults; - isAltQueryResults = nullptr; - - delete isAltQuery; - isAltQuery = nullptr; - - entry = SetPropertyValues(entry, cloneId, ownerName, propertyName, propertyDescription, reputation, isBestFriend, isFriend, isModeratorApproved, isAlt, isOwned, privacyOption, dateLastUpdated, performanceCost); - - entries.push_back(entry); } - delete propertyEntry; - propertyEntry = nullptr; - - delete propertyLookup; - propertyLookup = nullptr; - - propertyQueries[entity->GetObjectID()] = entries; - // Query here is to figure out whether or not to display the button to go to the next page or not. - int32_t numberOfProperties = 0; - - auto buttonQuery = BuildQuery(entity, sortMethod, character, "SELECT COUNT(*) FROM properties as p JOIN charinfo as ci ON ci.prop_clone_id = p.clone_id where p.zone_id = ? AND (p.description LIKE ? OR p.name LIKE ? OR ci.name LIKE ?) AND p.privacy_option >= ? ", false); - auto propertiesLeft = Database::Get()->CreatePreppedStmt(buttonQuery); - - propertiesLeft->setUInt(1, this->m_MapID); - propertiesLeft->setString(2, searchString.c_str()); - propertiesLeft->setString(3, searchString.c_str()); - propertiesLeft->setString(4, searchString.c_str()); - propertiesLeft->setInt(5, sortMethod == SORT_TYPE_FEATURED || sortMethod == SORT_TYPE_FRIENDS ? 1 : 2); - - auto result = propertiesLeft->executeQuery(); - result->next(); - numberOfProperties = result->getInt(1); - - delete result; - result = nullptr; - - delete propertiesLeft; - propertiesLeft = nullptr; - - GameMessages::SendPropertySelectQuery(m_Parent->GetObjectID(), startIndex, numberOfProperties - (startIndex + numResults) > 0, character->GetPropertyCloneID(), false, true, entries, sysAddr); + GameMessages::SendPropertySelectQuery(m_Parent->GetObjectID(), startIndex, lookupResult->totalEntriesMatchingQuery - (startIndex + numResults) > 0, character->GetPropertyCloneID(), false, true, entries, sysAddr); } diff --git a/dGame/dComponents/PropertyEntranceComponent.h b/dGame/dComponents/PropertyEntranceComponent.h index 510bb4890..c0e76dcb2 100644 --- a/dGame/dComponents/PropertyEntranceComponent.h +++ b/dGame/dComponents/PropertyEntranceComponent.h @@ -57,11 +57,7 @@ class PropertyEntranceComponent final : public Component { * Returns the map ID for this property * @return the map ID for this property */ - [[nodiscard]] LWOMAPID GetMapID() const { return m_MapID; }; - - PropertySelectQueryProperty SetPropertyValues(PropertySelectQueryProperty property, LWOCLONEID cloneId = LWOCLONEID_INVALID, std::string ownerName = "", std::string propertyName = "", std::string propertyDescription = "", float reputation = 0, bool isBFF = false, bool isFriend = false, bool isModeratorApproved = false, bool isAlt = false, bool isOwned = false, uint32_t privacyOption = 0, uint32_t timeLastUpdated = 0, float performanceCost = 0.0f); - - std::string BuildQuery(Entity* entity, int32_t sortMethod, Character* character, std::string customQuery = "", bool wantLimits = true); + [[nodiscard]] LWOMAPID GetMapID() const noexcept { return m_MapID; }; private: /** @@ -78,13 +74,4 @@ class PropertyEntranceComponent final : public Component { * The base map ID for this property (Avant Grove, etc). */ LWOMAPID m_MapID; - - enum ePropertySortType : int32_t { - SORT_TYPE_FRIENDS = 0, - SORT_TYPE_REPUTATION = 1, - SORT_TYPE_RECENT = 3, - SORT_TYPE_FEATURED = 5 - }; - - std::string baseQueryForProperties = "SELECT p.* FROM properties as p JOIN charinfo as ci ON ci.prop_clone_id = p.clone_id where p.zone_id = ? AND (p.description LIKE ? OR p.name LIKE ? OR ci.name LIKE ?) AND p.privacy_option >= ? "; }; diff --git a/dGame/dGameMessages/PropertySelectQueryProperty.cpp b/dGame/dGameMessages/PropertySelectQueryProperty.cpp index 2a2567644..d388a71ae 100644 --- a/dGame/dGameMessages/PropertySelectQueryProperty.cpp +++ b/dGame/dGameMessages/PropertySelectQueryProperty.cpp @@ -29,10 +29,10 @@ void PropertySelectQueryProperty::Serialize(RakNet::BitStream& stream) const { stream.Write(IsOwned); stream.Write(AccessType); stream.Write(DateLastPublished); - stream.Write(PerformanceIndex); stream.Write(PerformanceCost); } void PropertySelectQueryProperty::Deserialize(RakNet::BitStream& stream) const { // Do we need this? + // no } diff --git a/dGame/dGameMessages/PropertySelectQueryProperty.h b/dGame/dGameMessages/PropertySelectQueryProperty.h index 47d795a7f..85d389dd9 100644 --- a/dGame/dGameMessages/PropertySelectQueryProperty.h +++ b/dGame/dGameMessages/PropertySelectQueryProperty.h @@ -5,8 +5,7 @@ #include "Entity.h" -class PropertySelectQueryProperty final -{ +class PropertySelectQueryProperty final { public: void Serialize(RakNet::BitStream& stream) const; @@ -23,9 +22,8 @@ class PropertySelectQueryProperty final bool IsAlt = false; // Whether or not the property is owned by an alt of the account owner bool IsOwned = false; // Whether or not the property is owned uint32_t AccessType = 0; // The privacy option of the property - uint32_t DateLastPublished = 0; // The last day the property was published + uint64_t DateLastPublished = 0; // The last day the property was published float PerformanceCost = 0; // The performance cost of the property - uint32_t PerformanceIndex = 0; // The performance index of the property? Always 0? }; #endif From 9e7ef8c4eea710981624d6ebf094277bfbc56aa2 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Mon, 25 Nov 2024 20:55:50 -0800 Subject: [PATCH 4/6] fix: Player activated switches (#1655) --- dGame/Entity.cpp | 5 ----- dGame/dComponents/PetComponent.cpp | 2 +- dGame/dComponents/SwitchComponent.cpp | 20 ++++++++++++++++++++ dGame/dComponents/SwitchComponent.h | 3 +++ 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index 8066ce619..54629888f 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -1351,11 +1351,6 @@ void Entity::OnCollisionPhantom(const LWOOBJID otherEntity) { callback(other); } - SwitchComponent* switchComp = GetComponent(); - if (switchComp) { - switchComp->EntityEnter(other); - } - TriggerEvent(eTriggerEventType::ENTER, other); // POI system diff --git a/dGame/dComponents/PetComponent.cpp b/dGame/dComponents/PetComponent.cpp index 4e34db258..c2783a15f 100644 --- a/dGame/dComponents/PetComponent.cpp +++ b/dGame/dComponents/PetComponent.cpp @@ -381,7 +381,7 @@ void PetComponent::Update(float deltaTime) { float distance = Vector3::DistanceSquared(position, switchPosition); if (distance < 3 * 3) { m_Interaction = closestSwitch->GetParentEntity()->GetObjectID(); - closestSwitch->EntityEnter(m_Parent); + closestSwitch->OnUse(m_Parent); } else if (distance < 20 * 20) { haltDistance = 1; diff --git a/dGame/dComponents/SwitchComponent.cpp b/dGame/dComponents/SwitchComponent.cpp index e6ad6d001..cb13cc7f5 100644 --- a/dGame/dComponents/SwitchComponent.cpp +++ b/dGame/dComponents/SwitchComponent.cpp @@ -2,6 +2,7 @@ #include "EntityManager.h" #include "eTriggerEventType.h" #include "RenderComponent.h" +#include "DestroyableComponent.h" std::vector SwitchComponent::petSwitches; @@ -11,6 +12,13 @@ SwitchComponent::SwitchComponent(Entity* parent) : Component(parent) { m_ResetTime = m_Parent->GetVarAs(u"switch_reset_time"); m_QuickBuild = m_Parent->GetComponent(); + + const auto factions = GeneralUtils::SplitString(m_Parent->GetVar(u"respond_to_faction"), u':'); + for (const auto& faction : factions) { + auto factionID = GeneralUtils::TryParse(GeneralUtils::UTF16ToWTF8(faction)); + if (!factionID) continue; + m_FactionsToRespondTo.push_back(factionID.value()); + } } SwitchComponent::~SwitchComponent() { @@ -25,6 +33,17 @@ void SwitchComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitial outBitStream.Write(m_Active); } +void SwitchComponent::OnUse(Entity* originator) { + const auto* const destroyableComponent = originator->GetComponent(); + if (!destroyableComponent) return; + for (const auto faction : m_FactionsToRespondTo) { + if (destroyableComponent->HasFaction(faction)) { + EntityEnter(originator); + break; + } + } +} + void SwitchComponent::SetActive(bool active) { m_Active = active; @@ -63,6 +82,7 @@ void SwitchComponent::EntityEnter(Entity* entity) { RenderComponent::PlayAnimation(m_Parent, u"engaged"); m_PetBouncer->SetPetBouncerEnabled(true); } else { + GameMessages::SendKnockback(entity->GetObjectID(), m_Parent->GetObjectID(), m_Parent->GetObjectID(), 0.0f, NiPoint3(0.0f, 17.0f, 0.0f)); Game::entityManager->SerializeEntity(m_Parent); } diff --git a/dGame/dComponents/SwitchComponent.h b/dGame/dComponents/SwitchComponent.h index 862e57191..498194818 100644 --- a/dGame/dComponents/SwitchComponent.h +++ b/dGame/dComponents/SwitchComponent.h @@ -22,6 +22,7 @@ class SwitchComponent final : public Component { ~SwitchComponent() override; void Update(float deltaTime) override; + void OnUse(Entity* originator) override; Entity* GetParentEntity() const; @@ -101,6 +102,8 @@ class SwitchComponent final : public Component { * Attached pet bouncer */ BouncerComponent* m_PetBouncer = nullptr; + + std::vector m_FactionsToRespondTo{}; }; #endif // SWITCHCOMPONENT_H From c37a0c86c173bbfa3f89476810c7319eb6f48951 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Tue, 26 Nov 2024 20:14:07 -0800 Subject: [PATCH 5/6] fix: Old level files not loading (#1656) * emmo help * fix parsing live data --------- Co-authored-by: Aaron Kimbre --- dZoneManager/Level.cpp | 57 +++++++++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/dZoneManager/Level.cpp b/dZoneManager/Level.cpp index 5f35b6290..d2b67b290 100644 --- a/dZoneManager/Level.cpp +++ b/dZoneManager/Level.cpp @@ -131,12 +131,16 @@ void Level::ReadChunks(std::istream& file) { if (initPos == std::streamoff(0)) { //Really old chunk version file.seekg(0); Header header; - header.id = ChunkTypeID::FileInfo; //I guess? + header.id = ChunkTypeID::FileInfo; BinaryIO::BinaryRead(file, header.chunkVersion); BinaryIO::BinaryRead(file, header.chunkType); - file.ignore(1); - BinaryIO::BinaryRead(file, header.fileInfo.revision); - + uint8_t important = 0; + BinaryIO::BinaryRead(file, important); + // file.ignore(1); //probably used + if (header.chunkVersion > 36) { + BinaryIO::BinaryRead(file, header.fileInfo.revision); + } + // HARDCODED 3 if (header.chunkVersion >= 45) file.ignore(4); file.ignore(4 * (4 * 3)); @@ -170,25 +174,32 @@ void Level::ReadChunks(std::istream& file) { } } - for (uint32_t i = 0; i < 6; ++i) { - uint32_t count = 0; - BinaryIO::BinaryRead(file, count); - file.ignore(count); + // skydome info + uint32_t count = 0; + BinaryIO::BinaryRead(file, count); + file.ignore(count); + + if (header.chunkVersion >= 33) { + for (uint32_t i = 0; i < 5; ++i) { + uint32_t count = 0; + BinaryIO::BinaryRead(file, count); + file.ignore(count); + } } + // editor settings + if (!important && header.chunkVersion >= 37){ + file.ignore(4); - file.ignore(4); + uint32_t count = 0; + BinaryIO::BinaryRead(file, count); + file.ignore(count * 12); - uint32_t count = 0; - BinaryIO::BinaryRead(file, count); - file.ignore(count * 12); + } + header.id = ChunkTypeID::SceneObjectData; + header.fileInfo.version = header.chunkVersion; + ReadSceneObjectDataChunk(file, header); m_ChunkHeaders.insert(std::make_pair(header.id, header)); - - //Now pretend to be a normal file and read Objects chunk: - Header hdr; - hdr.id = ChunkTypeID::SceneObjectData; - ReadSceneObjectDataChunk(file, hdr); - m_ChunkHeaders.insert(std::make_pair(hdr.id, hdr)); } break; } } @@ -224,8 +235,14 @@ void Level::ReadSceneObjectDataChunk(std::istream& file, Header& header) { BinaryIO::BinaryRead(file, obj.id); BinaryIO::BinaryRead(file, obj.lot); - /*if (header.fileInfo->version >= 0x26)*/ BinaryIO::BinaryRead(file, obj.nodeType); - /*if (header.fileInfo->version >= 0x20)*/ BinaryIO::BinaryRead(file, obj.glomId); + if (header.fileInfo.version >= 38) { + uint32_t tmp = 1; + BinaryIO::BinaryRead(file, tmp); + if (tmp > -1 && tmp < 11) obj.nodeType = tmp; + } + if (header.fileInfo.version >= 32) { + BinaryIO::BinaryRead(file, obj.glomId); + } BinaryIO::BinaryRead(file, obj.position); BinaryIO::BinaryRead(file, obj.rotation); From 218a3f2d0d1d8135732b87c3f8151399be61b1fe Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Wed, 27 Nov 2024 14:26:10 -0800 Subject: [PATCH 6/6] Update BaseFootRaceManager.cpp (#1657) --- dScripts/ai/ACT/FootRace/BaseFootRaceManager.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/dScripts/ai/ACT/FootRace/BaseFootRaceManager.cpp b/dScripts/ai/ACT/FootRace/BaseFootRaceManager.cpp index c02bf565c..c5954c4c4 100644 --- a/dScripts/ai/ACT/FootRace/BaseFootRaceManager.cpp +++ b/dScripts/ai/ACT/FootRace/BaseFootRaceManager.cpp @@ -12,7 +12,10 @@ void BaseFootRaceManager::OnFireEventServerSide(Entity* self, Entity* sender, st if (splitArguments.size() > 1) { const auto eventName = splitArguments[0]; - const auto player = Game::entityManager->GetEntity(std::stoull(splitArguments[1])); + auto playerId = GeneralUtils::TryParse(splitArguments[1]); + if (!playerId) return; + + const auto player = Game::entityManager->GetEntity(playerId.value()); if (player != nullptr) { if (eventName == "updatePlayer") {