mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-22 12:42:49 +00:00
feat(ugc): migration gives old cars and rockets a build id
Cars and rockets built before builds were recorded were saved with subkey 0 and have no ugc_modular_build row, so the client has no blueprint id to ask for their icon with. The runtime fix only reaches characters that load again. Migrations mysql 98 and sqlite 81 (98/81 because the guilds work takes 97/80) run ModularBuildIdMigration after the SQL: every saved item with modules (x@ma) and no subkey gets what a new build gets, as the character-load fix does: a persistent id from object_id_tracker (with the character bit) as its subkey and a ugc_modular_build row with its modules and the character as owner. The XML is written with UpdateCharacterXml. Tried on a copy of a server's database with a couple of thousand such items: every one got an id and a build row, in seconds. Check: after the migration, old cars and rockets show their icons in the backpack (the UGC server makes them like any build) and still work (equip, race, launch). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
add_subdirectory(CDClientDatabase)
|
||||
add_subdirectory(GameDatabase)
|
||||
|
||||
add_library(dDatabase STATIC "MigrationRunner.cpp" "ModelNormalizeMigration.cpp" "ConfigSync.cpp")
|
||||
add_library(dDatabase STATIC "MigrationRunner.cpp" "ModelNormalizeMigration.cpp" "ModularBuildIdMigration.cpp" "ConfigSync.cpp")
|
||||
|
||||
add_custom_target(conncpp_dylib
|
||||
${CMAKE_COMMAND} -E copy $<TARGET_FILE:MariaDB::ConnCpp> ${PROJECT_BINARY_DIR})
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "Logger.h"
|
||||
#include "BinaryPathFinder.h"
|
||||
#include "ModelNormalizeMigration.h"
|
||||
#include "ModularBuildIdMigration.h"
|
||||
|
||||
#include <fstream>
|
||||
|
||||
@@ -52,6 +53,7 @@ void MigrationRunner::RunMigrations() {
|
||||
bool runNormalizeMigrations = false;
|
||||
bool runNormalizeAfterFirstPartMigrations = false;
|
||||
bool runBrickBuildsNotOnGrid = false;
|
||||
bool runModularBuildIds = false;
|
||||
for (const auto& entry : GeneralUtils::GetSqlFileNamesFromFolder((BinaryPathFinder::GetBinaryDir() / "./migrations/dlu/" / migrationFolder).string())) {
|
||||
auto migration = LoadMigration("dlu/" + migrationFolder + "/", entry);
|
||||
|
||||
@@ -70,6 +72,8 @@ void MigrationRunner::RunMigrations() {
|
||||
runNormalizeAfterFirstPartMigrations = true;
|
||||
} else if (migration.name.ends_with("_brickbuilds_not_on_grid.sql")) {
|
||||
runBrickBuildsNotOnGrid = true;
|
||||
} else if (migration.name.ends_with("_modular_build_ids.sql")) {
|
||||
runModularBuildIds = true;
|
||||
} else {
|
||||
finalSQL.append(migration.data.c_str());
|
||||
}
|
||||
@@ -77,7 +81,7 @@ void MigrationRunner::RunMigrations() {
|
||||
Database::Get()->InsertMigration(migration.name);
|
||||
}
|
||||
|
||||
if (finalSQL.empty() && !runSd0Migrations && !runNormalizeMigrations && !runNormalizeAfterFirstPartMigrations && !runBrickBuildsNotOnGrid) {
|
||||
if (finalSQL.empty() && !runSd0Migrations && !runNormalizeMigrations && !runNormalizeAfterFirstPartMigrations && !runBrickBuildsNotOnGrid && !runModularBuildIds) {
|
||||
LOG("Server database is up to date.");
|
||||
return;
|
||||
}
|
||||
@@ -113,6 +117,11 @@ void MigrationRunner::RunMigrations() {
|
||||
if (runBrickBuildsNotOnGrid) {
|
||||
ModelNormalizeMigration::RunBrickBuildGrid();
|
||||
}
|
||||
|
||||
// After the SQL, which makes ugc_modular_build and object_id_tracker on a new database
|
||||
if (runModularBuildIds) {
|
||||
ModularBuildIdMigration::Run();
|
||||
}
|
||||
}
|
||||
|
||||
void MigrationRunner::RunSQLiteMigrations() {
|
||||
|
||||
74
dDatabase/ModularBuildIdMigration.cpp
Normal file
74
dDatabase/ModularBuildIdMigration.cpp
Normal file
@@ -0,0 +1,74 @@
|
||||
#include "ModularBuildIdMigration.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "tinyxml2.h"
|
||||
|
||||
#include "Database.h"
|
||||
#include "eObjectBits.h"
|
||||
#include "GeneralUtils.h"
|
||||
#include "Logger.h"
|
||||
|
||||
namespace {
|
||||
// A persistent id, as ObjectIDManager::GetPersistentID hands them out: from a range reserved in object_id_tracker,
|
||||
// with the CHARACTER bit
|
||||
class PersistentIds {
|
||||
public:
|
||||
LWOOBJID Next() {
|
||||
if (!m_Range || m_Range->minID > m_Range->maxID) m_Range = Database::Get()->GetPersistentIdRange();
|
||||
LWOOBJID id = m_Range->minID++;
|
||||
GeneralUtils::SetBit(id, eObjectBits::CHARACTER);
|
||||
return id;
|
||||
}
|
||||
private:
|
||||
std::optional<IObjectIdTracker::Range> m_Range;
|
||||
};
|
||||
}
|
||||
|
||||
std::vector<ModularBuildIdMigration::NewBuild> ModularBuildIdMigration::AssignIds(tinyxml2::XMLDocument& document, const std::function<LWOOBJID()>& nextId) {
|
||||
std::vector<NewBuild> builds;
|
||||
auto* obj = document.FirstChildElement("obj");
|
||||
auto* inv = obj ? obj->FirstChildElement("inv") : nullptr;
|
||||
auto* items = inv ? inv->FirstChildElement("items") : nullptr;
|
||||
for (auto* bag = items ? items->FirstChildElement("in") : nullptr; bag; bag = bag->NextSiblingElement("in")) {
|
||||
for (auto* item = bag->FirstChildElement("i"); item; item = item->NextSiblingElement("i")) {
|
||||
if (item->Int64Attribute("sk", 0) != 0) continue;
|
||||
const auto* config = item->FirstChildElement("x");
|
||||
const char* saved = config ? config->Attribute("ma") : nullptr;
|
||||
if (!saved) continue;
|
||||
// Saved as the LDF type and value ("0:1:8129+1:8130"); the build row keeps the value
|
||||
std::string modules(saved);
|
||||
if (const auto colon = modules.find(':'); colon != std::string::npos && GeneralUtils::TryParse<int32_t>(modules.substr(0, colon))) modules = modules.substr(colon + 1);
|
||||
if (modules.empty()) continue;
|
||||
const auto id = nextId();
|
||||
item->SetAttribute("sk", id);
|
||||
builds.push_back({ id, std::move(modules) });
|
||||
}
|
||||
}
|
||||
return builds;
|
||||
}
|
||||
|
||||
void ModularBuildIdMigration::Run() {
|
||||
std::vector<LWOOBJID> characters;
|
||||
Database::Get()->ForEachCharacterXmlContaining("ma=\"", [&characters](LWOOBJID id, const std::string&) { characters.push_back(id); });
|
||||
|
||||
PersistentIds ids;
|
||||
uint32_t builds = 0, changed = 0;
|
||||
for (const auto characterId : characters) {
|
||||
const auto xml = Database::Get()->GetCharacterXml(characterId);
|
||||
tinyxml2::XMLDocument document;
|
||||
if (xml.empty() || document.Parse(xml.c_str(), xml.size()) != tinyxml2::XML_SUCCESS) {
|
||||
LOG("Character %llu: its saved XML can't be read, its cars and rockets keep no build id", characterId);
|
||||
continue;
|
||||
}
|
||||
const auto made = AssignIds(document, [&ids] { return ids.Next(); });
|
||||
if (made.empty()) continue;
|
||||
for (const auto& build : made) Database::Get()->InsertUgcBuild(build.modules, build.id, characterId);
|
||||
tinyxml2::XMLPrinter printer(0, true, 0);
|
||||
document.Print(&printer);
|
||||
Database::Get()->UpdateCharacterXml(characterId, printer.CStr());
|
||||
builds += static_cast<uint32_t>(made.size());
|
||||
changed++;
|
||||
}
|
||||
LOG("Gave %u cars and rockets of %u characters (made before builds were recorded) a build id", builds, changed);
|
||||
}
|
||||
34
dDatabase/ModularBuildIdMigration.h
Normal file
34
dDatabase/ModularBuildIdMigration.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#ifndef MODULARBUILDIDMIGRATION_H
|
||||
#define MODULARBUILDIDMIGRATION_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "dCommonVars.h"
|
||||
|
||||
namespace tinyxml2 {
|
||||
class XMLDocument;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cars and rockets built before builds were recorded: saved items with modules (x@ma, assemblyPartLOTs) and no subkey,
|
||||
* so they have no ugc_modular_build row and the client has no blueprint id to ask for their icon with. The migration
|
||||
* (dlu/mysql/98_modular_build_ids.sql, dlu/sqlite/81_modular_build_ids.sql) gives each what a new build gets
|
||||
* (ModularBuildFinish) and what InventoryComponent gives one when its character loads: a persistent id as its subkey
|
||||
* and a ugc_modular_build row with its modules and owner. Only modular builds have assemblyPartLOTs.
|
||||
*/
|
||||
namespace ModularBuildIdMigration {
|
||||
struct NewBuild {
|
||||
LWOOBJID id{};
|
||||
std::string modules; // "1:8129+1:8130+...", as ugc_modular_build.ldf_config
|
||||
};
|
||||
|
||||
// Gives every item of a character's saved XML with modules and no subkey the next id as its subkey; the builds to insert
|
||||
std::vector<NewBuild> AssignIds(tinyxml2::XMLDocument& document, const std::function<LWOOBJID()>& nextId);
|
||||
|
||||
void Run();
|
||||
};
|
||||
|
||||
#endif //!MODULARBUILDIDMIGRATION_H
|
||||
Reference in New Issue
Block a user