diff --git a/dDatabase/GameDatabase/ITables/IPropertyContents.h b/dDatabase/GameDatabase/ITables/IPropertyContents.h index e92827f7f..0ff729ee9 100644 --- a/dDatabase/GameDatabase/ITables/IPropertyContents.h +++ b/dDatabase/GameDatabase/ITables/IPropertyContents.h @@ -18,6 +18,8 @@ public: LOT lot{}; LWOOBJID ugcId{}; std::array behaviors{}; + // The character who placed the model; 0 (NULL in the database) for the property's owner and older models + LWOOBJID placedBy{}; }; // Inserts a new UGC model into the database. diff --git a/dDatabase/GameDatabase/MySQL/Tables/PropertyContents.cpp b/dDatabase/GameDatabase/MySQL/Tables/PropertyContents.cpp index cecea68b3..e25c6d415 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/PropertyContents.cpp +++ b/dDatabase/GameDatabase/MySQL/Tables/PropertyContents.cpp @@ -3,7 +3,7 @@ std::vector MySQLDatabase::GetPropertyModels(const LWOOBJID& propertyId) { auto result = ExecuteSelect( "SELECT id, lot, x, y, z, rx, ry, rz, rw, ugc_id, " - "behavior_1, behavior_2, behavior_3, behavior_4, behavior_5 " + "behavior_1, behavior_2, behavior_3, behavior_4, behavior_5, placed_by " "FROM properties_contents WHERE property_id = ?;", propertyId); std::vector toReturn; @@ -25,6 +25,7 @@ std::vector MySQLDatabase::GetPropertyModels(const LWO model.behaviors[2] = result->getUInt64("behavior_3"); model.behaviors[3] = result->getUInt64("behavior_4"); model.behaviors[4] = result->getUInt64("behavior_5"); + model.placedBy = result->getUInt64("placed_by"); toReturn.push_back(std::move(model)); } @@ -35,9 +36,9 @@ void MySQLDatabase::InsertNewPropertyModel(const LWOOBJID& propertyId, const IPr try { ExecuteInsert( "INSERT INTO properties_contents" - "(id, property_id, ugc_id, lot, x, y, z, rx, ry, rz, rw, model_name, model_description, behavior_1, behavior_2, behavior_3, behavior_4, behavior_5)" - "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", - // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 18 + "(id, property_id, ugc_id, lot, x, y, z, rx, ry, rz, rw, model_name, model_description, behavior_1, behavior_2, behavior_3, behavior_4, behavior_5, placed_by)" + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 18 19 model.id, propertyId, model.ugcId == 0 ? std::nullopt : std::optional(model.ugcId), static_cast(model.lot), model.position.x, model.position.y, model.position.z, model.rotation.x, model.rotation.y, model.rotation.z, model.rotation.w, name, "", // Model description. TODO implement this. @@ -45,7 +46,8 @@ void MySQLDatabase::InsertNewPropertyModel(const LWOOBJID& propertyId, const IPr model.behaviors[1], // behavior 2 model.behaviors[2], // behavior 3 model.behaviors[3], // behavior 4 - model.behaviors[4] // behavior 5 + model.behaviors[4], // behavior 5 + model.placedBy == 0 ? std::nullopt : std::optional(model.placedBy) ); } catch (sql::SQLException& e) { LOG("Error inserting new property model: %s", e.what()); @@ -85,6 +87,7 @@ std::optional MySQLDatabase::GetModel(const LWOOBJID m model->behaviors[2] = result->getUInt64("behavior_3"); model->behaviors[3] = result->getUInt64("behavior_4"); model->behaviors[4] = result->getUInt64("behavior_5"); + model->placedBy = result->getUInt64("placed_by"); } return model; diff --git a/dDatabase/GameDatabase/SQLite/Tables/PropertyContents.cpp b/dDatabase/GameDatabase/SQLite/Tables/PropertyContents.cpp index 5251c5349..f607221ad 100644 --- a/dDatabase/GameDatabase/SQLite/Tables/PropertyContents.cpp +++ b/dDatabase/GameDatabase/SQLite/Tables/PropertyContents.cpp @@ -3,7 +3,7 @@ std::vector SQLiteDatabase::GetPropertyModels(const LWOOBJID& propertyId) { auto [_, result] = ExecuteSelect( "SELECT id, lot, x, y, z, rx, ry, rz, rw, ugc_id, " - "behavior_1, behavior_2, behavior_3, behavior_4, behavior_5 " + "behavior_1, behavior_2, behavior_3, behavior_4, behavior_5, placed_by " "FROM properties_contents WHERE property_id = ?;", propertyId); std::vector toReturn; @@ -24,6 +24,7 @@ std::vector SQLiteDatabase::GetPropertyModels(const LW model.behaviors[2] = result.getInt64Field("behavior_3"); model.behaviors[3] = result.getInt64Field("behavior_4"); model.behaviors[4] = result.getInt64Field("behavior_5"); + model.placedBy = result.getInt64Field("placed_by"); toReturn.push_back(std::move(model)); result.nextRow(); @@ -35,9 +36,9 @@ void SQLiteDatabase::InsertNewPropertyModel(const LWOOBJID& propertyId, const IP try { ExecuteInsert( "INSERT INTO properties_contents" - "(id, property_id, ugc_id, lot, x, y, z, rx, ry, rz, rw, model_name, model_description, behavior_1, behavior_2, behavior_3, behavior_4, behavior_5)" - "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", - // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 18 + "(id, property_id, ugc_id, lot, x, y, z, rx, ry, rz, rw, model_name, model_description, behavior_1, behavior_2, behavior_3, behavior_4, behavior_5, placed_by)" + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 18 19 model.id, propertyId, model.ugcId == 0 ? std::nullopt : std::optional(model.ugcId), static_cast(model.lot), model.position.x, model.position.y, model.position.z, model.rotation.x, model.rotation.y, model.rotation.z, model.rotation.w, name, "", // Model description. TODO implement this. @@ -45,7 +46,8 @@ void SQLiteDatabase::InsertNewPropertyModel(const LWOOBJID& propertyId, const IP model.behaviors[1], // behavior 2 model.behaviors[2], // behavior 3 model.behaviors[3], // behavior 4 - model.behaviors[4] // behavior 5 + model.behaviors[4], // behavior 5 + model.placedBy == 0 ? std::nullopt : std::optional(model.placedBy) ); } catch (std::exception& e) { LOG("Error inserting new property model: %s", e.what()); @@ -86,6 +88,7 @@ std::optional SQLiteDatabase::GetModel(const LWOOBJID model->behaviors[2] = result.getInt64Field("behavior_3"); model->behaviors[3] = result.getInt64Field("behavior_4"); model->behaviors[4] = result.getInt64Field("behavior_5"); + model->placedBy = result.getInt64Field("placed_by"); } while (result.nextRow()); } diff --git a/migrations/dlu/mysql/93_properties_contents_placed_by.sql b/migrations/dlu/mysql/93_properties_contents_placed_by.sql new file mode 100644 index 000000000..ab2aaf098 --- /dev/null +++ b/migrations/dlu/mysql/93_properties_contents_placed_by.sql @@ -0,0 +1,7 @@ +/* properties_contents.placed_by is the character who placed the model (NULL: the property's owner, and every model + placed before this column). A model picked up by another player who can build there goes back to them. */ +SET @dlu_column = (SELECT IF(COUNT(*) = 0, 'ALTER TABLE properties_contents ADD COLUMN placed_by BIGINT NULL DEFAULT NULL', 'DO 0') FROM information_schema.columns + WHERE table_schema = DATABASE() AND table_name = 'properties_contents' AND column_name = 'placed_by'); +PREPARE dlu_column_stmt FROM @dlu_column; +EXECUTE dlu_column_stmt; +DEALLOCATE PREPARE dlu_column_stmt; diff --git a/migrations/dlu/sqlite/76_properties_contents_placed_by.sql b/migrations/dlu/sqlite/76_properties_contents_placed_by.sql new file mode 100644 index 000000000..17d1b7916 --- /dev/null +++ b/migrations/dlu/sqlite/76_properties_contents_placed_by.sql @@ -0,0 +1,2 @@ +/* The character who placed a property model (NULL: the owner). See the MySQL migration. */ +ALTER TABLE properties_contents ADD COLUMN placed_by BIGINT DEFAULT NULL; diff --git a/tests/dDatabaseTests/DatabaseParityTests.cpp b/tests/dDatabaseTests/DatabaseParityTests.cpp index bf3e76e35..dc11c1374 100644 --- a/tests/dDatabaseTests/DatabaseParityTests.cpp +++ b/tests/dDatabaseTests/DatabaseParityTests.cpp @@ -14,6 +14,7 @@ #include +#include #include #include #include @@ -679,6 +680,21 @@ TEST_F(ParitySeeded, Properties) { model.id = 1152921510000400002LL; db.InsertNewPropertyModel(PROP1, model, "Objects_14_name"); }); + Both("InsertNewPropertyModel placed by", [](GameDatabase& db) { + // NULL placed_by (the owner's) reads back as 0; a builder's id reads back as itself + IPropertyContents::Model model; + model.id = 1152921510000400003LL; model.lot = 6326; model.placedBy = CHAR_BOB; + db.InsertNewPropertyModel(PROP1, model, "Objects_6326_name"); + auto models = db.GetPropertyModels(PROP1); + std::ranges::sort(models, {}, &IPropertyContents::Model::id); + json out = json::array(); + for (const auto& placed : models) out.push_back({ placed.id, placed.lot, placed.placedBy }); + const auto one = db.GetModel(1152921510000400003LL); + out.push_back(one ? json{ one->id, one->placedBy } : json()); + const auto owners = db.GetModel(1152921510000400001LL); + out.push_back(owners ? json{ owners->id, owners->placedBy } : json()); + return out; + }); Both("GetModelPropertyId", [](GameDatabase& db) { return json{ db.GetModelPropertyId(1152921510000400001LL), db.GetModelPropertyId(1) }; }); for (const auto sort : { IProperty::ShowcaseSort::REPUTATION, IProperty::ShowcaseSort::NEWEST, IProperty::ShowcaseSort::NAME }) { Both("GetShowcaseProperties " + std::to_string(static_cast(sort)), [&](GameDatabase& db) {