mirror of
https://github.com/djdevin/recflare.git
synced 2026-09-09 23:21:30 -07:00
[econ] better quest rewards (for now until chests figured out)
This commit is contained in:
@@ -33,6 +33,7 @@ import avatarItemsJson from '../../../static/db/avatar-items.json'
|
||||
// caller's build, and these assertions are about the file's CONTENTS.
|
||||
import carriedItems from '../../../static/db/consumables.json'
|
||||
import skinsJson from '../../../static/db/skins.json'
|
||||
import questRewards from '../../../static/quest-rewards.json'
|
||||
import sf32025 from '../../../static/storefronts/sf3-2025.json'
|
||||
import sf3 from '../../../static/storefronts/sf3.json'
|
||||
import { SCHEMA_DDL } from '../../avatar-db'
|
||||
@@ -3113,6 +3114,7 @@ describe('econ endpoints', () => {
|
||||
AvatarItemDesc: string
|
||||
ConsumableItemDesc: string
|
||||
GiftRarity: number
|
||||
GiftContext: number
|
||||
}>
|
||||
}
|
||||
|
||||
@@ -3512,6 +3514,92 @@ describe('econ endpoints', () => {
|
||||
expect(held.map((cons) => cons.ConsumableItemDesc)).toContain(consumableBox?.ConsumableItemDesc)
|
||||
})
|
||||
|
||||
test('a giftContext naming a quest-rewards.json key pays one of that activity’s rewards', async () => {
|
||||
const request = async (body: string) =>
|
||||
exports.default.fetch(`${ORIGIN}/api/gamerewards/v1/request`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...(await bearer('83')),
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body,
|
||||
})
|
||||
await drainFrames()
|
||||
|
||||
// Quest_Goblin_S: forty avatar-item rewards, all at the goblin quest's S-rank context.
|
||||
const goblin = await request(
|
||||
'rewardType=PostGameActivity&Message=Quest%20complete&giftContext=Quest_Goblin_S'
|
||||
)
|
||||
expect(goblin.status).toBe(200)
|
||||
expect(await goblin.json()).toEqual([])
|
||||
const boxes = await giftBoxes('83')
|
||||
expect(boxes).toHaveLength(1)
|
||||
const box = boxes[0]
|
||||
expect(box).toMatchObject({ Xp: 5, Message: 'Quest complete', GiftContext: 4003 })
|
||||
expect(box?.AvatarItemDesc).not.toBe('')
|
||||
const row = questRewards.Quest_Goblin_S.find((r) => r.AvatarItemDesc === box?.AvatarItemDesc)
|
||||
expect(row).toBeDefined()
|
||||
expect(box?.GiftRarity).toBe(row?.GiftRarity)
|
||||
// …and the item is in the inventory, not just on the box.
|
||||
const items = await exports.default.fetch(`${ORIGIN}/api/avatar/v4/items`, {
|
||||
headers: await bearer('83'),
|
||||
})
|
||||
const owned = (await items.json()) as Array<{ avatarItemDesc: string }>
|
||||
expect(owned.map((i) => i.avatarItemDesc)).toContain(box?.AvatarItemDesc)
|
||||
// The box announces the activity's context, not the generic GameRewards one.
|
||||
const frames = await drainFrames()
|
||||
expect(frames[0]?.notificationType).toBe(NotificationType.GiftPackageReceivedImmediate)
|
||||
expect(frames[0]?.payload).toMatchObject({
|
||||
GiftContext: 4003,
|
||||
AvatarItemDesc: box?.AvatarItemDesc,
|
||||
})
|
||||
|
||||
// Lasertag's single reward is 50 Laser Tag tickets: credited to the balance, no item.
|
||||
const before = await getBalance(
|
||||
env.DB,
|
||||
83,
|
||||
CurrencyType.LaserTagTickets,
|
||||
DEFAULT_STARTING_TOKENS
|
||||
)
|
||||
expect((await request('rewardType=PostGameActivity&giftContext=Lasertag')).status).toBe(200)
|
||||
expect(
|
||||
await getBalance(env.DB, 83, CurrencyType.LaserTagTickets, DEFAULT_STARTING_TOKENS)
|
||||
).toBe(before + 50)
|
||||
const ticketBox = (await giftBoxes('83'))[1]
|
||||
expect(ticketBox).toMatchObject({
|
||||
Currency: 50,
|
||||
CurrencyType: CurrencyType.LaserTagTickets,
|
||||
AvatarItemDesc: '',
|
||||
GiftContext: 9000,
|
||||
})
|
||||
const ticketFrames = await drainFrames()
|
||||
expect(ticketFrames.map((f) => f.notificationType)).toContain(
|
||||
NotificationType.StorefrontBalanceUpdate
|
||||
)
|
||||
|
||||
// An activity the table doesn't know pays the plain XP box, as before. (The LAST box:
|
||||
// the two claims above also crossed level 1, and that level-up box sits in between.)
|
||||
expect((await request('rewardType=PostGameActivity&giftContext=Bowling')).status).toBe(200)
|
||||
const plain = (await giftBoxes('83')).at(-1)
|
||||
expect(plain).toMatchObject({ Xp: 5, AvatarItemDesc: '', Currency: 0, GiftContext: 50 })
|
||||
|
||||
// A reward the player already owns is never drawn again: Dodgeball has three rows, so
|
||||
// three claims hand over all three, and a fourth — nothing left to give — pays the
|
||||
// plain XP box rather than a duplicate.
|
||||
const dodgeball = questRewards.Dodgeball.map((r) => r.AvatarItemDesc)
|
||||
const handed: string[] = []
|
||||
for (let i = 0; i < 4; i++) {
|
||||
await env.DB.prepare(
|
||||
"DELETE FROM reward_status WHERE account_id = 83 AND gift_context = 'Dodgeball'"
|
||||
).run()
|
||||
expect((await request('rewardType=PostGameActivity&giftContext=Dodgeball')).status).toBe(200)
|
||||
const latest = (await giftBoxes('83')).findLast((b) => b.GiftContext === 8000 || b.GiftContext === 50)
|
||||
if (i < 3) handed.push(latest?.AvatarItemDesc as string)
|
||||
else expect(latest).toMatchObject({ AvatarItemDesc: '', GiftContext: 50 })
|
||||
}
|
||||
expect(handed.toSorted()).toEqual(dodgeball.toSorted())
|
||||
})
|
||||
|
||||
test('POST /api/gamerewards/v1/request is 401 without a token, and ignores a typeless ask', async () => {
|
||||
const anon = await exports.default.fetch(`${ORIGIN}/api/gamerewards/v1/request`, {
|
||||
method: 'POST',
|
||||
|
||||
Reference in New Issue
Block a user