feat(db): bbb_autosave table for the client's BBB autosave

The client sends SetBBBAutosave (996) with the model being built every five
minutes, before an AFK kick and before quitting, and expects the server to
rebuild an unfinished model later (RebuildBBBAutosaveMsg). This keeps the
last one per character with the model items that were in the BBB inventory
at the time. MySQL 82 / SQLite 65; parity test included.

Refs #1632

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-09-27 04:08:19 -05:00
parent dd16c67177
commit e25a6df45b
13 changed files with 187 additions and 1 deletions

View File

@@ -1506,6 +1506,25 @@ TEST_F(ParitySeeded, PropertyReputation) {
Both("GetPropertyReputationDays", [](GameDatabase& db) { return db.GetPropertyReputationDays(PROP_REP, 19980); });
}
TEST_F(ParitySeeded, BbbAutosave) {
std::string binary = "sd0\x01\xff";
for (int i = 0; i < 256; i++) binary += static_cast<char>(i);
const auto describe = [](const std::optional<IBbbAutosave::Info>& info) {
if (!info) return json(nullptr);
return json{ info->lxfml, info->sourceItems, info->updatedAt };
};
EXPECT_EQ(Both("GetBbbAutosave empty", [&](GameDatabase& db) { return describe(db.GetBbbAutosave(CHAR_BOB)); }), json(nullptr));
Both("SetBbbAutosave", [&](GameDatabase& db) {
db.SetBbbAutosave(CHAR_BOB, { "first", { 1152921510000000001LL }, 1700000000 });
db.SetBbbAutosave(CHAR_BOB, { binary, { 1152921510000000002LL, 1152921510000000003LL }, 1700000001 });
return describe(db.GetBbbAutosave(CHAR_BOB));
});
EXPECT_EQ(Both("DeleteBbbAutosave", [&](GameDatabase& db) {
db.DeleteBbbAutosave(CHAR_BOB);
return describe(db.GetBbbAutosave(CHAR_BOB));
}), json(nullptr));
}
TEST_F(ParitySeeded, Contraband) {
Both("GetContrabandItems empty", [](GameDatabase& db) { return db.GetContrabandItems(); });
Both("SetContrabandItem", [](GameDatabase& db) {