diff --git a/dDatabase/CDClientDatabase/CDClientManager.cpp b/dDatabase/CDClientDatabase/CDClientManager.cpp index 9aea07110..680a792f5 100644 --- a/dDatabase/CDClientDatabase/CDClientManager.cpp +++ b/dDatabase/CDClientDatabase/CDClientManager.cpp @@ -37,6 +37,7 @@ #include "CDRewardsTable.h" #include "CDPropertyEntranceComponentTable.h" #include "CDPropertyTemplateTable.h" +#include "CDModularBuildComponentTable.h" #include "CDFeatureGatingTable.h" #include "CDRailActivatorComponent.h" #include "CDRewardCodesTable.h" @@ -93,6 +94,7 @@ DEFINE_TABLE_STORAGE(CDPetComponentTable); DEFINE_TABLE_STORAGE(CDProximityMonitorComponentTable); DEFINE_TABLE_STORAGE(CDPropertyEntranceComponentTable); DEFINE_TABLE_STORAGE(CDPropertyTemplateTable); +DEFINE_TABLE_STORAGE(CDModularBuildComponentTable); DEFINE_TABLE_STORAGE(CDRailActivatorComponentTable); DEFINE_TABLE_STORAGE(CDRarityTableTable); DEFINE_TABLE_STORAGE(CDRebuildComponentTable); @@ -139,6 +141,7 @@ void CDClientManager::LoadValuesFromDatabase() { CDProximityMonitorComponentTable::Instance().LoadValuesFromDatabase(); CDPropertyEntranceComponentTable::Instance().LoadValuesFromDatabase(); CDPropertyTemplateTable::Instance().LoadValuesFromDatabase(); + CDModularBuildComponentTable::Instance().LoadValuesFromDatabase(); CDRailActivatorComponentTable::Instance().LoadValuesFromDatabase(); CDRarityTableTable::Instance().LoadValuesFromDatabase(); CDRebuildComponentTable::Instance().LoadValuesFromDatabase(); diff --git a/dDatabase/CDClientDatabase/CDClientTables/CDModularBuildComponentTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDModularBuildComponentTable.cpp new file mode 100644 index 000000000..5e8c84820 --- /dev/null +++ b/dDatabase/CDClientDatabase/CDClientTables/CDModularBuildComponentTable.cpp @@ -0,0 +1,57 @@ +#include "CDModularBuildComponentTable.h" + +#include +#include + +namespace { + std::optional ValueOf(const std::string& xml, const std::string& element) { + const std::regex pattern("<" + element + R"re(\s+value\s*=\s*"(-?\d+)")re"); + std::smatch match; + if (!std::regex_search(xml, match, pattern)) return std::nullopt; + return std::stoi(match[1].str()); + } +} + +void CDModularBuildComponentTable::ParseXml(const std::string_view xmlView, CDModularBuildComponent& build) { + const std::string xml(xmlView); + build.numberOfParts = static_cast(ValueOf(xml, "numberOfParts").value_or(0)); + + const auto rootPart = ValueOf(xml, "rootPart"); + if (!rootPart) return; + + // Each has a PartCode and an ExamplePartLOT + const std::regex module(R"()"); + for (auto it = std::sregex_iterator(xml.begin(), xml.end(), module); it != std::sregex_iterator(); ++it) { + const auto text = it->str(); + if (ValueOf(text, "PartCode") == rootPart) { + build.rootPartExampleLOT = ValueOf(text, "ExamplePartLOT").value_or(LOT_NULL); + return; + } + } +} + +void CDModularBuildComponentTable::LoadValuesFromDatabase() { + auto& entries = GetEntriesMutable(); + auto tableData = CDClientDatabase::ExecuteQuery("SELECT * FROM ModularBuildComponent;"); + while (!tableData.eof()) { + CDModularBuildComponent entry; + entry.id = tableData.getIntField("id", 0); + entry.buildType = tableData.getIntField("buildType", 0); + entry.createdLOT = tableData.getIntField("createdLOT", LOT_NULL); + ParseXml(tableData.getStringField("xml", ""), entry); + entries.push_back(entry); + tableData.nextRow(); + } + tableData.finalize(); +} + +std::optional CDModularBuildComponentTable::FindByNumberOfParts(const std::vector& builds, const uint32_t numberOfParts) { + for (const auto& build : builds) { + if (build.numberOfParts == numberOfParts && build.createdLOT != LOT_NULL) return build; + } + return std::nullopt; +} + +std::optional CDModularBuildComponentTable::GetByNumberOfParts(const uint32_t numberOfParts) const { + return FindByNumberOfParts(GetEntries(), numberOfParts); +} diff --git a/dDatabase/CDClientDatabase/CDClientTables/CDModularBuildComponentTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDModularBuildComponentTable.h new file mode 100644 index 000000000..947b5f4ff --- /dev/null +++ b/dDatabase/CDClientDatabase/CDClientTables/CDModularBuildComponentTable.h @@ -0,0 +1,27 @@ +#pragma once +#include "CDTable.h" +#include "dCommonVars.h" + +#include +#include + +// A modular build (rocket, car): ModularBuildComponent with what DLU needs from its xml +struct CDModularBuildComponent { + uint32_t id{}; + int32_t buildType{}; + LOT createdLOT{ LOT_NULL }; // the item the finished build becomes + uint32_t numberOfParts{}; // + LOT rootPartExampleLOT{ LOT_NULL }; // of the module whose PartCode is +}; + +class CDModularBuildComponentTable : public CDTable> { +public: + void LoadValuesFromDatabase(); + + // The first build made of this many parts + std::optional GetByNumberOfParts(uint32_t numberOfParts) const; + + // Fills numberOfParts and rootPartExampleLOT from a ModularBuildComponent.xml + static void ParseXml(std::string_view xml, CDModularBuildComponent& build); + static std::optional FindByNumberOfParts(const std::vector& builds, uint32_t numberOfParts); +}; diff --git a/dDatabase/CDClientDatabase/CDClientTables/CMakeLists.txt b/dDatabase/CDClientDatabase/CDClientTables/CMakeLists.txt index 57731ad93..a48ee30f8 100644 --- a/dDatabase/CDClientDatabase/CDClientTables/CMakeLists.txt +++ b/dDatabase/CDClientDatabase/CDClientTables/CMakeLists.txt @@ -29,6 +29,7 @@ set(DDATABASE_CDCLIENTDATABASE_CDCLIENTTABLES_SOURCES "CDActivitiesTable.cpp" "CDPhysicsComponentTable.cpp" "CDPropertyEntranceComponentTable.cpp" "CDPropertyTemplateTable.cpp" + "CDModularBuildComponentTable.cpp" "CDProximityMonitorComponentTable.cpp" "CDRailActivatorComponent.cpp" "CDRarityTableTable.cpp" diff --git a/dGame/dGameMessages/BuildingMessages.cpp b/dGame/dGameMessages/BuildingMessages.cpp index 40cb7f280..19eea22fd 100644 --- a/dGame/dGameMessages/BuildingMessages.cpp +++ b/dGame/dGameMessages/BuildingMessages.cpp @@ -30,6 +30,8 @@ #include "Sd0.h" #include "ScriptComponent.h" #include "BrickByBrick.h" +#include "CDClientManager.h" +#include "CDModularBuildComponentTable.h" #include "User.h" #include "UserManager.h" @@ -290,6 +292,8 @@ namespace GameMessages { auto* temp = inv->GetInventory(TEMP_MODELS); std::vector modList; + // The build this many parts make (3: a rocket, 7: a car), from ModularBuildComponent + const auto build = CDClientManager::GetTable()->GetByNumberOfParts(static_cast(count)); auto& oldPartList = character->GetVar(u"currentModifiedBuild"); bool everyPieceSwapped = !oldPartList.empty(); // If the player didn't put a build in initially, then they should not get this achievement. if (count >= 3 && count < 8) { @@ -316,8 +320,8 @@ namespace GameMessages { } // Doing this check for 1 singular mission that needs to know when you've swapped every part out during a car modular build. - // since all 8129's are the same, skip checking that - if (mod != 8129) { + // Every build has the same root part (the car's chassis), so it is not checked + if (!build || mod != build->rootPartExampleLOT) { if (oldPartList.find(GeneralUtils::UTF16ToWTF8(modToStr)) != std::string::npos) everyPieceSwapped = false; } @@ -331,10 +335,8 @@ namespace GameMessages { LWOOBJID newID = ObjectIDManager::GetPersistentID(); - if (count == 3) { - inv->AddItem(6416, 1, eLootSourceType::QUICKBUILD, eInventoryType::MODELS, config, LWOOBJID_EMPTY, true, false, newID); - } else if (count == 7) { - inv->AddItem(8092, 1, eLootSourceType::QUICKBUILD, eInventoryType::MODELS, config, LWOOBJID_EMPTY, true, false, newID); + if (build) { + inv->AddItem(build->createdLOT, 1, eLootSourceType::QUICKBUILD, eInventoryType::MODELS, config, LWOOBJID_EMPTY, true, false, newID); } auto* pCharacter = character->GetCharacter(); diff --git a/tests/dGameTests/CMakeLists.txt b/tests/dGameTests/CMakeLists.txt index 610b2d816..ff6fd5316 100644 --- a/tests/dGameTests/CMakeLists.txt +++ b/tests/dGameTests/CMakeLists.txt @@ -12,6 +12,7 @@ set(DGAMETEST_SOURCES "ItemSetPassiveAbilityTests.cpp" "PowerUpStatisticTests.cpp" "BrickByBrickTests.cpp" + "ModularBuildTableTests.cpp" ) add_subdirectory(dComponentsTests) diff --git a/tests/dGameTests/ModularBuildTableTests.cpp b/tests/dGameTests/ModularBuildTableTests.cpp new file mode 100644 index 000000000..966730137 --- /dev/null +++ b/tests/dGameTests/ModularBuildTableTests.cpp @@ -0,0 +1,59 @@ +#include "CDModularBuildComponentTable.h" + +#include + +// ModularBuildComponent's xml, laid out as the cdclient has it (a car: root part 0; a rocket: root part 2) +namespace { + constexpr std::string_view CAR_XML = R"( + + + + + + + + + + + + + + + +)"; + + constexpr std::string_view ROCKET_XML = "\r\n \r\n" + " \r\n\t\t\r\n\t\t\r\n \r\n" + "\t\t\r\n\t\t\t \r\n\t\t\t \r\n\t\t\r\n" + "\t\t\r\n\t\t\t \r\n\t\t\t \r\n\t\t\r\n"; +} + +TEST(ModularBuildTableTests, ReadsPartsAndRootPartFromTheXml) { + CDModularBuildComponent car; + CDModularBuildComponentTable::ParseXml(CAR_XML, car); + EXPECT_EQ(car.numberOfParts, 7u); + EXPECT_EQ(car.rootPartExampleLOT, 8129); + + CDModularBuildComponent rocket; + CDModularBuildComponentTable::ParseXml(ROCKET_XML, rocket); + EXPECT_EQ(rocket.numberOfParts, 3u); + EXPECT_EQ(rocket.rootPartExampleLOT, 4715); + + CDModularBuildComponent empty; + CDModularBuildComponentTable::ParseXml("", empty); + EXPECT_EQ(empty.numberOfParts, 0u); + EXPECT_EQ(empty.rootPartExampleLOT, LOT_NULL); +} + +TEST(ModularBuildTableTests, FindsTheBuildByItsNumberOfParts) { + const std::vector builds = { + { .id = 5, .buildType = 6, .createdLOT = 8092, .numberOfParts = 7, .rootPartExampleLOT = 8129 }, + { .id = 6, .buildType = 3, .createdLOT = 6416, .numberOfParts = 3, .rootPartExampleLOT = 4715 }, + { .id = 3, .buildType = 3, .createdLOT = 6416, .numberOfParts = 3, .rootPartExampleLOT = 4715 }, + }; + EXPECT_EQ(CDModularBuildComponentTable::FindByNumberOfParts(builds, 7)->createdLOT, 8092); + EXPECT_EQ(CDModularBuildComponentTable::FindByNumberOfParts(builds, 3)->createdLOT, 6416); + for (const uint32_t parts : { 0u, 4u, 5u, 6u, 8u }) { + EXPECT_FALSE(CDModularBuildComponentTable::FindByNumberOfParts(builds, parts).has_value()); + } +}