mirror of
https://github.com/djdevin/recflare.git
synced 2026-10-25 23:18:51 -07:00
[econ] fix consumable/skin boxes
This commit is contained in:
@@ -259,6 +259,13 @@ inconsistency here without checking the client first.
|
|||||||
each stored and pushed as `MessageReceived`. The `RelationshipChanged` frames only
|
each stored and pushed as `MessageReceived`. The `RelationshipChanged` frames only
|
||||||
refresh relationship state: sent alone, the request "worked" server-side and the target
|
refresh relationship state: sent alone, the request "worked" server-side and the target
|
||||||
saw nothing. Same pattern as the cheer.
|
saw nothing. Same pattern as the cheer.
|
||||||
|
- A gift box's `AvatarItemType` (`econ`: the stored box in `GET /api/avatar/v2/gifts`, `buyItem`'s
|
||||||
|
`BalanceUpdates[].Data`, `bulkpurchase`'s `GiftPackage`, and the `GiftPackageReceived*` frame)
|
||||||
|
must be NULL for anything that is not an avatar item — a consumable, a skin, a box. The client
|
||||||
|
routes the box on that field before it reads what else the box names, so a 0 sends it looking
|
||||||
|
for avatar item type 0 and it fails with "can't find avatar item". The 2025 store lists every
|
||||||
|
non-avatar drop with `AvatarItemType: null` for this reason; `boxAvatarItemType` applies it on
|
||||||
|
every surface, so don't coalesce it with `?? 0` anywhere a box is built.
|
||||||
- Accessibility is sent as the `RoomAccessibility` enum NAME on
|
- Accessibility is sent as the `RoomAccessibility` enum NAME on
|
||||||
`rooms` `PUT /rooms/:id/subrooms/:sid/accessibility` (`accessibility=Private`), not the
|
`rooms` `PUT /rooms/:id/subrooms/:sid/accessibility` (`accessibility=Private`), not the
|
||||||
ordinal the room-level `/rooms/:id/accessibility` takes. The enum has five members
|
ordinal the room-level `/rooms/:id/accessibility` takes. The enum has five members
|
||||||
|
|||||||
@@ -1099,6 +1099,21 @@ function truncateGiftMessage(message: string): string {
|
|||||||
* with nothing but the row to say who sent it or why. They default to Coach and the drop's
|
* with nothing but the row to say who sent it or why. They default to Coach and the drop's
|
||||||
* own context — a box the server handed over on nobody's behalf.
|
* own context — a box the server handed over on nobody's behalf.
|
||||||
*/
|
*/
|
||||||
|
/**
|
||||||
|
* `AvatarItemType` as a gift box carries it: the drop's own for an avatar item, NULL for
|
||||||
|
* anything else — a consumable, a skin, a box, a token bundle.
|
||||||
|
*
|
||||||
|
* The client routes a box by this field before it looks at what else the box names: a 0
|
||||||
|
* reads as "an avatar item of type 0", and a consumable or skin box sent with 0 fails with
|
||||||
|
* "can't find avatar item" because there is no such item to find. The 2025 store lists every
|
||||||
|
* non-avatar drop with `AvatarItemType: null` for exactly that reason, and this is what the
|
||||||
|
* old `?? 0` coalescing used to erase. Applied at every surface a box is serialized on — the
|
||||||
|
* stored box, both purchase responses and the hub frame — so they cannot disagree.
|
||||||
|
*/
|
||||||
|
function boxAvatarItemType(drop: StoreGiftDrop): number | null {
|
||||||
|
return drop.AvatarItemDesc !== '' ? (drop.AvatarItemType ?? 0) : null
|
||||||
|
}
|
||||||
|
|
||||||
function toGiftContent(
|
function toGiftContent(
|
||||||
giftDrop: StoreGiftDrop,
|
giftDrop: StoreGiftDrop,
|
||||||
message: string,
|
message: string,
|
||||||
@@ -1116,7 +1131,7 @@ function toGiftContent(
|
|||||||
ConsumableMappingId: consumableMappingId,
|
ConsumableMappingId: consumableMappingId,
|
||||||
ConsumablePreExistingCount: consumablePreExistingCount,
|
ConsumablePreExistingCount: consumablePreExistingCount,
|
||||||
AvatarItemDesc: giftDrop.AvatarItemDesc,
|
AvatarItemDesc: giftDrop.AvatarItemDesc,
|
||||||
AvatarItemType: giftDrop.AvatarItemType,
|
AvatarItemType: boxAvatarItemType(giftDrop),
|
||||||
CurrencyType: giftDrop.CurrencyType,
|
CurrencyType: giftDrop.CurrencyType,
|
||||||
Currency: giftDrop.Currency,
|
Currency: giftDrop.Currency,
|
||||||
Xp: giftDrop.Xp ?? 0,
|
Xp: giftDrop.Xp ?? 0,
|
||||||
@@ -1197,7 +1212,7 @@ async function pushGiftReceived(
|
|||||||
FromPlayerId: fromPlayerId,
|
FromPlayerId: fromPlayerId,
|
||||||
ConsumableItemDesc: gift.drop.ConsumableItemDesc,
|
ConsumableItemDesc: gift.drop.ConsumableItemDesc,
|
||||||
AvatarItemDesc: gift.drop.AvatarItemDesc,
|
AvatarItemDesc: gift.drop.AvatarItemDesc,
|
||||||
AvatarItemType: gift.drop.AvatarItemType ?? 0,
|
AvatarItemType: boxAvatarItemType(gift.drop),
|
||||||
EquipmentPrefabName: gift.drop.EquipmentPrefabName,
|
EquipmentPrefabName: gift.drop.EquipmentPrefabName,
|
||||||
EquipmentModificationGuid: gift.drop.EquipmentModificationGuid,
|
EquipmentModificationGuid: gift.drop.EquipmentModificationGuid,
|
||||||
CurrencyType: gift.drop.CurrencyType,
|
CurrencyType: gift.drop.CurrencyType,
|
||||||
@@ -1347,7 +1362,8 @@ function toSkinStoreItem(row: CatalogRow): StoreItem {
|
|||||||
Tooltip: row.tooltip ?? '',
|
Tooltip: row.tooltip ?? '',
|
||||||
ConsumableItemDesc: '',
|
ConsumableItemDesc: '',
|
||||||
AvatarItemDesc: '',
|
AvatarItemDesc: '',
|
||||||
AvatarItemType: 0,
|
// A skin is no avatar item: null, as the store lists every skin.
|
||||||
|
AvatarItemType: null,
|
||||||
EquipmentPrefabName: row.prefab_name ?? '',
|
EquipmentPrefabName: row.prefab_name ?? '',
|
||||||
EquipmentModificationGuid: row.item_key,
|
EquipmentModificationGuid: row.item_key,
|
||||||
Rarity: row.rarity,
|
Rarity: row.rarity,
|
||||||
@@ -1668,7 +1684,7 @@ function toBalanceUpdateData(
|
|||||||
FromPlayerId: fromPlayerId,
|
FromPlayerId: fromPlayerId,
|
||||||
ConsumableItemDesc: drop.ConsumableItemDesc,
|
ConsumableItemDesc: drop.ConsumableItemDesc,
|
||||||
AvatarItemDesc: drop.AvatarItemDesc,
|
AvatarItemDesc: drop.AvatarItemDesc,
|
||||||
AvatarItemType: drop.AvatarItemType ?? 0,
|
AvatarItemType: boxAvatarItemType(drop),
|
||||||
EquipmentPrefabName: drop.EquipmentPrefabName,
|
EquipmentPrefabName: drop.EquipmentPrefabName,
|
||||||
EquipmentModificationGuid: drop.EquipmentModificationGuid,
|
EquipmentModificationGuid: drop.EquipmentModificationGuid,
|
||||||
CurrencyType: drop.CurrencyType,
|
CurrencyType: drop.CurrencyType,
|
||||||
@@ -1816,7 +1832,7 @@ function toGiftPackage(
|
|||||||
PlayerId: playerId,
|
PlayerId: playerId,
|
||||||
FromPlayerId: fromPlayerId,
|
FromPlayerId: fromPlayerId,
|
||||||
ConsumableItemDesc: drop.ConsumableItemDesc,
|
ConsumableItemDesc: drop.ConsumableItemDesc,
|
||||||
AvatarItemType: drop.AvatarItemType ?? 0,
|
AvatarItemType: boxAvatarItemType(drop),
|
||||||
AvatarItemDesc: drop.AvatarItemDesc,
|
AvatarItemDesc: drop.AvatarItemDesc,
|
||||||
CustomAvatarItemId: null,
|
CustomAvatarItemId: null,
|
||||||
EquipmentPrefabName: drop.EquipmentPrefabName,
|
EquipmentPrefabName: drop.EquipmentPrefabName,
|
||||||
|
|||||||
@@ -2827,7 +2827,7 @@ describe('econ endpoints', () => {
|
|||||||
Data: Array<{
|
Data: Array<{
|
||||||
ConsumableItemDesc: string
|
ConsumableItemDesc: string
|
||||||
AvatarItemDesc: string
|
AvatarItemDesc: string
|
||||||
AvatarItemType: number
|
AvatarItemType: number | null
|
||||||
FromPlayerId: number
|
FromPlayerId: number
|
||||||
}>
|
}>
|
||||||
}>
|
}>
|
||||||
@@ -2837,8 +2837,9 @@ describe('econ endpoints', () => {
|
|||||||
const drop = body.BalanceUpdates[0].Data[0]
|
const drop = body.BalanceUpdates[0].Data[0]
|
||||||
expect(drop.ConsumableItemDesc).toBe(consumableDesc)
|
expect(drop.ConsumableItemDesc).toBe(consumableDesc)
|
||||||
expect(drop.AvatarItemDesc).toBe('')
|
expect(drop.AvatarItemDesc).toBe('')
|
||||||
// A consumable's AvatarItemType is null in the catalog; the response coalesces it to 0.
|
// A consumable's AvatarItemType is NULL, as the store lists it — never 0, which the
|
||||||
expect(drop.AvatarItemType).toBe(0)
|
// client reads as an avatar item and fails on ("can't find avatar item").
|
||||||
|
expect(drop.AvatarItemType).toBeNull()
|
||||||
// A self-buy is attributed to the "Coach" system account (id 1).
|
// A self-buy is attributed to the "Coach" system account (id 1).
|
||||||
expect(drop.FromPlayerId).toBe(1)
|
expect(drop.FromPlayerId).toBe(1)
|
||||||
|
|
||||||
@@ -4888,7 +4889,9 @@ describe('econ endpoints', () => {
|
|||||||
FromPlayerId: 1,
|
FromPlayerId: 1,
|
||||||
ConsumableItemDesc: '',
|
ConsumableItemDesc: '',
|
||||||
AvatarItemDesc: gift.AvatarItemDesc,
|
AvatarItemDesc: gift.AvatarItemDesc,
|
||||||
AvatarItemType: gift.AvatarItemType,
|
// The week's reward is a SKIN, so this is null whatever the block says: a 0 would
|
||||||
|
// send the client looking for an avatar item that does not exist.
|
||||||
|
AvatarItemType: gift.AvatarItemDesc === '' ? null : gift.AvatarItemType,
|
||||||
EquipmentPrefabName: gift.EquipmentPrefabName,
|
EquipmentPrefabName: gift.EquipmentPrefabName,
|
||||||
EquipmentModificationGuid: gift.EquipmentModificationGuid,
|
EquipmentModificationGuid: gift.EquipmentModificationGuid,
|
||||||
CurrencyType: 0,
|
CurrencyType: 0,
|
||||||
|
|||||||
Reference in New Issue
Block a user