Public Access
feat(points): add effect engines and effects shop
This commit is contained in:
1 parent
2ec5833f29
commit
dd45c0b5f6
12 files changed
+697
-11
No files matched your search
+53
-1
@@ -1,10 +1,11 @@
|
||||
import {Swords, Trophy} from 'lucide-react';
|
||||
import {apiFetch, request} from './api.js';
|
||||
import {storage} from './storage.js';
|
||||
|
||||
const REWARDS_URL = `${import.meta.env.VITE_CORE_API_URL}/api/rewards`;
|
||||
const JSON_HEADERS = {'Content-Type': 'application/json'};
|
||||
|
||||
export const SLOTS = ['AVATAR_BORDER', 'BADGE', 'ACCENT', 'BANNER_FRAME'];
|
||||
export const SLOTS = ['AVATAR_BORDER', 'BADGE', 'ACCENT', 'BANNER_FRAME', 'COMPLETED_EFFECT', 'PAGE_BACKGROUND', 'BANNER_BACKGROUND'];
|
||||
|
||||
// The shop catalog of Core (ShopItem) as looks: only these classes are ever applied, never a string from the server.
|
||||
// An unknown code is skipped.
|
||||
@@ -17,6 +18,28 @@ export const LOOKS = {
|
||||
ACCENT_TEAL: {accent: 'teal'},
|
||||
FRAME_GOLD: {className: 'ring-2 ring-amber-400 ring-offset-2 ring-offset-background'},
|
||||
FRAME_NEON: {className: 'ring-2 ring-fuchsia-500 ring-offset-2 ring-offset-background shadow-[0_0_14px] shadow-fuchsia-500/60'},
|
||||
// Completed effects (effects.css): `cover` is the class set of a tile, `detail` the decoration of the details page.
|
||||
FX_GOLD_SHINE: {cover: 'fx fx-cover-gold-shine outline outline-2 -outline-offset-2 outline-amber-400', detail: 'fx-detail-gold-shine'},
|
||||
FX_NEON_PULSE: {cover: 'fx fx-cover-neon-pulse outline outline-2 -outline-offset-2 outline-primary', detail: 'fx-detail-neon-pulse'},
|
||||
FX_HOLO: {cover: 'fx fx-cover-holo', detail: 'fx-detail-holo'},
|
||||
FX_EMBER: {cover: 'fx fx-cover-ember', detail: 'fx-detail-ember'},
|
||||
// Page and banner backgrounds: BG_ and BN_ of a name share one engine (components/EffectBackground.jsx).
|
||||
BG_TRIANGLES: {effect: 'triangles'},
|
||||
BG_STARFIELD: {effect: 'starfield'},
|
||||
BG_PIXELS: {effect: 'pixels'},
|
||||
BG_AURORA: {effect: 'aurora'},
|
||||
BG_MESH: {effect: 'mesh'},
|
||||
BG_GRID: {effect: 'grid'},
|
||||
BG_WAVES: {effect: 'waves'},
|
||||
BG_BOKEH: {effect: 'bokeh'},
|
||||
BN_TRIANGLES: {effect: 'triangles'},
|
||||
BN_STARFIELD: {effect: 'starfield'},
|
||||
BN_PIXELS: {effect: 'pixels'},
|
||||
BN_AURORA: {effect: 'aurora'},
|
||||
BN_MESH: {effect: 'mesh'},
|
||||
BN_GRID: {effect: 'grid'},
|
||||
BN_WAVES: {effect: 'waves'},
|
||||
BN_BOKEH: {effect: 'bokeh'},
|
||||
};
|
||||
|
||||
/** The premium accent worn, or null (then the appearance accent applies). */
|
||||
@@ -25,12 +48,41 @@ export const equippedAccent = (equipped) => LOOKS[equipped?.ACCENT]?.accent ?? n
|
||||
/** The classes of the item worn in a slot, or undefined. */
|
||||
export const lookClass = (equipped, slot) => LOOKS[equipped?.[slot]]?.className;
|
||||
|
||||
// Only a COMPLETED game shows the completed effect (FINISHED is not 100 %).
|
||||
const completedLook = (equipped, status) => (status === 'COMPLETED' ? LOOKS[equipped?.COMPLETED_EFFECT] : undefined);
|
||||
|
||||
/** The cover classes of the completed effect worn, or undefined. */
|
||||
export const coverEffect = (equipped, status) => completedLook(equipped, status)?.cover;
|
||||
|
||||
/** The details page decoration class of the completed effect worn, or undefined. */
|
||||
export const detailEffect = (equipped, status) => completedLook(equipped, status)?.detail;
|
||||
|
||||
/** The effect name of the page background worn, or null (then the game art applies). */
|
||||
export const backgroundEffect = (equipped) => LOOKS[equipped?.PAGE_BACKGROUND]?.effect ?? null;
|
||||
|
||||
/** The effect name of the banner background worn, or null. */
|
||||
export const bannerEffect = (equipped) => LOOKS[equipped?.BANNER_BACKGROUND]?.effect ?? null;
|
||||
|
||||
/** The badge worn: `{code, Icon, className}` or null. */
|
||||
export const equippedBadge = (equipped) => {
|
||||
const look = LOOKS[equipped?.BADGE];
|
||||
return look?.Icon ? {code: equipped.BADGE, ...look} : null;
|
||||
};
|
||||
|
||||
const EQUIPPED_STORAGE_KEY = 'tbp_equipped';
|
||||
|
||||
/** The last known worn looks, so the first paint does not flash the item 10 art before the wallet answers. */
|
||||
export const readCachedEquipped = () => {
|
||||
try {
|
||||
const value = JSON.parse(storage.get(EQUIPPED_STORAGE_KEY));
|
||||
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
export const cacheEquipped = (equipped) => storage.set(EQUIPPED_STORAGE_KEY, JSON.stringify(equipped));
|
||||
|
||||
/** GET /api/rewards/wallet on Core: `{ok, status, data}`. Data is the balance, catalog, owned codes, worn looks and history. */
|
||||
export const loadWallet = () => request(`${REWARDS_URL}/wallet`);
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import {describe, expect, it} from 'vitest';
|
||||
import {backgroundEffect, bannerEffect, coverEffect, detailEffect, LOOKS} from './rewards.js';
|
||||
|
||||
describe('effects worn', () => {
|
||||
const equipped = {COMPLETED_EFFECT: 'FX_GOLD_SHINE', PAGE_BACKGROUND: 'BG_STARFIELD', BANNER_BACKGROUND: 'BN_WAVES'};
|
||||
|
||||
it('shows the completed effect only on a COMPLETED game', () => {
|
||||
expect(coverEffect(equipped, 'COMPLETED')).toContain('fx-cover-gold-shine');
|
||||
expect(detailEffect(equipped, 'COMPLETED')).toBe('fx-detail-gold-shine');
|
||||
expect(coverEffect(equipped, 'FINISHED')).toBeUndefined();
|
||||
expect(detailEffect(equipped, 'FINISHED')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('ignores an unknown or missing code', () => {
|
||||
expect(coverEffect({COMPLETED_EFFECT: 'FX_FUTURE'}, 'COMPLETED')).toBeUndefined();
|
||||
expect(coverEffect(undefined, 'COMPLETED')).toBeUndefined();
|
||||
expect(backgroundEffect({PAGE_BACKGROUND: 'BG_FUTURE'})).toBeNull();
|
||||
expect(bannerEffect({})).toBeNull();
|
||||
});
|
||||
|
||||
it('maps a background and a banner of one name to the same engine', () => {
|
||||
expect(backgroundEffect(equipped)).toBe('starfield');
|
||||
expect(bannerEffect(equipped)).toBe('waves');
|
||||
expect(LOOKS.BG_GRID.effect).toBe(LOOKS.BN_GRID.effect);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user