diff --git a/dDatabase/GameDatabase/ITables/IPropertyContents.h b/dDatabase/GameDatabase/ITables/IPropertyContents.h index 9a16d5d7d..447d0c8e8 100644 --- a/dDatabase/GameDatabase/ITables/IPropertyContents.h +++ b/dDatabase/GameDatabase/ITables/IPropertyContents.h @@ -20,12 +20,16 @@ public: std::array 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 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 GetPropertyModels(const LWOOBJID& propertyId) = 0; diff --git a/dDatabase/GameDatabase/MySQL/MySQLDatabase.cpp b/dDatabase/GameDatabase/MySQL/MySQLDatabase.cpp index 7bbd2812d..ac29dce63 100644 --- a/dDatabase/GameDatabase/MySQL/MySQLDatabase.cpp +++ b/dDatabase/GameDatabase/MySQL/MySQLDatabase.cpp @@ -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); diff --git a/dDatabase/GameDatabase/MySQL/MySQLDatabase.h b/dDatabase/GameDatabase/MySQL/MySQLDatabase.h index accfb27ea..d0c8845f8 100644 --- a/dDatabase/GameDatabase/MySQL/MySQLDatabase.h +++ b/dDatabase/GameDatabase/MySQL/MySQLDatabase.h @@ -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 unsplitId) override; + void InsertUnsplitUgcModel(std::stringstream& sd0Data, const LWOOBJID id, const uint32_t accountId, const LWOOBJID characterId) override; std::vector GetMailForPlayer(const LWOOBJID characterId, const uint32_t numberOfMail) override; std::optional GetMail(const uint64_t mailId) override; uint32_t GetUnreadMailCount(const LWOOBJID characterId) override; diff --git a/dDatabase/GameDatabase/MySQL/Tables/Ugc.cpp b/dDatabase/GameDatabase/MySQL/Tables/Ugc.cpp index 6367560a9..afce6459a 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/Ugc.cpp +++ b/dDatabase/GameDatabase/MySQL/Tables/Ugc.cpp @@ -40,29 +40,39 @@ std::vector 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 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) { diff --git a/dDatabase/GameDatabase/SQLite/SQLiteDatabase.cpp b/dDatabase/GameDatabase/SQLite/SQLiteDatabase.cpp index 19a49e5f5..5199e4a4b 100644 --- a/dDatabase/GameDatabase/SQLite/SQLiteDatabase.cpp +++ b/dDatabase/GameDatabase/SQLite/SQLiteDatabase.cpp @@ -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); diff --git a/dDatabase/GameDatabase/SQLite/SQLiteDatabase.h b/dDatabase/GameDatabase/SQLite/SQLiteDatabase.h index afcd0d26e..874a02a41 100644 --- a/dDatabase/GameDatabase/SQLite/SQLiteDatabase.h +++ b/dDatabase/GameDatabase/SQLite/SQLiteDatabase.h @@ -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 unsplitId) override; + void InsertUnsplitUgcModel(std::stringstream& sd0Data, const LWOOBJID id, const uint32_t accountId, const LWOOBJID characterId) override; std::vector GetMailForPlayer(const LWOOBJID characterId, const uint32_t numberOfMail) override; std::optional GetMail(const uint64_t mailId) override; uint32_t GetUnreadMailCount(const LWOOBJID characterId) override; diff --git a/dDatabase/GameDatabase/SQLite/Tables/Ugc.cpp b/dDatabase/GameDatabase/SQLite/Tables/Ugc.cpp index 0649bb9f3..415d72505 100644 --- a/dDatabase/GameDatabase/SQLite/Tables/Ugc.cpp +++ b/dDatabase/GameDatabase/SQLite/Tables/Ugc.cpp @@ -41,29 +41,39 @@ std::vector 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 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) { diff --git a/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.cpp b/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.cpp index 1cd48ad98..ffbee021a 100644 --- a/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.cpp +++ b/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.cpp @@ -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 unsplitId) { + +} + +void TestSQLDatabase::InsertUnsplitUgcModel(std::stringstream& sd0Data, const LWOOBJID id, const uint32_t accountId, const LWOOBJID characterId) { } diff --git a/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.h b/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.h index b8706c497..54b52ee84 100644 --- a/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.h +++ b/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.h @@ -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 unsplitId) override; + void InsertUnsplitUgcModel(std::stringstream& sd0Data, const LWOOBJID id, const uint32_t accountId, const LWOOBJID characterId) override; std::vector GetMailForPlayer(const LWOOBJID characterId, const uint32_t numberOfMail) override; std::optional GetMail(const uint64_t mailId) override; uint32_t GetUnreadMailCount(const LWOOBJID characterId) override; diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 19d5859d6..a3ccc43bf 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -2530,6 +2530,12 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent outFile.close(); } + // Keep the save as the client sent it before splitting it; every model made from it points back to it + const auto accountId = entity->GetCharacter()->GetParentUser()->GetAccountID(); + const LWOOBJID unsplitId = ObjectIDManager::GetPersistentID(); + std::stringstream unsplitData(str); + Database::Get()->InsertUnsplitUgcModel(unsplitData, unsplitId, accountId, entity->GetCharacter()->GetID()); + auto splitLxfmls = Lxfml::Split(asStr); LOG_DEBUG("Split into %zu models", splitLxfmls.size()); @@ -2567,7 +2573,7 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent // Save each model to the database sd0.FromData(reinterpret_cast(splitLxfmls[i].lxfml.data()), splitLxfmls[i].lxfml.size()); auto sd0AsStream = sd0.GetAsStream(); - Database::Get()->InsertNewUgcModel(sd0AsStream, blueprintID, entity->GetCharacter()->GetParentUser()->GetAccountID(), entity->GetCharacter()->GetID()); + Database::Get()->InsertNewUgcModel(sd0AsStream, blueprintID, accountId, entity->GetCharacter()->GetID(), unsplitId); // Insert the new property model IPropertyContents::Model model; diff --git a/migrations/dlu/mysql/27_ugc_unsplit.sql b/migrations/dlu/mysql/27_ugc_unsplit.sql new file mode 100644 index 000000000..68030e34c --- /dev/null +++ b/migrations/dlu/mysql/27_ugc_unsplit.sql @@ -0,0 +1,11 @@ +-- Every brick by brick save as the client sent it (sd0), before the server splits it into one model per group of +-- connected bricks; each model made from it points back to it +CREATE TABLE IF NOT EXISTS ugc_unsplit ( + id BIGINT NOT NULL PRIMARY KEY, + account_id INT NOT NULL REFERENCES accounts(id), + character_id BIGINT NOT NULL REFERENCES charinfo(id), + lxfml MEDIUMBLOB NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +ALTER TABLE ugc ADD COLUMN unsplit_id BIGINT NULL DEFAULT NULL; diff --git a/migrations/dlu/sqlite/10_ugc_unsplit.sql b/migrations/dlu/sqlite/10_ugc_unsplit.sql new file mode 100644 index 000000000..e7d2e50f9 --- /dev/null +++ b/migrations/dlu/sqlite/10_ugc_unsplit.sql @@ -0,0 +1,11 @@ +-- Every brick by brick save as the client sent it (sd0), before the server splits it into one model per group of +-- connected bricks; each model made from it points back to it +CREATE TABLE IF NOT EXISTS ugc_unsplit ( + id BIGINT NOT NULL PRIMARY KEY, + account_id INTEGER NOT NULL REFERENCES accounts(id), + character_id BIGINT NOT NULL REFERENCES charinfo(id), + lxfml BLOB NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +ALTER TABLE ugc ADD COLUMN unsplit_id BIGINT NULL DEFAULT NULL;