mirror of
https://github.com/djdevin/recflare.git
synced 2026-10-26 07:28:53 -07:00
[store] 2025 store
This commit is contained in:
@@ -16,6 +16,9 @@
|
||||
* and each save's `ThumbnailFileName` put under `avatar/`, an existing id REPLACED, nothing
|
||||
* deleted.
|
||||
*
|
||||
* The export's `Price` is mostly 0; run `price-custom-avatar-items.ts` against it first to fill
|
||||
* in prices from the storefront dump.
|
||||
*
|
||||
* The saves name assetbundles by bare filename; the migration does nothing about those files,
|
||||
* which have to be served from wherever the client fetches them.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Fills in `Price` on an export of first-party custom avatar items from a storefront dump,
|
||||
* rewriting the export in place so `import-custom-avatar-items.ts` / `just admin cai-load`
|
||||
* carry the price into the table.
|
||||
*
|
||||
* bun apps/api/scripts/price-custom-avatar-items.ts <export.json> <storefront.json>
|
||||
*
|
||||
* The export (e.g. `apps/econ/static/db/2025-1-cai.json`) mostly carries `Price: 0`. The
|
||||
* storefront (e.g. `Watch_EnumValue_3.json`, a `{ StoreItems: [...] }` page) prices the same
|
||||
* items, joined on `GiftDrop.AvatarItemInfo.DownloadableAvatarItemId` = `CustomAvatarItemId`.
|
||||
* The price taken is the base token price (`CurrencyType` 2), ignoring any sale. The storefront
|
||||
* only prices what the export already holds: nothing is added from it.
|
||||
*
|
||||
* An item the storefront doesn't list keeps the price it had, unless that price is 0, which
|
||||
* would make it free to buy. Those are the Cryptid Creek event items and the Evergrown Hip Pack,
|
||||
* all rarity-50 rewards that were never sold, so they get the catalog's rarity-50 price.
|
||||
*/
|
||||
|
||||
import { readFileSync, writeFileSync } from 'node:fs'
|
||||
|
||||
import { priceForRarity } from '../../econ/src/catalog-load'
|
||||
|
||||
const TOKENS = 2
|
||||
|
||||
/** The rarity an item missing from the storefront is priced as, when it has no price. */
|
||||
const UNLISTED_RARITY = 50
|
||||
|
||||
interface StoreItem {
|
||||
GiftDrop?: { AvatarItemInfo?: { DownloadableAvatarItemId?: string | null } | null }
|
||||
Prices?: Array<{ CurrencyType: number; Price: number }>
|
||||
}
|
||||
|
||||
const [exportPath, storefrontPath] = process.argv.slice(2)
|
||||
if (!exportPath || !storefrontPath) {
|
||||
console.error(
|
||||
'usage: bun apps/api/scripts/price-custom-avatar-items.ts <export.json> <storefront.json>'
|
||||
)
|
||||
process.exit(2)
|
||||
}
|
||||
|
||||
// Tolerate the BOM a .NET export carries (`JSON.parse` rejects U+FEFF).
|
||||
const readJson = (path: string) => JSON.parse(readFileSync(path, 'utf8').replace(/^/, ''))
|
||||
|
||||
const items: Array<{ CustomAvatarItemId: string; Name: string; Price: number }> =
|
||||
readJson(exportPath)
|
||||
const storefront: { StoreItems: StoreItem[] } = readJson(storefrontPath)
|
||||
|
||||
const prices = new Map<string, number>()
|
||||
for (const storeItem of storefront.StoreItems) {
|
||||
const id = storeItem.GiftDrop?.AvatarItemInfo?.DownloadableAvatarItemId?.toLowerCase()
|
||||
const price = storeItem.Prices?.find((p) => p.CurrencyType === TOKENS)?.Price
|
||||
if (id && price !== undefined && !prices.has(id)) {
|
||||
prices.set(id, price)
|
||||
}
|
||||
}
|
||||
|
||||
let priced = 0
|
||||
let changed = 0
|
||||
const unmatched: string[] = []
|
||||
for (const item of items) {
|
||||
let price = prices.get(item.CustomAvatarItemId.toLowerCase())
|
||||
if (price === undefined) {
|
||||
unmatched.push(item.Name)
|
||||
if (item.Price) continue
|
||||
price = priceForRarity(UNLISTED_RARITY)
|
||||
}
|
||||
priced++
|
||||
if (item.Price !== price) {
|
||||
if (item.Price) {
|
||||
console.warn(`${item.Name}: ${item.Price} -> ${price}`)
|
||||
}
|
||||
item.Price = price
|
||||
changed++
|
||||
}
|
||||
}
|
||||
|
||||
writeFileSync(exportPath, JSON.stringify(items, null, 2) + '\n')
|
||||
console.log(`priced ${priced}/${items.length} items (${changed} changed) in ${exportPath}`)
|
||||
if (unmatched.length) {
|
||||
console.log(
|
||||
`not in the storefront (kept their price, or rarity ${UNLISTED_RARITY} if free): ${unmatched.join(', ')}`
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user