mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-23 17:35:57 +02:00
Objects: unlock free build-kit objects; Object Browser category icons
Many free object-dropper objects were treated as store items and hidden from the Object Browser and the park editor (pole jams, road signs, bleachers, A frames, quarter pipes, bowl pieces, ...). - Items whose record names a neighbourhood rank unlock (field 11) are open, even when no rank record lists them as a reward (20 objects, 6 cosmetics). - Build-kit objects that no record offers, sells or grants are open (169). Objects with any offer or bundle grant, and brand collabs (field 24), stay reserved: 42 of 439 on build 24855063. Cosmetics are unchanged. - Object Browser categories use their first object's thumbnail as the tab icon instead of the game's empty placeholder. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
129dc637be
commit
86fea38bcd
4 files changed
+58
-1
No files matched your search
@@ -28,6 +28,16 @@ int main(int argc, char** argv) {
|
||||
check(catalogs.items.at("own_bkbanks_generic_volcanolarge_00001").value("title", "") == "Volcano, L Wood",
|
||||
"item keyed by owned asset id");
|
||||
check(catalogs.items.at("own_costume_gen_dembones_00001").value("rarity_id", "") == "legendary", "item rarity");
|
||||
// Unlocked by neighbourhood rank, though no rank record lists them as rewards.
|
||||
check(!catalogs.reserved("own_bkrails_generic_urbanpolejamsmall_00001") &&
|
||||
!catalogs.reserved("own_bkindustrial_generic_signstopsign_00001"), "rank unlocks are not store items");
|
||||
// Objects nothing offers are open; ones sold, bundled or from a collab are not.
|
||||
check(!catalogs.reserved("own_bkrails_generic_urbanpolejammedium_00001") &&
|
||||
!catalogs.reserved("own_bkprops_generic_signnoskateboarding_00001"), "objects nothing offers are open");
|
||||
check(catalogs.reserved("own_bkbounceable_generic_dartboard_00001"), "an object sold for hard currency");
|
||||
check(catalogs.reserved("own_bkramps_generic_shoeboxanglednike_00001"), "an object from a store bundle");
|
||||
check(catalogs.reserved("own_bkpads_generic_manualpadroundxgames_00001"), "a collab object");
|
||||
check(catalogs.reserved("own_deckgraphic_gen_popsicle_00123"), "cosmetics nothing offers stay reserved");
|
||||
const auto& line = catalogs.challenges.at("Base_CH02_M02_Line01");
|
||||
check(line.value("type", "") == "Line" && line.value("neighborhood", "") == "neighbourhood_rank_historic",
|
||||
"challenge type and neighbourhood");
|
||||
@@ -81,6 +91,11 @@ int main(int argc, char** argv) {
|
||||
for (const auto& id : catalogs.entitlements) dump["entitlements"].push_back(id);
|
||||
std::ofstream(argv[2], std::ios::binary) << dump.dump(1);
|
||||
}
|
||||
// Build 24855063: 42 of 439 (28 sold for hard currency, 3 in the Nike bundle, 11 X Games).
|
||||
std::size_t objects{}, reserved_objects{};
|
||||
for (const auto& [key, item] : catalogs.items.items())
|
||||
if (key.starts_with("own_bk")) { ++objects; reserved_objects += catalogs.reserved(key); }
|
||||
std::cout << reserved_objects << " of " << objects << " objects reserved.\n";
|
||||
std::cout << catalogs.items.size() << " items, " << catalogs.challenges.size() << " challenges, "
|
||||
<< catalogs.entitlements.size() << " entitlements.\n";
|
||||
return failures ? 1 : 0;
|
||||
|
||||
@@ -36,6 +36,9 @@ std::string lower(std::string value) {
|
||||
|
||||
// Owned items: the presentation message (6) names the owned asset (6.10), the
|
||||
// title (6.2) and description (6.3); the rarity is field 23. First record wins.
|
||||
// Field 10 set marks an item owned from the start; field 11 names the
|
||||
// neighbourhood rank that unlocks it, which some items carry with no rank
|
||||
// record listing them as a reward.
|
||||
void add_item(Catalogs& out, const Fields& record) {
|
||||
const auto presentation = message(record, 6);
|
||||
if (!presentation) return;
|
||||
@@ -45,6 +48,13 @@ void add_item(Catalogs& out, const Fields& record) {
|
||||
if (out.items.contains(key)) return;
|
||||
if (const auto* flag = protobuf::first(record, 10, Wire::varint); flag && flag->integer == 1)
|
||||
out.open_items.insert(key);
|
||||
// E.g. Pole Jam S/L and the Stop sign: rank unlocks that add_source never
|
||||
// sees, because no neighbourhood_rank record grants them.
|
||||
if (text(record, 11).value_or("").starts_with("neighbourhood_rank"))
|
||||
out.open_items.insert(key);
|
||||
// Field 24 tags brand collabs (collab_xgames, collab_nike, ...).
|
||||
if (text(record, 24).value_or("").starts_with("collab_"))
|
||||
out.collab_items.insert(key);
|
||||
Json entry = Json::object();
|
||||
if (const auto description = text(*presentation, 3)) entry["description"] = *description;
|
||||
if (const auto rarity = text(record, 23)) entry["rarity_id"] = *rarity;
|
||||
@@ -117,6 +127,18 @@ bool free_offer(const Fields& offer) {
|
||||
return lower(prerequisite.id).find("premium") != std::string::npos;
|
||||
});
|
||||
}
|
||||
// Every own-create grant in any of the record's grant lists. Deliberately broad:
|
||||
// it does not tell free from paid, so an item named by any offer, store bundle,
|
||||
// pack or reward counts as granted and keeps the store's say over it (reserved()).
|
||||
// A bytes field that is not a grant list simply parses to no grants.
|
||||
void add_granted(std::set<std::string>& granted, const Fields& record) {
|
||||
std::set<std::uint32_t> numbers;
|
||||
for (const auto& field : record)
|
||||
if (field.wire == Wire::bytes) numbers.insert(field.number);
|
||||
for (const auto number : numbers)
|
||||
for (const auto& grant : grants(record, number))
|
||||
if (grant.kind == "own-create" && lower(grant.id.substr(0, 4)) == "own_") granted.insert(lower(grant.id));
|
||||
}
|
||||
void add_source(std::set<std::string>& open, const std::string& id, const Fields& record) {
|
||||
const auto open_grants = [&](std::uint32_t number) {
|
||||
for (const auto& grant : grants(record, number))
|
||||
@@ -261,6 +283,7 @@ Catalogs read_catalogs(const std::filesystem::path& folder) {
|
||||
add_object_category(out, *id, *fields);
|
||||
add_travel(out, *id, *fields);
|
||||
add_source(out.open_items, *id, *fields);
|
||||
add_granted(out.granted_items, *fields);
|
||||
}
|
||||
}
|
||||
out.entitlements.assign(entitlements.begin(), entitlements.end());
|
||||
@@ -270,6 +293,12 @@ Catalogs read_catalogs(const std::filesystem::path& folder) {
|
||||
|
||||
bool Catalogs::reserved(const std::string& key) const {
|
||||
if (!items.contains(key) || open_items.contains(key)) return false;
|
||||
// Build-kit objects nothing offers, sells or grants are taken as everyday
|
||||
// park pieces; brand collabs stay with the store. Many basic objects (A
|
||||
// frames, quarter pipes, bowl pieces, Pole Jam M) have no source record in
|
||||
// the cache, so without this the Object Browser and park editor hid them.
|
||||
// Cosmetics are not covered: unsourced ones include past store and pass items.
|
||||
if (key.starts_with("own_bk") && !granted_items.contains(key) && !collab_items.contains(key)) return false;
|
||||
return !key.starts_with("dev_") && key.find("_dev_") == std::string::npos && !key.ends_with("_dev");
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,9 @@ struct Catalogs {
|
||||
Json challenges = Json::object();
|
||||
std::vector<std::string> entitlements;
|
||||
std::set<std::string> open_items;
|
||||
// Items some record grants (an offer, bundle, pack or reward), free or not,
|
||||
// and the ones tagged as a brand collab.
|
||||
std::set<std::string> granted_items, collab_items;
|
||||
// Build-kit categories in record order.
|
||||
std::vector<ObjectCategory> object_categories;
|
||||
std::vector<TravelLocation> travel_locations;
|
||||
|
||||
Reference in new issue
Block a user