diff --git a/dDatabase/CDClientDatabase/CDClientManager.cpp b/dDatabase/CDClientDatabase/CDClientManager.cpp index 0e05c0b8..6ecfb0ad 100644 --- a/dDatabase/CDClientDatabase/CDClientManager.cpp +++ b/dDatabase/CDClientDatabase/CDClientManager.cpp @@ -25,6 +25,7 @@ #include "CDScriptComponentTable.h" #include "CDSkillBehaviorTable.h" #include "CDZoneTableTable.h" +#include "CDTamingBuildPuzzleTable.h" #include "CDVendorComponentTable.h" #include "CDActivitiesTable.h" #include "CDPackageComponentTable.h" @@ -41,8 +42,6 @@ #include "CDRewardCodesTable.h" #include "CDPetComponentTable.h" -#include - #ifndef CDCLIENT_CACHE_ALL // Uncomment this to cache the full cdclient database into memory. This will make the server load faster, but will use more memory. // A vanilla CDClient takes about 46MB of memory + the regular world data. @@ -55,13 +54,6 @@ #define CDCLIENT_DONT_CACHE_TABLE(x) #endif -class CDClientConnectionException : public std::exception { -public: - virtual const char* what() const throw() { - return "CDClientDatabase is not connected!"; - } -}; - // Using a macro to reduce repetitive code and issues from copy and paste. // As a note, ## in a macro is used to concatenate two tokens together. @@ -108,11 +100,14 @@ DEFINE_TABLE_STORAGE(CDRewardCodesTable); DEFINE_TABLE_STORAGE(CDRewardsTable); DEFINE_TABLE_STORAGE(CDScriptComponentTable); DEFINE_TABLE_STORAGE(CDSkillBehaviorTable); +DEFINE_TABLE_STORAGE(CDTamingBuildPuzzleTable); DEFINE_TABLE_STORAGE(CDVendorComponentTable); DEFINE_TABLE_STORAGE(CDZoneTableTable); void CDClientManager::LoadValuesFromDatabase() { - if (!CDClientDatabase::isConnected) throw CDClientConnectionException(); + if (!CDClientDatabase::isConnected) { + throw std::runtime_error{ "CDClientDatabase is not connected!" }; + } CDActivityRewardsTable::Instance().LoadValuesFromDatabase(); CDActivitiesTable::Instance().LoadValuesFromDatabase(); @@ -152,6 +147,7 @@ void CDClientManager::LoadValuesFromDatabase() { CDRewardsTable::Instance().LoadValuesFromDatabase(); CDScriptComponentTable::Instance().LoadValuesFromDatabase(); CDSkillBehaviorTable::Instance().LoadValuesFromDatabase(); + CDTamingBuildPuzzleTable::Instance().LoadValuesFromDatabase(); CDVendorComponentTable::Instance().LoadValuesFromDatabase(); CDZoneTableTable::Instance().LoadValuesFromDatabase(); } diff --git a/dDatabase/CDClientDatabase/CDClientTables/CDPetComponentTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDPetComponentTable.cpp index f3371ecb..80c10112 100644 --- a/dDatabase/CDClientDatabase/CDClientTables/CDPetComponentTable.cpp +++ b/dDatabase/CDClientDatabase/CDClientTables/CDPetComponentTable.cpp @@ -50,7 +50,7 @@ void CDPetComponentTable::LoadValuesFromDatabase() { } void CDPetComponentTable::LoadValuesFromDefaults() { - GetEntriesMutable().insert(std::make_pair(defaultEntry.id, defaultEntry)); + GetEntriesMutable().emplace(defaultEntry.id, defaultEntry); } CDPetComponent& CDPetComponentTable::GetByID(const uint32_t componentID) { diff --git a/dDatabase/CDClientDatabase/CDClientTables/CDTamingBuildPuzzleTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDTamingBuildPuzzleTable.cpp new file mode 100644 index 00000000..c2301b33 --- /dev/null +++ b/dDatabase/CDClientDatabase/CDClientTables/CDTamingBuildPuzzleTable.cpp @@ -0,0 +1,35 @@ +#include "CDTamingBuildPuzzleTable.h" + +void CDTamingBuildPuzzleTable::LoadValuesFromDatabase() { + // First, get the size of the table + uint32_t size = 0; + auto tableSize = CDClientDatabase::ExecuteQuery("SELECT COUNT(*) FROM TamingBuildPuzzles"); + while (!tableSize.eof()) { + size = tableSize.getIntField(0, 0); + tableSize.nextRow(); + } + + // Reserve the size + auto& entries = GetEntriesMutable(); + entries.reserve(size); + + // Now get the data + auto tableData = CDClientDatabase::ExecuteQuery("SELECT * FROM TamingBuildPuzzles"); + while (!tableData.eof()) { + const auto lot = static_cast(tableData.getIntField("NPCLot", LOT_NULL)); + entries.emplace(lot, CDTamingBuildPuzzle{ + .puzzleModelLot = lot, + .validPieces{ tableData.getStringField("ValidPiecesLXF") }, + .timeLimit = static_cast(tableData.getFloatField("Timelimit", 30.0f)), + .numValidPieces = tableData.getIntField("NumValidPieces", 6), + .imaginationCost = tableData.getIntField("imagCostPerBuild", 10) + }); + tableData.nextRow(); + } +} + +const CDTamingBuildPuzzle* CDTamingBuildPuzzleTable::GetByLOT(const LOT lot) const { + const auto& entries = GetEntries(); + const auto itr = entries.find(lot); + return itr != entries.cend() ? &itr->second : nullptr; +} diff --git a/dDatabase/CDClientDatabase/CDClientTables/CDTamingBuildPuzzleTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDTamingBuildPuzzleTable.h new file mode 100644 index 00000000..acbd65bf --- /dev/null +++ b/dDatabase/CDClientDatabase/CDClientTables/CDTamingBuildPuzzleTable.h @@ -0,0 +1,60 @@ +#pragma once +#include "CDTable.h" + +/** + * Information for the minigame to be completed + */ +struct CDTamingBuildPuzzle { + UNUSED_COLUMN(uint32_t id = 0;) + + // The LOT of the object that is to be created + LOT puzzleModelLot = LOT_NULL; + + // The LOT of the NPC + UNUSED_COLUMN(LOT npcLot = LOT_NULL;) + + // The .lxfml file that contains the bricks required to build the model + std::string validPieces{}; + + // The .lxfml file that contains the bricks NOT required to build the model + UNUSED_COLUMN(std::string invalidPieces{};) + + // Difficulty value + UNUSED_COLUMN(int32_t difficulty = 1;) + + // The time limit to complete the build + float timeLimit = 30.0f; + + // The number of pieces required to complete the minigame + int32_t numValidPieces = 6; + + // Number of valid pieces + UNUSED_COLUMN(int32_t totalNumPieces = 16;) + + // Model name + UNUSED_COLUMN(std::string modelName{};) + + // The .lxfml file that contains the full model + UNUSED_COLUMN(std::string fullModel{};) + + // The duration of the pet taming minigame + UNUSED_COLUMN(float duration = 45.0f;) + + // The imagination cost for the tamer to start the minigame + int32_t imaginationCost = 10; +}; + +class CDTamingBuildPuzzleTable : public CDTable> { +public: + /** + * Load values from the CD client database + */ + void LoadValuesFromDatabase(); + + /** + * Gets the pet ability table corresponding to the pet LOT + * @returns A pointer to the corresponding table, or nullptr if one cannot be found + */ + [[nodiscard]] + const CDTamingBuildPuzzle* GetByLOT(const LOT lot) const; +}; diff --git a/dDatabase/CDClientDatabase/CDClientTables/CMakeLists.txt b/dDatabase/CDClientDatabase/CDClientTables/CMakeLists.txt index af401db2..f4551646 100644 --- a/dDatabase/CDClientDatabase/CDClientTables/CMakeLists.txt +++ b/dDatabase/CDClientDatabase/CDClientTables/CMakeLists.txt @@ -36,5 +36,6 @@ set(DDATABASE_CDCLIENTDATABASE_CDCLIENTTABLES_SOURCES "CDActivitiesTable.cpp" "CDRewardsTable.cpp" "CDScriptComponentTable.cpp" "CDSkillBehaviorTable.cpp" + "CDTamingBuildPuzzleTable.cpp" "CDVendorComponentTable.cpp" "CDZoneTableTable.cpp" PARENT_SCOPE) diff --git a/dGame/dComponents/PetComponent.cpp b/dGame/dComponents/PetComponent.cpp index 68c29a6d..debe0bd8 100644 --- a/dGame/dComponents/PetComponent.cpp +++ b/dGame/dComponents/PetComponent.cpp @@ -2,6 +2,7 @@ #include "GameMessages.h" #include "BrickDatabase.h" #include "CDClientDatabase.h" +#include "CDTamingBuildPuzzleTable.h" #include "ChatPackets.h" #include "EntityManager.h" #include "Character.h" @@ -32,7 +33,6 @@ #include "eMissionState.h" #include "dNavMesh.h" -std::unordered_map PetComponent::buildCache{}; std::unordered_map PetComponent::currentActivities{}; std::unordered_map PetComponent::activePets{}; @@ -151,8 +151,7 @@ void PetComponent::OnUse(Entity* originator) { m_Tamer = LWOOBJID_EMPTY; } - auto* inventoryComponent = originator->GetComponent(); - + auto* const inventoryComponent = originator->GetComponent(); if (inventoryComponent == nullptr) { return; } @@ -161,86 +160,44 @@ void PetComponent::OnUse(Entity* originator) { return; } - auto* movementAIComponent = m_Parent->GetComponent(); - + auto* const movementAIComponent = m_Parent->GetComponent(); if (movementAIComponent != nullptr) { movementAIComponent->Stop(); } inventoryComponent->DespawnPet(); - const auto& cached = buildCache.find(m_Parent->GetLOT()); - int32_t imaginationCost = 0; - - std::string buildFile; - - if (cached == buildCache.end()) { - auto query = CDClientDatabase::CreatePreppedStmt( - "SELECT ValidPiecesLXF, PuzzleModelLot, Timelimit, NumValidPieces, imagCostPerBuild FROM TamingBuildPuzzles WHERE NPCLot = ?;"); - query.bind(1, static_cast(m_Parent->GetLOT())); - - auto result = query.execQuery(); - - if (result.eof()) { - ChatPackets::SendSystemMessage(originator->GetSystemAddress(), u"Failed to find the puzzle minigame for this pet."); - - return; - } - - if (result.fieldIsNull("ValidPiecesLXF")) { - result.finalize(); - - return; - } - - buildFile = std::string(result.getStringField("ValidPiecesLXF")); - - PetPuzzleData data; - data.buildFile = buildFile; - data.puzzleModelLot = result.getIntField("PuzzleModelLot"); - data.timeLimit = result.getFloatField("Timelimit"); - data.numValidPieces = result.getIntField("NumValidPieces"); - data.imaginationCost = result.getIntField("imagCostPerBuild"); - if (data.timeLimit <= 0) data.timeLimit = 60; - imaginationCost = data.imaginationCost; - - buildCache[m_Parent->GetLOT()] = data; - - result.finalize(); - } else { - buildFile = cached->second.buildFile; - imaginationCost = cached->second.imaginationCost; + const auto* const entry = CDClientManager::GetTable()->GetByLOT(m_Parent->GetLOT()); + if (!entry) { + ChatPackets::SendSystemMessage(originator->GetSystemAddress(), u"Failed to find the puzzle minigame for this pet."); + return; } - auto* destroyableComponent = originator->GetComponent(); - + const auto* const destroyableComponent = originator->GetComponent(); if (destroyableComponent == nullptr) { return; } - auto imagination = destroyableComponent->GetImagination(); - - if (imagination < imaginationCost) { + const auto imagination = destroyableComponent->GetImagination(); + if (imagination < entry->imaginationCost) { return; } - const auto& bricks = BrickDatabase::GetBricks(buildFile); - + const auto& bricks = BrickDatabase::GetBricks(entry->validPieces); if (bricks.empty()) { ChatPackets::SendSystemMessage(originator->GetSystemAddress(), u"Failed to load the puzzle minigame for this pet."); - LOG("Couldn't find %s for minigame!", buildFile.c_str()); + LOG("Couldn't find %s for minigame!", entry->validPieces.c_str()); return; } - auto petPosition = m_Parent->GetPosition(); + const auto petPosition = m_Parent->GetPosition(); - auto originatorPosition = originator->GetPosition(); + const auto originatorPosition = originator->GetPosition(); m_Parent->SetRotation(NiQuaternion::LookAt(petPosition, originatorPosition)); float interactionDistance = m_Parent->GetVar(u"interaction_distance"); - if (interactionDistance <= 0) { interactionDistance = 15; } @@ -476,9 +433,8 @@ void PetComponent::TryBuild(uint32_t numBricks, bool clientFailed) { return; } - const auto& cached = buildCache.find(m_Parent->GetLOT()); - - if (cached == buildCache.end()) return; + const auto* const entry = CDClientManager::GetTable()->GetByLOT(m_Parent->GetLOT()); + if (!entry) return; auto* destroyableComponent = tamer->GetComponent(); @@ -486,14 +442,14 @@ void PetComponent::TryBuild(uint32_t numBricks, bool clientFailed) { auto imagination = destroyableComponent->GetImagination(); - imagination -= cached->second.imaginationCost; + imagination -= entry->imaginationCost; destroyableComponent->SetImagination(imagination); Game::entityManager->SerializeEntity(tamer); if (clientFailed) { - if (imagination < cached->second.imaginationCost) { + if (imagination < entry->imaginationCost) { ClientFailTamingMinigame(); } } else { @@ -516,17 +472,14 @@ void PetComponent::NotifyTamingBuildSuccess(NiPoint3 position) { return; } - const auto& cached = buildCache.find(m_Parent->GetLOT()); - - if (cached == buildCache.end()) { - return; - } + const auto* const entry = CDClientManager::GetTable()->GetByLOT(m_Parent->GetLOT()); + if (!entry) return; GameMessages::SendPlayFXEffect(tamer, -1, u"petceleb", "", LWOOBJID_EMPTY, 1, 1, true); RenderComponent::PlayAnimation(tamer, u"rebuild-celebrate"); EntityInfo info{}; - info.lot = cached->second.puzzleModelLot; + info.lot = entry->puzzleModelLot; info.pos = position; info.rot = NiQuaternionConstant::IDENTITY; info.spawnerID = tamer->GetObjectID(); @@ -730,13 +683,10 @@ void PetComponent::ClientExitTamingMinigame(bool voluntaryExit) { } void PetComponent::StartTimer() { - const auto& cached = buildCache.find(m_Parent->GetLOT()); + const auto* const entry = CDClientManager::GetTable()->GetByLOT(m_Parent->GetLOT()); + if (!entry) return; - if (cached == buildCache.end()) { - return; - } - - m_Timer = cached->second.timeLimit; + m_Timer = entry->timeLimit; } void PetComponent::ClientFailTamingMinigame() { diff --git a/dGame/dComponents/PetComponent.h b/dGame/dComponents/PetComponent.h index 1abdf155..323330d2 100644 --- a/dGame/dComponents/PetComponent.h +++ b/dGame/dComponents/PetComponent.h @@ -250,11 +250,6 @@ private: */ static std::unordered_map currentActivities; - /** - * Cache of all the minigames and their information from the database - */ - static std::unordered_map buildCache; - /** * Flags that indicate that a player has tamed a pet, indexed by the LOT of the pet */