feat: rare values panel + fortune wheel UI + prize editor

Toolbar buttons, FortuneWheelView (animated wheel, prize icons, recent winners),
RareValuesView (diamond price guide + staff prize-editor tab), furni infostand
value line, useFortuneWheel/useRareValues hooks, it/en text examples.
This commit is contained in:
medievalshell
2026-05-28 02:39:02 +02:00
parent 00fbdc6f6d
commit 61aceaa422
13 changed files with 602 additions and 2 deletions
@@ -0,0 +1,69 @@
import { IWheelAdminPrize, IWheelAdminPrizeEdit, IWheelPrize, IWheelRecentWin, WheelAdminGetPrizesComposer, WheelAdminPrizesEvent, WheelAdminSavePrizesComposer, WheelBuySpinComposer, WheelDataEvent, WheelOpenComposer, WheelRecentWinsEvent, WheelResultEvent, WheelSpinComposer } from '@nitrots/nitro-renderer';
import { useCallback, useState } from 'react';
import { useBetween } from 'use-between';
import { SendMessageComposer } from '../../api';
import { useMessageEvent } from '../events';
// Fortune wheel state + actions. Shared via useBetween so the event listeners
// register once regardless of how many components read it.
const useFortuneWheelState = () =>
{
const [ freeSpins, setFreeSpins ] = useState(0);
const [ extraSpins, setExtraSpins ] = useState(0);
const [ spinCost, setSpinCost ] = useState(0);
const [ spinCostType, setSpinCostType ] = useState(-1);
const [ prizes, setPrizes ] = useState<IWheelPrize[]>([]);
const [ recentWins, setRecentWins ] = useState<IWheelRecentWin[]>([]);
const [ pendingPrizeId, setPendingPrizeId ] = useState<number>(-1);
const [ isSpinning, setIsSpinning ] = useState(false);
const [ adminPrizes, setAdminPrizes ] = useState<IWheelAdminPrize[]>([]);
useMessageEvent<WheelAdminPrizesEvent>(WheelAdminPrizesEvent, event =>
{
setAdminPrizes(event.getParser().prizes);
});
useMessageEvent<WheelDataEvent>(WheelDataEvent, event =>
{
const parser = event.getParser();
setFreeSpins(parser.freeSpins);
setExtraSpins(parser.extraSpins);
setSpinCost(parser.spinCost);
setSpinCostType(parser.spinCostType);
setPrizes(parser.prizes);
});
useMessageEvent<WheelResultEvent>(WheelResultEvent, event =>
{
setPendingPrizeId(event.getParser().prizeId);
setIsSpinning(true);
});
useMessageEvent<WheelRecentWinsEvent>(WheelRecentWinsEvent, event =>
{
setRecentWins(event.getParser().wins);
});
const open = useCallback(() => SendMessageComposer(new WheelOpenComposer()), []);
const spin = useCallback(() =>
{
setIsSpinning(prev =>
{
if(!prev) SendMessageComposer(new WheelSpinComposer());
return prev;
});
}, []);
const buySpin = useCallback(() => SendMessageComposer(new WheelBuySpinComposer()), []);
const finishSpin = useCallback(() =>
{
setIsSpinning(false);
setPendingPrizeId(-1);
}, []);
const loadAdminPrizes = useCallback(() => SendMessageComposer(new WheelAdminGetPrizesComposer()), []);
const saveAdminPrizes = useCallback((prizes: IWheelAdminPrizeEdit[]) => SendMessageComposer(new WheelAdminSavePrizesComposer(prizes)), []);
return { freeSpins, extraSpins, spinCost, spinCostType, prizes, recentWins, pendingPrizeId, isSpinning, open, spin, buySpin, finishSpin, adminPrizes, loadAdminPrizes, saveAdminPrizes };
};
export const useFortuneWheel = () => useBetween(useFortuneWheelState);
+2
View File
@@ -4,6 +4,7 @@ export * from './camera';
export * from './catalog';
export * from './chat-history';
export * from './events';
export * from './fortune-wheel/useFortuneWheel';
export * from './friends';
export * from './game-center';
export * from './groups';
@@ -14,6 +15,7 @@ export * from './mod-tools';
export * from './navigator';
export * from './notification';
export * from './purse';
export * from './rare-values/useRareValues';
export * from './rooms';
export * from './rooms/engine';
export * from './rooms/promotes';
+31
View File
@@ -0,0 +1,31 @@
import { IRareValue, RareValuesEvent, RequestRareValuesComposer } from '@nitrots/nitro-renderer';
import { useCallback, useEffect, useState } from 'react';
import { useBetween } from 'use-between';
import { SendMessageComposer } from '../../api';
import { useMessageEvent } from '../events';
// spriteId -> catalog value, fetched once from the server (RareValuesComposer).
// Shared across all consumers via useBetween so the request fires a single time.
// Read by both the furni infostand and the toolbar "Valore Rari" panel.
const useRareValuesState = () =>
{
const [ values, setValues ] = useState<Map<number, IRareValue>>(() => new Map());
const [ loaded, setLoaded ] = useState(false);
useMessageEvent<RareValuesEvent>(RareValuesEvent, event =>
{
setValues(event.getParser().values);
setLoaded(true);
});
useEffect(() =>
{
SendMessageComposer(new RequestRareValuesComposer());
}, []);
const getValue = useCallback((spriteId: number): IRareValue => (values.get(spriteId) ?? null), [ values ]);
return { values, loaded, getValue };
};
export const useRareValues = () => useBetween(useRareValuesState);