feat: keep every brick by brick save before it is split into models

The save is stored as the client sent it in the new ugc_unsplit table, and each model split from it points back to it (ugc.unsplit_id), so the whole build can be looked at or restored later. A save is kept while any model split from it exists: deleting models removes saves no model points to any more, and deleting a character removes its saves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-10-16 20:46:33 -05:00
parent f871261b16
commit b579344482
12 changed files with 77 additions and 13 deletions

View File

@@ -20,12 +20,16 @@ public:
std::array<LWOOBJID, 5> behaviors{};
};
// Inserts a new UGC model into the database.
// Inserts a new UGC model into the database. unsplitId is the save it was split from (InsertUnsplitUgcModel), if any.
virtual void InsertNewUgcModel(
std::stringstream& sd0Data,
const uint64_t blueprintId,
const uint32_t accountId,
const LWOOBJID characterId) = 0;
const LWOOBJID characterId,
const std::optional<LWOOBJID> unsplitId) = 0;
// Keeps a brick by brick save as the client sent it, before it is split into models.
virtual void InsertUnsplitUgcModel(std::stringstream& sd0Data, const LWOOBJID id, const uint32_t accountId, const LWOOBJID characterId) = 0;
// Get the property models for the given property id.
virtual std::vector<IPropertyContents::Model> GetPropertyModels(const LWOOBJID& propertyId) = 0;

View File

@@ -108,6 +108,7 @@ void MySQLDatabase::DeleteCharacter(const LWOOBJID characterId) {
ExecuteDelete("DELETE FROM properties_contents WHERE property_id IN (SELECT id FROM properties WHERE owner_id=?);", characterId);
ExecuteDelete("DELETE FROM properties WHERE owner_id=?;", characterId);
ExecuteDelete("DELETE FROM ugc WHERE character_id=?;", characterId);
ExecuteDelete("DELETE FROM ugc_unsplit WHERE character_id=?;", characterId);
ExecuteDelete("DELETE FROM activity_log WHERE character_id=?;", characterId);
ExecuteDelete("DELETE FROM mail WHERE receiver_id=?;", characterId);
ExecuteDelete("DELETE FROM charinfo WHERE id=? LIMIT 1;", characterId);

View File

@@ -99,7 +99,9 @@ public:
std::stringstream& sd0Data,
const uint64_t blueprintId,
const uint32_t accountId,
const LWOOBJID characterId) override;
const LWOOBJID characterId,
const std::optional<LWOOBJID> unsplitId) override;
void InsertUnsplitUgcModel(std::stringstream& sd0Data, const LWOOBJID id, const uint32_t accountId, const LWOOBJID characterId) override;
std::vector<MailInfo> GetMailForPlayer(const LWOOBJID characterId, const uint32_t numberOfMail) override;
std::optional<MailInfo> GetMail(const uint64_t mailId) override;
uint32_t GetUnreadMailCount(const LWOOBJID characterId) override;

View File

