mirror of
https://github.com/djdevin/recflare.git
synced 2026-10-25 23:18:51 -07:00
[econ] potentially fix hair again
This commit is contained in:
@@ -149,6 +149,14 @@ export interface CatalogRow {
|
||||
*
|
||||
* `AvatarItemId` is nullable and `Tooltip` may be null; both are true of the captured data and
|
||||
* the client reads them that way. Do not tighten either to keep a projection simple.
|
||||
*
|
||||
* `CreatedAt` and `ThumbnailImage` are OPTIONAL, never null: the capture this record mirrors
|
||||
* carried a date on most rows and simply had no key on the rest — the permanent hair dyes
|
||||
* among them — and never `null`. A row loaded from a store listing has no date at all, and
|
||||
* serving it as `"CreatedAt": null` on every record (as `lockeditems/bulk` did once the
|
||||
* loader moved to the store) broke the dorm mirror's hair dyes: the client resolves locked
|
||||
* hair colours through that route, and a null where it expects a date costs it the whole
|
||||
* response, not the field. Leave the key out, as the capture did.
|
||||
*/
|
||||
export interface CatalogAvatarItem {
|
||||
AvatarItemDesc: string
|
||||
@@ -160,8 +168,8 @@ export interface CatalogAvatarItem {
|
||||
TagList: string | null
|
||||
AvatarItemId: number | null
|
||||
IsBaseAvatarItem: boolean
|
||||
CreatedAt: string | null
|
||||
ThumbnailImage: string | null
|
||||
CreatedAt?: string
|
||||
ThumbnailImage?: string
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -201,8 +209,9 @@ export function toCatalogAvatarItem(row: CatalogRow): CatalogAvatarItem {
|
||||
TagList: row.tag_list,
|
||||
AvatarItemId: row.avatar_item_id,
|
||||
IsBaseAvatarItem: row.is_base_avatar_item === 1,
|
||||
CreatedAt: row.created_at,
|
||||
ThumbnailImage: row.thumbnail_image,
|
||||
// Absent rather than null when the row has none — see the record's note.
|
||||
...(row.created_at !== null ? { CreatedAt: row.created_at } : {}),
|
||||
...(row.thumbnail_image !== null ? { ThumbnailImage: row.thumbnail_image } : {}),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -152622,7 +152622,7 @@
|
||||
"Unique": true,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 20,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
@@ -152675,7 +152675,7 @@
|
||||
"Unique": true,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 20,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
@@ -152728,7 +152728,7 @@
|
||||
"Unique": true,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 20,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
@@ -152781,7 +152781,7 @@
|
||||
"Unique": true,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 20,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
@@ -152834,7 +152834,7 @@
|
||||
"Unique": true,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 20,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
@@ -152887,7 +152887,7 @@
|
||||
"Unique": true,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 20,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
@@ -156881,7 +156881,7 @@
|
||||
"Unique": false,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 50,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
@@ -156940,7 +156940,7 @@
|
||||
"Unique": false,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 50,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
@@ -156999,7 +156999,7 @@
|
||||
"Unique": false,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 50,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
@@ -157058,7 +157058,7 @@
|
||||
"Unique": false,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 50,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
@@ -157117,7 +157117,7 @@
|
||||
"Unique": false,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 50,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
@@ -157176,7 +157176,7 @@
|
||||
"Unique": false,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 50,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
@@ -157235,7 +157235,7 @@
|
||||
"Unique": false,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 50,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
@@ -157294,7 +157294,7 @@
|
||||
"Unique": false,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 50,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
@@ -157353,7 +157353,7 @@
|
||||
"Unique": false,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 50,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
@@ -157412,7 +157412,7 @@
|
||||
"Unique": false,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 50,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
@@ -157471,7 +157471,7 @@
|
||||
"Unique": false,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 50,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
@@ -157530,7 +157530,7 @@
|
||||
"Unique": false,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 50,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
@@ -157589,7 +157589,7 @@
|
||||
"Unique": false,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 50,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
@@ -157648,7 +157648,7 @@
|
||||
"Unique": false,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 50,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
@@ -157707,7 +157707,7 @@
|
||||
"Unique": false,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 50,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
@@ -157766,7 +157766,7 @@
|
||||
"Unique": false,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 50,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
@@ -157825,7 +157825,7 @@
|
||||
"Unique": false,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 50,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
@@ -157884,7 +157884,7 @@
|
||||
"Unique": false,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 50,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
@@ -157943,7 +157943,7 @@
|
||||
"Unique": false,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 50,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
@@ -158002,7 +158002,7 @@
|
||||
"Unique": false,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 50,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
@@ -158061,7 +158061,7 @@
|
||||
"Unique": false,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 50,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
@@ -158120,7 +158120,7 @@
|
||||
"Unique": false,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 50,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
@@ -158946,7 +158946,7 @@
|
||||
"Unique": false,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 30,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
@@ -158999,7 +158999,7 @@
|
||||
"Unique": false,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 30,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
@@ -159052,7 +159052,7 @@
|
||||
"Unique": false,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 30,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
@@ -159105,7 +159105,7 @@
|
||||
"Unique": false,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 30,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
@@ -159158,7 +159158,7 @@
|
||||
"Unique": false,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 30,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
@@ -159211,7 +159211,7 @@
|
||||
"Unique": false,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 30,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
@@ -165512,7 +165512,7 @@
|
||||
"Unique": false,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 30,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
@@ -165565,7 +165565,7 @@
|
||||
"Unique": false,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 30,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
@@ -165618,7 +165618,7 @@
|
||||
"Unique": false,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 30,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
@@ -165671,7 +165671,7 @@
|
||||
"Unique": false,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 30,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
@@ -165724,7 +165724,7 @@
|
||||
"Unique": false,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 30,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
@@ -172898,7 +172898,7 @@
|
||||
"Unique": false,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 20,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
@@ -172951,7 +172951,7 @@
|
||||
"Unique": false,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 20,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
@@ -173004,7 +173004,7 @@
|
||||
"Unique": false,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 20,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
@@ -173057,7 +173057,7 @@
|
||||
"Unique": false,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 20,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
@@ -173110,7 +173110,7 @@
|
||||
"Unique": false,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 20,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
@@ -173163,7 +173163,7 @@
|
||||
"Unique": false,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 20,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
@@ -183947,7 +183947,7 @@
|
||||
"Unique": false,
|
||||
"SubscribersOnly": false,
|
||||
"Rarity": 30,
|
||||
"Context": -1,
|
||||
"Context": 0,
|
||||
"Currency": 0,
|
||||
"CurrencyType": 0,
|
||||
"ItemCount": 1,
|
||||
|
||||
@@ -59,6 +59,14 @@ import type { StoreListing } from '../../../../apps/econ/src/catalog-load'
|
||||
* PNG per item that this server does not have.
|
||||
* gold a skin whose name carries `(Gold)` sells at `GOLD_SKIN_PRICE`, 100,000 tokens,
|
||||
* subscriber price the same: no discount, no sale.
|
||||
* context a drop whose `Context` is `NO_CONTEXT` (-1) gets 0. The dump carries -1 on
|
||||
* exactly 46 listings — the 22 permanent hair dyes and the 24 dice skins, the
|
||||
* things the live game sells from a surface of its own rather than the store page
|
||||
* — and every other listing says 0 or a positive category. Served with -1 the hair
|
||||
* dyes did not work; swapping those 22 listings for the ones the earlier generated
|
||||
* store carried (Context 0, ids and prices of its own) fixed them, and -1 is the
|
||||
* one member that set the dyes apart from the thousands of listings that work.
|
||||
* See `withStoreContext`.
|
||||
*
|
||||
* Everything else — `SubscriberDiscountPercent`, `SubscriberPrices`, custom-item listings, the
|
||||
* token bundles, the developer-tier items, `AvailableAt` dates — is the store as the game had
|
||||
@@ -78,6 +86,22 @@ const NEXT_UPDATE = '2226-06-14T00:12:20.1324853Z'
|
||||
export const blanksThumbnail = (listing: StoreListing): boolean =>
|
||||
listing.GiftDrop.EquipmentModificationGuid !== '' || listing.GiftDrop.ConsumableItemDesc !== ''
|
||||
|
||||
/** The `Context` the dump puts on a drop the store page does not sell: the hair dyes, the dice skins. */
|
||||
export const NO_CONTEXT = -1
|
||||
|
||||
/** The `Context` every ordinary listing carries — what the generated store gave the hair dyes. */
|
||||
const STORE_CONTEXT = 0
|
||||
|
||||
/** Whether a listing's drop carries the dump's out-of-store `Context`. */
|
||||
export const hasNoContext = (listing: StoreListing): boolean =>
|
||||
listing.GiftDrop.Context === NO_CONTEXT
|
||||
|
||||
/** The listing with its drop's `Context` brought in from {@link NO_CONTEXT} to the store's. */
|
||||
export const withStoreContext = (listing: StoreListing): StoreListing => ({
|
||||
...listing,
|
||||
GiftDrop: { ...listing.GiftDrop, Context: STORE_CONTEXT },
|
||||
})
|
||||
|
||||
/** A price list with its sale removed: the list price, nothing on top. */
|
||||
const withoutSale = <T extends { StorefrontSaleData: unknown }>(prices: T[] | null): T[] | null =>
|
||||
prices === null ? null : prices.map((p) => ({ ...p, StorefrontSaleData: null }))
|
||||
@@ -130,6 +154,14 @@ const build = new Command('build')
|
||||
item.SubscriberPrices = [priced]
|
||||
}
|
||||
|
||||
// Out-of-store drops brought onto the store page, see `withStoreContext`.
|
||||
let recontexted = 0
|
||||
for (const [i, item] of items.entries()) {
|
||||
if (!hasNoContext(item)) continue
|
||||
recontexted++
|
||||
items[i] = withStoreContext(item)
|
||||
}
|
||||
|
||||
writeFileSync(
|
||||
STOREFRONT_FILE,
|
||||
`${JSON.stringify({ ...dump, StoreItems: items, NextUpdate: NEXT_UPDATE }, null, '\t')}\n`
|
||||
@@ -152,7 +184,8 @@ const build = new Command('build')
|
||||
`✓ ${STOREFRONT_FILE}: ${items.length} listings from ${STORE_DUMP} ` +
|
||||
`(${repeats} repeated listing(s) dropped, ${sales} sale(s) removed) ` +
|
||||
`+ ${added} skin(s) from ${SKINS} at rarity ${UNLISTED_SKIN_RARITY}, ` +
|
||||
`${blanked} skin/consumable thumbnail(s) blanked, ${gold} gold skin(s) at ${GOLD_SKIN_PRICE}`
|
||||
`${blanked} skin/consumable thumbnail(s) blanked, ${gold} gold skin(s) at ${GOLD_SKIN_PRICE}, ` +
|
||||
`${recontexted} drop(s) with Context ${NO_CONTEXT} set to ${STORE_CONTEXT}`
|
||||
)
|
||||
)
|
||||
console.log(
|
||||
@@ -176,8 +209,9 @@ sf3-2025.json is the real 2025 store — ${STORE_DUMP} — with its captured
|
||||
sale removed, repeated listings collapsed and a far-future NextUpdate, plus the equipment skins
|
||||
the store never listed (${SKINS}) as rarity-${UNLISTED_SKIN_RARITY} listings numbered from
|
||||
${UNLISTED_SKIN_ID_BASE}; every skin's and consumable's ThumbnailImageName is ""; a "(Gold)"
|
||||
skin sells at ${GOLD_SKIN_PRICE} tokens with no discount. Ids, items and list prices are
|
||||
otherwise the dump's. \`runx catalog
|
||||
skin sells at ${GOLD_SKIN_PRICE} tokens with no discount; a drop with Context ${NO_CONTEXT} (the hair
|
||||
dyes and the dice skins) is served with Context 0. Ids, items and list prices are otherwise the
|
||||
dump's. \`runx catalog
|
||||
load\` reads the file this writes and nothing else, so a listing's PurchasableItemId is its
|
||||
catalog_id.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user