feat: contraband list with flagging and optional removal

Staff list contraband items on a new dashboard page (item search, reason, flag or flag and remove;
contraband_manage to edit, reports_view to see). World servers check every inventory when a
character loads and every item a player receives: each find is an economy flag of the new kind
Contraband, shown with the other flags and in the character's related data. Items marked for
removal are taken away, with a character snapshot kept first so they can be given back, an audit
entry and a mail or chat message telling the player why. Staff are skipped unless
contraband_ignore_staff is off. Worlds reload the list when it changes (RELOAD_CONTRABAND, added
at the end of ePlayerAction).

Fixes #1563

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-09-26 23:20:16 -05:00
parent 341ce89a6a
commit b2403b09ea
36 changed files with 766 additions and 22 deletions

View File

@@ -1425,27 +1425,29 @@ namespace {
}
char decoded[256]{};
mg_url_decode(query.c_str(), query.size(), decoded, sizeof(decoded), 1);
query = decoded;
auto stmt = CDClientDatabase::CreatePreppedStmt(
"SELECT id, name, displayName FROM Objects WHERE type = 'Loot' AND (name LIKE '%' || ? || '%' OR displayName LIKE '%' || ? || '%' OR id = ?) "
"ORDER BY name LIMIT 50;");
stmt.bind(1, query.c_str());
stmt.bind(2, query.c_str());
stmt.bind(3, GeneralUtils::TryParse<int>(query).value_or(-1));
auto result = stmt.execQuery();
nlohmann::json items = nlohmann::json::array();
while (!result.eof()) {
const std::string displayName = result.getStringField("displayName", "");
items.push_back({ {"lot", result.getIntField("id")}, {"name", displayName.empty() ? result.getStringField("name") : displayName} });
result.nextRow();
}
JsonReply(reply, eHTTPStatusCode::OK, items);
JsonReply(reply, eHTTPStatusCode::OK, SearchItems(decoded));
});
}
}
nlohmann::json SearchItems(const std::string& query) {
auto stmt = CDClientDatabase::CreatePreppedStmt(
"SELECT id, name, displayName FROM Objects WHERE type = 'Loot' AND (name LIKE '%' || ? || '%' OR displayName LIKE '%' || ? || '%' OR id = ?) "
"ORDER BY name LIMIT 50;");
stmt.bind(1, query.c_str());
stmt.bind(2, query.c_str());
stmt.bind(3, GeneralUtils::TryParse<int>(query).value_or(-1));
auto result = stmt.execQuery();
nlohmann::json items = nlohmann::json::array();
while (!result.eof()) {
const std::string displayName = result.getStringField("displayName", "");
items.push_back({ {"lot", result.getIntField("id")}, {"name", displayName.empty() ? result.getStringField("name") : displayName} });
result.nextRow();
}
return items;
}
// For the property showcase (Showcase.cpp), which serves the same model data to people who aren't the owner
namespace PropertyAssets {
std::optional<std::pair<std::string, std::string>> ModelLxfml(const IPropertyContents::Model& model, LWOOBJID propertyId) { return ::ModelLxfml(model, propertyId); }