refactor: modular build items and root part come from ModularBuildComponent

ModularBuildFinish hardcoded the item a finished build becomes (6416 for 3
parts, 8092 for 7) and the car chassis part (8129) that the every-part-
swapped check skips. They now come from ModularBuildComponent: createdLOT,
<numberOfParts> and the <ExamplePartLOT> of the <rootPart> module (new
CDModularBuildComponentTable). Same results with the 1.10.64 cdclient;
tests cover the xml parsing and the lookup.

Refs #691

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-09-27 04:29:03 -05:00
parent f882d971ff
commit fec4159ea5
7 changed files with 156 additions and 6 deletions

View File

@@ -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();

View File

@@ -0,0 +1,57 @@
#include "CDModularBuildComponentTable.h"
#include <regex>
#include <string>
namespace {
std::optional<int32_t> 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<uint32_t>(ValueOf(xml, "numberOfParts").value_or(0));
const auto rootPart = ValueOf(xml, "rootPart");
if (!rootPart) return;
// Each <Module> has a PartCode and an ExamplePartLOT
const std::regex module(R"(<Module\b[\s\S]*?</Module>)");
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<CDModularBuildComponent> CDModularBuildComponentTable::FindByNumberOfParts(const std::vector<CDModularBuildComponent>& 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<CDModularBuildComponent> CDModularBuildComponentTable::GetByNumberOfParts(const uint32_t numberOfParts) const {
return FindByNumberOfParts(GetEntries(), numberOfParts);
}

View File

@@ -0,0 +1,27 @@
#pragma once
#include "CDTable.h"
#include "dCommonVars.h"
#include <optional>
#include <string_view>
// 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{}; // <topology><numberOfParts value>
LOT rootPartExampleLOT{ LOT_NULL }; // <Module><ExamplePartLOT> of the module whose PartCode is <rootPart value>
};
class CDModularBuildComponentTable : public CDTable<CDModularBuildComponentTable, std::vector<CDModularBuildComponent>> {
public:
void LoadValuesFromDatabase();
// The first build made of this many parts
std::optional<CDModularBuildComponent> 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<CDModularBuildComponent> FindByNumberOfParts(const std::vector<CDModularBuildComponent>& builds, uint32_t numberOfParts);
};

View File

@@ -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"