mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-20 07:26:19 +00:00
feat(fortune-wheel): celebration reveal, spin animation, prize editor add/remove
Player experience: - Tiered win celebration overlay (WheelWinReveal): quiet message for the "nothing" slice, lighter reveal for common prizes, full confetti + jackpot glow for rare ones. Rarity classified client-side by type + amount (wheelPrizeTier), shared icon rendering (wheelPrizeIcon). - Three-phase spin motion (wind-back -> overshoot -> settle) with a reduced-motion fast path; responsive wheel scaling via ResizeObserver. Reveal-timing fix: - The server pushes the refreshed winners list (which already contains the just-won prize) the instant it answers the spin, ~5s before the wheel stops. useFortuneWheel now buffers that update mid-spin and flushes it in finishSpin so the prize is no longer spoiled in the winners panel. - handleTransitionEnd only reacts to the wheel's own transform transition, so a child icon's bubbling transitionend can't advance the spin phase machine early. Prize editor (admin): - Add/Remove prize buttons in FortuneWheelSettingsView. New rows carry a negative temp id collapsed to 0 on the wire (server inserts); removed rows are simply omitted (server soft-disables). Requires the matching emulator change to WheelManager.savePrize / WheelAdminSavePrizesEvent. i18n: wheel.win.* and rarevalues.editor.add/remove in en/it/nl.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { IWheelAdminPrize, IWheelAdminPrizeEdit, IWheelPrize, IWheelRecentWin, WheelAdminGetPrizesComposer, WheelAdminPrizesEvent, WheelAdminSavePrizesComposer, WheelBuySpinComposer, WheelDataEvent, WheelOpenComposer, WheelRecentWinsEvent, WheelResultEvent, WheelSpinComposer } from '@nitrots/nitro-renderer';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { useBetween } from 'use-between';
|
||||
import { SendMessageComposer } from '../../api';
|
||||
import { useMessageEvent } from '../events';
|
||||
@@ -18,6 +18,15 @@ const useFortuneWheelState = () =>
|
||||
const [ isSpinning, setIsSpinning ] = useState(false);
|
||||
const [ adminPrizes, setAdminPrizes ] = useState<IWheelAdminPrize[]>([]);
|
||||
|
||||
// While the wheel is animating we hold back the recent-wins refresh: the
|
||||
// server pushes the updated list (which already contains the just-won
|
||||
// prize) the instant it answers the spin, ~5s before the wheel actually
|
||||
// stops. Showing it immediately would spoil the result in the winners
|
||||
// panel. We buffer it here and flush it in finishSpin (called when the
|
||||
// reveal fires).
|
||||
const spinAnimatingRef = useRef(false);
|
||||
const bufferedWinsRef = useRef<IWheelRecentWin[] | null>(null);
|
||||
|
||||
useMessageEvent<WheelAdminPrizesEvent>(WheelAdminPrizesEvent, event =>
|
||||
{
|
||||
setAdminPrizes(event.getParser().prizes);
|
||||
@@ -35,13 +44,21 @@ const useFortuneWheelState = () =>
|
||||
|
||||
useMessageEvent<WheelResultEvent>(WheelResultEvent, event =>
|
||||
{
|
||||
// Set synchronously before the recent-wins packet (sent right after by
|
||||
// the server) is processed, so its handler knows a spin is animating.
|
||||
spinAnimatingRef.current = true;
|
||||
setPendingPrizeId(event.getParser().prizeId);
|
||||
setIsSpinning(true);
|
||||
});
|
||||
|
||||
useMessageEvent<WheelRecentWinsEvent>(WheelRecentWinsEvent, event =>
|
||||
{
|
||||
setRecentWins(event.getParser().wins);
|
||||
const wins = event.getParser().wins;
|
||||
|
||||
// Mid-spin: stash the refreshed list and reveal it once the wheel
|
||||
// stops. Otherwise (initial open, other refreshes) apply immediately.
|
||||
if(spinAnimatingRef.current) bufferedWinsRef.current = wins;
|
||||
else setRecentWins(wins);
|
||||
});
|
||||
|
||||
const open = useCallback(() => SendMessageComposer(new WheelOpenComposer()), []);
|
||||
@@ -56,8 +73,17 @@ const useFortuneWheelState = () =>
|
||||
const buySpin = useCallback(() => SendMessageComposer(new WheelBuySpinComposer()), []);
|
||||
const finishSpin = useCallback(() =>
|
||||
{
|
||||
spinAnimatingRef.current = false;
|
||||
setIsSpinning(false);
|
||||
setPendingPrizeId(-1);
|
||||
|
||||
// Flush the winners list that arrived during the spin, now that the
|
||||
// reveal has happened.
|
||||
if(bufferedWinsRef.current)
|
||||
{
|
||||
setRecentWins(bufferedWinsRef.current);
|
||||
bufferedWinsRef.current = null;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const loadAdminPrizes = useCallback(() => SendMessageComposer(new WheelAdminGetPrizesComposer()), []);
|
||||
|
||||
Reference in New Issue
Block a user