@@ -40,29 +40,39 @@ std::vector<IUgc::Model> MySQLDatabase::GetAllUgcModels() {
void MySQLDatabase::RemoveUnreferencedUgcModels() {
ExecuteDelete("DELETE FROM ugc WHERE id NOT IN (SELECT ugc_id FROM properties_contents WHERE ugc_id IS NOT NULL);");
// An unsplit save is kept while any model split from it is
ExecuteDelete("DELETE FROM ugc_unsplit WHERE id NOT IN (SELECT unsplit_id FROM ugc WHERE unsplit_id IS NOT NULL);");
}
void MySQLDatabase::InsertNewUgcModel(
std::stringstream& sd0Data, // cant be const sad
const uint64_t blueprintId,
const uint32_t accountId,
const LWOOBJID characterId) {
const LWOOBJID characterId,
const std::optional<LWOOBJID> unsplitId) {
const std::istream stream(sd0Data.rdbuf());
ExecuteInsert(
"INSERT INTO `ugc`(`id`, `account_id`, `character_id`, `is_optimized`, `lxfml`, `bake_ao`, `filename`) VALUES (?,?,?,?,?,?,?)",
"INSERT INTO `ugc`(`id`, `account_id`, `character_id`, `is_optimized`, `lxfml`, `bake_ao`, `filename`, `unsplit_id`) VALUES (?,?,?,?,?,?,?,?)",
blueprintId,
accountId,
characterId,
0,
&stream,
false,
"weedeater.lxfml"
"weedeater.lxfml",
unsplitId
);
}
void MySQLDatabase::InsertUnsplitUgcModel(std::stringstream& sd0Data, const LWOOBJID id, const uint32_t accountId, const LWOOBJID characterId) {
const std::istream stream(sd0Data.rdbuf());
ExecuteInsert("INSERT INTO `ugc_unsplit`(`id`, `account_id`, `character_id`, `lxfml`) VALUES (?,?,?,?)", id, accountId, characterId, &stream);
}
void MySQLDatabase::DeleteUgcModelData(const LWOOBJID& modelId) {
ExecuteDelete("DELETE FROM ugc WHERE id = ?;", modelId);
ExecuteDelete("DELETE FROM properties_contents WHERE ugc_id = ?;", modelId);
ExecuteDelete("DELETE FROM ugc_unsplit WHERE id NOT IN (SELECT unsplit_id FROM ugc WHERE unsplit_id IS NOT NULL);");
}
void MySQLDatabase::UpdateUgcModelData(const LWOOBJID& modelId, std::stringstream& lxfml) {

View File

@@ -75,6 +75,7 @@ void SQLiteDatabase::DeleteCharacter(const LWOOBJID characterId) {
ExecuteDelete("DELETE FROM properties_contents WHERE property_id IN (SELECT id FROM properties WHERE owner_id=?);", characterId);
ExecuteDelete("DELETE FROM properties WHERE owner_id=?;", characterId);
ExecuteDelete("DELETE FROM ugc WHERE character_id=?;", characterId);
ExecuteDelete("DELETE FROM ugc_unsplit WHERE character_id=?;", characterId);
ExecuteDelete("DELETE FROM activity_log WHERE character_id=?;", characterId);
ExecuteDelete("DELETE FROM mail WHERE receiver_id=?;", characterId);
ExecuteDelete("DELETE FROM charinfo WHERE id=?;", characterId);

View File

@@ -83,7 +83,9 @@ public:
std::stringstream& sd0Data,
const uint64_t blueprintId,
const uint32_t accountId,
const LWOOBJID characterId) override;
const LWOOBJID characterId,
const std::optional<LWOOBJID> unsplitId) override;
void InsertUnsplitUgcModel(std::stringstream& sd0Data, const LWOOBJID id, const uint32_t accountId, const LWOOBJID characterId) override;
std::vector<MailInfo> GetMailForPlayer(const LWOOBJID characterId, const uint32_t numberOfMail) override;
std::optional<MailInfo> GetMail(const uint64_t mailId) override;
uint32_t GetUnreadMailCount(const LWOOBJID characterId) override;

View File

@@ -41,29 +41,39 @@ std::vector<IUgc::Model> SQLiteDatabase::GetAllUgcModels() {
void SQLiteDatabase::RemoveUnreferencedUgcModels() {
ExecuteDelete("DELETE FROM ugc WHERE id NOT IN (SELECT ugc_id FROM properties_contents WHERE ugc_id IS NOT NULL);");
// An unsplit save is kept while any model split from it is
ExecuteDelete("DELETE FROM ugc_unsplit WHERE id NOT IN (SELECT unsplit_id FROM ugc WHERE unsplit_id IS NOT NULL);");
}
void SQLiteDatabase::InsertNewUgcModel(
std::stringstream& sd0Data, // cant be const sad
const uint64_t blueprintId,
const uint32_t accountId,
const LWOOBJID characterId) {
const LWOOBJID characterId,
const std::optional<LWOOBJID> unsplitId) {
const std::istream stream(sd0Data.rdbuf());
ExecuteInsert(
"INSERT INTO `ugc`(`id`, `account_id`, `character_id`, `is_optimized`, `lxfml`, `bake_ao`, `filename`) VALUES (?,?,?,?,?,?,?)",
"INSERT INTO `ugc`(`id`, `account_id`, `character_id`, `is_optimized`, `lxfml`, `bake_ao`, `filename`, `unsplit_id`) VALUES (?,?,?,?,?,?,?,?)",
blueprintId,
accountId,
characterId,
0,
&stream,
false,
"weedeater.lxfml"
"weedeater.lxfml",
unsplitId
);
}
void SQLiteDatabase::InsertUnsplitUgcModel(std::stringstream& sd0Data, const LWOOBJID id, const uint32_t accountId, const LWOOBJID characterId) {
const std::istream stream(sd0Data.rdbuf());
ExecuteInsert("INSERT INTO `ugc_unsplit`(`id`, `account_id`, `character_id`, `lxfml`) VALUES (?,?,?,?)", id, accountId, characterId, &stream);
}
void SQLiteDatabase::DeleteUgcModelData(const LWOOBJID& modelId) {
ExecuteDelete("DELETE FROM ugc WHERE id = ?;", modelId);
ExecuteDelete("DELETE FROM properties_contents WHERE ugc_id = ?;", modelId);
ExecuteDelete("DELETE FROM ugc_unsplit WHERE id NOT IN (SELECT unsplit_id FROM ugc WHERE unsplit_id IS NOT NULL);");
}
void SQLiteDatabase::UpdateUgcModelData(const LWOOBJID& modelId, std::stringstream& lxfml) {

View File

@@ -192,7 +192,11 @@ void TestSQLDatabase::InsertNewMail(const MailInfo& mail) {
}
void TestSQLDatabase::InsertNewUgcModel(std::stringstream& sd0Data, const uint64_t blueprintId, const uint32_t accountId, const LWOOBJID characterId) {
void TestSQLDatabase::InsertNewUgcModel(std::stringstream& sd0Data, const uint64_t blueprintId, const uint32_t accountId, const LWOOBJID characterId, const std::optional<LWOOBJID> unsplitId) {
}
void TestSQLDatabase::InsertUnsplitUgcModel(std::stringstream& sd0Data, const LWOOBJID id, const uint32_t accountId, const LWOOBJID characterId) {
}

View File

@@ -62,7 +62,9 @@ class TestSQLDatabase : public GameDatabase {
std::stringstream& sd0Data,
const uint64_t blueprintId,
const uint32_t accountId,
const LWOOBJID characterId) override;
const LWOOBJID characterId,
const std::optional<LWOOBJID> unsplitId) override;
void InsertUnsplitUgcModel(std::stringstream& sd0Data, const LWOOBJID id, const uint32_t accountId, const LWOOBJID characterId) override;
std::vector<MailInfo> GetMailForPlayer(const LWOOBJID characterId, const uint32_t numberOfMail) override;
std::optional<MailInfo> GetMail(const uint64_t mailId) override;
uint32_t GetUnreadMailCount(const LWOOBJID characterId) override;